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):
86 self._mapper = connector.Mapper()
88 self._last_handler = None
90 def AddResponse(self, response):
91 self._responses.insert(0, response)
93 def PopResponse(self):
94 if len(self._responses) > 0:
95 return self._responses.pop()
99 def GetLastHandler(self):
100 return self._last_handler
102 def FetchResponse(self, path, method):
107 HandlerClass, items, args = self._mapper.getController(path)
108 self._last_handler = HandlerClass(items, args, None)
109 if not hasattr(self._last_handler, method.upper()):
111 response = "Method not implemented"
112 except http.HttpException, ex:
114 response = ex.message
117 response = self.PopResponse()
119 return code, response
122 class RapiMockTest(unittest.TestCase):
126 self.assertEqual((404, None), rapi.FetchResponse("/foo", "GET"))
127 self.assertEqual((501, "Method not implemented"),
128 rapi.FetchResponse("/version", "POST"))
129 rapi.AddResponse("2")
130 code, response = rapi.FetchResponse("/version", "GET")
131 self.assertEqual(200, code)
132 self.assertEqual("2", response)
133 self.failUnless(isinstance(rapi.GetLastHandler(), rlib2.R_version))
136 class GanetiRapiClientTests(testutils.GanetiTestCase):
138 testutils.GanetiTestCase.setUp(self)
140 self.rapi = RapiMock()
141 self.http = OpenerDirectorMock(self.rapi)
142 self.client = client.GanetiRapiClient('master.foo.com')
143 self.client._http = self.http
144 # Hard-code the version for easier testing.
145 self.client._version = 2
147 def assertHandler(self, handler_cls):
148 self.failUnless(isinstance(self.rapi.GetLastHandler(), handler_cls))
150 def assertQuery(self, key, value):
151 self.assertEqual(value, self.rapi.GetLastHandler().queryargs.get(key, None))
153 def assertItems(self, items):
154 self.assertEqual(items, self.rapi.GetLastHandler().items)
156 def assertBulk(self):
157 self.assertTrue(self.rapi.GetLastHandler().useBulk())
159 def assertDryRun(self):
160 self.assertTrue(self.rapi.GetLastHandler().dryRun())
162 def testGetVersion(self):
163 self.client._version = None
164 self.rapi.AddResponse("2")
165 self.assertEqual(2, self.client.GetVersion())
166 self.assertHandler(rlib2.R_version)
168 def testGetOperatingSystems(self):
169 self.rapi.AddResponse("[\"beos\"]")
170 self.assertEqual(["beos"], self.client.GetOperatingSystems())
171 self.assertHandler(rlib2.R_2_os)
173 def testGetClusterTags(self):
174 self.rapi.AddResponse("[\"tag\"]")
175 self.assertEqual(["tag"], self.client.GetClusterTags())
176 self.assertHandler(rlib2.R_2_tags)
178 def testAddClusterTags(self):
179 self.rapi.AddResponse("1234")
180 self.assertEqual(1234,
181 self.client.AddClusterTags(["awesome"], dry_run=True))
182 self.assertHandler(rlib2.R_2_tags)
184 self.assertQuery("tag", ["awesome"])
186 def testDeleteClusterTags(self):
187 self.rapi.AddResponse("5107")
188 self.assertEqual(5107, self.client.DeleteClusterTags(["awesome"],
190 self.assertHandler(rlib2.R_2_tags)
192 self.assertQuery("tag", ["awesome"])
194 def testGetInfo(self):
195 self.rapi.AddResponse("{}")
196 self.assertEqual({}, self.client.GetInfo())
197 self.assertHandler(rlib2.R_2_info)
199 def testGetInstances(self):
200 self.rapi.AddResponse("[]")
201 self.assertEqual([], self.client.GetInstances(bulk=True))
202 self.assertHandler(rlib2.R_2_instances)
205 def testGetInstanceInfo(self):
206 self.rapi.AddResponse("[]")
207 self.assertEqual([], self.client.GetInstanceInfo("instance"))
208 self.assertHandler(rlib2.R_2_instances_name)
209 self.assertItems(["instance"])
211 def testCreateInstance(self):
212 self.rapi.AddResponse("1234")
213 self.assertEqual(1234, self.client.CreateInstance(dry_run=True))
214 self.assertHandler(rlib2.R_2_instances)
217 def testDeleteInstance(self):
218 self.rapi.AddResponse("1234")
219 self.assertEqual(1234, self.client.DeleteInstance("instance", dry_run=True))
220 self.assertHandler(rlib2.R_2_instances_name)
221 self.assertItems(["instance"])
224 def testGetInstanceTags(self):
225 self.rapi.AddResponse("[]")
226 self.assertEqual([], self.client.GetInstanceTags("fooinstance"))
227 self.assertHandler(rlib2.R_2_instances_name_tags)
228 self.assertItems(["fooinstance"])
230 def testAddInstanceTags(self):
231 self.rapi.AddResponse("1234")
232 self.assertEqual(1234,
233 self.client.AddInstanceTags("fooinstance", ["awesome"], dry_run=True))
234 self.assertHandler(rlib2.R_2_instances_name_tags)
235 self.assertItems(["fooinstance"])
237 self.assertQuery("tag", ["awesome"])
239 def testDeleteInstanceTags(self):
240 self.rapi.AddResponse("25826")
241 self.assertEqual(25826, self.client.DeleteInstanceTags("foo", ["awesome"],
243 self.assertHandler(rlib2.R_2_instances_name_tags)
244 self.assertItems(["foo"])
246 self.assertQuery("tag", ["awesome"])
248 def testRebootInstance(self):
249 self.rapi.AddResponse("6146")
250 job_id = self.client.RebootInstance("i-bar", reboot_type="hard",
251 ignore_secondaries=True, dry_run=True)
252 self.assertEqual(6146, job_id)
253 self.assertHandler(rlib2.R_2_instances_name_reboot)
254 self.assertItems(["i-bar"])
256 self.assertQuery("type", ["hard"])
257 self.assertQuery("ignore_secondaries", ["True"])
259 def testShutdownInstance(self):
260 self.rapi.AddResponse("1487")
261 self.assertEqual(1487, self.client.ShutdownInstance("foo-instance",
263 self.assertHandler(rlib2.R_2_instances_name_shutdown)
264 self.assertItems(["foo-instance"])
267 def testStartupInstance(self):
268 self.rapi.AddResponse("27149")
269 self.assertEqual(27149, self.client.StartupInstance("bar-instance",
271 self.assertHandler(rlib2.R_2_instances_name_startup)
272 self.assertItems(["bar-instance"])
275 def testReinstallInstance(self):
276 self.rapi.AddResponse("19119")
277 self.assertEqual(19119, self.client.ReinstallInstance("baz-instance", "DOS",
279 self.assertHandler(rlib2.R_2_instances_name_reinstall)
280 self.assertItems(["baz-instance"])
281 self.assertQuery("os", ["DOS"])
282 self.assertQuery("nostartup", ["1"])
284 def testReplaceInstanceDisks(self):
285 self.rapi.AddResponse("999")
286 job_id = self.client.ReplaceInstanceDisks("instance-name",
287 ["hda", "hdc"], dry_run=True, iallocator="hail")
288 self.assertEqual(999, job_id)
289 self.assertHandler(rlib2.R_2_instances_name_replace_disks)
290 self.assertItems(["instance-name"])
291 self.assertQuery("disks", ["hda,hdc"])
292 self.assertQuery("mode", ["replace_auto"])
293 self.assertQuery("iallocator", ["hail"])
296 self.assertRaises(client.InvalidReplacementMode,
297 self.client.ReplaceInstanceDisks,
298 "instance_a", ["hda"], mode="invalid_mode")
299 self.assertRaises(client.GanetiApiError,
300 self.client.ReplaceInstanceDisks,
301 "instance-foo", ["hda"], mode="replace_on_secondary")
303 self.rapi.AddResponse("1000")
304 job_id = self.client.ReplaceInstanceDisks("instance-bar",
305 ["hda"], mode="replace_on_secondary", remote_node="foo-node",
307 self.assertEqual(1000, job_id)
308 self.assertItems(["instance-bar"])
309 self.assertQuery("disks", ["hda"])
310 self.assertQuery("remote_node", ["foo-node"])
313 def testGetJobs(self):
314 self.rapi.AddResponse('[ { "id": "123", "uri": "\\/2\\/jobs\\/123" },'
315 ' { "id": "124", "uri": "\\/2\\/jobs\\/124" } ]')
316 self.assertEqual([123, 124], self.client.GetJobs())
317 self.assertHandler(rlib2.R_2_jobs)
319 def testGetJobStatus(self):
320 self.rapi.AddResponse("{\"foo\": \"bar\"}")
321 self.assertEqual({"foo": "bar"}, self.client.GetJobStatus(1234))
322 self.assertHandler(rlib2.R_2_jobs_id)
323 self.assertItems(["1234"])
325 def testWaitForJobChange(self):
326 fields = ["id", "summary"]
328 "job_info": [123, "something"],
332 self.rapi.AddResponse(serializer.DumpJson(expected))
333 result = self.client.WaitForJobChange(123, fields, [], -1)
334 self.assertEqualValues(expected, result)
335 self.assertHandler(rlib2.R_2_jobs_id_wait)
336 self.assertItems(["123"])
338 def testCancelJob(self):
339 self.rapi.AddResponse("[true, \"Job 123 will be canceled\"]")
340 self.assertEqual([True, "Job 123 will be canceled"],
341 self.client.CancelJob(999, dry_run=True))
342 self.assertHandler(rlib2.R_2_jobs_id)
343 self.assertItems(["999"])
346 def testGetNodes(self):
347 self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
348 " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
349 self.assertEqual(["node1", "node2"], self.client.GetNodes())
350 self.assertHandler(rlib2.R_2_nodes)
352 self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
353 " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
354 self.assertEqual([{"id": "node1", "uri": "uri1"},
355 {"id": "node2", "uri": "uri2"}],
356 self.client.GetNodes(bulk=True))
357 self.assertHandler(rlib2.R_2_nodes)
360 def testGetNodeInfo(self):
361 self.rapi.AddResponse("{}")
362 self.assertEqual({}, self.client.GetNodeInfo("node-foo"))
363 self.assertHandler(rlib2.R_2_nodes_name)
364 self.assertItems(["node-foo"])
366 def testEvacuateNode(self):
367 self.rapi.AddResponse("9876")
368 job_id = self.client.EvacuateNode("node-1", remote_node="node-2")
369 self.assertEqual(9876, job_id)
370 self.assertHandler(rlib2.R_2_nodes_name_evacuate)
371 self.assertItems(["node-1"])
372 self.assertQuery("remote_node", ["node-2"])
374 self.rapi.AddResponse("8888")
375 job_id = self.client.EvacuateNode("node-3", iallocator="hail", dry_run=True)
376 self.assertEqual(8888, job_id)
377 self.assertItems(["node-3"])
378 self.assertQuery("iallocator", ["hail"])
381 self.assertRaises(client.GanetiApiError,
382 self.client.EvacuateNode,
383 "node-4", iallocator="hail", remote_node="node-5")
385 def testMigrateNode(self):
386 self.rapi.AddResponse("1111")
387 self.assertEqual(1111, self.client.MigrateNode("node-a", dry_run=True))
388 self.assertHandler(rlib2.R_2_nodes_name_migrate)
389 self.assertItems(["node-a"])
390 self.assertQuery("live", ["1"])
393 def testGetNodeRole(self):
394 self.rapi.AddResponse("\"master\"")
395 self.assertEqual("master", self.client.GetNodeRole("node-a"))
396 self.assertHandler(rlib2.R_2_nodes_name_role)
397 self.assertItems(["node-a"])
399 def testSetNodeRole(self):
400 self.rapi.AddResponse("789")
401 self.assertEqual(789,
402 self.client.SetNodeRole("node-foo", "master-candidate", force=True))
403 self.assertHandler(rlib2.R_2_nodes_name_role)
404 self.assertItems(["node-foo"])
405 self.assertQuery("force", ["True"])
406 self.assertEqual("\"master-candidate\"", self.http.last_request.data)
408 self.assertRaises(client.InvalidNodeRole,
409 self.client.SetNodeRole, "node-bar", "fake-role")
411 def testGetNodeStorageUnits(self):
412 self.rapi.AddResponse("42")
414 self.client.GetNodeStorageUnits("node-x", "lvm-pv", "fields"))
415 self.assertHandler(rlib2.R_2_nodes_name_storage)
416 self.assertItems(["node-x"])
417 self.assertQuery("storage_type", ["lvm-pv"])
418 self.assertQuery("output_fields", ["fields"])
420 self.assertRaises(client.InvalidStorageType,
421 self.client.GetNodeStorageUnits,
422 "node-y", "floppy-disk", "fields")
424 def testModifyNodeStorageUnits(self):
425 self.rapi.AddResponse("14")
427 self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda"))
428 self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
429 self.assertItems(["node-z"])
430 self.assertQuery("storage_type", ["lvm-pv"])
431 self.assertQuery("name", ["hda"])
433 self.assertRaises(client.InvalidStorageType,
434 self.client.ModifyNodeStorageUnits,
435 "node-n", "floppy-disk", "hdc")
437 def testRepairNodeStorageUnits(self):
438 self.rapi.AddResponse("99")
439 self.assertEqual(99, self.client.RepairNodeStorageUnits("node-z", "lvm-pv",
441 self.assertHandler(rlib2.R_2_nodes_name_storage_repair)
442 self.assertItems(["node-z"])
443 self.assertQuery("storage_type", ["lvm-pv"])
444 self.assertQuery("name", ["hda"])
446 self.assertRaises(client.InvalidStorageType,
447 self.client.RepairNodeStorageUnits,
448 "node-n", "floppy-disk", "hdc")
450 def testGetNodeTags(self):
451 self.rapi.AddResponse("[\"fry\", \"bender\"]")
452 self.assertEqual(["fry", "bender"], self.client.GetNodeTags("node-k"))
453 self.assertHandler(rlib2.R_2_nodes_name_tags)
454 self.assertItems(["node-k"])
456 def testAddNodeTags(self):
457 self.rapi.AddResponse("1234")
458 self.assertEqual(1234,
459 self.client.AddNodeTags("node-v", ["awesome"], dry_run=True))
460 self.assertHandler(rlib2.R_2_nodes_name_tags)
461 self.assertItems(["node-v"])
463 self.assertQuery("tag", ["awesome"])
465 def testDeleteNodeTags(self):
466 self.rapi.AddResponse("16861")
467 self.assertEqual(16861, self.client.DeleteNodeTags("node-w", ["awesome"],
469 self.assertHandler(rlib2.R_2_nodes_name_tags)
470 self.assertItems(["node-w"])
472 self.assertQuery("tag", ["awesome"])
475 if __name__ == '__main__':
476 testutils.GanetiTestProgram()