Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 723f4565

History | View | Annotate | Download (19.2 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 import rapi
31
from ganeti.rapi import baserlib
32

    
33

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

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

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

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

    
65

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

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

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

76
    """
77
    return constants.RAPI_VERSION
78

    
79

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

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

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

    
91

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

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

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

101
    Example: ["debian-etch"]
102

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

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

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

    
116

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

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

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

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

    
134

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

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

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

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

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

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

    
170

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

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

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

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

    
190

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

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

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

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

    
206

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

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

214
    @return: Node role
215

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

    
222
    return _NR_MAP[result[0][0]]
223

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

227
    @return: a job id
228

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

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

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

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

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

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

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

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

    
262
    return baserlib.SubmitJob([op])
263

    
264

    
265
class R_2_nodes_name_evacuate(baserlib.R_Generic):
266
  """/2/nodes/[node_name]/evacuate resource.
267

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

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

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

    
281
    return baserlib.SubmitJob([op])
282

    
283

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

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

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

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

    
297
    return baserlib.SubmitJob([op])
298

    
299

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

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

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

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

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

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

    
325

    
326
class R_2_nodes_name_storage_modify(baserlib.R_Generic):
327
  """/2/nodes/[node_name]/storage/modify ressource.
328

329
  """
330
  def PUT(self):
331
    node_name = self.items[0]
332

    
333
    storage_type = self._checkStringVariable("storage_type", None)
334
    if not storage_type:
335
      raise http.HttpBadRequest("Missing the required 'storage_type'"
336
                                " parameter")
337

    
338
    name = self._checkStringVariable("name", None)
339
    if not name:
340
      raise http.HttpBadRequest("Missing the required 'name'"
341
                                " parameter")
342

    
343
    changes = {}
344

    
345
    if "allocatable" in self.queryargs:
346
      changes[constants.SF_ALLOCATABLE] = \
347
        bool(self._checkIntVariable("allocatable", default=1))
348

    
349
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
350
                                     storage_type=storage_type,
351
                                     name=name,
352
                                     changes=changes)
353
    return baserlib.SubmitJob([op])
354

    
355

    
356
class R_2_nodes_name_storage_repair(baserlib.R_Generic):
357
  """/2/nodes/[node_name]/storage/repair ressource.
358

359
  """
360
  def PUT(self):
361
    node_name = self.items[0]
362

    
363
    storage_type = self._checkStringVariable("storage_type", None)
364
    if not storage_type:
365
      raise http.HttpBadRequest("Missing the required 'storage_type'"
366
                                " parameter")
367

    
368
    name = self._checkStringVariable("name", None)
369
    if not name:
370
      raise http.HttpBadRequest("Missing the required 'name'"
371
                                " parameter")
372

    
373
    op = opcodes.OpRepairNodeStorage(node_name=node_name,
374
                                     storage_type=storage_type,
375
                                     name=name)
376
    return baserlib.SubmitJob([op])
377

    
378

    
379
class R_2_instances(baserlib.R_Generic):
380
  """/2/instances resource.
381

382
  """
383
  def GET(self):
384
    """Returns a list of all available instances.
385

386
    """
387
    client = baserlib.GetClient()
388

    
389
    use_locking = self.useLocking()
390
    if self.useBulk():
391
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
392
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
393
    else:
394
      instancesdata = client.QueryInstances([], ["name"], use_locking)
395
      instanceslist = [row[0] for row in instancesdata]
396
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
397
                                   uri_fields=("id", "uri"))
398

    
399
  def POST(self):
400
    """Create an instance.
401

402
    @return: a job id
403

404
    """
405
    if not isinstance(self.req.request_body, dict):
406
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
407

    
408
    beparams = baserlib.MakeParamsDict(self.req.request_body,
409
                                       constants.BES_PARAMETERS)
410
    hvparams = baserlib.MakeParamsDict(self.req.request_body,
411
                                       constants.HVS_PARAMETERS)
412
    fn = self.getBodyParameter
413

    
414
    # disk processing
415
    disk_data = fn('disks')
416
    if not isinstance(disk_data, list):
417
      raise http.HttpBadRequest("The 'disks' parameter should be a list")
418
    disks = []
419
    for idx, d in enumerate(disk_data):
420
      if not isinstance(d, int):
421
        raise http.HttpBadRequest("Disk %d specification wrong: should"
422
                                  " be an integer")
423
      disks.append({"size": d})
424
    # nic processing (one nic only)
