Document DRBD dynamic resync params in man pages
[ganeti-local] / test / ganeti.rapi.client_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2010, 2011 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     # Node evacuation
169     self.assertEqual(client.NODE_EVAC_PRI, constants.NODE_EVAC_PRI)
170     self.assertEqual(client.NODE_EVAC_SEC, constants.NODE_EVAC_SEC)
171     self.assertEqual(client.NODE_EVAC_ALL, constants.NODE_EVAC_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.NODE_EVAC_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 testPowercycleNode(self):
976     self.rapi.AddResponse("23051")
977     self.assertEqual(23051,
978         self.client.PowercycleNode("node5468", force=True))
979     self.assertHandler(rlib2.R_2_nodes_name_powercycle)
980     self.assertItems(["node5468"])
981     self.assertQuery("force", ["1"])
982     self.assertFalse(self.rapi.GetLastRequestData())
983     self.assertEqual(self.rapi.CountPending(), 0)
984
985   def testModifyNode(self):
986     self.rapi.AddResponse("3783")
987     job_id = self.client.ModifyNode("node16979.example.com", drained=True)
988     self.assertEqual(job_id, 3783)
989     self.assertHandler(rlib2.R_2_nodes_name_modify)
990     self.assertItems(["node16979.example.com"])
991     self.assertEqual(self.rapi.CountPending(), 0)
992
993   def testGetNodeStorageUnits(self):
994     self.rapi.AddResponse("42")
995     self.assertEqual(42,
996         self.client.GetNodeStorageUnits("node-x", "lvm-pv", "fields"))
997     self.assertHandler(rlib2.R_2_nodes_name_storage)
998     self.assertItems(["node-x"])
999     self.assertQuery("storage_type", ["lvm-pv"])
1000     self.assertQuery("output_fields", ["fields"])
1001
1002   def testModifyNodeStorageUnits(self):
1003     self.rapi.AddResponse("14")
1004     self.assertEqual(14,
1005         self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda"))
1006     self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
1007     self.assertItems(["node-z"])
1008     self.assertQuery("storage_type", ["lvm-pv"])
1009     self.assertQuery("name", ["hda"])
1010     self.assertQuery("allocatable", None)
1011
1012     for allocatable, query_allocatable in [(True, "1"), (False, "0")]:
1013       self.rapi.AddResponse("7205")
1014       job_id = self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda",
1015                                                   allocatable=allocatable)
1016       self.assertEqual(7205, job_id)
1017       self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
1018       self.assertItems(["node-z"])
1019       self.assertQuery("storage_type", ["lvm-pv"])
1020       self.assertQuery("name", ["hda"])
1021       self.assertQuery("allocatable", [query_allocatable])
1022
1023   def testRepairNodeStorageUnits(self):
1024     self.rapi.AddResponse("99")
1025     self.assertEqual(99, self.client.RepairNodeStorageUnits("node-z", "lvm-pv",
1026                                                             "hda"))
1027     self.assertHandler(rlib2.R_2_nodes_name_storage_repair)
1028     self.assertItems(["node-z"])
1029     self.assertQuery("storage_type", ["lvm-pv"])
1030     self.assertQuery("name", ["hda"])
1031
1032   def testGetNodeTags(self):
1033     self.rapi.AddResponse("[\"fry\", \"bender\"]")
1034     self.assertEqual(["fry", "bender"], self.client.GetNodeTags("node-k"))
1035     self.assertHandler(rlib2.R_2_nodes_name_tags)
1036     self.assertItems(["node-k"])
1037
1038   def testAddNodeTags(self):
1039     self.rapi.AddResponse("1234")
1040     self.assertEqual(1234,
1041         self.client.AddNodeTags("node-v", ["awesome"], dry_run=True))
1042     self.assertHandler(rlib2.R_2_nodes_name_tags)
1043     self.assertItems(["node-v"])
1044     self.assertDryRun()
1045     self.assertQuery("tag", ["awesome"])
1046
1047   def testDeleteNodeTags(self):
1048     self.rapi.AddResponse("16861")
1049     self.assertEqual(16861, self.client.DeleteNodeTags("node-w", ["awesome"],
1050                                                        dry_run=True))
1051     self.assertHandler(rlib2.R_2_nodes_name_tags)
1052     self.assertItems(["node-w"])
1053     self.assertDryRun()
1054     self.assertQuery("tag", ["awesome"])
1055
1056   def testGetGroups(self):
1057     groups = [{"name": "group1",
1058                "uri": "/2/groups/group1",
1059                },
1060               {"name": "group2",
1061                "uri": "/2/groups/group2",
1062                },
1063               ]
1064     self.rapi.AddResponse(serializer.DumpJson(groups))
1065     self.assertEqual(["group1", "group2"], self.client.GetGroups())
1066     self.assertHandler(rlib2.R_2_groups)
1067
1068   def testGetGroupsBulk(self):
1069     groups = [{"name": "group1",
1070                "uri": "/2/groups/group1",
1071                "node_cnt": 2,
1072                "node_list": ["gnt1.test",
1073                              "gnt2.test",
1074                              ],
1075                },
1076               {"name": "group2",
1077                "uri": "/2/groups/group2",
1078                "node_cnt": 1,
1079                "node_list": ["gnt3.test",
1080                              ],
1081                },
1082               ]
1083     self.rapi.AddResponse(serializer.DumpJson(groups))
1084
1085     self.assertEqual(groups, self.client.GetGroups(bulk=True))
1086     self.assertHandler(rlib2.R_2_groups)
1087     self.assertBulk()
1088
1089   def testGetGroup(self):
1090     group = {"ctime": None,
1091              "name": "default",
1092              }
1093     self.rapi.AddResponse(serializer.DumpJson(group))
1094     self.assertEqual({"ctime": None, "name": "default"},
1095                      self.client.GetGroup("default"))
1096     self.assertHandler(rlib2.R_2_groups_name)
1097     self.assertItems(["default"])
1098
1099   def testCreateGroup(self):
1100     self.rapi.AddResponse("12345")
1101     job_id = self.client.CreateGroup("newgroup", dry_run=True)
1102     self.assertEqual(job_id, 12345)
1103     self.assertHandler(rlib2.R_2_groups)
1104     self.assertDryRun()
1105
1106   def testDeleteGroup(self):
1107     self.rapi.AddResponse("12346")
1108     job_id = self.client.DeleteGroup("newgroup", dry_run=True)
1109     self.assertEqual(job_id, 12346)
1110     self.assertHandler(rlib2.R_2_groups_name)
1111     self.assertDryRun()
1112
1113   def testRenameGroup(self):
1114     self.rapi.AddResponse("12347")
1115     job_id = self.client.RenameGroup("oldname", "newname")
1116     self.assertEqual(job_id, 12347)
1117     self.assertHandler(rlib2.R_2_groups_name_rename)
1118
1119   def testModifyGroup(self):
1120     self.rapi.AddResponse("12348")
1121     job_id = self.client.ModifyGroup("mygroup", alloc_policy="foo")
1122     self.assertEqual(job_id, 12348)
1123     self.assertHandler(rlib2.R_2_groups_name_modify)
1124
1125   def testAssignGroupNodes(self):
1126     self.rapi.AddResponse("12349")
1127     job_id = self.client.AssignGroupNodes("mygroup", ["node1", "node2"],
1128                                           force=True, dry_run=True)
1129     self.assertEqual(job_id, 12349)
1130     self.assertHandler(rlib2.R_2_groups_name_assign_nodes)
1131     self.assertDryRun()
1132     self.assertUseForce()
1133
1134   def testModifyInstance(self):
1135     self.rapi.AddResponse("23681")
1136     job_id = self.client.ModifyInstance("inst7210", os_name="linux")
1137     self.assertEqual(job_id, 23681)
1138     self.assertItems(["inst7210"])
1139     self.assertHandler(rlib2.R_2_instances_name_modify)
1140     self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()),
1141                      { "os_name": "linux", })
1142
1143   def testModifyCluster(self):
1144     for mnh in [None, False, True]:
1145       self.rapi.AddResponse("14470")
1146       self.assertEqual(14470,
1147         self.client.ModifyCluster(maintain_node_health=mnh))
1148       self.assertHandler(rlib2.R_2_cluster_modify)
1149       self.assertItems([])
1150       data = serializer.LoadJson(self.rapi.GetLastRequestData())
1151       self.assertEqual(len(data), 1)
1152       self.assertEqual(data["maintain_node_health"], mnh)
1153       self.assertEqual(self.rapi.CountPending(), 0)
1154
1155   def testRedistributeConfig(self):
1156     self.rapi.AddResponse("3364")
1157     job_id = self.client.RedistributeConfig()
1158     self.assertEqual(job_id, 3364)
1159     self.assertItems([])
1160     self.assertHandler(rlib2.R_2_redist_config)
1161
1162   def testActivateInstanceDisks(self):
1163     self.rapi.AddResponse("23547")
1164     job_id = self.client.ActivateInstanceDisks("inst28204")
1165     self.assertEqual(job_id, 23547)
1166     self.assertItems(["inst28204"])
1167     self.assertHandler(rlib2.R_2_instances_name_activate_disks)
1168     self.assertFalse(self.rapi.GetLastHandler().queryargs)
1169
1170   def testActivateInstanceDisksIgnoreSize(self):
1171     self.rapi.AddResponse("11044")
1172     job_id = self.client.ActivateInstanceDisks("inst28204", ignore_size=True)
1173     self.assertEqual(job_id, 11044)
1174     self.assertItems(["inst28204"])
1175     self.assertHandler(rlib2.R_2_instances_name_activate_disks)
1176     self.assertQuery("ignore_size", ["1"])
1177
1178   def testDeactivateInstanceDisks(self):
1179     self.rapi.AddResponse("14591")
1180     job_id = self.client.DeactivateInstanceDisks("inst28234")
1181     self.assertEqual(job_id, 14591)
1182     self.assertItems(["inst28234"])
1183     self.assertHandler(rlib2.R_2_instances_name_deactivate_disks)
1184     self.assertFalse(self.rapi.GetLastHandler().queryargs)
1185
1186   def testRecreateInstanceDisks(self):
1187     self.rapi.AddResponse("13553")
1188     job_id = self.client.RecreateInstanceDisks("inst23153")
1189     self.assertEqual(job_id, 13553)
1190     self.assertItems(["inst23153"])
1191     self.assertHandler(rlib2.R_2_instances_name_recreate_disks)
1192     self.assertFalse(self.rapi.GetLastHandler().queryargs)
1193
1194   def testGetInstanceConsole(self):
1195     self.rapi.AddResponse("26876")
1196     job_id = self.client.GetInstanceConsole("inst21491")
1197     self.assertEqual(job_id, 26876)
1198     self.assertItems(["inst21491"])
1199     self.assertHandler(rlib2.R_2_instances_name_console)
1200     self.assertFalse(self.rapi.GetLastHandler().queryargs)
1201     self.assertFalse(self.rapi.GetLastRequestData())
1202
1203   def testGrowInstanceDisk(self):
1204     for idx, wait_for_sync in enumerate([None, False, True]):
1205       amount = 128 + (512 * idx)
1206       self.assertEqual(self.rapi.CountPending(), 0)
1207       self.rapi.AddResponse("30783")
1208       self.assertEqual(30783,
1209         self.client.GrowInstanceDisk("eze8ch", idx, amount,
1210                                      wait_for_sync=wait_for_sync))
1211       self.assertHandler(rlib2.R_2_instances_name_disk_grow)
1212       self.assertItems(["eze8ch", str(idx)])
1213       data = serializer.LoadJson(self.rapi.GetLastRequestData())
1214       if wait_for_sync is None:
1215         self.assertEqual(len(data), 1)
1216         self.assert_("wait_for_sync" not in data)
1217       else:
1218         self.assertEqual(len(data), 2)
1219         self.assertEqual(data["wait_for_sync"], wait_for_sync)
1220       self.assertEqual(data["amount"], amount)
1221       self.assertEqual(self.rapi.CountPending(), 0)
1222
1223   def testGetGroupTags(self):
1224     self.rapi.AddResponse("[]")
1225     self.assertEqual([], self.client.GetGroupTags("fooGroup"))
1226     self.assertHandler(rlib2.R_2_groups_name_tags)
1227     self.assertItems(["fooGroup"])
1228
1229   def testAddGroupTags(self):
1230     self.rapi.AddResponse("1234")
1231     self.assertEqual(1234,
1232         self.client.AddGroupTags("fooGroup", ["awesome"], dry_run=True))
1233     self.assertHandler(rlib2.R_2_groups_name_tags)
1234     self.assertItems(["fooGroup"])
1235     self.assertDryRun()
1236     self.assertQuery("tag", ["awesome"])
1237
1238   def testDeleteGroupTags(self):
1239     self.rapi.AddResponse("25826")
1240     self.assertEqual(25826, self.client.DeleteGroupTags("foo", ["awesome"],
1241                                                         dry_run=True))
1242     self.assertHandler(rlib2.R_2_groups_name_tags)
1243     self.assertItems(["foo"])
1244     self.assertDryRun()
1245     self.assertQuery("tag", ["awesome"])
1246
1247   def testQuery(self):
1248     for idx, what in enumerate(constants.QR_VIA_RAPI):
1249       for idx2, qfilter in enumerate([None, ["?", "name"]]):
1250         job_id = 11010 + (idx << 4) + (idx2 << 16)
1251         fields = sorted(query.ALL_FIELDS[what].keys())[:10]
1252
1253         self.rapi.AddResponse(str(job_id))
1254         self.assertEqual(self.client.Query(what, fields, qfilter=qfilter),
1255                          job_id)
1256         self.assertItems([what])
1257         self.assertHandler(rlib2.R_2_query)
1258         self.assertFalse(self.rapi.GetLastHandler().queryargs)
1259         data = serializer.LoadJson(self.rapi.GetLastRequestData())
1260         self.assertEqual(data["fields"], fields)
1261         if qfilter is None:
1262           self.assertTrue("qfilter" not in data)
1263         else:
1264           self.assertEqual(data["qfilter"], qfilter)
1265         self.assertEqual(self.rapi.CountPending(), 0)
1266
1267   def testQueryFields(self):
1268     exp_result = objects.QueryFieldsResponse(fields=[
1269       objects.QueryFieldDefinition(name="pnode", title="PNode",
1270                                    kind=constants.QFT_NUMBER),
1271       objects.QueryFieldDefinition(name="other", title="Other",
1272                                    kind=constants.QFT_BOOL),
1273       ])
1274
1275     for what in constants.QR_VIA_RAPI:
1276       for fields in [None, ["name", "_unknown_"], ["&", "?|"]]:
1277         self.rapi.AddResponse(serializer.DumpJson(exp_result.ToDict()))
1278         result = self.client.QueryFields(what, fields=fields)
1279         self.assertItems([what])
1280         self.assertHandler(rlib2.R_2_query_fields)
1281         self.assertFalse(self.rapi.GetLastRequestData())
1282
1283         queryargs = self.rapi.GetLastHandler().queryargs
1284         if fields is None:
1285           self.assertFalse(queryargs)
1286         else:
1287           self.assertEqual(queryargs, {
1288             "fields": [",".join(fields)],
1289             })
1290
1291         self.assertEqual(objects.QueryFieldsResponse.FromDict(result).ToDict(),
1292                          exp_result.ToDict())
1293
1294         self.assertEqual(self.rapi.CountPending(), 0)
1295
1296   def testWaitForJobCompletionNoChange(self):
1297     resp = serializer.DumpJson({
1298       "status": constants.JOB_STATUS_WAITING,
1299       })
1300
1301     for retries in [1, 5, 25]:
1302       for _ in range(retries):
1303         self.rapi.AddResponse(resp)
1304
1305       self.assertFalse(self.client.WaitForJobCompletion(22789, period=None,
1306                                                         retries=retries))
1307       self.assertHandler(rlib2.R_2_jobs_id)
1308       self.assertItems(["22789"])
1309
1310       self.assertEqual(self.rapi.CountPending(), 0)
1311
1312   def testWaitForJobCompletionAlreadyFinished(self):
1313     self.rapi.AddResponse(serializer.DumpJson({
1314       "status": constants.JOB_STATUS_SUCCESS,
1315       }))
1316
1317     self.assertTrue(self.client.WaitForJobCompletion(22793, period=None,
1318                                                      retries=1))
1319     self.assertHandler(rlib2.R_2_jobs_id)
1320     self.assertItems(["22793"])
1321
1322     self.assertEqual(self.rapi.CountPending(), 0)
1323
1324   def testWaitForJobCompletionEmptyResponse(self):
1325     self.rapi.AddResponse("{}")
1326     self.assertFalse(self.client.WaitForJobCompletion(22793, period=None,
1327                                                      retries=10))
1328     self.assertHandler(rlib2.R_2_jobs_id)
1329     self.assertItems(["22793"])
1330
1331     self.assertEqual(self.rapi.CountPending(), 0)
1332
1333   def testWaitForJobCompletionOutOfRetries(self):
1334     for retries in [3, 10, 21]:
1335       for _ in range(retries):
1336         self.rapi.AddResponse(serializer.DumpJson({
1337           "status": constants.JOB_STATUS_RUNNING,
1338           }))
1339
1340       self.assertFalse(self.client.WaitForJobCompletion(30948, period=None,
1341                                                         retries=retries - 1))
1342       self.assertHandler(rlib2.R_2_jobs_id)
1343       self.assertItems(["30948"])
1344
1345       self.assertEqual(self.rapi.CountPending(), 1)
1346       self.rapi.ResetResponses()
1347
1348   def testWaitForJobCompletionSuccessAndFailure(self):
1349     for retries in [1, 4, 13]:
1350       for (success, end_status) in [(False, constants.JOB_STATUS_ERROR),
1351                                     (True, constants.JOB_STATUS_SUCCESS)]:
1352         for _ in range(retries):
1353           self.rapi.AddResponse(serializer.DumpJson({
1354             "status": constants.JOB_STATUS_RUNNING,
1355             }))
1356
1357         self.rapi.AddResponse(serializer.DumpJson({
1358           "status": end_status,
1359           }))
1360
1361         result = self.client.WaitForJobCompletion(3187, period=None,
1362                                                   retries=retries + 1)
1363         self.assertEqual(result, success)
1364         self.assertHandler(rlib2.R_2_jobs_id)
1365         self.assertItems(["3187"])
1366
1367         self.assertEqual(self.rapi.CountPending(), 0)
1368
1369
1370 class RapiTestRunner(unittest.TextTestRunner):
1371   def run(self, *args):
1372     global _used_handlers
1373     assert _used_handlers is None
1374
1375     _used_handlers = set()
1376     try:
1377       # Run actual tests
1378       result = unittest.TextTestRunner.run(self, *args)
1379
1380       diff = (set(connector.CONNECTOR.values()) - _used_handlers -
1381              _KNOWN_UNUSED)
1382       if diff:
1383         raise AssertionError("The following RAPI resources were not used by the"
1384                              " RAPI client: %r" % utils.CommaJoin(diff))
1385     finally:
1386       # Reset global variable
1387       _used_handlers = None
1388
1389     return result
1390
1391
1392 if __name__ == '__main__':
1393   client.UsesRapiClient(testutils.GanetiTestProgram)(testRunner=RapiTestRunner)