Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 12eff9b9

History | View | Annotate | Download (23.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
  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
# Request data version field
87
_REQ_DATA_VERSION = "__version__"
88

    
89
# Timeout for /2/jobs/[job_id]/wait. Gives job up to 10 seconds to change.
90
_WFJC_TIMEOUT = 10
91

    
92

    
93
class R_version(baserlib.R_Generic):
94
  """/version resource.
95

96
  This resource should be used to determine the remote API version and
97
  to adapt clients accordingly.
98

99
  """
100
  @staticmethod
101
  def GET():
102
    """Returns the remote API version.
103

104
    """
105
    return constants.RAPI_VERSION
106

    
107

    
108
class R_2_info(baserlib.R_Generic):
109
  """Cluster info.
110

111
  """
112
  @staticmethod
113
  def GET():
114
    """Returns cluster information.
115

116
    """
117
    client = baserlib.GetClient()
118
    return client.QueryClusterInfo()
119

    
120

    
121
class R_2_os(baserlib.R_Generic):
122
  """/2/os resource.
123

124
  """
125
  @staticmethod
126
  def GET():
127
    """Return a list of all OSes.
128

129
    Can return error 500 in case of a problem.
130

131
    Example: ["debian-etch"]
132

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

    
142
    if not isinstance(diagnose_data, list):
143
      raise http.HttpBadGateway(message="Can't get OS list")
144

    
145
    os_names = []
146
    for (name, valid, variants) in diagnose_data:
147
      if valid:
148
        os_names.extend(cli.CalculateOSNames(name, variants))
149

    
150
    return os_names
151

    
152

    
153
class R_2_redist_config(baserlib.R_Generic):
154
  """/2/redistribute-config resource.
155

156
  """
157
  @staticmethod
158
  def PUT():
159
    """Redistribute configuration to all nodes.
160

161
    """
162
    return baserlib.SubmitJob([opcodes.OpRedistributeConfig()])
163

    
164

    
165
class R_2_jobs(baserlib.R_Generic):
166
  """/2/jobs resource.
167

168
  """
169
  @staticmethod
170
  def GET():
171
    """Returns a dictionary of jobs.
172

173
    @return: a dictionary with jobs id and uri.
174

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

    
183

    
184
class R_2_jobs_id(baserlib.R_Generic):
185
  """/2/jobs/[job_id] resource.
186

187
  """
188
  def GET(self):
189
    """Returns a job status.
190

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

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

    
211
  def DELETE(self):
212
    """Cancel not-yet-started job.
213

214
    """
215
    job_id = self.items[0]
216
    result = baserlib.GetClient().CancelJob(job_id)
217
    return result
218

    
219

    
220
class R_2_jobs_id_wait(baserlib.R_Generic):
221
  """/2/jobs/[job_id]/wait resource.
222

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

    
228
  def GET(self):
229
    """Waits for job changes.
230

231
    """
232
    job_id = self.items[0]
233

    
234
    fields = self.getBodyParameter("fields")
235
    prev_job_info = self.getBodyParameter("previous_job_info", None)
236
    prev_log_serial = self.getBodyParameter("previous_log_serial", None)
237

    
238
    if not isinstance(fields, list):
239
      raise http.HttpBadRequest("The 'fields' parameter should be a list")
240

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

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

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

    
257
    if result == constants.JOB_NOTCHANGED:
258
      # No changes
259
      return None
260

    
261
    (job_info, log_entries) = result
262

    
263
    return {
264
      "job_info": job_info,
265
      "log_entries": log_entries,
266
      }
267

    
268

    
269
class R_2_nodes(baserlib.R_Generic):
270
  """/2/nodes resource.
271

272
  """
273
  def GET(self):
274
    """Returns a list of all nodes.
275

276
    """
277
    client = baserlib.GetClient()
278

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

    
288

    
289
class R_2_nodes_name(baserlib.R_Generic):
290
  """/2/nodes/[node_name] resources.
291

292
  """
293
  def GET(self):
294
    """Send information about a node.
295

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

    
302
    return baserlib.MapFields(N_FIELDS, result[0])
303

    
304

    
305
class R_2_nodes_name_role(baserlib.R_Generic):
306
  """ /2/nodes/[node_name]/role resource.
307

308
  """
309
  def GET(self):
310
    """Returns the current node role.
311

312
    @return: Node role
313

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

    
320
    return _NR_MAP[result[0][0]]
321

    
322
  def PUT(self):
323
    """Sets the node role.
324

325
    @return: a job id
326

327
    """
328
    if not isinstance(self.req.request_body, basestring):
329
      raise http.HttpBadRequest("Invalid body contents, not a string")
330

    
331
    node_name = self.items[0]
332
    role = self.req.request_body
333

    
334
    if role == _NR_REGULAR:
335
      candidate = False
336
      offline = False
337
      drained = False
338

    
339
    elif role == _NR_MASTER_CANDIATE:
340
      candidate = True
341
      offline = drained = None
342

    
343
    elif role == _NR_DRAINED:
344
      drained = True
345
      candidate = offline = None
346

    
347
    elif role == _NR_OFFLINE:
348
      offline = True
349
      candidate = drained = None
350

    
351
    else:
352
      raise http.HttpBadRequest("Can't set '%s' role" % role)
353

    
354
    op = opcodes.OpSetNodeParams(node_name=node_name,
355
                                 master_candidate=candidate,
356
                                 offline=offline,
357
                                 drained=drained,
358
                                 force=bool(self.useForce()))
359

    
360
    return baserlib.SubmitJob([op])
361

    
362

    
363
class R_2_nodes_name_evacuate(baserlib.R_Generic):
364
  """/2/nodes/[node_name]/evacuate resource.
365

366
  """
367
  def POST(self):
368
    """Evacuate all secondary instances off a node.
369

370
    """
371
    node_name = self.items[0]
372
    remote_node = self._checkStringVariable("remote_node", default=None)
373
    iallocator = self._checkStringVariable("iallocator", default=None)
374

    
375
    op = opcodes.OpEvacuateNode(node_name=node_name,
376
                                remote_node=remote_node,
377
                                iallocator=iallocator)
378

    
379
    return baserlib.SubmitJob([op])
380

    
381

    
382
class R_2_nodes_name_migrate(baserlib.R_Generic):
383
  """/2/nodes/[node_name]/migrate resource.
384

385
  """
386
  def POST(self):
387
    """Migrate all primary instances from a node.
388

389
    """
390
    node_name = self.items[0]
391
    live = bool(self._checkIntVariable("live", default=1))
392

    
393
    op = opcodes.OpMigrateNode(node_name=node_name, live=live)
394

    
395
    return baserlib.SubmitJob([op])
396

    
397

    
398
class R_2_nodes_name_storage(baserlib.R_Generic):
399
  """/2/nodes/[node_name]/storage ressource.
400

401
  """
402
  # LUQueryNodeStorage acquires locks, hence restricting access to GET
403
  GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
404

    
405
  def GET(self):
406
    node_name = self.items[0]
407

    
408
    storage_type = self._checkStringVariable("storage_type", None)
409
    if not storage_type:
410
      raise http.HttpBadRequest("Missing the required 'storage_type'"
411
                                " parameter")
412

    
413
    output_fields = self._checkStringVariable("output_fields", None)
414
    if not output_fields:
415
      raise http.HttpBadRequest("Missing the required 'output_fields'"
416
                                " parameter")
417

    
418
    op = opcodes.OpQueryNodeStorage(nodes=[node_name],
419
                                    storage_type=storage_type,
420
                                    output_fields=output_fields.split(","))
421
    return baserlib.SubmitJob([op])
422

    
423

    
424
class R_2_nodes_name_storage_modify(baserlib.R_Generic):
425
  """/2/nodes/[node_name]/storage/modify ressource.
426

427
  """
428
  def PUT(self):
429
    node_name = self.items[0]
430

    
431
    storage_type = self._checkStringVariable("storage_type", None)
432
    if not storage_type:
433
      raise http.HttpBadRequest("Missing the required 'storage_type'"
434
                                " parameter")
435

    
436
    name = self._checkStringVariable("name", None)
437
    if not name:
438
      raise http.HttpBadRequest("Missing the required 'name'"
439
                                " parameter")
440

    
441
    changes = {}
442

    
443
    if "allocatable" in self.queryargs:
444
      changes[constants.SF_ALLOCATABLE] = \
445
        bool(self._checkIntVariable("allocatable", default=1))
446

    
447
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
448
                                     storage_type=storage_type,
449
                                     name=name,
450
                                     changes=changes)
