Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 0897dc97

History | View | Annotate | Download (37.1 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
            "master_capable", "vm_capable",
73
            "group.uuid",
74
            ] + _COMMON_FIELDS
75

    
76
G_FIELDS = ["name", "uuid",
77
            "node_cnt", "node_list",
78
            ]
79

    
80
_NR_DRAINED = "drained"
81
_NR_MASTER_CANDIATE = "master-candidate"
82
_NR_MASTER = "master"
83
_NR_OFFLINE = "offline"
84
_NR_REGULAR = "regular"
85

    
86
_NR_MAP = {
87
  "M": _NR_MASTER,
88
  "C": _NR_MASTER_CANDIATE,
89
  "D": _NR_DRAINED,
90
  "O": _NR_OFFLINE,
91
  "R": _NR_REGULAR,
92
  }
93

    
94
# Request data version field
95
_REQ_DATA_VERSION = "__version__"
96

    
97
# Feature string for instance creation request data version 1
98
_INST_CREATE_REQV1 = "instance-create-reqv1"
99

    
100
# Feature string for instance reinstall request version 1
101
_INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
102

    
103
# Timeout for /2/jobs/[job_id]/wait. Gives job up to 10 seconds to change.
104
_WFJC_TIMEOUT = 10
105

    
106

    
107
class R_version(baserlib.R_Generic):
108
  """/version resource.
109

110
  This resource should be used to determine the remote API version and
111
  to adapt clients accordingly.
112

113
  """
114
  @staticmethod
115
  def GET():
116
    """Returns the remote API version.
117

118
    """
119
    return constants.RAPI_VERSION
120

    
121

    
122
class R_2_info(baserlib.R_Generic):
123
  """Cluster info.
124

125
  """
126
  @staticmethod
127
  def GET():
128
    """Returns cluster information.
129

130
    """
131
    client = baserlib.GetClient()
132
    return client.QueryClusterInfo()
133

    
134

    
135
class R_2_features(baserlib.R_Generic):
136
  """/2/features resource.
137

138
  """
139
  @staticmethod
140
  def GET():
141
    """Returns list of optional RAPI features implemented.
142

143
    """
144
    return [_INST_CREATE_REQV1, _INST_REINSTALL_REQV1]
145

    
146

    
147
class R_2_os(baserlib.R_Generic):
148
  """/2/os resource.
149

150
  """
151
  @staticmethod
152
  def GET():
153
    """Return a list of all OSes.
154

155
    Can return error 500 in case of a problem.
156

157
    Example: ["debian-etch"]
158

159
    """
160
    cl = baserlib.GetClient()
161
    op = opcodes.OpDiagnoseOS(output_fields=["name", "variants"], names=[])
162
    job_id = baserlib.SubmitJob([op], cl)
163
    # we use custom feedback function, instead of print we log the status
164
    result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
165
    diagnose_data = result[0]
166

    
167
    if not isinstance(diagnose_data, list):
168
      raise http.HttpBadGateway(message="Can't get OS list")
169

    
170
    os_names = []
171
    for (name, variants) in diagnose_data:
172
      os_names.extend(cli.CalculateOSNames(name, variants))
173

    
174
    return os_names
175

    
176

    
177
class R_2_redist_config(baserlib.R_Generic):
178
  """/2/redistribute-config resource.
179

180
  """
181
  @staticmethod
182
  def PUT():
183
    """Redistribute configuration to all nodes.
184

185
    """
186
    return baserlib.SubmitJob([opcodes.OpRedistributeConfig()])
187

    
188

    
189
class R_2_jobs(baserlib.R_Generic):
190
  """/2/jobs resource.
191

192
  """
193
  @staticmethod
194
  def GET():
195
    """Returns a dictionary of jobs.
196

197
    @return: a dictionary with jobs id and uri.
198

199
    """
200
    fields = ["id"]
201
    cl = baserlib.GetClient()
202
    # Convert the list of lists to the list of ids
203
    result = [job_id for [job_id] in cl.QueryJobs(None, fields)]
204
    return baserlib.BuildUriList(result, "/2/jobs/%s",
205
                                 uri_fields=("id", "uri"))
206

    
207

    
208
class R_2_jobs_id(baserlib.R_Generic):
209
  """/2/jobs/[job_id] resource.
210

211
  """
212
  def GET(self):
213
    """Returns a job status.
214

215
    @return: a dictionary with job parameters.
216
        The result includes:
217
            - id: job ID as a number
218
            - status: current job status as a string
219
            - ops: involved OpCodes as a list of dictionaries for each
220
              opcodes in the job
221
            - opstatus: OpCodes status as a list
222
            - opresult: OpCodes results as a list of lists
223

224
    """
225
    fields = ["id", "ops", "status", "summary",
226
              "opstatus", "opresult", "oplog",
227
              "received_ts", "start_ts", "end_ts",
228
              ]
229
    job_id = self.items[0]
230
    result = baserlib.GetClient().QueryJobs([job_id, ], fields)[0]
231
    if result is None:
232
      raise http.HttpNotFound()
233
    return baserlib.MapFields(fields, result)
234

    
235
  def DELETE(self):
