Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (24.1 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_features(baserlib.R_Generic):
122
  """/2/features resource.
123

124
  """
125
  @staticmethod
126
  def GET():
127
    """Returns list of optional RAPI features implemented.
128

129
    """
130
    return []
131

    
132

    
133
class R_2_os(baserlib.R_Generic):
134
  """/2/os resource.
135

136
  """
137
  @staticmethod
138
  def GET():
139
    """Return a list of all OSes.
140

141
    Can return error 500 in case of a problem.
142

143
    Example: ["debian-etch"]
144

145
    """
146
    cl = baserlib.GetClient()
147
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants"],
148
                              names=[])
149
    job_id = baserlib.SubmitJob([op], cl)
150
    # we use custom feedback function, instead of print we log the status
151
    result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
152
    diagnose_data = result[0]
153

    
154
    if not isinstance(diagnose_data, list):
155
      raise http.HttpBadGateway(message="Can't get OS list")
156

    
157
    os_names = []
158
    for (name, valid, variants) in diagnose_data:
159
      if valid:
160
        os_names.extend(cli.CalculateOSNames(name, variants))
161

    
162
    return os_names
163

    
164

    
165
class R_2_redist_config(baserlib.R_Generic):
166
  """/2/redistribute-config resource.
167

168
  """
169
  @staticmethod
170
  def PUT():
171
    """Redistribute configuration to all nodes.
172

173
    """
174
    return baserlib.SubmitJob([opcodes.OpRedistributeConfig()])
175

    
176

    
177
class R_2_jobs(baserlib.R_Generic):
178
  """/2/jobs resource.
179

180
  """
181
  @staticmethod
182
  def GET():
183
    """Returns a dictionary of jobs.
184

185
    @return: a dictionary with jobs id and uri.
186

187
    """
188
    fields = ["id"]
189
    cl = baserlib.GetClient()
190
    # Convert the list of lists to the list of ids
191
    result = [job_id for [job_id] in cl.QueryJobs(None, fields)]
192
    return baserlib.BuildUriList(result, "/2/jobs/%s",
193
                                 uri_fields=("id", "uri"))
194

    
195

    
196
class R_2_jobs_id(baserlib.R_Generic):
197
  """/2/jobs/[job_id] resource.
198

199
  """
200
  def GET(self):
201
    """Returns a job status.
202

203
    @return: a dictionary with job parameters.
204
        The result includes:
205
            - id: job ID as a number
206
            - status: current job status as a string
207
            - ops: involved OpCodes as a list of dictionaries for each
208
              opcodes in the job
209
            - opstatus: OpCodes status as a list
210
            - opresult: OpCodes results as a list of lists
211

212
    """
213
    fields = ["id", "ops", "status", "summary",
214
              "opstatus", "opresult", "oplog",
215
              "received_ts", "start_ts", "end_ts",
216
              ]
217
    job_id = self.items[0]
218
    result = baserlib.GetClient().QueryJobs([job_id, ], fields)[0]
219
    if result is None:
220
      raise http.HttpNotFound()
221
    return baserlib.MapFields(fields, result)
222

    
223
  def DELETE(self):
224
    """Cancel not-yet-started job.
225

226
    """
227
    job_id = self.items[0]
228
    result = baserlib.GetClient().CancelJob(job_id)
229
    return result
230

    
231

    
232
class R_2_jobs_id_wait(baserlib.R_Generic):
233
  """/2/jobs/[job_id]/wait resource.
234

235
  """
236
  # WaitForJobChange provides access to sensitive information and blocks
237
  # machine resources (it's a blocking RAPI call), hence restricting access.
238
  GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
239

    
240
  def GET(self):
241
    """Waits for job changes.
242

243
    """
244
    job_id = self.items[0]
245

    
246
    fields = self.getBodyParameter("fields")
247
    prev_job_info = self.getBodyParameter("previous_job_info", None)
248
    prev_log_serial = self.getBodyParameter("previous_log_serial", None)
249

    
250
    if not isinstance(fields, list):
251
      raise http.HttpBadRequest("The 'fields' parameter should be a list")
