Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 4b63dc7a

History | View | Annotate | Download (35 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 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", "oper_vcpus", "status",
62
            "custom_hvparams", "custom_beparams", "custom_nicparams",
63
            ] + _COMMON_FIELDS
64

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

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

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

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

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

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

    
97

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

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

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

109
    """
110
    return constants.RAPI_VERSION
111

    
112

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

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

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

    
125

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

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

134
    """
135
    return [_INST_CREATE_REQV1]
136

    
137

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

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

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

148
    Example: ["debian-etch"]
149

150
    """
151
    cl = baserlib.GetClient()
152
    op = opcodes.OpDiagnoseOS(output_fields=["name", "variants"], 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, variants) in diagnose_data:
163
      os_names.extend(cli.CalculateOSNames(name, variants))
164

    
165
    return os_names
166

    
167

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

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

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

    
179

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

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

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

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

    
198

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

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

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

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

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

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

    
234

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

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

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

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

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

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

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

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

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

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

    
276
    (job_info, log_entries) = result
277

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

    
283

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

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

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

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

    
303

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

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

311
    """
312
    node_name = self.items[0]
313
    client = baserlib.GetClient()
314

    
315
    result = baserlib.HandleItemQueryErrors(client.QueryNodes,
316
                                            names=[node_name], fields=N_FIELDS,
317
                                            use_locking=self.useLocking())
318

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

    
321

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

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

329
    @return: Node role
330

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

    
337
    return _NR_MAP[result[0][0]]
338

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

342
    @return: a job id
343

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

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

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

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

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

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

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

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

    
377
    return baserlib.SubmitJob([op])
378

    
379

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

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

387
    """
388
    node_name = self.items[0]
389
    remote_node = self._checkStringVariable("remote_node", default=None)
390
    iallocator = self._checkStringVariable("iallocator", default=None)
391
    early_r = bool(self._checkIntVariable("early_release", default=0))
392
    dry_run = bool(self.dryRun())
393

    
394
    cl = baserlib.GetClient()
395

    
396
    op = opcodes.OpNodeEvacuationStrategy(nodes=[node_name],
397
                                          iallocator=iallocator,
398
                                          remote_node=remote_node)
399

    
400
    job_id = baserlib.SubmitJob([op], cl)
401
    # we use custom feedback function, instead of print we log the status
402
    result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
403

    
404
    jobs = []
405
    for iname, node in result:
406
      if dry_run:
407
        jid = None
408
      else:
409
        op = opcodes.OpReplaceDisks(instance_name=iname,
410
                                    remote_node=node, disks=[],
411
                                    mode=constants.REPLACE_DISK_CHG,
412
                                    early_release=early_r)
413
        jid = baserlib.SubmitJob([op])
414
      jobs.append((jid, iname, node))
415

    
416
    return jobs
417

    
418

    
419
class R_2_nodes_name_migrate(baserlib.R_Generic):
420
  """/2/nodes/[node_name]/migrate resource.
421

422
  """
423
  def POST(self):
424
    """Migrate all primary instances from a node.
425

426
    """
427
    node_name = self.items[0]
428

    
429
    if "live" in self.queryargs and "mode" in self.queryargs:
430
      raise http.HttpBadRequest("Only one of 'live' and 'mode' should"
431
                                " be passed")
432
    elif "live" in self.queryargs:
433
      if self._checkIntVariable("live", default=1):
434
        mode = constants.HT_MIGRATION_LIVE
435
      else:
436
        mode = constants.HT_MIGRATION_NONLIVE
437
    else:
438
      mode = self._checkStringVariable("mode", default=None)
439

    
440
    op = opcodes.OpMigrateNode(node_name=node_name, mode=mode)
441

    
442
    return baserlib.SubmitJob([op])
443

    
444

    
445
class R_2_nodes_name_storage(baserlib.R_Generic):
446
  """/2/nodes/[node_name]/storage ressource.
447

448
  """
449
  # LUQueryNodeStorage acquires locks, hence restricting access to GET
450
  GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
451

    
452
  def GET(self):
453
    node_name = self.items[0]
454

    
455
    storage_type = self._checkStringVariable("storage_type", None)
456
    if not storage_type:
457
      raise http.HttpBadRequest("Missing the required 'storage_type'"
458
                                " parameter")
459

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

    
465
    op = opcodes.OpQueryNodeStorage(nodes=[node_name],
466
                                    storage_type=storage_type,
467
                                    output_fields=output_fields.split(","))
468
    return baserlib.SubmitJob([op])
469

    
470

    
471
class R_2_nodes_name_storage_modify(baserlib.R_Generic):
472
  """/2/nodes/[node_name]/storage/modify ressource.
473

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

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

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

    
488
    changes = {}
489

    
490
    if "allocatable" in self.queryargs:
491
      changes[constants.SF_ALLOCATABLE] = \
492
        bool(self._checkIntVariable("allocatable", default=1))
493

    
494
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
495
                                     storage_type=storage_type,
496
                                     name=name,
497
                                     changes=changes)
498
    return baserlib.SubmitJob([op])
499

    
500

    
501
class R_2_nodes_name_storage_repair(baserlib.R_Generic):
502
  """/2/nodes/[node_name]/storage/repair ressource.
503

504
  """
505
  def PUT(self):
506
    node_name = self.items[0]
507

    
508
    storage_type = self._checkStringVariable("storage_type", None)
509
    if not storage_type:
510
      raise http.HttpBadRequest("Missing the required 'storage_type'"
511
                                " parameter")
512

    
513
    name = self._checkStringVariable("name", None)
514
    if not name:
515
      raise http.HttpBadRequest("Missing the required 'name'"
516
                                " parameter")
517

    
518
    op = opcodes.OpRepairNodeStorage(node_name=node_name,
519
                                     storage_type=storage_type,
520
                                     name=name)
521
    return baserlib.SubmitJob([op])
522

    
523

    
524
def _ParseInstanceCreateRequestVersion1(data, dry_run):
525
  """Parses an instance creation request version 1.
526

527
  @rtype: L{opcodes.OpCreateInstance}
528
  @return: Instance creation opcode
529

530
  """
531
  # Disks
532
  disks_input = baserlib.CheckParameter(data, "disks", exptype=list)
533

    
534
  disks = []
535
  for idx, i in enumerate(disks_input):
536
    baserlib.CheckType(i, dict, "Disk %d specification" % idx)
537

    
538
    # Size is mandatory
539
    try:
540
      size = i[constants.IDISK_SIZE]
541
    except KeyError:
542
      raise http.HttpBadRequest("Disk %d specification wrong: missing disk"
543
                                " size" % idx)
544

    
545
    disk = {
546
      constants.IDISK_SIZE: size,
547
      }
548

    
549
    # Optional disk access mode
550
    try:
551
      disk_access = i[constants.IDISK_MODE]
552
    except KeyError:
553
      pass
554
    else:
555
      disk[constants.IDISK_MODE] = disk_access
556

    
557
    disks.append(disk)
558

    
559
  assert len(disks_input) == len(disks)
560

    
561
  # Network interfaces
562
  nics_input = baserlib.CheckParameter(data, "nics", exptype=list)
563

    
564
  nics = []
565
  for idx, i in enumerate(nics_input):
566
    baserlib.CheckType(i, dict, "NIC %d specification" % idx)
567

    
568
    nic = {}
569

    
570
    for field in constants.INIC_PARAMS:
571
      try:
572
        value = i[field]
573
      except KeyError:
574
        continue
575

    
576
      nic[field] = value
577

    
578
    nics.append(nic)
579

    
580
  assert len(nics_input) == len(nics)
581

    
582
  # HV/BE parameters
583
  hvparams = baserlib.CheckParameter(data, "hvparams", default={})
584
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
585

    
586
  beparams = baserlib.CheckParameter(data, "beparams", default={})
587
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
588

    
589
  return opcodes.OpCreateInstance(
590
    mode=baserlib.CheckParameter(data, "mode"),
591
    instance_name=baserlib.CheckParameter(data, "name"),
592
    os_type=baserlib.CheckParameter(data, "os"),
593
    osparams=baserlib.CheckParameter(data, "osparams", default={}),
594
    force_variant=baserlib.CheckParameter(data, "force_variant",
595
                                          default=False),
596
    pnode=baserlib.CheckParameter(data, "pnode", default=None),
597
    snode=baserlib.CheckParameter(data, "snode", default=None),
598
    disk_template=baserlib.CheckParameter(data, "disk_template"),
599
    disks=disks,
600
    nics=nics,
601
    src_node=baserlib.CheckParameter(data, "src_node", default=None),
602
    src_path=baserlib.CheckParameter(data, "src_path", default=None),
603
    start=baserlib.CheckParameter(data, "start", default=True),
604
    wait_for_sync=True,
605
    ip_check=baserlib.CheckParameter(data, "ip_check", default=True),
606
    name_check=baserlib.CheckParameter(data, "name_check", default=True),
607
    file_storage_dir=baserlib.CheckParameter(data, "file_storage_dir",
608
                                             default=None),
609
    file_driver=baserlib.CheckParameter(data, "file_driver",
610
                                        default=constants.FD_LOOP),
611
    source_handshake=baserlib.CheckParameter(data, "source_handshake",
612
                                             default=None),
613
    source_x509_ca=baserlib.CheckParameter(data, "source_x509_ca",
614
                                           default=None),
615
    source_instance_name=baserlib.CheckParameter(data, "source_instance_name",
616
                                                 default=None),
617
    iallocator=baserlib.CheckParameter(data, "iallocator", default=None),
618
    hypervisor=baserlib.CheckParameter(data, "hypervisor", default=None),
619
    hvparams=hvparams,
620
    beparams=beparams,
621
    dry_run=dry_run,
622
    )
623

    
624

    
625
class R_2_instances(baserlib.R_Generic):
626
  """/2/instances resource.
627

628
  """
629
  def GET(self):
630
    """Returns a list of all available instances.
631

632
    """
633
    client = baserlib.GetClient()
634

    
635
    use_locking = self.useLocking()
636
    if self.useBulk():
637
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
638
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
639
    else:
640
      instancesdata = client.QueryInstances([], ["name"], use_locking)
641
      instanceslist = [row[0] for row in instancesdata]
642
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
643
                                   uri_fields=("id", "uri"))
644

    
645
  def _ParseVersion0CreateRequest(self):
646
    """Parses an instance creation request version 0.
647

648
    Request data version 0 is deprecated and should not be used anymore.
649

650
    @rtype: L{opcodes.OpCreateInstance}
651
    @return: Instance creation opcode
652

653
    """
654
    # Do not modify anymore, request data version 0 is deprecated
655
    beparams = baserlib.MakeParamsDict(self.request_body,
656
                                       constants.BES_PARAMETERS)
657
    hvparams = baserlib.MakeParamsDict(self.request_body,
658
                                       constants.HVS_PARAMETERS)
659
    fn = self.getBodyParameter
660

    
661
    # disk processing
662
    disk_data = fn('disks')
663
    if not isinstance(disk_data, list):
664
      raise http.HttpBadRequest("The 'disks' parameter should be a list")
665
    disks = []
666
    for idx, d in enumerate(disk_data):
667
      if not isinstance(d, int):
668
        raise http.HttpBadRequest("Disk %d specification wrong: should"
669
                                  " be an integer" % idx)
670
      disks.append({"size": d})
671

    
672
    # nic processing (one nic only)
673
    nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
674
    if fn("ip", None) is not None:
675
      nics[0]["ip"] = fn("ip")
676
    if fn("mode", None) is not None:
677
      nics[0]["mode"] = fn("mode")
678
    if fn("link", None) is not None:
679
      nics[0]["link"] = fn("link")
680
    if fn("bridge", None) is not None:
681
      nics[0]["bridge"] = fn("bridge")
682

    
683
    # Do not modify anymore, request data version 0 is deprecated
684
    return opcodes.OpCreateInstance(
685
      mode=constants.INSTANCE_CREATE,
686
      instance_name=fn('name'),
687
      disks=disks,
688
      disk_template=fn('disk_template'),
689
      os_type=fn('os'),
690
      pnode=fn('pnode', None),
691
      snode=fn('snode', None),
692
      iallocator=fn('iallocator', None),
693
      nics=nics,
694
      start=fn('start', True),
695
      ip_check=fn('ip_check', True),
696
      name_check=fn('name_check', True),
697
      wait_for_sync=True,
698
      hypervisor=fn('hypervisor', None),
699
      hvparams=hvparams,
700
      beparams=beparams,
701
      file_storage_dir=fn('file_storage_dir', None),
702
      file_driver=fn('file_driver', constants.FD_LOOP),
703
      dry_run=bool(self.dryRun()),
704
      )
