Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 6395cebb

History | View | Annotate | Download (27.5 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 utils
49
from ganeti import rapi
50
from ganeti.rapi import baserlib
51

    
52

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

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

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

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

    
87
# Request data version field
88
_REQ_DATA_VERSION = "__version__"
89

    
90
# Feature string for instance creation request data version 1
91
_INST_CREATE_REQV1 = "instance-create-reqv1"
92

    
93
# Timeout for /2/jobs/[job_id]/wait. Gives job up to 10 seconds to change.
94
_WFJC_TIMEOUT = 10
95

    
96

    
97
class R_version(baserlib.R_Generic):
98
  """/version resource.
99

100
  This resource should be used to determine the remote API version and
101
  to adapt clients accordingly.
102

103
  """
104
  @staticmethod
105
  def GET():
106
    """Returns the remote API version.
107

108
    """
109
    return constants.RAPI_VERSION
110

    
111

    
112
class R_2_info(baserlib.R_Generic):
113
  """Cluster info.
114

115
  """
116
  @staticmethod
117
  def GET():
118
    """Returns cluster information.
119

120
    """
121
    client = baserlib.GetClient()
122
    return client.QueryClusterInfo()
123

    
124

    
125
class R_2_features(baserlib.R_Generic):
126
  """/2/features resource.
127

128
  """
129
  @staticmethod
130
  def GET():
131
    """Returns list of optional RAPI features implemented.
132

133
    """
134
    return [_INST_CREATE_REQV1]
135

    
136

    
137
class R_2_os(baserlib.R_Generic):
138
  """/2/os resource.
139

140
  """
141
  @staticmethod
142
  def GET():
143
    """Return a list of all OSes.
144

145
    Can return error 500 in case of a problem.
146

147
    Example: ["debian-etch"]
148

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

    
158
    if not isinstance(diagnose_data, list):
159
      raise http.HttpBadGateway(message="Can't get OS list")
160

    
161
    os_names = []
162
    for (name, valid, variants) in diagnose_data:
163
      if valid:
164
        os_names.extend(cli.CalculateOSNames(name, variants))
165

    
166
    return os_names
167

    
168

    
169
class R_2_redist_config(baserlib.R_Generic):
170
  """/2/redistribute-config resource.
171

172
  """
173
  @staticmethod
174
  def PUT():
175
    """Redistribute configuration to all nodes.
176

177
    """
178
    return baserlib.SubmitJob([opcodes.OpRedistributeConfig()])
179

    
180

    
181
class R_2_jobs(baserlib.R_Generic):
182
  """/2/jobs resource.
183

184
  """
185
  @staticmethod
186
  def GET():
187
    """Returns a dictionary of jobs.
188

189
    @return: a dictionary with jobs id and uri.
190

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

    
199

    
200
class R_2_jobs_id(baserlib.R_Generic):
201
  """/2/jobs/[job_id] resource.
202

203
  """
204
  def GET(self):
205
    """Returns a job status.
206

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

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

    
227
  def DELETE(self):
228
    """Cancel not-yet-started job.
229

230
    """
231
    job_id = self.items[0]
232
    result = baserlib.GetClient().CancelJob(job_id)
233
    return result
234

    
235

    
236
class R_2_jobs_id_wait(baserlib.R_Generic):
237
  """/2/jobs/[job_id]/wait resource.
238

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

    
244
  def GET(self):
245
    """Waits for job changes.
246

247
    """
248
    job_id = self.items[0]
249

    
250
    fields = self.getBodyParameter("fields")
251
    prev_job_info = self.getBodyParameter("previous_job_info", None)
252
    prev_log_serial = self.getBodyParameter("previous_log_serial", None)
253

    
254
    if not isinstance(fields, list):
255
      raise http.HttpBadRequest("The 'fields' parameter should be a list")
256

    
257
    if not (prev_job_info is None or isinstance(prev_job_info, list)):
258
      raise http.HttpBadRequest("The 'previous_job_info' parameter should"
259
                                " be a list")
260

    
261
    if not (prev_log_serial is None or
262
            isinstance(prev_log_serial, (int, long))):
263
      raise http.HttpBadRequest("The 'previous_log_serial' parameter should"
264
                                " be a number")
265

    
266
    client = baserlib.GetClient()
267
    result = client.WaitForJobChangeOnce(job_id, fields,
268
                                         prev_job_info, prev_log_serial,
269
                                         timeout=_WFJC_TIMEOUT)
270
    if not result:
271
      raise http.HttpNotFound()
272

    
273
    if result == constants.JOB_NOTCHANGED:
274
      # No changes
275
      return None
276

    
277
    (job_info, log_entries) = result
278

    
279
    return {
280
      "job_info": job_info,
281
      "log_entries": log_entries,
282
      }
283

    
284

    
285
class R_2_nodes(baserlib.R_Generic):
286
  """/2/nodes resource.
287

288
  """
289
  def GET(self):
290
    """Returns a list of all nodes.
291

292
    """
293
    client = baserlib.GetClient()
294

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

    
304

    
305
class R_2_nodes_name(baserlib.R_Generic):
306
  """/2/nodes/[node_name] resources.
307

308
  """
309
  def GET(self):
310
    """Send information about a node.
311

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

    
318
    return baserlib.MapFields(N_FIELDS, result[0])
319

    
320

    
321
class R_2_nodes_name_role(baserlib.R_Generic):
322
  """ /2/nodes/[node_name]/role resource.
323

324
  """
325
  def GET(self):
326
    """Returns the current node role.
327

328
    @return: Node role
329

330
    """
331
    node_name = self.items[0]
332
    client = baserlib.GetClient()
333
    result = client.QueryNodes(names=[node_name], fields=["role"],
334
                               use_locking=self.useLocking())
335

    
336
    return _NR_MAP[result[0][0]]
337

    
338
  def PUT(self):
339
    """Sets the node role.
340

341
    @return: a job id
342

343
    """
344
    if not isinstance(self.req.request_body, basestring):
345
      raise http.HttpBadRequest("Invalid body contents, not a string")
346

    
347
    node_name = self.items[0]
348
    role = self.req.request_body
349

    
350
    if role == _NR_REGULAR:
351
      candidate = False
352
      offline = False
353
      drained = False
354

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

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

    
363
    elif role == _NR_OFFLINE:
364
      offline = True
365
      candidate = drained = None
366

    
367
    else:
368
      raise http.HttpBadRequest("Can't set '%s' role" % role)
369

    
370
    op = opcodes.OpSetNodeParams(node_name=node_name,
371
                                 master_candidate=candidate,
372
                                 offline=offline,
373
                                 drained=drained,
374
                                 force=bool(self.useForce()))
375

    
376
    return baserlib.SubmitJob([op])
377

    
378

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

382
  """
383
  def POST(self):
384
    """Evacuate all secondary instances off a node.
385

386
    """
387
    node_name = self.items[0]
388
    remote_node = self._checkStringVariable("remote_node", default=None)
389
    iallocator = self._checkStringVariable("iallocator", default=None)
390

    
391
    op = opcodes.OpEvacuateNode(node_name=node_name,
392
                                remote_node=remote_node,
393
                                iallocator=iallocator)
394

    
395
    return baserlib.SubmitJob([op])
396

    
397

    
398
class R_2_nodes_name_migrate(baserlib.R_Generic):
399
  """/2/nodes/[node_name]/migrate resource.
400

401
  """
402
  def POST(self):
403
    """Migrate all primary instances from a node.
404

405
    """
406
    node_name = self.items[0]
407
    live = bool(self._checkIntVariable("live", default=1))
408

    
409
    op = opcodes.OpMigrateNode(node_name=node_name, live=live)
410

    
411
    return baserlib.SubmitJob([op])
412

    
413

    
414
class R_2_nodes_name_storage(baserlib.R_Generic):
415
  """/2/nodes/[node_name]/storage ressource.
416

417
  """
418
  # LUQueryNodeStorage acquires locks, hence restricting access to GET
419
  GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
420

    
421
  def GET(self):
422
    node_name = self.items[0]
423

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

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

    
434
    op = opcodes.OpQueryNodeStorage(nodes=[node_name],
435
                                    storage_type=storage_type,
436
                                    output_fields=output_fields.split(","))
437
    return baserlib.SubmitJob([op])
438

    
439

    
440
class R_2_nodes_name_storage_modify(baserlib.R_Generic):
441
  """/2/nodes/[node_name]/storage/modify ressource.
442

443
  """
444
  def PUT(self):
445
    node_name = self.items[0]
446

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

    
452
    name = self._checkStringVariable("name", None)
453
    if not name:
454
      raise http.HttpBadRequest("Missing the required 'name'"
455
                                " parameter")
456

    
457
    changes = {}
458

    
459
    if "allocatable" in self.queryargs:
460
      changes[constants.SF_ALLOCATABLE] = \
461
        bool(self._checkIntVariable("allocatable", default=1))
462

    
463
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
464
                                     storage_type=storage_type,
465
                                     name=name,
466
                                     changes=changes)
