From 8a47b4478ad658e748382137fa66bfd274929aae Mon Sep 17 00:00:00 2001
From: Michael Hanselmann <hansmi@google.com>
Date: Wed, 12 May 2010 19:56:20 +0200
Subject: [PATCH] RAPI client: Implement instance creation

Currently this only supports the new instance creation request data
format version 1, but support for the old version can be easily
implemented.

Most arguments are optional and documented in the RAPI documentation.

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Guido Trotter <ultrotter@google.com>
---
 lib/rapi/client.py                  | 55 ++++++++++++++++++++++++++---
 test/ganeti.rapi.client_unittest.py | 42 ++++++++++++++++++++--
 2 files changed, 90 insertions(+), 7 deletions(-)

diff --git a/lib/rapi/client.py b/lib/rapi/client.py
index 97d89e003..dadb820d8 100644
--- a/lib/rapi/client.py
+++ b/lib/rapi/client.py
@@ -56,6 +56,10 @@ NODE_ROLE_MASTER = "master"
 NODE_ROLE_OFFLINE = "offline"
 NODE_ROLE_REGULAR = "regular"
 
+# Internal constants
+_REQ_DATA_VERSION_FIELD = "__version__"
+_INST_CREATE_REQV1 = "instance-create-reqv1"
+
 
 class Error(Exception):
   """Base error class for this module.
@@ -555,23 +559,64 @@ class GanetiRapiClient(object):
                              ("/%s/instances/%s" %
                               (GANETI_RAPI_VERSION, instance)), None, None)
 
-  def CreateInstance(self, dry_run=False):
+  def CreateInstance(self, mode, name, disk_template, disks, nics,
+                     **kwargs):
     """Creates a new instance.
 
+    More details for parameters can be found in the RAPI documentation.
+
+    @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
     @type dry_run: bool
-    @param dry_run: whether to perform a dry run
+    @keyword dry_run: whether to perform a dry run
 
     @rtype: int
     @return: job id
 
     """
-    # TODO: Pass arguments needed to actually create an instance.
     query = []
-    if dry_run:
+
+    if kwargs.get("dry_run"):
       query.append(("dry-run", 1))
 
+    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")
+    else:
+      # TODO: Implement instance creation request data version 0
+      # When implementing version 0, care should be taken to refuse unknown
+      # parameters and invalid values. The interface of this function must stay
+      # exactly the same for version 0 and 1 (e.g. they aren't allowed to
+      # require different data types).
+      raise NotImplementedError("Support for instance creation request data"
+                                " version 0 is not yet implemented")
+
     return self._SendRequest(HTTP_POST, "/%s/instances" % GANETI_RAPI_VERSION,
-                             query, None)
+                             query, body)
 
   def DeleteInstance(self, instance, dry_run=False):
     """Deletes an instance.
diff --git a/test/ganeti.rapi.client_unittest.py b/test/ganeti.rapi.client_unittest.py
index 8840a4b2a..f06a5bf93 100755
--- a/test/ganeti.rapi.client_unittest.py
+++ b/test/ganeti.rapi.client_unittest.py
@@ -239,12 +239,50 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
     self.assertHandler(rlib2.R_2_instances_name)
     self.assertItems(["instance"])
 
+  def testCreateInstanceOldVersion(self):
+    self.rapi.AddResponse(serializer.DumpJson([]))
+    self.assertRaises(NotImplementedError, self.client.CreateInstance,
+                      "create", "inst1.example.com", "plain", [], [],
+                      dry_run=True)
+
   def testCreateInstance(self):
-    self.rapi.AddResponse("1234")
-    self.assertEqual(1234, self.client.CreateInstance(dry_run=True))
+    self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_CREATE_REQV1]))
+    self.rapi.AddResponse("23030")
+    job_id = self.client.CreateInstance("create", "inst1.example.com",
+                                        "plain", [], [], dry_run=True)
+    self.assertEqual(job_id, 23030)
     self.assertHandler(rlib2.R_2_instances)
     self.assertDryRun()
 
+    data = serializer.LoadJson(self.http.last_request.data)
+
+    for field in ["dry_run", "beparams", "hvparams", "start"]:
+      self.assertFalse(field in data)
+
+    self.assertEqual(data["name"], "inst1.example.com")
+    self.assertEqual(data["disk_template"], "plain")
+
+  def testCreateInstance2(self):
+    self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_CREATE_REQV1]))
+    self.rapi.AddResponse("24740")
+    job_id = self.client.CreateInstance("import", "inst2.example.com",
+                                        "drbd8", [{"size": 100,}],
+                                        [{}, {"bridge": "br1", }],
+                                        dry_run=False, start=True,
+                                        pnode="node1", snode="node9",
+                                        ip_check=False)
+    self.assertEqual(job_id, 24740)
+    self.assertHandler(rlib2.R_2_instances)
+
+    data = serializer.LoadJson(self.http.last_request.data)
+    self.assertEqual(data[rlib2._REQ_DATA_VERSION], 1)
+    self.assertEqual(data["name"], "inst2.example.com")
+    self.assertEqual(data["disk_template"], "drbd8")
+    self.assertEqual(data["start"], True)
+    self.assertEqual(data["ip_check"], False)
+    self.assertEqualValues(data["disks"], [{"size": 100,}])
+    self.assertEqualValues(data["nics"], [{}, {"bridge": "br1", }])
+
   def testDeleteInstance(self):
     self.rapi.AddResponse("1234")
     self.assertEqual(1234, self.client.DeleteInstance("instance", dry_run=True))
-- 
GitLab