Handle ESRCH when sending signals
[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 import re
26 import unittest
27 import warnings
28
29 from ganeti import http
30 from ganeti import serializer
31
32 from ganeti.rapi import connector
33 from ganeti.rapi import rlib2
34 from ganeti.rapi import client
35
36 import testutils
37
38
39 _URI_RE = re.compile(r"https://(?P<host>.*):(?P<port>\d+)(?P<path>/.*)")
40
41
42 def _GetPathFromUri(uri):
43   """Gets the path and query from a URI.
44
45   """
46   match = _URI_RE.match(uri)
47   if match:
48     return match.groupdict()["path"]
49   else:
50     return None
51
52
53 class HttpResponseMock:
54   """Dumb mock of httplib.HTTPResponse.
55
56   """
57
58   def __init__(self, code, data):
59     self.code = code
60     self._data = data
61
62   def read(self):
63     return self._data
64
65
66 class OpenerDirectorMock:
67   """Mock for urllib.OpenerDirector.
68
69   """
70
71   def __init__(self, rapi):
72     self._rapi = rapi
73     self.last_request = None
74
75   def open(self, req):
76     self.last_request = req
77
78     path = _GetPathFromUri(req.get_full_url())
79     code, resp_body = self._rapi.FetchResponse(path, req.get_method())
80     return HttpResponseMock(code, resp_body)
81
82
83 class RapiMock(object):
84   def __init__(self):
85     self._mapper = connector.Mapper()
86     self._responses = []
87     self._last_handler = None
88
89   def AddResponse(self, response, code=200):
90     self._responses.insert(0, (code, response))
91
92   def GetLastHandler(self):
93     return self._last_handler
94
95   def FetchResponse(self, path, method):
96     try:
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")
101
102     except http.HttpException, ex:
103       code = ex.code
104       response = ex.message
105     else:
106       if not self._responses:
107         raise Exception("No responses")
108
109       (code, response) = self._responses.pop()
110
111     return code, response
112
113
114 class RapiMockTest(unittest.TestCase):
115   def test(self):
116     rapi = RapiMock()
117     path = "/version"
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))
126
127
128 class GanetiRapiClientTests(testutils.GanetiTestCase):
129   def setUp(self):
130     testutils.GanetiTestCase.setUp(self)
131
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
138
139   def assertHandler(self, handler_cls):
140     self.failUnless(isinstance(self.rapi.GetLastHandler(), handler_cls))
141
142   def assertQuery(self, key, value):
143     self.assertEqual(value, self.rapi.GetLastHandler().queryargs.get(key, None))
144
145   def assertItems(self, items):
146     self.assertEqual(items, self.rapi.GetLastHandler().items)
147
148   def assertBulk(self):
149     self.assertTrue(self.rapi.GetLastHandler().useBulk())
150
151   def assertDryRun(self):
152     self.assertTrue(self.rapi.GetLastHandler().dryRun())
153
154   def testEncodeQuery(self):
155     query = [
156       ("a", None),
157       ("b", 1),
158       ("c", 2),
159       ("d", "Foo"),
160       ("e", True),
161       ]
162
163     expected = [
164       ("a", ""),
165       ("b", 1),
166       ("c", 2),
167       ("d", "Foo"),
168       ("e", 1),
169       ]
170
171     self.assertEqualValues(self.client._EncodeQuery(query),
172                            expected)
173
174     # invalid types
175     for i in [[1, 2, 3], {"moo": "boo"}, (1, 2, 3)]:
176       self.assertRaises(ValueError, self.client._EncodeQuery, [("x", i)])
177
178   def testHttpError(self):
179     self.rapi.AddResponse(None, code=404)
180     try:
181       self.client.GetJobStatus(15140)
182     except client.GanetiApiError, err:
183       self.assertEqual(err.code, 404)
184     else:
185       self.fail("Didn't raise exception")
186
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)
192
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)
198
199   def testGetOperatingSystems(self):
200     self.rapi.AddResponse("[\"beos\"]")
201     self.assertEqual(["beos"], self.client.GetOperatingSystems())
202     self.assertHandler(rlib2.R_2_os)
203
204   def testGetClusterTags(self):
205     self.rapi.AddResponse("[\"tag\"]")
206     self.assertEqual(["tag"], self.client.GetClusterTags())
207     self.assertHandler(rlib2.R_2_tags)
208
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)
214     self.assertDryRun()
215     self.assertQuery("tag", ["awesome"])
216
217   def testDeleteClusterTags(self):
218     self.rapi.AddResponse("5107")
219     self.assertEqual(5107, self.client.DeleteClusterTags(["awesome"],
220                                                          dry_run=True))
221     self.assertHandler(rlib2.R_2_tags)
222     self.assertDryRun()
223     self.assertQuery("tag", ["awesome"])
224
225   def testGetInfo(self):
226     self.rapi.AddResponse("{}")
227     self.assertEqual({}, self.client.GetInfo())
228     self.assertHandler(rlib2.R_2_info)
229
230   def testGetInstances(self):
231     self.rapi.AddResponse("[]")
232     self.assertEqual([], self.client.GetInstances(bulk=True))
233     self.assertHandler(rlib2.R_2_instances)
234     self.assertBulk()
235
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"])
241
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)
248
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"])
254
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"])
260
261   def testCreateInstanceOldVersion(self):
262     self.rapi.AddResponse(serializer.DumpJson([]))
263     self.assertRaises(NotImplementedError, self.client.CreateInstance,
264                       "create", "inst1.example.com", "plain", [], [],
265                       dry_run=True)
266
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)
274     self.assertDryRun()
275
276     data = serializer.LoadJson(self.http.last_request.data)
277
278     for field in ["dry_run", "beparams", "hvparams", "start"]:
279       self.assertFalse(field in data)
280
281     self.assertEqual(data["name"], "inst1.example.com")
282     self.assertEqual(data["disk_template"], "plain")
283
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",
292                                         ip_check=False)
293     self.assertEqual(job_id, 24740)
294     self.assertHandler(rlib2.R_2_instances)
295
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", }])
304
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"])
310     self.assertDryRun()
311
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"])
317
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"])
324     self.assertDryRun()
325     self.assertQuery("tag", ["awesome"])
326
327   def testDeleteInstanceTags(self):
328     self.rapi.AddResponse("25826")
329     self.assertEqual(25826, self.client.DeleteInstanceTags("foo", ["awesome"],
330                                                            dry_run=True))
331     self.assertHandler(rlib2.R_2_instances_name_tags)
332     self.assertItems(["foo"])
333     self.assertDryRun()
334     self.assertQuery("tag", ["awesome"])
335
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"])
343     self.assertDryRun()
344     self.assertQuery("type", ["hard"])
345     self.assertQuery("ignore_secondaries", ["1"])
346
347   def testShutdownInstance(self):
348     self.rapi.AddResponse("1487")
349     self.assertEqual(1487, self.client.ShutdownInstance("foo-instance",
350                                                         dry_run=True))
351     self.assertHandler(rlib2.R_2_instances_name_shutdown)
352     self.assertItems(["foo-instance"])
353     self.assertDryRun()
354
355   def testStartupInstance(self):
356     self.rapi.AddResponse("27149")
357     self.assertEqual(27149, self.client.StartupInstance("bar-instance",
358                                                         dry_run=True))
359     self.assertHandler(rlib2.R_2_instances_name_startup)
360     self.assertItems(["bar-instance"])
361     self.assertDryRun()
362
363   def testReinstallInstance(self):
364     self.rapi.AddResponse("19119")
365     self.assertEqual(19119, self.client.ReinstallInstance("baz-instance", "DOS",
366                                                           no_startup=True))
367     self.assertHandler(rlib2.R_2_instances_name_reinstall)
368     self.assertItems(["baz-instance"])
369     self.assertQuery("os", ["DOS"])
370     self.assertQuery("nostartup", ["1"])
371
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"])
382     self.assertDryRun()
383
384     self.rapi.AddResponse("1000")
385     job_id = self.client.ReplaceInstanceDisks("instance-bar",
386         disks=[1], mode="replace_on_secondary", remote_node="foo-node",
387         dry_run=True)
388     self.assertEqual(1000, job_id)
389     self.assertItems(["instance-bar"])
390     self.assertQuery("disks", ["1"])
391     self.assertQuery("remote_node", ["foo-node"])
392     self.assertDryRun()
393
394     self.rapi.AddResponse("5175")
395     self.assertEqual(5175, self.client.ReplaceInstanceDisks("instance-moo"))
396     self.assertItems(["instance-moo"])
397     self.assertQuery("disks", None)
398
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"])
405
406   def testExportInstance(self):
407     self.rapi.AddResponse("19695")
408     job_id = self.client.ExportInstance("inst2", "local", "nodeX",
409                                         shutdown=True)
410     self.assertEqual(job_id, 19695)
411     self.assertHandler(rlib2.R_2_instances_name_export)
412     self.assertItems(["inst2"])
413
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)
418
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)
424
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"])
430
431   def testWaitForJobChange(self):
432     fields = ["id", "summary"]
433     expected = {
434       "job_info": [123, "something"],
435       "log_entries": [],
436       }
437
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"])
443
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"])
450     self.assertDryRun()
451
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)
457
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)
464     self.assertBulk()
465
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"])
471
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"])
479
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"])
485     self.assertDryRun()
486
487     self.assertRaises(client.GanetiApiError,
488                       self.client.EvacuateNode,
489                       "node-4", iallocator="hail", remote_node="node-5")
490
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"])
497     self.assertDryRun()
498
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"])
504
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)
513
514   def testGetNodeStorageUnits(self):
515     self.rapi.AddResponse("42")
516     self.assertEqual(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"])
522
523   def testModifyNodeStorageUnits(self):
524     self.rapi.AddResponse("14")
525     self.assertEqual(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)
532
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])
543
544   def testRepairNodeStorageUnits(self):
545     self.rapi.AddResponse("99")
546     self.assertEqual(99, self.client.RepairNodeStorageUnits("node-z", "lvm-pv",
547                                                             "hda"))
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"])
552
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"])
558
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"])
565     self.assertDryRun()
566     self.assertQuery("tag", ["awesome"])
567
568   def testDeleteNodeTags(self):
569     self.rapi.AddResponse("16861")
570     self.assertEqual(16861, self.client.DeleteNodeTags("node-w", ["awesome"],
571                                                        dry_run=True))
572     self.assertHandler(rlib2.R_2_nodes_name_tags)
573     self.assertItems(["node-w"])
574     self.assertDryRun()
575     self.assertQuery("tag", ["awesome"])
576
577
578 if __name__ == '__main__':
579   testutils.GanetiTestProgram()