RAPI client: Update ReplaceInstanceDisks
[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 testGetOperatingSystems(self):
194     self.rapi.AddResponse("[\"beos\"]")
195     self.assertEqual(["beos"], self.client.GetOperatingSystems())
196     self.assertHandler(rlib2.R_2_os)
197
198   def testGetClusterTags(self):
199     self.rapi.AddResponse("[\"tag\"]")
200     self.assertEqual(["tag"], self.client.GetClusterTags())
201     self.assertHandler(rlib2.R_2_tags)
202
203   def testAddClusterTags(self):
204     self.rapi.AddResponse("1234")
205     self.assertEqual(1234,
206         self.client.AddClusterTags(["awesome"], dry_run=True))
207     self.assertHandler(rlib2.R_2_tags)
208     self.assertDryRun()
209     self.assertQuery("tag", ["awesome"])
210
211   def testDeleteClusterTags(self):
212     self.rapi.AddResponse("5107")
213     self.assertEqual(5107, self.client.DeleteClusterTags(["awesome"],
214                                                          dry_run=True))
215     self.assertHandler(rlib2.R_2_tags)
216     self.assertDryRun()
217     self.assertQuery("tag", ["awesome"])
218
219   def testGetInfo(self):
220     self.rapi.AddResponse("{}")
221     self.assertEqual({}, self.client.GetInfo())
222     self.assertHandler(rlib2.R_2_info)
223
224   def testGetInstances(self):
225     self.rapi.AddResponse("[]")
226     self.assertEqual([], self.client.GetInstances(bulk=True))
227     self.assertHandler(rlib2.R_2_instances)
228     self.assertBulk()
229
230   def testGetInstanceInfo(self):
231     self.rapi.AddResponse("[]")
232     self.assertEqual([], self.client.GetInstanceInfo("instance"))
233     self.assertHandler(rlib2.R_2_instances_name)
234     self.assertItems(["instance"])
235
236   def testCreateInstance(self):
237     self.rapi.AddResponse("1234")
238     self.assertEqual(1234, self.client.CreateInstance(dry_run=True))
239     self.assertHandler(rlib2.R_2_instances)
240     self.assertDryRun()
241
242   def testDeleteInstance(self):
243     self.rapi.AddResponse("1234")
244     self.assertEqual(1234, self.client.DeleteInstance("instance", dry_run=True))
245     self.assertHandler(rlib2.R_2_instances_name)
246     self.assertItems(["instance"])
247     self.assertDryRun()
248
249   def testGetInstanceTags(self):
250     self.rapi.AddResponse("[]")
251     self.assertEqual([], self.client.GetInstanceTags("fooinstance"))
252     self.assertHandler(rlib2.R_2_instances_name_tags)
253     self.assertItems(["fooinstance"])
254
255   def testAddInstanceTags(self):
256     self.rapi.AddResponse("1234")
257     self.assertEqual(1234,
258         self.client.AddInstanceTags("fooinstance", ["awesome"], dry_run=True))
259     self.assertHandler(rlib2.R_2_instances_name_tags)
260     self.assertItems(["fooinstance"])
261     self.assertDryRun()
262     self.assertQuery("tag", ["awesome"])
263
264   def testDeleteInstanceTags(self):
265     self.rapi.AddResponse("25826")
266     self.assertEqual(25826, self.client.DeleteInstanceTags("foo", ["awesome"],
267                                                            dry_run=True))
268     self.assertHandler(rlib2.R_2_instances_name_tags)
269     self.assertItems(["foo"])
270     self.assertDryRun()
271     self.assertQuery("tag", ["awesome"])
272
273   def testRebootInstance(self):
274     self.rapi.AddResponse("6146")
275     job_id = self.client.RebootInstance("i-bar", reboot_type="hard",
276                                         ignore_secondaries=True, dry_run=True)
277     self.assertEqual(6146, job_id)
278     self.assertHandler(rlib2.R_2_instances_name_reboot)
279     self.assertItems(["i-bar"])
280     self.assertDryRun()
281     self.assertQuery("type", ["hard"])
282     self.assertQuery("ignore_secondaries", ["1"])
283
284   def testShutdownInstance(self):
285     self.rapi.AddResponse("1487")
286     self.assertEqual(1487, self.client.ShutdownInstance("foo-instance",
287                                                         dry_run=True))
288     self.assertHandler(rlib2.R_2_instances_name_shutdown)
289     self.assertItems(["foo-instance"])
290     self.assertDryRun()
291
292   def testStartupInstance(self):
293     self.rapi.AddResponse("27149")
294     self.assertEqual(27149, self.client.StartupInstance("bar-instance",
295                                                         dry_run=True))
296     self.assertHandler(rlib2.R_2_instances_name_startup)
297     self.assertItems(["bar-instance"])
298     self.assertDryRun()
299
300   def testReinstallInstance(self):
301     self.rapi.AddResponse("19119")
302     self.assertEqual(19119, self.client.ReinstallInstance("baz-instance", "DOS",
303                                                           no_startup=True))
304     self.assertHandler(rlib2.R_2_instances_name_reinstall)
305     self.assertItems(["baz-instance"])
306     self.assertQuery("os", ["DOS"])
307     self.assertQuery("nostartup", ["1"])
308
309   def testReplaceInstanceDisks(self):
310     self.rapi.AddResponse("999")
311     job_id = self.client.ReplaceInstanceDisks("instance-name",
312         disks=[0, 1], dry_run=True, iallocator="hail")
313     self.assertEqual(999, job_id)
314     self.assertHandler(rlib2.R_2_instances_name_replace_disks)
315     self.assertItems(["instance-name"])
316     self.assertQuery("disks", ["0,1"])
317     self.assertQuery("mode", ["replace_auto"])
318     self.assertQuery("iallocator", ["hail"])
319     self.assertDryRun()
320
321     self.rapi.AddResponse("1000")
322     job_id = self.client.ReplaceInstanceDisks("instance-bar",
323         disks=[1], mode="replace_on_secondary", remote_node="foo-node",
324         dry_run=True)
325     self.assertEqual(1000, job_id)
326     self.assertItems(["instance-bar"])
327     self.assertQuery("disks", ["1"])
328     self.assertQuery("remote_node", ["foo-node"])
329     self.assertDryRun()
330
331     self.rapi.AddResponse("5175")
332     self.assertEqual(5175, self.client.ReplaceInstanceDisks("instance-moo"))
333     self.assertItems(["instance-moo"])
334     self.assertQuery("disks", None)
335
336   def testGetJobs(self):
337     self.rapi.AddResponse('[ { "id": "123", "uri": "\\/2\\/jobs\\/123" },'
338                           '  { "id": "124", "uri": "\\/2\\/jobs\\/124" } ]')
339     self.assertEqual([123, 124], self.client.GetJobs())
340     self.assertHandler(rlib2.R_2_jobs)
341
342   def testGetJobStatus(self):
343     self.rapi.AddResponse("{\"foo\": \"bar\"}")
344     self.assertEqual({"foo": "bar"}, self.client.GetJobStatus(1234))
345     self.assertHandler(rlib2.R_2_jobs_id)
346     self.assertItems(["1234"])
347
348   def testWaitForJobChange(self):
349     fields = ["id", "summary"]
350     expected = {
351       "job_info": [123, "something"],
352       "log_entries": [],
353       }
354
355     self.rapi.AddResponse(serializer.DumpJson(expected))
356     result = self.client.WaitForJobChange(123, fields, [], -1)
357     self.assertEqualValues(expected, result)
358     self.assertHandler(rlib2.R_2_jobs_id_wait)
359     self.assertItems(["123"])
360
361   def testCancelJob(self):
362     self.rapi.AddResponse("[true, \"Job 123 will be canceled\"]")
363     self.assertEqual([True, "Job 123 will be canceled"],
364                      self.client.CancelJob(999, dry_run=True))
365     self.assertHandler(rlib2.R_2_jobs_id)
366     self.assertItems(["999"])
367     self.assertDryRun()
368
369   def testGetNodes(self):
370     self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
371                           " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
372     self.assertEqual(["node1", "node2"], self.client.GetNodes())
373     self.assertHandler(rlib2.R_2_nodes)
374
375     self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
376                           " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
377     self.assertEqual([{"id": "node1", "uri": "uri1"},
378                       {"id": "node2", "uri": "uri2"}],
379                      self.client.GetNodes(bulk=True))
380     self.assertHandler(rlib2.R_2_nodes)
381     self.assertBulk()
382
383   def testGetNodeInfo(self):
384     self.rapi.AddResponse("{}")
385     self.assertEqual({}, self.client.GetNodeInfo("node-foo"))
386     self.assertHandler(rlib2.R_2_nodes_name)
387     self.assertItems(["node-foo"])
388
389   def testEvacuateNode(self):
390     self.rapi.AddResponse("9876")
391     job_id = self.client.EvacuateNode("node-1", remote_node="node-2")
392     self.assertEqual(9876, job_id)
393     self.assertHandler(rlib2.R_2_nodes_name_evacuate)
394     self.assertItems(["node-1"])
395     self.assertQuery("remote_node", ["node-2"])
396
397     self.rapi.AddResponse("8888")
398     job_id = self.client.EvacuateNode("node-3", iallocator="hail", dry_run=True)
399     self.assertEqual(8888, job_id)
400     self.assertItems(["node-3"])
401     self.assertQuery("iallocator", ["hail"])
402     self.assertDryRun()
403
404     self.assertRaises(client.GanetiApiError,
405                       self.client.EvacuateNode,
406                       "node-4", iallocator="hail", remote_node="node-5")
407
408   def testMigrateNode(self):
409     self.rapi.AddResponse("1111")
410     self.assertEqual(1111, self.client.MigrateNode("node-a", dry_run=True))
411     self.assertHandler(rlib2.R_2_nodes_name_migrate)
412     self.assertItems(["node-a"])
413     self.assertQuery("live", ["1"])
414     self.assertDryRun()
415
416   def testGetNodeRole(self):
417     self.rapi.AddResponse("\"master\"")
418     self.assertEqual("master", self.client.GetNodeRole("node-a"))
419     self.assertHandler(rlib2.R_2_nodes_name_role)
420     self.assertItems(["node-a"])
421
422   def testSetNodeRole(self):
423     self.rapi.AddResponse("789")
424     self.assertEqual(789,
425         self.client.SetNodeRole("node-foo", "master-candidate", force=True))
426     self.assertHandler(rlib2.R_2_nodes_name_role)
427     self.assertItems(["node-foo"])
428     self.assertQuery("force", ["1"])
429     self.assertEqual("\"master-candidate\"", self.http.last_request.data)
430
431     self.assertRaises(client.InvalidNodeRole,
432                       self.client.SetNodeRole, "node-bar", "fake-role")
433
434   def testGetNodeStorageUnits(self):
435     self.rapi.AddResponse("42")
436     self.assertEqual(42,
437         self.client.GetNodeStorageUnits("node-x", "lvm-pv", "fields"))
438     self.assertHandler(rlib2.R_2_nodes_name_storage)
439     self.assertItems(["node-x"])
440     self.assertQuery("storage_type", ["lvm-pv"])
441     self.assertQuery("output_fields", ["fields"])
442
443   def testModifyNodeStorageUnits(self):
444     self.rapi.AddResponse("14")
445     self.assertEqual(14,
446         self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda"))
447     self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
448     self.assertItems(["node-z"])
449     self.assertQuery("storage_type", ["lvm-pv"])
450     self.assertQuery("name", ["hda"])
451     self.assertQuery("allocatable", None)
452
453     for allocatable, query_allocatable in [(True, "1"), (False, "0")]:
454       self.rapi.AddResponse("7205")
455       job_id = self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda",
456                                                   allocatable=allocatable)
457       self.assertEqual(7205, job_id)
458       self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
459       self.assertItems(["node-z"])
460       self.assertQuery("storage_type", ["lvm-pv"])
461       self.assertQuery("name", ["hda"])
462       self.assertQuery("allocatable", [query_allocatable])
463
464   def testRepairNodeStorageUnits(self):
465     self.rapi.AddResponse("99")
466     self.assertEqual(99, self.client.RepairNodeStorageUnits("node-z", "lvm-pv",
467                                                             "hda"))
468     self.assertHandler(rlib2.R_2_nodes_name_storage_repair)
469     self.assertItems(["node-z"])
470     self.assertQuery("storage_type", ["lvm-pv"])
471     self.assertQuery("name", ["hda"])
472
473   def testGetNodeTags(self):
474     self.rapi.AddResponse("[\"fry\", \"bender\"]")
475     self.assertEqual(["fry", "bender"], self.client.GetNodeTags("node-k"))
476     self.assertHandler(rlib2.R_2_nodes_name_tags)
477     self.assertItems(["node-k"])
478
479   def testAddNodeTags(self):
480     self.rapi.AddResponse("1234")
481     self.assertEqual(1234,
482         self.client.AddNodeTags("node-v", ["awesome"], dry_run=True))
483     self.assertHandler(rlib2.R_2_nodes_name_tags)
484     self.assertItems(["node-v"])
485     self.assertDryRun()
486     self.assertQuery("tag", ["awesome"])
487
488   def testDeleteNodeTags(self):
489     self.rapi.AddResponse("16861")
490     self.assertEqual(16861, self.client.DeleteNodeTags("node-w", ["awesome"],
491                                                        dry_run=True))
492     self.assertHandler(rlib2.R_2_nodes_name_tags)
493     self.assertItems(["node-w"])
494     self.assertDryRun()
495     self.assertQuery("tag", ["awesome"])
496
497
498 if __name__ == '__main__':
499   testutils.GanetiTestProgram()