705

    
706
  def POST(self):
707
    """Create an instance.
708

709
    @return: a job id
710

711
    """
712
    if not isinstance(self.request_body, dict):
713
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
714

    
715
    # Default to request data version 0
716
    data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0)
717

    
718
    if data_version == 0:
719
      op = self._ParseVersion0CreateRequest()
720
    elif data_version == 1:
721
      op = _ParseInstanceCreateRequestVersion1(self.request_body,
722
                                               self.dryRun())
723
    else:
724
      raise http.HttpBadRequest("Unsupported request data version %s" %
725
                                data_version)
726

    
727
    return baserlib.SubmitJob([op])
728

    
729

    
730
class R_2_instances_name(baserlib.R_Generic):
731
  """/2/instances/[instance_name] resources.
732

733
  """
734
  def GET(self):
735
    """Send information about an instance.
736

737
    """
738
    client = baserlib.GetClient()
739
    instance_name = self.items[0]
740

    
741
    result = baserlib.HandleItemQueryErrors(client.QueryInstances,
742
                                            names=[instance_name],
743
                                            fields=I_FIELDS,
744
                                            use_locking=self.useLocking())
745

    
746
    return baserlib.MapFields(I_FIELDS, result[0])
747

    
748
  def DELETE(self):
749
    """Delete an instance.
750

751
    """
752
    op = opcodes.OpRemoveInstance(instance_name=self.items[0],
753
                                  ignore_failures=False,
754
                                  dry_run=bool(self.dryRun()))
755
    return baserlib.SubmitJob([op])
756

    
757

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

761
  """
762
  def GET(self):
763
    """Request detailed instance information.
764

765
    """
766
    instance_name = self.items[0]
767
    static = bool(self._checkIntVariable("static", default=0))
768

    
769
    op = opcodes.OpQueryInstanceData(instances=[instance_name],
770
                                     static=static)
771
    return baserlib.SubmitJob([op])
772

    
773

    
774
class R_2_instances_name_reboot(baserlib.R_Generic):
775
  """/2/instances/[instance_name]/reboot resource.
776

777
  Implements an instance reboot.
778

779
  """
780
  def POST(self):
781
    """Reboot an instance.
782

783
    The URI takes type=[hard|soft|full] and
784
    ignore_secondaries=[False|True] parameters.
785

786
    """
787
    instance_name = self.items[0]
788
    reboot_type = self.queryargs.get('type',
789
                                     [constants.INSTANCE_REBOOT_HARD])[0]
790
    ignore_secondaries = bool(self._checkIntVariable('ignore_secondaries'))
791
    op = opcodes.OpRebootInstance(instance_name=instance_name,
792
                                  reboot_type=reboot_type,
793
                                  ignore_secondaries=ignore_secondaries,
794
                                  dry_run=bool(self.dryRun()))
795

    
796
    return baserlib.SubmitJob([op])
797

    
798

    
799
class R_2_instances_name_startup(baserlib.R_Generic):
800
  """/2/instances/[instance_name]/startup resource.
801

802
  Implements an instance startup.
803

804
  """
805
  def PUT(self):
806
    """Startup an instance.
807

808
    The URI takes force=[False|True] parameter to start the instance
809
    if even if secondary disks are failing.
810

811
    """
812
    instance_name = self.items[0]
813
    force_startup = bool(self._checkIntVariable('force'))
814
    op = opcodes.OpStartupInstance(instance_name=instance_name,
815
                                   force=force_startup,
816
                                   dry_run=bool(self.dryRun()))
817

    
818
    return baserlib.SubmitJob([op])
819

    
820

    
821
class R_2_instances_name_shutdown(baserlib.R_Generic):
822
  """/2/instances/[instance_name]/shutdown resource.
823

824
  Implements an instance shutdown.
825

826
  """
827
  def PUT(self):
828
    """Shutdown an instance.
829

830
    """
831
    instance_name = self.items[0]
832
    op = opcodes.OpShutdownInstance(instance_name=instance_name,
833
                                    dry_run=bool(self.dryRun()))
834

    
835
    return baserlib.SubmitJob([op])
836

    
837

    
838
class R_2_instances_name_reinstall(baserlib.R_Generic):
839
  """/2/instances/[instance_name]/reinstall resource.
840

841
  Implements an instance reinstall.
842

843
  """
844
  def POST(self):
845
    """Reinstall an instance.
846

847
    The URI takes os=name and nostartup=[0|1] optional
848
    parameters. By default, the instance will be started
849
    automatically.
850

851
    """
852
    instance_name = self.items[0]
853
    ostype = self._checkStringVariable('os')
854
    nostartup = self._checkIntVariable('nostartup')
855
    ops = [
856
      opcodes.OpShutdownInstance(instance_name=instance_name),
857
      opcodes.OpReinstallInstance(instance_name=instance_name, os_type=ostype),
858
      ]
859
    if not nostartup:
860
      ops.append(opcodes.OpStartupInstance(instance_name=instance_name,
861
                                           force=False))
862
    return baserlib.SubmitJob(ops)
863

    
864

    
865
class R_2_instances_name_replace_disks(baserlib.R_Generic):
866
  """/2/instances/[instance_name]/replace-disks resource.
867

868
  """
869
  def POST(self):
870
    """Replaces disks on an instance.
871

872
    """
873
    instance_name = self.items[0]
874
    remote_node = self._checkStringVariable("remote_node", default=None)
875
    mode = self._checkStringVariable("mode", default=None)
876
    raw_disks = self._checkStringVariable("disks", default=None)
877
    iallocator = self._checkStringVariable("iallocator", default=None)
878

    
879
    if raw_disks:
880
      try:
881
        disks = [int(part) for part in raw_disks.split(",")]
882
      except ValueError, err:
883
        raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
884
    else:
885
      disks = []
886

    
887
    op = opcodes.OpReplaceDisks(instance_name=instance_name,
888
                                remote_node=remote_node,
889
                                mode=mode,
890
                                disks=disks,
891
                                iallocator=iallocator)
892

    
893
    return baserlib.SubmitJob([op])
894

    
895

    
896
class R_2_instances_name_activate_disks(baserlib.R_Generic):
897
  """/2/instances/[instance_name]/activate-disks resource.
898

899
  """
900
  def PUT(self):
901
    """Activate disks for an instance.
902

903
    The URI might contain ignore_size to ignore current recorded size.
904

905
    """
906
    instance_name = self.items[0]
907
    ignore_size = bool(self._checkIntVariable('ignore_size'))
908

    
909
    op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
910
                                         ignore_size=ignore_size)
911

    
912
    return baserlib.SubmitJob([op])
913

    
914

    
915
class R_2_instances_name_deactivate_disks(baserlib.R_Generic):
916
  """/2/instances/[instance_name]/deactivate-disks resource.
917

918
  """
919
  def PUT(self):
920
    """Deactivate disks for an instance.
921

922
    """
923
    instance_name = self.items[0]
924

    
925
    op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
926

    
927
    return baserlib.SubmitJob([op])
928

    
929

    
930
class R_2_instances_name_prepare_export(baserlib.R_Generic):
931
  """/2/instances/[instance_name]/prepare-export resource.
932

933
  """
934
  def PUT(self):
935
    """Prepares an export for an instance.
936

937
    @return: a job id
938

939
    """
940
    instance_name = self.items[0]
941
    mode = self._checkStringVariable("mode")
942

    
943
    op = opcodes.OpPrepareExport(instance_name=instance_name,
944
                                 mode=mode)
945

    
946
    return baserlib.SubmitJob([op])
947

    
948

    
949
def _ParseExportInstanceRequest(name, data):
950
  """Parses a request for an instance export.
951

952
  @rtype: L{opcodes.OpExportInstance}
953
  @return: Instance export opcode
954

955
  """
956
  mode = baserlib.CheckParameter(data, "mode",
957
                                 default=constants.EXPORT_MODE_LOCAL)
958
  target_node = baserlib.CheckParameter(data, "destination")
959
  shutdown = baserlib.CheckParameter(data, "shutdown", exptype=bool)
960
  remove_instance = baserlib.CheckParameter(data, "remove_instance",
961
                                            exptype=bool, default=False)
962
  x509_key_name = baserlib.CheckParameter(data, "x509_key_name", default=None)
963
  destination_x509_ca = baserlib.CheckParameter(data, "destination_x509_ca",
964
                                                default=None)
965

    
966
  return opcodes.OpExportInstance(instance_name=name,
967
                                  mode=mode,
968
                                  target_node=target_node,
969
                                  shutdown=shutdown,
970
                                  remove_instance=remove_instance,
971
                                  x509_key_name=x509_key_name,
972
                                  destination_x509_ca=destination_x509_ca)
973

    
974

    
975
class R_2_instances_name_export(baserlib.R_Generic):
976
  """/2/instances/[instance_name]/export resource.
977

978
  """
979
  def PUT(self):
980
    """Exports an instance.
981

982
    @return: a job id
983

984
    """
985
    if not isinstance(self.request_body, dict):
986
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
987

    
988
    op = _ParseExportInstanceRequest(self.items[0], self.request_body)
989

    
990
    return baserlib.SubmitJob([op])
991

    
992

    
993
def _ParseMigrateInstanceRequest(name, data):
994
  """Parses a request for an instance migration.
995

996
  @rtype: L{opcodes.OpMigrateInstance}
997
  @return: Instance migration opcode
998

999
  """
1000
  mode = baserlib.CheckParameter(data, "mode", default=None)
1001
  cleanup = baserlib.CheckParameter(data, "cleanup", exptype=bool,
1002
                                    default=False)
1003

    
1004
  return opcodes.OpMigrateInstance(instance_name=name, mode=mode,
1005
                                   cleanup=cleanup)
1006

    
1007

    
1008
class R_2_instances_name_migrate(baserlib.R_Generic):
1009
  """/2/instances/[instance_name]/migrate resource.
1010

1011
  """
1012
  def PUT(self):
1013
    """Migrates an instance.
1014

1015
    @return: a job id
1016

1017
    """
1018
    baserlib.CheckType(self.request_body, dict, "Body contents")
1019

    
1020
    op = _ParseMigrateInstanceRequest(self.items[0], self.request_body)
1021

    
1022
    return baserlib.SubmitJob([op])
1023

    
1024

    
1025
def _ParseRenameInstanceRequest(name, data):
1026
  """Parses a request for renaming an instance.
1027

1028
  @rtype: L{opcodes.OpRenameInstance}
1029
  @return: Instance rename opcode
1030

1031
  """
1032
  new_name = baserlib.CheckParameter(data, "new_name")
1033
  ip_check = baserlib.CheckParameter(data, "ip_check", default=True)
1034
  name_check = baserlib.CheckParameter(data, "name_check", default=True)
1035

    
1036
  return opcodes.OpRenameInstance(instance_name=name, new_name=new_name,
1037
                                  name_check=name_check, ip_check=ip_check)
1038

    
1039

    
1040
class R_2_instances_name_rename(baserlib.R_Generic):
1041
  """/2/instances/[instance_name]/rename resource.
1042

1043
  """
1044
  def PUT(self):
1045
    """Changes the name of an instance.
1046

1047
    @return: a job id
1048

1049
    """
1050
    baserlib.CheckType(self.request_body, dict, "Body contents")
1051

    
1052
    op = _ParseRenameInstanceRequest(self.items[0], self.request_body)
1053

    
1054
    return baserlib.SubmitJob([op])
1055

    
1056

    
1057
def _ParseModifyInstanceRequest(name, data):
1058
  """Parses a request for modifying an instance.
1059

1060
  @rtype: L{opcodes.OpSetInstanceParams}
1061
  @return: Instance modify opcode
1062

