4 # Copyright (C) 2010 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Script for unittesting the RAPI client module"""
29 from ganeti import http
30 from ganeti import serializer
32 from ganeti.rapi import connector
33 from ganeti.rapi import rlib2
34 from ganeti.rapi import client
39 _URI_RE = re.compile(r"https://(?P<host>.*):(?P<port>\d+)(?P<path>/.*)")
42 def _GetPathFromUri(uri):
43 """Gets the path and query from a URI.
46 match = _URI_RE.match(uri)
48 return match.groupdict()["path"]
53 class HttpResponseMock:
54 """Dumb mock of httplib.HTTPResponse.
58 def __init__(self, code, data):
66 class OpenerDirectorMock:
67 """Mock for urllib.OpenerDirector.
71 def __init__(self, rapi):
73 self.last_request = None
76 self.last_request = req
78 path = _GetPathFromUri(req.get_full_url())
79 code, resp_body = self._rapi.FetchResponse(path, req.get_method())
80 return HttpResponseMock(code, resp_body)
83 class RapiMock(object):
85 self._mapper = connector.Mapper()
87 self._last_handler = None
89 def AddResponse(self, response, code=200):
90 self._responses.insert(0, (code, response))
92 def GetLastHandler(self):
93 return self._last_handler
95 def FetchResponse(self, path, method):
97 HandlerClass, items, args = self._mapper.getController(path)
98 self._last_handler = HandlerClass(items, args, None)
99 if not hasattr(self._last_handler, method.upper()):
100 raise http.HttpNotImplemented(message="Method not implemented")
102 except http.HttpException, ex:
104 response = ex.message
106 if not self._responses:
107 raise Exception("No responses")
109 (code, response) = self._responses.pop()
111 return code, response
114 class RapiMockTest(unittest.TestCase):
118 self.assertEqual((404, None), rapi.FetchResponse("/foo", "GET"))
119 self.assertEqual((501, "Method not implemented"),
120 rapi.FetchResponse("/version", "POST"))
121 rapi.AddResponse("2")
122 code, response = rapi.FetchResponse("/version", "GET")
123 self.assertEqual(200, code)
124 self.assertEqual("2", response)
125 self.failUnless(isinstance(rapi.GetLastHandler(), rlib2.R_version))
128 class GanetiRapiClientTests(testutils.GanetiTestCase):
130 testutils.GanetiTestCase.setUp(self)
132 self.rapi = RapiMock()
133 self.http = OpenerDirectorMock(self.rapi)
134 self.client = client.GanetiRapiClient('master.foo.com')
135 self.client._http = self.http
136 # Hard-code the version for easier testing.
137 self.client._version = 2
139 def assertHandler(self, handler_cls):
140 self.failUnless(isinstance(self.rapi.GetLastHandler(), handler_cls))
142 def assertQuery(self, key, value):
143 self.assertEqual(value, self.rapi.GetLastHandler().queryargs.get(key, None))
145 def assertItems(self, items):
146 self.assertEqual(items, self.rapi.GetLastHandler().items)
148 def assertBulk(self):
149 self.assertTrue(self.rapi.GetLastHandler().useBulk())
151 def assertDryRun(self):
152 self.assertTrue(self.rapi.GetLastHandler().dryRun())
154 def testEncodeQuery(self):
171 self.assertEqualValues(self.client._EncodeQuery(query),
175 for i in [[1, 2, 3], {"moo": "boo"}, (1, 2, 3)]:
176 self.assertRaises(ValueError, self.client._EncodeQuery, [("x", i)])
178 def testHttpError(self):
179 self.rapi.AddResponse(None, code=404)
181 self.client.GetJobStatus(15140)
182 except client.GanetiApiError, err:
183 self.assertEqual(err.code, 404)
185 self.fail("Didn't raise exception")
187 def testGetVersion(self):
188 self.client._version = None
189 self.rapi.AddResponse("2")
190 self.assertEqual(2, self.client.GetVersion())
191 self.assertHandler(rlib2.R_version)
193 def testGetFeatures(self):
194 for features in [[], ["foo", "bar", "baz"]]:
195 self.rapi.AddResponse(serializer.DumpJson(features))
196 self.assertEqual(features, self.client.GetFeatures())
197 self.assertHandler(rlib2.R_2_features)
199 def testGetOperatingSystems(self):
200 self.rapi.AddResponse("[\"beos\"]")
201 self.assertEqual(["beos"], self.client.GetOperatingSystems())
202 self.assertHandler(rlib2.R_2_os)
204 def testGetClusterTags(self):
205 self.rapi.AddResponse("[\"tag\"]")
206 self.assertEqual(["tag"], self.client.GetClusterTags())
207 self.assertHandler(rlib2.R_2_tags)
209 def testAddClusterTags(self):
210 self.rapi.AddResponse("1234")
211 self.assertEqual(1234,
212 self.client.AddClusterTags(["awesome"], dry_run=True))
213 self.assertHandler(rlib2.R_2_tags)
215 self.assertQuery("tag", ["awesome"])
217 def testDeleteClusterTags(self):
218 self.rapi.AddResponse("5107")
219 self.assertEqual(5107, self.client.DeleteClusterTags(["awesome"],
221 self.assertHandler(rlib2.R_2_tags)
223 self.assertQuery("tag", ["awesome"])
225 def testGetInfo(self):
226 self.rapi.AddResponse("{}")
227 self.assertEqual({}, self.client.GetInfo())
228 self.assertHandler(rlib2.R_2_info)
230 def testGetInstances(self):
231 self.rapi.AddResponse("[]")
232 self.assertEqual([], self.client.GetInstances(bulk=True))
233 self.assertHandler(rlib2.R_2_instances)
236 def testGetInstance(self):
237 self.rapi.AddResponse("[]")
238 self.assertEqual([], self.client.GetInstance("instance"))
239 self.assertHandler(rlib2.R_2_instances_name)
240 self.assertItems(["instance"])
242 def testGetInstanceInfo(self):
243 self.rapi.AddResponse("21291")
244 self.assertEqual(21291, self.client.GetInstanceInfo("inst3"))
245 self.assertHandler(rlib2.R_2_instances_name_info)
246 self.assertItems(["inst3"])
247 self.assertQuery("static", None)
249 self.rapi.AddResponse("3428")
250 self.assertEqual(3428, self.client.GetInstanceInfo("inst31", static=False))
251 self.assertHandler(rlib2.R_2_instances_name_info)
252 self.assertItems(["inst31"])
253 self.assertQuery("static", ["0"])
255 self.rapi.AddResponse("15665")
256 self.assertEqual(15665, self.client.GetInstanceInfo("inst32", static=True))
257 self.assertHandler(rlib2.R_2_instances_name_info)
258 self.assertItems(["inst32"])
259 self.assertQuery("static", ["1"])
261 def testCreateInstanceOldVersion(self):
262 self.rapi.AddResponse(serializer.DumpJson([]))
263 self.assertRaises(NotImplementedError, self.client.CreateInstance,
264 "create", "inst1.example.com", "plain", [], [],
267 def testCreateInstance(self):
268 self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_CREATE_REQV1]))
269 self.rapi.AddResponse("23030")
270 job_id = self.client.CreateInstance("create", "inst1.example.com",
271 "plain", [], [], dry_run=True)
272 self.assertEqual(job_id, 23030)
273 self.assertHandler(rlib2.R_2_instances)
276 data = serializer.LoadJson(self.http.last_request.data)
278 for field in ["dry_run", "beparams", "hvparams", "start"]:
279 self.assertFalse(field in data)
281 self.assertEqual(data["name"], "inst1.example.com")
282 self.assertEqual(data["disk_template"], "plain")
284 def testCreateInstance2(self):
285 self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_CREATE_REQV1]))
286 self.rapi.AddResponse("24740")
287 job_id = self.client.CreateInstance("import", "inst2.example.com",
288 "drbd8", [{"size": 100,}],
289 [{}, {"bridge": "br1", }],
290 dry_run=False, start=True,
291 pnode="node1", snode="node9",
293 self.assertEqual(job_id, 24740)
294 self.assertHandler(rlib2.R_2_instances)
296 data = serializer.LoadJson(self.http.last_request.data)
297 self.assertEqual(data[rlib2._REQ_DATA_VERSION], 1)
298 self.assertEqual(data["name"], "inst2.example.com")
299 self.assertEqual(data["disk_template"], "drbd8")
300 self.assertEqual(data["start"], True)
301 self.assertEqual(data["ip_check"], False)
302 self.assertEqualValues(data["disks"], [{"size": 100,}])
303 self.assertEqualValues(data["nics"], [{}, {"bridge": "br1", }])
305 def testDeleteInstance(self):
306 self.rapi.AddResponse("1234")
307 self.assertEqual(1234, self.client.DeleteInstance("instance", dry_run=True))
308 self.assertHandler(rlib2.R_2_instances_name)
309 self.assertItems(["instance"])
312 def testGetInstanceTags(self):
313 self.rapi.AddResponse("[]")
314 self.assertEqual([], self.client.GetInstanceTags("fooinstance"))
315 self.assertHandler(rlib2.R_2_instances_name_tags)
316 self.assertItems(["fooinstance"])
318 def testAddInstanceTags(self):
319 self.rapi.AddResponse("1234")
320 self.assertEqual(1234,
321 self.client.AddInstanceTags("fooinstance", ["awesome"], dry_run=True))
322 self.assertHandler(rlib2.R_2_instances_name_tags)
323 self.assertItems(["fooinstance"])
325 self.assertQuery("tag", ["awesome"])
327 def testDeleteInstanceTags(self):
328 self.rapi.AddResponse("25826")
329 self.assertEqual(25826, self.client.DeleteInstanceTags("foo", ["awesome"],
331 self.assertHandler(rlib2.R_2_instances_name_tags)
332 self.assertItems(["foo"])
334 self.assertQuery("tag", ["awesome"])
336 def testRebootInstance(self):
337 self.rapi.AddResponse("6146")
338 job_id = self.client.RebootInstance("i-bar", reboot_type="hard",
339 ignore_secondaries=True, dry_run=True)
340 self.assertEqual(6146, job_id)
341 self.assertHandler(rlib2.R_2_instances_name_reboot)
342 self.assertItems(["i-bar"])
344 self.assertQuery("type", ["hard"])
345 self.assertQuery("ignore_secondaries", ["1"])
347 def testShutdownInstance(self):
348 self.rapi.AddResponse("1487")
349 self.assertEqual(1487, self.client.ShutdownInstance("foo-instance",
351 self.assertHandler(rlib2.R_2_instances_name_shutdown)
352 self.assertItems(["foo-instance"])
355 def testStartupInstance(self):
356 self.rapi.AddResponse("27149")
357 self.assertEqual(27149, self.client.StartupInstance("bar-instance",
359 self.assertHandler(rlib2.R_2_instances_name_startup)
360 self.assertItems(["bar-instance"])
363 def testReinstallInstance(self):
364 self.rapi.AddResponse("19119")
365 self.assertEqual(19119, self.client.ReinstallInstance("baz-instance", "DOS",
367 self.assertHandler(rlib2.R_2_instances_name_reinstall)
368 self.assertItems(["baz-instance"])
369 self.assertQuery("os", ["DOS"])
370 self.assertQuery("nostartup", ["1"])
372 def testReplaceInstanceDisks(self):
373 self.rapi.AddResponse("999")
374 job_id = self.client.ReplaceInstanceDisks("instance-name",
375 disks=[0, 1], dry_run=True, iallocator="hail")
376 self.assertEqual(999, job_id)
377 self.assertHandler(rlib2.R_2_instances_name_replace_disks)
378 self.assertItems(["instance-name"])
379 self.assertQuery("disks", ["0,1"])
380 self.assertQuery("mode", ["replace_auto"])
381 self.assertQuery("iallocator", ["hail"])
384 self.rapi.AddResponse("1000")
385 job_id = self.client.ReplaceInstanceDisks("instance-bar",
386 disks=[1], mode="replace_on_secondary", remote_node="foo-node",
388 self.assertEqual(1000, job_id)
389 self.assertItems(["instance-bar"])
390 self.assertQuery("disks", ["1"])
391 self.assertQuery("remote_node", ["foo-node"])
394 self.rapi.AddResponse("5175")
395 self.assertEqual(5175, self.client.ReplaceInstanceDisks("instance-moo"))
396 self.assertItems(["instance-moo"])
397 self.assertQuery("disks", None)
399 def testPrepareExport(self):
400 self.rapi.AddResponse("8326")
401 self.assertEqual(8326, self.client.PrepareExport("inst1", "local"))
402 self.assertHandler(rlib2.R_2_instances_name_prepare_export)
403 self.assertItems(["inst1"])
404 self.assertQuery("mode", ["local"])
406 def testExportInstance(self):
407 self.rapi.AddResponse("19695")
408 job_id = self.client.ExportInstance("inst2", "local", "nodeX",
410 self.assertEqual(job_id, 19695)
411 self.assertHandler(rlib2.R_2_instances_name_export)
412 self.assertItems(["inst2"])
414 data = serializer.LoadJson(self.http.last_request.data)
415 self.assertEqual(data["mode"], "local")
416 self.assertEqual(data["destination"], "nodeX")
417 self.assertEqual(data["shutdown"], True)
419 def testGetJobs(self):
420 self.rapi.AddResponse('[ { "id": "123", "uri": "\\/2\\/jobs\\/123" },'
421 ' { "id": "124", "uri": "\\/2\\/jobs\\/124" } ]')
422 self.assertEqual([123, 124], self.client.GetJobs())
423 self.assertHandler(rlib2.R_2_jobs)
425 def testGetJobStatus(self):
426 self.rapi.AddResponse("{\"foo\": \"bar\"}")
427 self.assertEqual({"foo": "bar"}, self.client.GetJobStatus(1234))
428 self.assertHandler(rlib2.R_2_jobs_id)
429 self.assertItems(["1234"])
431 def testWaitForJobChange(self):
432 fields = ["id", "summary"]
434 "job_info": [123, "something"],
438 self.rapi.AddResponse(serializer.DumpJson(expected))
439 result = self.client.WaitForJobChange(123, fields, [], -1)
440 self.assertEqualValues(expected, result)
441 self.assertHandler(rlib2.R_2_jobs_id_wait)
442 self.assertItems(["123"])
444 def testCancelJob(self):
445 self.rapi.AddResponse("[true, \"Job 123 will be canceled\"]")
446 self.assertEqual([True, "Job 123 will be canceled"],
447 self.client.CancelJob(999, dry_run=True))
448 self.assertHandler(rlib2.R_2_jobs_id)
449 self.assertItems(["999"])
452 def testGetNodes(self):
453 self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
454 " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
455 self.assertEqual(["node1", "node2"], self.client.GetNodes())
456 self.assertHandler(rlib2.R_2_nodes)
458 self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
459 " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
460 self.assertEqual([{"id": "node1", "uri": "uri1"},
461 {"id": "node2", "uri": "uri2"}],
462 self.client.GetNodes(bulk=True))
463 self.assertHandler(rlib2.R_2_nodes)
466 def testGetNode(self):
467 self.rapi.AddResponse("{}")
468 self.assertEqual({}, self.client.GetNode("node-foo"))
469 self.assertHandler(rlib2.R_2_nodes_name)
470 self.assertItems(["node-foo"])
472 def testEvacuateNode(self):
473 self.rapi.AddResponse("9876")
474 job_id = self.client.EvacuateNode("node-1", remote_node="node-2")
475 self.assertEqual(9876, job_id)
476 self.assertHandler(rlib2.R_2_nodes_name_evacuate)
477 self.assertItems(["node-1"])
478 self.assertQuery("remote_node", ["node-2"])
480 self.rapi.AddResponse("8888")
481 job_id = self.client.EvacuateNode("node-3", iallocator="hail", dry_run=True)
482 self.assertEqual(8888, job_id)
483 self.assertItems(["node-3"])
484 self.assertQuery("iallocator", ["hail"])
487 self.assertRaises(client.GanetiApiError,
488 self.client.EvacuateNode,
489 "node-4", iallocator="hail", remote_node="node-5")
491 def testMigrateNode(self):
492 self.rapi.AddResponse("1111")
493 self.assertEqual(1111, self.client.MigrateNode("node-a", dry_run=True))
494 self.assertHandler(rlib2.R_2_nodes_name_migrate)
495 self.assertItems(["node-a"])
496 self.assertQuery("live", ["1"])
499 def testGetNodeRole(self):
500 self.rapi.AddResponse("\"master\"")
501 self.assertEqual("master", self.client.GetNodeRole("node-a"))
502 self.assertHandler(rlib2.R_2_nodes_name_role)
503 self.assertItems(["node-a"])
505 def testSetNodeRole(self):
506 self.rapi.AddResponse("789")
507 self.assertEqual(789,
508 self.client.SetNodeRole("node-foo", "master-candidate", force=True))
509 self.assertHandler(rlib2.R_2_nodes_name_role)
510 self.assertItems(["node-foo"])
511 self.assertQuery("force", ["1"])
512 self.assertEqual("\"master-candidate\"", self.http.last_request.data)
514 def testGetNodeStorageUnits(self):
515 self.rapi.AddResponse("42")
517 self.client.GetNodeStorageUnits("node-x", "lvm-pv", "fields"))
518 self.assertHandler(rlib2.R_2_nodes_name_storage)
519 self.assertItems(["node-x"])
520 self.assertQuery("storage_type", ["lvm-pv"])
521 self.assertQuery("output_fields", ["fields"])
523 def testModifyNodeStorageUnits(self):
524 self.rapi.AddResponse("14")
526 self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda"))
527 self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
528 self.assertItems(["node-z"])
529 self.assertQuery("storage_type", ["lvm-pv"])
530 self.assertQuery("name", ["hda"])
531 self.assertQuery("allocatable", None)
533 for allocatable, query_allocatable in [(True, "1"), (False, "0")]:
534 self.rapi.AddResponse("7205")
535 job_id = self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda",
536 allocatable=allocatable)
537 self.assertEqual(7205, job_id)
538 self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
539 self.assertItems(["node-z"])
540 self.assertQuery("storage_type", ["lvm-pv"])
541 self.assertQuery("name", ["hda"])
542 self.assertQuery("allocatable", [query_allocatable])
544 def testRepairNodeStorageUnits(self):
545 self.rapi.AddResponse("99")
546 self.assertEqual(99, self.client.RepairNodeStorageUnits("node-z", "lvm-pv",
548 self.assertHandler(rlib2.R_2_nodes_name_storage_repair)
549 self.assertItems(["node-z"])
550 self.assertQuery("storage_type", ["lvm-pv"])
551 self.assertQuery("name", ["hda"])
553 def testGetNodeTags(self):
554 self.rapi.AddResponse("[\"fry\", \"bender\"]")
555 self.assertEqual(["fry", "bender"], self.client.GetNodeTags("node-k"))
556 self.assertHandler(rlib2.R_2_nodes_name_tags)
557 self.assertItems(["node-k"])
559 def testAddNodeTags(self):
560 self.rapi.AddResponse("1234")
561 self.assertEqual(1234,
562 self.client.AddNodeTags("node-v", ["awesome"], dry_run=True))
563 self.assertHandler(rlib2.R_2_nodes_name_tags)
564 self.assertItems(["node-v"])
566 self.assertQuery("tag", ["awesome"])
568 def testDeleteNodeTags(self):
569 self.rapi.AddResponse("16861")
570 self.assertEqual(16861, self.client.DeleteNodeTags("node-w", ["awesome"],
572 self.assertHandler(rlib2.R_2_nodes_name_tags)
573 self.assertItems(["node-w"])
575 self.assertQuery("tag", ["awesome"])
578 if __name__ == '__main__':
579 testutils.GanetiTestProgram()