467
    return baserlib.SubmitJob([op])
468

    
469

    
470
class R_2_nodes_name_storage_repair(baserlib.R_Generic):
471
  """/2/nodes/[node_name]/storage/repair ressource.
472

473
  """
474
  def PUT(self):
475
    node_name = self.items[0]
476

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

    
482
    name = self._checkStringVariable("name", None)
483
    if not name:
484
      raise http.HttpBadRequest("Missing the required 'name'"
485
                                " parameter")
486

    
487
    op = opcodes.OpRepairNodeStorage(node_name=node_name,
488
                                     storage_type=storage_type,
489
                                     name=name)
490
    return baserlib.SubmitJob([op])
491

    
492

    
493
def _ParseInstanceCreateRequestVersion1(data, dry_run):
494
  """Parses an instance creation request version 1.
495

496
  @rtype: L{opcodes.OpCreateInstance}
497
  @return: Instance creation opcode
498

499
  """
500
  # Disks
501
  disks_input = baserlib.CheckParameter(data, "disks", exptype=list)
502

    
503
  disks = []
504
  for idx, i in enumerate(disks_input):
505
    baserlib.CheckType(i, dict, "Disk %d specification" % idx)
506

    
507
    # Size is mandatory
508
    try:
509
      size = i["size"]
510
    except KeyError:
