Adding RAPI client for instance multi allocation
authorRené Nussbaumer <rn@google.com>
Wed, 26 Sep 2012 12:47:41 +0000 (14:47 +0200)
committerRené Nussbaumer <rn@google.com>
Thu, 27 Sep 2012 11:42:31 +0000 (13:42 +0200)
It was very easy to refactor CreateInstance to repurpose some part of it
for the instance multi allocation request. So we do this.

Signed-off-by: René Nussbaumer <rn@google.com>
Reviewed-by: Michael Hanselmann <hansmi@google.com>

lib/rapi/client.py
test/ganeti.rapi.client_unittest.py

index 6ccf1fb..da552fd 100644 (file)
@@ -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")
index ade6190..98f117a 100755 (executable)
@@ -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)