4 # Copyright (C) 2006, 2007, 2008, 2009, 2010 Google Inc.
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.
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.
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
22 """Remote API version 2 baserlib.library.
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.
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.
35 So when adding new methods, if they are operating on the URI entity itself,
36 PUT should be prefered over POST.
40 # pylint: disable-msg=C0103
42 # C0103: Invalid name, since the R_* names are not conforming
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
53 _COMMON_FIELDS = ["ctime", "mtime", "uuid", "serial_no", "tags"]
54 I_FIELDS = ["name", "admin_state", "os",
57 "nic.ips", "nic.macs", "nic.modes", "nic.links", "nic.bridges",
59 "disk.sizes", "disk_usage",
60 "beparams", "hvparams",
61 "oper_state", "oper_ram", "oper_vcpus", "status",
62 "custom_hvparams", "custom_beparams", "custom_nicparams",
65 N_FIELDS = ["name", "offline", "master_candidate", "drained",
67 "mtotal", "mnode", "mfree",
68 "pinst_cnt", "sinst_cnt",
69 "ctotal", "cnodes", "csockets",
71 "pinst_list", "sinst_list",
72 "master_capable", "vm_capable",
76 G_FIELDS = ["name", "uuid",
77 "node_cnt", "node_list",
78 "ctime", "mtime", "serial_no",
79 ] # "tags" is missing to be able to use _COMMON_FIELDS here.
81 _NR_DRAINED = "drained"
82 _NR_MASTER_CANDIATE = "master-candidate"
84 _NR_OFFLINE = "offline"
85 _NR_REGULAR = "regular"
89 "C": _NR_MASTER_CANDIATE,
95 # Request data version field
96 _REQ_DATA_VERSION = "__version__"
98 # Feature string for instance creation request data version 1
99 _INST_CREATE_REQV1 = "instance-create-reqv1"
101 # Feature string for instance reinstall request version 1
102 _INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
104 # Timeout for /2/jobs/[job_id]/wait. Gives job up to 10 seconds to change.
108 class R_version(baserlib.R_Generic):
109 """/version resource.
111 This resource should be used to determine the remote API version and
112 to adapt clients accordingly.
117 """Returns the remote API version.
120 return constants.RAPI_VERSION
123 class R_2_info(baserlib.R_Generic):
129 """Returns cluster information.
132 client = baserlib.GetClient()
133 return client.QueryClusterInfo()
136 class R_2_features(baserlib.R_Generic):
137 """/2/features resource.
142 """Returns list of optional RAPI features implemented.
145 return [_INST_CREATE_REQV1, _INST_REINSTALL_REQV1]
148 class R_2_os(baserlib.R_Generic):
154 """Return a list of all OSes.
156 Can return error 500 in case of a problem.
158 Example: ["debian-etch"]
161 cl = baserlib.GetClient()
162 op = opcodes.OpDiagnoseOS(output_fields=["name", "variants"], names=[])
163 job_id = baserlib.SubmitJob([op], cl)
164 # we use custom feedback function, instead of print we log the status
165 result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
166 diagnose_data = result[0]
168 if not isinstance(diagnose_data, list):
169 raise http.HttpBadGateway(message="Can't get OS list")
172 for (name, variants) in diagnose_data:
173 os_names.extend(cli.CalculateOSNames(name, variants))
178 class R_2_redist_config(baserlib.R_Generic):
179 """/2/redistribute-config resource.
184 """Redistribute configuration to all nodes.
187 return baserlib.SubmitJob([opcodes.OpRedistributeConfig()])
190 class R_2_jobs(baserlib.R_Generic):
196 """Returns a dictionary of jobs.
198 @return: a dictionary with jobs id and uri.
202 cl = baserlib.GetClient()
203 # Convert the list of lists to the list of ids
204 result = [job_id for [job_id] in cl.QueryJobs(None, fields)]
205 return baserlib.BuildUriList(result, "/2/jobs/%s",
206 uri_fields=("id", "uri"))
209 class R_2_jobs_id(baserlib.R_Generic):
210 """/2/jobs/[job_id] resource.
214 """Returns a job status.
216 @return: a dictionary with job parameters.
218 - id: job ID as a number
219 - status: current job status as a string
220 - ops: involved OpCodes as a list of dictionaries for each
222 - opstatus: OpCodes status as a list
223 - opresult: OpCodes results as a list of lists
226 fields = ["id", "ops", "status", "summary",
227 "opstatus", "opresult", "oplog",
228 "received_ts", "start_ts", "end_ts",
230 job_id = self.items[0]
231 result = baserlib.GetClient().QueryJobs([job_id, ], fields)[0]
233 raise http.HttpNotFound()
234 return baserlib.MapFields(fields, result)
237 """Cancel not-yet-started job.
240 job_id = self.items[0]
241 result = baserlib.GetClient().CancelJob(job_id)
245 class R_2_jobs_id_wait(baserlib.R_Generic):
246 """/2/jobs/[job_id]/wait resource.
249 # WaitForJobChange provides access to sensitive information and blocks
250 # machine resources (it's a blocking RAPI call), hence restricting access.
251 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
254 """Waits for job changes.
257 job_id = self.items[0]
259 fields = self.getBodyParameter("fields")
260 prev_job_info = self.getBodyParameter("previous_job_info", None)
261 prev_log_serial = self.getBodyParameter("previous_log_serial", None)
263 if not isinstance(fields, list):
264 raise http.HttpBadRequest("The 'fields' parameter should be a list")
266 if not (prev_job_info is None or isinstance(prev_job_info, list)):
267 raise http.HttpBadRequest("The 'previous_job_info' parameter should"
270 if not (prev_log_serial is None or
271 isinstance(prev_log_serial, (int, long))):
272 raise http.HttpBadRequest("The 'previous_log_serial' parameter should"
275 client = baserlib.GetClient()
276 result = client.WaitForJobChangeOnce(job_id, fields,
277 prev_job_info, prev_log_serial,
278 timeout=_WFJC_TIMEOUT)
280 raise http.HttpNotFound()
282 if result == constants.JOB_NOTCHANGED:
286 (job_info, log_entries) = result
289 "job_info": job_info,
290 "log_entries": log_entries,
294 class R_2_nodes(baserlib.R_Generic):
295 """/2/nodes resource.
299 """Returns a list of all nodes.
302 client = baserlib.GetClient()
305 bulkdata = client.QueryNodes([], N_FIELDS, False)
306 return baserlib.MapBulkFields(bulkdata, N_FIELDS)
308 nodesdata = client.QueryNodes([], ["name"], False)
309 nodeslist = [row[0] for row in nodesdata]
310 return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
311 uri_fields=("id", "uri"))
314 class R_2_nodes_name(baserlib.R_Generic):
315 """/2/nodes/[node_name] resources.
319 """Send information about a node.
322 node_name = self.items[0]
323 client = baserlib.GetClient()
325 result = baserlib.HandleItemQueryErrors(client.QueryNodes,
326 names=[node_name], fields=N_FIELDS,
327 use_locking=self.useLocking())
329 return baserlib.MapFields(N_FIELDS, result[0])
332 class R_2_nodes_name_role(baserlib.R_Generic):
333 """ /2/nodes/[node_name]/role resource.
337 """Returns the current node role.
342 node_name = self.items[0]
343 client = baserlib.GetClient()
344 result = client.QueryNodes(names=[node_name], fields=["role"],
345 use_locking=self.useLocking())
347 return _NR_MAP[result[0][0]]
350 """Sets the node role.
355 if not isinstance(self.request_body, basestring):
356 raise http.HttpBadRequest("Invalid body contents, not a string")
358 node_name = self.items[0]
359 role = self.request_body
361 if role == _NR_REGULAR:
366 elif role == _NR_MASTER_CANDIATE:
368 offline = drained = None
370 elif role == _NR_DRAINED:
372 candidate = offline = None
374 elif role == _NR_OFFLINE:
376 candidate = drained = None
379 raise http.HttpBadRequest("Can't set '%s' role" % role)
381 op = opcodes.OpSetNodeParams(node_name=node_name,
382 master_candidate=candidate,
385 force=bool(self.useForce()))
387 return baserlib.SubmitJob([op])
390 class R_2_nodes_name_evacuate(baserlib.R_Generic):
391 """/2/nodes/[node_name]/evacuate resource.
395 """Evacuate all secondary instances off a node.
398 node_name = self.items[0]
399 remote_node = self._checkStringVariable("remote_node", default=None)
400 iallocator = self._checkStringVariable("iallocator", default=None)
401 early_r = bool(self._checkIntVariable("early_release", default=0))
402 dry_run = bool(self.dryRun())
404 cl = baserlib.GetClient()
406 op = opcodes.OpNodeEvacuationStrategy(nodes=[node_name],
407 iallocator=iallocator,
408 remote_node=remote_node)
410 job_id = baserlib.SubmitJob([op], cl)
411 # we use custom feedback function, instead of print we log the status
412 result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
415 for iname, node in result:
419 op = opcodes.OpReplaceDisks(instance_name=iname,
420 remote_node=node, disks=[],
421 mode=constants.REPLACE_DISK_CHG,
422 early_release=early_r)
423 jid = baserlib.SubmitJob([op])
424 jobs.append((jid, iname, node))
429 class R_2_nodes_name_migrate(baserlib.R_Generic):
430 """/2/nodes/[node_name]/migrate resource.
434 """Migrate all primary instances from a node.
437 node_name = self.items[0]
439 if "live" in self.queryargs and "mode" in self.queryargs:
440 raise http.HttpBadRequest("Only one of 'live' and 'mode' should"
442 elif "live" in self.queryargs:
443 if self._checkIntVariable("live", default=1):
444 mode = constants.HT_MIGRATION_LIVE
446 mode = constants.HT_MIGRATION_NONLIVE
448 mode = self._checkStringVariable("mode", default=None)
450 op = opcodes.OpMigrateNode(node_name=node_name, mode=mode)
452 return baserlib.SubmitJob([op])
455 class R_2_nodes_name_storage(baserlib.R_Generic):
456 """/2/nodes/[node_name]/storage ressource.
459 # LUQueryNodeStorage acquires locks, hence restricting access to GET
460 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
463 node_name = self.items[0]
465 storage_type = self._checkStringVariable("storage_type", None)
467 raise http.HttpBadRequest("Missing the required 'storage_type'"
470 output_fields = self._checkStringVariable("output_fields", None)
471 if not output_fields:
472 raise http.HttpBadRequest("Missing the required 'output_fields'"
475 op = opcodes.OpQueryNodeStorage(nodes=[node_name],
476 storage_type=storage_type,
477 output_fields=output_fields.split(","))
478 return baserlib.SubmitJob([op])
481 class R_2_nodes_name_storage_modify(baserlib.R_Generic):
482 """/2/nodes/[node_name]/storage/modify ressource.
486 node_name = self.items[0]
488 storage_type = self._checkStringVariable("storage_type", None)
490 raise http.HttpBadRequest("Missing the required 'storage_type'"
493 name = self._checkStringVariable("name", None)
495 raise http.HttpBadRequest("Missing the required 'name'"
500 if "allocatable" in self.queryargs:
501 changes[constants.SF_ALLOCATABLE] = \
502 bool(self._checkIntVariable("allocatable", default=1))
504 op = opcodes.OpModifyNodeStorage(node_name=node_name,
505 storage_type=storage_type,
508 return baserlib.SubmitJob([op])
511 class R_2_nodes_name_storage_repair(baserlib.R_Generic):
512 """/2/nodes/[node_name]/storage/repair ressource.
516 node_name = self.items[0]
518 storage_type = self._checkStringVariable("storage_type", None)
520 raise http.HttpBadRequest("Missing the required 'storage_type'"
523 name = self._checkStringVariable("name", None)
525 raise http.HttpBadRequest("Missing the required 'name'"
528 op = opcodes.OpRepairNodeStorage(node_name=node_name,
529 storage_type=storage_type,
531 return baserlib.SubmitJob([op])
534 class R_2_groups(baserlib.R_Generic):
535 """/2/groups resource.
539 """Returns a list of all node groups.
542 client = baserlib.GetClient()
545 bulkdata = client.QueryGroups([], G_FIELDS, False)
546 return baserlib.MapBulkFields(bulkdata, G_FIELDS)
548 data = client.QueryGroups([], ["name"], False)
549 groupnames = [row[0] for row in data]
550 return baserlib.BuildUriList(groupnames, "/2/groups/%s",
551 uri_fields=("name", "uri"))
554 class R_2_groups_name(baserlib.R_Generic):
555 """/2/groups/[group_name] resources.
559 """Send information about a node group.
562 group_name = self.items[0]
563 client = baserlib.GetClient()
565 result = baserlib.HandleItemQueryErrors(client.QueryGroups,
566 names=[group_name], fields=G_FIELDS,
567 use_locking=self.useLocking())
569 return baserlib.MapFields(G_FIELDS, result[0])
573 def _ParseInstanceCreateRequestVersion1(data, dry_run):
574 """Parses an instance creation request version 1.
576 @rtype: L{opcodes.OpCreateInstance}
577 @return: Instance creation opcode
581 disks_input = baserlib.CheckParameter(data, "disks", exptype=list)
584 for idx, i in enumerate(disks_input):
585 baserlib.CheckType(i, dict, "Disk %d specification" % idx)
589 size = i[constants.IDISK_SIZE]
591 raise http.HttpBadRequest("Disk %d specification wrong: missing disk"
595 constants.IDISK_SIZE: size,
598 # Optional disk access mode
600 disk_access = i[constants.IDISK_MODE]
604 disk[constants.IDISK_MODE] = disk_access
608 assert len(disks_input) == len(disks)
611 nics_input = baserlib.CheckParameter(data, "nics", exptype=list)
614 for idx, i in enumerate(nics_input):
615 baserlib.CheckType(i, dict, "NIC %d specification" % idx)
619 for field in constants.INIC_PARAMS:
629 assert len(nics_input) == len(nics)
632 hvparams = baserlib.CheckParameter(data, "hvparams", default={})
633 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
635 beparams = baserlib.CheckParameter(data, "beparams", default={})
636 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
638 return opcodes.OpCreateInstance(
639 mode=baserlib.CheckParameter(data, "mode"),
640 instance_name=baserlib.CheckParameter(data, "name"),
641 os_type=baserlib.CheckParameter(data, "os"),
642 osparams=baserlib.CheckParameter(data, "osparams", default={}),
643 force_variant=baserlib.CheckParameter(data, "force_variant",
645 no_install=baserlib.CheckParameter(data, "no_install", default=False),
646 pnode=baserlib.CheckParameter(data, "pnode", default=None),
647 snode=baserlib.CheckParameter(data, "snode", default=None),
648 disk_template=baserlib.CheckParameter(data, "disk_template"),
651 src_node=baserlib.CheckParameter(data, "src_node", default=None),
652 src_path=baserlib.CheckParameter(data, "src_path", default=None),
653 start=baserlib.CheckParameter(data, "start", default=True),
655 ip_check=baserlib.CheckParameter(data, "ip_check", default=True),
656 name_check=baserlib.CheckParameter(data, "name_check", default=True),
657 file_storage_dir=baserlib.CheckParameter(data, "file_storage_dir",
659 file_driver=baserlib.CheckParameter(data, "file_driver",
660 default=constants.FD_LOOP),
661 source_handshake=baserlib.CheckParameter(data, "source_handshake",
663 source_x509_ca=baserlib.CheckParameter(data, "source_x509_ca",
665 source_instance_name=baserlib.CheckParameter(data, "source_instance_name",
667 iallocator=baserlib.CheckParameter(data, "iallocator", default=None),
668 hypervisor=baserlib.CheckParameter(data, "hypervisor", default=None),
675 class R_2_instances(baserlib.R_Generic):
676 """/2/instances resource.
680 """Returns a list of all available instances.
683 client = baserlib.GetClient()
685 use_locking = self.useLocking()
687 bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
688 return baserlib.MapBulkFields(bulkdata, I_FIELDS)
690 instancesdata = client.QueryInstances([], ["name"], use_locking)
691 instanceslist = [row[0] for row in instancesdata]
692 return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
693 uri_fields=("id", "uri"))
695 def _ParseVersion0CreateRequest(self):
696 """Parses an instance creation request version 0.
698 Request data version 0 is deprecated and should not be used anymore.
700 @rtype: L{opcodes.OpCreateInstance}
701 @return: Instance creation opcode
704 # Do not modify anymore, request data version 0 is deprecated
705 beparams = baserlib.MakeParamsDict(self.request_body,
706 constants.BES_PARAMETERS)
707 hvparams = baserlib.MakeParamsDict(self.request_body,
708 constants.HVS_PARAMETERS)
709 fn = self.getBodyParameter
712 disk_data = fn('disks')
713 if not isinstance(disk_data, list):
714 raise http.HttpBadRequest("The 'disks' parameter should be a list")
716 for idx, d in enumerate(disk_data):
717 if not isinstance(d, int):
718 raise http.HttpBadRequest("Disk %d specification wrong: should"
719 " be an integer" % idx)
720 disks.append({"size": d})
722 # nic processing (one nic only)
723 nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
724 if fn("ip", None) is not None:
725 nics[0]["ip"] = fn("ip")
726 if fn("mode", None) is not None:
727 nics[0]["mode"] = fn("mode")
728 if fn("link", None) is not None:
729 nics[0]["link"] = fn("link")
730 if fn("bridge", None) is not None:
731 nics[0]["bridge"] = fn("bridge")
733 # Do not modify anymore, request data version 0 is deprecated
734 return opcodes.OpCreateInstance(
735 mode=constants.INSTANCE_CREATE,
736 instance_name=fn('name'),
738 disk_template=fn('disk_template'),
740 pnode=fn('pnode', None),
741 snode=fn('snode', None),
742 iallocator=fn('iallocator', None),
744 start=fn('start', True),
745 ip_check=fn('ip_check', True),
746 name_check=fn('name_check', True),
748 hypervisor=fn('hypervisor', None),
751 file_storage_dir=fn('file_storage_dir', None),
752 file_driver=fn('file_driver', constants.FD_LOOP),
753 dry_run=bool(self.dryRun()),
757 """Create an instance.
762 if not isinstance(self.request_body, dict):
763 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
765 # Default to request data version 0
766 data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0)
768 if data_version == 0:
769 op = self._ParseVersion0CreateRequest()
770 elif data_version == 1:
771 op = _ParseInstanceCreateRequestVersion1(self.request_body,
774 raise http.HttpBadRequest("Unsupported request data version %s" %
777 return baserlib.SubmitJob([op])
780 class R_2_instances_name(baserlib.R_Generic):
781 """/2/instances/[instance_name] resources.
785 """Send information about an instance.
788 client = baserlib.GetClient()
789 instance_name = self.items[0]
791 result = baserlib.HandleItemQueryErrors(client.QueryInstances,
792 names=[instance_name],
794 use_locking=self.useLocking())
796 return baserlib.MapFields(I_FIELDS, result[0])
799 """Delete an instance.
802 op = opcodes.OpRemoveInstance(instance_name=self.items[0],
803 ignore_failures=False,
804 dry_run=bool(self.dryRun()))
805 return baserlib.SubmitJob([op])
808 class R_2_instances_name_info(baserlib.R_Generic):
809 """/2/instances/[instance_name]/info resource.
813 """Request detailed instance information.
816 instance_name = self.items[0]
817 static = bool(self._checkIntVariable("static", default=0))
819 op = opcodes.OpQueryInstanceData(instances=[instance_name],
821 return baserlib.SubmitJob([op])
824 class R_2_instances_name_reboot(baserlib.R_Generic):
825 """/2/instances/[instance_name]/reboot resource.
827 Implements an instance reboot.
831 """Reboot an instance.
833 The URI takes type=[hard|soft|full] and
834 ignore_secondaries=[False|True] parameters.
837 instance_name = self.items[0]
838 reboot_type = self.queryargs.get('type',
839 [constants.INSTANCE_REBOOT_HARD])[0]
840 ignore_secondaries = bool(self._checkIntVariable('ignore_secondaries'))
841 op = opcodes.OpRebootInstance(instance_name=instance_name,
842 reboot_type=reboot_type,
843 ignore_secondaries=ignore_secondaries,
844 dry_run=bool(self.dryRun()))
846 return baserlib.SubmitJob([op])
849 class R_2_instances_name_startup(baserlib.R_Generic):
850 """/2/instances/[instance_name]/startup resource.
852 Implements an instance startup.
856 """Startup an instance.
858 The URI takes force=[False|True] parameter to start the instance
859 if even if secondary disks are failing.
862 instance_name = self.items[0]
863 force_startup = bool(self._checkIntVariable('force'))
864 op = opcodes.OpStartupInstance(instance_name=instance_name,
866 dry_run=bool(self.dryRun()))
868 return baserlib.SubmitJob([op])
871 class R_2_instances_name_shutdown(baserlib.R_Generic):
872 """/2/instances/[instance_name]/shutdown resource.
874 Implements an instance shutdown.
878 """Shutdown an instance.
881 instance_name = self.items[0]
882 op = opcodes.OpShutdownInstance(instance_name=instance_name,
883 dry_run=bool(self.dryRun()))
885 return baserlib.SubmitJob([op])
888 def _ParseInstanceReinstallRequest(name, data):
889 """Parses a request for reinstalling an instance.
892 if not isinstance(data, dict):
893 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
895 ostype = baserlib.CheckParameter(data, "os")
896 start = baserlib.CheckParameter(data, "start", exptype=bool,
898 osparams = baserlib.CheckParameter(data, "osparams", default=None)
901 opcodes.OpShutdownInstance(instance_name=name),
902 opcodes.OpReinstallInstance(instance_name=name, os_type=ostype,
907 ops.append(opcodes.OpStartupInstance(instance_name=name, force=False))
912 class R_2_instances_name_reinstall(baserlib.R_Generic):
913 """/2/instances/[instance_name]/reinstall resource.
915 Implements an instance reinstall.
919 """Reinstall an instance.
921 The URI takes os=name and nostartup=[0|1] optional
922 parameters. By default, the instance will be started
926 if self.request_body:
928 raise http.HttpBadRequest("Can't combine query and body parameters")
930 body = self.request_body
932 if not self.queryargs:
933 raise http.HttpBadRequest("Missing query parameters")
934 # Legacy interface, do not modify/extend
936 "os": self._checkStringVariable("os"),
937 "start": not self._checkIntVariable("nostartup"),
940 ops = _ParseInstanceReinstallRequest(self.items[0], body)
942 return baserlib.SubmitJob(ops)
945 class R_2_instances_name_replace_disks(baserlib.R_Generic):
946 """/2/instances/[instance_name]/replace-disks resource.
950 """Replaces disks on an instance.
953 instance_name = self.items[0]
954 remote_node = self._checkStringVariable("remote_node", default=None)
955 mode = self._checkStringVariable("mode", default=None)
956 raw_disks = self._checkStringVariable("disks", default=None)
957 iallocator = self._checkStringVariable("iallocator", default=None)
961 disks = [int(part) for part in raw_disks.split(",")]
962 except ValueError, err:
963 raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
967 op = opcodes.OpReplaceDisks(instance_name=instance_name,
968 remote_node=remote_node,
971 iallocator=iallocator)
973 return baserlib.SubmitJob([op])
976 class R_2_instances_name_activate_disks(baserlib.R_Generic):
977 """/2/instances/[instance_name]/activate-disks resource.
981 """Activate disks for an instance.
983 The URI might contain ignore_size to ignore current recorded size.
986 instance_name = self.items[0]
987 ignore_size = bool(self._checkIntVariable('ignore_size'))
989 op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
990 ignore_size=ignore_size)
992 return baserlib.SubmitJob([op])
995 class R_2_instances_name_deactivate_disks(baserlib.R_Generic):
996 """/2/instances/[instance_name]/deactivate-disks resource.
1000 """Deactivate disks for an instance.
1003 instance_name = self.items[0]
1005 op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
1007 return baserlib.SubmitJob([op])
1010 class R_2_instances_name_prepare_export(baserlib.R_Generic):
1011 """/2/instances/[instance_name]/prepare-export resource.
1015 """Prepares an export for an instance.
1020 instance_name = self.items[0]
1021 mode = self._checkStringVariable("mode")
1023 op = opcodes.OpPrepareExport(instance_name=instance_name,
1026 return baserlib.SubmitJob([op])
1029 def _ParseExportInstanceRequest(name, data):
1030 """Parses a request for an instance export.
1032 @rtype: L{opcodes.OpExportInstance}
1033 @return: Instance export opcode
1036 mode = baserlib.CheckParameter(data, "mode",
1037 default=constants.EXPORT_MODE_LOCAL)
1038 target_node = baserlib.CheckParameter(data, "destination")
1039 shutdown = baserlib.CheckParameter(data, "shutdown", exptype=bool)
1040 remove_instance = baserlib.CheckParameter(data, "remove_instance",
1041 exptype=bool, default=False)
1042 x509_key_name = baserlib.CheckParameter(data, "x509_key_name", default=None)
1043 destination_x509_ca = baserlib.CheckParameter(data, "destination_x509_ca",
1046 return opcodes.OpExportInstance(instance_name=name,
1048 target_node=target_node,
1050 remove_instance=remove_instance,
1051 x509_key_name=x509_key_name,
1052 destination_x509_ca=destination_x509_ca)
1055 class R_2_instances_name_export(baserlib.R_Generic):
1056 """/2/instances/[instance_name]/export resource.
1060 """Exports an instance.
1065 if not isinstance(self.request_body, dict):
1066 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
1068 op = _ParseExportInstanceRequest(self.items[0], self.request_body)
1070 return baserlib.SubmitJob([op])
1073 def _ParseMigrateInstanceRequest(name, data):
1074 """Parses a request for an instance migration.
1076 @rtype: L{opcodes.OpMigrateInstance}
1077 @return: Instance migration opcode
1080 mode = baserlib.CheckParameter(data, "mode", default=None)
1081 cleanup = baserlib.CheckParameter(data, "cleanup", exptype=bool,
1084 return opcodes.OpMigrateInstance(instance_name=name, mode=mode,
1088 class R_2_instances_name_migrate(baserlib.R_Generic):
1089 """/2/instances/[instance_name]/migrate resource.
1093 """Migrates an instance.
1098 baserlib.CheckType(self.request_body, dict, "Body contents")
1100 op = _ParseMigrateInstanceRequest(self.items[0], self.request_body)
1102 return baserlib.SubmitJob([op])
1105 def _ParseRenameInstanceRequest(name, data):
1106 """Parses a request for renaming an instance.
1108 @rtype: L{opcodes.OpRenameInstance}
1109 @return: Instance rename opcode
1112 new_name = baserlib.CheckParameter(data, "new_name")
1113 ip_check = baserlib.CheckParameter(data, "ip_check", default=True)
1114 name_check = baserlib.CheckParameter(data, "name_check", default=True)
1116 return opcodes.OpRenameInstance(instance_name=name, new_name=new_name,
1117 name_check=name_check, ip_check=ip_check)
1120 class R_2_instances_name_rename(baserlib.R_Generic):
1121 """/2/instances/[instance_name]/rename resource.
1125 """Changes the name of an instance.
1130 baserlib.CheckType(self.request_body, dict, "Body contents")
1132 op = _ParseRenameInstanceRequest(self.items[0], self.request_body)
1134 return baserlib.SubmitJob([op])
1137 def _ParseModifyInstanceRequest(name, data):
1138 """Parses a request for modifying an instance.
1140 @rtype: L{opcodes.OpSetInstanceParams}
1141 @return: Instance modify opcode
1144 osparams = baserlib.CheckParameter(data, "osparams", default={})
1145 force = baserlib.CheckParameter(data, "force", default=False)
1146 nics = baserlib.CheckParameter(data, "nics", default=[])
1147 disks = baserlib.CheckParameter(data, "disks", default=[])
1148 disk_template = baserlib.CheckParameter(data, "disk_template", default=None)
1149 remote_node = baserlib.CheckParameter(data, "remote_node", default=None)
1150 os_name = baserlib.CheckParameter(data, "os_name", default=None)
1151 force_variant = baserlib.CheckParameter(data, "force_variant", default=False)
1154 hvparams = baserlib.CheckParameter(data, "hvparams", default={})
1155 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES,
1156 allowed_values=[constants.VALUE_DEFAULT])
1158 beparams = baserlib.CheckParameter(data, "beparams", default={})
1159 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES,
1160 allowed_values=[constants.VALUE_DEFAULT])
1162 return opcodes.OpSetInstanceParams(instance_name=name, hvparams=hvparams,
1163 beparams=beparams, osparams=osparams,
1164 force=force, nics=nics, disks=disks,
1165 disk_template=disk_template,
1166 remote_node=remote_node, os_name=os_name,
1167 force_variant=force_variant)
1170 class R_2_instances_name_modify(baserlib.R_Generic):
1171 """/2/instances/[instance_name]/modify resource.
1175 """Changes some parameters of an instance.
1180 baserlib.CheckType(self.request_body, dict, "Body contents")
1182 op = _ParseModifyInstanceRequest(self.items[0], self.request_body)
1184 return baserlib.SubmitJob([op])
1187 class _R_Tags(baserlib.R_Generic):
1188 """ Quasiclass for tagging resources
1190 Manages tags. When inheriting this class you must define the
1196 def __init__(self, items, queryargs, req):
1197 """A tag resource constructor.
1199 We have to override the default to sort out cluster naming case.
1202 baserlib.R_Generic.__init__(self, items, queryargs, req)
1204 if self.TAG_LEVEL == constants.TAG_CLUSTER:
1207 self.name = items[0]
1210 """Returns a list of tags.
1212 Example: ["tag1", "tag2", "tag3"]
1215 # pylint: disable-msg=W0212
1216 return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
1219 """Add a set of tags.
1221 The request as a list of strings should be PUT to this URI. And
1222 you'll have back a job id.
1225 # pylint: disable-msg=W0212
1226 if 'tag' not in self.queryargs:
1227 raise http.HttpBadRequest("Please specify tag(s) to add using the"
1228 " the 'tag' parameter")
1229 return baserlib._Tags_PUT(self.TAG_LEVEL,
1230 self.queryargs['tag'], name=self.name,
1231 dry_run=bool(self.dryRun()))
1236 In order to delete a set of tags, the DELETE
1237 request should be addressed to URI like:
1238 /tags?tag=[tag]&tag=[tag]
1241 # pylint: disable-msg=W0212
1242 if 'tag' not in self.queryargs:
1243 # no we not gonna delete all tags
1244 raise http.HttpBadRequest("Cannot delete all tags - please specify"
1245 " tag(s) using the 'tag' parameter")
1246 return baserlib._Tags_DELETE(self.TAG_LEVEL,
1247 self.queryargs['tag'],
1249 dry_run=bool(self.dryRun()))
1252 class R_2_instances_name_tags(_R_Tags):
1253 """ /2/instances/[instance_name]/tags resource.
1255 Manages per-instance tags.
1258 TAG_LEVEL = constants.TAG_INSTANCE
1261 class R_2_nodes_name_tags(_R_Tags):
1262 """ /2/nodes/[node_name]/tags resource.
1264 Manages per-node tags.
1267 TAG_LEVEL = constants.TAG_NODE
1270 class R_2_tags(_R_Tags):
1271 """ /2/instances/tags resource.
1273 Manages cluster tags.
1276 TAG_LEVEL = constants.TAG_CLUSTER