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