511
      raise http.HttpBadRequest("Disk %d specification wrong: missing disk"
512
                                " size" % idx)
513

    
514
    disk = {
515
      "size": size,
516
      }
517

    
518
    # Optional disk access mode
519
    try:
520
      disk_access = i["mode"]
521
    except KeyError:
522
      pass
523
    else:
524
      disk["mode"] = disk_access
525

    
526
    disks.append(disk)
527

    
528
  assert len(disks_input) == len(disks)
529

    
530
  # Network interfaces
531
  nics_input = baserlib.CheckParameter(data, "nics", exptype=list)
532

    
533
  nics = []
534
  for idx, i in enumerate(nics_input):
535
    baserlib.CheckType(i, dict, "NIC %d specification" % idx)
536

    
537
    nic = {}
538

    
539
    for field in ["mode", "ip", "link", "bridge"]:
540
      try:
541
        value = i[field]
542
      except KeyError:
543
        continue
544

    
545
      nic[field] = value
546

    
547
    nics.append(nic)
548

    
549
  assert len(nics_input) == len(nics)
550

    
551
  # HV/BE parameters
552
  hvparams = baserlib.CheckParameter(data, "hvparams", default={})
553
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
554

    
555
  beparams = baserlib.CheckParameter(data, "beparams", default={})
556
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
557

    
558
  return opcodes.OpCreateInstance(
559
    mode=baserlib.CheckParameter(data, "mode"),
560
    instance_name=baserlib.CheckParameter(data, "name"),
561
    os_type=baserlib.CheckParameter(data, "os", default=None),
562
    force_variant=baserlib.CheckParameter(data, "force_variant",
563
                                          default=False),
564
    pnode=baserlib.CheckParameter(data, "pnode", default=None),
565
    snode=baserlib.CheckParameter(data, "snode", default=None),
566
    disk_template=baserlib.CheckParameter(data, "disk_template"),
567
    disks=disks,
568
    nics=nics,
569
    src_node=baserlib.CheckParameter(data, "src_node", default=None),
570
    src_path=baserlib.CheckParameter(data, "src_path", default=None),
571
    start=baserlib.CheckParameter(data, "start", default=True),
572
    wait_for_sync=True,
573
    ip_check=baserlib.CheckParameter(data, "ip_check", default=True),
574
    name_check=baserlib.CheckParameter(data, "name_check", default=True),
575
    file_storage_dir=baserlib.CheckParameter(data, "file_storage_dir",
576
                                             default=None),
577
    file_driver=baserlib.CheckParameter(data, "file_driver",
578
                                        default=constants.FD_LOOP),
579
    iallocator=baserlib.CheckParameter(data, "iallocator", default=None),
580
    hypervisor=baserlib.CheckParameter(data, "hypervisor", default=None),
581
    hvparams=hvparams,
582
    beparams=beparams,
583
    dry_run=dry_run,
584
    )