425
    nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
426
    if fn("ip", None) is not None:
427
      nics[0]["ip"] = fn("ip")
428
    if fn("mode", None) is not None:
429
      nics[0]["mode"] = fn("mode")
430
    if fn("link", None) is not None:
431
      nics[0]["link"] = fn("link")
432
    if fn("bridge", None) is not None:
433
       nics[0]["bridge"] = fn("bridge")
434

    
435
    op = opcodes.OpCreateInstance(
436
      mode=constants.INSTANCE_CREATE,
437
      instance_name=fn('name'),
438
      disks=disks,
439
      disk_template=fn('disk_template'),
440
      os_type=fn('os'),
441
      pnode=fn('pnode', None),
442
      snode=fn('snode', None),
443
      iallocator=fn('iallocator', None),
444
      nics=nics,
445
      start=fn('start', True),
446
      ip_check=fn('ip_check', True),
447
      wait_for_sync=True,
448
      hypervisor=fn('hypervisor', None),
449
      hvparams=hvparams,
450
      beparams=beparams,
451
      file_storage_dir=fn('file_storage_dir', None),
452
      file_driver=fn('file_driver', 'loop'),
453
      dry_run=bool(self.dryRun()),
454
      )
455

    
456
    return baserlib.SubmitJob([op])
457

    
458

    
459
class R_2_instances_name(baserlib.R_Generic):
460
  """/2/instances/[instance_name] resources.
461

462
  """
463
  def GET(self):
464
    """Send information about an instance.
465

466
    """
467
    client = baserlib.GetClient()
468
    instance_name = self.items[0]
469
    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
470
                                   use_locking=self.useLocking())
471

    
472
    return baserlib.MapFields(I_FIELDS, result[0])
473

    
474
  def DELETE(self):
475
    """Delete an instance.
476

477
    """
478
    op = opcodes.OpRemoveInstance(instance_name=self.items[0],
479
                                  ignore_failures=False,
480
                                  dry_run=bool(self.dryRun()))
481
    return baserlib.SubmitJob([op])
482

    
483

    
484
class R_2_instances_name_reboot(baserlib.R_Generic):
485
  """/2/instances/[instance_name]/reboot resource.
486

487
  Implements an instance reboot.
488

489
  """
490
  def POST(self):
491
    """Reboot an instance.
492

493
    The URI takes type=[hard|soft|full] and
494
    ignore_secondaries=[False|True] parameters.
495

496
    """
497
    instance_name = self.items[0]
498
    reboot_type = self.queryargs.get('type',
499
                                     [constants.INSTANCE_REBOOT_HARD])[0]
500
    ignore_secondaries = bool(self.queryargs.get('ignore_secondaries',
501
                                                 [False])[0])
502
    op = opcodes.OpRebootInstance(instance_name=instance_name,
503
                                  reboot_type=reboot_type,
504
                                  ignore_secondaries=ignore_secondaries,
505
                                  dry_run=bool(self.dryRun()))
506

    
507
    return baserlib.SubmitJob([op])
508

    
509

    
510
class R_2_instances_name_startup(baserlib.R_Generic):
511
  """/2/instances/[instance_name]/startup resource.
512

513
  Implements an instance startup.
514

515
  """
516
  def PUT(self):
517
    """Startup an instance.
518

519
    The URI takes force=[False|True] parameter to start the instance
520
    if even if secondary disks are failing.
521

522
    """
523
    instance_name = self.items[0]
524
    force_startup = bool(self.queryargs.get('force', [False])[0])
525
    op = opcodes.OpStartupInstance(instance_name=instance_name,
526
                                   force=force_startup,
527
                                   dry_run=bool(self.dryRun()))
528

    
529
    return baserlib.SubmitJob([op])
530

    
531

    
532
class R_2_instances_name_shutdown(baserlib.R_Generic):
533
  """/2/instances/[instance_name]/shutdown resource.
534

535
  Implements an instance shutdown.
536

537
  """
538
  def PUT(self):
539
    """Shutdown an instance.
540

541
    """
542
    instance_name = self.items[0]
543
    op = opcodes.OpShutdownInstance(instance_name=instance_name,
544
                                    dry_run=bool(self.dryRun()))
545

    
546
    return baserlib.SubmitJob([op])
547

    
548

    
549
class R_2_instances_name_reinstall(baserlib.R_Generic):
550
  """/2/instances/[instance_name]/reinstall resource.
551

552
  Implements an instance reinstall.
553

554
  """
