diff --git a/doc/rapi.rst b/doc/rapi.rst index 23805d6f734f5ef00638e76b37fbf352ba31d661..3b785aeb8ac3769ba4448cd8288d116aba1e8211 100644 --- a/doc/rapi.rst +++ b/doc/rapi.rst @@ -1337,6 +1337,28 @@ be a job id. It supports the bool ``force`` argument. + +``/2/nodes/[node_name]/modify`` ++++++++++++++++++++++++++++++++ + +Modifies the parameters of a node. Supports the following commands: +``POST``. + +``POST`` +~~~~~~~~ + +Returns a job ID. + +Body parameters: + +.. opcode_params:: OP_NODE_SET_PARAMS + :exclude: node_name + +Job result: + +.. opcode_result:: OP_NODE_SET_PARAMS + + ``/2/nodes/[node_name]/storage`` ++++++++++++++++++++++++++++++++ diff --git a/lib/constants.py b/lib/constants.py index 79d0b2fed0d3921cc3ba71d3c2a781702d804aa8..003774c588b98e76ac4f3d05fb011a24dd9cdf40 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -270,6 +270,7 @@ EXPORT_CONF_FILE = "config.ini" XEN_BOOTLOADER = _autoconf.XEN_BOOTLOADER XEN_KERNEL = _autoconf.XEN_KERNEL XEN_INITRD = _autoconf.XEN_INITRD +XEN_CMD = "xm" KVM_PATH = _autoconf.KVM_PATH SOCAT_PATH = _autoconf.SOCAT_PATH diff --git a/lib/hypervisor/hv_xen.py b/lib/hypervisor/hv_xen.py index f8113a8109e099851739a3ee5b37d8b5432a0af6..7e2418f203dfa09e5cb7a0e5703a3c79954b5d66 100644 --- a/lib/hypervisor/hv_xen.py +++ b/lib/hypervisor/hv_xen.py @@ -47,9 +47,22 @@ class XenHypervisor(hv_base.BaseHypervisor): ANCILLARY_FILES = [ "/etc/xen/xend-config.sxp", + "/etc/xen/xl.conf", "/etc/xen/scripts/vif-bridge", ] + @staticmethod + def _ConfigFileName(instance_name): + """Get the config file name for an instance. + + @param instance_name: instance name + @type instance_name: str + @return: fully qualified path to instance config file + @rtype: str + + """ + return "/etc/xen/%s" % instance_name + @classmethod def _WriteConfigFile(cls, instance, block_devices): """Write the Xen config file for the instance. @@ -64,7 +77,7 @@ class XenHypervisor(hv_base.BaseHypervisor): This version of the function just writes the config file from static data. """ - utils.WriteFile("/etc/xen/%s" % instance_name, data=data) + utils.WriteFile(XenHypervisor._ConfigFileName(instance_name), data=data) @staticmethod def _ReadConfigFile(instance_name): @@ -72,7 +85,8 @@ class XenHypervisor(hv_base.BaseHypervisor): """ try: - file_content = utils.ReadFile("/etc/xen/%s" % instance_name) + file_content = utils.ReadFile( + XenHypervisor._ConfigFileName(instance_name)) except EnvironmentError, err: raise errors.HypervisorError("Failed to load Xen config file: %s" % err) return file_content @@ -82,7 +96,7 @@ class XenHypervisor(hv_base.BaseHypervisor): """Remove the xen configuration file. """ - utils.RemoveFile("/etc/xen/%s" % instance_name) + utils.RemoveFile(XenHypervisor._ConfigFileName(instance_name)) @classmethod def _CreateConfigCpus(cls, cpu_mask): @@ -121,7 +135,7 @@ class XenHypervisor(hv_base.BaseHypervisor): """Helper function for L{_GetXMList} to run "xm list". """ - result = utils.RunCmd(["xm", "list"]) + result = utils.RunCmd([constants.XEN_CMD, "list"]) if result.failed: logging.error("xm list failed (%s): %s", result.fail_reason, result.output) @@ -217,10 +231,10 @@ class XenHypervisor(hv_base.BaseHypervisor): """ self._WriteConfigFile(instance, block_devices) - cmd = ["xm", "create"] + cmd = [constants.XEN_CMD, "create"] if startup_paused: - cmd.extend(["--paused"]) - cmd.extend([instance.name]) + cmd.extend(["-p"]) + cmd.extend([self._ConfigFileName(instance.name)]) result = utils.RunCmd(cmd) if result.failed: @@ -236,9 +250,9 @@ class XenHypervisor(hv_base.BaseHypervisor): name = instance.name self._RemoveConfigFile(name) if force: - command = ["xm", "destroy", name] + command = [constants.XEN_CMD, "destroy", name] else: - command = ["xm", "shutdown", name] + command = [constants.XEN_CMD, "shutdown", name] result = utils.RunCmd(command) if result.failed: @@ -255,7 +269,7 @@ class XenHypervisor(hv_base.BaseHypervisor): raise errors.HypervisorError("Failed to reboot instance %s," " not running" % instance.name) - result = utils.RunCmd(["xm", "reboot", instance.name]) + result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name]) if result.failed: raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" % (instance.name, result.fail_reason, @@ -293,7 +307,7 @@ class XenHypervisor(hv_base.BaseHypervisor): """ # note: in xen 3, memory has changed to total_memory - result = utils.RunCmd(["xm", "info"]) + result = utils.RunCmd([constants.XEN_CMD, "info"]) if result.failed: logging.error("Can't run 'xm info' (%s): %s", result.fail_reason, result.output) @@ -357,7 +371,7 @@ class XenHypervisor(hv_base.BaseHypervisor): For Xen, this verifies that the xend process is running. """ - result = utils.RunCmd(["xm", "info"]) + result = utils.RunCmd([constants.XEN_CMD, "info"]) if result.failed: return "'xm info' failed: %s, %s" % (result.fail_reason, result.output) @@ -464,7 +478,12 @@ class XenHypervisor(hv_base.BaseHypervisor): raise errors.HypervisorError("Remote host %s not listening on port" " %s, cannot migrate" % (target, port)) - args = ["xm", "migrate", "-p", "%d" % port] + # FIXME: migrate must be upgraded for transitioning to "xl" (xen 4.1). + # -l doesn't exist anymore + # -p doesn't exist anymore + # -C config_file must be passed + # ssh must recognize the key of the target host for the migration + args = [constants.XEN_CMD, "migrate", "-p", "%d" % port] if live: args.append("-l") args.extend([instance.name, target]) @@ -524,7 +543,7 @@ class XenHypervisor(hv_base.BaseHypervisor): try: cls.LinuxPowercycle() finally: - utils.RunCmd(["xm", "debug", "R"]) + utils.RunCmd([constants.XEN_CMD, "debug", "R"]) class XenPvmHypervisor(XenHypervisor): @@ -617,11 +636,12 @@ class XenPvmHypervisor(XenHypervisor): # just in case it exists utils.RemoveFile("/etc/xen/auto/%s" % instance.name) try: - utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue()) + utils.WriteFile(cls._ConfigFileName(instance.name), + data=config.getvalue()) except EnvironmentError, err: raise errors.HypervisorError("Cannot write Xen instance confile" - " file /etc/xen/%s: %s" % - (instance.name, err)) + " file %s: %s" % + (cls._ConfigFileName(instance.name), err)) return True @@ -763,11 +783,11 @@ class XenHvmHypervisor(XenHypervisor): # just in case it exists utils.RemoveFile("/etc/xen/auto/%s" % instance.name) try: - utils.WriteFile("/etc/xen/%s" % instance.name, + utils.WriteFile(cls._ConfigFileName(instance.name), data=config.getvalue()) except EnvironmentError, err: raise errors.HypervisorError("Cannot write Xen instance confile" - " file /etc/xen/%s: %s" % - (instance.name, err)) + " file %s: %s" % + (cls._ConfigFileName(instance.name), err)) return True diff --git a/lib/opcodes.py b/lib/opcodes.py index 1367c2a55256a8275d40d2a0b738c0e263e2a0b5..714d066c208d72931e38e23ddf721f35982834b6 100644 --- a/lib/opcodes.py +++ b/lib/opcodes.py @@ -158,7 +158,7 @@ _TestNicDef = ht.TDictOf(ht.TElemOf(constants.INIC_PARAMS), _TSetParamsResultItemItems = [ ht.Comment("name of changed parameter")(ht.TNonEmptyString), - ht.TAny, + ht.Comment("new value")(ht.TAny), ] _TSetParamsResult = \ diff --git a/lib/rapi/client.py b/lib/rapi/client.py index e72b30839a9eb73cbb2ee23e75077a8421d0fe35..6b37c71e930745e43ae9c94d0164514ab869a771 100644 --- a/lib/rapi/client.py +++ b/lib/rapi/client.py @@ -1452,7 +1452,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904 ("/%s/nodes/%s/role" % (GANETI_RAPI_VERSION, node)), None, None) - def SetNodeRole(self, node, role, force=False): + def SetNodeRole(self, node, role, force=False, auto_promote=None): """Sets the role for a node. @type node: str @@ -1461,6 +1461,9 @@ class GanetiRapiClient(object): # pylint: disable=R0904 @param role: the role to set for the node @type force: bool @param force: whether to force the role change + @type auto_promote: bool + @param auto_promote: Whether node(s) should be promoted to master candidate + if necessary @rtype: string @return: job id @@ -1470,6 +1473,9 @@ class GanetiRapiClient(object): # pylint: disable=R0904 ("force", force), ] + if auto_promote is not None: + query.append(("auto-promote", auto_promote)) + return self._SendRequest(HTTP_PUT, ("/%s/nodes/%s/role" % (GANETI_RAPI_VERSION, node)), query, role) @@ -1481,7 +1487,6 @@ class GanetiRapiClient(object): # pylint: disable=R0904 @param node: Node name @type force: bool @param force: Whether to force the operation - @rtype: string @return: job id @@ -1494,6 +1499,21 @@ class GanetiRapiClient(object): # pylint: disable=R0904 ("/%s/nodes/%s/powercycle" % (GANETI_RAPI_VERSION, node)), query, None) + def ModifyNode(self, node, **kwargs): + """Modifies a node. + + More details for parameters can be found in the RAPI documentation. + + @type node: string + @param node: Node name + @rtype: string + @return: job id + + """ + return self._SendRequest(HTTP_PUT, + ("/%s/nodes/%s/modify" % + (GANETI_RAPI_VERSION, node)), None, kwargs) + def GetNodeStorageUnits(self, node, storage_type, output_fields): """Gets the storage units for a node. diff --git a/lib/rapi/connector.py b/lib/rapi/connector.py index a20027bee2511de36f41c1dc62d3fab4b40bb8bc..b701d7141c34170258e3c26f87e73375f838a5ac 100644 --- a/lib/rapi/connector.py +++ b/lib/rapi/connector.py @@ -117,6 +117,8 @@ def GetHandlers(node_name_pattern, instance_name_pattern, rlib2.R_2_nodes_name_evacuate, re.compile(r"^/2/nodes/(%s)/migrate$" % node_name_pattern): rlib2.R_2_nodes_name_migrate, + re.compile(r"^/2/nodes/(%s)/modify$" % node_name_pattern): + rlib2.R_2_nodes_name_modify, re.compile(r"^/2/nodes/(%s)/storage$" % node_name_pattern): rlib2.R_2_nodes_name_storage, re.compile(r"^/2/nodes/(%s)/storage/modify$" % node_name_pattern): diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py index a3277e25fb333006dc14940ec3fc4e9184adfe45..ccaccf944966228083d8a5ce76a1cef71d37339c 100644 --- a/lib/rapi/rlib2.py +++ b/lib/rapi/rlib2.py @@ -468,6 +468,7 @@ class R_2_nodes_name_role(baserlib.OpcodeResource): "offline": offline, "drained": drained, "force": self.useForce(), + "auto_promote": bool(self._checkIntVariable("auto-promote", default=0)), }) @@ -522,6 +523,23 @@ class R_2_nodes_name_migrate(baserlib.OpcodeResource): }) +class R_2_nodes_name_modify(baserlib.OpcodeResource): + """/2/nodes/[node_name]/modify resource. + + """ + PUT_OPCODE = opcodes.OpNodeSetParams + + def GetPutOpInput(self): + """Changes parameters of a node. + + """ + assert len(self.items) == 1 + + return (self.request_body, { + "node_name": self.items[0], + }) + + class R_2_nodes_name_storage(baserlib.OpcodeResource): """/2/nodes/[node_name]/storage resource. diff --git a/test/ganeti.rapi.client_unittest.py b/test/ganeti.rapi.client_unittest.py index c84f72e915aba045d0602200b13c906133b485b7..e98198dc14d68d2e92f676bfa9100a2c03ff8642 100755 --- a/test/ganeti.rapi.client_unittest.py +++ b/test/ganeti.rapi.client_unittest.py @@ -983,6 +983,14 @@ class GanetiRapiClientTests(testutils.GanetiTestCase): self.assertFalse(self.rapi.GetLastRequestData()) self.assertEqual(self.rapi.CountPending(), 0) + def testModifyNode(self): + self.rapi.AddResponse("3783") + job_id = self.client.ModifyNode("node16979.example.com", drained=True) + self.assertEqual(job_id, 3783) + self.assertHandler(rlib2.R_2_nodes_name_modify) + self.assertItems(["node16979.example.com"]) + self.assertEqual(self.rapi.CountPending(), 0) + def testGetNodeStorageUnits(self): self.rapi.AddResponse("42") self.assertEqual(42,