585

    
586

    
587
class R_2_instances(baserlib.R_Generic):
588
  """/2/instances resource.
589

590
  """
591
  def GET(self):
592
    """Returns a list of all available instances.
593

594
    """
595
    client = baserlib.GetClient()
596

    
597
    use_locking = self.useLocking()
598
    if self.useBulk():
599
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
600
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
601
    else:
602
      instancesdata = client.QueryInstances([], ["name"], use_locking)
603
      instanceslist = [row[0] for row in instancesdata]
604
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
605
                                   uri_fields=("id", "uri"))
606

    
607
  def _ParseVersion0CreateRequest(self):
608
    """Parses an instance creation request version 0.
609

610
    Request data version 0 is deprecated and should not be used anymore.
611

612
    @rtype: L{opcodes.OpCreateInstance}
613
    @return: Instance creation opcode
614

615
    """
616
    # Do not modify anymore, request data version 0 is deprecated
617
    beparams = baserlib.MakeParamsDict(self.req.request_body,
618
                                       constants.BES_PARAMETERS)
619
    hvparams = baserlib.MakeParamsDict(self.req.request_body,
620
                                       constants.HVS_PARAMETERS)
621
    fn = self.getBodyParameter
622

    
623
    # disk processing
624
    disk_data = fn('disks')
625
    if not isinstance(disk_data, list):
626
      raise http.HttpBadRequest("The 'disks' parameter should be a list")
627
    disks = []
628
    for idx, d in enumerate(disk_data):
629
      if not isinstance(d, int):
630
        raise http.HttpBadRequest("Disk %d specification wrong: should"
631
                                  " be an integer" % idx)
632
      disks.append({"size": d})
633

    
634
    # nic processing (one nic only)
635
    nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
636
    if fn("ip", None) is not None:
637
      nics[0]["ip"] = fn("ip")
638
    if fn("mode", None) is not None:
639
      nics[0]["mode"] = fn("mode")
640
    if fn("link", None) is not None:
641
      nics[0]["link"] = fn("link")
642
    if fn("bridge", None) is not None:
643
      nics[0]["bridge"] = fn("bridge")
644

    
645
    # Do not modify anymore, request data version 0 is deprecated
646
    return opcodes.OpCreateInstance(
647
      mode=constants.INSTANCE_CREATE,
648
      instance_name=fn('name'),
649
      disks=disks,
650
      disk_template=fn('disk_template'),
651
      os_type=fn('os'),
652
      pnode=fn('pnode', None),
653
      snode=fn('snode', None),
654
      iallocator=fn('iallocator', None),
655
      nics=nics,
656
      start=fn('start', True),
657
      ip_check=fn('ip_check', True),
658
      name_check=fn('name_check', True),
659
      wait_for_sync=True,
660
      hypervisor=fn('hypervisor', None),
661
      hvparams=hvparams,
662
      beparams=beparams,
663
      file_storage_dir=fn('file_storage_dir', None),
664
      file_driver=fn('file_driver', constants.FD_LOOP),
665
      dry_run=bool(self.dryRun()),
666
      )
667

    
668
  def POST(self):
669
    """Create an instance.
670

671
    @return: a job id
672

673
    """
674
    if not isinstance(self.req.request_body, dict):
675
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
676

    
677
    # Default to request data version 0
678
    data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0)
679

    
680
    if data_version == 0:
681
      op = self._ParseVersion0CreateRequest()
682
    elif data_version == 1:
683
      op = _ParseInstanceCreateRequestVersion1(self.req.request_body,
684
                                               self.dryRun())
685
    else:
686
      raise http.HttpBadRequest("Unsupported request data version %s" %
687
                                data_version)
688

    
689
    return baserlib.SubmitJob([op])
690

    
691

    
692
class R_2_instances_name(baserlib.R_Generic):
693
  """/2/instances/[instance_name] resources.
694

695
  """
696
  def GET(self):
697
    """Send information about an instance.
698

699
    """
700
    client = baserlib.GetClient()
701
    instance_name = self.items[0]
702
    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
703
                                   use_locking=self.useLocking())
704

    
705
    return baserlib.MapFields(I_FIELDS, result[0])