252

    
253
    if not (prev_job_info is None or isinstance(prev_job_info, list)):
254
      raise http.HttpBadRequest("The 'previous_job_info' parameter should"
255
                                " be a list")
256

    
257
    if not (prev_log_serial is None or
258
            isinstance(prev_log_serial, (int, long))):
259
      raise http.HttpBadRequest("The 'previous_log_serial' parameter should"
260
                                " be a number")
261

    
262
    client = baserlib.GetClient()
263
    result = client.WaitForJobChangeOnce(job_id, fields,
264
                                         prev_job_info, prev_log_serial,
265
                                         timeout=_WFJC_TIMEOUT)
266
    if not result:
267
      raise http.HttpNotFound()
268

    
269
    if result == constants.JOB_NOTCHANGED:
270
      # No changes
271
      return None
272

    
273
    (job_info, log_entries) = result
274

    
275
    return {
276
      "job_info": job_info,
277
      "log_entries": log_entries,
278
      }
279

    
280

    
281
class R_2_nodes(baserlib.R_Generic):
282
  """/2/nodes resource.
283

284
  """
285
  def GET(self):
286
    """Returns a list of all nodes.
287

288
    """
289
    client = baserlib.GetClient()
290

    
291
    if self.useBulk():
292
      bulkdata = client.QueryNodes([], N_FIELDS, False)
293
      return baserlib.MapBulkFields(bulkdata, N_FIELDS)
294
    else:
295
      nodesdata = client.QueryNodes([], ["name"], False)
296
      nodeslist = [row[0] for row in nodesdata]
297
      return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
298
                                   uri_fields=("id", "uri"))
299

    
300

    
301
class R_2_nodes_name(baserlib.R_Generic):
302
  """/2/nodes/[node_name] resources.
303

304
  """
305
  def GET(self):
306
    """Send information about a node.
307

308
    """
309
    node_name = self.items[0]
310
    client = baserlib.GetClient()
311
    result = client.QueryNodes(names=[node_name], fields=N_FIELDS,
312
                               use_locking=self.useLocking())
313

    
314
    return baserlib.MapFields(N_FIELDS, result[0])
315

    
316

    
317
class R_2_nodes_name_role(baserlib.R_Generic):
318
  """ /2/nodes/[node_name]/role resource.
319

320
  """
321
  def GET(self):
322
    """Returns the current node role.
323

324
    @return: Node role
325

326
    """
327
    node_name = self.items[0]
328
    client = baserlib.GetClient()
329
    result = client.QueryNodes(names=[node_name], fields=["role"],
330
                               use_locking=self.useLocking())
331

    
332
    return _NR_MAP[result[0][0]]
333

    
334
  def PUT(self):
335
    """Sets the node role.
336

337
    @return: a job id
338

339
    """
340
    if not isinstance(self.req.request_body, basestring):
341
      raise http.HttpBadRequest("Invalid body contents, not a string")
342

    
343
    node_name = self.items[0]
344
    role = self.req.request_body
345

    
346
    if role == _NR_REGULAR:
347
      candidate = False
348
      offline = False
349
      drained = False
350

    
351
    elif role == _NR_MASTER_CANDIATE:
352
      candidate = True
353
      offline = drained = None
354

    
355
    elif role == _NR_DRAINED:
356
      drained = True
357
      candidate = offline = None
358

    
359
    elif role == _NR_OFFLINE:
360
      offline = True
361
      candidate = drained = None
362

    
363
    else:
364
      raise http.HttpBadRequest("Can't set '%s' role" % role)
365

    
366
    op = opcodes.OpSetNodeParams(node_name=node_name,
367
                                 master_candidate=candidate,
368
                                 offline=offline,
369
                                 drained=drained,
370
                                 force=bool(self.useForce()))
371

    
372
    return baserlib.SubmitJob([op])
373

    
374

    
375
class R_2_nodes_name_evacuate(baserlib.R_Generic):
376
  """/2/nodes/[node_name]/evacuate resource.
377

378
  """
379
  def POST(self):