236
    """Cancel not-yet-started job.
237

238
    """
239
    job_id = self.items[0]
240
    result = baserlib.GetClient().CancelJob(job_id)
241
    return result
242

    
243

    
244
class R_2_jobs_id_wait(baserlib.R_Generic):
245
  """/2/jobs/[job_id]/wait resource.
246

247
  """
248
  # WaitForJobChange provides access to sensitive information and blocks
249
  # machine resources (it's a blocking RAPI call), hence restricting access.
250
  GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
251

    
252
  def GET(self):
253
    """Waits for job changes.
254

255
    """
256
    job_id = self.items[0]
257

    
258
    fields = self.getBodyParameter("fields")
259
    prev_job_info = self.getBodyParameter("previous_job_info", None)
260
    prev_log_serial = self.getBodyParameter("previous_log_serial", None)
261

    
262
    if not isinstance(fields, list):
263
      raise http.HttpBadRequest("The 'fields' parameter should be a list")
264

    
265
    if not (prev_job_info is None or isinstance(prev_job_info, list)):
266
      raise http.HttpBadRequest("The 'previous_job_info' parameter should"
267
                                " be a list")
268

    
269
    if not (prev_log_serial is None or
270
            isinstance(prev_log_serial, (int, long))):
271
      raise http.HttpBadRequest("The 'previous_log_serial' parameter should"
272
                                " be a number")
273

    
274
    client = baserlib.GetClient()
275
    result = client.WaitForJobChangeOnce(job_id, fields,
276
                                         prev_job_info, prev_log_serial,
277
                                         timeout=_WFJC_TIMEOUT)
278
    if not result:
279
      raise http.HttpNotFound()
280

    
281
    if result == constants.JOB_NOTCHANGED:
282
      # No changes
283
      return None
284

    
285
    (job_info, log_entries) = result
286

    
287
    return {
288
      "job_info": job_info,
289
      "log_entries": log_entries,
290
      }
291

    
292

    
293
class R_2_nodes(baserlib.R_Generic):
294
  """/2/nodes resource.
295

296
  """
297
  def GET(self):
298
    """Returns a list of all nodes.
299

300
    """
301
    client = baserlib.GetClient()
302

    
303
    if self.useBulk():
304
      bulkdata = client.QueryNodes([], N_FIELDS, False)
305
      return baserlib.MapBulkFields(bulkdata, N_FIELDS)
306
    else:
307
      nodesdata = client.QueryNodes([], ["name"], False)
308
      nodeslist = [row[0] for row in nodesdata]
309
      return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
310
                                   uri_fields=("id", "uri"))
311

    
312

    
313
class R_2_nodes_name(baserlib.R_Generic):
314
  """/2/nodes/[node_name] resources.
315

316
  """
317
  def GET(self):
318
    """Send information about a node.
319

320
    """
321
    node_name = self.items[0]
322
    client = baserlib.GetClient()
323

    
324
    result = baserlib.HandleItemQueryErrors(client.QueryNodes,
325
                                            names=[node_name], fields=N_FIELDS,
326
                                            use_locking=self.useLocking())
327

    
328
    return baserlib.MapFields(N_FIELDS, result[0])
329

    
330

    
331
class R_2_nodes_name_role(baserlib.R_Generic):
332
  """ /2/nodes/[node_name]/role resource.
333

334
  """
335
  def GET(self):
336
    """Returns the current node role.
337

338
    @return: Node role
339

340
    """
341
    node_name = self.items[0]
342
    client = baserlib.GetClient()
343
    result = client.QueryNodes(names=[node_name], fields=["role"],
344
                               use_locking=self.useLocking())
345

    
346
    return _NR_MAP[result[0][0]]
347

    
348
  def PUT(self):
349
    """Sets the node role.
350

351
    @return: a job id
352

353
    """
354
    if not isinstance(self.request_body, basestring):
355
      raise http.HttpBadRequest("Invalid body contents, not a string")
356

    
357
    node_name = self.items[0]
358
    role = self.request_body
359

    
360
    if role == _NR_REGULAR:
361
      candidate = False
362
      offline = False
363
      drained = False
364

    
365
    elif role == _NR_MASTER_CANDIATE:
366
      candidate = True
367
      offline = drained = None
368

    
369
    elif role == _NR_DRAINED:
370
      drained = True
371
      candidate = offline = None
372

    
373
    elif role == _NR_OFFLINE:
374
      offline = True
375
      candidate = drained = None
376

    
377
    else:
378
      raise http.HttpBadRequest("Can't set '%s' role" % role)
379

    
380
    op = opcodes.OpSetNodeParams(node_name=node_name,
381
                                 master_candidate=candidate,
382
                                 offline=offline,
383
                                 drained=drained,
384
                                 force=bool(self.useForce()))
385

    
386
    return baserlib.SubmitJob([op])
387

    
388

    
389
class R_2_nodes_name_evacuate(baserlib.R_Generic):
390
  """/2/nodes/[node_name]/evacuate resource.
391

392
  """
393
  def POST(self):
394
    """Evacuate all secondary instances off a node.
395

396
    """
397
    node_name = self.items[0]
398
    remote_node = self._checkStringVariable("remote_node", default=None)
