RAPI client: De-/activating instance disks
[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 assertUseForce(self):
355     self.assertTrue(self.rapi.GetLastHandler().useForce())
356
357   def testEncodeQuery(self):
358     query = [
359       ("a", None),
360       ("b", 1),
361       ("c", 2),
362       ("d", "Foo"),
363       ("e", True),
364       ]
365
366     expected = [
367       ("a", ""),
368       ("b", 1),
369       ("c", 2),
370       ("d", "Foo"),
371       ("e", 1),
372       ]
373
374     self.assertEqualValues(self.client._EncodeQuery(query),
375                            expected)
376
377     # invalid types
378     for i in [[1, 2, 3], {"moo": "boo"}, (1, 2, 3)]:
379       self.assertRaises(ValueError, self.client._EncodeQuery, [("x", i)])
380
381   def testCurlSettings(self):
382     self.rapi.AddResponse("2")
383     self.assertEqual(2, self.client.GetVersion())
384     self.assertHandler(rlib2.R_version)
385
386     # Signals should be disabled by default
387     self.assert_(self.curl.getopt(pycurl.NOSIGNAL))
388
389     # No auth and no proxy
390     self.assertFalse(self.curl.getopt(pycurl.USERPWD))
391     self.assert_(self.curl.getopt(pycurl.PROXY) is None)
392
393     # Content-type is required for requests
394     headers = self.curl.getopt(pycurl.HTTPHEADER)
395     self.assert_("Content-type: application/json" in headers)
396
397   def testHttpError(self):
398     self.rapi.AddResponse(None, code=404)
399     try:
400       self.client.GetJobStatus(15140)
401     except client.GanetiApiError, err:
402       self.assertEqual(err.code, 404)
403     else:
404       self.fail("Didn't raise exception")
405
406   def testGetVersion(self):
407     self.rapi.AddResponse("2")
408     self.assertEqual(2, self.client.GetVersion())
409     self.assertHandler(rlib2.R_version)
410
411   def testGetFeatures(self):
412     for features in [[], ["foo", "bar", "baz"]]:
413       self.rapi.AddResponse(serializer.DumpJson(features))
414       self.assertEqual(features, self.client.GetFeatures())
415       self.assertHandler(rlib2.R_2_features)
416
417   def testGetFeaturesNotFound(self):
418     self.rapi.AddResponse(None, code=404)
419     self.assertEqual([], self.client.GetFeatures())
420
421   def testGetOperatingSystems(self):
422     self.rapi.AddResponse("[\"beos\"]")
423     self.assertEqual(["beos"], self.client.GetOperatingSystems())
424     self.assertHandler(rlib2.R_2_os)
425
426   def testGetClusterTags(self):
427     self.rapi.AddResponse("[\"tag\"]")
428     self.assertEqual(["tag"], self.client.GetClusterTags())
429     self.assertHandler(rlib2.R_2_tags)
430
431   def testAddClusterTags(self):
432     self.rapi.AddResponse("1234")
433     self.assertEqual(1234,
434         self.client.AddClusterTags(["awesome"], dry_run=True))
435     self.assertHandler(rlib2.R_2_tags)
436     self.assertDryRun()
437     self.assertQuery("tag", ["awesome"])
438
439   def testDeleteClusterTags(self):
440     self.rapi.AddResponse("5107")
441     self.assertEqual(5107, self.client.DeleteClusterTags(["awesome"],
442                                                          dry_run=True))
443     self.assertHandler(rlib2.R_2_tags)
444     self.assertDryRun()
445     self.assertQuery("tag", ["awesome"])
446
447   def testGetInfo(self):
448     self.rapi.AddResponse("{}")
449     self.assertEqual({}, self.client.GetInfo())
450     self.assertHandler(rlib2.R_2_info)
451
452   def testGetInstances(self):
453     self.rapi.AddResponse("[]")
454     self.assertEqual([], self.client.GetInstances(bulk=True))
455     self.assertHandler(rlib2.R_2_instances)
456     self.assertBulk()
457
458   def testGetInstance(self):
459     self.rapi.AddResponse("[]")
460     self.assertEqual([], self.client.GetInstance("instance"))
461     self.assertHandler(rlib2.R_2_instances_name)
462     self.assertItems(["instance"])
463
464   def testGetInstanceInfo(self):
465     self.rapi.AddResponse("21291")
466     self.assertEqual(21291, self.client.GetInstanceInfo("inst3"))
467     self.assertHandler(rlib2.R_2_instances_name_info)
468     self.assertItems(["inst3"])
469     self.assertQuery("static", None)
470
471     self.rapi.AddResponse("3428")
472     self.assertEqual(3428, self.client.GetInstanceInfo("inst31", static=False))
473     self.assertHandler(rlib2.R_2_instances_name_info)
474     self.assertItems(["inst31"])
475     self.assertQuery("static", ["0"])
476
477     self.rapi.AddResponse("15665")
478     self.assertEqual(15665, self.client.GetInstanceInfo("inst32", static=True))
479     self.assertHandler(rlib2.R_2_instances_name_info)
480     self.assertItems(["inst32"])
481     self.assertQuery("static", ["1"])
482
483   def testCreateInstanceOldVersion(self):
484     # No NICs
485     self.rapi.AddResponse(None, code=404)
486     self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
487                       "create", "inst1.example.com", "plain", [], [])
488     self.assertEqual(self.rapi.CountPending(), 0)
489
490     # More than one NIC
491     self.rapi.AddResponse(None, code=404)
492     self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
493                       "create", "inst1.example.com", "plain", [],
494                       [{}, {}, {}])
495     self.assertEqual(self.rapi.CountPending(), 0)
496
497     # Unsupported NIC fields
498     self.rapi.AddResponse(None, code=404)
499     self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
500                       "create", "inst1.example.com", "plain", [],
501                       [{"x": True, "y": False}])
502     self.assertEqual(self.rapi.CountPending(), 0)
503
504     # Unsupported disk fields
505     self.rapi.AddResponse(None, code=404)
506     self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
507                       "create", "inst1.example.com", "plain",
508                       [{}, {"moo": "foo",}], [{}])
509     self.assertEqual(self.rapi.CountPending(), 0)
510
511     # Unsupported fields
512     self.rapi.AddResponse(None, code=404)
513     self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
514                       "create", "inst1.example.com", "plain", [], [{}],
515                       hello_world=123)
516     self.assertEqual(self.rapi.CountPending(), 0)
517
518     self.rapi.AddResponse(None, code=404)
519     self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
520                       "create", "inst1.example.com", "plain", [], [{}],
521                       memory=128)
522     self.assertEqual(self.rapi.CountPending(), 0)
523
524     # Normal creation
525     testnics = [
526       [{}],
527       [{ "mac": constants.VALUE_AUTO, }],
528       [{ "ip": "192.0.2.99", "mode": constants.NIC_MODE_ROUTED, }],
529       ]
530
531     testdisks = [
532       [],
533       [{ "size": 128, }],
534       [{ "size": 321, }, { "size": 4096, }],
535       ]
536
537     for idx, nics in enumerate(testnics):
538       for disks in testdisks:
539         beparams = {
540           constants.BE_MEMORY: 512,
541           constants.BE_AUTO_BALANCE: False,
542           }
543         hvparams = {
544           constants.HV_MIGRATION_PORT: 9876,
545           constants.HV_VNC_TLS: True,
546           }
547
548         self.rapi.AddResponse(None, code=404)
549         self.rapi.AddResponse(serializer.DumpJson(3122617 + idx))
550         job_id = self.client.CreateInstance("create", "inst1.example.com",
551                                             "plain", disks, nics,
552                                             pnode="node99", dry_run=True,
553                                             hvparams=hvparams,
554                                             beparams=beparams)
555         self.assertEqual(job_id, 3122617 + idx)
556         self.assertHandler(rlib2.R_2_instances)
557         self.assertDryRun()
558         self.assertEqual(self.rapi.CountPending(), 0)
559
560         data = serializer.LoadJson(self.rapi.GetLastRequestData())
561         self.assertEqual(data["name"], "inst1.example.com")
562         self.assertEqual(data["disk_template"], "plain")
563         self.assertEqual(data["pnode"], "node99")
564         self.assertEqual(data[constants.BE_MEMORY], 512)
565         self.assertEqual(data[constants.BE_AUTO_BALANCE], False)
566         self.assertEqual(data[constants.HV_MIGRATION_PORT], 9876)
567         self.assertEqual(data[constants.HV_VNC_TLS], True)
568         self.assertEqual(data["disks"], [disk["size"] for disk in disks])
569
570   def testCreateInstance(self):
571     self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_CREATE_REQV1]))
572     self.rapi.AddResponse("23030")
573     job_id = self.client.CreateInstance("create", "inst1.example.com",
574                                         "plain", [], [], dry_run=True)
575     self.assertEqual(job_id, 23030)
576     self.assertHandler(rlib2.R_2_instances)
577     self.assertDryRun()
578
579     data = serializer.LoadJson(self.rapi.GetLastRequestData())
580
581     for field in ["dry_run", "beparams", "hvparams", "start"]:
582       self.assertFalse(field in data)
583
584     self.assertEqual(data["name"], "inst1.example.com")
585     self.assertEqual(data["disk_template"], "plain")
586
587   def testCreateInstance2(self):
588     self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_CREATE_REQV1]))
589     self.rapi.AddResponse("24740")
590     job_id = self.client.CreateInstance("import", "inst2.example.com",
591                                         "drbd8", [{"size": 100,}],
592                                         [{}, {"bridge": "br1", }],
593                                         dry_run=False, start=True,
594                                         pnode="node1", snode="node9",
595                                         ip_check=False)
596     self.assertEqual(job_id, 24740)
597     self.assertHandler(rlib2.R_2_instances)
598
599     data = serializer.LoadJson(self.rapi.GetLastRequestData())
600     self.assertEqual(data[rlib2._REQ_DATA_VERSION], 1)
601     self.assertEqual(data["name"], "inst2.example.com")
602     self.assertEqual(data["disk_template"], "drbd8")
603     self.assertEqual(data["start"], True)
604     self.assertEqual(data["ip_check"], False)
605     self.assertEqualValues(data["disks"], [{"size": 100,}])
606     self.assertEqualValues(data["nics"], [{}, {"bridge": "br1", }])
607
608   def testDeleteInstance(self):
609     self.rapi.AddResponse("1234")
610     self.assertEqual(1234, self.client.DeleteInstance("instance", dry_run=True))
611     self.assertHandler(rlib2.R_2_instances_name)
612     self.assertItems(["instance"])
613     self.assertDryRun()
614
615   def testGetInstanceTags(self):
616     self.rapi.AddResponse("[]")
617     self.assertEqual([], self.client.GetInstanceTags("fooinstance"))
618     self.assertHandler(rlib2.R_2_instances_name_tags)
619     self.assertItems(["fooinstance"])
620
621   def testAddInstanceTags(self):
622     self.rapi.AddResponse("1234")
623     self.assertEqual(1234,
624         self.client.AddInstanceTags("fooinstance", ["awesome"], dry_run=True))
625     self.assertHandler(rlib2.R_2_instances_name_tags)
626     self.assertItems(["fooinstance"])
627     self.assertDryRun()
628     self.assertQuery("tag", ["awesome"])
629
630   def testDeleteInstanceTags(self):
631     self.rapi.AddResponse("25826")
632     self.assertEqual(25826, self.client.DeleteInstanceTags("foo", ["awesome"],
633                                                            dry_run=True))
634     self.assertHandler(rlib2.R_2_instances_name_tags)
635     self.assertItems(["foo"])
636     self.assertDryRun()
637     self.assertQuery("tag", ["awesome"])
638
639   def testRebootInstance(self):
640     self.rapi.AddResponse("6146")
641     job_id = self.client.RebootInstance("i-bar", reboot_type="hard",
642                                         ignore_secondaries=True, dry_run=True)
643     self.assertEqual(6146, job_id)
644     self.assertHandler(rlib2.R_2_instances_name_reboot)
645     self.assertItems(["i-bar"])
646     self.assertDryRun()
647     self.assertQuery("type", ["hard"])
648     self.assertQuery("ignore_secondaries", ["1"])
649
650   def testShutdownInstance(self):
651     self.rapi.AddResponse("1487")
652     self.assertEqual(1487, self.client.ShutdownInstance("foo-instance",
653                                                         dry_run=True))
654     self.assertHandler(rlib2.R_2_instances_name_shutdown)
655     self.assertItems(["foo-instance"])
656     self.assertDryRun()
657
658   def testStartupInstance(self):
659     self.rapi.AddResponse("27149")
660     self.assertEqual(27149, self.client.StartupInstance("bar-instance",
661                                                         dry_run=True))
662     self.assertHandler(rlib2.R_2_instances_name_startup)
663     self.assertItems(["bar-instance"])
664     self.assertDryRun()
665
666   def testReinstallInstance(self):
667     self.rapi.AddResponse(serializer.DumpJson([]))
668     self.rapi.AddResponse("19119")
669     self.assertEqual(19119, self.client.ReinstallInstance("baz-instance",
670                                                           os="DOS",
671                                                           no_startup=True))
672     self.assertHandler(rlib2.R_2_instances_name_reinstall)
673     self.assertItems(["baz-instance"])
674     self.assertQuery("os", ["DOS"])
675     self.assertQuery("nostartup", ["1"])
676     self.assertEqual(self.rapi.CountPending(), 0)
677
678   def testReinstallInstanceNew(self):
679     self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_REINSTALL_REQV1]))
680     self.rapi.AddResponse("25689")
681     self.assertEqual(25689, self.client.ReinstallInstance("moo-instance",
682                                                           os="Debian",
683                                                           no_startup=True))
684     self.assertHandler(rlib2.R_2_instances_name_reinstall)
685     self.assertItems(["moo-instance"])
686     data = serializer.LoadJson(self.rapi.GetLastRequestData())
687     self.assertEqual(len(data), 2)
688     self.assertEqual(data["os"], "Debian")
689     self.assertEqual(data["start"], False)
690     self.assertEqual(self.rapi.CountPending(), 0)
691
692   def testReinstallInstanceWithOsparams1(self):
693     self.rapi.AddResponse(serializer.DumpJson([]))
694     self.assertRaises(client.GanetiApiError, self.client.ReinstallInstance,
695                       "doo-instance", osparams={"x": "y"})
696     self.assertEqual(self.rapi.CountPending(), 0)
697
698   def testReinstallInstanceWithOsparams2(self):
699     osparams = {
700       "Hello": "World",
701       "foo": "bar",
702       }
703     self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_REINSTALL_REQV1]))
704     self.rapi.AddResponse("1717")
705     self.assertEqual(1717, self.client.ReinstallInstance("zoo-instance",
706                                                          osparams=osparams))
707     self.assertHandler(rlib2.R_2_instances_name_reinstall)
708     self.assertItems(["zoo-instance"])
709     data = serializer.LoadJson(self.rapi.GetLastRequestData())
710     self.assertEqual(len(data), 2)
711     self.assertEqual(data["osparams"], osparams)
712     self.assertEqual(data["start"], True)
713     self.assertEqual(self.rapi.CountPending(), 0)
714
715   def testReplaceInstanceDisks(self):
716     self.rapi.AddResponse("999")
717     job_id = self.client.ReplaceInstanceDisks("instance-name",
718         disks=[0, 1], dry_run=True, iallocator="hail")
719     self.assertEqual(999, job_id)
720     self.assertHandler(rlib2.R_2_instances_name_replace_disks)
721     self.assertItems(["instance-name"])
722     self.assertQuery("disks", ["0,1"])
723     self.assertQuery("mode", ["replace_auto"])
724     self.assertQuery("iallocator", ["hail"])
725     self.assertDryRun()
726
727     self.rapi.AddResponse("1000")
728     job_id = self.client.ReplaceInstanceDisks("instance-bar",
729         disks=[1], mode="replace_on_secondary", remote_node="foo-node",
730         dry_run=True)
731     self.assertEqual(1000, job_id)
732     self.assertItems(["instance-bar"])
733     self.assertQuery("disks", ["1"])
734     self.assertQuery("remote_node", ["foo-node"])
735     self.assertDryRun()
736
737     self.rapi.AddResponse("5175")
738     self.assertEqual(5175, self.client.ReplaceInstanceDisks("instance-moo"))
739     self.assertItems(["instance-moo"])
740     self.assertQuery("disks", None)
741
742   def testPrepareExport(self):
743     self.rapi.AddResponse("8326")
744     self.assertEqual(8326, self.client.PrepareExport("inst1", "local"))
745     self.assertHandler(rlib2.R_2_instances_name_prepare_export)
746     self.assertItems(["inst1"])
747     self.assertQuery("mode", ["local"])
748
749   def testExportInstance(self):
750     self.rapi.AddResponse("19695")
751     job_id = self.client.ExportInstance("inst2", "local", "nodeX",
752                                         shutdown=True)
753     self.assertEqual(job_id, 19695)
754     self.assertHandler(rlib2.R_2_instances_name_export)
755     self.assertItems(["inst2"])
756
757     data = serializer.LoadJson(self.rapi.GetLastRequestData())
758     self.assertEqual(data["mode"], "local")
759     self.assertEqual(data["destination"], "nodeX")
760     self.assertEqual(data["shutdown"], True)
761
762   def testMigrateInstanceDefaults(self):
763     self.rapi.AddResponse("24873")
764     job_id = self.client.MigrateInstance("inst91")
765     self.assertEqual(job_id, 24873)
766     self.assertHandler(rlib2.R_2_instances_name_migrate)
767     self.assertItems(["inst91"])
768
769     data = serializer.LoadJson(self.rapi.GetLastRequestData())
770     self.assertFalse(data)
771
772   def testMigrateInstance(self):
773     for mode in constants.HT_MIGRATION_MODES:
774       for cleanup in [False, True]:
775         self.rapi.AddResponse("31910")
776         job_id = self.client.MigrateInstance("inst289", mode=mode,
777                                              cleanup=cleanup)
778         self.assertEqual(job_id, 31910)
779         self.assertHandler(rlib2.R_2_instances_name_migrate)
780         self.assertItems(["inst289"])
781
782         data = serializer.LoadJson(self.rapi.GetLastRequestData())
783         self.assertEqual(len(data), 2)
784         self.assertEqual(data["mode"], mode)
785         self.assertEqual(data["cleanup"], cleanup)
786
787   def testRenameInstanceDefaults(self):
788     new_name = "newnametha7euqu"
789     self.rapi.AddResponse("8791")
790     job_id = self.client.RenameInstance("inst18821", new_name)
791     self.assertEqual(job_id, 8791)
792     self.assertHandler(rlib2.R_2_instances_name_rename)
793     self.assertItems(["inst18821"])
794
795     data = serializer.LoadJson(self.rapi.GetLastRequestData())
796     self.assertEqualValues(data, {"new_name": new_name, })
797
798   def testRenameInstance(self):
799     new_name = "new-name-yiux1iin"
800     for ip_check in [False, True]:
801       for name_check in [False, True]:
802         self.rapi.AddResponse("24776")
803         job_id = self.client.RenameInstance("inst20967", new_name,
804                                              ip_check=ip_check,
805                                              name_check=name_check)
806         self.assertEqual(job_id, 24776)
807         self.assertHandler(rlib2.R_2_instances_name_rename)
808         self.assertItems(["inst20967"])
809
810         data = serializer.LoadJson(self.rapi.GetLastRequestData())
811         self.assertEqual(len(data), 3)
812         self.assertEqual(data["new_name"], new_name)
813         self.assertEqual(data["ip_check"], ip_check)
814         self.assertEqual(data["name_check"], name_check)
815
816   def testGetJobs(self):
817     self.rapi.AddResponse('[ { "id": "123", "uri": "\\/2\\/jobs\\/123" },'
818                           '  { "id": "124", "uri": "\\/2\\/jobs\\/124" } ]')
819     self.assertEqual([123, 124], self.client.GetJobs())
820     self.assertHandler(rlib2.R_2_jobs)
821
822   def testGetJobStatus(self):
823     self.rapi.AddResponse("{\"foo\": \"bar\"}")
824     self.assertEqual({"foo": "bar"}, self.client.GetJobStatus(1234))
825     self.assertHandler(rlib2.R_2_jobs_id)
826     self.assertItems(["1234"])
827
828   def testWaitForJobChange(self):
829     fields = ["id", "summary"]
830     expected = {
831       "job_info": [123, "something"],
832       "log_entries": [],
833       }
834
835     self.rapi.AddResponse(serializer.DumpJson(expected))
836     result = self.client.WaitForJobChange(123, fields, [], -1)
837     self.assertEqualValues(expected, result)
838     self.assertHandler(rlib2.R_2_jobs_id_wait)
839     self.assertItems(["123"])
840
841   def testCancelJob(self):
842     self.rapi.AddResponse("[true, \"Job 123 will be canceled\"]")
843     self.assertEqual([True, "Job 123 will be canceled"],
844                      self.client.CancelJob(999, dry_run=True))
845     self.assertHandler(rlib2.R_2_jobs_id)
846     self.assertItems(["999"])
847     self.assertDryRun()
848
849   def testGetNodes(self):
850     self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
851                           " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
852     self.assertEqual(["node1", "node2"], self.client.GetNodes())
853     self.assertHandler(rlib2.R_2_nodes)
854
855     self.rapi.AddResponse("[ { \"id\": \"node1\", \"uri\": \"uri1\" },"
856                           " { \"id\": \"node2\", \"uri\": \"uri2\" } ]")
857     self.assertEqual([{"id": "node1", "uri": "uri1"},
858                       {"id": "node2", "uri": "uri2"}],
859                      self.client.GetNodes(bulk=True))
860     self.assertHandler(rlib2.R_2_nodes)
861     self.assertBulk()
862
863   def testGetNode(self):
864     self.rapi.AddResponse("{}")
865     self.assertEqual({}, self.client.GetNode("node-foo"))
866     self.assertHandler(rlib2.R_2_nodes_name)
867     self.assertItems(["node-foo"])
868
869   def testEvacuateNode(self):
870     self.rapi.AddResponse("9876")
871     job_id = self.client.EvacuateNode("node-1", remote_node="node-2")
872     self.assertEqual(9876, job_id)
873     self.assertHandler(rlib2.R_2_nodes_name_evacuate)
874     self.assertItems(["node-1"])
875     self.assertQuery("remote_node", ["node-2"])
876
877     self.rapi.AddResponse("8888")
878     job_id = self.client.EvacuateNode("node-3", iallocator="hail", dry_run=True)
879     self.assertEqual(8888, job_id)
880     self.assertItems(["node-3"])
881     self.assertQuery("iallocator", ["hail"])
882     self.assertDryRun()
883
884     self.assertRaises(client.GanetiApiError,
885                       self.client.EvacuateNode,
886                       "node-4", iallocator="hail", remote_node="node-5")
887
888   def testMigrateNode(self):
889     self.rapi.AddResponse("1111")
890     self.assertEqual(1111, self.client.MigrateNode("node-a", dry_run=True))
891     self.assertHandler(rlib2.R_2_nodes_name_migrate)
892     self.assertItems(["node-a"])
893     self.assert_("mode" not in self.rapi.GetLastHandler().queryargs)
894     self.assertDryRun()
895
896     self.rapi.AddResponse("1112")
897     self.assertEqual(1112, self.client.MigrateNode("node-a", dry_run=True,
898                                                    mode="live"))
899     self.assertHandler(rlib2.R_2_nodes_name_migrate)
900     self.assertItems(["node-a"])
901     self.assertQuery("mode", ["live"])
902     self.assertDryRun()
903
904   def testGetNodeRole(self):
905     self.rapi.AddResponse("\"master\"")
906     self.assertEqual("master", self.client.GetNodeRole("node-a"))
907     self.assertHandler(rlib2.R_2_nodes_name_role)
908     self.assertItems(["node-a"])
909
910   def testSetNodeRole(self):
911     self.rapi.AddResponse("789")
912     self.assertEqual(789,
913         self.client.SetNodeRole("node-foo", "master-candidate", force=True))
914     self.assertHandler(rlib2.R_2_nodes_name_role)
915     self.assertItems(["node-foo"])
916     self.assertQuery("force", ["1"])
917     self.assertEqual("\"master-candidate\"", self.rapi.GetLastRequestData())
918
919   def testGetNodeStorageUnits(self):
920     self.rapi.AddResponse("42")
921     self.assertEqual(42,
922         self.client.GetNodeStorageUnits("node-x", "lvm-pv", "fields"))
923     self.assertHandler(rlib2.R_2_nodes_name_storage)
924     self.assertItems(["node-x"])
925     self.assertQuery("storage_type", ["lvm-pv"])
926     self.assertQuery("output_fields", ["fields"])
927
928   def testModifyNodeStorageUnits(self):
929     self.rapi.AddResponse("14")
930     self.assertEqual(14,
931         self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda"))
932     self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
933     self.assertItems(["node-z"])
934     self.assertQuery("storage_type", ["lvm-pv"])
935     self.assertQuery("name", ["hda"])
936     self.assertQuery("allocatable", None)
937
938     for allocatable, query_allocatable in [(True, "1"), (False, "0")]:
939       self.rapi.AddResponse("7205")
940       job_id = self.client.ModifyNodeStorageUnits("node-z", "lvm-pv", "hda",
941                                                   allocatable=allocatable)
942       self.assertEqual(7205, job_id)
943       self.assertHandler(rlib2.R_2_nodes_name_storage_modify)
944       self.assertItems(["node-z"])
945       self.assertQuery("storage_type", ["lvm-pv"])
946       self.assertQuery("name", ["hda"])
947       self.assertQuery("allocatable", [query_allocatable])
948
949   def testRepairNodeStorageUnits(self):
950     self.rapi.AddResponse("99")
951     self.assertEqual(99, self.client.RepairNodeStorageUnits("node-z", "lvm-pv",
952                                                             "hda"))
953     self.assertHandler(rlib2.R_2_nodes_name_storage_repair)
954     self.assertItems(["node-z"])
955     self.assertQuery("storage_type", ["lvm-pv"])
956     self.assertQuery("name", ["hda"])
957
958   def testGetNodeTags(self):
959     self.rapi.AddResponse("[\"fry\", \"bender\"]")
960     self.assertEqual(["fry", "bender"], self.client.GetNodeTags("node-k"))
961     self.assertHandler(rlib2.R_2_nodes_name_tags)
962     self.assertItems(["node-k"])
963
964   def testAddNodeTags(self):
965     self.rapi.AddResponse("1234")
966     self.assertEqual(1234,
967         self.client.AddNodeTags("node-v", ["awesome"], dry_run=True))
968     self.assertHandler(rlib2.R_2_nodes_name_tags)
969     self.assertItems(["node-v"])
970     self.assertDryRun()
971     self.assertQuery("tag", ["awesome"])
972
973   def testDeleteNodeTags(self):
974     self.rapi.AddResponse("16861")
975     self.assertEqual(16861, self.client.DeleteNodeTags("node-w", ["awesome"],
976                                                        dry_run=True))
977     self.assertHandler(rlib2.R_2_nodes_name_tags)
978     self.assertItems(["node-w"])
979     self.assertDryRun()
980     self.assertQuery("tag", ["awesome"])
981
982   def testGetGroups(self):
983     groups = [{"name": "group1",
984                "uri": "/2/groups/group1",
985                },
986               {"name": "group2",
987                "uri": "/2/groups/group2",
988                },
989               ]
990     self.rapi.AddResponse(serializer.DumpJson(groups))
991     self.assertEqual(["group1", "group2"], self.client.GetGroups())
992     self.assertHandler(rlib2.R_2_groups)
993
994   def testGetGroupsBulk(self):
995     groups = [{"name": "group1",
996                "uri": "/2/groups/group1",
997                "node_cnt": 2,
998                "node_list": ["gnt1.test",
999                              "gnt2.test",
1000                              ],
1001                },
1002               {"name": "group2",
1003                "uri": "/2/groups/group2",
1004                "node_cnt": 1,
1005                "node_list": ["gnt3.test",
1006                              ],
1007                },
1008               ]
1009     self.rapi.AddResponse(serializer.DumpJson(groups))
1010
1011     self.assertEqual(groups, self.client.GetGroups(bulk=True))
1012     self.assertHandler(rlib2.R_2_groups)
1013     self.assertBulk()
1014
1015   def testGetGroup(self):
1016     group = {"ctime": None,
1017              "name": "default",
1018              }
1019     self.rapi.AddResponse(serializer.DumpJson(group))
1020     self.assertEqual({"ctime": None, "name": "default"},
1021                      self.client.GetGroup("default"))
1022     self.assertHandler(rlib2.R_2_groups_name)
1023     self.assertItems(["default"])
1024
1025   def testCreateGroup(self):
1026     self.rapi.AddResponse("12345")
1027     job_id = self.client.CreateGroup("newgroup", dry_run=True)
1028     self.assertEqual(job_id, 12345)
1029     self.assertHandler(rlib2.R_2_groups)
1030     self.assertDryRun()
1031
1032   def testDeleteGroup(self):
1033     self.rapi.AddResponse("12346")
1034     job_id = self.client.DeleteGroup("newgroup", dry_run=True)
1035     self.assertEqual(job_id, 12346)
1036     self.assertHandler(rlib2.R_2_groups_name)
1037     self.assertDryRun()
1038
1039   def testRenameGroup(self):
1040     self.rapi.AddResponse("12347")
1041     job_id = self.client.RenameGroup("oldname", "newname")
1042     self.assertEqual(job_id, 12347)
1043     self.assertHandler(rlib2.R_2_groups_name_rename)
1044
1045   def testModifyGroup(self):
1046     self.rapi.AddResponse("12348")
1047     job_id = self.client.ModifyGroup("mygroup", alloc_policy="foo")
1048     self.assertEqual(job_id, 12348)
1049     self.assertHandler(rlib2.R_2_groups_name_modify)
1050
1051   def testAssignGroupNodes(self):
1052     self.rapi.AddResponse("12349")
1053     job_id = self.client.AssignGroupNodes("mygroup", ["node1", "node2"],
1054                                           force=True, dry_run=True)
1055     self.assertEqual(job_id, 12349)
1056     self.assertHandler(rlib2.R_2_groups_name_assign_nodes)
1057     self.assertDryRun()
1058     self.assertUseForce()
1059
1060   def testModifyInstance(self):
1061     self.rapi.AddResponse("23681")
1062     job_id = self.client.ModifyInstance("inst7210", os_name="linux")
1063     self.assertEqual(job_id, 23681)
1064     self.assertItems(["inst7210"])
1065     self.assertHandler(rlib2.R_2_instances_name_modify)
1066     self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()),
1067                      { "os_name": "linux", })
1068
1069   def testModifyCluster(self):
1070     for mnh in [None, False, True]:
1071       self.rapi.AddResponse("14470")
1072       self.assertEqual(14470,
1073         self.client.ModifyCluster(maintain_node_health=mnh))
1074       self.assertHandler(rlib2.R_2_cluster_modify)
1075       self.assertItems([])
1076       data = serializer.LoadJson(self.rapi.GetLastRequestData())
1077       self.assertEqual(len(data), 1)
1078       self.assertEqual(data["maintain_node_health"], mnh)
1079       self.assertEqual(self.rapi.CountPending(), 0)
1080
1081   def testRedistributeConfig(self):
1082     self.rapi.AddResponse("3364")
1083     job_id = self.client.RedistributeConfig()
1084     self.assertEqual(job_id, 3364)
1085     self.assertItems([])
1086     self.assertHandler(rlib2.R_2_redist_config)
1087
1088   def testActivateInstanceDisks(self):
1089     self.rapi.AddResponse("23547")
1090     job_id = self.client.ActivateInstanceDisks("inst28204")
1091     self.assertEqual(job_id, 23547)
1092     self.assertItems(["inst28204"])
1093     self.assertHandler(rlib2.R_2_instances_name_activate_disks)
1094     self.assertFalse(self.rapi.GetLastHandler().queryargs)
1095
1096   def testActivateInstanceDisksIgnoreSize(self):
1097     self.rapi.AddResponse("11044")
1098     job_id = self.client.ActivateInstanceDisks("inst28204", ignore_size=True)
1099     self.assertEqual(job_id, 11044)
1100     self.assertItems(["inst28204"])
1101     self.assertHandler(rlib2.R_2_instances_name_activate_disks)
1102     self.assertQuery("ignore_size", ["1"])
1103
1104   def testDeactivateInstanceDisks(self):
1105     self.rapi.AddResponse("14591")
1106     job_id = self.client.DeactivateInstanceDisks("inst28234")
1107     self.assertEqual(job_id, 14591)
1108     self.assertItems(["inst28234"])
1109     self.assertHandler(rlib2.R_2_instances_name_deactivate_disks)
1110     self.assertFalse(self.rapi.GetLastHandler().queryargs)
1111
1112   def testGrowInstanceDisk(self):
1113     for idx, wait_for_sync in enumerate([None, False, True]):
1114       amount = 128 + (512 * idx)
1115       self.assertEqual(self.rapi.CountPending(), 0)
1116       self.rapi.AddResponse("30783")
1117       self.assertEqual(30783,
1118         self.client.GrowInstanceDisk("eze8ch", idx, amount,
1119                                      wait_for_sync=wait_for_sync))
1120       self.assertHandler(rlib2.R_2_instances_name_disk_grow)
1121       self.assertItems(["eze8ch", str(idx)])
1122       data = serializer.LoadJson(self.rapi.GetLastRequestData())
1123       if wait_for_sync is None:
1124         self.assertEqual(len(data), 1)
1125         self.assert_("wait_for_sync" not in data)
1126       else:
1127         self.assertEqual(len(data), 2)
1128         self.assertEqual(data["wait_for_sync"], wait_for_sync)
1129       self.assertEqual(data["amount"], amount)
1130       self.assertEqual(self.rapi.CountPending(), 0)
1131
1132
1133 if __name__ == '__main__':
1134   client.UsesRapiClient(testutils.GanetiTestProgram)()