706

    
707
  def DELETE(self):
708
    """Delete an instance.
709

710
    """
711
    op = opcodes.OpRemoveInstance(instance_name=self.items[0],
712
                                  ignore_failures=False,
713
                                  dry_run=bool(self.dryRun()))
714
    return baserlib.SubmitJob([op])
715

    
716

    
717
class R_2_instances_name_info(baserlib.R_Generic):
718
  """/2/instances/[instance_name]/info resource.
719

720
  """
721
  def GET(self):
722
    """Request detailed instance information.
723

724
    """
725
    instance_name = self.items[0]
726
    static = bool(self._checkIntVariable("static", default=0))
727

    
728
    op = opcodes.OpQueryInstanceData(instances=[instance_name],
729
                                     static=static)
730
    return baserlib.SubmitJob([op])
731

    
732

    
733
class R_2_instances_name_reboot(baserlib.R_Generic):
734
  """/2/instances/[instance_name]/reboot resource.
735

736
  Implements an instance reboot.
737

738
  """
739
  def POST(self):
740
    """Reboot an instance.
741

742
    The URI takes type=[hard|soft|full] and
743
    ignore_secondaries=[False|True] parameters.
744

745
    """
746
    instance_name = self.items[0]
747
    reboot_type = self.queryargs.get('type',
748
                                     [constants.INSTANCE_REBOOT_HARD])[0]
749
    ignore_secondaries = bool(self._checkIntVariable('ignore_secondaries'))
750
    op = opcodes.OpRebootInstance(instance_name=instance_name,
751
                                  reboot_type=reboot_type,
752
                                  ignore_secondaries=ignore_secondaries,
753
                                  dry_run=bool(self.dryRun()))
754

    
755
    return baserlib.SubmitJob([op])
756

    
757

    
758
class R_2_instances_name_startup(baserlib.R_Generic):
759
  """/2/instances/[instance_name]/startup resource.
760

761
  Implements an instance startup.
762

763
  """
764
  def PUT(self):
765
    """Startup an instance.
766

767
    The URI takes force=[False|True] parameter to start the instance
768
    if even if secondary disks are failing.
769

770
    """
771
    instance_name = self.items[0]
772
    force_startup = bool(self._checkIntVariable('force'))
773
    op = opcodes.OpStartupInstance(instance_name=instance_name,
774
                                   force=force_startup,
775
                                   dry_run=bool(self.dryRun()))
776

    
777
    return baserlib.SubmitJob([op])
778

    
779

    
780
class R_2_instances_name_shutdown(baserlib.R_Generic):
781
  """/2/instances/[instance_name]/shutdown resource.
782

783
  Implements an instance shutdown.
784

785
  """
786
  def PUT(self):
787
    """Shutdown an instance.
788

789
    """
790
    instance_name = self.items[0]
791
    op = opcodes.OpShutdownInstance(instance_name=instance_name,
792
                                    dry_run=bool(self.dryRun()))
793

    
794
    return baserlib.SubmitJob([op])
795

    
796

    
797
class R_2_instances_name_reinstall(baserlib.R_Generic):
798
  """/2/instances/[instance_name]/reinstall resource.
799

800
  Implements an instance reinstall.
801

802
  """
803
  def POST(self):
804
    """Reinstall an instance.
805

806
    The URI takes os=name and nostartup=[0|1] optional
807
    parameters. By default, the instance will be started
808
    automatically.
809

810
    """
811
    instance_name = self.items[0]
812
    ostype = self._checkStringVariable('os')
813
    nostartup = self._checkIntVariable('nostartup')
814
    ops = [
815
      opcodes.OpShutdownInstance(instance_name=instance_name),
816
      opcodes.OpReinstallInstance(instance_name=instance_name, os_type=ostype),
817
      ]
818
    if not nostartup:
819
      ops.append(opcodes.OpStartupInstance(instance_name=instance_name,
820
                                           force=False))
821
    return baserlib.SubmitJob(ops)
822

    
823

    
824
class R_2_instances_name_replace_disks(baserlib.R_Generic):
825
  """/2/instances/[instance_name]/replace-disks resource.
826

827
  """
828
  def POST(self):
829
    """Replaces disks on an instance.
830

831
    """
832
    instance_name = self.items[0]
833
    remote_node = self._checkStringVariable("remote_node", default=None)
