Revision 48436b97
b/lib/rapi/client.py | ||
---|---|---|
60 | 60 |
# Internal constants |
61 | 61 |
_REQ_DATA_VERSION_FIELD = "__version__" |
62 | 62 |
_INST_CREATE_REQV1 = "instance-create-reqv1" |
63 |
_INST_NIC_PARAMS = frozenset(["mac", "ip", "mode", "link", "bridge"]) |
|
64 |
_INST_CREATE_V0_DISK_PARAMS = frozenset(["size"]) |
|
65 |
_INST_CREATE_V0_PARAMS = frozenset([ |
|
66 |
"os", "pnode", "snode", "iallocator", "start", "ip_check", "name_check", |
|
67 |
"hypervisor", "file_storage_dir", "file_driver", "dry_run", |
|
68 |
]) |
|
69 |
_INST_CREATE_V0_DPARAMS = frozenset(["beparams", "hvparams"]) |
|
63 | 70 |
|
64 | 71 |
|
65 | 72 |
class Error(Exception): |
... | ... | |
676 | 683 |
body.update((key, value) for key, value in kwargs.iteritems() |
677 | 684 |
if key != "dry_run") |
678 | 685 |
else: |
679 |
# TODO: Implement instance creation request data version 0 |
|
680 |
# When implementing version 0, care should be taken to refuse unknown |
|
681 |
# parameters and invalid values. The interface of this function must stay |
|
686 |
# Old request format (version 0) |
|
687 |
|
|
688 |
# The following code must make sure that an exception is raised when an |
|
689 |
# unsupported setting is requested by the caller. Otherwise this can lead |
|
690 |
# to bugs difficult to find. The interface of this function must stay |
|
682 | 691 |
# exactly the same for version 0 and 1 (e.g. they aren't allowed to |
683 | 692 |
# require different data types). |
684 |
raise NotImplementedError("Support for instance creation request data" |
|
685 |
" version 0 is not yet implemented") |
|
693 |
|
|
694 |
# Validate disks |
|
695 |
for idx, disk in enumerate(disks): |
|
696 |
unsupported = set(disk.keys()) - _INST_CREATE_V0_DISK_PARAMS |
|
697 |
if unsupported: |
|
698 |
raise GanetiApiError("Server supports request version 0 only, but" |
|
699 |
" disk %s specifies the unsupported parameters" |
|
700 |
" %s, allowed are %s" % |
|
701 |
(idx, unsupported, |
|
702 |
list(_INST_CREATE_V0_DISK_PARAMS))) |
|
703 |
|
|
704 |
assert (len(_INST_CREATE_V0_DISK_PARAMS) == 1 and |
|
705 |
"size" in _INST_CREATE_V0_DISK_PARAMS) |
|
706 |
disk_sizes = [disk["size"] for disk in disks] |
|
707 |
|
|
708 |
# Validate NICs |
|
709 |
if not nics: |
|
710 |
raise GanetiApiError("Server supports request version 0 only, but" |
|
711 |
" no NIC specified") |
|
712 |
elif len(nics) > 1: |
|
713 |
raise GanetiApiError("Server supports request version 0 only, but" |
|
714 |
" more than one NIC specified") |
|
715 |
|
|
716 |
assert len(nics) == 1 |
|
717 |
|
|
718 |
unsupported = set(nics[0].keys()) - _INST_NIC_PARAMS |
|
719 |
if unsupported: |
|
720 |
raise GanetiApiError("Server supports request version 0 only, but" |
|
721 |
" NIC 0 specifies the unsupported parameters %s," |
|
722 |
" allowed are %s" % |
|
723 |
(unsupported, list(_INST_NIC_PARAMS))) |
|
724 |
|
|
725 |
# Validate other parameters |
|
726 |
unsupported = (set(kwargs.keys()) - _INST_CREATE_V0_PARAMS - |
|
727 |
_INST_CREATE_V0_DPARAMS) |
|
728 |
if unsupported: |
|
729 |
allowed = _INST_CREATE_V0_PARAMS.union(_INST_CREATE_V0_DPARAMS) |
|
730 |
raise GanetiApiError("Server supports request version 0 only, but" |
|
731 |
" the following unsupported parameters are" |
|
732 |
" specified: %s, allowed are %s" % |
|
733 |
(unsupported, list(allowed))) |
|
734 |
|
|
735 |
# All required fields for request data version 0 |
|
736 |
body = { |
|
737 |
_REQ_DATA_VERSION_FIELD: 0, |
|
738 |
"name": name, |
|
739 |
"disk_template": disk_template, |
|
740 |
"disks": disk_sizes, |
|
741 |
} |
|
742 |
|
|
743 |
# NIC fields |
|
744 |
assert len(nics) == 1 |
|
745 |
assert not (set(body.keys()) & set(nics[0].keys())) |
|
746 |
body.update(nics[0]) |
|
747 |
|
|
748 |
# Copy supported fields |
|
749 |
assert not (set(body.keys()) & set(kwargs.keys())) |
|
750 |
body.update(dict((key, value) for key, value in kwargs.items() |
|
751 |
if key in _INST_CREATE_V0_PARAMS)) |
|
752 |
|
|
753 |
# Merge dictionaries |
|
754 |
for i in (value for key, value in kwargs.items() |
|
755 |
if key in _INST_CREATE_V0_DPARAMS): |
|
756 |
assert not (set(body.keys()) & set(i.keys())) |
|
757 |
body.update(i) |
|
758 |
|
|
759 |
assert not (set(kwargs.keys()) - |
|
760 |
(_INST_CREATE_V0_PARAMS | _INST_CREATE_V0_DPARAMS)) |
|
761 |
assert not (set(body.keys()) & _INST_CREATE_V0_DPARAMS) |
|
686 | 762 |
|
687 | 763 |
return self._SendRequest(HTTP_POST, "/%s/instances" % GANETI_RAPI_VERSION, |
688 | 764 |
query, body) |
b/test/ganeti.rapi.client_unittest.py | ||
---|---|---|
26 | 26 |
import unittest |
27 | 27 |
import warnings |
28 | 28 |
|
29 |
from ganeti import constants |
|
29 | 30 |
from ganeti import http |
30 | 31 |
from ganeti import serializer |
31 | 32 |
|
... | ... | |
89 | 90 |
def AddResponse(self, response, code=200): |
90 | 91 |
self._responses.insert(0, (code, response)) |
91 | 92 |
|
93 |
def CountPending(self): |
|
94 |
return len(self._responses) |
|
95 |
|
|
92 | 96 |
def GetLastHandler(self): |
93 | 97 |
return self._last_handler |
94 | 98 |
|
... | ... | |
111 | 115 |
return code, response |
112 | 116 |
|
113 | 117 |
|
118 |
class TestConstants(unittest.TestCase): |
|
119 |
def test(self): |
|
120 |
self.assertEqual(client.GANETI_RAPI_PORT, constants.DEFAULT_RAPI_PORT) |
|
121 |
self.assertEqual(client.GANETI_RAPI_VERSION, constants.RAPI_VERSION) |
|
122 |
self.assertEqual(client._REQ_DATA_VERSION_FIELD, rlib2._REQ_DATA_VERSION) |
|
123 |
self.assertEqual(client._INST_CREATE_REQV1, rlib2._INST_CREATE_REQV1) |
|
124 |
self.assertEqual(client._INST_NIC_PARAMS, constants.INIC_PARAMS) |
|
125 |
|
|
126 |
|
|
114 | 127 |
class RapiMockTest(unittest.TestCase): |
115 | 128 |
def test(self): |
116 | 129 |
rapi = RapiMock() |
... | ... | |
196 | 209 |
self.assertEqual(features, self.client.GetFeatures()) |
197 | 210 |
self.assertHandler(rlib2.R_2_features) |
198 | 211 |
|
212 |
def testGetFeaturesNotFound(self): |
|
213 |
self.rapi.AddResponse(None, code=404) |
|
214 |
self.assertEqual([], self.client.GetFeatures()) |
|
215 |
|
|
199 | 216 |
def testGetOperatingSystems(self): |
200 | 217 |
self.rapi.AddResponse("[\"beos\"]") |
201 | 218 |
self.assertEqual(["beos"], self.client.GetOperatingSystems()) |
... | ... | |
259 | 276 |
self.assertQuery("static", ["1"]) |
260 | 277 |
|
261 | 278 |
def testCreateInstanceOldVersion(self): |
262 |
self.rapi.AddResponse(serializer.DumpJson([])) |
|
263 |
self.assertRaises(NotImplementedError, self.client.CreateInstance, |
|
264 |
"create", "inst1.example.com", "plain", [], [], |
|
265 |
dry_run=True) |
|
279 |
# No NICs |
|
280 |
self.rapi.AddResponse(None, code=404) |
|
281 |
self.assertRaises(client.GanetiApiError, self.client.CreateInstance, |
|
282 |
"create", "inst1.example.com", "plain", [], []) |
|
283 |
self.assertEqual(self.rapi.CountPending(), 0) |
|
284 |
|
|
285 |
# More than one NIC |
|
286 |
self.rapi.AddResponse(None, code=404) |
|
287 |
self.assertRaises(client.GanetiApiError, self.client.CreateInstance, |
|
288 |
"create", "inst1.example.com", "plain", [], |
|
289 |
[{}, {}, {}]) |
|
290 |
self.assertEqual(self.rapi.CountPending(), 0) |
|
291 |
|
|
292 |
# Unsupported NIC fields |
|
293 |
self.rapi.AddResponse(None, code=404) |
|
294 |
self.assertRaises(client.GanetiApiError, self.client.CreateInstance, |
|
295 |
"create", "inst1.example.com", "plain", [], |
|
296 |
[{"x": True, "y": False}]) |
|
297 |
self.assertEqual(self.rapi.CountPending(), 0) |
|
298 |
|
|
299 |
# Unsupported disk fields |
|
300 |
self.rapi.AddResponse(None, code=404) |
|
301 |
self.assertRaises(client.GanetiApiError, self.client.CreateInstance, |
|
302 |
"create", "inst1.example.com", "plain", |
|
303 |
[{}, {"moo": "foo",}], [{}]) |
|
304 |
self.assertEqual(self.rapi.CountPending(), 0) |
|
305 |
|
|
306 |
# Unsupported fields |
|
307 |
self.rapi.AddResponse(None, code=404) |
|
308 |
self.assertRaises(client.GanetiApiError, self.client.CreateInstance, |
|
309 |
"create", "inst1.example.com", "plain", [], [{}], |
|
310 |
hello_world=123) |
|
311 |
self.assertEqual(self.rapi.CountPending(), 0) |
|
312 |
|
|
313 |
self.rapi.AddResponse(None, code=404) |
|
314 |
self.assertRaises(client.GanetiApiError, self.client.CreateInstance, |
|
315 |
"create", "inst1.example.com", "plain", [], [{}], |
|
316 |
memory=128) |
|
317 |
self.assertEqual(self.rapi.CountPending(), 0) |
|
318 |
|
|
319 |
# Normal creation |
|
320 |
testnics = [ |
|
321 |
[{}], |
|
322 |
[{ "mac": constants.VALUE_AUTO, }], |
|
323 |
[{ "ip": "192.0.2.99", "mode": constants.NIC_MODE_ROUTED, }], |
|
324 |
] |
|
325 |
|
|
326 |
testdisks = [ |
|
327 |
[], |
|
328 |
[{ "size": 128, }], |
|
329 |
[{ "size": 321, }, { "size": 4096, }], |
|
330 |
] |
|
331 |
|
|
332 |
for idx, nics in enumerate(testnics): |
|
333 |
for disks in testdisks: |
|
334 |
beparams = { |
|
335 |
constants.BE_MEMORY: 512, |
|
336 |
constants.BE_AUTO_BALANCE: False, |
|
337 |
} |
|
338 |
hvparams = { |
|
339 |
constants.HV_MIGRATION_PORT: 9876, |
|
340 |
constants.HV_VNC_TLS: True, |
|
341 |
} |
|
342 |
|
|
343 |
self.rapi.AddResponse(None, code=404) |
|
344 |
self.rapi.AddResponse(serializer.DumpJson(3122617 + idx)) |
|
345 |
job_id = self.client.CreateInstance("create", "inst1.example.com", |
|
346 |
"plain", disks, nics, |
|
347 |
pnode="node99", dry_run=True, |
|
348 |
hvparams=hvparams, |
|
349 |
beparams=beparams) |
|
350 |
self.assertEqual(job_id, 3122617 + idx) |
|
351 |
self.assertHandler(rlib2.R_2_instances) |
|
352 |
self.assertDryRun() |
|
353 |
self.assertEqual(self.rapi.CountPending(), 0) |
|
354 |
|
|
355 |
data = serializer.LoadJson(self.http.last_request.data) |
|
356 |
self.assertEqual(data["name"], "inst1.example.com") |
|
357 |
self.assertEqual(data["disk_template"], "plain") |
|
358 |
self.assertEqual(data["pnode"], "node99") |
|
359 |
self.assertEqual(data[constants.BE_MEMORY], 512) |
|
360 |
self.assertEqual(data[constants.BE_AUTO_BALANCE], False) |
|
361 |
self.assertEqual(data[constants.HV_MIGRATION_PORT], 9876) |
|
362 |
self.assertEqual(data[constants.HV_VNC_TLS], True) |
|
363 |
self.assertEqual(data["disks"], [disk["size"] for disk in disks]) |
|
266 | 364 |
|
267 | 365 |
def testCreateInstance(self): |
268 | 366 |
self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_CREATE_REQV1])) |
Also available in: Unified diff