399
    iallocator = self._checkStringVariable("iallocator", default=None)
400
    early_r = bool(self._checkIntVariable("early_release", default=0))
401
    dry_run = bool(self.dryRun())
402

    
403
    cl = baserlib.GetClient()
404

    
405
    op = opcodes.OpNodeEvacuationStrategy(nodes=[node_name],
406
                                          iallocator=iallocator,
407
                                          remote_node=remote_node)
408

    
409
    job_id = baserlib.SubmitJob([op], cl)
410
    # we use custom feedback function, instead of print we log the status
411
    result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
412

    
413
    jobs = []
414
    for iname, node in result:
415
      if dry_run:
416
        jid = None
417
      else:
418
        op = opcodes.OpReplaceDisks(instance_name=iname,
419
                                    remote_node=node, disks=[],
420
                                    mode=constants.REPLACE_DISK_CHG,
421
                                    early_release=early_r)
422
        jid = baserlib.SubmitJob([op])
423
      jobs.append((jid, iname, node))
424

    
425
    return jobs
426

    
427

    
428
class R_2_nodes_name_migrate(baserlib.R_Generic):
429
  """/2/nodes/[node_name]/migrate resource.
430

431
  """
432
  def POST(self):
433
    """Migrate all primary instances from a node.
434

435
    """
436
    node_name = self.items[0]
437

    
438
    if "live" in self.queryargs and "mode" in self.queryargs:
439
      raise http.HttpBadRequest("Only one of 'live' and 'mode' should"
440
                                " be passed")
441
    elif "live" in self.queryargs:
442
      if self._checkIntVariable("live", default=1):
443
        mode = constants.HT_MIGRATION_LIVE
444
      else:
445
        mode = constants.HT_MIGRATION_NONLIVE
446
    else:
447
      mode = self._checkStringVariable("mode", default=None)
448

    
449
    op = opcodes.OpMigrateNode(node_name=node_name, mode=mode)
450

    
451
    return baserlib.SubmitJob([op])
452

    
453

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

457
  """
458
  # LUQueryNodeStorage acquires locks, hence restricting access to GET
459
  GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
460

    
461
  def GET(self):
462
    node_name = self.items[0]
463

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

    
469
    output_fields = self._checkStringVariable("output_fields", None)
470
    if not output_fields:
471
      raise http.HttpBadRequest("Missing the required 'output_fields'"
472
                                " parameter")
473

    
474
    op = opcodes.OpQueryNodeStorage(nodes=[node_name],
475
                                    storage_type=storage_type,
476
                                    output_fields=output_fields.split(","))
477
    return baserlib.SubmitJob([op])
478

    
479

    
480
class R_2_nodes_name_storage_modify(baserlib.R_Generic):
481
  """/2/nodes/[node_name]/storage/modify ressource.
482

483
  """
484
  def PUT(self):
485
    node_name = self.items[0]
486

    
487
    storage_type = self._checkStringVariable("storage_type", None)
488
    if not storage_type:
489
      raise http.HttpBadRequest("Missing the required 'storage_type'"
490
                                " parameter")
491

    
492
    name = self._checkStringVariable("name", None)
493
    if not name:
494
      raise http.HttpBadRequest("Missing the required 'name'"
495
                                " parameter")
496

    
497
    changes = {}
498

    
499
    if "allocatable" in self.queryargs:
500
      changes[constants.SF_ALLOCATABLE] = \
501
        bool(self._checkIntVariable("allocatable", default=1))
502

    
503
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
504
                                     storage_type=storage_type,
505
                                     name=name,
506
                                     changes=changes)
507
    return baserlib.SubmitJob([op])
508

    
509

    
510
class R_2_nodes_name_storage_repair(baserlib.R_Generic):
511
  """/2/nodes/[node_name]/storage/repair ressource.
512

513
  """
514
  def PUT(self):
515
    node_name = self.items[0]
516

    
517
    storage_type = self._checkStringVariable("storage_type", None)
518
    if not storage_type:
519
      raise http.HttpBadRequest("Missing the required 'storage_type'"
520
                                " parameter")
521

    
522
    name = self._checkStringVariable("name", None)
523
    if not name:
524
      raise http.HttpBadRequest("Missing the required 'name'"
525
                                " parameter")
526

    
527
    op = opcodes.OpRepairNodeStorage(node_name=node_name,
528
                                     storage_type=storage_type,
529
                                     name=name)
530
    return baserlib.SubmitJob([op])
531

    
532

    
533
class R_2_groups(baserlib.R_Generic):
534
  """/2/groups resource.
535

536
  """
537
  def GET(self):
538
    """Returns a list of all node groups.
539

540
    """
541
    client = baserlib.GetClient()
542

    
543
    if self.useBulk():
544
      bulkdata = client.QueryGroups([], G_FIELDS, False)
545
      return baserlib.MapBulkFields(bulkdata, G_FIELDS)
546
    else:
547
      data = client.QueryGroups([], ["name"], False)
548
      groupnames = [row[0] for row in data]
549
      return baserlib.BuildUriList(groupnames, "/2/groups/%s",
550
                                   uri_fields=("name", "uri"))
551

    
552

    
553
class R_2_groups_name(baserlib.R_Generic):
554
  """/2/groups/[group_name] resources.
555

556
  """
557
  def GET(self):
558
    """Send information about a node group.
559

560
    """
561
    group_name = self.items[0]
562
    client = baserlib.GetClient()
563

    
564
    result = baserlib.HandleItemQueryErrors(client.QueryGroups,
565
                                            names=[group_name], fields=G_FIELDS,
566
                                            use_locking=self.useLocking())
567

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

    
570

    
571

    
572
def _ParseInstanceCreateRequestVersion1(data, dry_run):
573
  """Parses an instance creation request version 1.
574

575
  @rtype: L{opcodes.OpCreateInstance}
576
  @return: Instance creation opcode
577

578
  """
579
  # Disks
580
  disks_input = baserlib.CheckParameter(data, "disks", exptype=list)
581

    
582
  disks = []
583
  for idx, i in enumerate(disks_input):
584
    baserlib.CheckType(i, dict, "Disk %d specification" % idx)
585

    
586
    # Size is mandatory
587
    try:
588
      size = i[constants.IDISK_SIZE]
589
    except KeyError:
590
      raise http.HttpBadRequest("Disk %d specification wrong: missing disk"
591
                                " size" % idx)
592

    
593
    disk = {
594
      constants.IDISK_SIZE: size,
595
      }
596

    
597
    # Optional disk access mode
598
    try:
599
      disk_access = i[constants.IDISK_MODE]
600
    except KeyError:
601
      pass
602
    else:
603
      disk[constants.IDISK_MODE] = disk_access
604

    
605
    disks.append(disk)
606

    
607
  assert len(disks_input) == len(disks)
608

    
609
  # Network interfaces
610
  nics_input = baserlib.CheckParameter(data, "nics", exptype=list)
611

    
612
  nics = []
613
  for idx, i in enumerate(nics_input):
614
    baserlib.CheckType(i, dict, "NIC %d specification" % idx)
615

    
616
    nic = {}
617

    
618
    for field in constants.INIC_PARAMS:
619
      try:
620
        value = i[field]
621
      except KeyError:
622
        continue
623

    
624
      nic[field] = value
625

    
626
    nics.append(nic)
627

    
628
  assert len(nics_input) == len(nics)
629

    
630
  # HV/BE parameters
631
  hvparams = baserlib.CheckParameter(data, "hvparams", default={})
632
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
633

    
634
  beparams = baserlib.CheckParameter(data, "beparams", default={})
635
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
636

    
637
  return opcodes.OpCreateInstance(
638
    mode=baserlib.CheckParameter(data, "mode"),
639
    instance_name=baserlib.CheckParameter(data, "name"),
640
    os_type=baserlib.CheckParameter(data, "os"),
641
    osparams=baserlib.CheckParameter(data, "osparams", default={}),
642
    force_variant=baserlib.CheckParameter(data, "force_variant",
643
                                          default=False),
644
    no_install=baserlib.CheckParameter(data, "no_install", default=False),
645
    pnode=baserlib.CheckParameter(data, "pnode", default=None),
646
    snode=baserlib.CheckParameter(data, "snode", default=None),
647
    disk_template=baserlib.CheckParameter(data, "disk_template"),
648
    disks=disks,
649
    nics=nics,
650
    src_node=baserlib.CheckParameter(data, "src_node", default=None),
651
    src_path=baserlib.CheckParameter(data, "src_path", default=None),
652
    start=baserlib.CheckParameter(data, "start", default=True),
653
    wait_for_sync=True,
654
    ip_check=baserlib.CheckParameter(data, "ip_check", default=True),
655
    name_check=baserlib.CheckParameter(data, "name_check", default=True),
656
    file_storage_dir=baserlib.CheckParameter(data, "file_storage_dir",
657
                                             default=None),
658
    file_driver=baserlib.CheckParameter(data, "file_driver",
659
                                        default=constants.FD_LOOP),
660
    source_handshake=baserlib.CheckParameter(data, "source_handshake",
661
                                             default=None),
662
    source_x509_ca=baserlib.CheckParameter(data, "source_x509_ca",
663
                                           default=None),
664
    source_instance_name=baserlib.CheckParameter(data, "source_instance_name",
665
                                                 default=None),
666
    iallocator=baserlib.CheckParameter(data, "iallocator", default=None),
667
    hypervisor=baserlib.CheckParameter(data, "hypervisor", default=None),
668
    hvparams=hvparams,
669
    beparams=beparams,
670
    dry_run=dry_run,
671
    )
672

    
673

    
674
class R_2_instances(baserlib.R_Generic):
675
  """/2/instances resource.
676

677
  """
678
  def GET(self):
679
    """Returns a list of all available instances.
680

681
    """
682
    client = baserlib.GetClient()
683

    
684
    use_locking = self.useLocking()
685
    if self.useBulk():
686
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
687
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
688
    else:
689
      instancesdata = client.QueryInstances([], ["name"], use_locking)
690
      instanceslist = [row[0] for row in instancesdata]
691
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
692
                                   uri_fields=("id", "uri"))
693

    
694
  def _ParseVersion0CreateRequest(self):
695
    """Parses an instance creation request version 0.
