4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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 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",
78 "node_cnt", "node_list",
79 "ctime", "mtime", "serial_no",
80 ] # "tags" is missing to be able to use _COMMON_FIELDS here.
82 _NR_DRAINED = "drained"
83 _NR_MASTER_CANDIATE = "master-candidate"
85 _NR_OFFLINE = "offline"
86 _NR_REGULAR = "regular"
89 constants.NR_MASTER: _NR_MASTER,
90 constants.NR_MCANDIDATE: _NR_MASTER_CANDIATE,
91 constants.NR_DRAINED: _NR_DRAINED,
92 constants.NR_OFFLINE: _NR_OFFLINE,
93 constants.NR_REGULAR: _NR_REGULAR,
96 assert frozenset(_NR_MAP.keys()) == constants.NR_ALL
98 # Request data version field
99 _REQ_DATA_VERSION = "__version__"
101 # Feature string for instance creation request data version 1
102 _INST_CREATE_REQV1 = "instance-create-reqv1"
104 # Feature string for instance reinstall request version 1
105 _INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
107 # Timeout for /2/jobs/[job_id]/wait. Gives job up to 10 seconds to change.
111 class R_version(baserlib.R_Generic):
112 """/version resource.
114 This resource should be used to determine the remote API version and
115 to adapt clients accordingly.
120 """Returns the remote API version.
123 return constants.RAPI_VERSION
126 class R_2_info(baserlib.R_Generic):
132 """Returns cluster information.
135 client = baserlib.GetClient()
136 return client.QueryClusterInfo()
139 class R_2_features(baserlib.R_Generic):
140 """/2/features resource.
145 """Returns list of optional RAPI features implemented.
148 return [_INST_CREATE_REQV1, _INST_REINSTALL_REQV1]
151 class R_2_os(baserlib.R_Generic):
157 """Return a list of all OSes.
159 Can return error 500 in case of a problem.
161 Example: ["debian-etch"]
164 cl = baserlib.GetClient()
165 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
166 job_id = baserlib.SubmitJob([op], cl)
167 # we use custom feedback function, instead of print we log the status
168 result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
169 diagnose_data = result[0]
171 if not isinstance(diagnose_data, list):
172 raise http.HttpBadGateway(message="Can't get OS list")
175 for (name, variants) in diagnose_data:
176 os_names.extend(cli.CalculateOSNames(name, variants))
181 class R_2_redist_config(baserlib.R_Generic):
182 """/2/redistribute-config resource.
187 """Redistribute configuration to all nodes.
190 return baserlib.SubmitJob([opcodes.OpClusterRedistConf()])
193 class R_2_cluster_modify(baserlib.R_Generic):
194 """/2/modify resource.
198 """Modifies cluster parameters.
203 op = baserlib.FillOpcode(opcodes.OpClusterSetParams, self.request_body,
206 return baserlib.SubmitJob([op])
209 class R_2_jobs(baserlib.R_Generic):
215 """Returns a dictionary of jobs.
217 @return: a dictionary with jobs id and uri.
221 cl = baserlib.GetClient()
222 # Convert the list of lists to the list of ids
223 result = [job_id for [job_id] in cl.QueryJobs(None, fields)]
224 return baserlib.BuildUriList(result, "/2/jobs/%s",
225 uri_fields=("id", "uri"))
228 class R_2_jobs_id(baserlib.R_Generic):
229 """/2/jobs/[job_id] resource.
233 """Returns a job status.
235 @return: a dictionary with job parameters.
237 - id: job ID as a number
238 - status: current job status as a string
239 - ops: involved OpCodes as a list of dictionaries for each
241 - opstatus: OpCodes status as a list
242 - opresult: OpCodes results as a list of lists
245 fields = ["id", "ops", "status", "summary",
246 "opstatus", "opresult", "oplog",
247 "received_ts", "start_ts", "end_ts",
249 job_id = self.items[0]
250 result = baserlib.GetClient().QueryJobs([job_id, ], fields)[0]
252 raise http.HttpNotFound()
253 return baserlib.MapFields(fields, result)
256 """Cancel not-yet-started job.
259 job_id = self.items[0]
260 result = baserlib.GetClient().CancelJob(job_id)
264 class R_2_jobs_id_wait(baserlib.R_Generic):
265 """/2/jobs/[job_id]/wait resource.
268 # WaitForJobChange provides access to sensitive information and blocks
269 # machine resources (it's a blocking RAPI call), hence restricting access.
270 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
273 """Waits for job changes.
276 job_id = self.items[0]
278 fields = self.getBodyParameter("fields")
279 prev_job_info = self.getBodyParameter("previous_job_info", None)
280 prev_log_serial = self.getBodyParameter("previous_log_serial", None)
282 if not isinstance(fields, list):
283 raise http.HttpBadRequest("The 'fields' parameter should be a list")
285 if not (prev_job_info is None or isinstance(prev_job_info, list)):
286 raise http.HttpBadRequest("The 'previous_job_info' parameter should"
289 if not (prev_log_serial is None or
290 isinstance(prev_log_serial, (int, long))):
291 raise http.HttpBadRequest("The 'previous_log_serial' parameter should"
294 client = baserlib.GetClient()
295 result = client.WaitForJobChangeOnce(job_id, fields,
296 prev_job_info, prev_log_serial,
297 timeout=_WFJC_TIMEOUT)
299 raise http.HttpNotFound()
301 if result == constants.JOB_NOTCHANGED:
305 (job_info, log_entries) = result
308 "job_info": job_info,
309 "log_entries": log_entries,
313 class R_2_nodes(baserlib.R_Generic):
314 """/2/nodes resource.
318 """Returns a list of all nodes.
321 client = baserlib.GetClient()
324 bulkdata = client.QueryNodes([], N_FIELDS, False)
325 return baserlib.MapBulkFields(bulkdata, N_FIELDS)
327 nodesdata = client.QueryNodes([], ["name"], False)
328 nodeslist = [row[0] for row in nodesdata]
329 return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
330 uri_fields=("id", "uri"))
333 class R_2_nodes_name(baserlib.R_Generic):
334 """/2/nodes/[node_name] resource.
338 """Send information about a node.
341 node_name = self.items[0]
342 client = baserlib.GetClient()
344 result = baserlib.HandleItemQueryErrors(client.QueryNodes,
345 names=[node_name], fields=N_FIELDS,
346 use_locking=self.useLocking())
348 return baserlib.MapFields(N_FIELDS, result[0])
351 class R_2_nodes_name_role(baserlib.R_Generic):
352 """ /2/nodes/[node_name]/role resource.
356 """Returns the current node role.
361 node_name = self.items[0]
362 client = baserlib.GetClient()
363 result = client.QueryNodes(names=[node_name], fields=["role"],
364 use_locking=self.useLocking())
366 return _NR_MAP[result[0][0]]
369 """Sets the node role.
374 if not isinstance(self.request_body, basestring):
375 raise http.HttpBadRequest("Invalid body contents, not a string")
377 node_name = self.items[0]
378 role = self.request_body
380 if role == _NR_REGULAR:
385 elif role == _NR_MASTER_CANDIATE:
387 offline = drained = None
389 elif role == _NR_DRAINED:
391 candidate = offline = None
393 elif role == _NR_OFFLINE:
395 candidate = drained = None
398 raise http.HttpBadRequest("Can't set '%s' role" % role)
400 op = opcodes.OpNodeSetParams(node_name=node_name,
401 master_candidate=candidate,
404 force=bool(self.useForce()))
406 return baserlib.SubmitJob([op])
409 class R_2_nodes_name_evacuate(baserlib.R_Generic):
410 """/2/nodes/[node_name]/evacuate resource.
414 """Evacuate all secondary instances off a node.
417 node_name = self.items[0]
418 remote_node = self._checkStringVariable("remote_node", default=None)
419 iallocator = self._checkStringVariable("iallocator", default=None)
420 early_r = bool(self._checkIntVariable("early_release", default=0))
421 dry_run = bool(self.dryRun())
423 cl = baserlib.GetClient()
425 op = opcodes.OpNodeEvacStrategy(nodes=[node_name],
426 iallocator=iallocator,
427 remote_node=remote_node)
429 job_id = baserlib.SubmitJob([op], cl)
430 # we use custom feedback function, instead of print we log the status
431 result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
434 for iname, node in result[0]:
438 op = opcodes.OpInstanceReplaceDisks(instance_name=iname,
439 remote_node=node, disks=[],
440 mode=constants.REPLACE_DISK_CHG,
441 early_release=early_r)
442 jid = baserlib.SubmitJob([op])
443 jobs.append((jid, iname, node))
448 class R_2_nodes_name_migrate(baserlib.R_Generic):
449 """/2/nodes/[node_name]/migrate resource.
453 """Migrate all primary instances from a node.
456 node_name = self.items[0]
458 if "live" in self.queryargs and "mode" in self.queryargs:
459 raise http.HttpBadRequest("Only one of 'live' and 'mode' should"
461 elif "live" in self.queryargs:
462 if self._checkIntVariable("live", default=1):
463 mode = constants.HT_MIGRATION_LIVE
465 mode = constants.HT_MIGRATION_NONLIVE
467 mode = self._checkStringVariable("mode", default=None)
469 op = opcodes.OpNodeMigrate(node_name=node_name, mode=mode)
471 return baserlib.SubmitJob([op])
474 class R_2_nodes_name_storage(baserlib.R_Generic):
475 """/2/nodes/[node_name]/storage resource.
478 # LUNodeQueryStorage acquires locks, hence restricting access to GET
479 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
482 node_name = self.items[0]
484 storage_type = self._checkStringVariable("storage_type", None)
486 raise http.HttpBadRequest("Missing the required 'storage_type'"
489 output_fields = self._checkStringVariable("output_fields", None)
490 if not output_fields:
491 raise http.HttpBadRequest("Missing the required 'output_fields'"
494 op = opcodes.OpNodeQueryStorage(nodes=[node_name],
495 storage_type=storage_type,
496 output_fields=output_fields.split(","))
497 return baserlib.SubmitJob([op])
500 class R_2_nodes_name_storage_modify(baserlib.R_Generic):
501 """/2/nodes/[node_name]/storage/modify resource.
505 node_name = self.items[0]
507 storage_type = self._checkStringVariable("storage_type", None)
509 raise http.HttpBadRequest("Missing the required 'storage_type'"
512 name = self._checkStringVariable("name", None)
514 raise http.HttpBadRequest("Missing the required 'name'"
519 if "allocatable" in self.queryargs:
520 changes[constants.SF_ALLOCATABLE] = \
521 bool(self._checkIntVariable("allocatable", default=1))
523 op = opcodes.OpNodeModifyStorage(node_name=node_name,
524 storage_type=storage_type,
527 return baserlib.SubmitJob([op])
530 class R_2_nodes_name_storage_repair(baserlib.R_Generic):
531 """/2/nodes/[node_name]/storage/repair resource.
535 node_name = self.items[0]
537 storage_type = self._checkStringVariable("storage_type", None)
539 raise http.HttpBadRequest("Missing the required 'storage_type'"
542 name = self._checkStringVariable("name", None)
544 raise http.HttpBadRequest("Missing the required 'name'"
547 op = opcodes.OpRepairNodeStorage(node_name=node_name,
548 storage_type=storage_type,
550 return baserlib.SubmitJob([op])
553 def _ParseCreateGroupRequest(data, dry_run):
554 """Parses a request for creating a node group.
556 @rtype: L{opcodes.OpGroupAdd}
557 @return: Group creation opcode
565 "name": "group_name",
568 return baserlib.FillOpcode(opcodes.OpGroupAdd, data, override,
572 class R_2_groups(baserlib.R_Generic):
573 """/2/groups resource.
577 """Returns a list of all node groups.
580 client = baserlib.GetClient()
583 bulkdata = client.QueryGroups([], G_FIELDS, False)
584 return baserlib.MapBulkFields(bulkdata, G_FIELDS)
586 data = client.QueryGroups([], ["name"], False)
587 groupnames = [row[0] for row in data]
588 return baserlib.BuildUriList(groupnames, "/2/groups/%s",
589 uri_fields=("name", "uri"))
592 """Create a node group.
597 baserlib.CheckType(self.request_body, dict, "Body contents")
598 op = _ParseCreateGroupRequest(self.request_body, self.dryRun())
599 return baserlib.SubmitJob([op])
602 class R_2_groups_name(baserlib.R_Generic):
603 """/2/groups/[group_name] resource.
607 """Send information about a node group.
610 group_name = self.items[0]
611 client = baserlib.GetClient()
613 result = baserlib.HandleItemQueryErrors(client.QueryGroups,
614 names=[group_name], fields=G_FIELDS,
615 use_locking=self.useLocking())
617 return baserlib.MapFields(G_FIELDS, result[0])
620 """Delete a node group.
623 op = opcodes.OpGroupRemove(group_name=self.items[0],
624 dry_run=bool(self.dryRun()))
626 return baserlib.SubmitJob([op])
629 def _ParseModifyGroupRequest(name, data):
630 """Parses a request for modifying a node group.
632 @rtype: L{opcodes.OpGroupSetParams}
633 @return: Group modify opcode
636 return baserlib.FillOpcode(opcodes.OpGroupSetParams, data, {
642 class R_2_groups_name_modify(baserlib.R_Generic):
643 """/2/groups/[group_name]/modify resource.
647 """Changes some parameters of node group.
652 baserlib.CheckType(self.request_body, dict, "Body contents")
654 op = _ParseModifyGroupRequest(self.items[0], self.request_body)
656 return baserlib.SubmitJob([op])
659 def _ParseRenameGroupRequest(name, data, dry_run):
660 """Parses a request for renaming a node group.
663 @param name: name of the node group to rename
665 @param data: the body received by the rename request
667 @param dry_run: whether to perform a dry run
669 @rtype: L{opcodes.OpGroupRename}
670 @return: Node group rename opcode
673 return baserlib.FillOpcode(opcodes.OpGroupRename, data, {
679 class R_2_groups_name_rename(baserlib.R_Generic):
680 """/2/groups/[group_name]/rename resource.
684 """Changes the name of a node group.
689 baserlib.CheckType(self.request_body, dict, "Body contents")
690 op = _ParseRenameGroupRequest(self.items[0], self.request_body,
692 return baserlib.SubmitJob([op])
695 class R_2_groups_name_assign_nodes(baserlib.R_Generic):
696 """/2/groups/[group_name]/assign-nodes resource.
700 """Assigns nodes to a group.
705 op = baserlib.FillOpcode(opcodes.OpGroupAssignNodes, self.request_body, {
706 "group_name": self.items[0],
707 "dry_run": self.dryRun(),
708 "force": self.useForce(),
711 return baserlib.SubmitJob([op])
714 def _ParseInstanceCreateRequestVersion1(data, dry_run):
715 """Parses an instance creation request version 1.
717 @rtype: L{opcodes.OpInstanceCreate}
718 @return: Instance creation opcode
727 "name": "instance_name",
730 return baserlib.FillOpcode(opcodes.OpInstanceCreate, data, override,
734 class R_2_instances(baserlib.R_Generic):
735 """/2/instances resource.
739 """Returns a list of all available instances.
742 client = baserlib.GetClient()
744 use_locking = self.useLocking()
746 bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
747 return baserlib.MapBulkFields(bulkdata, I_FIELDS)
749 instancesdata = client.QueryInstances([], ["name"], use_locking)
750 instanceslist = [row[0] for row in instancesdata]
751 return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
752 uri_fields=("id", "uri"))
755 """Create an instance.
760 if not isinstance(self.request_body, dict):
761 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
763 # Default to request data version 0
764 data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0)
766 if data_version == 0:
767 raise http.HttpBadRequest("Instance creation request version 0 is no"
769 elif data_version == 1:
770 data = self.request_body.copy()
771 # Remove "__version__"
772 data.pop(_REQ_DATA_VERSION, None)
773 op = _ParseInstanceCreateRequestVersion1(data, self.dryRun())
775 raise http.HttpBadRequest("Unsupported request data version %s" %
778 return baserlib.SubmitJob([op])
781 class R_2_instances_name(baserlib.R_Generic):
782 """/2/instances/[instance_name] resource.
786 """Send information about an instance.
789 client = baserlib.GetClient()
790 instance_name = self.items[0]
792 result = baserlib.HandleItemQueryErrors(client.QueryInstances,
793 names=[instance_name],
795 use_locking=self.useLocking())
797 return baserlib.MapFields(I_FIELDS, result[0])
800 """Delete an instance.
803 op = opcodes.OpInstanceRemove(instance_name=self.items[0],
804 ignore_failures=False,
805 dry_run=bool(self.dryRun()))
806 return baserlib.SubmitJob([op])
809 class R_2_instances_name_info(baserlib.R_Generic):
810 """/2/instances/[instance_name]/info resource.
814 """Request detailed instance information.
817 instance_name = self.items[0]
818 static = bool(self._checkIntVariable("static", default=0))
820 op = opcodes.OpInstanceQueryData(instances=[instance_name],
822 return baserlib.SubmitJob([op])
825 class R_2_instances_name_reboot(baserlib.R_Generic):
826 """/2/instances/[instance_name]/reboot resource.
828 Implements an instance reboot.
832 """Reboot an instance.
834 The URI takes type=[hard|soft|full] and
835 ignore_secondaries=[False|True] parameters.
838 instance_name = self.items[0]
839 reboot_type = self.queryargs.get('type',
840 [constants.INSTANCE_REBOOT_HARD])[0]
841 ignore_secondaries = bool(self._checkIntVariable('ignore_secondaries'))
842 op = opcodes.OpInstanceReboot(instance_name=instance_name,
843 reboot_type=reboot_type,
844 ignore_secondaries=ignore_secondaries,
845 dry_run=bool(self.dryRun()))
847 return baserlib.SubmitJob([op])
850 class R_2_instances_name_startup(baserlib.R_Generic):
851 """/2/instances/[instance_name]/startup resource.
853 Implements an instance startup.
857 """Startup an instance.
859 The URI takes force=[False|True] parameter to start the instance
860 if even if secondary disks are failing.
863 instance_name = self.items[0]
864 force_startup = bool(self._checkIntVariable('force'))
865 op = opcodes.OpInstanceStartup(instance_name=instance_name,
867 dry_run=bool(self.dryRun()))
869 return baserlib.SubmitJob([op])
872 def _ParseShutdownInstanceRequest(name, data, dry_run):
873 """Parses a request for an instance shutdown.
875 @rtype: L{opcodes.OpInstanceShutdown}
876 @return: Instance shutdown opcode
879 return baserlib.FillOpcode(opcodes.OpInstanceShutdown, data, {
880 "instance_name": name,
885 class R_2_instances_name_shutdown(baserlib.R_Generic):
886 """/2/instances/[instance_name]/shutdown resource.
888 Implements an instance shutdown.
892 """Shutdown an instance.
897 baserlib.CheckType(self.request_body, dict, "Body contents")
899 op = _ParseShutdownInstanceRequest(self.items[0], self.request_body,
902 return baserlib.SubmitJob([op])
905 def _ParseInstanceReinstallRequest(name, data):
906 """Parses a request for reinstalling an instance.
909 if not isinstance(data, dict):
910 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
912 ostype = baserlib.CheckParameter(data, "os", default=None)
913 start = baserlib.CheckParameter(data, "start", exptype=bool,
915 osparams = baserlib.CheckParameter(data, "osparams", default=None)
918 opcodes.OpInstanceShutdown(instance_name=name),
919 opcodes.OpInstanceReinstall(instance_name=name, os_type=ostype,
924 ops.append(opcodes.OpInstanceStartup(instance_name=name, force=False))
929 class R_2_instances_name_reinstall(baserlib.R_Generic):
930 """/2/instances/[instance_name]/reinstall resource.
932 Implements an instance reinstall.
936 """Reinstall an instance.
938 The URI takes os=name and nostartup=[0|1] optional
939 parameters. By default, the instance will be started
943 if self.request_body:
945 raise http.HttpBadRequest("Can't combine query and body parameters")
947 body = self.request_body
949 # Legacy interface, do not modify/extend
951 "os": self._checkStringVariable("os"),
952 "start": not self._checkIntVariable("nostartup"),
957 ops = _ParseInstanceReinstallRequest(self.items[0], body)
959 return baserlib.SubmitJob(ops)
962 def _ParseInstanceReplaceDisksRequest(name, data):
963 """Parses a request for an instance export.
965 @rtype: L{opcodes.OpInstanceReplaceDisks}
966 @return: Instance export opcode
970 "instance_name": name,
975 raw_disks = data["disks"]
979 if not ht.TListOf(ht.TInt)(raw_disks): # pylint: disable-msg=E1102
980 # Backwards compatibility for strings of the format "1, 2, 3"
982 data["disks"] = [int(part) for part in raw_disks.split(",")]
983 except (TypeError, ValueError), err:
984 raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
986 return baserlib.FillOpcode(opcodes.OpInstanceReplaceDisks, data, override)
989 class R_2_instances_name_replace_disks(baserlib.R_Generic):
990 """/2/instances/[instance_name]/replace-disks resource.
994 """Replaces disks on an instance.
997 op = _ParseInstanceReplaceDisksRequest(self.items[0], self.request_body)
999 return baserlib.SubmitJob([op])
1002 class R_2_instances_name_activate_disks(baserlib.R_Generic):
1003 """/2/instances/[instance_name]/activate-disks resource.
1007 """Activate disks for an instance.
1009 The URI might contain ignore_size to ignore current recorded size.
1012 instance_name = self.items[0]
1013 ignore_size = bool(self._checkIntVariable('ignore_size'))
1015 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
1016 ignore_size=ignore_size)
1018 return baserlib.SubmitJob([op])
1021 class R_2_instances_name_deactivate_disks(baserlib.R_Generic):
1022 """/2/instances/[instance_name]/deactivate-disks resource.
1026 """Deactivate disks for an instance.
1029 instance_name = self.items[0]
1031 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name)
1033 return baserlib.SubmitJob([op])
1036 class R_2_instances_name_prepare_export(baserlib.R_Generic):
1037 """/2/instances/[instance_name]/prepare-export resource.
1041 """Prepares an export for an instance.
1046 instance_name = self.items[0]
1047 mode = self._checkStringVariable("mode")
1049 op = opcodes.OpBackupPrepare(instance_name=instance_name,
1052 return baserlib.SubmitJob([op])
1055 def _ParseExportInstanceRequest(name, data):
1056 """Parses a request for an instance export.
1058 @rtype: L{opcodes.OpBackupExport}
1059 @return: Instance export opcode
1062 # Rename "destination" to "target_node"
1064 data["target_node"] = data.pop("destination")
1068 return baserlib.FillOpcode(opcodes.OpBackupExport, data, {
1069 "instance_name": name,
1073 class R_2_instances_name_export(baserlib.R_Generic):
1074 """/2/instances/[instance_name]/export resource.
1078 """Exports an instance.
1083 if not isinstance(self.request_body, dict):
1084 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
1086 op = _ParseExportInstanceRequest(self.items[0], self.request_body)
1088 return baserlib.SubmitJob([op])
1091 def _ParseMigrateInstanceRequest(name, data):
1092 """Parses a request for an instance migration.
1094 @rtype: L{opcodes.OpInstanceMigrate}
1095 @return: Instance migration opcode
1098 return baserlib.FillOpcode(opcodes.OpInstanceMigrate, data, {
1099 "instance_name": name,
1103 class R_2_instances_name_migrate(baserlib.R_Generic):
1104 """/2/instances/[instance_name]/migrate resource.
1108 """Migrates an instance.
1113 baserlib.CheckType(self.request_body, dict, "Body contents")
1115 op = _ParseMigrateInstanceRequest(self.items[0], self.request_body)
1117 return baserlib.SubmitJob([op])
1120 def _ParseRenameInstanceRequest(name, data):
1121 """Parses a request for renaming an instance.
1123 @rtype: L{opcodes.OpInstanceRename}
1124 @return: Instance rename opcode
1127 return baserlib.FillOpcode(opcodes.OpInstanceRename, data, {
1128 "instance_name": name,
1132 class R_2_instances_name_rename(baserlib.R_Generic):
1133 """/2/instances/[instance_name]/rename resource.
1137 """Changes the name of an instance.
1142 baserlib.CheckType(self.request_body, dict, "Body contents")
1144 op = _ParseRenameInstanceRequest(self.items[0], self.request_body)
1146 return baserlib.SubmitJob([op])
1149 def _ParseModifyInstanceRequest(name, data):
1150 """Parses a request for modifying an instance.
1152 @rtype: L{opcodes.OpInstanceSetParams}
1153 @return: Instance modify opcode
1156 return baserlib.FillOpcode(opcodes.OpInstanceSetParams, data, {
1157 "instance_name": name,
1161 class R_2_instances_name_modify(baserlib.R_Generic):
1162 """/2/instances/[instance_name]/modify resource.
1166 """Changes some parameters of an instance.
1171 baserlib.CheckType(self.request_body, dict, "Body contents")
1173 op = _ParseModifyInstanceRequest(self.items[0], self.request_body)
1175 return baserlib.SubmitJob([op])
1178 class R_2_instances_name_disk_grow(baserlib.R_Generic):
1179 """/2/instances/[instance_name]/disk/[disk_index]/grow resource.
1183 """Increases the size of an instance disk.
1188 op = baserlib.FillOpcode(opcodes.OpInstanceGrowDisk, self.request_body, {
1189 "instance_name": self.items[0],
1190 "disk": int(self.items[1]),
1193 return baserlib.SubmitJob([op])
1196 class R_2_instances_name_console(baserlib.R_Generic):
1197 """/2/instances/[instance_name]/console resource.
1200 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
1203 """Request information for connecting to instance's console.
1205 @return: Serialized instance console description, see
1206 L{objects.InstanceConsole}
1209 client = baserlib.GetClient()
1211 ((console, ), ) = client.QueryInstances([self.items[0]], ["console"], False)
1214 raise http.HttpServiceUnavailable("Instance console unavailable")
1216 assert isinstance(console, dict)
1220 def _GetQueryFields(args):
1225 fields = args["fields"]
1227 raise http.HttpBadRequest("Missing 'fields' query argument")
1229 return _SplitQueryFields(fields[0])
1232 def _SplitQueryFields(fields):
1236 return [i.strip() for i in fields.split(",")]
1239 class R_2_query(baserlib.R_Generic):
1240 """/2/query/[resource] resource.
1243 # Results might contain sensitive information
1244 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
1246 def _Query(self, fields, filter_):
1247 return baserlib.GetClient().Query(self.items[0], fields, filter_).ToDict()
1250 """Returns resource information.
1252 @return: Query result, see L{objects.QueryResponse}
1255 return self._Query(_GetQueryFields(self.queryargs), None)
1258 """Submits job querying for resources.
1260 @return: Query result, see L{objects.QueryResponse}
1263 body = self.request_body
1265 baserlib.CheckType(body, dict, "Body contents")
1268 fields = body["fields"]
1270 fields = _GetQueryFields(self.queryargs)
1272 return self._Query(fields, self.request_body.get("filter", None))
1275 class R_2_query_fields(baserlib.R_Generic):
1276 """/2/query/[resource]/fields resource.
1280 """Retrieves list of available fields for a resource.
1282 @return: List of serialized L{objects.QueryFieldDefinition}
1286 raw_fields = self.queryargs["fields"]
1290 fields = _SplitQueryFields(raw_fields[0])
1292 return baserlib.GetClient().QueryFields(self.items[0], fields).ToDict()
1295 class _R_Tags(baserlib.R_Generic):
1296 """ Quasiclass for tagging resources
1298 Manages tags. When inheriting this class you must define the
1304 def __init__(self, items, queryargs, req):
1305 """A tag resource constructor.
1307 We have to override the default to sort out cluster naming case.
1310 baserlib.R_Generic.__init__(self, items, queryargs, req)
1312 if self.TAG_LEVEL == constants.TAG_CLUSTER:
1315 self.name = items[0]
1318 """Returns a list of tags.
1320 Example: ["tag1", "tag2", "tag3"]
1323 # pylint: disable-msg=W0212
1324 return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
1327 """Add a set of tags.
1329 The request as a list of strings should be PUT to this URI. And
1330 you'll have back a job id.
1333 # pylint: disable-msg=W0212
1334 if 'tag' not in self.queryargs:
1335 raise http.HttpBadRequest("Please specify tag(s) to add using the"
1336 " the 'tag' parameter")
1337 return baserlib._Tags_PUT(self.TAG_LEVEL,
1338 self.queryargs['tag'], name=self.name,
1339 dry_run=bool(self.dryRun()))
1344 In order to delete a set of tags, the DELETE
1345 request should be addressed to URI like:
1346 /tags?tag=[tag]&tag=[tag]
1349 # pylint: disable-msg=W0212
1350 if 'tag' not in self.queryargs:
1351 # no we not gonna delete all tags
1352 raise http.HttpBadRequest("Cannot delete all tags - please specify"
1353 " tag(s) using the 'tag' parameter")
1354 return baserlib._Tags_DELETE(self.TAG_LEVEL,
1355 self.queryargs['tag'],
1357 dry_run=bool(self.dryRun()))
1360 class R_2_instances_name_tags(_R_Tags):
1361 """ /2/instances/[instance_name]/tags resource.
1363 Manages per-instance tags.
1366 TAG_LEVEL = constants.TAG_INSTANCE
1369 class R_2_nodes_name_tags(_R_Tags):
1370 """ /2/nodes/[node_name]/tags resource.
1372 Manages per-node tags.
1375 TAG_LEVEL = constants.TAG_NODE
1378 class R_2_tags(_R_Tags):
1379 """ /2/tags resource.
1381 Manages cluster tags.
1384 TAG_LEVEL = constants.TAG_CLUSTER