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