Merge branch 'devel-2.5'
[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 import pycurl
29
30 from ganeti import constants
31 from ganeti import http
32 from ganeti import serializer
33 from ganeti import utils
34 from ganeti import query
35 from ganeti import objects
36
37 from ganeti.rapi import connector
38 from ganeti.rapi import rlib2
39 from ganeti.rapi import client
40
41 import testutils
42
43
44 _URI_RE = re.compile(r"https://(?P<host>.*):(?P<port>\d+)(?P<path>/.*)")
45
46 # List of resource handlers which aren't used by the RAPI client
47 _KNOWN_UNUSED = set([
48   rlib2.R_root,
49   rlib2.R_2,
50   ])
51
52 # Global variable for collecting used handlers
53 _used_handlers = None
54
55
56 def _GetPathFromUri(uri):
57   """Gets the path and query from a URI.
58
59   """
60   match = _URI_RE.match(uri)
61   if match:
62     return match.groupdict()["path"]
63   else:
64     return None
65
66
67 class FakeCurl:
68   def __init__(self, rapi):
69     self._rapi = rapi
70     self._opts = {}
71     self._info = {}
72
73   def setopt(self, opt, value):
74     self._opts[opt] = value
75
76   def getopt(self, opt):
77     return self._opts.get(opt)
78
79   def unsetopt(self, opt):
80     self._opts.pop(opt, None)
81
82   def getinfo(self, info):
83     return self._info[info]
84
85   def perform(self):
86     method = self._opts[pycurl.CUSTOMREQUEST]
87     url = self._opts[pycurl.URL]
88     request_body = self._opts[pycurl.POSTFIELDS]
89     writefn = self._opts[pycurl.WRITEFUNCTION]
90
91     path = _GetPathFromUri(url)
92     (code, resp_body) = self._rapi.FetchResponse(path, method, request_body)
93
94     self._info[pycurl.RESPONSE_CODE] = code
95     if resp_body is not None:
96       writefn(resp_body)
97
98
99 class RapiMock(object):
100   def __init__(self):
101     self._mapper = connector.Mapper()
102     self._responses = []
103     self._last_handler = None
104     self._last_req_data = None
105
106   def ResetResponses(self):
107     del self._responses[:]
108
109   def AddResponse(self, response, code=200):
110     self._responses.insert(0, (code, response))
111
112   def CountPending(self):
113     return len(self._responses)
114
115   def GetLastHandler(self):
116     return self._last_handler
117
118   def GetLastRequestData(self):
119     return self._last_req_data
120
121   def FetchResponse(self, path, method, request_body):
122     self._last_req_data = request_body
123
124     try:
125       (handler_cls, items, args) = self._mapper.getController(path)
126
127       # Record handler as used
128       _used_handlers.add(handler_cls)
129
130       self._last_handler = handler_cls(items, args, None)
131       if not hasattr(self._last_handler, method.upper()):
132         raise http.HttpNotImplemented(message="Method not implemented")
133
134     except http.HttpException, ex:
135       code = ex.code
136       response = ex.message
137     else:
138       if not self._responses:
139         raise Exception("No responses")
140
141       (code, response) = self._responses.pop()
142
143     return code, response
144
145
146 class TestConstants(unittest.TestCase):
147   def test(self):
148     self.assertEqual(client.GANETI_RAPI_PORT, constants.DEFAULT_RAPI_PORT)
149     self.assertEqual(client.GANETI_RAPI_VERSION, constants.RAPI_VERSION)
150     self.assertEqual(client.HTTP_APP_JSON, http.HTTP_APP_JSON)
151     self.assertEqual(client._REQ_DATA_VERSION_FIELD, rlib2._REQ_DATA_VERSION)
152     self.assertEqual(client._INST_CREATE_REQV1, rlib2._INST_CREATE_REQV1)
153     self.assertEqual(client._INST_REINSTALL_REQV1, rlib2._INST_REINSTALL_REQV1)
154     self.assertEqual(client._NODE_MIGRATE_REQV1, rlib2._NODE_MIGRATE_REQV1)
155     self.assertEqual(client._NODE_EVAC_RES1, rlib2._NODE_EVAC_RES1)
156     self.assertEqual(client._INST_NIC_PARAMS, constants.INIC_PARAMS)
157     self.assertEqual(client.JOB_STATUS_QUEUED, constants.JOB_STATUS_QUEUED)
158     self.assertEqual(client.JOB_STATUS_WAITING, constants.JOB_STATUS_WAITING)
159     self.assertEqual(client.JOB_STATUS_CANCELING,
160                      constants.JOB_STATUS_CANCELING)
161     self.assertEqual(client.JOB_STATUS_RUNNING, constants.JOB_STATUS_RUNNING)
162     self.assertEqual(client.JOB_STATUS_CANCELED, constants.JOB_STATUS_CANCELED)
163     self.assertEqual(client.JOB_STATUS_SUCCESS, constants.JOB_STATUS_SUCCESS)
164     self.assertEqual(client.JOB_STATUS_ERROR, constants.JOB_STATUS_ERROR)
165     self.assertEqual(client.JOB_STATUS_FINALIZED, constants.JOBS_FINALIZED)
166     self.assertEqual(client.JOB_STATUS_ALL, constants.JOB_STATUS_ALL)
167
168     # Legacy name
169     self.assertEqual(client.JOB_STATUS_WAITLOCK, constants.JOB_STATUS_WAITING)
170
171
172 class RapiMockTest(unittest.TestCase):
173   def test(self):
174     rapi = RapiMock()
175     path = "/version"
176     self.assertEqual((404, None), rapi.FetchResponse("/foo", "GET", None))
177     self.assertEqual((501, "Method not implemented"),
178                      rapi.FetchResponse("/version", "POST", None))
179     rapi.AddResponse("2")
180     code, response = rapi.FetchResponse("/version", "GET", None)
181     self.assertEqual(200, code)
182     self.assertEqual("2", response)
183     self.failUnless(isinstance(rapi.GetLastHandler(), rlib2.R_version))
184
185
186 def _FakeNoSslPycurlVersion():
187   # Note: incomplete version tuple
188   return (3, "7.16.0", 462848, "mysystem", 1581, None, 0)
189
190
191 def _FakeFancySslPycurlVersion():
192   # Note: incomplete version tuple
193   return (3, "7.16.0", 462848, "mysystem", 1581, "FancySSL/1.2.3", 0)
194
195
196 def _FakeOpenSslPycurlVersion():
197   # Note: incomplete version tuple
198   return (2, "7.15.5", 462597, "othersystem", 668, "OpenSSL/0.9.8c", 0)
199
200
201 def _FakeGnuTlsPycurlVersion():
202   # Note: incomplete version tuple
203   return (3, "7.18.0", 463360, "somesystem", 1581, "GnuTLS/2.0.4", 0)
204
205
206 class TestExtendedConfig(unittest.TestCase):
207   def testAuth(self):
208     cl = client.GanetiRapiClient("master.example.com",
209                                  username="user", password="pw",
210                                  curl_factory=lambda: FakeCurl(RapiMock()))
211
212     curl = cl._CreateCurl()
213     self.assertEqual(curl.getopt(pycurl.HTTPAUTH), pycurl.HTTPAUTH_BASIC)
214     self.assertEqual(curl.getopt(pycurl.USERPWD), "user:pw")
215
216   def testInvalidAuth(self):
217     # No username
218     self.assertRaises(client.Error, client.GanetiRapiClient,
219                       "master-a.example.com", password="pw")
220     # No password
221     self.assertRaises(client.Error, client.GanetiRapiClient,
222                       "master-b.example.com", username="user")
223
224   def testCertVerifyInvalidCombinations(self):
225     self.assertRaises(client.Error, client.GenericCurlConfig,
226                       use_curl_cabundle=True, cafile="cert1.pem")
227     self.assertRaises(client.Error, client.GenericCurlConfig,
228                       use_curl_cabundle=True, capath="certs/")
229     self.assertRaises(client.Error, client.GenericCurlConfig,
230                       use_curl_cabundle=True,
231                       cafile="cert1.pem", capath="certs/")
232
233   def testProxySignalVerifyHostname(self):
234     for use_gnutls in [False, True]:
235       if use_gnutls:
236         pcverfn = _FakeGnuTlsPycurlVersion
237       else:
238         pcverfn = _FakeOpenSslPycurlVersion
239
240       for proxy in ["", "http://127.0.0.1:1234"]:
241         for use_signal in [False, True]:
242           for verify_hostname in [False, True]:
243             cfgfn = client.GenericCurlConfig(proxy=proxy, use_signal=use_signal,
244                                              verify_hostname=verify_hostname,
245                                              _pycurl_version_fn=pcverfn)
246
247             curl_factory = lambda: FakeCurl(RapiMock())
248             cl = client.GanetiRapiClient("master.example.com",
249                                          curl_config_fn=cfgfn,
250                                          curl_factory=curl_factory)
251
252             curl = cl._CreateCurl()
253             self.assertEqual(curl.getopt(pycurl.PROXY), proxy)
254             self.assertEqual(curl.getopt(pycurl.NOSIGNAL), not use_signal)
255
256             if verify_hostname:
257               self.assertEqual(curl.getopt(pycurl.SSL_VERIFYHOST), 2)
258             else:
259               self.assertEqual(curl.getopt(pycurl.SSL_VERIFYHOST), 0)
260
261   def testNoCertVerify(self):
262     cfgfn = client.GenericCurlConfig()
263
264     curl_factory = lambda: FakeCurl(RapiMock())
265     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
266                                  curl_factory=curl_factory)
267
268     curl = cl._CreateCurl()
269     self.assertFalse(curl.getopt(pycurl.SSL_VERIFYPEER))
270     self.assertFalse(curl.getopt(pycurl.CAINFO))
271     self.assertFalse(curl.getopt(pycurl.CAPATH))
272
273   def testCertVerifyCurlBundle(self):
274     cfgfn = client.GenericCurlConfig(use_curl_cabundle=True)
275
276     curl_factory = lambda: FakeCurl(RapiMock())
277     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
278                                  curl_factory=curl_factory)
279
280     curl = cl._CreateCurl()
281     self.assert_(curl.getopt(pycurl.SSL_VERIFYPEER))
282     self.assertFalse(curl.getopt(pycurl.CAINFO))
283     self.assertFalse(curl.getopt(pycurl.CAPATH))
284
285   def testCertVerifyCafile(self):
286     mycert = "/tmp/some/UNUSED/cert/file.pem"
287     cfgfn = client.GenericCurlConfig(cafile=mycert)
288
289     curl_factory = lambda: FakeCurl(RapiMock())
290     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
291                                  curl_factory=curl_factory)
292
293     curl = cl._CreateCurl()
294     self.assert_(curl.getopt(pycurl.SSL_VERIFYPEER))
295     self.assertEqual(curl.getopt(pycurl.CAINFO), mycert)
296     self.assertFalse(curl.getopt(pycurl.CAPATH))
297
298   def testCertVerifyCapath(self):
299     certdir = "/tmp/some/UNUSED/cert/directory"
300     pcverfn = _FakeOpenSslPycurlVersion
301     cfgfn = client.GenericCurlConfig(capath=certdir,
302                                      _pycurl_version_fn=pcverfn)
303
304     curl_factory = lambda: FakeCurl(RapiMock())
305     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
306                                  curl_factory=curl_factory)
307
308     curl = cl._CreateCurl()
309     self.assert_(curl.getopt(pycurl.SSL_VERIFYPEER))
310     self.assertEqual(curl.getopt(pycurl.CAPATH), certdir)
311     self.assertFalse(curl.getopt(pycurl.CAINFO))
312
313   def testCertVerifyCapathGnuTls(self):
314     certdir = "/tmp/some/UNUSED/cert/directory"
315     pcverfn = _FakeGnuTlsPycurlVersion
316     cfgfn = client.GenericCurlConfig(capath=certdir,
317                                      _pycurl_version_fn=pcverfn)
318
319     curl_factory = lambda: FakeCurl(RapiMock())
320     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
321                                  curl_factory=curl_factory)
322
323     self.assertRaises(client.Error, cl._CreateCurl)
324
325   def testCertVerifyNoSsl(self):
326     certdir = "/tmp/some/UNUSED/cert/directory"
327     pcverfn = _FakeNoSslPycurlVersion
328     cfgfn = client.GenericCurlConfig(capath=certdir,
329                                      _pycurl_version_fn=pcverfn)
330
331     curl_factory = lambda: FakeCurl(RapiMock())
332     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
333                                  curl_factory=curl_factory)
334
335     self.assertRaises(client.Error, cl._CreateCurl)
336
337   def testCertVerifyFancySsl(self):
338     certdir = "/tmp/some/UNUSED/cert/directory"
339     pcverfn = _FakeFancySslPycurlVersion
340     cfgfn = client.GenericCurlConfig(capath=certdir,
341                                      _pycurl_version_fn=pcverfn)
342
343     curl_factory = lambda: FakeCurl(RapiMock())
344     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
345                                  curl_factory=curl_factory)
346
347     self.assertRaises(NotImplementedError, cl._CreateCurl)
348
349   def testCertVerifyCapath(self):
350     for connect_timeout in [None, 1, 5, 10, 30, 60, 300]:
351       for timeout in [None, 1, 30, 60, 3600, 24 * 3600]:
352         cfgfn = client.GenericCurlConfig(connect_timeout=connect_timeout,
353                                          timeout=timeout)
354
355         curl_factory = lambda: FakeCurl(RapiMock())
356         cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
357                                      curl_factory=curl_factory)
358
359         curl = cl._CreateCurl()
360         self.assertEqual(curl.getopt(pycurl.CONNECTTIMEOUT), connect_timeout)
361         self.assertEqual(curl.getopt(pycurl.TIMEOUT), timeout)
362
363
364 class GanetiRapiClientTests(testutils.GanetiTestCase):
365   def setUp(self):
366     testutils.GanetiTestCase.setUp(self)
367
368     self.rapi = RapiMock()
369     self.curl = FakeCurl(self.rapi)
370     self.client = client.GanetiRapiClient("master.example.com",
371                                           curl_factory=lambda: self.curl)
372
373   def assertHandler(self, handler_cls):
374     self.failUnless(isinstance(self.rapi.GetLastHandler(), handler_cls))
375
376   def assertQuery(self, key, value):
377     self.assertEqual(value, self.rapi.GetLastHandler().queryargs.get(key, None))
378
379   def assertItems(self, items):
380     self.assertEqual(items, self.rapi.GetLastHandler().items)
381
382   def assertBulk(self):
383     self.assertTrue(self.rapi.GetLastHandler().useBulk())
384
385   def assertDryRun(self):
386     self.assertTrue(self.rapi.GetLastHandler().dryRun())
387
388   def assertUseForce(self):
389     self.assertTrue(self.rapi.GetLastHandler().useForce())
390
391   def testEncodeQuery(self):
392     query = [
393       ("a", None),
394       ("b", 1),
395       ("c", 2),
396       ("d", "Foo"),
397       ("e", True),
398       ]
399
400     expected = [
401       ("a", ""),
402       ("b", 1),
403       ("c", 2),
404       ("d", "Foo"),
405       ("e", 1),
406       ]
407
408     self.assertEqualValues(self.client._EncodeQuery(query),
409                            expected)
410
411     # invalid types
412     for i in [[1, 2, 3], {"moo": "boo"}, (1, 2, 3)]:
413       self.assertRaises(ValueError, self.client._EncodeQuery, [("x", i)])
414
415   def testCurlSettings(self):
416     self.rapi.AddResponse("2")
417     self.assertEqual(2, self.client.GetVersion())
418     self.assertHandler(rlib2.R_version)
419
420     # Signals should be disabled by default
421     self.assert_(self.curl.getopt(pycurl.NOSIGNAL))
422
423     # No auth and no proxy
424     self.assertFalse(self.curl.getopt(pycurl.USERPWD))
425     self.assert_(self.curl.getopt(pycurl.PROXY) is None)
426
427     # Content-type is required for requests
428     headers = self.curl.getopt(pycurl.HTTPHEADER)
429     self.assert_("Content-type: application/json" in headers)
430
431   def testHttpError(self):
432     self.rapi.AddResponse(None, code=404)
433     try:
434       self.client.GetJobStatus(15140)
435     except client.GanetiApiError, err:
436       self.assertEqual(err.code, 404)
437     else:
438       self.fail("Didn't raise exception")
439
440   def testGetVersion(self):
441     self.rapi.AddResponse("2")
442     self.assertEqual(2, self.client.GetVersion())
443     self.assertHandler(rlib2.R_version)
444
445   def testGetFeatures(self):
446     for features in [[], ["foo", "bar", "baz"]]:
447       self.rapi.AddResponse(serializer.DumpJson(features))
448       self.assertEqual(features, self.client.GetFeatures())
449       self.assertHandler(rlib2.R_2_features)
450
451   def testGetFeaturesNotFound(self):
452     self.rapi.AddResponse(None, code=404)
453     self.assertEqual([], self.client.GetFeatures())
454
455   def testGetOperatingSystems(self):
456     self.rapi.AddResponse("[\"beos\"]")
457     self.assertEqual(["beos"], self.client.GetOperatingSystems())
458     self.assertHandler(rlib2.R_2_os)
459
460   def testGetClusterTags(self):
461     self.rapi.AddResponse("[\"tag\"]")
462     self.assertEqual(["tag"], self.client.GetClusterTags())
463     self.assertHandler(rlib2.R_2_tags)
464
465   def testAddClusterTags(self):
466     self.rapi.AddResponse("1234")
467     self.assertEqual(1234,
468         self.client.AddClusterTags(["awesome"], dry_run=True))
469     self.assertHandler(rlib2.R_2_tags)
470     self.assertDryRun()
471     self.assertQuery("tag", ["awesome"])
472
473   def testDeleteClusterTags(self):
474     self.rapi.AddResponse("5107")
475     self.assertEqual(5107, self.client.DeleteClusterTags(["awesome"],
476                                                          dry_run=True))
477     self.assertHandler(rlib2.R_2_tags)
478     self.assertDryRun()
479     self.assertQuery("tag", ["awesome"])
480
481   def testGetInfo(self):
482     self.rapi.AddResponse("{}")
483     self.assertEqual({}, self.client.GetInfo())
484     self.assertHandler(rlib2.R_2_info)
485
486   def testGetInstances(self):
487     self.rapi.AddResponse("[]")
488     self.assertEqual([], self.client.GetInstances(bulk=True))
489     self.assertHandler(rlib2.R_2_instances)
490     self.assertBulk()
491
492   def testGetInstance(self):
493     self.rapi.AddResponse("[]")
494     self.assertEqual([], self.client.GetInstance("instance"))
495     self.assertHandler(rlib2.R_2_instances_name)
496     self.assertItems(["instance"])
497
498   def testGetInstanceInfo(self):
499     self.rapi.AddResponse("21291")
500     self.assertEqual(21291, self.client.GetInstanceInfo("inst3"))
501     self.assertHandler(rlib2.R_2_instances_name_info)
502     self.assertItems(["inst3"])
503     self.assertQuery("static", None)
504
505     self.rapi.AddResponse("3428")
506     self.assertEqual(3428, self.client.GetInstanceInfo("inst31", static=False))
507     self.assertHandler(rlib2.R_2_instances_name_info)
508     self.assertItems(["inst31"])
509     self.assertQuery("static", ["0"])
510
511     self.rapi.AddResponse("15665")
512     self.assertEqual(15665, self.client.GetInstanceInfo("inst32", static=True))
513     self.assertHandler(rlib2.R_2_instances_name_info)
514     self.assertItems(["inst32"])
515     self.assertQuery("static", ["1"])
516
517   def testCreateInstanceOldVersion(self):
518     # The old request format, version 0, is no longer supported
519     self.rapi.AddResponse(None, code=404)
520     self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
521                       "create", "inst1.example.com", "plain", [], [])
522     self.assertEqual(self.rapi.CountPending(), 0)
523
524   def testCreateInstance(self):
525     self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_CREATE_REQV1]))
526     self.rapi.AddResponse("23030")
527     job_id = self.client.CreateInstance("create", "inst1.example.com",
528                                         "plain", [], [], dry_run=True)
529     self.assertEqual(job_id, 23030)
530     self.assertHandler(rlib2.R_2_instances)
531     self.assertDryRun()
532
533     data = serializer.LoadJson(self.rapi.GetLastRequestData())
534
535     for field in ["dry_run", "beparams", "hvparams", "start"]:
536       self.assertFalse(field in data)
537
538     self.assertEqual(data["name"], "inst1.example.com")
539     self.assertEqual(data["disk_template"], "plain")
540
541   def testCreateInstance2(self):
542     self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_CREATE_REQV1]))
543     self.rapi.AddResponse("24740")
544     job_id = self.client.CreateInstance("import", "inst2.example.com",
545                                         "drbd8", [{"size": 100,}],
546                                         [{}, {"bridge": "br1", }],
547                                         dry_run=False, start=True,
548                                         pnode="node1", snode="node9",
549                                         ip_check=False)
550     self.assertEqual(job_id, 24740)
551     self.assertHandler(rlib2.R_2_instances)
552
553     data = serializer.LoadJson(self.rapi.GetLastRequestData())
554     self.assertEqual(data[rlib2._REQ_DATA_VERSION], 1)
555     self.assertEqual(data["name"], "inst2.example.com")
556     self.assertEqual(data["disk_template"], "drbd8")
557     self.assertEqual(data["start"], True)
558     self.assertEqual(data["ip_check"], False)
559     self.assertEqualValues(data["disks"], [{"size": 100,}])
560     self.assertEqualValues(data["nics"], [{}, {"bridge": "br1", }])
561
562   def testDeleteInstance(self):
563     self.rapi.AddResponse("1234")
564     self.assertEqual(1234, self.client.DeleteInstance("instance", dry_run=True))
565     self.assertHandler(rlib2.R_2_instances_name)
566     self.assertItems(["instance"])
567     self.assertDryRun()
568
569   def testGetInstanceTags(self):
570     self.rapi.AddResponse("[]")
571     self.assertEqual([], self.client.GetInstanceTags("fooinstance"))
572     self.assertHandler(rlib2.R_2_instances_name_tags)
573     self.assertItems(["fooinstance"])
574
575   def testAddInstanceTags(self):
576     self.rapi.AddResponse("1234")
577     self.assertEqual(1234,
578         self.client.AddInstanceTags("fooinstance", ["awesome"], dry_run=True))
579     self.assertHandler(rlib2.R_2_instances_name_tags)
580     self.assertItems(["fooinstance"])
581     self.assertDryRun()
582     self.assertQuery("tag", ["awesome"])
583
584   def testDeleteInstanceTags(self):
585     self.rapi.AddResponse("25826")
586     self.assertEqual(25826, self.client.DeleteInstanceTags("foo", ["awesome"],
587                                                            dry_run=True))
588     self.assertHandler(rlib2.R_2_instances_name_tags)
589     self.assertItems(["foo"])
590     self.assertDryRun()
591     self.assertQuery("tag", ["awesome"])
592
593   def testRebootInstance(self):
594     self.rapi.AddResponse("6146")
595     job_id = self.client.RebootInstance("i-bar", reboot_type="hard",
596                                         ignore_secondaries=True, dry_run=True)
597     self.assertEqual(6146, job_id)
598     self.assertHandler(rlib2.R_2_instances_name_reboot)
599     self.assertItems(["i-bar"])
600     self.assertDryRun()
601     self.assertQuery("type", ["hard"])
602     self.assertQuery("ignore_secondaries", ["1"])
603
604   def testShutdownInstance(self):
605     self.rapi.AddResponse("1487")
606     self.assertEqual(1487, self.client.ShutdownInstance("foo-instance",
607                                                         dry_run=True))
608     self.assertHandler(rlib2.R_2_instances_name_shutdown)
609     self.assertItems(["foo-instance"])
610     self.assertDryRun()
611
612   def testStartupInstance(self):
613     self.rapi.AddResponse("27149")
614     self.assertEqual(27149, self.client.StartupInstance("bar-instance",
615                                                         dry_run=True))
616     self.assertHandler(rlib2.R_2_instances_name_startup)
617     self.assertItems(["bar-instance"])
618     self.assertDryRun()
619
620   def testReinstallInstance(self):
621     self.rapi.AddResponse(serializer.DumpJson([]))
622     self.rapi.AddResponse("19119")
623     self.assertEqual(19119, self.client.ReinstallInstance("baz-instance",
624                                                           os="DOS",
625                                                           no_startup=True))
626     self.assertHandler(rlib2.R_2_instances_name_reinstall)
627     self.assertItems(["baz-instance"])
628     self.assertQuery("os", ["DOS"])
629     self.assertQuery("nostartup", ["1"])
630     self.assertEqual(self.rapi.CountPending(), 0)
631
632   def testReinstallInstanceNew(self):
633     self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_REINSTALL_REQV1]))
634     self.rapi.AddResponse("25689")
635     self.assertEqual(25689, self.client.ReinstallInstance("moo-instance",
636                                                           os="Debian",
637                                                           no_startup=True))
638     self.assertHandler(rlib2.R_2_instances_name_reinstall)
639     self.assertItems(["moo-instance"])
640     data = serializer.LoadJson(self.rapi.GetLastRequestData())
641     self.assertEqual(len(data), 2)
642     self.assertEqual(data["os"], "Debian")
643     self.assertEqual(data["start"], False)
644     self.assertEqual(self.rapi.CountPending(), 0)
645
646   def testReinstallInstanceWithOsparams1(self):
647     self.rapi.AddResponse(serializer.DumpJson([]))
648     self.assertRaises(client.GanetiApiError, self.client.ReinstallInstance,
649                       "doo-instance", osparams={"x": "y"})
650     self.assertEqual(self.rapi.CountPending(), 0)
651
652   def testReinstallInstanceWithOsparams2(self):
653     osparams = {
654       "Hello": "World",
655       "foo": "bar",
656       }
657     self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_REINSTALL_REQV1]))
658     self.rapi.AddResponse("1717")
659     self.assertEqual(1717, self.client.ReinstallInstance("zoo-instance",
660                                                          osparams=osparams))
661     self.assertHandler(rlib2.R_2_instances_name_reinstall)
662     self.assertItems(["zoo-instance"])
663     data = serializer.LoadJson(self.rapi.GetLastRequestData())
664     self.assertEqual(len(data), 2)
665     self.assertEqual(data["osparams"], osparams)
666     self.assertEqual(data["start"], True)
667     self.assertEqual(self.rapi.CountPending(), 0)
668
669   def testReplaceInstanceDisks(self):
670     self.rapi.AddResponse("999")
671     job_id = self.client.ReplaceInstanceDisks("instance-name",
672         disks=[0, 1], dry_run=True, iallocator="hail")
673     self.assertEqual(999, job_id)
674     self.assertHandler(rlib2.R_2_instances_name_replace_disks)
675     self.assertItems(["instance-name"])
676     self.assertQuery("disks", ["0,1"])
677     self.assertQuery("mode", ["replace_auto"])
678     self.assertQuery("iallocator", ["hail"])
679     self.assertDryRun()
680
681     self.rapi.AddResponse("1000")
682     job_id = self.client.ReplaceInstanceDisks("instance-bar",
683         disks=[1], mode="replace_on_secondary", remote_node="foo-node",
684         dry_run=True)
685     self.assertEqual(1000, job_id)
686     self.assertItems(["instance-bar"])
687     self.assertQuery("disks", ["1"])
688     self.assertQuery("remote_node", ["foo-node"])
689     self.assertDryRun()
690
691     self.rapi.AddResponse("5175")
692     self.assertEqual(5175, self.client.ReplaceInstanceDisks("instance-moo"))
693     self.assertItems(["instance-moo"])
694     self.assertQuery("disks", None)
695
696   def testPrepareExport(self):
697     self.rapi.AddResponse("8326")
698     self.assertEqual(8326, self.client.PrepareExport("inst1", "local"))
699     self.assertHandler(rlib2.R_2_instances_name_prepare_export)
700     self.assertItems(["inst1"])
701     self.assertQuery("mode", ["local"])
702
703   def testExportInstance(self):
704     self.rapi.AddResponse("19695")
705     job_id = self.client.ExportInstance("inst2", "local", "nodeX",
706                                         shutdown=True)
707     self.assertEqual(job_id, 19695)
708     self.assertHandler(rlib2.R_2_instances_name_export)
709     self.assertItems(["inst2"])
710
711     data = serializer.LoadJson(self.rapi.GetLastRequestData())
712     self.assertEqual(data["mode"], "local")
713     self.assertEqual(data["destination"], "nodeX")
714     self.assertEqual(data["shutdown"], True)
715
716   def testMigrateInstanceDefaults(self):
717     self.rapi.AddResponse("24873")
718     job_id = self.client.MigrateInstance("inst91")
719     self.assertEqual(job_id, 24873)
720     self.assertHandler(rlib2.R_2_instances_name_migrate)
721     self.assertItems(["inst91"])
722
723     data = serializer.LoadJson(self.rapi.GetLastRequestData())
724     self.assertFalse(data)
725
726   def testMigrateInstance(self):
727     for mode in constants.HT_MIGRATION_MODES:
728       for cleanup in [False, True]:
729         self.rapi.AddResponse("31910")
730         job_id = self.client.MigrateInstance("inst289", mode=mode,
731                                              cleanup=cleanup)
732         self.assertEqual(job_id, 31910)
733         self.assertHandler(rlib2.R_2_instances_name_migrate)
734         self.assertItems(["inst289"])
735
736         data = serializer.LoadJson(self.rapi.GetLastRequestData())
737         self.assertEqual(len(data), 2)
738         self.assertEqual(data["mode"], mode)
739         self.assertEqual(data["cleanup"], cleanup)
740
741   def testFailoverInstanceDefaults(self):
742     self.rapi.AddResponse("7639")
743     job_id = self.client.FailoverInstance("inst13579")
744     self.assertEqual(job_id, 7639)
745     self.assertHandler(rlib2.R_2_instances_name_failover)
746     self.assertItems(["inst13579"])
747
748     data = serializer.LoadJson(self.rapi.GetLastRequestData())
749     self.assertFalse(data)
750
751   def testFailoverInstance(self):
752     for iallocator in ["dumb", "hail"]:
753       for ignore_consistency in [False, True]:
754         for target_node in ["node-a", "node2"]:
755           self.rapi.AddResponse("19161")
756           job_id = \
757             self.client.FailoverInstance("inst251", iallocator=iallocator,
758                                          ignore_consistency=ignore_consistency,
759                                          target_node=target_node)
760           self.assertEqual(job_id, 19161)
761           self.assertHandler(rlib2.R_2_instances_name_failover)
762           self.assertItems(["inst251"])
763
764           data = serializer.LoadJson(self.rapi.GetLastRequestData())
765           self.assertEqual(len(data), 3)
766           self.assertEqual(data["iallocator"], iallocator)
767           self.assertEqual(data["ignore_consistency"], ignore_consistency)
768           self.assertEqual(data["target_node"], target_node)
769           self.assertEqual(self.rapi.CountPending(), 0)
770
771   def testRenameInstanceDefaults(self):
772     new_name = "newnametha7euqu"
773     self.rapi.AddResponse("8791")
774     job_id = self.client.RenameInstance("inst18821", new_name)
775     self.assertEqual(job_id, 8791)
776     self.assertHandler(rlib2.R_2_instances_name_rename)
777     self.assertItems(["inst18821"])
778
779     data = serializer.LoadJson(self.rapi.GetLastRequestData())
780     self.assertEqualValues(data, {"new_name": new_name, })
781
782   def testRenameInstance(self):
783     new_name = "new-name-yiux1iin"
784     for ip_check in [False, True]:
785       for name_check in [False, True]:
786         self.rapi.AddResponse("24776")
787         job_id = self.client.RenameInstance("inst20967", new_name,
788                                              ip_check=ip_check,
789                                              name_check=name_check)
790         self.assertEqual(job_id, 24776)
791         self.assertHandler(rlib2.R_2_instances_name_rename)
792         self.assertItems(["inst20967"])
793
794         data = serializer.LoadJson(self.rapi.GetLastRequestData())
795         self.assertEqual(len(data), 3)
796         self.assertEqual(data["new_name"], new_name)
797         self.assertEqual(data["ip_check"], ip_check)
798         self.assertEqual(data["name_check"], name_check)
799
800   def testGetJobs(self):
801     self.rapi.AddResponse('[ { "id": "123", "uri": "\\/2\\/jobs\\/123" },'
802                           '  { "id": "124", "uri": "\\/2\\/jobs\\/124" } ]')
803     self.assertEqual([123, 124], self.client.GetJobs())
804     self.assertHandler(rlib2.R_2_jobs)
805
806   def testGetJobStatus(self):
807     self.rapi.AddResponse("{\"foo\": \"bar\"}")
808     self.assertEqual({"foo": "bar"}, self.client.GetJobStatus(1234))
809     self.assertHandler(rlib2.R_2_jobs_id)
810     self.assertItems(["1234"])
811
812   def testWaitForJobChange(self):
813     fields = ["id", "summary"]
814     expected = {
815       "job_info": [123, "something"],
816       "log_entries": [],
817       }
818
819     self.rapi.AddResponse(serializer.DumpJson(expected))
820     result = self.client.WaitForJobChange(123, fields, [], -1)
821     self.assertEqualValues(expected, result)
822     self.assertHandler(rlib2.R_2_jobs_id_wait)
823     self.assertItems(["123"])
824
825   def testCancelJob(self):
826     self.rapi.AddResponse("[true, \"Job 123 will be canceled\"]")
827     self.assertEqual([True, "Job 123 will be canceled"],
828                      self.client.CancelJob(999, dry_run=True))
829     self.assertHandler(rlib2.R_2_jobs_id)
830     self.assertItems(["999"])
831     self.assertDryRun()
832
833   def testGetNodes(self):
834     self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
835                           " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
836     self.assertEqual(["node1", "node2"], self.client.GetNodes())
837     self.assertHandler(rlib2.R_2_nodes)
838
839     self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
840                           " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
841     self.assertEqual([{"id": "node1", "uri": "uri1"},
842                       {"id": "node2", "uri": "uri2"}],
843                      self.client.GetNodes(bulk=True))
844     self.assertHandler(rlib2.R_2_nodes)
845     self.assertBulk()
846
847   def testGetNode(self):
848     self.rapi.AddResponse("{}")
849     self.assertEqual({}, self.client.GetNode("node-foo"))
850     self.assertHandler(rlib2.R_2_nodes_name)
851     self.assertItems(["node-foo"])
852
853   def testEvacuateNode(self):
854     self.rapi.AddResponse(serializer.DumpJson([rlib2._NODE_EVAC_RES1]))
855     self.rapi.AddResponse("9876")
856     job_id = self.client.EvacuateNode("node-1", remote_node="node-2")
857     self.assertEqual(9876, job_id)
858     self.assertHandler(rlib2.R_2_nodes_name_evacuate)
859     self.assertItems(["node-1"])
860     self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()),
861                      { "remote_node": "node-2", })
862     self.assertEqual(self.rapi.CountPending(), 0)
863
864     self.rapi.AddResponse(serializer.DumpJson([rlib2._NODE_EVAC_RES1]))
865     self.rapi.AddResponse("8888")
866     job_id = self.client.EvacuateNode("node-3", iallocator="hail", dry_run=True)
867     self.assertEqual(8888, job_id)
868     self.assertItems(["node-3"])
869     self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()),
870                      { "iallocator": "hail", })
871     self.assertDryRun()
872
873     self.assertRaises(client.GanetiApiError,
874                       self.client.EvacuateNode,
875                       "node-4", iallocator="hail", remote_node="node-5")
876     self.assertEqual(self.rapi.CountPending(), 0)
877
878   def testEvacuateNodeOldResponse(self):
879     self.rapi.AddResponse(serializer.DumpJson([]))
880     self.assertRaises(client.GanetiApiError, self.client.EvacuateNode,
881                       "node-4", accept_old=False)
882     self.assertEqual(self.rapi.CountPending(), 0)
883
884     self.rapi.AddResponse(serializer.DumpJson([]))
885     self.assertRaises(client.GanetiApiError, self.client.EvacuateNode,
886                       "node-4", accept_old=True)
887     self.assertEqual(self.rapi.CountPending(), 0)
888
889     self.rapi.AddResponse(serializer.DumpJson([]))
890     self.assertRaises(client.GanetiApiError, self.client.EvacuateNode,
891                       "node-4", accept_old=True, primary=True)
892     self.assertEqual(self.rapi.CountPending(), 0)
893
894     self.rapi.AddResponse(serializer.DumpJson([]))
895     self.assertRaises(client.GanetiApiError, self.client.EvacuateNode,
896                       "node-4", accept_old=True, secondary=False)
897     self.assertEqual(self.rapi.CountPending(), 0)
898
899     for sec in [True, None]:
900       self.rapi.AddResponse(serializer.DumpJson([]))
901       self.rapi.AddResponse(serializer.DumpJson([["res", "foo"]]))
902       result = self.client.EvacuateNode("node-3", iallocator="hail",
903                                         dry_run=True, accept_old=True,
904                                         primary=False, secondary=sec)
905       self.assertEqual(result, [["res", "foo"]])
906       self.assertItems(["node-3"])
907       self.assertQuery("iallocator", ["hail"])
908       self.assertFalse(self.rapi.GetLastRequestData())
909       self.assertDryRun()
910       self.assertEqual(self.rapi.CountPending(), 0)
911
912   def testMigrateNode(self):
913     self.rapi.AddResponse(serializer.DumpJson([]))
914     self.rapi.AddResponse("1111")
915     self.assertEqual(1111, self.client.MigrateNode("node-a", dry_run=True))
916     self.assertHandler(rlib2.R_2_nodes_name_migrate)
917     self.assertItems(["node-a"])
918     self.assert_("mode" not in self.rapi.GetLastHandler().queryargs)
919     self.assertDryRun()
920     self.assertFalse(self.rapi.GetLastRequestData())
921
922     self.rapi.AddResponse(serializer.DumpJson([]))
923     self.rapi.AddResponse("1112")
924     self.assertEqual(1112, self.client.MigrateNode("node-a", dry_run=True,
925                                                    mode="live"))
926     self.assertHandler(rlib2.R_2_nodes_name_migrate)
927     self.assertItems(["node-a"])
928     self.assertQuery("mode", ["live"])
929     self.assertDryRun()
930     self.assertFalse(self.rapi.GetLastRequestData())
931
932     self.rapi.AddResponse(serializer.DumpJson([]))
933     self.assertRaises(client.GanetiApiError, self.client.MigrateNode,
934                       "node-c", target_node="foonode")
935     self.assertEqual(self.rapi.CountPending(), 0)
936
937   def testMigrateNodeBodyData(self):
938     self.rapi.AddResponse(serializer.DumpJson([rlib2._NODE_MIGRATE_REQV1]))
939     self.rapi.AddResponse("27539")
940     self.assertEqual(27539, self.client.MigrateNode("node-a", dry_run=False,
941                                                     mode="live"))
942     self.assertHandler(rlib2.R_2_nodes_name_migrate)
943     self.assertItems(["node-a"])
944     self.assertFalse(self.rapi.GetLastHandler().queryargs)
945     self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()),
946                      { "mode": "live", })
947
948     self.rapi.AddResponse(serializer.DumpJson([rlib2._NODE_MIGRATE_REQV1]))
949     self.rapi.AddResponse("14219")
950     self.assertEqual(14219, self.client.MigrateNode("node-x", dry_run=True,
951                                                     target_node="node9",
952                                                     iallocator="ial"))
953     self.assertHandler(rlib2.R_2_nodes_name_migrate)
954     self.assertItems(["node-x"])
955     self.assertDryRun()
956     self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()),
957                      { "target_node": "node9", "iallocator": "ial", })
958
959     self.assertEqual(self.rapi.CountPending(), 0)
960
961   def testGetNodeRole(self):
962     self.rapi.AddResponse("\"master\"")
963     self.assertEqual("master", self.client.GetNodeRole("node-a"))
964     self.assertHandler(rlib2.R_2_nodes_name_role)
965     self.assertItems(["node-a"])
966
967   def testSetNodeRole(self):
968     self.rapi.AddResponse("789")
969     self.assertEqual(789,
970         self.client.SetNodeRole("node-foo", "master-candidate", force=True))
971     self.assertHandler(rlib2.R_2_nodes_name_role)
972     self.assertItems(["node-foo"])
973     self.assertQuery("force", ["1"])
974     self.assertEqual("\"master-candidate\"", self.rapi.GetLastRequestData())
975
976   def testPowercycleNode(self):
977     self.rapi.AddResponse("23051")
978     self.assertEqual(23051,
979         self.client.PowercycleNode("node5468", force=True))
980     self.assertHandler(rlib2.R_2_nodes_name_powercycle)
981     self.assertItems(["node5468"])
982     self.assertQuery("force", ["1"])
983     self.assertFalse(self.rapi.GetLastRequestData())
984     self.assertEqual(self.rapi.CountPending(), 0)
985
986   def testGetNodeStorageUnits(self):
987     self.rapi.AddResponse("42")
988     self.assertEqual(42,
989         self.client.GetNodeStorageUnits("node-x", "lvm-pv", "fields"))
990     self.assertHandler(rlib2.R_2_nodes_name_storage)
991     self.assertItems(["node-x"])
992     self.assertQuery("storage_type", ["lvm-pv"])
993     self.assertQuery("output_fields", ["fields"])
994
995   def testModifyNodeStorageUnits(self):
996     self.rapi.AddResponse("14")
997     self.assertEqual(14,
998         self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda"))
999     self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
1000     self.assertItems(["node-z"])
1001     self.assertQuery("storage_type", ["lvm-pv"])
1002     self.assertQuery("name", ["hda"])
1003     self.assertQuery("allocatable", None)
1004
1005     for allocatable, query_allocatable in [(True, "1"), (False, "0")]:
1006       self.rapi.AddResponse("7205")
1007       job_id = self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda",
1008                                                   allocatable=allocatable)
1009       self.assertEqual(7205, job_id)
1010       self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
1011       self.assertItems(["node-z"])
1012       self.assertQuery("storage_type", ["lvm-pv"])
1013       self.assertQuery("name", ["hda"])
1014       self.assertQuery("allocatable", [query_allocatable])
1015
1016   def testRepairNodeStorageUnits(self):
1017     self.rapi.AddResponse("99")
1018     self.assertEqual(99, self.client.RepairNodeStorageUnits("node-z", "lvm-pv",
1019                                                             "hda"))
1020     self.assertHandler(rlib2.R_2_nodes_name_storage_repair)
1021     self.assertItems(["node-z"])
1022     self.assertQuery("storage_type", ["lvm-pv"])
1023     self.assertQuery("name", ["hda"])
1024
1025   def testGetNodeTags(self):
1026     self.rapi.AddResponse("[\"fry\", \"bender\"]")
1027     self.assertEqual(["fry", "bender"], self.client.GetNodeTags("node-k"))
1028     self.assertHandler(rlib2.R_2_nodes_name_tags)
1029     self.assertItems(["node-k"])
1030
1031   def testAddNodeTags(self):
1032     self.rapi.AddResponse("1234")
1033     self.assertEqual(1234,
1034         self.client.AddNodeTags("node-v", ["awesome"], dry_run=True))
1035     self.assertHandler(rlib2.R_2_nodes_name_tags)
1036     self.assertItems(["node-v"])
1037     self.assertDryRun()
1038     self.assertQuery("tag", ["awesome"])
1039
1040   def testDeleteNodeTags(self):
1041     self.rapi.AddResponse("16861")
1042     self.assertEqual(16861, self.client.DeleteNodeTags("node-w", ["awesome"],
1043                                                        dry_run=True))
1044     self.assertHandler(rlib2.R_2_nodes_name_tags)
1045     self.assertItems(["node-w"])
1046     self.assertDryRun()
1047     self.assertQuery("tag", ["awesome"])
1048
1049   def testGetGroups(self):
1050     groups = [{"name": "group1",
1051                "uri": "/2/groups/group1",
1052                },
1053               {"name": "group2",
1054                "uri": "/2/groups/group2",
1055                },
1056               ]
1057     self.rapi.AddResponse(serializer.DumpJson(groups))
1058     self.assertEqual(["group1", "group2"], self.client.GetGroups())
1059     self.assertHandler(rlib2.R_2_groups)
1060
1061   def testGetGroupsBulk(self):
1062     groups = [{"name": "group1",
1063                "uri": "/2/groups/group1",
1064                "node_cnt": 2,
1065                "node_list": ["gnt1.test",
1066                              "gnt2.test",
1067                              ],
1068                },
1069               {"name": "group2",
1070                "uri": "/2/groups/group2",
1071                "node_cnt": 1,
1072                "node_list": ["gnt3.test",
1073                              ],
1074                },
1075               ]
1076     self.rapi.AddResponse(serializer.DumpJson(groups))
1077
1078     self.assertEqual(groups, self.client.GetGroups(bulk=True))
1079     self.assertHandler(rlib2.R_2_groups)
1080     self.assertBulk()
1081
1082   def testGetGroup(self):
1083     group = {"ctime": None,
1084              "name": "default",
1085              }
1086     self.rapi.AddResponse(serializer.DumpJson(group))
1087     self.assertEqual({"ctime": None, "name": "default"},
1088                      self.client.GetGroup("default"))
1089     self.assertHandler(rlib2.R_2_groups_name)
1090     self.assertItems(["default"])
1091
1092   def testCreateGroup(self):
1093     self.rapi.AddResponse("12345")
1094     job_id = self.client.CreateGroup("newgroup", dry_run=True)
1095     self.assertEqual(job_id, 12345)
1096     self.assertHandler(rlib2.R_2_groups)
1097     self.assertDryRun()
1098
1099   def testDeleteGroup(self):
1100     self.rapi.AddResponse("12346")
1101     job_id = self.client.DeleteGroup("newgroup", dry_run=True)
1102     self.assertEqual(job_id, 12346)
1103     self.assertHandler(rlib2.R_2_groups_name)
1104     self.assertDryRun()
1105
1106   def testRenameGroup(self):
1107     self.rapi.AddResponse("12347")
1108     job_id = self.client.RenameGroup("oldname", "newname")
1109     self.assertEqual(job_id, 12347)
1110     self.assertHandler(rlib2.R_2_groups_name_rename)
1111
1112   def testModifyGroup(self):
1113     self.rapi.AddResponse("12348")
1114     job_id = self.client.ModifyGroup("mygroup", alloc_policy="foo")
1115     self.assertEqual(job_id, 12348)
1116     self.assertHandler(rlib2.R_2_groups_name_modify)
1117
1118   def testAssignGroupNodes(self):
1119     self.rapi.AddResponse("12349")
1120     job_id = self.client.AssignGroupNodes("mygroup", ["node1", "node2"],
1121                                           force=True, dry_run=True)
1122     self.assertEqual(job_id, 12349)
1123     self.assertHandler(rlib2.R_2_groups_name_assign_nodes)
1124     self.assertDryRun()
1125     self.assertUseForce()
1126
1127   def testModifyInstance(self):
1128     self.rapi.AddResponse("23681")
1129     job_id = self.client.ModifyInstance("inst7210", os_name="linux")
1130     self.assertEqual(job_id, 23681)
1131     self.assertItems(["inst7210"])
1132     self.assertHandler(rlib2.R_2_instances_name_modify)
1133     self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()),
1134                      { "os_name": "linux", })
1135
1136   def testModifyCluster(self):
1137     for mnh in [None, False, True]:
1138       self.rapi.AddResponse("14470")
1139       self.assertEqual(14470,
1140         self.client.ModifyCluster(maintain_node_health=mnh))
1141       self.assertHandler(rlib2.R_2_cluster_modify)
1142       self.assertItems([])
1143       data = serializer.LoadJson(self.rapi.GetLastRequestData())
1144       self.assertEqual(len(data), 1)
1145       self.assertEqual(data["maintain_node_health"], mnh)
1146       self.assertEqual(self.rapi.CountPending(), 0)
1147
1148   def testRedistributeConfig(self):
1149     self.rapi.AddResponse("3364")
1150     job_id = self.client.RedistributeConfig()
1151     self.assertEqual(job_id, 3364)
1152     self.assertItems([])
1153     self.assertHandler(rlib2.R_2_redist_config)
1154
1155   def testActivateInstanceDisks(self):
1156     self.rapi.AddResponse("23547")
1157     job_id = self.client.ActivateInstanceDisks("inst28204")
1158     self.assertEqual(job_id, 23547)
1159     self.assertItems(["inst28204"])
1160     self.assertHandler(rlib2.R_2_instances_name_activate_disks)
1161     self.assertFalse(self.rapi.GetLastHandler().queryargs)
1162
1163   def testActivateInstanceDisksIgnoreSize(self):
1164     self.rapi.AddResponse("11044")
1165     job_id = self.client.ActivateInstanceDisks("inst28204", ignore_size=True)
1166     self.assertEqual(job_id, 11044)
1167     self.assertItems(["inst28204"])
1168     self.assertHandler(rlib2.R_2_instances_name_activate_disks)
1169     self.assertQuery("ignore_size", ["1"])
1170
1171   def testDeactivateInstanceDisks(self):
1172     self.rapi.AddResponse("14591")
1173     job_id = self.client.DeactivateInstanceDisks("inst28234")
1174     self.assertEqual(job_id, 14591)
1175     self.assertItems(["inst28234"])
1176     self.assertHandler(rlib2.R_2_instances_name_deactivate_disks)
1177     self.assertFalse(self.rapi.GetLastHandler().queryargs)
1178
1179   def testRecreateInstanceDisks(self):
1180     self.rapi.AddResponse("13553")
1181     job_id = self.client.RecreateInstanceDisks("inst23153")
1182     self.assertEqual(job_id, 13553)
1183     self.assertItems(["inst23153"])
1184     self.assertHandler(rlib2.R_2_instances_name_recreate_disks)
1185     self.assertFalse(self.rapi.GetLastHandler().queryargs)
1186
1187   def testGetInstanceConsole(self):
1188     self.rapi.AddResponse("26876")
1189     job_id = self.client.GetInstanceConsole("inst21491")
1190     self.assertEqual(job_id, 26876)
1191     self.assertItems(["inst21491"])
1192     self.assertHandler(rlib2.R_2_instances_name_console)
1193     self.assertFalse(self.rapi.GetLastHandler().queryargs)
1194     self.assertFalse(self.rapi.GetLastRequestData())
1195
1196   def testGrowInstanceDisk(self):
1197     for idx, wait_for_sync in enumerate([None, False, True]):
1198       amount = 128 + (512 * idx)
1199       self.assertEqual(self.rapi.CountPending(), 0)
1200       self.rapi.AddResponse("30783")
1201       self.assertEqual(30783,
1202         self.client.GrowInstanceDisk("eze8ch", idx, amount,
1203                                      wait_for_sync=wait_for_sync))
1204       self.assertHandler(rlib2.R_2_instances_name_disk_grow)
1205       self.assertItems(["eze8ch", str(idx)])
1206       data = serializer.LoadJson(self.rapi.GetLastRequestData())
1207       if wait_for_sync is None:
1208         self.assertEqual(len(data), 1)
1209         self.assert_("wait_for_sync" not in data)
1210       else:
1211         self.assertEqual(len(data), 2)
1212         self.assertEqual(data["wait_for_sync"], wait_for_sync)
1213       self.assertEqual(data["amount"], amount)
1214       self.assertEqual(self.rapi.CountPending(), 0)
1215
1216   def testGetGroupTags(self):
1217     self.rapi.AddResponse("[]")
1218     self.assertEqual([], self.client.GetGroupTags("fooGroup"))
1219     self.assertHandler(rlib2.R_2_groups_name_tags)
1220     self.assertItems(["fooGroup"])
1221
1222   def testAddGroupTags(self):
1223     self.rapi.AddResponse("1234")
1224     self.assertEqual(1234,
1225         self.client.AddGroupTags("fooGroup", ["awesome"], dry_run=True))
1226     self.assertHandler(rlib2.R_2_groups_name_tags)
1227     self.assertItems(["fooGroup"])
1228     self.assertDryRun()
1229     self.assertQuery("tag", ["awesome"])
1230
1231   def testDeleteGroupTags(self):
1232     self.rapi.AddResponse("25826")
1233     self.assertEqual(25826, self.client.DeleteGroupTags("foo", ["awesome"],
1234                                                         dry_run=True))
1235     self.assertHandler(rlib2.R_2_groups_name_tags)
1236     self.assertItems(["foo"])
1237     self.assertDryRun()
1238     self.assertQuery("tag", ["awesome"])
1239
1240   def testQuery(self):
1241     for idx, what in enumerate(constants.QR_VIA_RAPI):
1242       for idx2, filter_ in enumerate([None, ["?", "name"]]):
1243         job_id = 11010 + (idx << 4) + (idx2 << 16)
1244         fields = sorted(query.ALL_FIELDS[what].keys())[:10]
1245
1246         self.rapi.AddResponse(str(job_id))
1247         self.assertEqual(self.client.Query(what, fields, filter_=filter_),
1248                          job_id)
1249         self.assertItems([what])
1250         self.assertHandler(rlib2.R_2_query)
1251         self.assertFalse(self.rapi.GetLastHandler().queryargs)
1252         data = serializer.LoadJson(self.rapi.GetLastRequestData())
1253         self.assertEqual(data["fields"], fields)
1254         if filter_ is None:
1255           self.assertTrue("filter" not in data)
1256         else:
1257           self.assertEqual(data["filter"], filter_)
1258         self.assertEqual(self.rapi.CountPending(), 0)
1259
1260   def testQueryFields(self):
1261     exp_result = objects.QueryFieldsResponse(fields=[
1262       objects.QueryFieldDefinition(name="pnode", title="PNode",
1263                                    kind=constants.QFT_NUMBER),
1264       objects.QueryFieldDefinition(name="other", title="Other",
1265                                    kind=constants.QFT_BOOL),
1266       ])
1267
1268     for what in constants.QR_VIA_RAPI:
1269       for fields in [None, ["name", "_unknown_"], ["&", "?|"]]:
1270         self.rapi.AddResponse(serializer.DumpJson(exp_result.ToDict()))
1271         result = self.client.QueryFields(what, fields=fields)
1272         self.assertItems([what])
1273         self.assertHandler(rlib2.R_2_query_fields)
1274         self.assertFalse(self.rapi.GetLastRequestData())
1275
1276         queryargs = self.rapi.GetLastHandler().queryargs
1277         if fields is None:
1278           self.assertFalse(queryargs)
1279         else:
1280           self.assertEqual(queryargs, {
1281             "fields": [",".join(fields)],
1282             })
1283
1284         self.assertEqual(objects.QueryFieldsResponse.FromDict(result).ToDict(),
1285                          exp_result.ToDict())
1286
1287         self.assertEqual(self.rapi.CountPending(), 0)
1288
1289   def testWaitForJobCompletionNoChange(self):
1290     resp = serializer.DumpJson({
1291       "status": constants.JOB_STATUS_WAITING,
1292       })
1293
1294     for retries in [1, 5, 25]:
1295       for _ in range(retries):
1296         self.rapi.AddResponse(resp)
1297
1298       self.assertFalse(self.client.WaitForJobCompletion(22789, period=None,
1299                                                         retries=retries))
1300       self.assertHandler(rlib2.R_2_jobs_id)
1301       self.assertItems(["22789"])
1302
1303       self.assertEqual(self.rapi.CountPending(), 0)
1304
1305   def testWaitForJobCompletionAlreadyFinished(self):
1306     self.rapi.AddResponse(serializer.DumpJson({
1307       "status": constants.JOB_STATUS_SUCCESS,
1308       }))
1309
1310     self.assertTrue(self.client.WaitForJobCompletion(22793, period=None,
1311                                                      retries=1))
1312     self.assertHandler(rlib2.R_2_jobs_id)
1313     self.assertItems(["22793"])
1314
1315     self.assertEqual(self.rapi.CountPending(), 0)
1316
1317   def testWaitForJobCompletionEmptyResponse(self):
1318     self.rapi.AddResponse("{}")
1319     self.assertFalse(self.client.WaitForJobCompletion(22793, period=None,
1320                                                      retries=10))
1321     self.assertHandler(rlib2.R_2_jobs_id)
1322     self.assertItems(["22793"])
1323
1324     self.assertEqual(self.rapi.CountPending(), 0)
1325
1326   def testWaitForJobCompletionOutOfRetries(self):
1327     for retries in [3, 10, 21]:
1328       for _ in range(retries):
1329         self.rapi.AddResponse(serializer.DumpJson({
1330           "status": constants.JOB_STATUS_RUNNING,
1331           }))
1332
1333       self.assertFalse(self.client.WaitForJobCompletion(30948, period=None,
1334                                                         retries=retries - 1))
1335       self.assertHandler(rlib2.R_2_jobs_id)
1336       self.assertItems(["30948"])
1337
1338       self.assertEqual(self.rapi.CountPending(), 1)
1339       self.rapi.ResetResponses()
1340
1341   def testWaitForJobCompletionSuccessAndFailure(self):
1342     for retries in [1, 4, 13]:
1343       for (success, end_status) in [(False, constants.JOB_STATUS_ERROR),
1344                                     (True, constants.JOB_STATUS_SUCCESS)]:
1345         for _ in range(retries):
1346           self.rapi.AddResponse(serializer.DumpJson({
1347             "status": constants.JOB_STATUS_RUNNING,
1348             }))
1349
1350         self.rapi.AddResponse(serializer.DumpJson({
1351           "status": end_status,
1352           }))
1353
1354         result = self.client.WaitForJobCompletion(3187, period=None,
1355                                                   retries=retries + 1)
1356         self.assertEqual(result, success)
1357         self.assertHandler(rlib2.R_2_jobs_id)
1358         self.assertItems(["3187"])
1359
1360         self.assertEqual(self.rapi.CountPending(), 0)
1361
1362
1363 class RapiTestRunner(unittest.TextTestRunner):
1364   def run(self, *args):
1365     global _used_handlers
1366     assert _used_handlers is None
1367
1368     _used_handlers = set()
1369     try:
1370       # Run actual tests
1371       result = unittest.TextTestRunner.run(self, *args)
1372
1373       diff = (set(connector.CONNECTOR.values()) - _used_handlers -
1374              _KNOWN_UNUSED)
1375       if diff:
1376         raise AssertionError("The following RAPI resources were not used by the"
1377                              " RAPI client: %r" % utils.CommaJoin(diff))
1378     finally:
1379       # Reset global variable
1380       _used_handlers = None
1381
1382     return result
1383
1384
1385 if __name__ == '__main__':
1386   client.UsesRapiClient(testutils.GanetiTestProgram)(testRunner=RapiTestRunner)