Commit 54dc4fdb authored by Michael Hanselmann's avatar Michael Hanselmann

Merge remote branch 'origin/devel-2.1'

* origin/devel-2.1:
  RAPI client: Implement old instance creation request format
  rlib2: Use constants for disk and NIC parameters

Conflicts:
	test/ganeti.rapi.client_unittest.py: Trivial
	test/ganeti.rapi.rlib2_unittest.py: Trivial
Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarIustin Pop <iustin@google.com>
parents 926feaf1 48436b97
......@@ -70,6 +70,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"])
# Older pycURL versions don't have all error constants
try:
......@@ -599,13 +606,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)
......
......@@ -528,22 +528,22 @@ def _ParseInstanceCreateRequestVersion1(data, dry_run):
# Size is mandatory
try:
size = i["size"]
size = i[constants.IDISK_SIZE]
except KeyError:
raise http.HttpBadRequest("Disk %d specification wrong: missing disk"
" size" % idx)
disk = {
"size": size,
constants.IDISK_SIZE: size,
}
# Optional disk access mode
try:
disk_access = i["mode"]
disk_access = i[constants.IDISK_MODE]
except KeyError:
pass
else:
disk["mode"] = disk_access
disk[constants.IDISK_MODE] = disk_access
disks.append(disk)
......@@ -558,7 +558,7 @@ def _ParseInstanceCreateRequestVersion1(data, dry_run):
nic = {}
for field in ["mode", "ip", "link", "bridge"]:
for field in constants.INIC_PARAMS:
try:
value = i[field]
except KeyError:
......
......@@ -94,6 +94,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
......@@ -128,6 +131,7 @@ class TestConstants(unittest.TestCase):
self.assertEqual(client.HTTP_APP_JSON, http.HTTP_APP_JSON)
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):
......@@ -389,6 +393,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())
......@@ -452,10 +460,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.rapi.GetLastRequestData())
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]))
......
......@@ -67,7 +67,9 @@ class TestParseInstanceCreateRequestVersion1(testutils.GanetiTestCase):
# Two NICs
[
{ "ip": "192.0.2.6", "mode": constants.NIC_MODE_ROUTED, },
{ "ip": "192.0.2.6", "mode": constants.NIC_MODE_ROUTED,
"mac": "01:23:45:67:68:9A",
},
{ "mode": constants.NIC_MODE_BRIDGED, "link": "n0", "bridge": "br1", },
],
......@@ -122,21 +124,16 @@ class TestParseInstanceCreateRequestVersion1(testutils.GanetiTestCase):
self.assertEqual(len(op.disks), len(disks))
self.assertEqual(len(op.nics), len(nics))
self.assert_(compat.all(opdisk.get("size") ==
disk.get("size") and
opdisk.get("mode") ==
disk.get("mode") and
"unknown" not in opdisk
for opdisk, disk in zip(op.disks,
disks)))
self.assert_(compat.all(opnic.get("size") ==
nic.get("size") and
opnic.get("mode") ==
nic.get("mode") and
"unknown" not in opnic and
"foobar" not in opnic
for opnic, nic in zip(op.nics, nics)))
for opdisk, disk in zip(op.disks, disks):
for key in constants.IDISK_PARAMS:
self.assertEqual(opdisk.get(key), disk.get(key))
self.assertFalse("unknown" in opdisk)
for opnic, nic in zip(op.nics, nics):
for key in constants.INIC_PARAMS:
self.assertEqual(opnic.get(key), nic.get(key))
self.assertFalse("unknown" in opnic)
self.assertFalse("foobar" in opnic)
if beparams is None:
self.assertEqualValues(op.beparams, {})
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment