Retry connection in import-export daemon
[ganeti-local] / test / ganeti.rapi.client_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2010 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 """Script for unittesting the RAPI client module"""
23
24
25 try:
26   import httplib2
27   BaseHttp = httplib2.Http
28   from ganeti.rapi import client
29 except ImportError:
30   httplib2 = None
31   BaseHttp = object
32
33 import re
34 import unittest
35 import warnings
36
37 from ganeti import http
38
39 from ganeti.rapi import connector
40 from ganeti.rapi import rlib2
41
42 import testutils
43
44
45 _URI_RE = re.compile(r"https://(?P<host>.*):(?P<port>\d+)(?P<path>/.*)")
46
47
48 def _GetPathFromUri(uri):
49   """Gets the path and query from a URI.
50
51   """
52   match = _URI_RE.match(uri)
53   if match:
54     return match.groupdict()["path"]
55   else:
56     return None
57
58
59 class HttpResponseMock(dict):
60   """Dumb mock of httplib2.Response.
61
62   """
63
64   def __init__(self, status):
65     self.status = status
66     self['status'] = status
67
68
69 class HttpMock(BaseHttp):
70   """Mock for httplib.Http.
71
72   """
73
74   def __init__(self, rapi):
75     self._rapi = rapi
76     self._last_request = None
77
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])
81
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
86
87
88 class RapiMock(object):
89
90   def __init__(self):
91     self._mapper = connector.Mapper()
92     self._responses = []
93     self._last_handler = None
94
95   def AddResponse(self, response):
96     self._responses.insert(0, response)
97
98   def PopResponse(self):
99     if len(self._responses) > 0:
100       return self._responses.pop()
101     else:
102       return None
103
104   def GetLastHandler(self):
105     return self._last_handler
106
107   def FetchResponse(self, path, method):
108     code = 200
109     response = None
110
111     try:
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()):
115         code = 400
116         response = "Bad request"
117     except http.HttpException, ex:
118       code = ex.code
119       response = ex.message
120
121     if not response:
122       response = self.PopResponse()
123
124     return code, response
125
126
127 class RapiMockTest(unittest.TestCase):
128
129   def test(self):
130     rapi = RapiMock()
131     path = "/version"
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))
140
141
142 class GanetiRapiClientTests(unittest.TestCase):
143   """Tests for remote API client.
144
145   """
146
147   def setUp(self):
148     # Monkey-patch a fake VerifyCertificate function
149     self._verify_certificate = client._VerifyCertificate
150     client._VerifyCertificate = lambda x, y, z: True
151
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
158
159   def tearDown(self):
160     # Un-do the monkey-patch
161     client._VerifyCertificate = self._verify_certificate
162
163   def assertHandler(self, handler_cls):
164     self.failUnless(isinstance(self.rapi.GetLastHandler(), handler_cls))
165
166   def assertQuery(self, key, value):
167     self.assertEqual(value, self.rapi.GetLastHandler().queryargs.get(key, None))
168
169   def assertItems(self, items):
170     self.assertEqual(items, self.rapi.GetLastHandler().items)
171
172   def assertBulk(self):
173     self.assertTrue(self.rapi.GetLastHandler().useBulk())
174
175   def assertDryRun(self):
176     self.assertTrue(self.rapi.GetLastHandler().dryRun())
177
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)
183
184   def testGetOperatingSystems(self):
185     self.rapi.AddResponse("[\"beos\"]")
186     self.assertEqual(["beos"], self.client.GetOperatingSystems())
187     self.assertHandler(rlib2.R_2_os)
188
189   def testGetClusterTags(self):
190     self.rapi.AddResponse("[\"tag\"]")
191     self.assertEqual(["tag"], self.client.GetClusterTags())
192     self.assertHandler(rlib2.R_2_tags)
193
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)
199     self.assertDryRun()
200     self.assertQuery("tag", ["awesome"])
201
202   def testDeleteClusterTags(self):
203     self.client.DeleteClusterTags(["awesome"], dry_run=True)
204     self.assertHandler(rlib2.R_2_tags)
205     self.assertDryRun()
206     self.assertQuery("tag", ["awesome"])
207
208   def testGetInfo(self):
209     self.rapi.AddResponse("{}")
210     self.assertEqual({}, self.client.GetInfo())
211     self.assertHandler(rlib2.R_2_info)
212
213   def testGetInstances(self):
214     self.rapi.AddResponse("[]")
215     self.assertEqual([], self.client.GetInstances(bulk=True))
216     self.assertHandler(rlib2.R_2_instances)
217     self.assertBulk()
218
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"])
224
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)
229     self.assertDryRun()
230
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"])
236     self.assertDryRun()
237
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"])
243
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"])
250     self.assertDryRun()
251     self.assertQuery("tag", ["awesome"])
252
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"])
257     self.assertDryRun()
258     self.assertQuery("tag", ["awesome"])
259
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"])
265     self.assertDryRun()
266     self.assertQuery("type", ["hard"])
267     self.assertQuery("ignore_secondaries", ["True"])
268
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"])
273     self.assertDryRun()
274
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"])
279     self.assertDryRun()
280
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"])
287
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"])
298     self.assertDryRun()
299
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")
306
307     self.rapi.AddResponse("1000")
308     job_id = self.client.ReplaceInstanceDisks("instance-bar",
309         ["hda"], mode="replace_on_secondary", remote_node="foo-node",
310         dry_run=True)
311     self.assertEqual(1000, job_id)
312     self.assertItems(["instance-bar"])
313     self.assertQuery("disks", ["hda"])
314     self.assertQuery("remote_node", ["foo-node"])
315     self.assertDryRun()
316
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)
322
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"])
328
329   def testDeleteJob(self):
330     self.client.DeleteJob(999, dry_run=True)
331     self.assertHandler(rlib2.R_2_jobs_id)
332     self.assertItems(["999"])
333     self.assertDryRun()
334
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)
340
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)
347     self.assertBulk()
348
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"])
354
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"])
362
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"])
368     self.assertDryRun()
369
370     self.assertRaises(client.GanetiApiError,
371                       self.client.EvacuateNode,
372                       "node-4", iallocator="hail", remote_node="node-5")
373
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"])
380     self.assertDryRun()
381
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"])
387
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)
396
397     self.assertRaises(client.InvalidNodeRole,
398                       self.client.SetNodeRole, "node-bar", "fake-role")
399
400   def testGetNodeStorageUnits(self):
401     self.rapi.AddResponse("42")
402     self.assertEqual(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"])
408
409     self.assertRaises(client.InvalidStorageType,
410                       self.client.GetNodeStorageUnits,
411                       "node-y", "floppy-disk", "fields")
412
413   def testModifyNodeStorageUnits(self):
414     self.rapi.AddResponse("14")
415     self.assertEqual(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"])
421
422     self.assertRaises(client.InvalidStorageType,
423                       self.client.ModifyNodeStorageUnits,
424                       "node-n", "floppy-disk", "hdc")
425
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"])
431
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"])
438     self.assertDryRun()
439     self.assertQuery("tag", ["awesome"])
440
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"])
445     self.assertDryRun()
446     self.assertQuery("tag", ["awesome"])
447
448
449 if __name__ == '__main__':
450   if httplib2 is None:
451     warnings.warn("These tests require the httplib2 library")
452   else:
453     testutils.GanetiTestProgram()