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 _NR_DRAINED = "drained"
77 _NR_MASTER_CANDIATE = "master-candidate"
79 _NR_OFFLINE = "offline"
80 _NR_REGULAR = "regular"
84 "C": _NR_MASTER_CANDIATE,
90 # Request data version field
91 _REQ_DATA_VERSION = "__version__"
93 # Feature string for instance creation request data version 1
94 _INST_CREATE_REQV1 = "instance-create-reqv1"
96 # Feature string for instance reinstall request version 1
97 _INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
99 # Timeout for /2/jobs/[job_id]/wait. Gives job up to 10 seconds to change.
103 class R_version(baserlib.R_Generic):
104 """/version resource.
106 This resource should be used to determine the remote API version and
107 to adapt clients accordingly.
112 """Returns the remote API version.
115 return constants.RAPI_VERSION
118 class R_2_info(baserlib.R_Generic):
124 """Returns cluster information.
127 client = baserlib.GetClient()
128 return client.QueryClusterInfo()
131 class R_2_features(baserlib.R_Generic):
132 """/2/features resource.
137 """Returns list of optional RAPI features implemented.
140 return [_INST_CREATE_REQV1, _INST_REINSTALL_REQV1]
143 class R_2_os(baserlib.R_Generic):
149 """Return a list of all OSes.
151 Can return error 500 in case of a problem.
153 Example: ["debian-etch"]
156 cl = baserlib.GetClient()
157 op = opcodes.OpDiagnoseOS(output_fields=["name", "variants"], names=[])
158 job_id = baserlib.SubmitJob([op], cl)
159 # we use custom feedback function, instead of print we log the status
160 result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
161 diagnose_data = result[0]
163 if not isinstance(diagnose_data, list):
164 raise http.HttpBadGateway(message="Can't get OS list")
167 for (name, variants) in diagnose_data:
168 os_names.extend(cli.CalculateOSNames(name, variants))
173 class R_2_redist_config(baserlib.R_Generic):
174 """/2/redistribute-config resource.
179 """Redistribute configuration to all nodes.
182 return baserlib.SubmitJob([opcodes.OpRedistributeConfig()])
185 class R_2_jobs(baserlib.R_Generic):
191 """Returns a dictionary of jobs.
193 @return: a dictionary with jobs id and uri.
197 cl = baserlib.GetClient()
198 # Convert the list of lists to the list of ids
199 result = [job_id for [job_id] in cl.QueryJobs(None, fields)]
200 return baserlib.BuildUriList(result, "/2/jobs/%s",
201 uri_fields=("id", "uri"))
204 class R_2_jobs_id(baserlib.R_Generic):
205 """/2/jobs/[job_id] resource.
209 """Returns a job status.
211 @return: a dictionary with job parameters.
213 - id: job ID as a number
214 - status: current job status as a string
215 - ops: involved OpCodes as a list of dictionaries for each
217 - opstatus: OpCodes status as a list
218 - opresult: OpCodes results as a list of lists
221 fields = ["id", "ops", "status", "summary",
222 "opstatus", "opresult", "oplog",
223 "received_ts", "start_ts", "end_ts",
225 job_id = self.items[0]
226 result = baserlib.GetClient().QueryJobs([job_id, ], fields)[0]
228 raise http.HttpNotFound()
229 return baserlib.MapFields(fields, result)
232 """Cancel not-yet-started job.
235 job_id = self.items[0]
236 result = baserlib.GetClient().CancelJob(job_id)
240 class R_2_jobs_id_wait(baserlib.R_Generic):
241 """/2/jobs/[job_id]/wait resource.
244 # WaitForJobChange provides access to sensitive information and blocks
245 # machine resources (it's a blocking RAPI call), hence restricting access.
246 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
249 """Waits for job changes.
252 job_id = self.items[0]
254 fields = self.getBodyParameter("fields")
255 prev_job_info = self.getBodyParameter("previous_job_info", None)
256 prev_log_serial = self.getBodyParameter("previous_log_serial", None)
258 if not isinstance(fields, list):
259 raise http.HttpBadRequest("The 'fields' parameter should be a list")
261 if not (prev_job_info is None or isinstance(prev_job_info, list)):
262 raise http.HttpBadRequest("The 'previous_job_info' parameter should"
265 if not (prev_log_serial is None or
266 isinstance(prev_log_serial, (int, long))):
267 raise http.HttpBadRequest("The 'previous_log_serial' parameter should"
270 client = baserlib.GetClient()
271 result = client.WaitForJobChangeOnce(job_id, fields,
272 prev_job_info, prev_log_serial,
273 timeout=_WFJC_TIMEOUT)
275 raise http.HttpNotFound()
277 if result == constants.JOB_NOTCHANGED:
281 (job_info, log_entries) = result
284 "job_info": job_info,
285 "log_entries": log_entries,
289 class R_2_nodes(baserlib.R_Generic):
290 """/2/nodes resource.
294 """Returns a list of all nodes.
297 client = baserlib.GetClient()
300 bulkdata = client.QueryNodes([], N_FIELDS, False)
301 return baserlib.MapBulkFields(bulkdata, N_FIELDS)
303 nodesdata = client.QueryNodes([], ["name"], False)
304 nodeslist = [row[0] for row in nodesdata]
305 return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
306 uri_fields=("id", "uri"))
309 class R_2_nodes_name(baserlib.R_Generic):
310 """/2/nodes/[node_name] resources.
314 """Send information about a node.
317 node_name = self.items[0]
318 client = baserlib.GetClient()
320 result = baserlib.HandleItemQueryErrors(client.QueryNodes,
321 names=[node_name], fields=N_FIELDS,
322 use_locking=self.useLocking())
324 return baserlib.MapFields(N_FIELDS, result[0])
327 class R_2_nodes_name_role(baserlib.R_Generic):
328 """ /2/nodes/[node_name]/role resource.
332 """Returns the current node role.
337 node_name = self.items[0]
338 client = baserlib.GetClient()
339 result = client.QueryNodes(names=[node_name], fields=["role"],
340 use_locking=self.useLocking())
342 return _NR_MAP[result[0][0]]
345 """Sets the node role.
350 if not isinstance(self.request_body, basestring):
351 raise http.HttpBadRequest("Invalid body contents, not a string")
353 node_name = self.items[0]
354 role = self.request_body
356 if role == _NR_REGULAR:
361 elif role == _NR_MASTER_CANDIATE:
363 offline = drained = None
365 elif role == _NR_DRAINED:
367 candidate = offline = None
369 elif role == _NR_OFFLINE:
371 candidate = drained = None
374 raise http.HttpBadRequest("Can't set '%s' role" % role)
376 op = opcodes.OpSetNodeParams(node_name=node_name,
377 master_candidate=candidate,
380 force=bool(self.useForce()))
382 return baserlib.SubmitJob([op])
385 class R_2_nodes_name_evacuate(baserlib.R_Generic):
386 """/2/nodes/[node_name]/evacuate resource.
390 """Evacuate all secondary instances off a node.
393 node_name = self.items[0]
394 remote_node = self._checkStringVariable("remote_node", default=None)
395 iallocator = self._checkStringVariable("iallocator", default=None)
396 early_r = bool(self._checkIntVariable("early_release", default=0))
397 dry_run = bool(self.dryRun())
399 cl = baserlib.GetClient()
401 op = opcodes.OpNodeEvacuationStrategy(nodes=[node_name],
402 iallocator=iallocator,
403 remote_node=remote_node)
405 job_id = baserlib.SubmitJob([op], cl)
406 # we use custom feedback function, instead of print we log the status
407 result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
410 for iname, node in result:
414 op = opcodes.OpReplaceDisks(instance_name=iname,
415 remote_node=node, disks=[],
416 mode=constants.REPLACE_DISK_CHG,
417 early_release=early_r)
418 jid = baserlib.SubmitJob([op])
419 jobs.append((jid, iname, node))
424 class R_2_nodes_name_migrate(baserlib.R_Generic):
425 """/2/nodes/[node_name]/migrate resource.
429 """Migrate all primary instances from a node.
432 node_name = self.items[0]
434 if "live" in self.queryargs and "mode" in self.queryargs:
435 raise http.HttpBadRequest("Only one of 'live' and 'mode' should"
437 elif "live" in self.queryargs:
438 if self._checkIntVariable("live", default=1):
439 mode = constants.HT_MIGRATION_LIVE
441 mode = constants.HT_MIGRATION_NONLIVE
443 mode = self._checkStringVariable("mode", default=None)
445 op = opcodes.OpMigrateNode(node_name=node_name, mode=mode)
447 return baserlib.SubmitJob([op])
450 class R_2_nodes_name_storage(baserlib.R_Generic):
451 """/2/nodes/[node_name]/storage ressource.
454 # LUQueryNodeStorage acquires locks, hence restricting access to GET
455 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
458 node_name = self.items[0]
460 storage_type = self._checkStringVariable("storage_type", None)
462 raise http.HttpBadRequest("Missing the required 'storage_type'"
465 output_fields = self._checkStringVariable("output_fields", None)
466 if not output_fields:
467 raise http.HttpBadRequest("Missing the required 'output_fields'"
470 op = opcodes.OpQueryNodeStorage(nodes=[node_name],
471 storage_type=storage_type,
472 output_fields=output_fields.split(","))
473 return baserlib.SubmitJob([op])
476 class R_2_nodes_name_storage_modify(baserlib.R_Generic):
477 """/2/nodes/[node_name]/storage/modify ressource.
481 node_name = self.items[0]
483 storage_type = self._checkStringVariable("storage_type", None)
485 raise http.HttpBadRequest("Missing the required 'storage_type'"
488 name = self._checkStringVariable("name", None)
490 raise http.HttpBadRequest("Missing the required 'name'"
495 if "allocatable" in self.queryargs:
496 changes[constants.SF_ALLOCATABLE] = \
497 bool(self._checkIntVariable("allocatable", default=1))
499 op = opcodes.OpModifyNodeStorage(node_name=node_name,
500 storage_type=storage_type,
503 return baserlib.SubmitJob([op])
506 class R_2_nodes_name_storage_repair(baserlib.R_Generic):
507 """/2/nodes/[node_name]/storage/repair ressource.
511 node_name = self.items[0]
513 storage_type = self._checkStringVariable("storage_type", None)
515 raise http.HttpBadRequest("Missing the required 'storage_type'"
518 name = self._checkStringVariable("name", None)
520 raise http.HttpBadRequest("Missing the required 'name'"
523 op = opcodes.OpRepairNodeStorage(node_name=node_name,
524 storage_type=storage_type,
526 return baserlib.SubmitJob([op])
529 def _ParseInstanceCreateRequestVersion1(data, dry_run):
530 """Parses an instance creation request version 1.
532 @rtype: L{opcodes.OpCreateInstance}
533 @return: Instance creation opcode
537 disks_input = baserlib.CheckParameter(data, "disks", exptype=list)
540 for idx, i in enumerate(disks_input):
541 baserlib.CheckType(i, dict, "Disk %d specification" % idx)
545 size = i[constants.IDISK_SIZE]
547 raise http.HttpBadRequest("Disk %d specification wrong: missing disk"
551 constants.IDISK_SIZE: size,
554 # Optional disk access mode
556 disk_access = i[constants.IDISK_MODE]
560 disk[constants.IDISK_MODE] = disk_access
564 assert len(disks_input) == len(disks)
567 nics_input = baserlib.CheckParameter(data, "nics", exptype=list)
570 for idx, i in enumerate(nics_input):
571 baserlib.CheckType(i, dict, "NIC %d specification" % idx)
575 for field in constants.INIC_PARAMS:
585 assert len(nics_input) == len(nics)
588 hvparams = baserlib.CheckParameter(data, "hvparams", default={})
589 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
591 beparams = baserlib.CheckParameter(data, "beparams", default={})
592 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
594 return opcodes.OpCreateInstance(
595 mode=baserlib.CheckParameter(data, "mode"),
596 instance_name=baserlib.CheckParameter(data, "name"),
597 os_type=baserlib.CheckParameter(data, "os"),
598 osparams=baserlib.CheckParameter(data, "osparams", default={}),
599 force_variant=baserlib.CheckParameter(data, "force_variant",
601 no_install=baserlib.CheckParameter(data, "no_install", default=False),
602 pnode=baserlib.CheckParameter(data, "pnode", default=None),
603 snode=baserlib.CheckParameter(data, "snode", default=None),
604 disk_template=baserlib.CheckParameter(data, "disk_template"),
607 src_node=baserlib.CheckParameter(data, "src_node", default=None),
608 src_path=baserlib.CheckParameter(data, "src_path", default=None),
609 start=baserlib.CheckParameter(data, "start", default=True),
611 ip_check=baserlib.CheckParameter(data, "ip_check", default=True),
612 name_check=baserlib.CheckParameter(data, "name_check", default=True),
613 file_storage_dir=baserlib.CheckParameter(data, "file_storage_dir",
615 file_driver=baserlib.CheckParameter(data, "file_driver",
616 default=constants.FD_LOOP),
617 source_handshake=baserlib.CheckParameter(data, "source_handshake",
619 source_x509_ca=baserlib.CheckParameter(data, "source_x509_ca",
621 source_instance_name=baserlib.CheckParameter(data, "source_instance_name",
623 iallocator=baserlib.CheckParameter(data, "iallocator", default=None),
624 hypervisor=baserlib.CheckParameter(data, "hypervisor", default=None),
631 class R_2_instances(baserlib.R_Generic):
632 """/2/instances resource.
636 """Returns a list of all available instances.
639 client = baserlib.GetClient()
641 use_locking = self.useLocking()
643 bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
644 return baserlib.MapBulkFields(bulkdata, I_FIELDS)
646 instancesdata = client.QueryInstances([], ["name"], use_locking)
647 instanceslist = [row[0] for row in instancesdata]
648 return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
649 uri_fields=("id", "uri"))
651 def _ParseVersion0CreateRequest(self):
652 """Parses an instance creation request version 0.
654 Request data version 0 is deprecated and should not be used anymore.
656 @rtype: L{opcodes.OpCreateInstance}
657 @return: Instance creation opcode
660 # Do not modify anymore, request data version 0 is deprecated
661 beparams = baserlib.MakeParamsDict(self.request_body,
662 constants.BES_PARAMETERS)
663 hvparams = baserlib.MakeParamsDict(self.request_body,
664 constants.HVS_PARAMETERS)
665 fn = self.getBodyParameter
668 disk_data = fn('disks')
669 if not isinstance(disk_data, list):
670 raise http.HttpBadRequest("The 'disks' parameter should be a list")
672 for idx, d in enumerate(disk_data):
673 if not isinstance(d, int):
674 raise http.HttpBadRequest("Disk %d specification wrong: should"
675 " be an integer" % idx)
676 disks.append({"size": d})
678 # nic processing (one nic only)
679 nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
680 if fn("ip", None) is not None:
681 nics[0]["ip"] = fn("ip")
682 if fn("mode", None) is not None:
683 nics[0]["mode"] = fn("mode")
684 if fn("link", None) is not None:
685 nics[0]["link"] = fn("link")
686 if fn("bridge", None) is not None:
687 nics[0]["bridge"] = fn("bridge")
689 # Do not modify anymore, request data version 0 is deprecated
690 return opcodes.OpCreateInstance(
691 mode=constants.INSTANCE_CREATE,
692 instance_name=fn('name'),
694 disk_template=fn('disk_template'),
696 pnode=fn('pnode', None),
697 snode=fn('snode', None),
698 iallocator=fn('iallocator', None),
700 start=fn('start', True),
701 ip_check=fn('ip_check', True),
702 name_check=fn('name_check', True),
704 hypervisor=fn('hypervisor', None),
707 file_storage_dir=fn('file_storage_dir', None),
708 file_driver=fn('file_driver', constants.FD_LOOP),
709 dry_run=bool(self.dryRun()),
713 """Create an instance.
718 if not isinstance(self.request_body, dict):
719 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
721 # Default to request data version 0
722 data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0)
724 if data_version == 0:
725 op = self._ParseVersion0CreateRequest()
726 elif data_version == 1:
727 op = _ParseInstanceCreateRequestVersion1(self.request_body,
730 raise http.HttpBadRequest("Unsupported request data version %s" %
733 return baserlib.SubmitJob([op])
736 class R_2_instances_name(baserlib.R_Generic):
737 """/2/instances/[instance_name] resources.
741 """Send information about an instance.
744 client = baserlib.GetClient()
745 instance_name = self.items[0]
747 result = baserlib.HandleItemQueryErrors(client.QueryInstances,
748 names=[instance_name],
750 use_locking=self.useLocking())
752 return baserlib.MapFields(I_FIELDS, result[0])
755 """Delete an instance.
758 op = opcodes.OpRemoveInstance(instance_name=self.items[0],
759 ignore_failures=False,
760 dry_run=bool(self.dryRun()))
761 return baserlib.SubmitJob([op])
764 class R_2_instances_name_info(baserlib.R_Generic):
765 """/2/instances/[instance_name]/info resource.
769 """Request detailed instance information.
772 instance_name = self.items[0]
773 static = bool(self._checkIntVariable("static", default=0))
775 op = opcodes.OpQueryInstanceData(instances=[instance_name],
777 return baserlib.SubmitJob([op])
780 class R_2_instances_name_reboot(baserlib.R_Generic):
781 """/2/instances/[instance_name]/reboot resource.
783 Implements an instance reboot.
787 """Reboot an instance.
789 The URI takes type=[hard|soft|full] and
790 ignore_secondaries=[False|True] parameters.
793 instance_name = self.items[0]
794 reboot_type = self.queryargs.get('type',
795 [constants.INSTANCE_REBOOT_HARD])[0]
796 ignore_secondaries = bool(self._checkIntVariable('ignore_secondaries'))
797 op = opcodes.OpRebootInstance(instance_name=instance_name,
798 reboot_type=reboot_type,
799 ignore_secondaries=ignore_secondaries,
800 dry_run=bool(self.dryRun()))
802 return baserlib.SubmitJob([op])
805 class R_2_instances_name_startup(baserlib.R_Generic):
806 """/2/instances/[instance_name]/startup resource.
808 Implements an instance startup.
812 """Startup an instance.
814 The URI takes force=[False|True] parameter to start the instance
815 if even if secondary disks are failing.
818 instance_name = self.items[0]
819 force_startup = bool(self._checkIntVariable('force'))
820 op = opcodes.OpStartupInstance(instance_name=instance_name,
822 dry_run=bool(self.dryRun()))
824 return baserlib.SubmitJob([op])
827 class R_2_instances_name_shutdown(baserlib.R_Generic):
828 """/2/instances/[instance_name]/shutdown resource.
830 Implements an instance shutdown.
834 """Shutdown an instance.
837 instance_name = self.items[0]
838 op = opcodes.OpShutdownInstance(instance_name=instance_name,
839 dry_run=bool(self.dryRun()))
841 return baserlib.SubmitJob([op])
844 def _ParseInstanceReinstallRequest(name, data):
845 """Parses a request for reinstalling an instance.
848 if not isinstance(data, dict):
849 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
851 ostype = baserlib.CheckParameter(data, "os")
852 start = baserlib.CheckParameter(data, "start", exptype=bool,
854 osparams = baserlib.CheckParameter(data, "osparams", default=None)
857 opcodes.OpShutdownInstance(instance_name=name),
858 opcodes.OpReinstallInstance(instance_name=name, os_type=ostype,
863 ops.append(opcodes.OpStartupInstance(instance_name=name, force=False))
868 class R_2_instances_name_reinstall(baserlib.R_Generic):
869 """/2/instances/[instance_name]/reinstall resource.
871 Implements an instance reinstall.
875 """Reinstall an instance.
877 The URI takes os=name and nostartup=[0|1] optional
878 parameters. By default, the instance will be started
882 if self.request_body:
884 raise http.HttpBadRequest("Can't combine query and body parameters")
886 body = self.request_body
888 if not self.queryargs:
889 raise http.HttpBadRequest("Missing query parameters")
890 # Legacy interface, do not modify/extend
892 "os": self._checkStringVariable("os"),
893 "start": not self._checkIntVariable("nostartup"),
896 ops = _ParseInstanceReinstallRequest(self.items[0], body)
898 return baserlib.SubmitJob(ops)
901 class R_2_instances_name_replace_disks(baserlib.R_Generic):
902 """/2/instances/[instance_name]/replace-disks resource.
906 """Replaces disks on an instance.
909 instance_name = self.items[0]
910 remote_node = self._checkStringVariable("remote_node", default=None)
911 mode = self._checkStringVariable("mode", default=None)
912 raw_disks = self._checkStringVariable("disks", default=None)
913 iallocator = self._checkStringVariable("iallocator", default=None)
917 disks = [int(part) for part in raw_disks.split(",")]
918 except ValueError, err:
919 raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
923 op = opcodes.OpReplaceDisks(instance_name=instance_name,
924 remote_node=remote_node,
927 iallocator=iallocator)
929 return baserlib.SubmitJob([op])
932 class R_2_instances_name_activate_disks(baserlib.R_Generic):
933 """/2/instances/[instance_name]/activate-disks resource.
937 """Activate disks for an instance.
939 The URI might contain ignore_size to ignore current recorded size.
942 instance_name = self.items[0]
943 ignore_size = bool(self._checkIntVariable('ignore_size'))
945 op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
946 ignore_size=ignore_size)
948 return baserlib.SubmitJob([op])
951 class R_2_instances_name_deactivate_disks(baserlib.R_Generic):
952 """/2/instances/[instance_name]/deactivate-disks resource.
956 """Deactivate disks for an instance.
959 instance_name = self.items[0]
961 op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
963 return baserlib.SubmitJob([op])
966 class R_2_instances_name_prepare_export(baserlib.R_Generic):
967 """/2/instances/[instance_name]/prepare-export resource.
971 """Prepares an export for an instance.
976 instance_name = self.items[0]
977 mode = self._checkStringVariable("mode")
979 op = opcodes.OpPrepareExport(instance_name=instance_name,
982 return baserlib.SubmitJob([op])
985 def _ParseExportInstanceRequest(name, data):
986 """Parses a request for an instance export.
988 @rtype: L{opcodes.OpExportInstance}
989 @return: Instance export opcode
992 mode = baserlib.CheckParameter(data, "mode",
993 default=constants.EXPORT_MODE_LOCAL)
994 target_node = baserlib.CheckParameter(data, "destination")
995 shutdown = baserlib.CheckParameter(data, "shutdown", exptype=bool)
996 remove_instance = baserlib.CheckParameter(data, "remove_instance",
997 exptype=bool, default=False)
998 x509_key_name = baserlib.CheckParameter(data, "x509_key_name", default=None)
999 destination_x509_ca = baserlib.CheckParameter(data, "destination_x509_ca",
1002 return opcodes.OpExportInstance(instance_name=name,
1004 target_node=target_node,
1006 remove_instance=remove_instance,
1007 x509_key_name=x509_key_name,
1008 destination_x509_ca=destination_x509_ca)
1011 class R_2_instances_name_export(baserlib.R_Generic):
1012 """/2/instances/[instance_name]/export resource.
1016 """Exports an instance.
1021 if not isinstance(self.request_body, dict):
1022 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
1024 op = _ParseExportInstanceRequest(self.items[0], self.request_body)
1026 return baserlib.SubmitJob([op])
1029 def _ParseMigrateInstanceRequest(name, data):
1030 """Parses a request for an instance migration.
1032 @rtype: L{opcodes.OpMigrateInstance}
1033 @return: Instance migration opcode
1036 mode = baserlib.CheckParameter(data, "mode", default=None)
1037 cleanup = baserlib.CheckParameter(data, "cleanup", exptype=bool,
1040 return opcodes.OpMigrateInstance(instance_name=name, mode=mode,
1044 class R_2_instances_name_migrate(baserlib.R_Generic):
1045 """/2/instances/[instance_name]/migrate resource.
1049 """Migrates an instance.
1054 baserlib.CheckType(self.request_body, dict, "Body contents")
1056 op = _ParseMigrateInstanceRequest(self.items[0], self.request_body)
1058 return baserlib.SubmitJob([op])
1061 def _ParseRenameInstanceRequest(name, data):
1062 """Parses a request for renaming an instance.
1064 @rtype: L{opcodes.OpRenameInstance}
1065 @return: Instance rename opcode
1068 new_name = baserlib.CheckParameter(data, "new_name")
1069 ip_check = baserlib.CheckParameter(data, "ip_check", default=True)
1070 name_check = baserlib.CheckParameter(data, "name_check", default=True)
1072 return opcodes.OpRenameInstance(instance_name=name, new_name=new_name,
1073 name_check=name_check, ip_check=ip_check)
1076 class R_2_instances_name_rename(baserlib.R_Generic):
1077 """/2/instances/[instance_name]/rename resource.
1081 """Changes the name of an instance.
1086 baserlib.CheckType(self.request_body, dict, "Body contents")
1088 op = _ParseRenameInstanceRequest(self.items[0], self.request_body)
1090 return baserlib.SubmitJob([op])
1093 def _ParseModifyInstanceRequest(name, data):
1094 """Parses a request for modifying an instance.
1096 @rtype: L{opcodes.OpSetInstanceParams}
1097 @return: Instance modify opcode
1100 osparams = baserlib.CheckParameter(data, "osparams", default={})
1101 force = baserlib.CheckParameter(data, "force", default=False)
1102 nics = baserlib.CheckParameter(data, "nics", default=[])
1103 disks = baserlib.CheckParameter(data, "disks", default=[])
1104 disk_template = baserlib.CheckParameter(data, "disk_template", default=None)
1105 remote_node = baserlib.CheckParameter(data, "remote_node", default=None)
1106 os_name = baserlib.CheckParameter(data, "os_name", default=None)
1107 force_variant = baserlib.CheckParameter(data, "force_variant", default=False)
1110 hvparams = baserlib.CheckParameter(data, "hvparams", default={})
1111 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES,
1112 allowed_values=[constants.VALUE_DEFAULT])
1114 beparams = baserlib.CheckParameter(data, "beparams", default={})
1115 utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES,
1116 allowed_values=[constants.VALUE_DEFAULT])
1118 return opcodes.OpSetInstanceParams(instance_name=name, hvparams=hvparams,
1119 beparams=beparams, osparams=osparams,
1120 force=force, nics=nics, disks=disks,
1121 disk_template=disk_template,
1122 remote_node=remote_node, os_name=os_name,
1123 force_variant=force_variant)
1126 class R_2_instances_name_modify(baserlib.R_Generic):
1127 """/2/instances/[instance_name]/modify resource.
1131 """Changes some parameters of an instance.
1136 baserlib.CheckType(self.request_body, dict, "Body contents")
1138 op = _ParseModifyInstanceRequest(self.items[0], self.request_body)
1140 return baserlib.SubmitJob([op])
1143 class _R_Tags(baserlib.R_Generic):
1144 """ Quasiclass for tagging resources
1146 Manages tags. When inheriting this class you must define the
1152 def __init__(self, items, queryargs, req):
1153 """A tag resource constructor.
1155 We have to override the default to sort out cluster naming case.
1158 baserlib.R_Generic.__init__(self, items, queryargs, req)
1160 if self.TAG_LEVEL == constants.TAG_CLUSTER:
1163 self.name = items[0]
1166 """Returns a list of tags.
1168 Example: ["tag1", "tag2", "tag3"]
1171 # pylint: disable-msg=W0212
1172 return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
1175 """Add a set of tags.
1177 The request as a list of strings should be PUT to this URI. And
1178 you'll have back a job id.
1181 # pylint: disable-msg=W0212
1182 if 'tag' not in self.queryargs:
1183 raise http.HttpBadRequest("Please specify tag(s) to add using the"
1184 " the 'tag' parameter")
1185 return baserlib._Tags_PUT(self.TAG_LEVEL,
1186 self.queryargs['tag'], name=self.name,
1187 dry_run=bool(self.dryRun()))
1192 In order to delete a set of tags, the DELETE
1193 request should be addressed to URI like:
1194 /tags?tag=[tag]&tag=[tag]
1197 # pylint: disable-msg=W0212
1198 if 'tag' not in self.queryargs:
1199 # no we not gonna delete all tags
1200 raise http.HttpBadRequest("Cannot delete all tags - please specify"
1201 " tag(s) using the 'tag' parameter")
1202 return baserlib._Tags_DELETE(self.TAG_LEVEL,
1203 self.queryargs['tag'],
1205 dry_run=bool(self.dryRun()))
1208 class R_2_instances_name_tags(_R_Tags):
1209 """ /2/instances/[instance_name]/tags resource.
1211 Manages per-instance tags.
1214 TAG_LEVEL = constants.TAG_INSTANCE
1217 class R_2_nodes_name_tags(_R_Tags):
1218 """ /2/nodes/[node_name]/tags resource.
1220 Manages per-node tags.
1223 TAG_LEVEL = constants.TAG_NODE
1226 class R_2_tags(_R_Tags):
1227 """ /2/instances/tags resource.
1229 Manages cluster tags.
1232 TAG_LEVEL = constants.TAG_CLUSTER