696

697
    Request data version 0 is deprecated and should not be used anymore.
698

699
    @rtype: L{opcodes.OpCreateInstance}
700
    @return: Instance creation opcode
701

702
    """
703
    # Do not modify anymore, request data version 0 is deprecated
704
    beparams = baserlib.MakeParamsDict(self.request_body,
705
                                       constants.BES_PARAMETERS)
706
    hvparams = baserlib.MakeParamsDict(self.request_body,
707
                                       constants.HVS_PARAMETERS)
708
    fn = self.getBodyParameter
709

    
710
    # disk processing
711
    disk_data = fn('disks')
712
    if not isinstance(disk_data, list):
713
      raise http.HttpBadRequest("The 'disks' parameter should be a list")
714
    disks = []
715
    for idx, d in enumerate(disk_data):
716
      if not isinstance(d, int):
717
        raise http.HttpBadRequest("Disk %d specification wrong: should"
718
                                  " be an integer" % idx)
719
      disks.append({"size": d})
720

    
721
    # nic processing (one nic only)
722
    nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
723
    if fn("ip", None) is not None:
724
      nics[0]["ip"] = fn("ip")
725
    if fn("mode", None) is not None:
726
      nics[0]["mode"] = fn("mode")
727
    if fn("link", None) is not None:
728
      nics[0]["link"] = fn("link")
729
    if fn("bridge", None) is not None:
730
      nics[0]["bridge"] = fn("bridge")
731

    
732
    # Do not modify anymore, request data version 0 is deprecated
733
    return opcodes.OpCreateInstance(
734
      mode=constants.INSTANCE_CREATE,
735
      instance_name=fn('name'),
736
      disks=disks,
737
      disk_template=fn('disk_template'),
738
      os_type=fn('os'),
739
      pnode=fn('pnode', None),
740
      snode=fn('snode', None),
741
      iallocator=fn('iallocator', None),
742
      nics=nics,
743
      start=fn('start', True),
744
      ip_check=fn('ip_check', True),
745
      name_check=fn('name_check', True),
746
      wait_for_sync=True,
747
      hypervisor=fn('hypervisor', None),
748
      hvparams=hvparams,
749
      beparams=beparams,
750
      file_storage_dir=fn('file_storage_dir', None),
751
      file_driver=fn('file_driver', constants.FD_LOOP),
752
      dry_run=bool(self.dryRun()),
753
      )
754

    
755
  def POST(self):
756
    """Create an instance.
757

758
    @return: a job id
759

760
    """
761
    if not isinstance(self.request_body, dict):
762
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
763

    
764
    # Default to request data version 0
765
    data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0)
766

    
767
    if data_version == 0:
768
      op = self._ParseVersion0CreateRequest()
769
    elif data_version == 1:
770
      op = _ParseInstanceCreateRequestVersion1(self.request_body,
771
                                               self.dryRun())
772
    else:
773
      raise http.HttpBadRequest("Unsupported request data version %s" %
774
                                data_version)
775

    
776
    return baserlib.SubmitJob([op])
777

    
778

    
779
class R_2_instances_name(baserlib.R_Generic):
780
  """/2/instances/[instance_name] resources.
781

782
  """
783
  def GET(self):
784
    """Send information about an instance.
785

786
    """
787
    client = baserlib.GetClient()
788
    instance_name = self.items[0]
789

    
790
    result = baserlib.HandleItemQueryErrors(client.QueryInstances,
791
                                            names=[instance_name],
792
                                            fields=I_FIELDS,
793
                                            use_locking=self.useLocking())
794

    
795
    return baserlib.MapFields(I_FIELDS, result[0])
796

    
797
  def DELETE(self):
798
    """Delete an instance.
799

800
    """
801
    op = opcodes.OpRemoveInstance(instance_name=self.items[0],
802
                                  ignore_failures=False,
803
                                  dry_run=bool(self.dryRun()))
804
    return baserlib.SubmitJob([op])
805

    
806

    
807
class R_2_instances_name_info(baserlib.R_Generic):
808
  """/2/instances/[instance_name]/info resource.
809

810
  """
811
  def GET(self):
812
    """Request detailed instance information.
813

814
    """
815
    instance_name = self.items[0]
816
    static = bool(self._checkIntVariable("static", default=0))
817

    
818
    op = opcodes.OpQueryInstanceData(instances=[instance_name],
819
                                     static=static)
820
    return baserlib.SubmitJob([op])
821

    
822

    
823
class R_2_instances_name_reboot(baserlib.R_Generic):
824
  """/2/instances/[instance_name]/reboot resource.
825

826
  Implements an instance reboot.
827

828
  """
829
  def POST(self):
830
    """Reboot an instance.
831

832
    The URI takes type=[hard|soft|full] and
833
    ignore_secondaries=[False|True] parameters.
834

