Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 793a8f7c

History | View | Annotate | Download (23.4 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
  PUT or POST?
25
  ============
26

27
  According to RFC2616 the main difference between PUT and POST is that
28
  POST can create new resources but PUT can only create the resource the
29
  URI was pointing to on the PUT request.
30

31
  To be in context of this module for instance creation POST on
32
  /2/instances is legitim while PUT would be not, due to it does create a
33
  new entity and not just replace /2/instances with it.
34

35
  So when adding new methods, if they are operating on the URI entity itself,
36
  PUT should be prefered over POST.
37

38
"""
39

    
40
# pylint: disable-msg=C0103
41

    
42
# C0103: Invalid name, since the R_* names are not conforming
43

    
44
from ganeti import opcodes
45
from ganeti import http
46
from ganeti import constants
47
from ganeti import cli
48
from ganeti import rapi
49
from ganeti.rapi import baserlib
50

    
51

    
52
_COMMON_FIELDS = ["ctime", "mtime", "uuid", "serial_no", "tags"]
53
I_FIELDS = ["name", "admin_state", "os",
54
            "pnode", "snodes",
55
            "disk_template",
56
            "nic.ips", "nic.macs", "nic.modes", "nic.links", "nic.bridges",
57
            "network_port",
58
            "disk.sizes", "disk_usage",
59
            "beparams", "hvparams",
60
            "oper_state", "oper_ram", "status",
61
            ] + _COMMON_FIELDS
62

    
63
N_FIELDS = ["name", "offline", "master_candidate", "drained",
64
            "dtotal", "dfree",
65
            "mtotal", "mnode", "mfree",
66
            "pinst_cnt", "sinst_cnt",
67
            "ctotal", "cnodes", "csockets",
68
            "pip", "sip", "role",
69
            "pinst_list", "sinst_list",
70
            ] + _COMMON_FIELDS
71

    
72
_NR_DRAINED = "drained"
73
_NR_MASTER_CANDIATE = "master-candidate"
74
_NR_MASTER = "master"
75
_NR_OFFLINE = "offline"
76
_NR_REGULAR = "regular"
77

    
78
_NR_MAP = {
79
  "M": _NR_MASTER,
80
  "C": _NR_MASTER_CANDIATE,
81
  "D": _NR_DRAINED,
82
  "O": _NR_OFFLINE,
83
  "R": _NR_REGULAR,
84
  }
85

    
86
# Timeout for /2/jobs/[job_id]/wait. Gives job up to 10 seconds to change.
87
_WFJC_TIMEOUT = 10
88

    
89

    
90
class R_version(baserlib.R_Generic):
91
  """/version resource.
92

93
  This resource should be used to determine the remote API version and
94
  to adapt clients accordingly.
95

96
  """
97
  @staticmethod
98
  def GET():
99
    """Returns the remote API version.
100

101
    """
102
    return constants.RAPI_VERSION
103

    
104

    
105
class R_2_info(baserlib.R_Generic):
106
  """Cluster info.
107

108
  """
109
  @staticmethod
110
  def GET():
111
    """Returns cluster information.
112

113
    """
114
    client = baserlib.GetClient()
115
    return client.QueryClusterInfo()
116

    
117

    
118
class R_2_os(baserlib.R_Generic):
119
  """/2/os resource.
120

121
  """
122
  @staticmethod
123
  def GET():
124
    """Return a list of all OSes.
125

126
    Can return error 500 in case of a problem.
127

128
    Example: ["debian-etch"]
129

130
    """
131
    cl = baserlib.GetClient()
132
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants"],
133
                              names=[])
134
    job_id = baserlib.SubmitJob([op], cl)
135
    # we use custom feedback function, instead of print we log the status
136
    result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
137
    diagnose_data = result[0]
138

    
139
    if not isinstance(diagnose_data, list):
140
      raise http.HttpBadGateway(message="Can't get OS list")
141

    
142
    os_names = []
143
    for (name, valid, variants) in diagnose_data:
144
      if valid:
145
        os_names.extend(cli.CalculateOSNames(name, variants))
146

    
147
    return os_names
148

    
149

    
150
class R_2_redist_config(baserlib.R_Generic):
151
  """/2/redistribute-config resource.
152

153
  """
154
  @staticmethod
155
  def PUT():
156
    """Redistribute configuration to all nodes.
157

158
    """
159
    return baserlib.SubmitJob([opcodes.OpRedistributeConfig()])
160

    
161

    
162
class R_2_jobs(baserlib.R_Generic):
163
  """/2/jobs resource.
164

165
  """
166
  @staticmethod
167
  def GET():
168
    """Returns a dictionary of jobs.
169

170
    @return: a dictionary with jobs id and uri.
171

172
    """
173
    fields = ["id"]
174
    cl = baserlib.GetClient()
175
    # Convert the list of lists to the list of ids
176
    result = [job_id for [job_id] in cl.QueryJobs(None, fields)]
177
    return baserlib.BuildUriList(result, "/2/jobs/%s",
178
                                 uri_fields=("id", "uri"))
179

    
180

    
181
class R_2_jobs_id(baserlib.R_Generic):
182
  """/2/jobs/[job_id] resource.
183

184
  """
185
  def GET(self):
186
    """Returns a job status.
187

188
    @return: a dictionary with job parameters.
189
        The result includes:
190
            - id: job ID as a number
191
            - status: current job status as a string
192
            - ops: involved OpCodes as a list of dictionaries for each
193
              opcodes in the job
194
            - opstatus: OpCodes status as a list
195
            - opresult: OpCodes results as a list of lists
196

197
    """
198
    fields = ["id", "ops", "status", "summary",
199
              "opstatus", "opresult", "oplog",
200
              "received_ts", "start_ts", "end_ts",
201
              ]
202
    job_id = self.items[0]
203
    result = baserlib.GetClient().QueryJobs([job_id, ], fields)[0]
204
    if result is None:
205
      raise http.HttpNotFound()
206
    return baserlib.MapFields(fields, result)
207

    
208
  def DELETE(self):
209
    """Cancel not-yet-started job.
210

211
    """
212
    job_id = self.items[0]
213
    result = baserlib.GetClient().CancelJob(job_id)
214
    return result
215

    
216

    
217
class R_2_jobs_id_wait(baserlib.R_Generic):
218
  """/2/jobs/[job_id]/wait resource.
219

220
  """
221
  # WaitForJobChange provides access to sensitive information and blocks
222
  # machine resources (it's a blocking RAPI call), hence restricting access.
223
  GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
224

    
225
  def GET(self):
226
    """Waits for job changes.
227

228
    """
229
    job_id = self.items[0]
230

    
231
    fields = self.getBodyParameter("fields")
232
    prev_job_info = self.getBodyParameter("previous_job_info", None)
233
    prev_log_serial = self.getBodyParameter("previous_log_serial", None)
234

    
235
    if not isinstance(fields, list):
236
      raise http.HttpBadRequest("The 'fields' parameter should be a list")
237

    
238
    if not (prev_job_info is None or isinstance(prev_job_info, list)):
239
      raise http.HttpBadRequest("The 'previous_job_info' parameter should"
240
                                " be a list")
241

    
242
    if not (prev_log_serial is None or
243
            isinstance(prev_log_serial, (int, long))):
244
      raise http.HttpBadRequest("The 'previous_log_serial' parameter should"
245
                                " be a number")
246

    
247
    client = baserlib.GetClient()
248
    result = client.WaitForJobChangeOnce(job_id, fields,
249
                                         prev_job_info, prev_log_serial,
250
                                         timeout=_WFJC_TIMEOUT)
251
    if not result:
252
      raise http.HttpNotFound()
253

    
254
    if result == constants.JOB_NOTCHANGED:
255
      # No changes
256
      return None
257

    
258
    (job_info, log_entries) = result
259

    
260
    return {
261
      "job_info": job_info,
262
      "log_entries": log_entries,
263
      }
264

    
265

    
266
class R_2_nodes(baserlib.R_Generic):
267
  """/2/nodes resource.
268

269
  """
270
  def GET(self):
271
    """Returns a list of all nodes.
272

273
    """
274
    client = baserlib.GetClient()
275

    
276
    if self.useBulk():
277
      bulkdata = client.QueryNodes([], N_FIELDS, False)
278
      return baserlib.MapBulkFields(bulkdata, N_FIELDS)
279
    else:
280
      nodesdata = client.QueryNodes([], ["name"], False)
281
      nodeslist = [row[0] for row in nodesdata]
282
      return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
283
                                   uri_fields=("id", "uri"))
284

    
285

    
286
class R_2_nodes_name(baserlib.R_Generic):
287
  """/2/nodes/[node_name] resources.
288

289
  """
290
  def GET(self):
291
    """Send information about a node.
292

293
    """
294
    node_name = self.items[0]
295
    client = baserlib.GetClient()
296
    result = client.QueryNodes(names=[node_name], fields=N_FIELDS,
297
                               use_locking=self.useLocking())
298

    
299
    return baserlib.MapFields(N_FIELDS, result[0])
300

    
301

    
302
class R_2_nodes_name_role(baserlib.R_Generic):
303
  """ /2/nodes/[node_name]/role resource.
304

305
  """
306
  def GET(self):
307
    """Returns the current node role.
308

309
    @return: Node role
310

311
    """
312
    node_name = self.items[0]
313
    client = baserlib.GetClient()
314
    result = client.QueryNodes(names=[node_name], fields=["role"],
315
                               use_locking=self.useLocking())
316

    
317
    return _NR_MAP[result[0][0]]
318

    
319
  def PUT(self):
320
    """Sets the node role.
321

322
    @return: a job id
323

324
    """
325
    if not isinstance(self.req.request_body, basestring):
326
      raise http.HttpBadRequest("Invalid body contents, not a string")
327

    
328
    node_name = self.items[0]
329
    role = self.req.request_body
330

    
331
    if role == _NR_REGULAR:
332
      candidate = False
333
      offline = False
334
      drained = False
335

    
336
    elif role == _NR_MASTER_CANDIATE:
337
      candidate = True
338
      offline = drained = None
339

    
340
    elif role == _NR_DRAINED:
341
      drained = True
342
      candidate = offline = None
343

    
344
    elif role == _NR_OFFLINE:
345
      offline = True
346
      candidate = drained = None
347

    
348
    else:
349
      raise http.HttpBadRequest("Can't set '%s' role" % role)
350

    
351
    op = opcodes.OpSetNodeParams(node_name=node_name,
352
                                 master_candidate=candidate,
353
                                 offline=offline,
354
                                 drained=drained,
355
                                 force=bool(self.useForce()))
356

    
357
    return baserlib.SubmitJob([op])
358

    
359

    
360
class R_2_nodes_name_evacuate(baserlib.R_Generic):
361
  """/2/nodes/[node_name]/evacuate resource.
362

363
  """
364
  def POST(self):
365
    """Evacuate all secondary instances off a node.
366

367
    """
368
    node_name = self.items[0]
369
    remote_node = self._checkStringVariable("remote_node", default=None)
370
    iallocator = self._checkStringVariable("iallocator", default=None)
371

    
372
    op = opcodes.OpEvacuateNode(node_name=node_name,
373
                                remote_node=remote_node,
374
                                iallocator=iallocator)
375

    
376
    return baserlib.SubmitJob([op])
377

    
378

    
379
class R_2_nodes_name_migrate(baserlib.R_Generic):
380
  """/2/nodes/[node_name]/migrate resource.
381

382
  """
383
  def POST(self):
384
    """Migrate all primary instances from a node.
385

386
    """
387
    node_name = self.items[0]
388
    live = bool(self._checkIntVariable("live", default=1))
389

    
390
    op = opcodes.OpMigrateNode(node_name=node_name, live=live)
391

    
392
    return baserlib.SubmitJob([op])
393

    
394

    
395
class R_2_nodes_name_storage(baserlib.R_Generic):
396
  """/2/nodes/[node_name]/storage ressource.
397

398
  """
399
  # LUQueryNodeStorage acquires locks, hence restricting access to GET
400
  GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
401

    
402
  def GET(self):
403
    node_name = self.items[0]
404

    
405
    storage_type = self._checkStringVariable("storage_type", None)
406
    if not storage_type:
407
      raise http.HttpBadRequest("Missing the required 'storage_type'"
408
                                " parameter")
409

    
410
    output_fields = self._checkStringVariable("output_fields", None)
411
    if not output_fields:
412
      raise http.HttpBadRequest("Missing the required 'output_fields'"
413
                                " parameter")
414

    
415
    op = opcodes.OpQueryNodeStorage(nodes=[node_name],
416
                                    storage_type=storage_type,
417
                                    output_fields=output_fields.split(","))
418
    return baserlib.SubmitJob([op])
419

    
420

    
421
class R_2_nodes_name_storage_modify(baserlib.R_Generic):
422
  """/2/nodes/[node_name]/storage/modify ressource.
423

424
  """
425
  def PUT(self):
426
    node_name = self.items[0]
427

    
428
    storage_type = self._checkStringVariable("storage_type", None)
429
    if not storage_type:
430
      raise http.HttpBadRequest("Missing the required 'storage_type'"
431
                                " parameter")
432

    
433
    name = self._checkStringVariable("name", None)
434
    if not name:
435
      raise http.HttpBadRequest("Missing the required 'name'"
436
                                " parameter")
437

    
438
    changes = {}
439

    
440
    if "allocatable" in self.queryargs:
441
      changes[constants.SF_ALLOCATABLE] = \
442
        bool(self._checkIntVariable("allocatable", default=1))
443

    
444
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
445
                                     storage_type=storage_type,
446
                                     name=name,
447
                                     changes=changes)
448
    return baserlib.SubmitJob([op])
449

    
450

    
451
class R_2_nodes_name_storage_repair(baserlib.R_Generic):
452
  """/2/nodes/[node_name]/storage/repair ressource.
453

454
  """
455
  def PUT(self):
456
    node_name = self.items[0]
457

    
458
    storage_type = self._checkStringVariable("storage_type", None)
459
    if not storage_type:
460
      raise http.HttpBadRequest("Missing the required 'storage_type'"
461
                                " parameter")
462

    
463
    name = self._checkStringVariable("name", None)
464
    if not name:
465
      raise http.HttpBadRequest("Missing the required 'name'"
466
                                " parameter")
467

    
468
    op = opcodes.OpRepairNodeStorage(node_name=node_name,
469
                                     storage_type=storage_type,
470
                                     name=name)
471
    return baserlib.SubmitJob([op])
472

    
473

    
474
class R_2_instances(baserlib.R_Generic):
475
  """/2/instances resource.
476

477
  """
478
  def GET(self):
479
    """Returns a list of all available instances.
480

481
    """
482
    client = baserlib.GetClient()
483

    
484
    use_locking = self.useLocking()
485
    if self.useBulk():
486
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
487
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
488
    else:
489
      instancesdata = client.QueryInstances([], ["name"], use_locking)
490
      instanceslist = [row[0] for row in instancesdata]
491
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
492
                                   uri_fields=("id", "uri"))
493

    
494
  def POST(self):
495
    """Create an instance.
496

497
    @return: a job id
498

499
    """
500
    if not isinstance(self.req.request_body, dict):
501
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
502

    
503
    beparams = baserlib.MakeParamsDict(self.req.request_body,
504
                                       constants.BES_PARAMETERS)
505
    hvparams = baserlib.MakeParamsDict(self.req.request_body,
506
                                       constants.HVS_PARAMETERS)
507
    fn = self.getBodyParameter
508

    
509
    # disk processing
510
    disk_data = fn('disks')
511
    if not isinstance(disk_data, list):
512
      raise http.HttpBadRequest("The 'disks' parameter should be a list")
513
    disks = []
514
    for idx, d in enumerate(disk_data):
515
      if not isinstance(d, int):
516
        raise http.HttpBadRequest("Disk %d specification wrong: should"
517
                                  " be an integer" % idx)
518
      disks.append({"size": d})
519
    # nic processing (one nic only)
520
    nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
521
    if fn("ip", None) is not None:
522
      nics[0]["ip"] = fn("ip")
523
    if fn("mode", None) is not None:
524
      nics[0]["mode"] = fn("mode")
525
    if fn("link", None) is not None:
526
      nics[0]["link"] = fn("link")
527
    if fn("bridge", None) is not None:
528
      nics[0]["bridge"] = fn("bridge")
529

    
530
    op = opcodes.OpCreateInstance(
531
      mode=constants.INSTANCE_CREATE,
532
      instance_name=fn('name'),
533
      disks=disks,
534
      disk_template=fn('disk_template'),
535
      os_type=fn('os'),
536
      pnode=fn('pnode', None),
537
      snode=fn('snode', None),
538
      iallocator=fn('iallocator', None),
539
      nics=nics,
540
      start=fn('start', True),
541
      ip_check=fn('ip_check', True),
542
      name_check=fn('name_check', True),
543
      wait_for_sync=True,
544
      hypervisor=fn('hypervisor', None),
545
      hvparams=hvparams,
546
      beparams=beparams,
547
      file_storage_dir=fn('file_storage_dir', None),
548
      file_driver=fn('file_driver', 'loop'),
549
      dry_run=bool(self.dryRun()),
550
      )
551

    
552
    return baserlib.SubmitJob([op])
553

    
554

    
555
class R_2_instances_name(baserlib.R_Generic):
556
  """/2/instances/[instance_name] resources.
557

558
  """
559
  def GET(self):
560
    """Send information about an instance.
561

562
    """
563
    client = baserlib.GetClient()
564
    instance_name = self.items[0]
565
    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
566
                                   use_locking=self.useLocking())
567

    
568
    return baserlib.MapFields(I_FIELDS, result[0])
569

    
570
  def DELETE(self):
571
    """Delete an instance.
572

573
    """
574
    op = opcodes.OpRemoveInstance(instance_name=self.items[0],
575
                                  ignore_failures=False,
576
                                  dry_run=bool(self.dryRun()))
577
    return baserlib.SubmitJob([op])
578

    
579

    
580
class R_2_instances_name_info(baserlib.R_Generic):
581
  """/2/instances/[instance_name]/info resource.
582

583
  """
584
  def GET(self):
585
    """Request detailed instance information.
586

587
    """
588
    instance_name = self.items[0]
589
    static = bool(self._checkIntVariable("static", default=0))
590

    
591
    op = opcodes.OpQueryInstanceData(instances=[instance_name],
592
                                     static=static)
593
    return baserlib.SubmitJob([op])
594

    
595

    
596
class R_2_instances_name_reboot(baserlib.R_Generic):
597
  """/2/instances/[instance_name]/reboot resource.
598

599
  Implements an instance reboot.
600

601
  """
602
  def POST(self):
603
    """Reboot an instance.
604

605
    The URI takes type=[hard|soft|full] and
606
    ignore_secondaries=[False|True] parameters.
607

608
    """
609
    instance_name = self.items[0]
610
    reboot_type = self.queryargs.get('type',
611
                                     [constants.INSTANCE_REBOOT_HARD])[0]
612
    ignore_secondaries = bool(self._checkIntVariable('ignore_secondaries'))
613
    op = opcodes.OpRebootInstance(instance_name=instance_name,
614
                                  reboot_type=reboot_type,
615
                                  ignore_secondaries=ignore_secondaries,
616
                                  dry_run=bool(self.dryRun()))
617

    
618
    return baserlib.SubmitJob([op])
619

    
620

    
621
class R_2_instances_name_startup(baserlib.R_Generic):
622
  """/2/instances/[instance_name]/startup resource.
623

624
  Implements an instance startup.
625

626
  """
627
  def PUT(self):
628
    """Startup an instance.
629

630
    The URI takes force=[False|True] parameter to start the instance
631
    if even if secondary disks are failing.
632

633
    """
634
    instance_name = self.items[0]
635
    force_startup = bool(self._checkIntVariable('force'))
636
    op = opcodes.OpStartupInstance(instance_name=instance_name,
637
                                   force=force_startup,
638
                                   dry_run=bool(self.dryRun()))
639

    
640
    return baserlib.SubmitJob([op])
641

    
642

    
643
class R_2_instances_name_shutdown(baserlib.R_Generic):
644
  """/2/instances/[instance_name]/shutdown resource.
645

646
  Implements an instance shutdown.
647

648
  """
649
  def PUT(self):
650
    """Shutdown an instance.
651

652
    """
653
    instance_name = self.items[0]
654
    op = opcodes.OpShutdownInstance(instance_name=instance_name,
655
                                    dry_run=bool(self.dryRun()))
656

    
657
    return baserlib.SubmitJob([op])
658

    
659

    
660
class R_2_instances_name_reinstall(baserlib.R_Generic):
661
  """/2/instances/[instance_name]/reinstall resource.
662

663
  Implements an instance reinstall.
664

665
  """
666
  def POST(self):
667
    """Reinstall an instance.
668

669
    The URI takes os=name and nostartup=[0|1] optional
670
    parameters. By default, the instance will be started
671
    automatically.
672

673
    """
674
    instance_name = self.items[0]
675
    ostype = self._checkStringVariable('os')
676
    nostartup = self._checkIntVariable('nostartup')
677
    ops = [
678
      opcodes.OpShutdownInstance(instance_name=instance_name),
679
      opcodes.OpReinstallInstance(instance_name=instance_name, os_type=ostype),
680
      ]
681
    if not nostartup:
682
      ops.append(opcodes.OpStartupInstance(instance_name=instance_name,
683
                                           force=False))
684
    return baserlib.SubmitJob(ops)
685

    
686

    
687
class R_2_instances_name_replace_disks(baserlib.R_Generic):
688
  """/2/instances/[instance_name]/replace-disks resource.
689

690
  """
691
  def POST(self):
692
    """Replaces disks on an instance.
693

694
    """
695
    instance_name = self.items[0]
696
    remote_node = self._checkStringVariable("remote_node", default=None)
697
    mode = self._checkStringVariable("mode", default=None)
698
    raw_disks = self._checkStringVariable("disks", default=None)
699
    iallocator = self._checkStringVariable("iallocator", default=None)
700

    
701
    if raw_disks:
702
      try:
703
        disks = [int(part) for part in raw_disks.split(",")]
704
      except ValueError, err:
705
        raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
706
    else:
707
      disks = []
708

    
709
    op = opcodes.OpReplaceDisks(instance_name=instance_name,
710
                                remote_node=remote_node,
711
                                mode=mode,
712
                                disks=disks,
713
                                iallocator=iallocator)
714

    
715
    return baserlib.SubmitJob([op])
716

    
717

    
718
class R_2_instances_name_activate_disks(baserlib.R_Generic):
719
  """/2/instances/[instance_name]/activate-disks resource.
720

721
  """
722
  def PUT(self):
723
    """Activate disks for an instance.
724

725
    The URI might contain ignore_size to ignore current recorded size.
726

727
    """
728
    instance_name = self.items[0]
729
    ignore_size = bool(self._checkIntVariable('ignore_size'))
730

    
731
    op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
732
                                         ignore_size=ignore_size)
733

    
734
    return baserlib.SubmitJob([op])
735

    
736

    
737
class R_2_instances_name_deactivate_disks(baserlib.R_Generic):
738
  """/2/instances/[instance_name]/deactivate-disks resource.
739

740
  """
741
  def PUT(self):
742
    """Deactivate disks for an instance.
743

744
    """
745
    instance_name = self.items[0]
746

    
747
    op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
748

    
749
    return baserlib.SubmitJob([op])
750

    
751

    
752
class _R_Tags(baserlib.R_Generic):
753
  """ Quasiclass for tagging resources
754

755
  Manages tags. When inheriting this class you must define the
756
  TAG_LEVEL for it.
757

758
  """
759
  TAG_LEVEL = None
760

    
761
  def __init__(self, items, queryargs, req):
762
    """A tag resource constructor.
763

764
    We have to override the default to sort out cluster naming case.
765

766
    """
767
    baserlib.R_Generic.__init__(self, items, queryargs, req)
768

    
769
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
770
      self.name = items[0]
771
    else:
772
      self.name = ""
773

    
774
  def GET(self):
775
    """Returns a list of tags.
776

777
    Example: ["tag1", "tag2", "tag3"]
778

779
    """
780
    # pylint: disable-msg=W0212
781
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
782

    
783
  def PUT(self):
784
    """Add a set of tags.
785

786
    The request as a list of strings should be PUT to this URI. And
787
    you'll have back a job id.
788

789
    """
790
    # pylint: disable-msg=W0212
791
    if 'tag' not in self.queryargs:
792
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
793
                                " the 'tag' parameter")
794
    return baserlib._Tags_PUT(self.TAG_LEVEL,
795
                              self.queryargs['tag'], name=self.name,
796
                              dry_run=bool(self.dryRun()))
797

    
798
  def DELETE(self):
799
    """Delete a tag.
800

801
    In order to delete a set of tags, the DELETE
802
    request should be addressed to URI like:
803
    /tags?tag=[tag]&tag=[tag]
804

805
    """
806
    # pylint: disable-msg=W0212
807
    if 'tag' not in self.queryargs:
808
      # no we not gonna delete all tags
809
      raise http.HttpBadRequest("Cannot delete all tags - please specify"
810
                                " tag(s) using the 'tag' parameter")
811
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
812
                                 self.queryargs['tag'],
813
                                 name=self.name,
814
                                 dry_run=bool(self.dryRun()))
815

    
816

    
817
class R_2_instances_name_tags(_R_Tags):
818
  """ /2/instances/[instance_name]/tags resource.
819

820
  Manages per-instance tags.
821

822
  """
823
  TAG_LEVEL = constants.TAG_INSTANCE
824

    
825

    
826
class R_2_nodes_name_tags(_R_Tags):
827
  """ /2/nodes/[node_name]/tags resource.
828

829
  Manages per-node tags.
830

831
  """
832
  TAG_LEVEL = constants.TAG_NODE
833

    
834

    
835
class R_2_tags(_R_Tags):
836
  """ /2/instances/tags resource.
837

838
  Manages cluster tags.
839

840
  """
841
  TAG_LEVEL = constants.TAG_CLUSTER