380
    """Evacuate all secondary instances off a node.
381

382
    """
383
    node_name = self.items[0]
384
    remote_node = self._checkStringVariable("remote_node", default=None)
385
    iallocator = self._checkStringVariable("iallocator", default=None)
386

    
387
    op = opcodes.OpEvacuateNode(node_name=node_name,
388
                                remote_node=remote_node,
389
                                iallocator=iallocator)
390

    
391
    return baserlib.SubmitJob([op])
392

    
393

    
394
class R_2_nodes_name_migrate(baserlib.R_Generic):
395
  """/2/nodes/[node_name]/migrate resource.
396

397
  """
398
  def POST(self):
399
    """Migrate all primary instances from a node.
400

401
    """
402
    node_name = self.items[0]
403
    live = bool(self._checkIntVariable("live", default=1))
404

    
405
    op = opcodes.OpMigrateNode(node_name=node_name, live=live)
406

    
407
    return baserlib.SubmitJob([op])
408

    
409

    
410
class R_2_nodes_name_storage(baserlib.R_Generic):
411
  """/2/nodes/[node_name]/storage ressource.
412

413
  """
414
  # LUQueryNodeStorage acquires locks, hence restricting access to GET
415
  GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
416

    
417
  def GET(self):
418
    node_name = self.items[0]
419

    
420
    storage_type = self._checkStringVariable("storage_type", None)
421
    if not storage_type:
422
      raise http.HttpBadRequest("Missing the required 'storage_type'"
423
                                " parameter")
424

    
425
    output_fields = self._checkStringVariable("output_fields", None)
426
    if not output_fields:
427
      raise http.HttpBadRequest("Missing the required 'output_fields'"
428
                                " parameter")
429

    
430
    op = opcodes.OpQueryNodeStorage(nodes=[node_name],
431
                                    storage_type=storage_type,
432
                                    output_fields=output_fields.split(","))
433
    return baserlib.SubmitJob([op])
434

    
435

    
436
class R_2_nodes_name_storage_modify(baserlib.R_Generic):
437
  """/2/nodes/[node_name]/storage/modify ressource.
438

439
  """
440
  def PUT(self):
441
    node_name = self.items[0]
442

    
443
    storage_type = self._checkStringVariable("storage_type", None)
444
    if not storage_type:
445
      raise http.HttpBadRequest("Missing the required 'storage_type'"
446
                                " parameter")
447

    
448
    name = self._checkStringVariable("name", None)
449
    if not name:
450
      raise http.HttpBadRequest("Missing the required 'name'"
451
                                " parameter")
452

    
453
    changes = {}
454

    
455
    if "allocatable" in self.queryargs:
456
      changes[constants.SF_ALLOCATABLE] = \
457
        bool(self._checkIntVariable("allocatable", default=1))
458

    
459
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
460
                                     storage_type=storage_type,
461
                                     name=name,
462
                                     changes=changes)
463
    return baserlib.SubmitJob([op])
464

    
465

    
466
class R_2_nodes_name_storage_repair(baserlib.R_Generic):
467
  """/2/nodes/[node_name]/storage/repair ressource.
468

469
  """
470
  def PUT(self):
471
    node_name = self.items[0]
472

    
473
    storage_type = self._checkStringVariable("storage_type", None)
474
    if not storage_type:
475
      raise http.HttpBadRequest("Missing the required 'storage_type'"
476
                                " parameter")
477

    
478
    name = self._checkStringVariable("name", None)
479
    if not name:
480
      raise http.HttpBadRequest("Missing the required 'name'"
481
                                " parameter")
482

    
483
    op = opcodes.OpRepairNodeStorage(node_name=node_name,
484
                                     storage_type=storage_type,
485
                                     name=name)
486
    return baserlib.SubmitJob([op])
487

    
488

    
489
class R_2_instances(baserlib.R_Generic):
490
  """/2/instances resource.
491

492
  """
493
  def GET(self):
494
    """Returns a list of all available instances.
495

496
    """
497
    client = baserlib.GetClient()
498

    
499
    use_locking = self.useLocking()