835
    """
836
    instance_name = self.items[0]
837
    reboot_type = self.queryargs.get('type',
838
                                     [constants.INSTANCE_REBOOT_HARD])[0]
839
    ignore_secondaries = bool(self._checkIntVariable('ignore_secondaries'))
840
    op = opcodes.OpRebootInstance(instance_name=instance_name,
841
                                  reboot_type=reboot_type,
842
                                  ignore_secondaries=ignore_secondaries,
843
                                  dry_run=bool(self.dryRun()))
844

    
845
    return baserlib.SubmitJob([op])
846

    
847

    
848
class R_2_instances_name_startup(baserlib.R_Generic):
849
  """/2/instances/[instance_name]/startup resource.
850

851
  Implements an instance startup.
852

853
  """
854
  def PUT(self):
855
    """Startup an instance.
856

857
    The URI takes force=[False|True] parameter to start the instance
858
    if even if secondary disks are failing.
859

860
    """
861
    instance_name = self.items[0]
862
    force_startup = bool(self._checkIntVariable('force'))
863
    op = opcodes.OpStartupInstance(instance_name=instance_name,
864
                                   force=force_startup,
865
                                   dry_run=bool(self.dryRun()))
866

    
867
    return baserlib.SubmitJob([op])
868

    
869

    
870
class R_2_instances_name_shutdown(baserlib.R_Generic):
871
  """/2/instances/[instance_name]/shutdown resource.
872

873
  Implements an instance shutdown.
874

875
  """
876
  def PUT(self):
877
    """Shutdown an instance.
878

879
    """
880
    instance_name = self.items[0]
881
    op = opcodes.OpShutdownInstance(instance_name=instance_name,
882
                                    dry_run=bool(self.dryRun()))
883

    
884
    return baserlib.SubmitJob([op])
885

    
886

    
887
def _ParseInstanceReinstallRequest(name, data):
888
  """Parses a request for reinstalling an instance.
889

890
  """
891
  if not isinstance(data, dict):
892
    raise http.HttpBadRequest("Invalid body contents, not a dictionary")
893

    
894
  ostype = baserlib.CheckParameter(data, "os")
895
  start = baserlib.CheckParameter(data, "start", exptype=bool,
896
                                  default=True)
897
  osparams = baserlib.CheckParameter(data, "osparams", default=None)
898

    
899
  ops = [
900
    opcodes.OpShutdownInstance(instance_name=name),
901
    opcodes.OpReinstallInstance(instance_name=name, os_type=ostype,
902
                                osparams=osparams),
903
    ]
904

    
905
  if start:
906
    ops.append(opcodes.OpStartupInstance(instance_name=name, force=False))
907

    
908
  return ops
909

    
910

    
911
class R_2_instances_name_reinstall(baserlib.R_Generic):
912
  """/2/instances/[instance_name]/reinstall resource.
913

914
  Implements an instance reinstall.
915

916
  """
917
  def POST(self):
918
    """Reinstall an instance.
919

920
    The URI takes os=name and nostartup=[0|1] optional
921
    parameters. By default, the instance will be started
922
    automatically.
923

924
    """
925
    if self.request_body:
926
      if self.queryargs:
927
        raise http.HttpBadRequest("Can't combine query and body parameters")
928

    
929
      body = self.request_body
930
    else:
931
      if not self.queryargs:
932
        raise http.HttpBadRequest("Missing query parameters")
933
      # Legacy interface, do not modify/extend
934
      body = {
935
        "os": self._checkStringVariable("os"),
936
        "start": not self._checkIntVariable("nostartup"),
937
        }
938

    
939
    ops = _ParseInstanceReinstallRequest(self.items[0], body)
940

    
941
    return baserlib.SubmitJob(ops)
942

    
943

    
944
class R_2_instances_name_replace_disks(baserlib.R_Generic):
945
  """/2/instances/[instance_name]/replace-disks resource.
946

947
  """
948
  def POST(self):
949
    """Replaces disks on an instance.
950

951
    """
952
    instance_name = self.items[0]
953
    remote_node = self._checkStringVariable("remote_node", default=None)
954
    mode = self._checkStringVariable("mode", default=None)
955
    raw_disks = self._checkStringVariable("disks", default=None)
956
    iallocator = self._checkStringVariable("iallocator", default=None)
957

    
958
    if raw_disks:
959
      try:
960
        disks = [int(part) for part in raw_disks.split(",")]
961
      except ValueError, err:
962
        raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
963
    else:
964
      disks = []
965

    
966
    op = opcodes.OpReplaceDisks(instance_name=instance_name,
967
                                remote_node=remote_node,
968
                                mode=mode,
969
                                disks=disks,
970
                                iallocator=iallocator)
971

    
972
    return baserlib.SubmitJob([op])
973

    
974

    
975
class R_2_instances_name_activate_disks(baserlib.R_Generic):
976
  """/2/instances/[instance_name]/activate-disks resource.
977

978
  """
979
  def PUT(self):
980
    """Activate disks for an instance.
981

982
    The URI might contain ignore_size to ignore current recorded size.
983

984
    """
985
    instance_name = self.items[0]
986
    ignore_size = bool(self._checkIntVariable('ignore_size'))
987

    
988
    op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
989
                                         ignore_size=ignore_size)
990

    
991
    return baserlib.SubmitJob([op])
992

    
993

    
994
class R_2_instances_name_deactivate_disks(baserlib.R_Generic):
995
  """/2/instances/[instance_name]/deactivate-disks resource.