834
    mode = self._checkStringVariable("mode", default=None)
835
    raw_disks = self._checkStringVariable("disks", default=None)
836
    iallocator = self._checkStringVariable("iallocator", default=None)
837

    
838
    if raw_disks:
839
      try:
840
        disks = [int(part) for part in raw_disks.split(",")]
841
      except ValueError, err:
842
        raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
843
    else:
844
      disks = []
845

    
846
    op = opcodes.OpReplaceDisks(instance_name=instance_name,
847
                                remote_node=remote_node,
848
                                mode=mode,
849
                                disks=disks,
850
                                iallocator=iallocator)
851

    
852
    return baserlib.SubmitJob([op])
853

    
854

    
855
class R_2_instances_name_activate_disks(baserlib.R_Generic):
856
  """/2/instances/[instance_name]/activate-disks resource.
857

858
  """
859
  def PUT(self):
860
    """Activate disks for an instance.
861

862
    The URI might contain ignore_size to ignore current recorded size.
863

864
    """
865
    instance_name = self.items[0]
866
    ignore_size = bool(self._checkIntVariable('ignore_size'))
867

    
868
    op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
869
                                         ignore_size=ignore_size)
870

    
871
    return baserlib.SubmitJob([op])
872

    
873

    
874
class R_2_instances_name_deactivate_disks(baserlib.R_Generic):
875
  """/2/instances/[instance_name]/deactivate-disks resource.
876

877
  """
878
  def PUT(self):
879
    """Deactivate disks for an instance.
880

881
    """
882
    instance_name = self.items[0]
883

    
884
    op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
885

    
886
    return baserlib.SubmitJob([op])
887

    
888

    
889
class _R_Tags(baserlib.R_Generic):
890
  """ Quasiclass for tagging resources
891

892
  Manages tags. When inheriting this class you must define the
893
  TAG_LEVEL for it.
894

895
  """
896
  TAG_LEVEL = None
897

    
898
  def __init__(self, items, queryargs, req):
899
    """A tag resource constructor.
900

901
    We have to override the default to sort out cluster naming case.
902

903
    """
904
    baserlib.R_Generic.__init__(self, items, queryargs, req)
905

    
906
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
907
      self.name = items[0]
908
    else:
909
      self.name = ""
910

    
911
  def GET(self):
912
    """Returns a list of tags.
913

914
    Example: ["tag1", "tag2", "tag3"]
915

916
    """
917
    # pylint: disable-msg=W0212
918
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
919

    
920
  def PUT(self):
921
    """Add a set of tags.
922

923
    The request as a list of strings should be PUT to this URI. And
924
    you'll have back a job id.
925

926
    """
927
    # pylint: disable-msg=W0212
928
    if 'tag' not in self.queryargs:
929
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
930
                                " the 'tag' parameter")
931
    return baserlib._Tags_PUT(self.TAG_LEVEL,
932
                              self.queryargs['tag'], name=self.name,
933
                              dry_run=bool(self.dryRun()))
934

    
935
  def DELETE(self):
936
    """Delete a tag.
937

938
    In order to delete a set of tags, the DELETE
939
    request should be addressed to URI like:
940
    /tags?tag=[tag]&tag=[tag]
941

942
    """
943
    # pylint: disable-msg=W0212
944
    if 'tag' not in self.queryargs:
945
      # no we not gonna delete all tags
946
      raise http.HttpBadRequest("Cannot delete all tags - please specify"
947
                                " tag(s) using the 'tag' parameter")
948
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
949
                                 self.queryargs['tag'],
950
                                 name=self.name,
951
                                 dry_run=bool(self.dryRun()))
952

    
953

    
954
class R_2_instances_name_tags(_R_Tags):
955
  """ /2/instances/[instance_name]/tags resource.
956

957
  Manages per-instance tags.
958

959
  """
960
  TAG_LEVEL = constants.TAG_INSTANCE
961

    
962

    
963
class R_2_nodes_name_tags(_R_Tags):
964
  """ /2/nodes/[node_name]/tags resource.
965

966
  Manages per-node tags.
967

968
  """
969
  TAG_LEVEL = constants.TAG_NODE
970

    
971

    
972
class R_2_tags(_R_Tags):
973
  """ /2/instances/tags resource.
974

975
  Manages cluster tags.
976

977
  """
978
  TAG_LEVEL = constants.TAG_CLUSTER