555
  def POST(self):
556
    """Reinstall an instance.
557

558
    The URI takes os=name and nostartup=[0|1] optional
559
    parameters. By default, the instance will be started
560
    automatically.
561

562
    """
563
    instance_name = self.items[0]
564
    ostype = self._checkStringVariable('os')
565
    nostartup = self._checkIntVariable('nostartup')
566
    ops = [
567
      opcodes.OpShutdownInstance(instance_name=instance_name),
568
      opcodes.OpReinstallInstance(instance_name=instance_name, os_type=ostype),
569
      ]
570
    if not nostartup:
571
      ops.append(opcodes.OpStartupInstance(instance_name=instance_name,
572
                                           force=False))
573
    return baserlib.SubmitJob(ops)
574

    
575

    
576
class R_2_instances_name_replace_disks(baserlib.R_Generic):
577
  """/2/instances/[instance_name]/replace-disks resource.
578

579
  """
580
  def POST(self):
581
    """Replaces disks on an instance.
582

583
    """
584
    instance_name = self.items[0]
585
    remote_node = self._checkStringVariable("remote_node", default=None)
586
    mode = self._checkStringVariable("mode", default=None)
587
    raw_disks = self._checkStringVariable("disks", default=None)
588
    iallocator = self._checkStringVariable("iallocator", default=None)
589

    
590
    if raw_disks:
591
      try:
592
        disks = [int(part) for part in raw_disks.split(",")]
593
      except ValueError, err:
594
        raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
595
    else:
596
      disks = []
597

    
598
    op = opcodes.OpReplaceDisks(instance_name=instance_name,
599
                                remote_node=remote_node,
600
                                mode=mode,
601
                                disks=disks,
602
                                iallocator=iallocator)
603

    
604
    return baserlib.SubmitJob([op])
605

    
606

    
607
class _R_Tags(baserlib.R_Generic):
608
  """ Quasiclass for tagging resources
609

610
  Manages tags. When inheriting this class you must define the
611
  TAG_LEVEL for it.
612

613
  """
614
  TAG_LEVEL = None
615

    
616
  def __init__(self, items, queryargs, req):
617
    """A tag resource constructor.
618

619
    We have to override the default to sort out cluster naming case.
620

621
    """
622
    baserlib.R_Generic.__init__(self, items, queryargs, req)
623

    
624
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
625
      self.name = items[0]
626
    else:
627
      self.name = ""
628

    
629
  def GET(self):
630
    """Returns a list of tags.
631

632
    Example: ["tag1", "tag2", "tag3"]
633

634
    """
635
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
636

    
637
  def PUT(self):
638
    """Add a set of tags.
639

640
    The request as a list of strings should be PUT to this URI. And
641
    you'll have back a job id.
642

643
    """
644
    if 'tag' not in self.queryargs:
645
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
646
                                " the 'tag' parameter")
647
    return baserlib._Tags_PUT(self.TAG_LEVEL,
648
                              self.queryargs['tag'], name=self.name,
649
                              dry_run=bool(self.dryRun()))
650

    
651
  def DELETE(self):
652
    """Delete a tag.
653

654
    In order to delete a set of tags, the DELETE
655
    request should be addressed to URI like:
656
    /tags?tag=[tag]&tag=[tag]
657

658
    """
659
    if 'tag' not in self.queryargs:
660
      # no we not gonna delete all tags
661
      raise http.HttpBadRequest("Cannot delete all tags - please specify"
662
                                " tag(s) using the 'tag' parameter")
663
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
664
                                 self.queryargs['tag'],
665
                                 name=self.name,
666
                                 dry_run=bool(self.dryRun()))
667

    
668

    
669
class R_2_instances_name_tags(_R_Tags):
670
  """ /2/instances/[instance_name]/tags resource.
671

672
  Manages per-instance tags.
673

674
  """
675
  TAG_LEVEL = constants.TAG_INSTANCE
676

    
677

    
678
class R_2_nodes_name_tags(_R_Tags):
679
  """ /2/nodes/[node_name]/tags resource.
680

681
  Manages per-node tags.
682

683
  """
684
  TAG_LEVEL = constants.TAG_NODE
685

    
686

    
687
class R_2_tags(_R_Tags):
688
  """ /2/instances/tags resource.
689

690
  Manages cluster tags.
691

692
  """
693
  TAG_LEVEL = constants.TAG_CLUSTER