Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 64dae8fc

History | View | Annotate | Download (13.9 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Remote API version 2 baserlib.library.
23

24
"""
25

    
26
from ganeti import opcodes
27
from ganeti import http
28
from ganeti import constants
29
from ganeti import cli
30
from ganeti.rapi import baserlib
31

    
32

    
33
I_FIELDS = ["name", "admin_state", "os",
34
            "pnode", "snodes",
35
            "disk_template",
36
            "nic.ips", "nic.macs", "nic.modes", "nic.links",
37
            "network_port",
38
            "disk.sizes", "disk_usage",
39
            "beparams", "hvparams",
40
            "oper_state", "oper_ram", "status",
41
            "tags"]
42

    
43
N_FIELDS = ["name", "offline", "master_candidate", "drained",
44
            "dtotal", "dfree",
45
            "mtotal", "mnode", "mfree",
46
            "pinst_cnt", "sinst_cnt", "tags",
47
            "ctotal", "cnodes", "csockets",
48
            ]
49

    
50
_NR_DRAINED = "drained"
51
_NR_MASTER_CANDIATE = "master-candidate"
52
_NR_MASTER = "master"
53
_NR_OFFLINE = "offline"
54
_NR_REGULAR = "regular"
55

    
56
_NR_MAP = {
57
  "M": _NR_MASTER,
58
  "C": _NR_MASTER_CANDIATE,
59
  "D": _NR_DRAINED,
60
  "O": _NR_OFFLINE,
61
  "R": _NR_REGULAR,
62
  }
63

    
64

    
65
class R_version(baserlib.R_Generic):
66
  """/version resource.
67

68
  This resource should be used to determine the remote API version and
69
  to adapt clients accordingly.
70

71
  """
72
  def GET(self):
73
    """Returns the remote API version.
74

75
    """
76
    return constants.RAPI_VERSION
77

    
78

    
79
class R_2_info(baserlib.R_Generic):
80
  """Cluster info.
81

82
  """
83
  def GET(self):
84
    """Returns cluster information.
85

86
    """
87
    client = baserlib.GetClient()
88
    return client.QueryClusterInfo()
89

    
90

    
91
class R_2_os(baserlib.R_Generic):
92
  """/2/os resource.
93

94
  """
95
  def GET(self):
96
    """Return a list of all OSes.
97

98
    Can return error 500 in case of a problem.
99

100
    Example: ["debian-etch"]
101

102
    """
103
    cl = baserlib.GetClient()
104
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
105
    job_id = baserlib.SubmitJob([op], cl)
106
    # we use custom feedback function, instead of print we log the status
107
    result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
108
    diagnose_data = result[0]
109

    
110
    if not isinstance(diagnose_data, list):
111
      raise http.HttpBadGateway(message="Can't get OS list")
112

    
113
    return [row[0] for row in diagnose_data if row[1]]
114

    
115

    
116
class R_2_jobs(baserlib.R_Generic):
117
  """/2/jobs resource.
118

119
  """
120
  def GET(self):
121
    """Returns a dictionary of jobs.
122

123
    @return: a dictionary with jobs id and uri.
124

125
    """
126
    fields = ["id"]
127
    cl = baserlib.GetClient()
128
    # Convert the list of lists to the list of ids
129
    result = [job_id for [job_id] in cl.QueryJobs(None, fields)]
130
    return baserlib.BuildUriList(result, "/2/jobs/%s",
131
                                 uri_fields=("id", "uri"))
132

    
133

    
134
class R_2_jobs_id(baserlib.R_Generic):
135
  """/2/jobs/[job_id] resource.
136

137
  """
138
  def GET(self):
139
    """Returns a job status.
140

141
    @return: a dictionary with job parameters.
142
        The result includes:
143
            - id: job ID as a number
144
            - status: current job status as a string
145
            - ops: involved OpCodes as a list of dictionaries for each
146
              opcodes in the job
147
            - opstatus: OpCodes status as a list
148
            - opresult: OpCodes results as a list of lists
149

150
    """
151
    fields = ["id", "ops", "status", "summary",
152
              "opstatus", "opresult", "oplog",
153
              "received_ts", "start_ts", "end_ts",
154
              ]
155
    job_id = self.items[0]
156
    result = baserlib.GetClient().QueryJobs([job_id, ], fields)[0]
157
    if result is None:
158
      raise http.HttpNotFound()
159
    return baserlib.MapFields(fields, result)
160

    
161
  def DELETE(self):
162
    """Cancel not-yet-started job.
163

164
    """
165
    job_id = self.items[0]
166
    result = baserlib.GetClient().CancelJob(job_id)
167
    return result
168

    
169

    
170
class R_2_nodes(baserlib.R_Generic):
171
  """/2/nodes resource.
172

173
  """
174
  def GET(self):
175
    """Returns a list of all nodes.
176

177
    """
178
    client = baserlib.GetClient()
179

    
180
    if self.useBulk():
181
      bulkdata = client.QueryNodes([], N_FIELDS, False)
182
      return baserlib.MapBulkFields(bulkdata, N_FIELDS)
183
    else:
184
      nodesdata = client.QueryNodes([], ["name"], False)
185
      nodeslist = [row[0] for row in nodesdata]
186
      return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
187
                                   uri_fields=("id", "uri"))
188

    
189

    
190
class R_2_nodes_name(baserlib.R_Generic):
191
  """/2/nodes/[node_name] resources.
192

193
  """
194
  def GET(self):
195
    """Send information about a node.
196

197
    """
198
    node_name = self.items[0]
199
    client = baserlib.GetClient()
200
    result = client.QueryNodes(names=[node_name], fields=N_FIELDS,
201
                               use_locking=self.useLocking())
202

    
203
    return baserlib.MapFields(N_FIELDS, result[0])
204

    
205

    
206
class R_2_nodes_name_role(baserlib.R_Generic):
207
  """ /2/nodes/[node_name]/role resource.
208

209
  """
210
  def GET(self):
211
    """Returns the current node role.
212

213
    @return: Node role
214

215
    """
216
    node_name = self.items[0]
217
    client = baserlib.GetClient()
218
    result = client.QueryNodes(names=[node_name], fields=["role"],
219
                               use_locking=self.useLocking())
220

    
221
    return _NR_MAP[result[0][0]]
222

    
223
  def PUT(self):
224
    """Sets the node role.
225

226
    @return: a job id
227

228
    """
229
    if not isinstance(self.req.request_body, basestring):
230
      raise http.HttpBadRequest("Invalid body contents, not a string")
231

    
232
    node_name = self.items[0]
233
    role = self.req.request_body
234

    
235
    if role == _NR_REGULAR:
236
      candidate = False
237
      offline = False
238
      drained = False
239

    
240
    elif role == _NR_MASTER_CANDIATE:
241
      candidate = True
242
      offline = drained = None
243

    
244
    elif role == _NR_DRAINED:
245
      drained = True
246
      candidate = offline = None
247

    
248
    elif role == _NR_OFFLINE:
249
      offline = True
250
      candidate = drained = None
251

    
252
    else:
253
      raise http.HttpBadRequest("Can't set '%s' role" % role)
254

    
255
    op = opcodes.OpSetNodeParams(node_name=node_name,
256
                                 master_candidate=candidate,
257
                                 offline=offline,
258
                                 drained=drained,
259
                                 force=bool(self.useForce()))
260

    
261
    return baserlib.SubmitJob([op])
262

    
263

    
264
class R_2_instances(baserlib.R_Generic):
265
  """/2/instances resource.
266

267
  """
268
  def GET(self):
269
    """Returns a list of all available instances.
270

271
    """
272
    client = baserlib.GetClient()
273

    
274
    use_locking = self.useLocking()
275
    if self.useBulk():
276
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
277
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
278
    else:
279
      instancesdata = client.QueryInstances([], ["name"], use_locking)
280
      instanceslist = [row[0] for row in instancesdata]
281
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
282
                                   uri_fields=("id", "uri"))
283

    
284
  def POST(self):
285
    """Create an instance.
286

287
    @return: a job id
288

289
    """
290
    if not isinstance(self.req.request_body, dict):
291
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
292

    
293
    beparams = baserlib.MakeParamsDict(self.req.request_body,
294
                                       constants.BES_PARAMETERS)
295
    hvparams = baserlib.MakeParamsDict(self.req.request_body,
296
                                       constants.HVS_PARAMETERS)
297
    fn = self.getBodyParameter
298

    
299
    # disk processing
300
    disk_data = fn('disks')
301
    if not isinstance(disk_data, list):
302
      raise http.HttpBadRequest("The 'disks' parameter should be a list")
303
    disks = []
304
    for idx, d in enumerate(disk_data):
305
      if not isinstance(d, int):
306
        raise http.HttpBadRequest("Disk %d specification wrong: should"
307
                                  " be an integer")
308
      disks.append({"size": d})
309
    # nic processing (one nic only)
310
    nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
311
    if fn("ip", None) is not None:
312
      nics[0]["ip"] = fn("ip")
313
    if fn("mode", None) is not None:
314
      nics[0]["mode"] = fn("mode")
315
    if fn("link", None) is not None:
316
      nics[0]["link"] = fn("link")
317
    if fn("bridge", None) is not None:
318
       nics[0]["bridge"] = fn("bridge")
319

    
320
    op = opcodes.OpCreateInstance(
321
      mode=constants.INSTANCE_CREATE,
322
      instance_name=fn('name'),
323
      disks=disks,
324
      disk_template=fn('disk_template'),
325
      os_type=fn('os'),
326
      pnode=fn('pnode', None),
327
      snode=fn('snode', None),
328
      iallocator=fn('iallocator', None),
329
      nics=nics,
330
      start=fn('start', True),
331
      ip_check=fn('ip_check', True),
332
      wait_for_sync=True,
333
      hypervisor=fn('hypervisor', None),
334
      hvparams=hvparams,
335
      beparams=beparams,
336
      file_storage_dir=fn('file_storage_dir', None),
337
      file_driver=fn('file_driver', 'loop'),
338
      dry_run=bool(self.dryRun()),
339
      )
340

    
341
    return baserlib.SubmitJob([op])
342

    
343

    
344
class R_2_instances_name(baserlib.R_Generic):
345
  """/2/instances/[instance_name] resources.
346

347
  """
348
  def GET(self):
349
    """Send information about an instance.
350

351
    """
352
    client = baserlib.GetClient()
353
    instance_name = self.items[0]
354
    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
355
                                   use_locking=self.useLocking())
356

    
357
    return baserlib.MapFields(I_FIELDS, result[0])
358

    
359
  def DELETE(self):
360
    """Delete an instance.
361

362
    """
363
    op = opcodes.OpRemoveInstance(instance_name=self.items[0],
364
                                  ignore_failures=False,
365
                                  dry_run=bool(self.dryRun()))
366
    return baserlib.SubmitJob([op])
367

    
368

    
369
class R_2_instances_name_reboot(baserlib.R_Generic):
370
  """/2/instances/[instance_name]/reboot resource.
371

372
  Implements an instance reboot.
373

374
  """
375
  def POST(self):
376
    """Reboot an instance.
377

378
    The URI takes type=[hard|soft|full] and
379
    ignore_secondaries=[False|True] parameters.
380

381
    """
382
    instance_name = self.items[0]
383
    reboot_type = self.queryargs.get('type',
384
                                     [constants.INSTANCE_REBOOT_HARD])[0]
385
    ignore_secondaries = bool(self.queryargs.get('ignore_secondaries',
386
                                                 [False])[0])
387
    op = opcodes.OpRebootInstance(instance_name=instance_name,
388
                                  reboot_type=reboot_type,
389
                                  ignore_secondaries=ignore_secondaries,
390
                                  dry_run=bool(self.dryRun()))
391

    
392
    return baserlib.SubmitJob([op])
393

    
394

    
395
class R_2_instances_name_startup(baserlib.R_Generic):
396
  """/2/instances/[instance_name]/startup resource.
397

398
  Implements an instance startup.
399

400
  """
401
  def PUT(self):
402
    """Startup an instance.
403

404
    The URI takes force=[False|True] parameter to start the instance
405
    if even if secondary disks are failing.
406

407
    """
408
    instance_name = self.items[0]
409
    force_startup = bool(self.queryargs.get('force', [False])[0])
410
    op = opcodes.OpStartupInstance(instance_name=instance_name,
411
                                   force=force_startup,
412
                                   dry_run=bool(self.dryRun()))
413

    
414
    return baserlib.SubmitJob([op])
415

    
416

    
417
class R_2_instances_name_shutdown(baserlib.R_Generic):
418
  """/2/instances/[instance_name]/shutdown resource.
419

420
  Implements an instance shutdown.
421

422
  """
423
  def PUT(self):
424
    """Shutdown an instance.
425

426
    """
427
    instance_name = self.items[0]
428
    op = opcodes.OpShutdownInstance(instance_name=instance_name,
429
                                    dry_run=bool(self.dryRun()))
430

    
431
    return baserlib.SubmitJob([op])
432

    
433

    
434
class _R_Tags(baserlib.R_Generic):
435
  """ Quasiclass for tagging resources
436

437
  Manages tags. When inheriting this class you must define the
438
  TAG_LEVEL for it.
439

440
  """
441
  TAG_LEVEL = None
442

    
443
  def __init__(self, items, queryargs, req):
444
    """A tag resource constructor.
445

446
    We have to override the default to sort out cluster naming case.
447

448
    """
449
    baserlib.R_Generic.__init__(self, items, queryargs, req)
450

    
451
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
452
      self.name = items[0]
453
    else:
454
      self.name = ""
455

    
456
  def GET(self):
457
    """Returns a list of tags.
458

459
    Example: ["tag1", "tag2", "tag3"]
460

461
    """
462
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
463

    
464
  def PUT(self):
465
    """Add a set of tags.
466

467
    The request as a list of strings should be PUT to this URI. And
468
    you'll have back a job id.
469

470
    """
471
    if 'tag' not in self.queryargs:
472
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
473
                                " the 'tag' parameter")
474
    return baserlib._Tags_PUT(self.TAG_LEVEL,
475
                              self.queryargs['tag'], name=self.name,
476
                              dry_run=bool(self.dryRun()))
477

    
478
  def DELETE(self):
479
    """Delete a tag.
480

481
    In order to delete a set of tags, the DELETE
482
    request should be addressed to URI like:
483
    /tags?tag=[tag]&tag=[tag]
484

485
    """
486
    if 'tag' not in self.queryargs:
487
      # no we not gonna delete all tags
488
      raise http.HttpBadRequest("Cannot delete all tags - please specify"
489
                                " tag(s) using the 'tag' parameter")
490
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
491
                                 self.queryargs['tag'],
492
                                 name=self.name,
493
                                 dry_run=bool(self.dryRun()))
494

    
495

    
496
class R_2_instances_name_tags(_R_Tags):
497
  """ /2/instances/[instance_name]/tags resource.
498

499
  Manages per-instance tags.
500

501
  """
502
  TAG_LEVEL = constants.TAG_INSTANCE
503

    
504

    
505
class R_2_nodes_name_tags(_R_Tags):
506
  """ /2/nodes/[node_name]/tags resource.
507

508
  Manages per-node tags.
509

510
  """
511
  TAG_LEVEL = constants.TAG_NODE
512

    
513

    
514
class R_2_tags(_R_Tags):
515
  """ /2/instances/tags resource.
516

517
  Manages cluster tags.
518

519
  """
520
  TAG_LEVEL = constants.TAG_CLUSTER