From 48436b9747f85c9799190e6181696fbdde48c4f0 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Tue, 13 Jul 2010 19:45:44 +0200 Subject: [PATCH] RAPI client: Implement old instance creation request format Commit 8a47b4478 implemented instance creation in the RAPI client, but it left out support for the old instance creation request format. This patch now implements the old format as good as possible. This will only be used when talking to clusters before Ganeti 2.1.3. Signed-off-by: Michael Hanselmann <hansmi@google.com> Reviewed-by: Iustin Pop <iustin@google.com> --- lib/rapi/client.py | 86 ++++++++++++++++++++-- test/ganeti.rapi.client_unittest.py | 106 ++++++++++++++++++++++++++-- 2 files changed, 183 insertions(+), 9 deletions(-) diff --git a/lib/rapi/client.py b/lib/rapi/client.py index e2d7be51a..5088172aa 100644 --- a/lib/rapi/client.py +++ b/lib/rapi/client.py @@ -60,6 +60,13 @@ NODE_ROLE_REGULAR = "regular" # Internal constants _REQ_DATA_VERSION_FIELD = "__version__" _INST_CREATE_REQV1 = "instance-create-reqv1" +_INST_NIC_PARAMS = frozenset(["mac", "ip", "mode", "link", "bridge"]) +_INST_CREATE_V0_DISK_PARAMS = frozenset(["size"]) +_INST_CREATE_V0_PARAMS = frozenset([ + "os", "pnode", "snode", "iallocator", "start", "ip_check", "name_check", + "hypervisor", "file_storage_dir", "file_driver", "dry_run", + ]) +_INST_CREATE_V0_DPARAMS = frozenset(["beparams", "hvparams"]) class Error(Exception): @@ -676,13 +683,82 @@ class GanetiRapiClient(object): 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 + # Old request format (version 0) + + # The following code must make sure that an exception is raised when an + # unsupported setting is requested by the caller. Otherwise this can lead + # to bugs difficult to find. 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") + + # Validate disks + for idx, disk in enumerate(disks): + unsupported = set(disk.keys()) - _INST_CREATE_V0_DISK_PARAMS + if unsupported: + raise GanetiApiError("Server supports request version 0 only, but" + " disk %s specifies the unsupported parameters" + " %s, allowed are %s" % + (idx, unsupported, + list(_INST_CREATE_V0_DISK_PARAMS))) + + assert (len(_INST_CREATE_V0_DISK_PARAMS) == 1 and + "size" in _INST_CREATE_V0_DISK_PARAMS) + disk_sizes = [disk["size"] for disk in disks] + + # Validate NICs + if not nics: + raise GanetiApiError("Server supports request version 0 only, but" + " no NIC specified") + elif len(nics) > 1: + raise GanetiApiError("Server supports request version 0 only, but" + " more than one NIC specified") + + assert len(nics) == 1 + + unsupported = set(nics[0].keys()) - _INST_NIC_PARAMS + if unsupported: + raise GanetiApiError("Server supports request version 0 only, but" + " NIC 0 specifies the unsupported parameters %s," + " allowed are %s" % + (unsupported, list(_INST_NIC_PARAMS))) + + # Validate other parameters + unsupported = (set(kwargs.keys()) - _INST_CREATE_V0_PARAMS - + _INST_CREATE_V0_DPARAMS) + if unsupported: + allowed = _INST_CREATE_V0_PARAMS.union(_INST_CREATE_V0_DPARAMS) + raise GanetiApiError("Server supports request version 0 only, but" + " the following unsupported parameters are" + " specified: %s, allowed are %s" % + (unsupported, list(allowed))) + + # All required fields for request data version 0 + body = { + _REQ_DATA_VERSION_FIELD: 0, + "name": name, + "disk_template": disk_template, + "disks": disk_sizes, + } + + # NIC fields + assert len(nics) == 1 + assert not (set(body.keys()) & set(nics[0].keys())) + body.update(nics[0]) + + # Copy supported fields + assert not (set(body.keys()) & set(kwargs.keys())) + body.update(dict((key, value) for key, value in kwargs.items() + if key in _INST_CREATE_V0_PARAMS)) + + # Merge dictionaries + for i in (value for key, value in kwargs.items() + if key in _INST_CREATE_V0_DPARAMS): + assert not (set(body.keys()) & set(i.keys())) + body.update(i) + + assert not (set(kwargs.keys()) - + (_INST_CREATE_V0_PARAMS | _INST_CREATE_V0_DPARAMS)) + assert not (set(body.keys()) & _INST_CREATE_V0_DPARAMS) return self._SendRequest(HTTP_POST, "/%s/instances" % GANETI_RAPI_VERSION, query, body) diff --git a/test/ganeti.rapi.client_unittest.py b/test/ganeti.rapi.client_unittest.py index 54fd5ff23..851ec138f 100755 --- a/test/ganeti.rapi.client_unittest.py +++ b/test/ganeti.rapi.client_unittest.py @@ -26,6 +26,7 @@ import re import unittest import warnings +from ganeti import constants from ganeti import http from ganeti import serializer @@ -89,6 +90,9 @@ class RapiMock(object): def AddResponse(self, response, code=200): self._responses.insert(0, (code, response)) + def CountPending(self): + return len(self._responses) + def GetLastHandler(self): return self._last_handler @@ -111,6 +115,15 @@ class RapiMock(object): return code, response +class TestConstants(unittest.TestCase): + def test(self): + self.assertEqual(client.GANETI_RAPI_PORT, constants.DEFAULT_RAPI_PORT) + self.assertEqual(client.GANETI_RAPI_VERSION, constants.RAPI_VERSION) + self.assertEqual(client._REQ_DATA_VERSION_FIELD, rlib2._REQ_DATA_VERSION) + self.assertEqual(client._INST_CREATE_REQV1, rlib2._INST_CREATE_REQV1) + self.assertEqual(client._INST_NIC_PARAMS, constants.INIC_PARAMS) + + class RapiMockTest(unittest.TestCase): def test(self): rapi = RapiMock() @@ -196,6 +209,10 @@ class GanetiRapiClientTests(testutils.GanetiTestCase): self.assertEqual(features, self.client.GetFeatures()) self.assertHandler(rlib2.R_2_features) + def testGetFeaturesNotFound(self): + self.rapi.AddResponse(None, code=404) + self.assertEqual([], self.client.GetFeatures()) + def testGetOperatingSystems(self): self.rapi.AddResponse("[\"beos\"]") self.assertEqual(["beos"], self.client.GetOperatingSystems()) @@ -259,10 +276,91 @@ class GanetiRapiClientTests(testutils.GanetiTestCase): self.assertQuery("static", ["1"]) def testCreateInstanceOldVersion(self): - self.rapi.AddResponse(serializer.DumpJson([])) - self.assertRaises(NotImplementedError, self.client.CreateInstance, - "create", "inst1.example.com", "plain", [], [], - dry_run=True) + # No NICs + self.rapi.AddResponse(None, code=404) + self.assertRaises(client.GanetiApiError, self.client.CreateInstance, + "create", "inst1.example.com", "plain", [], []) + self.assertEqual(self.rapi.CountPending(), 0) + + # More than one NIC + self.rapi.AddResponse(None, code=404) + self.assertRaises(client.GanetiApiError, self.client.CreateInstance, + "create", "inst1.example.com", "plain", [], + [{}, {}, {}]) + self.assertEqual(self.rapi.CountPending(), 0) + + # Unsupported NIC fields + self.rapi.AddResponse(None, code=404) + self.assertRaises(client.GanetiApiError, self.client.CreateInstance, + "create", "inst1.example.com", "plain", [], + [{"x": True, "y": False}]) + self.assertEqual(self.rapi.CountPending(), 0) + + # Unsupported disk fields + self.rapi.AddResponse(None, code=404) + self.assertRaises(client.GanetiApiError, self.client.CreateInstance, + "create", "inst1.example.com", "plain", + [{}, {"moo": "foo",}], [{}]) + self.assertEqual(self.rapi.CountPending(), 0) + + # Unsupported fields + self.rapi.AddResponse(None, code=404) + self.assertRaises(client.GanetiApiError, self.client.CreateInstance, + "create", "inst1.example.com", "plain", [], [{}], + hello_world=123) + self.assertEqual(self.rapi.CountPending(), 0) + + self.rapi.AddResponse(None, code=404) + self.assertRaises(client.GanetiApiError, self.client.CreateInstance, + "create", "inst1.example.com", "plain", [], [{}], + memory=128) + self.assertEqual(self.rapi.CountPending(), 0) + + # Normal creation + testnics = [ + [{}], + [{ "mac": constants.VALUE_AUTO, }], + [{ "ip": "192.0.2.99", "mode": constants.NIC_MODE_ROUTED, }], + ] + + testdisks = [ + [], + [{ "size": 128, }], + [{ "size": 321, }, { "size": 4096, }], + ] + + for idx, nics in enumerate(testnics): + for disks in testdisks: + beparams = { + constants.BE_MEMORY: 512, + constants.BE_AUTO_BALANCE: False, + } + hvparams = { + constants.HV_MIGRATION_PORT: 9876, + constants.HV_VNC_TLS: True, + } + + self.rapi.AddResponse(None, code=404) + self.rapi.AddResponse(serializer.DumpJson(3122617 + idx)) + job_id = self.client.CreateInstance("create", "inst1.example.com", + "plain", disks, nics, + pnode="node99", dry_run=True, + hvparams=hvparams, + beparams=beparams) + self.assertEqual(job_id, 3122617 + idx) + self.assertHandler(rlib2.R_2_instances) + self.assertDryRun() + self.assertEqual(self.rapi.CountPending(), 0) + + data = serializer.LoadJson(self.http.last_request.data) + self.assertEqual(data["name"], "inst1.example.com") + self.assertEqual(data["disk_template"], "plain") + self.assertEqual(data["pnode"], "node99") + self.assertEqual(data[constants.BE_MEMORY], 512) + self.assertEqual(data[constants.BE_AUTO_BALANCE], False) + self.assertEqual(data[constants.HV_MIGRATION_PORT], 9876) + self.assertEqual(data[constants.HV_VNC_TLS], True) + self.assertEqual(data["disks"], [disk["size"] for disk in disks]) def testCreateInstance(self): self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_CREATE_REQV1])) -- GitLab