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 testGetOperatingSystems(self):
194 self.rapi.AddResponse("[\"beos\"]")
195 self.assertEqual(["beos"], self.client.GetOperatingSystems())
196 self.assertHandler(rlib2.R_2_os)
198 def testGetClusterTags(self):
199 self.rapi.AddResponse("[\"tag\"]")
200 self.assertEqual(["tag"], self.client.GetClusterTags())
201 self.assertHandler(rlib2.R_2_tags)
203 def testAddClusterTags(self):
204 self.rapi.AddResponse("1234")
205 self.assertEqual(1234,
206 self.client.AddClusterTags(["awesome"], dry_run=True))
207 self.assertHandler(rlib2.R_2_tags)
209 self.assertQuery("tag", ["awesome"])
211 def testDeleteClusterTags(self):
212 self.rapi.AddResponse("5107")
213 self.assertEqual(5107, self.client.DeleteClusterTags(["awesome"],
215 self.assertHandler(rlib2.R_2_tags)
217 self.assertQuery("tag", ["awesome"])
219 def testGetInfo(self):
220 self.rapi.AddResponse("{}")
221 self.assertEqual({}, self.client.GetInfo())
222 self.assertHandler(rlib2.R_2_info)
224 def testGetInstances(self):
225 self.rapi.AddResponse("[]")
226 self.assertEqual([], self.client.GetInstances(bulk=True))
227 self.assertHandler(rlib2.R_2_instances)
230 def testGetInstanceInfo(self):
231 self.rapi.AddResponse("[]")
232 self.assertEqual([], self.client.GetInstanceInfo("instance"))
233 self.assertHandler(rlib2.R_2_instances_name)
234 self.assertItems(["instance"])
236 def testCreateInstance(self):
237 self.rapi.AddResponse("1234")
238 self.assertEqual(1234, self.client.CreateInstance(dry_run=True))
239 self.assertHandler(rlib2.R_2_instances)
242 def testDeleteInstance(self):
243 self.rapi.AddResponse("1234")
244 self.assertEqual(1234, self.client.DeleteInstance("instance", dry_run=True))
245 self.assertHandler(rlib2.R_2_instances_name)
246 self.assertItems(["instance"])
249 def testGetInstanceTags(self):
250 self.rapi.AddResponse("[]")
251 self.assertEqual([], self.client.GetInstanceTags("fooinstance"))
252 self.assertHandler(rlib2.R_2_instances_name_tags)
253 self.assertItems(["fooinstance"])
255 def testAddInstanceTags(self):
256 self.rapi.AddResponse("1234")
257 self.assertEqual(1234,
258 self.client.AddInstanceTags("fooinstance", ["awesome"], dry_run=True))
259 self.assertHandler(rlib2.R_2_instances_name_tags)
260 self.assertItems(["fooinstance"])
262 self.assertQuery("tag", ["awesome"])
264 def testDeleteInstanceTags(self):
265 self.rapi.AddResponse("25826")
266 self.assertEqual(25826, self.client.DeleteInstanceTags("foo", ["awesome"],
268 self.assertHandler(rlib2.R_2_instances_name_tags)
269 self.assertItems(["foo"])
271 self.assertQuery("tag", ["awesome"])
273 def testRebootInstance(self):
274 self.rapi.AddResponse("6146")
275 job_id = self.client.RebootInstance("i-bar", reboot_type="hard",
276 ignore_secondaries=True, dry_run=True)
277 self.assertEqual(6146, job_id)
278 self.assertHandler(rlib2.R_2_instances_name_reboot)
279 self.assertItems(["i-bar"])
281 self.assertQuery("type", ["hard"])
282 self.assertQuery("ignore_secondaries", ["1"])
284 def testShutdownInstance(self):
285 self.rapi.AddResponse("1487")
286 self.assertEqual(1487, self.client.ShutdownInstance("foo-instance",
288 self.assertHandler(rlib2.R_2_instances_name_shutdown)
289 self.assertItems(["foo-instance"])
292 def testStartupInstance(self):
293 self.rapi.AddResponse("27149")
294 self.assertEqual(27149, self.client.StartupInstance("bar-instance",
296 self.assertHandler(rlib2.R_2_instances_name_startup)
297 self.assertItems(["bar-instance"])
300 def testReinstallInstance(self):
301 self.rapi.AddResponse("19119")
302 self.assertEqual(19119, self.client.ReinstallInstance("baz-instance", "DOS",
304 self.assertHandler(rlib2.R_2_instances_name_reinstall)
305 self.assertItems(["baz-instance"])
306 self.assertQuery("os", ["DOS"])
307 self.assertQuery("nostartup", ["1"])
309 def testReplaceInstanceDisks(self):
310 self.rapi.AddResponse("999")
311 job_id = self.client.ReplaceInstanceDisks("instance-name",
312 disks=[0, 1], dry_run=True, iallocator="hail")
313 self.assertEqual(999, job_id)
314 self.assertHandler(rlib2.R_2_instances_name_replace_disks)
315 self.assertItems(["instance-name"])
316 self.assertQuery("disks", ["0,1"])
317 self.assertQuery("mode", ["replace_auto"])
318 self.assertQuery("iallocator", ["hail"])
321 self.rapi.AddResponse("1000")
322 job_id = self.client.ReplaceInstanceDisks("instance-bar",
323 disks=[1], mode="replace_on_secondary", remote_node="foo-node",
325 self.assertEqual(1000, job_id)
326 self.assertItems(["instance-bar"])
327 self.assertQuery("disks", ["1"])
328 self.assertQuery("remote_node", ["foo-node"])
331 self.rapi.AddResponse("5175")
332 self.assertEqual(5175, self.client.ReplaceInstanceDisks("instance-moo"))
333 self.assertItems(["instance-moo"])
334 self.assertQuery("disks", None)
336 def testGetJobs(self):
337 self.rapi.AddResponse('[ { "id": "123", "uri": "\\/2\\/jobs\\/123" },'
338 ' { "id": "124", "uri": "\\/2\\/jobs\\/124" } ]')
339 self.assertEqual([123, 124], self.client.GetJobs())
340 self.assertHandler(rlib2.R_2_jobs)
342 def testGetJobStatus(self):
343 self.rapi.AddResponse("{\"foo\": \"bar\"}")
344 self.assertEqual({"foo": "bar"}, self.client.GetJobStatus(1234))
345 self.assertHandler(rlib2.R_2_jobs_id)
346 self.assertItems(["1234"])
348 def testWaitForJobChange(self):
349 fields = ["id", "summary"]
351 "job_info": [123, "something"],
355 self.rapi.AddResponse(serializer.DumpJson(expected))
356 result = self.client.WaitForJobChange(123, fields, [], -1)
357 self.assertEqualValues(expected, result)
358 self.assertHandler(rlib2.R_2_jobs_id_wait)
359 self.assertItems(["123"])
361 def testCancelJob(self):
362 self.rapi.AddResponse("[true, \"Job 123 will be canceled\"]")
363 self.assertEqual([True, "Job 123 will be canceled"],
364 self.client.CancelJob(999, dry_run=True))
365 self.assertHandler(rlib2.R_2_jobs_id)
366 self.assertItems(["999"])
369 def testGetNodes(self):
370 self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
371 " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
372 self.assertEqual(["node1", "node2"], self.client.GetNodes())
373 self.assertHandler(rlib2.R_2_nodes)
375 self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
376 " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
377 self.assertEqual([{"id": "node1", "uri": "uri1"},
378 {"id": "node2", "uri": "uri2"}],
379 self.client.GetNodes(bulk=True))
380 self.assertHandler(rlib2.R_2_nodes)
383 def testGetNodeInfo(self):
384 self.rapi.AddResponse("{}")
385 self.assertEqual({}, self.client.GetNodeInfo("node-foo"))
386 self.assertHandler(rlib2.R_2_nodes_name)
387 self.assertItems(["node-foo"])
389 def testEvacuateNode(self):
390 self.rapi.AddResponse("9876")
391 job_id = self.client.EvacuateNode("node-1", remote_node="node-2")
392 self.assertEqual(9876, job_id)
393 self.assertHandler(rlib2.R_2_nodes_name_evacuate)
394 self.assertItems(["node-1"])
395 self.assertQuery("remote_node", ["node-2"])
397 self.rapi.AddResponse("8888")
398 job_id = self.client.EvacuateNode("node-3", iallocator="hail", dry_run=True)
399 self.assertEqual(8888, job_id)
400 self.assertItems(["node-3"])
401 self.assertQuery("iallocator", ["hail"])
404 self.assertRaises(client.GanetiApiError,
405 self.client.EvacuateNode,
406 "node-4", iallocator="hail", remote_node="node-5")
408 def testMigrateNode(self):
409 self.rapi.AddResponse("1111")
410 self.assertEqual(1111, self.client.MigrateNode("node-a", dry_run=True))
411 self.assertHandler(rlib2.R_2_nodes_name_migrate)
412 self.assertItems(["node-a"])
413 self.assertQuery("live", ["1"])
416 def testGetNodeRole(self):
417 self.rapi.AddResponse("\"master\"")
418 self.assertEqual("master", self.client.GetNodeRole("node-a"))
419 self.assertHandler(rlib2.R_2_nodes_name_role)
420 self.assertItems(["node-a"])
422 def testSetNodeRole(self):
423 self.rapi.AddResponse("789")
424 self.assertEqual(789,
425 self.client.SetNodeRole("node-foo", "master-candidate", force=True))
426 self.assertHandler(rlib2.R_2_nodes_name_role)
427 self.assertItems(["node-foo"])
428 self.assertQuery("force", ["1"])
429 self.assertEqual("\"master-candidate\"", self.http.last_request.data)
431 self.assertRaises(client.InvalidNodeRole,
432 self.client.SetNodeRole, "node-bar", "fake-role")
434 def testGetNodeStorageUnits(self):
435 self.rapi.AddResponse("42")
437 self.client.GetNodeStorageUnits("node-x", "lvm-pv", "fields"))
438 self.assertHandler(rlib2.R_2_nodes_name_storage)
439 self.assertItems(["node-x"])
440 self.assertQuery("storage_type", ["lvm-pv"])
441 self.assertQuery("output_fields", ["fields"])
443 def testModifyNodeStorageUnits(self):
444 self.rapi.AddResponse("14")
446 self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda"))
447 self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
448 self.assertItems(["node-z"])
449 self.assertQuery("storage_type", ["lvm-pv"])
450 self.assertQuery("name", ["hda"])
451 self.assertQuery("allocatable", None)
453 for allocatable, query_allocatable in [(True, "1"), (False, "0")]:
454 self.rapi.AddResponse("7205")
455 job_id = self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda",
456 allocatable=allocatable)
457 self.assertEqual(7205, job_id)
458 self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
459 self.assertItems(["node-z"])
460 self.assertQuery("storage_type", ["lvm-pv"])
461 self.assertQuery("name", ["hda"])
462 self.assertQuery("allocatable", [query_allocatable])
464 def testRepairNodeStorageUnits(self):
465 self.rapi.AddResponse("99")
466 self.assertEqual(99, self.client.RepairNodeStorageUnits("node-z", "lvm-pv",
468 self.assertHandler(rlib2.R_2_nodes_name_storage_repair)
469 self.assertItems(["node-z"])
470 self.assertQuery("storage_type", ["lvm-pv"])
471 self.assertQuery("name", ["hda"])
473 def testGetNodeTags(self):
474 self.rapi.AddResponse("[\"fry\", \"bender\"]")
475 self.assertEqual(["fry", "bender"], self.client.GetNodeTags("node-k"))
476 self.assertHandler(rlib2.R_2_nodes_name_tags)
477 self.assertItems(["node-k"])
479 def testAddNodeTags(self):
480 self.rapi.AddResponse("1234")
481 self.assertEqual(1234,
482 self.client.AddNodeTags("node-v", ["awesome"], dry_run=True))
483 self.assertHandler(rlib2.R_2_nodes_name_tags)
484 self.assertItems(["node-v"])
486 self.assertQuery("tag", ["awesome"])
488 def testDeleteNodeTags(self):
489 self.rapi.AddResponse("16861")
490 self.assertEqual(16861, self.client.DeleteNodeTags("node-w", ["awesome"],
492 self.assertHandler(rlib2.R_2_nodes_name_tags)
493 self.assertItems(["node-w"])
495 self.assertQuery("tag", ["awesome"])
498 if __name__ == '__main__':
499 testutils.GanetiTestProgram()