500
    if self.useBulk():
501
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
502
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
503
    else:
504
      instancesdata = client.QueryInstances([], ["name"], use_locking)
505
      instanceslist = [row[0] for row in instancesdata]
506
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
507
                                   uri_fields=("id", "uri"))
508

    
509
  def _ParseVersion0CreateRequest(self):
510
    """Parses an instance creation request version 0.
511

512
    @rtype: L{opcodes.OpCreateInstance}
513
    @return: Instance creation opcode
514

515
    """
516
    beparams = baserlib.MakeParamsDict(self.req.request_body,
517
                                       constants.BES_PARAMETERS)
518
    hvparams = baserlib.MakeParamsDict(self.req.request_body,
519
                                       constants.HVS_PARAMETERS)
520
    fn = self.getBodyParameter
521

    
522
    # disk processing
523
    disk_data = fn('disks')
524
    if not isinstance(disk_data, list):
525
      raise http.HttpBadRequest("The 'disks' parameter should be a list")
526
    disks = []
527
    for idx, d in enumerate(disk_data):
528
      if not isinstance(d, int):
529
        raise http.HttpBadRequest("Disk %d specification wrong: should"
530
                                  " be an integer" % idx)
531
      disks.append({"size": d})
532

    
533
    # nic processing (one nic only)
534
    nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
535
    if fn("ip", None) is not None:
536
      nics[0]["ip"] = fn("ip")
537
    if fn("mode", None) is not None:
538
      nics[0]["mode"] = fn("mode")
539
    if fn("link", None) is not None:
540
      nics[0]["link"] = fn("link")
541
    if fn("bridge", None) is not None:
542
      nics[0]["bridge"] = fn("bridge")
543

    
544
    return opcodes.OpCreateInstance(
545
      mode=constants.INSTANCE_CREATE,
546
      instance_name=fn('name'),
547
      disks=disks,
548
      disk_template=fn('disk_template'),
549
      os_type=fn('os'),
550
      pnode=fn('pnode', None),
551
      snode=fn('snode', None),
552
      iallocator=fn('iallocator', None),
553
      nics=nics,
554
      start=fn('start', True),
555
      ip_check=fn('ip_check', True),
556
      name_check=fn('name_check', True),
557
      wait_for_sync=True,
558
      hypervisor=fn('hypervisor', None),
559
      hvparams=hvparams,
560
      beparams=beparams,
561
      file_storage_dir=fn('file_storage_dir', None),
562
      file_driver=fn('file_driver', 'loop'),
563
      dry_run=bool(self.dryRun()),
564
      )
565

    
566
  def POST(self):
567
    """Create an instance.
568

569
    @return: a job id
570

571
    """
572
    if not isinstance(self.req.request_body, dict):
573
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
574

    
575
    # Default to request data version 0
576
    data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0)
577

    
578
    if data_version == 0:
579
      op = self._ParseVersion0CreateRequest()
580
    else:
581
      raise http.HttpBadRequest("Unsupported request data version %s" %
582
                                data_version)
583

    
584
    return baserlib.SubmitJob([op])
585

    
586

    
587
class R_2_instances_name(baserlib.R_Generic):
588
  """/2/instances/[instance_name] resources.
589

590
  """
591
  def GET(self):
592
    """Send information about an instance.
593

594
    """
595
    client = baserlib.GetClient()
596
    instance_name = self.items[0]
597
    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
598
                                   use_locking=self.useLocking())
599

    
600
    return baserlib.MapFields(I_FIELDS, result[0])
601

    
602
  def DELETE(self):
603
    """Delete an instance.
604

605
    """
606
    op = opcodes.OpRemoveInstance(instance_name=self.items[0],
607
                                  ignore_failures=False,
608
                                  dry_run=bool(self.dryRun()))
609
    return baserlib.SubmitJob([op])
610

    
611

    
612
class R_2_instances_name_info(baserlib.R_Generic):
613
  """/2/instances/[instance_name]/info resource.
614

615
  """
616
  def GET(self):
617
    """Request detailed instance information.
618

619
    """
620
    instance_name = self.items[0]
