Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 7a95a954

History | View | Annotate | Download (16.6 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_nodes_name_evacuate(baserlib.R_Generic):
265
  """/2/nodes/[node_name]/evacuate resource.
266

267
  """
268
  def POST(self):
269
    """Evacuate all secondary instances off a node.
270

271
    """
272
    node_name = self.items[0]
273
    remote_node = self._checkStringVariable("remote_node", default=None)
274
    iallocator = self._checkStringVariable("iallocator", default=None)
275

    
276
    op = opcodes.OpEvacuateNode(node_name=node_name,
277
                                remote_node=remote_node,
278
                                iallocator=iallocator)
279

    
280
    return baserlib.SubmitJob([op])
281

    
282

    
283
class R_2_nodes_name_migrate(baserlib.R_Generic):
284
  """/2/nodes/[node_name]/migrate resource.
285

286
  """
287
  def POST(self):
288
    """Migrate all primary instances from a node.
289

290
    """
291
    node_name = self.items[0]
292
    live = bool(self._checkIntVariable("live", default=1))
293

    
294
    op = opcodes.OpMigrateNode(node_name=node_name, live=live)
295

    
296
    return baserlib.SubmitJob([op])
297

    
298

    
299
class R_2_nodes_name_storage(baserlib.R_Generic):
300
  """/2/nodes/[node_name]/storage ressource.
301

302
  """
303
  # LUQueryNodeStorage acquires locks, hence restricting access to GET
304
  GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
305

    
306
  def GET(self):
307
    node_name = self.items[0]
308

    
309
    storage_type = self._checkStringVariable("storage_type", None)
310
    if not storage_type:
311
      raise http.HttpBadRequest("Missing the required 'storage_type'"
312
                                " parameter")
313

    
314
    output_fields = self._checkStringVariable("output_fields", None)
315
    if not output_fields:
316
      raise http.HttpBadRequest("Missing the required 'output_fields'"
317
                                " parameter")
318

    
319
    op = opcodes.OpQueryNodeStorage(nodes=[node_name],
320
                                    storage_type=storage_type,
321
                                    output_fields=output_fields.split(","))
322
    return baserlib.SubmitJob([op])
323

    
324

    
325
class R_2_instances(baserlib.R_Generic):
326
  """/2/instances resource.
327

328
  """
329
  def GET(self):
330
    """Returns a list of all available instances.
331

332
    """
333
    client = baserlib.GetClient()
334

    
335
    use_locking = self.useLocking()
336
    if self.useBulk():
337
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
338
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
339
    else:
340
      instancesdata = client.QueryInstances([], ["name"], use_locking)
341
      instanceslist = [row[0] for row in instancesdata]
342
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
343
                                   uri_fields=("id", "uri"))
344

    
345
  def POST(self):
346
    """Create an instance.
347

348
    @return: a job id
349

350
    """
351
    if not isinstance(self.req.request_body, dict):
352
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
353

    
354
    beparams = baserlib.MakeParamsDict(self.req.request_body,
355
                                       constants.BES_PARAMETERS)
356
    hvparams = baserlib.MakeParamsDict(self.req.request_body,
357
                                       constants.HVS_PARAMETERS)
358
    fn = self.getBodyParameter
359

    
360
    # disk processing
361
    disk_data = fn('disks')
362
    if not isinstance(disk_data, list):
363
      raise http.HttpBadRequest("The 'disks' parameter should be a list")
364
    disks = []
365
    for idx, d in enumerate(disk_data):
366
      if not isinstance(d, int):
367
        raise http.HttpBadRequest("Disk %d specification wrong: should"
368
                                  " be an integer")
369
      disks.append({"size": d})
370
    # nic processing (one nic only)
371
    nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
372
    if fn("ip", None) is not None:
373
      nics[0]["ip"] = fn("ip")
374
    if fn("mode", None) is not None:
375
      nics[0]["mode"] = fn("mode")
376
    if fn("link", None) is not None:
377
      nics[0]["link"] = fn("link")
378
    if fn("bridge", None) is not None:
379
       nics[0]["bridge"] = fn("bridge")
380

    
381
    op = opcodes.OpCreateInstance(
382
      mode=constants.INSTANCE_CREATE,
383
      instance_name=fn('name'),
384
      disks=disks,
385
      disk_template=fn('disk_template'),
386
      os_type=fn('os'),
387
      pnode=fn('pnode', None),
388
      snode=fn('snode', None),
389
      iallocator=fn('iallocator', None),
390
      nics=nics,
391
      start=fn('start', True),
392
      ip_check=fn('ip_check', True),
393
      wait_for_sync=True,
394
      hypervisor=fn('hypervisor', None),
395
      hvparams=hvparams,
396
      beparams=beparams,
397
      file_storage_dir=fn('file_storage_dir', None),
398
      file_driver=fn('file_driver', 'loop'),
399
      dry_run=bool(self.dryRun()),
400
      )
401

    
402
    return baserlib.SubmitJob([op])
403

    
404

    
405
class R_2_instances_name(baserlib.R_Generic):
406
  """/2/instances/[instance_name] resources.
407

408
  """
409
  def GET(self):
410
    """Send information about an instance.
411

412
    """
413
    client = baserlib.GetClient()
414
    instance_name = self.items[0]
415
    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
416
                                   use_locking=self.useLocking())
417

    
418
    return baserlib.MapFields(I_FIELDS, result[0])
419

    
420
  def DELETE(self):
421
    """Delete an instance.
422

423
    """
424
    op = opcodes.OpRemoveInstance(instance_name=self.items[0],
425
                                  ignore_failures=False,
426
                                  dry_run=bool(self.dryRun()))
427
    return baserlib.SubmitJob([op])
428

    
429

    
430
class R_2_instances_name_reboot(baserlib.R_Generic):
431
  """/2/instances/[instance_name]/reboot resource.
432

433
  Implements an instance reboot.
434

435
  """
436
  def POST(self):
437
    """Reboot an instance.
438

439
    The URI takes type=[hard|soft|full] and
440
    ignore_secondaries=[False|True] parameters.
441

442
    """
443
    instance_name = self.items[0]
444
    reboot_type = self.queryargs.get('type',
445
                                     [constants.INSTANCE_REBOOT_HARD])[0]
446
    ignore_secondaries = bool(self.queryargs.get('ignore_secondaries',
447
                                                 [False])[0])
448
    op = opcodes.OpRebootInstance(instance_name=instance_name,
449
                                  reboot_type=reboot_type,
450
                                  ignore_secondaries=ignore_secondaries,
451
                                  dry_run=bool(self.dryRun()))
452

    
453
    return baserlib.SubmitJob([op])
454

    
455

    
456
class R_2_instances_name_startup(baserlib.R_Generic):
457
  """/2/instances/[instance_name]/startup resource.
458

459
  Implements an instance startup.
460

461
  """
462
  def PUT(self):
463
    """Startup an instance.
464

465
    The URI takes force=[False|True] parameter to start the instance
466
    if even if secondary disks are failing.
467

468
    """
469
    instance_name = self.items[0]
470
    force_startup = bool(self.queryargs.get('force', [False])[0])
471
    op = opcodes.OpStartupInstance(instance_name=instance_name,
472
                                   force=force_startup,
473
                                   dry_run=bool(self.dryRun()))
474

    
475
    return baserlib.SubmitJob([op])
476

    
477

    
478
class R_2_instances_name_shutdown(baserlib.R_Generic):
479
  """/2/instances/[instance_name]/shutdown resource.
480

481
  Implements an instance shutdown.
482

483
  """
484
  def PUT(self):
485
    """Shutdown an instance.
486

487
    """
488
    instance_name = self.items[0]
489
    op = opcodes.OpShutdownInstance(instance_name=instance_name,
490
                                    dry_run=bool(self.dryRun()))
491

    
492
    return baserlib.SubmitJob([op])
493

    
494

    
495
class R_2_instances_name_reinstall(baserlib.R_Generic):
496
  """/2/instances/[instance_name]/reinstall resource.
497

498
  Implements an instance reinstall.
499

500
  """
501

    
502
  DOC_URI = "/2/instances/[instance_name]/reinstall"
503

    
504
  def POST(self):
505
    """Reinstall an instance.
506

507
    The URI takes os=name and nostartup=[0|1] optional
508
    parameters. By default, the instance will be started
509
    automatically.
510

511
    """
512
    instance_name = self.items[0]
513
    ostype = self._checkStringVariable('os')
514
    nostartup = self._checkIntVariable('nostartup')
515
    ops = [
516
      opcodes.OpShutdownInstance(instance_name=instance_name),
517
      opcodes.OpReinstallInstance(instance_name=instance_name, os_type=ostype),
518
      ]
519
    if not nostartup:
520
      ops.append(opcodes.OpStartupInstance(instance_name=instance_name,
521
                                           force=False))
522
    return baserlib.SubmitJob(ops)
523

    
524

    
525
class _R_Tags(baserlib.R_Generic):
526
  """ Quasiclass for tagging resources
527

528
  Manages tags. When inheriting this class you must define the
529
  TAG_LEVEL for it.
530

531
  """
532
  TAG_LEVEL = None
533

    
534
  def __init__(self, items, queryargs, req):
535
    """A tag resource constructor.
536

537
    We have to override the default to sort out cluster naming case.
538

539
    """
540
    baserlib.R_Generic.__init__(self, items, queryargs, req)
541

    
542
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
543
      self.name = items[0]
544
    else:
545
      self.name = ""
546

    
547
  def GET(self):
548
    """Returns a list of tags.
549

550
    Example: ["tag1", "tag2", "tag3"]
551

552
    """
553
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
554

    
555
  def PUT(self):
556
    """Add a set of tags.
557

558
    The request as a list of strings should be PUT to this URI. And
559
    you'll have back a job id.
560

561
    """
562
    if 'tag' not in self.queryargs:
563
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
564
                                " the 'tag' parameter")
565
    return baserlib._Tags_PUT(self.TAG_LEVEL,
566
                              self.queryargs['tag'], name=self.name,
567
                              dry_run=bool(self.dryRun()))
568

    
569
  def DELETE(self):
570
    """Delete a tag.
571

572
    In order to delete a set of tags, the DELETE
573
    request should be addressed to URI like:
574
    /tags?tag=[tag]&tag=[tag]
575

576
    """
577
    if 'tag' not in self.queryargs:
578
      # no we not gonna delete all tags
579
      raise http.HttpBadRequest("Cannot delete all tags - please specify"
580
                                " tag(s) using the 'tag' parameter")
581
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
582
                                 self.queryargs['tag'],
583
                                 name=self.name,
584
                                 dry_run=bool(self.dryRun()))
585

    
586

    
587
class R_2_instances_name_tags(_R_Tags):
588
  """ /2/instances/[instance_name]/tags resource.
589

590
  Manages per-instance tags.
591

592
  """
593
  TAG_LEVEL = constants.TAG_INSTANCE
594

    
595

    
596
class R_2_nodes_name_tags(_R_Tags):
597
  """ /2/nodes/[node_name]/tags resource.
598

599
  Manages per-node tags.
600

601
  """
602
  TAG_LEVEL = constants.TAG_NODE
603

    
604

    
605
class R_2_tags(_R_Tags):
606
  """ /2/instances/tags resource.
607

608
  Manages cluster tags.
609

610
  """
611
  TAG_LEVEL = constants.TAG_CLUSTER