996

997
  """
998
  def PUT(self):
999
    """Deactivate disks for an instance.
1000

1001
    """
1002
    instance_name = self.items[0]
1003

    
1004
    op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
1005

    
1006
    return baserlib.SubmitJob([op])
1007

    
1008

    
1009
class R_2_instances_name_prepare_export(baserlib.R_Generic):
1010
  """/2/instances/[instance_name]/prepare-export resource.
1011

1012
  """
1013
  def PUT(self):
1014
    """Prepares an export for an instance.
1015

1016
    @return: a job id
1017

1018
    """
1019
    instance_name = self.items[0]
1020
    mode = self._checkStringVariable("mode")
1021

    
1022
    op = opcodes.OpPrepareExport(instance_name=instance_name,
1023
                                 mode=mode)
1024

    
1025
    return baserlib.SubmitJob([op])
1026

    
1027

    
1028
def _ParseExportInstanceRequest(name, data):
1029
  """Parses a request for an instance export.
1030

1031
  @rtype: L{opcodes.OpExportInstance}
1032
  @return: Instance export opcode
1033

1034
  """
1035
  mode = baserlib.CheckParameter(data, "mode",
1036
                                 default=constants.EXPORT_MODE_LOCAL)
1037
  target_node = baserlib.CheckParameter(data, "destination")
1038
  shutdown = baserlib.CheckParameter(data, "shutdown", exptype=bool)
1039
  remove_instance = baserlib.CheckParameter(data, "remove_instance",
1040
                                            exptype=bool, default=False)
1041
  x509_key_name = baserlib.CheckParameter(data, "x509_key_name", default=None)
1042
  destination_x509_ca = baserlib.CheckParameter(data, "destination_x509_ca",
1043
                                                default=None)
1044

    
1045
  return opcodes.OpExportInstance(instance_name=name,
1046
                                  mode=mode,
1047
                                  target_node=target_node,
1048
                                  shutdown=shutdown,
1049
                                  remove_instance=remove_instance,
1050
                                  x509_key_name=x509_key_name,
1051
                                  destination_x509_ca=destination_x509_ca)
1052

    
1053

    
1054
class R_2_instances_name_export(baserlib.R_Generic):
1055
  """/2/instances/[instance_name]/export resource.
1056

1057
  """
1058
  def PUT(self):
1059
    """Exports an instance.
1060

1061
    @return: a job id
1062

1063
    """
1064
    if not isinstance(self.request_body, dict):
1065
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
1066

    
1067
    op = _ParseExportInstanceRequest(self.items[0], self.request_body)
1068

    
1069
    return baserlib.SubmitJob([op])
1070

    
1071

    
1072
def _ParseMigrateInstanceRequest(name, data):
1073
  """Parses a request for an instance migration.
1074

1075
  @rtype: L{opcodes.OpMigrateInstance}
1076
  @return: Instance migration opcode
1077

1078
  """
1079
  mode = baserlib.CheckParameter(data, "mode", default=None)
1080
  cleanup = baserlib.CheckParameter(data, "cleanup", exptype=bool,
1081
                                    default=False)
1082

    
1083
  return opcodes.OpMigrateInstance(instance_name=name, mode=mode,
1084
                                   cleanup=cleanup)
1085

    
1086

    
1087
class R_2_instances_name_migrate(baserlib.R_Generic):
1088
  """/2/instances/[instance_name]/migrate resource.
1089

1090
  """
1091
  def PUT(self):
1092
    """Migrates an instance.
1093

1094
    @return: a job id
1095

1096
    """
1097
    baserlib.CheckType(self.request_body, dict, "Body contents")
1098

    
1099
    op = _ParseMigrateInstanceRequest(self.items[0], self.request_body)
1100

    
1101
    return baserlib.SubmitJob([op])
1102

    
1103

    
1104
def _ParseRenameInstanceRequest(name, data):
1105
  """Parses a request for renaming an instance.
1106

1107
  @rtype: L{opcodes.OpRenameInstance}
1108
  @return: Instance rename opcode
1109

1110
  """
1111
  new_name = baserlib.CheckParameter(data, "new_name")
1112
  ip_check = baserlib.CheckParameter(data, "ip_check", default=True)
1113
  name_check = baserlib.CheckParameter(data, "name_check", default=True)
1114

    
1115
  return opcodes.OpRenameInstance(instance_name=name, new_name=new_name,
1116
                                  name_check=name_check, ip_check=ip_check)
1117

    
1118

    
1119
class R_2_instances_name_rename(baserlib.R_Generic):
1120
  """/2/instances/[instance_name]/rename resource.
1121

1122
  """
1123
  def PUT(self):
1124
    """Changes the name of an instance.
1125

1126
    @return: a job id
1127

1128
    """
1129
    baserlib.CheckType(self.request_body, dict, "Body contents")
1130

    
1131
    op = _ParseRenameInstanceRequest(self.items[0], self.request_body)
1132

    
1133
    return baserlib.SubmitJob([op])
1134

    
1135

    
1136
def _ParseModifyInstanceRequest(name, data):
1137
  """Parses a request for modifying an instance.