621
    static = bool(self._checkIntVariable("static", default=0))
622

    
623
    op = opcodes.OpQueryInstanceData(instances=[instance_name],
624
                                     static=static)
625
    return baserlib.SubmitJob([op])
626

    
627

    
628
class R_2_instances_name_reboot(baserlib.R_Generic):
629
  """/2/instances/[instance_name]/reboot resource.
630

631
  Implements an instance reboot.
632

633
  """
634
  def POST(self):
635
    """Reboot an instance.
636

637
    The URI takes type=[hard|soft|full] and
638
    ignore_secondaries=[False|True] parameters.
639

640
    """
641
    instance_name = self.items[0]
642
    reboot_type = self.queryargs.get('type',
643
                                     [constants.INSTANCE_REBOOT_HARD])[0]
644
    ignore_secondaries = bool(self._checkIntVariable('ignore_secondaries'))
645
    op = opcodes.OpRebootInstance(instance_name=instance_name,
646
                                  reboot_type=reboot_type,
647
                                  ignore_secondaries=ignore_secondaries,
648
                                  dry_run=bool(self.dryRun()))
649

    
650
    return baserlib.SubmitJob([op])
651

    
652

    
653
class R_2_instances_name_startup(baserlib.R_Generic):
654
  """/2/instances/[instance_name]/startup resource.
655

656
  Implements an instance startup.
657

658
  """
659
  def PUT(self):
660
    """Startup an instance.
661

662
    The URI takes force=[False|True] parameter to start the instance
663
    if even if secondary disks are failing.
664

665
    """
666
    instance_name = self.items[0]
667
    force_startup = bool(self._checkIntVariable('force'))
668
    op = opcodes.OpStartupInstance(instance_name=instance_name,
669
                                   force=force_startup,
670
                                   dry_run=bool(self.dryRun()))
671

    
672
    return baserlib.SubmitJob([op])
673

    
674

    
675
class R_2_instances_name_shutdown(baserlib.R_Generic):
676
  """/2/instances/[instance_name]/shutdown resource.
677

678
  Implements an instance shutdown.
679

680
  """
681
  def PUT(self):
682
    """Shutdown an instance.
683

684
    """
685
    instance_name = self.items[0]
686
    op = opcodes.OpShutdownInstance(instance_name=instance_name,
687
                                    dry_run=bool(self.dryRun()))
688

    
689
    return baserlib.SubmitJob([op])
690

    
691

    
692
class R_2_instances_name_reinstall(baserlib.R_Generic):
693
  """/2/instances/[instance_name]/reinstall resource.
694

695
  Implements an instance reinstall.
696

697
  """
698
  def POST(self):
699
    """Reinstall an instance.
700

701
    The URI takes os=name and nostartup=[0|1] optional
702
    parameters. By default, the instance will be started
703
    automatically.
704

705
    """
706
    instance_name = self.items[0]
707
    ostype = self._checkStringVariable('os')
708
    nostartup = self._checkIntVariable('nostartup')
709
    ops = [
710
      opcodes.OpShutdownInstance(instance_name=instance_name),
711
      opcodes.OpReinstallInstance(instance_name=instance_name, os_type=ostype),
712
      ]
713
    if not nostartup:
714
      ops.append(opcodes.OpStartupInstance(instance_name=instance_name,
715
                                           force=False))
716
    return baserlib.SubmitJob(ops)
717

    
718

    
719
class R_2_instances_name_replace_disks(baserlib.R_Generic):
720
  """/2/instances/[instance_name]/replace-disks resource.
721

722
  """
723
  def POST(self):
724
    """Replaces disks on an instance.
725

726
    """
727
    instance_name = self.items[0]
728
    remote_node = self._checkStringVariable("remote_node", default=None)
729
    mode = self._checkStringVariable("mode", default=None)
730
    raw_disks = self._checkStringVariable("disks", default=None)
731
    iallocator = self._checkStringVariable("iallocator", default=None)
732

    
733
    if raw_disks:
734
      try:
735
        disks = [int(part) for part in raw_disks.split(",")]