451
    return baserlib.SubmitJob([op])
452

    
453

    
454
class R_2_nodes_name_storage_repair(baserlib.R_Generic):
455
  """/2/nodes/[node_name]/storage/repair ressource.
456

457
  """
458
  def PUT(self):
459
    node_name = self.items[0]
460

    
461
    storage_type = self._checkStringVariable("storage_type", None)
462
    if not storage_type:
463
      raise http.HttpBadRequest("Missing the required 'storage_type'"
464
                                " parameter")
465

    
466
    name = self._checkStringVariable("name", None)
467
    if not name:
468
      raise http.HttpBadRequest("Missing the required 'name'"
469
                                " parameter")
470

    
471
    op = opcodes.OpRepairNodeStorage(node_name=node_name,
472
                                     storage_type=storage_type,
473
                                     name=name)
474
    return baserlib.SubmitJob([op])
475

    
476

    
477
class R_2_instances(baserlib.R_Generic):
478
  """/2/instances resource.
479

480
  """
481
  def GET(self):
482
    """Returns a list of all available instances.
483

484
    """
485
    client = baserlib.GetClient()
486

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

    
497
  def _ParseVersion0CreateRequest(self):
498
    """Parses an instance creation request version 0.
499

500
    @rtype: L{opcodes.OpCreateInstance}
501
    @return: Instance creation opcode
502

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

    
510
    # disk processing
511
    disk_data = fn('disks')
512
    if not isinstance(disk_data, list):
513
      raise http.HttpBadRequest("The 'disks' parameter should be a list")
514
    disks = []
515
    for idx, d in enumerate(disk_data):
516
      if not isinstance(d, int):
517
        raise http.HttpBadRequest("Disk %d specification wrong: should"
518
                                  " be an integer" % idx)
519
      disks.append({"size": d})
520

    
521
    # nic processing (one nic only)
522
    nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
523
    if fn("ip", None) is not None:
524
      nics[0]["ip"] = fn("ip")
525
    if fn("mode", None) is not None:
526
      nics[0]["mode"] = fn("mode")
527
    if fn("link", None) is not None:
528
      nics[0]["link"] = fn("link")
529
    if fn("bridge", None) is not None:
530
      nics[0]["bridge"] = fn("bridge")
531

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

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

557
    @return: a job id
558

559
    """