1138

1139
  @rtype: L{opcodes.OpSetInstanceParams}
1140
  @return: Instance modify opcode
1141

1142
  """
1143
  osparams = baserlib.CheckParameter(data, "osparams", default={})
1144
  force = baserlib.CheckParameter(data, "force", default=False)
1145
  nics = baserlib.CheckParameter(data, "nics", default=[])
1146
  disks = baserlib.CheckParameter(data, "disks", default=[])
1147
  disk_template = baserlib.CheckParameter(data, "disk_template", default=None)
1148
  remote_node = baserlib.CheckParameter(data, "remote_node", default=None)
1149
  os_name = baserlib.CheckParameter(data, "os_name", default=None)
1150
  force_variant = baserlib.CheckParameter(data, "force_variant", default=False)
1151

    
1152
  # HV/BE parameters
1153
  hvparams = baserlib.CheckParameter(data, "hvparams", default={})
1154
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES,
1155
                      allowed_values=[constants.VALUE_DEFAULT])
1156

    
1157
  beparams = baserlib.CheckParameter(data, "beparams", default={})
1158
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES,
1159
                      allowed_values=[constants.VALUE_DEFAULT])
1160

    
1161
  return opcodes.OpSetInstanceParams(instance_name=name, hvparams=hvparams,
1162
                                     beparams=beparams, osparams=osparams,
1163
                                     force=force, nics=nics, disks=disks,
1164
                                     disk_template=disk_template,
1165
                                     remote_node=remote_node, os_name=os_name,
1166
                                     force_variant=force_variant)
1167

    
1168

    
1169
class R_2_instances_name_modify(baserlib.R_Generic):
1170
  """/2/instances/[instance_name]/modify resource.
1171

1172
  """
1173
  def PUT(self):
1174
    """Changes some parameters of an instance.
1175

1176
    @return: a job id
1177

1178
    """
1179
    baserlib.CheckType(self.request_body, dict, "Body contents")
1180

    
1181
    op = _ParseModifyInstanceRequest(self.items[0], self.request_body)
1182

    
1183
    return baserlib.SubmitJob([op])
1184

    
1185

    
1186
class _R_Tags(baserlib.R_Generic):
1187
  """ Quasiclass for tagging resources
1188

1189
  Manages tags. When inheriting this class you must define the
1190
  TAG_LEVEL for it.
1191

1192
  """
1193
  TAG_LEVEL = None
1194

    
1195
  def __init__(self, items, queryargs, req):
1196
    """A tag resource constructor.
1197

1198
    We have to override the default to sort out cluster naming case.
1199

1200
    """
1201
    baserlib.R_Generic.__init__(self, items, queryargs, req)
1202

    
1203
    if self.TAG_LEVEL == constants.TAG_CLUSTER:
1204
      self.name = None
1205
    else:
1206
      self.name = items[0]
1207

    
1208
  def GET(self):
1209
    """Returns a list of tags.
1210

1211
    Example: ["tag1", "tag2", "tag3"]
1212

1213
    """
1214
    # pylint: disable-msg=W0212
1215
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
1216

    
1217
  def PUT(self):
1218
    """Add a set of tags.
1219

1220
    The request as a list of strings should be PUT to this URI. And
1221
    you'll have back a job id.
1222

1223
    """
1224
    # pylint: disable-msg=W0212
1225
    if 'tag' not in self.queryargs:
1226
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
1227
                                " the 'tag' parameter")
1228
    return baserlib._Tags_PUT(self.TAG_LEVEL,
1229
                              self.queryargs['tag'], name=self.name,
1230
                              dry_run=bool(self.dryRun()))
1231

    
1232
  def DELETE(self):
1233
    """Delete a tag.
1234

1235
    In order to delete a set of tags, the DELETE
1236
    request should be addressed to URI like:
1237
    /tags?tag=[tag]&tag=[tag]
1238

1239
    """
1240
    # pylint: disable-msg=W0212
1241
    if 'tag' not in self.queryargs:
1242
      # no we not gonna delete all tags
1243
      raise http.HttpBadRequest("Cannot delete all tags - please specify"
1244
                                " tag(s) using the 'tag' parameter")
1245
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
1246
                                 self.queryargs['tag'],
1247
                                 name=self.name,
1248
                                 dry_run=bool(self.dryRun()))
1249

    
1250

    
1251
class R_2_instances_name_tags(_R_Tags):
1252
  """ /2/instances/[instance_name]/tags resource.
1253

1254
  Manages per-instance tags.
1255

1256
  """
1257
  TAG_LEVEL = constants.TAG_INSTANCE
1258

    
1259

    
1260
class R_2_nodes_name_tags(_R_Tags):
1261
  """ /2/nodes/[node_name]/tags resource.
1262

1263
  Manages per-node tags.
1264

1265
  """
1266
  TAG_LEVEL = constants.TAG_NODE
1267

    
1268

    
1269
class R_2_tags(_R_Tags):
1270
  """ /2/instances/tags resource.
1271

1272
  Manages cluster tags.
1273

1274
  """
1275
  TAG_LEVEL = constants.TAG_CLUSTER