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