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"""
27 BaseHttp = httplib2.Http
28 from ganeti.rapi import client
37 from ganeti import http
39 from ganeti.rapi import connector
40 from ganeti.rapi import rlib2
45 _URI_RE = re.compile(r"https://(?P<host>.*):(?P<port>\d+)(?P<path>/.*)")
48 def _GetPathFromUri(uri):
49 """Gets the path and query from a URI.
52 match = _URI_RE.match(uri)
54 return match.groupdict()["path"]
59 class HttpResponseMock(dict):
60 """Dumb mock of httplib2.Response.
64 def __init__(self, status):
66 self['status'] = status
69 class HttpMock(BaseHttp):
70 """Mock for httplib.Http.
74 def __init__(self, rapi):
76 self._last_request = None
78 last_request_url = property(lambda self: self._last_request[0])
79 last_request_method = property(lambda self: self._last_request[1])
80 last_request_body = property(lambda self: self._last_request[2])
82 def request(self, url, method, body, headers):
83 self._last_request = (url, method, body)
84 code, resp_body = self._rapi.FetchResponse(_GetPathFromUri(url), method)
85 return HttpResponseMock(code), resp_body
88 class RapiMock(object):
91 self._mapper = connector.Mapper()
93 self._last_handler = None
95 def AddResponse(self, response):
96 self._responses.insert(0, response)
98 def PopResponse(self):
99 if len(self._responses) > 0:
100 return self._responses.pop()
104 def GetLastHandler(self):
105 return self._last_handler
107 def FetchResponse(self, path, method):
112 HandlerClass, items, args = self._mapper.getController(path)
113 self._last_handler = HandlerClass(items, args, None)
114 if not hasattr(self._last_handler, method.upper()):
116 response = "Bad request"
117 except http.HttpException, ex:
119 response = ex.message
122 response = self.PopResponse()
124 return code, response
127 class RapiMockTest(unittest.TestCase):
132 self.assertEqual((404, None), rapi.FetchResponse("/foo", "GET"))
133 self.assertEqual((400, "Bad request"),
134 rapi.FetchResponse("/version", "POST"))
135 rapi.AddResponse("2")
136 code, response = rapi.FetchResponse("/version", "GET")
137 self.assertEqual(200, code)
138 self.assertEqual("2", response)
139 self.failUnless(isinstance(rapi.GetLastHandler(), rlib2.R_version))
142 class GanetiRapiClientTests(unittest.TestCase):
143 """Tests for remote API client.
148 # Monkey-patch a fake VerifyCertificate function
149 self._verify_certificate = client._VerifyCertificate
150 client._VerifyCertificate = lambda x, y, z: True
152 self.rapi = RapiMock()
153 self.http = HttpMock(self.rapi)
154 self.client = client.GanetiRapiClient('master.foo.com')
155 self.client._http = self.http
156 # Hard-code the version for easier testing.
157 self.client._version = 2
160 # Un-do the monkey-patch
161 client._VerifyCertificate = self._verify_certificate
163 def assertHandler(self, handler_cls):
164 self.failUnless(isinstance(self.rapi.GetLastHandler(), handler_cls))
166 def assertQuery(self, key, value):
167 self.assertEqual(value, self.rapi.GetLastHandler().queryargs.get(key, None))
169 def assertItems(self, items):
170 self.assertEqual(items, self.rapi.GetLastHandler().items)
172 def assertBulk(self):
173 self.assertTrue(self.rapi.GetLastHandler().useBulk())
175 def assertDryRun(self):
176 self.assertTrue(self.rapi.GetLastHandler().dryRun())
178 def testGetVersion(self):
179 self.client._version = None
180 self.rapi.AddResponse("2")
181 self.assertEqual(2, self.client.GetVersion())
182 self.assertHandler(rlib2.R_version)
184 def testGetOperatingSystems(self):
185 self.rapi.AddResponse("[\"beos\"]")
186 self.assertEqual(["beos"], self.client.GetOperatingSystems())
187 self.assertHandler(rlib2.R_2_os)
189 def testGetClusterTags(self):
190 self.rapi.AddResponse("[\"tag\"]")
191 self.assertEqual(["tag"], self.client.GetClusterTags())
192 self.assertHandler(rlib2.R_2_tags)
194 def testAddClusterTags(self):
195 self.rapi.AddResponse("1234")
196 self.assertEqual(1234,
197 self.client.AddClusterTags(["awesome"], dry_run=True))
198 self.assertHandler(rlib2.R_2_tags)
200 self.assertQuery("tag", ["awesome"])
202 def testDeleteClusterTags(self):
203 self.client.DeleteClusterTags(["awesome"], dry_run=True)
204 self.assertHandler(rlib2.R_2_tags)
206 self.assertQuery("tag", ["awesome"])
208 def testGetInfo(self):
209 self.rapi.AddResponse("{}")
210 self.assertEqual({}, self.client.GetInfo())
211 self.assertHandler(rlib2.R_2_info)
213 def testGetInstances(self):
214 self.rapi.AddResponse("[]")
215 self.assertEqual([], self.client.GetInstances(bulk=True))
216 self.assertHandler(rlib2.R_2_instances)
219 def testGetInstanceInfo(self):
220 self.rapi.AddResponse("[]")
221 self.assertEqual([], self.client.GetInstanceInfo("instance"))
222 self.assertHandler(rlib2.R_2_instances_name)
223 self.assertItems(["instance"])
225 def testCreateInstance(self):
226 self.rapi.AddResponse("1234")
227 self.assertEqual(1234, self.client.CreateInstance(dry_run=True))
228 self.assertHandler(rlib2.R_2_instances)
231 def testDeleteInstance(self):
232 self.rapi.AddResponse("1234")
233 self.assertEqual(1234, self.client.DeleteInstance("instance", dry_run=True))
234 self.assertHandler(rlib2.R_2_instances_name)
235 self.assertItems(["instance"])
238 def testGetInstanceTags(self):
239 self.rapi.AddResponse("[]")
240 self.assertEqual([], self.client.GetInstanceTags("fooinstance"))
241 self.assertHandler(rlib2.R_2_instances_name_tags)
242 self.assertItems(["fooinstance"])
244 def testAddInstanceTags(self):
245 self.rapi.AddResponse("1234")
246 self.assertEqual(1234,
247 self.client.AddInstanceTags("fooinstance", ["awesome"], dry_run=True))
248 self.assertHandler(rlib2.R_2_instances_name_tags)
249 self.assertItems(["fooinstance"])
251 self.assertQuery("tag", ["awesome"])
253 def testDeleteInstanceTags(self):
254 self.client.DeleteInstanceTags("foo", ["awesome"], dry_run=True)
255 self.assertHandler(rlib2.R_2_instances_name_tags)
256 self.assertItems(["foo"])
258 self.assertQuery("tag", ["awesome"])
260 def testRebootInstance(self):
261 self.client.RebootInstance("i-bar", reboot_type="hard",
262 ignore_secondaries=True, dry_run=True)
263 self.assertHandler(rlib2.R_2_instances_name_reboot)
264 self.assertItems(["i-bar"])
266 self.assertQuery("type", ["hard"])
267 self.assertQuery("ignore_secondaries", ["True"])
269 def testShutdownInstance(self):
270 self.client.ShutdownInstance("foo-instance", dry_run=True)
271 self.assertHandler(rlib2.R_2_instances_name_shutdown)
272 self.assertItems(["foo-instance"])
275 def testStartupInstance(self):
276 self.client.StartupInstance("bar-instance", dry_run=True)
277 self.assertHandler(rlib2.R_2_instances_name_startup)
278 self.assertItems(["bar-instance"])
281 def testReinstallInstance(self):
282 self.client.ReinstallInstance("baz-instance", "DOS", no_startup=True)
283 self.assertHandler(rlib2.R_2_instances_name_reinstall)
284 self.assertItems(["baz-instance"])
285 self.assertQuery("os", ["DOS"])
286 self.assertQuery("nostartup", ["1"])
288 def testReplaceInstanceDisks(self):
289 self.rapi.AddResponse("999")
290 job_id = self.client.ReplaceInstanceDisks("instance-name",
291 ["hda", "hdc"], dry_run=True)
292 self.assertEqual(999, job_id)
293 self.assertHandler(rlib2.R_2_instances_name_replace_disks)
294 self.assertItems(["instance-name"])
295 self.assertQuery("disks", ["hda,hdc"])
296 self.assertQuery("mode", ["replace_auto"])
297 self.assertQuery("iallocator", ["hail"])
300 self.assertRaises(client.InvalidReplacementMode,
301 self.client.ReplaceInstanceDisks,
302 "instance_a", ["hda"], mode="invalid_mode")
303 self.assertRaises(client.GanetiApiError,
304 self.client.ReplaceInstanceDisks,
305 "instance-foo", ["hda"], mode="replace_on_secondary")
307 self.rapi.AddResponse("1000")
308 job_id = self.client.ReplaceInstanceDisks("instance-bar",
309 ["hda"], mode="replace_on_secondary", remote_node="foo-node",
311 self.assertEqual(1000, job_id)
312 self.assertItems(["instance-bar"])
313 self.assertQuery("disks", ["hda"])
314 self.assertQuery("remote_node", ["foo-node"])
317 def testGetJobs(self):
318 self.rapi.AddResponse('[ { "id": "123", "uri": "\\/2\\/jobs\\/123" },'
319 ' { "id": "124", "uri": "\\/2\\/jobs\\/124" } ]')
320 self.assertEqual([123, 124], self.client.GetJobs())
321 self.assertHandler(rlib2.R_2_jobs)
323 def testGetJobStatus(self):
324 self.rapi.AddResponse("{\"foo\": \"bar\"}")
325 self.assertEqual({"foo": "bar"}, self.client.GetJobStatus(1234))
326 self.assertHandler(rlib2.R_2_jobs_id)
327 self.assertItems(["1234"])
329 def testDeleteJob(self):
330 self.client.DeleteJob(999, dry_run=True)
331 self.assertHandler(rlib2.R_2_jobs_id)
332 self.assertItems(["999"])
335 def testGetNodes(self):
336 self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
337 " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
338 self.assertEqual(["node1", "node2"], self.client.GetNodes())
339 self.assertHandler(rlib2.R_2_nodes)
341 self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
342 " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
343 self.assertEqual([{"id": "node1", "uri": "uri1"},
344 {"id": "node2", "uri": "uri2"}],
345 self.client.GetNodes(bulk=True))
346 self.assertHandler(rlib2.R_2_nodes)
349 def testGetNodeInfo(self):
350 self.rapi.AddResponse("{}")
351 self.assertEqual({}, self.client.GetNodeInfo("node-foo"))
352 self.assertHandler(rlib2.R_2_nodes_name)
353 self.assertItems(["node-foo"])
355 def testEvacuateNode(self):
356 self.rapi.AddResponse("9876")
357 job_id = self.client.EvacuateNode("node-1", remote_node="node-2")
358 self.assertEqual(9876, job_id)
359 self.assertHandler(rlib2.R_2_nodes_name_evacuate)
360 self.assertItems(["node-1"])
361 self.assertQuery("remote_node", ["node-2"])
363 self.rapi.AddResponse("8888")
364 job_id = self.client.EvacuateNode("node-3", iallocator="hail", dry_run=True)
365 self.assertEqual(8888, job_id)
366 self.assertItems(["node-3"])
367 self.assertQuery("iallocator", ["hail"])
370 self.assertRaises(client.GanetiApiError,
371 self.client.EvacuateNode,
372 "node-4", iallocator="hail", remote_node="node-5")
374 def testMigrateNode(self):
375 self.rapi.AddResponse("1111")
376 self.assertEqual(1111, self.client.MigrateNode("node-a", dry_run=True))
377 self.assertHandler(rlib2.R_2_nodes_name_migrate)
378 self.assertItems(["node-a"])
379 self.assertQuery("live", ["1"])
382 def testGetNodeRole(self):
383 self.rapi.AddResponse("\"master\"")
384 self.assertEqual("master", self.client.GetNodeRole("node-a"))
385 self.assertHandler(rlib2.R_2_nodes_name_role)
386 self.assertItems(["node-a"])
388 def testSetNodeRole(self):
389 self.rapi.AddResponse("789")
390 self.assertEqual(789,
391 self.client.SetNodeRole("node-foo", "master-candidate", force=True))
392 self.assertHandler(rlib2.R_2_nodes_name_role)
393 self.assertItems(["node-foo"])
394 self.assertQuery("force", ["True"])
395 self.assertEqual("master-candidate", self.http.last_request_body)
397 self.assertRaises(client.InvalidNodeRole,
398 self.client.SetNodeRole, "node-bar", "fake-role")
400 def testGetNodeStorageUnits(self):
401 self.rapi.AddResponse("42")
403 self.client.GetNodeStorageUnits("node-x", "lvm-pv", "fields"))
404 self.assertHandler(rlib2.R_2_nodes_name_storage)
405 self.assertItems(["node-x"])
406 self.assertQuery("storage_type", ["lvm-pv"])
407 self.assertQuery("output_fields", ["fields"])
409 self.assertRaises(client.InvalidStorageType,
410 self.client.GetNodeStorageUnits,
411 "node-y", "floppy-disk", "fields")
413 def testModifyNodeStorageUnits(self):
414 self.rapi.AddResponse("14")
416 self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda"))
417 self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
418 self.assertItems(["node-z"])
419 self.assertQuery("storage_type", ["lvm-pv"])
420 self.assertQuery("name", ["hda"])
422 self.assertRaises(client.InvalidStorageType,
423 self.client.ModifyNodeStorageUnits,
424 "node-n", "floppy-disk", "hdc")
426 def testGetNodeTags(self):
427 self.rapi.AddResponse("[\"fry\", \"bender\"]")
428 self.assertEqual(["fry", "bender"], self.client.GetNodeTags("node-k"))
429 self.assertHandler(rlib2.R_2_nodes_name_tags)
430 self.assertItems(["node-k"])
432 def testAddNodeTags(self):
433 self.rapi.AddResponse("1234")
434 self.assertEqual(1234,
435 self.client.AddNodeTags("node-v", ["awesome"], dry_run=True))
436 self.assertHandler(rlib2.R_2_nodes_name_tags)
437 self.assertItems(["node-v"])
439 self.assertQuery("tag", ["awesome"])
441 def testDeleteNodeTags(self):
442 self.client.DeleteNodeTags("node-w", ["awesome"], dry_run=True)
443 self.assertHandler(rlib2.R_2_nodes_name_tags)
444 self.assertItems(["node-w"])
446 self.assertQuery("tag", ["awesome"])
449 if __name__ == '__main__':
451 warnings.warn("These tests require the httplib2 library")
453 testutils.GanetiTestProgram()