560
    if not isinstance(self.req.request_body, dict):
561
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
562

    
563
    # Default to request data version 0
564
    data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0)
565

    
566
    if data_version == 0:
567
      op = self._ParseVersion0CreateRequest()
568
    else:
569
      raise http.HttpBadRequest("Unsupported request data version %s" %
570
                                data_version)
571

    
572
    return baserlib.SubmitJob([op])
573

    
574

    
575
class R_2_instances_name(baserlib.R_Generic):
576
  """/2/instances/[instance_name] resources.
577

578
  """
579
  def GET(self):
580
    """Send information about an instance.
581

582
    """
583
    client = baserlib.GetClient()
584
    instance_name = self.items[0]
585
    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
586
                                   use_locking=self.useLocking())
587

    
588
    return baserlib.MapFields(I_FIELDS, result[0])
589

    
590
  def DELETE(self):
591
    """Delete an instance.
592

593
    """
594
    op = opcodes.OpRemoveInstance(instance_name=self.items[0],
595
                                  ignore_failures=False,
596
                                  dry_run=bool(self.dryRun()))
597
    return baserlib.SubmitJob([op])
598

    
599

    
600
class R_2_instances_name_info(baserlib.R_Generic):
601
  """/2/instances/[instance_name]/info resource.
602

603
  """
604
  def GET(self):
