diff --git a/lib/rapi/client.py b/lib/rapi/client.py index 97d89e003e76420c353ef9e0a642e8e615e5a0d9..dadb820d8d60de4004c4f58a47807325df353b79 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 8840a4b2ab14ebd2d649a18c52a4b764b69238b1..f06a5bf9336a05a2b5e50d323296b005b9115edf 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))