736
      except ValueError, err:
737
        raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
738
    else:
739
      disks = []
740

    
741
    op = opcodes.OpReplaceDisks(instance_name=instance_name,
742
                                remote_node=remote_node,
743
                                mode=mode,
744
                                disks=disks,
745
                                iallocator=iallocator)
746

    
747
    return baserlib.SubmitJob([op])
748

    
749

    
750
class R_2_instances_name_activate_disks(baserlib.R_Generic):
751
  """/2/instances/[instance_name]/activate-disks resource.
752

753
  """
754
  def PUT(self):
755
    """Activate disks for an instance.
756

757
    The URI might contain ignore_size to ignore current recorded size.
758

759
    """
760
    instance_name = self.items[0]
761
    ignore_size = bool(self._checkIntVariable('ignore_size'))
762

    
763
    op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
764
                                         ignore_size=ignore_size)
765

    
766
    return baserlib.SubmitJob([op])
767

    
768

    
769
class R_2_instances_name_deactivate_disks(baserlib.R_Generic):
770
  """/2/instances/[instance_name]/deactivate-disks resource.
771

772
  """
773
  def PUT(self):
774
    """Deactivate disks for an instance.
775

776
    """
777
    instance_name = self.items[0]
778

    
779
    op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
780

    
781
    return baserlib.SubmitJob([op])
782

    
783

    
784
class _R_Tags(baserlib.R_Generic):
785
  """ Quasiclass for tagging resources
786

787
  Manages tags. When inheriting this class you must define the
788
  TAG_LEVEL for it.
789

790
  """
791
  TAG_LEVEL = None
792

    
793
  def __init__(self, items, queryargs, req):
794
    """A tag resource constructor.
795

796
    We have to override the default to sort out cluster naming case.
797

798
    """
799
    baserlib.R_Generic.__init__(self, items, queryargs, req)
800

    
801
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
802
      self.name = items[0]
803
    else:
804
      self.name = ""
805

    
806
  def GET(self):
807
    """Returns a list of tags.
808

809
    Example: ["tag1", "tag2", "tag3"]
810

811
    """
812
    # pylint: disable-msg=W0212
813
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
814

    
815
  def PUT(self):
816
    """Add a set of tags.
817

818
    The request as a list of strings should be PUT to this URI. And
819
    you'll have back a job id.
820

821
    """
822
    # pylint: disable-msg=W0212
823
    if 'tag' not in self.queryargs:
824
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
825
                                " the 'tag' parameter")
826
    return baserlib._Tags_PUT(self.TAG_LEVEL,
827
                              self.queryargs['tag'], name=self.name,
828
                              dry_run=bool(self.dryRun()))
829

    
830
  def DELETE(self):
831
    """Delete a tag.
832

833
    In order to delete a set of tags, the DELETE
834
    request should be addressed to URI like:
835
    /tags?tag=[tag]&tag=[tag]
836

837
    """
838
    # pylint: disable-msg=W0212
839
    if 'tag' not in self.queryargs:
840
      # no we not gonna delete all tags
841
      raise http.HttpBadRequest("Cannot delete all tags - please specify"
842
                                " tag(s) using the 'tag' parameter")
843
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
844
                                 self.queryargs['tag'],
845
                                 name=self.name,
846
                                 dry_run=bool(self.dryRun()))
847

    
848

    
849
class R_2_instances_name_tags(_R_Tags):
850
  """ /2/instances/[instance_name]/tags resource.
851

852
  Manages per-instance tags.
853

854
  """
855
  TAG_LEVEL = constants.TAG_INSTANCE
856

    
857

    
858
class R_2_nodes_name_tags(_R_Tags):
859
  """ /2/nodes/[node_name]/tags resource.
860

861
  Manages per-node tags.
862

863
  """
864
  TAG_LEVEL = constants.TAG_NODE
865

    
866

    
867
class R_2_tags(_R_Tags):
868
  """ /2/instances/tags resource.
869

870
  Manages cluster tags.
871

872
  """
873
  TAG_LEVEL = constants.TAG_CLUSTER