1063
  """
1064
  osparams = baserlib.CheckParameter(data, "osparams", default={})
1065
  force = baserlib.CheckParameter(data, "force", default=False)
1066
  nics = baserlib.CheckParameter(data, "nics", default=[])
1067
  disks = baserlib.CheckParameter(data, "disks", default=[])
1068
  disk_template = baserlib.CheckParameter(data, "disk_template", default=None)
1069
  remote_node = baserlib.CheckParameter(data, "remote_node", default=None)
1070
  os_name = baserlib.CheckParameter(data, "os_name", default=None)
1071
  force_variant = baserlib.CheckParameter(data, "force_variant", default=False)
1072

    
1073
  # HV/BE parameters
1074
  hvparams = baserlib.CheckParameter(data, "hvparams", default={})
1075
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES,
1076
                      allowed_values=[constants.VALUE_DEFAULT])
1077

    
1078
  beparams = baserlib.CheckParameter(data, "beparams", default={})
1079
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES,
1080
                      allowed_values=[constants.VALUE_DEFAULT])
1081

    
1082
  return opcodes.OpSetInstanceParams(instance_name=name, hvparams=hvparams,
1083
                                     beparams=beparams, osparams=osparams,
1084
                                     force=force, nics=nics, disks=disks,
1085
                                     disk_template=disk_template,
1086
                                     remote_node=remote_node, os_name=os_name,
1087
                                     force_variant=force_variant)
1088

    
1089

    
1090
class R_2_instances_name_modify(baserlib.R_Generic):
1091
  """/2/instances/[instance_name]/modify resource.
1092

1093
  """
1094
  def PUT(self):
1095
    """Changes some parameters of an instance.
1096

1097
    @return: a job id
1098

1099
    """
1100
    baserlib.CheckType(self.request_body, dict, "Body contents")
1101

    
1102
    op = _ParseModifyInstanceRequest(self.items[0], self.request_body)
1103

    
1104
    return baserlib.SubmitJob([op])
1105

    
1106

    
1107
class _R_Tags(baserlib.R_Generic):
1108
  """ Quasiclass for tagging resources
1109

1110
  Manages tags. When inheriting this class you must define the
1111
  TAG_LEVEL for it.
1112

1113
  """
1114
  TAG_LEVEL = None
1115

    
1116
  def __init__(self, items, queryargs, req):
1117
    """A tag resource constructor.
1118

1119
    We have to override the default to sort out cluster naming case.
1120

1121
    """
1122
    baserlib.R_Generic.__init__(self, items, queryargs, req)
1123

    
1124
    if self.TAG_LEVEL == constants.TAG_CLUSTER:
1125
      self.name = None
1126
    else:
1127
      self.name = items[0]
1128

    
1129
  def GET(self):
1130
    """Returns a list of tags.
1131

1132
    Example: ["tag1", "tag2", "tag3"]
1133

1134
    """
1135
    # pylint: disable-msg=W0212
1136
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
1137

    
1138
  def PUT(self):
1139
    """Add a set of tags.
1140

1141
    The request as a list of strings should be PUT to this URI. And
1142
    you'll have back a job id.
1143

1144
    """
1145
    # pylint: disable-msg=W0212
1146
    if 'tag' not in self.queryargs:
1147
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
1148
                                " the 'tag' parameter")
1149
    return baserlib._Tags_PUT(self.TAG_LEVEL,
1150
                              self.queryargs['tag'], name=self.name,
1151
                              dry_run=bool(self.dryRun()))
1152

    
1153
  def DELETE(self):
1154
    """Delete a tag.
1155

1156
    In order to delete a set of tags, the DELETE
1157
    request should be addressed to URI like:
1158
    /tags?tag=[tag]&tag=[tag]
1159

1160
    """
1161
    # pylint: disable-msg=W0212
1162
    if 'tag' not in self.queryargs:
1163
      # no we not gonna delete all tags
1164
      raise http.HttpBadRequest("Cannot delete all tags - please specify"
1165
                                " tag(s) using the 'tag' parameter")
1166
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
1167
                                 self.queryargs['tag'],
1168
                                 name=self.name,
1169
                                 dry_run=bool(self.dryRun()))
1170

    
1171

    
1172
class R_2_instances_name_tags(_R_Tags):
1173
  """ /2/instances/[instance_name]/tags resource.
1174

1175
  Manages per-instance tags.
1176

1177
  """
1178
  TAG_LEVEL = constants.TAG_INSTANCE
1179

    
1180

    
1181
class R_2_nodes_name_tags(_R_Tags):
1182
  """ /2/nodes/[node_name]/tags resource.
1183

1184
  Manages per-node tags.
1185

1186
  """
1187
  TAG_LEVEL = constants.TAG_NODE
1188

    
1189

    
1190
class R_2_tags(_R_Tags):
1191
  """ /2/instances/tags resource.
1192

1193
  Manages cluster tags.
1194

1195
  """
1196
  TAG_LEVEL = constants.TAG_CLUSTER