RAPI client: Fix error message for unsupported methods in unittest
[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
85   def __init__(self):
86     self._mapper = connector.Mapper()
87     self._responses = []
88     self._last_handler = None
89
90   def AddResponse(self, response):
91     self._responses.insert(0, response)
92
93   def PopResponse(self):
94     if len(self._responses) > 0:
95       return self._responses.pop()
96     else:
97       return None
98
99   def GetLastHandler(self):
100     return self._last_handler
101
102   def FetchResponse(self, path, method):
103     code = 200
104     response = None
105
106     try:
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()):
110         code = 501
111         response = "Method not implemented"
112     except http.HttpException, ex:
113       code = ex.code
114       response = ex.message
115
116     if not response:
117       response = self.PopResponse()
118
119     return code, response
120
121
122 class RapiMockTest(unittest.TestCase):
123   def test(self):
124     rapi = RapiMock()
125     path = "/version"
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))
134
135
136 class GanetiRapiClientTests(testutils.GanetiTestCase):
137   def setUp(self):
138     testutils.GanetiTestCase.setUp(self)
139
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
146
147   def assertHandler(self, handler_cls):
148     self.failUnless(isinstance(self.rapi.GetLastHandler(), handler_cls))
149
150   def assertQuery(self, key, value):
151     self.assertEqual(value, self.rapi.GetLastHandler().queryargs.get(key, None))
152
153   def assertItems(self, items):
154     self.assertEqual(items, self.rapi.GetLastHandler().items)
155
156   def assertBulk(self):
157     self.assertTrue(self.rapi.GetLastHandler().useBulk())
158
159   def assertDryRun(self):
160     self.assertTrue(self.rapi.GetLastHandler().dryRun())
161
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)
167
168   def testGetOperatingSystems(self):
169     self.rapi.AddResponse("[\"beos\"]")
170     self.assertEqual(["beos"], self.client.GetOperatingSystems())
171     self.assertHandler(rlib2.R_2_os)
172
173   def testGetClusterTags(self):
174     self.rapi.AddResponse("[\"tag\"]")
175     self.assertEqual(["tag"], self.client.GetClusterTags())
176     self.assertHandler(rlib2.R_2_tags)
177
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)
183     self.assertDryRun()
184     self.assertQuery("tag", ["awesome"])
185
186   def testDeleteClusterTags(self):
187     self.rapi.AddResponse("5107")
188     self.assertEqual(5107, self.client.DeleteClusterTags(["awesome"],
189                                                          dry_run=True))
190     self.assertHandler(rlib2.R_2_tags)
191     self.assertDryRun()
192     self.assertQuery("tag", ["awesome"])
193
194   def testGetInfo(self):
195     self.rapi.AddResponse("{}")
196     self.assertEqual({}, self.client.GetInfo())
197     self.assertHandler(rlib2.R_2_info)
198
199   def testGetInstances(self):
200     self.rapi.AddResponse("[]")
201     self.assertEqual([], self.client.GetInstances(bulk=True))
202     self.assertHandler(rlib2.R_2_instances)
203     self.assertBulk()
204
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"])
210
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)
215     self.assertDryRun()
216
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"])
222     self.assertDryRun()
223
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"])
229
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"])
236     self.assertDryRun()
237     self.assertQuery("tag", ["awesome"])
238
239   def testDeleteInstanceTags(self):
240     self.rapi.AddResponse("25826")
241     self.assertEqual(25826, self.client.DeleteInstanceTags("foo", ["awesome"],
242                                                            dry_run=True))
243     self.assertHandler(rlib2.R_2_instances_name_tags)
244     self.assertItems(["foo"])
245     self.assertDryRun()
246     self.assertQuery("tag", ["awesome"])
247
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"])
255     self.assertDryRun()
256     self.assertQuery("type", ["hard"])
257     self.assertQuery("ignore_secondaries", ["True"])
258
259   def testShutdownInstance(self):
260     self.rapi.AddResponse("1487")
261     self.assertEqual(1487, self.client.ShutdownInstance("foo-instance",
262                                                         dry_run=True))
263     self.assertHandler(rlib2.R_2_instances_name_shutdown)
264     self.assertItems(["foo-instance"])
265     self.assertDryRun()
266
267   def testStartupInstance(self):
268     self.rapi.AddResponse("27149")
269     self.assertEqual(27149, self.client.StartupInstance("bar-instance",
270                                                         dry_run=True))
271     self.assertHandler(rlib2.R_2_instances_name_startup)
272     self.assertItems(["bar-instance"])
273     self.assertDryRun()
274
275   def testReinstallInstance(self):
276     self.rapi.AddResponse("19119")
277     self.assertEqual(19119, self.client.ReinstallInstance("baz-instance", "DOS",
278                                                           no_startup=True))
279     self.assertHandler(rlib2.R_2_instances_name_reinstall)
280     self.assertItems(["baz-instance"])
281     self.assertQuery("os", ["DOS"])
282     self.assertQuery("nostartup", ["1"])
283
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"])
294     self.assertDryRun()
295
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")
302
303     self.rapi.AddResponse("1000")
304     job_id = self.client.ReplaceInstanceDisks("instance-bar",
305         ["hda"], mode="replace_on_secondary", remote_node="foo-node",
306         dry_run=True)
307     self.assertEqual(1000, job_id)
308     self.assertItems(["instance-bar"])
309     self.assertQuery("disks", ["hda"])
310     self.assertQuery("remote_node", ["foo-node"])
311     self.assertDryRun()
312
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)
318
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"])
324
325   def testWaitForJobChange(self):
326     fields = ["id", "summary"]
327     expected = {
328       "job_info": [123, "something"],
329       "log_entries": [],
330       }
331
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"])
337
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"])
344     self.assertDryRun()
345
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)
351
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)
358     self.assertBulk()
359
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"])
365
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"])
373
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"])
379     self.assertDryRun()
380
381     self.assertRaises(client.GanetiApiError,
382                       self.client.EvacuateNode,
383                       "node-4", iallocator="hail", remote_node="node-5")
384
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"])
391     self.assertDryRun()
392
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"])
398
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)
407
408     self.assertRaises(client.InvalidNodeRole,
409                       self.client.SetNodeRole, "node-bar", "fake-role")
410
411   def testGetNodeStorageUnits(self):
412     self.rapi.AddResponse("42")
413     self.assertEqual(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"])
419
420     self.assertRaises(client.InvalidStorageType,
421                       self.client.GetNodeStorageUnits,
422                       "node-y", "floppy-disk", "fields")
423
424   def testModifyNodeStorageUnits(self):
425     self.rapi.AddResponse("14")
426     self.assertEqual(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"])
432
433     self.assertRaises(client.InvalidStorageType,
434                       self.client.ModifyNodeStorageUnits,
435                       "node-n", "floppy-disk", "hdc")
436
437   def testRepairNodeStorageUnits(self):
438     self.rapi.AddResponse("99")
439     self.assertEqual(99, self.client.RepairNodeStorageUnits("node-z", "lvm-pv",
440                                                             "hda"))
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"])
445
446     self.assertRaises(client.InvalidStorageType,
447                       self.client.RepairNodeStorageUnits,
448                       "node-n", "floppy-disk", "hdc")
449
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"])
455
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"])
462     self.assertDryRun()
463     self.assertQuery("tag", ["awesome"])
464
465   def testDeleteNodeTags(self):
466     self.rapi.AddResponse("16861")
467     self.assertEqual(16861, self.client.DeleteNodeTags("node-w", ["awesome"],
468                                                        dry_run=True))
469     self.assertHandler(rlib2.R_2_nodes_name_tags)
470     self.assertItems(["node-w"])
471     self.assertDryRun()
472     self.assertQuery("tag", ["awesome"])
473
474
475 if __name__ == '__main__':
476   testutils.GanetiTestProgram()