diff --git a/lib/rapi/client.py b/lib/rapi/client.py index 6ccf1fb7b46b6e9a2a65d0d3d6818da1e7694e00..da552fd7d322ee1356ffe689649c83288f42129c 100644 --- a/lib/rapi/client.py +++ b/lib/rapi/client.py @@ -674,6 +674,78 @@ class GanetiRapiClient(object): # pylint: disable=R0904 ("/%s/instances/%s/info" % (GANETI_RAPI_VERSION, instance)), query, None) + @staticmethod + def _UpdateWithKwargs(base, **kwargs): + """Updates the base with params from kwargs. + + @param base: The base dict, filled with required fields + + @note: This is an inplace update of base + + """ + conflicts = set(kwargs.iterkeys()) & set(base.iterkeys()) + if conflicts: + raise GanetiApiError("Required fields can not be specified as" + " keywords: %s" % ", ".join(conflicts)) + + base.update((key, value) for key, value in kwargs.iteritems() + if key != "dry_run") + + def InstanceAllocation(self, mode, name, disk_template, disks, nics, + **kwargs): + """Generates an instance allocation as used by multiallocate. + + More details for parameters can be found in the RAPI documentation. + It is the same as used by CreateInstance. + + @type mode: string + @param mode: Instance creation mode + @type name: string + @param name: Hostname of the instance to create + @type disk_template: string + @param disk_template: Disk template for instance (e.g. plain, diskless, + file, or drbd) + @type disks: list of dicts + @param disks: List of disk definitions + @type nics: list of dicts + @param nics: List of NIC definitions + + @return: A dict with the generated entry + + """ + # All required fields for request data version 1 + alloc = { + "mode": mode, + "name": name, + "disk_template": disk_template, + "disks": disks, + "nics": nics, + } + + self._UpdateWithKwargs(alloc, **kwargs) + + return alloc + + def InstancesMultiAlloc(self, instances, **kwargs): + """Tries to allocate multiple instances. + + More details for parameters can be found in the RAPI documentation. + + @param instances: A list of L{InstanceAllocation} results + + """ + query = [] + body = { + "instances": instances, + } + self._UpdateWithKwargs(body, **kwargs) + + _AppendDryRunIf(query, kwargs.get("dry_run")) + + return self._SendRequest(HTTP_POST, + "/%s/instances-multi-alloc" % GANETI_RAPI_VERSION, + query, body) + def CreateInstance(self, mode, name, disk_template, disks, nics, **kwargs): """Creates a new instance. @@ -703,23 +775,9 @@ class GanetiRapiClient(object): # pylint: disable=R0904 _AppendDryRunIf(query, kwargs.get("dry_run")) if _INST_CREATE_REQV1 in self.GetFeatures(): - # All required fields for request data version 1 - body = { - _REQ_DATA_VERSION_FIELD: 1, - "mode": mode, - "name": name, - "disk_template": disk_template, - "disks": disks, - "nics": nics, - } - - conflicts = set(kwargs.iterkeys()) & set(body.iterkeys()) - if conflicts: - raise GanetiApiError("Required fields can not be specified as" - " keywords: %s" % ", ".join(conflicts)) - - body.update((key, value) for key, value in kwargs.iteritems() - if key != "dry_run") + body = self.InstanceAllocation(mode, name, disk_template, disks, nics, + **kwargs) + body[_REQ_DATA_VERSION_FIELD] = 1 else: raise GanetiApiError("Server does not support new-style (version 1)" " instance creation requests") diff --git a/test/ganeti.rapi.client_unittest.py b/test/ganeti.rapi.client_unittest.py index ade61908cec54d78f52c4bc32d7e124e4373004e..98f117ab564a07ffdeca6a4849438f05e803363a 100755 --- a/test/ganeti.rapi.client_unittest.py +++ b/test/ganeti.rapi.client_unittest.py @@ -26,6 +26,7 @@ import unittest import warnings import pycurl +from ganeti import opcodes from ganeti import constants from ganeti import http from ganeti import serializer @@ -46,7 +47,6 @@ import testutils _KNOWN_UNUSED = set([ rlib2.R_root, rlib2.R_2, - rlib2.R_2_instances_multi_alloc, ]) # Global variable for collecting used handlers @@ -481,6 +481,21 @@ class GanetiRapiClientTests(testutils.GanetiTestCase): self.assertItems(["inst32"]) self.assertQuery("static", ["1"]) + def testInstancesMultiAlloc(self): + response = { + constants.JOB_IDS_KEY: ["23423"], + opcodes.OpInstanceMultiAlloc.ALLOCATABLE_KEY: ["foobar"], + opcodes.OpInstanceMultiAlloc.FAILED_KEY: ["foobar2"], + } + self.rapi.AddResponse(serializer.DumpJson(response)) + insts = [self.client.InstanceAllocation("create", "foobar", + "plain", [], []), + self.client.InstanceAllocation("create", "foobar2", + "drbd8", [{"size": 100}], [])] + resp = self.client.InstancesMultiAlloc(insts) + self.assertEqual(resp, response) + self.assertHandler(rlib2.R_2_instances_multi_alloc) + def testCreateInstanceOldVersion(self): # The old request format, version 0, is no longer supported self.rapi.AddResponse(None, code=404)