605
    """Request detailed instance information.
606

607
    """
608
    instance_name = self.items[0]
609
    static = bool(self._checkIntVariable("static", default=0))
610

    
611
    op = opcodes.OpQueryInstanceData(instances=[instance_name],
612
                                     static=static)
613
    return baserlib.SubmitJob([op])
614

    
615

    
616
class R_2_instances_name_reboot(baserlib.R_Generic):
617
  """/2/instances/[instance_name]/reboot resource.
618

619
  Implements an instance reboot.
620

621
  """
622
  def POST(self):
623
    """Reboot an instance.
624

625
    The URI takes type=[hard|soft|full] and
626
    ignore_secondaries=[False|True] parameters.
627

628
    """
629
    instance_name = self.items[0]
630
    reboot_type = self.queryargs.get('type',
631
                                     [constants.INSTANCE_REBOOT_HARD])[0]
632
    ignore_secondaries = bool(self._checkIntVariable('ignore_secondaries'))
633
    op = opcodes.OpRebootInstance(instance_name=instance_name,
634
                                  reboot_type=reboot_type,
635
                                  ignore_secondaries=ignore_secondaries,
636
                                  dry_run=bool(self.dryRun()))
637

    
638
    return baserlib.SubmitJob([op])
639

    
640

    
641
class R_2_instances_name_startup(baserlib.R_Generic):
642
  """/2/instances/[instance_name]/startup resource.
643

644
  Implements an instance startup.
645

646
  """
647
  def PUT(self):
648
    """Startup an instance.
649

650
    The URI takes force=[False|True] parameter to start the instance
651
    if even if secondary disks are failing.
652

653
    """
654
    instance_name = self.items[0]
655
    force_startup = bool(self._checkIntVariable('force'))
656
    op = opcodes.OpStartupInstance(instance_name=instance_name,
657
                                   force=force_startup,
658
                                   dry_run=bool(self.dryRun()))
659

    
660
    return baserlib.SubmitJob([op])
661

    
662

    
663
class R_2_instances_name_shutdown(baserlib.R_Generic):
664
  """/2/instances/[instance_name]/shutdown resource.
665

666
  Implements an instance shutdown.
667

668
  """
669
  def PUT(self):
670
    """Shutdown an instance.
671

672
    """
673
    instance_name = self.items[0]
674
    op = opcodes.OpShutdownInstance(instance_name=instance_name,
675
                                    dry_run=bool(self.dryRun()))
676

    
677
    return baserlib.SubmitJob([op])
678

    
679

    
680
class R_2_instances_name_reinstall(baserlib.R_Generic):
681
  """/2/instances/[instance_name]/reinstall resource.
682

683
  Implements an instance reinstall.
684

685
  """
686
  def POST(self):
687
    """Reinstall an instance.
688

689
    The URI takes os=name and nostartup=[0|1] optional
690
    parameters. By default, the instance will be started
691
    automatically.
692

693
    """
694
    instance_name = self.items[0]
695
    ostype = self._checkStringVariable('os')
696
    nostartup = self._checkIntVariable('nostartup')
697
    ops = [
698
      opcodes.OpShutdownInstance(instance_name=instance_name),
699
      opcodes.OpReinstallInstance(instance_name=instance_name, os_type=ostype),
700
      ]
701
    if not nostartup:
702
      ops.append(opcodes.OpStartupInstance(instance_name=instance_name,
703
                                           force=False))
704
    return baserlib.SubmitJob(ops)
705

    
706

    
707
class R_2_instances_name_replace_disks(baserlib.R_Generic):
708
  """/2/instances/[instance_name]/replace-disks resource.
709

710
  """
711
  def POST(self):
712
    """Replaces disks on an instance.
713

714
    """
715
    instance_name = self.items[0]
716
    remote_node = self._checkStringVariable("remote_node", default=None)
717
    mode = self._checkStringVariable("mode", default=None)
718
    raw_disks = self._checkStringVariable("disks", default=None)
719
    iallocator = self._checkStringVariable("iallocator", default=None)
720

    
721
    if raw_disks:
722
      try:
723
        disks = [int(part) for part in raw_disks.split(",")]
724
      except ValueError, err:
725
        raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
726
    else:
727
      disks = []
728

    
729
    op = opcodes.OpReplaceDisks(instance_name=instance_name,
730
                                remote_node=remote_node,
731
                                mode=mode,
732
                                disks=disks,
733
                                iallocator=iallocator)
734

    
735
    return baserlib.SubmitJob([op])
736

    
737

    
738
class R_2_instances_name_activate_disks(baserlib.R_Generic):
739
  """/2/instances/[instance_name]/activate-disks resource.
740

741
  """
742
  def PUT(self):
743
    """Activate disks for an instance.
744

745
    The URI might contain ignore_size to ignore current recorded size.
746

747
    """
748
    instance_name = self.items[0]
749
    ignore_size = bool(self._checkIntVariable('ignore_size'))
750

    
751
    op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
752
                                         ignore_size=ignore_size)
753

    
754
    return baserlib.SubmitJob([op])
755

    
756

    
757
class R_2_instances_name_deactivate_disks(baserlib.R_Generic):
758
  """/2/instances/[instance_name]/deactivate-disks resource.
759

760
  """
761
  def PUT(self):
762
    """Deactivate disks for an instance.
763

764
    """
765
    instance_name = self.items[0]
766

    
767
    op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
768

    
769
    return baserlib.SubmitJob([op])
770

    
771

    
772
class _R_Tags(baserlib.R_Generic):
773
  """ Quasiclass for tagging resources
774

775
  Manages tags. When inheriting this class you must define the
776
  TAG_LEVEL for it.
777

778
  """
779
  TAG_LEVEL = None
780

    
781
  def __init__(self, items, queryargs, req):
782
    """A tag resource constructor.
783

784
    We have to override the default to sort out cluster naming case.
785

786
    """
787
    baserlib.R_Generic.__init__(self, items, queryargs, req)
788

    
789
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
790
      self.name = items[0]
791
    else:
792
      self.name = ""
793

    
794
  def GET(self):
795
    """Returns a list of tags.
796

797
    Example: ["tag1", "tag2", "tag3"]
798

799
    """
800
    # pylint: disable-msg=W0212
801
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
802

    
803
  def PUT(self):
804
    """Add a set of tags.
805

806
    The request as a list of strings should be PUT to this URI. And
807
    you'll have back a job id.
808

809
    """
810
    # pylint: disable-msg=W0212
811
    if 'tag' not in self.queryargs:
812
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
813
                                " the 'tag' parameter")
814
    return baserlib._Tags_PUT(self.TAG_LEVEL,
815
                              self.queryargs['tag'], name=self.name,
816
                              dry_run=bool(self.dryRun()))
817

    
818
  def DELETE(self):
819
    """Delete a tag.
820

821
    In order to delete a set of tags, the DELETE
822
    request should be addressed to URI like:
823
    /tags?tag=[tag]&tag=[tag]
824

825
    """
826
    # pylint: disable-msg=W0212
827
    if 'tag' not in self.queryargs:
828
      # no we not gonna delete all tags
829
      raise http.HttpBadRequest("Cannot delete all tags - please specify"
830
                                " tag(s) using the 'tag' parameter")
831
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
832
                                 self.queryargs['tag'],
833
                                 name=self.name,
834
                                 dry_run=bool(self.dryRun()))
835

    
836

    
837
class R_2_instances_name_tags(_R_Tags):
838
  """ /2/instances/[instance_name]/tags resource.
839

840
  Manages per-instance tags.
841

842
  """
843
  TAG_LEVEL = constants.TAG_INSTANCE
844

    
845

    
846
class R_2_nodes_name_tags(_R_Tags):
847
  """ /2/nodes/[node_name]/tags resource.
848

849
  Manages per-node tags.
850

851
  """
852
  TAG_LEVEL = constants.TAG_NODE
853

    
854

    
855
class R_2_tags(_R_Tags):
856
  """ /2/instances/tags resource.
857

858
  Manages cluster tags.
859

860
  """
861
  TAG_LEVEL = constants.TAG_CLUSTER