Add support for classic queries
[ganeti-local] / test / ganeti.rapi.client_unittest.py
index b5f7050..98f117a 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 #
 
 #!/usr/bin/python
 #
 
-# Copyright (C) 2010 Google Inc.
+# Copyright (C) 2010, 2011 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 """Script for unittesting the RAPI client module"""
 
 
 """Script for unittesting the RAPI client module"""
 
 
-import re
 import unittest
 import warnings
 import pycurl
 
 import unittest
 import warnings
 import pycurl
 
+from ganeti import opcodes
 from ganeti import constants
 from ganeti import http
 from ganeti import serializer
 from ganeti import utils
 from ganeti import query
 from ganeti import objects
 from ganeti import constants
 from ganeti import http
 from ganeti import serializer
 from ganeti import utils
 from ganeti import query
 from ganeti import objects
+from ganeti import rapi
 
 
+import ganeti.rapi.testutils
 from ganeti.rapi import connector
 from ganeti.rapi import rlib2
 from ganeti.rapi import client
 from ganeti.rapi import connector
 from ganeti.rapi import rlib2
 from ganeti.rapi import client
@@ -41,61 +43,16 @@ from ganeti.rapi import client
 import testutils
 
 
 import testutils
 
 
-_URI_RE = re.compile(r"https://(?P<host>.*):(?P<port>\d+)(?P<path>/.*)")
-
 # List of resource handlers which aren't used by the RAPI client
 _KNOWN_UNUSED = set([
 # List of resource handlers which aren't used by the RAPI client
 _KNOWN_UNUSED = set([
-  connector.R_root,
-  connector.R_2,
+  rlib2.R_root,
+  rlib2.R_2,
   ])
 
 # Global variable for collecting used handlers
 _used_handlers = None
 
 
   ])
 
 # Global variable for collecting used handlers
 _used_handlers = None
 
 
-def _GetPathFromUri(uri):
-  """Gets the path and query from a URI.
-
-  """
-  match = _URI_RE.match(uri)
-  if match:
-    return match.groupdict()["path"]
-  else:
-    return None
-
-
-class FakeCurl:
-  def __init__(self, rapi):
-    self._rapi = rapi
-    self._opts = {}
-    self._info = {}
-
-  def setopt(self, opt, value):
-    self._opts[opt] = value
-
-  def getopt(self, opt):
-    return self._opts.get(opt)
-
-  def unsetopt(self, opt):
-    self._opts.pop(opt, None)
-
-  def getinfo(self, info):
-    return self._info[info]
-
-  def perform(self):
-    method = self._opts[pycurl.CUSTOMREQUEST]
-    url = self._opts[pycurl.URL]
-    request_body = self._opts[pycurl.POSTFIELDS]
-    writefn = self._opts[pycurl.WRITEFUNCTION]
-
-    path = _GetPathFromUri(url)
-    (code, resp_body) = self._rapi.FetchResponse(path, method, request_body)
-
-    self._info[pycurl.RESPONSE_CODE] = code
-    if resp_body is not None:
-      writefn(resp_body)
-
-
 class RapiMock(object):
   def __init__(self):
     self._mapper = connector.Mapper()
 class RapiMock(object):
   def __init__(self):
     self._mapper = connector.Mapper()
@@ -103,6 +60,9 @@ class RapiMock(object):
     self._last_handler = None
     self._last_req_data = None
 
     self._last_handler = None
     self._last_req_data = None
 
+  def ResetResponses(self):
+    del self._responses[:]
+
   def AddResponse(self, response, code=200):
     self._responses.insert(0, (code, response))
 
   def AddResponse(self, response, code=200):
     self._responses.insert(0, (code, response))
 
@@ -115,7 +75,7 @@ class RapiMock(object):
   def GetLastRequestData(self):
     return self._last_req_data
 
   def GetLastRequestData(self):
     return self._last_req_data
 
-  def FetchResponse(self, path, method, request_body):
+  def FetchResponse(self, path, method, headers, request_body):
     self._last_req_data = request_body
 
     try:
     self._last_req_data = request_body
 
     try:
@@ -146,11 +106,8 @@ class TestConstants(unittest.TestCase):
     self.assertEqual(client.GANETI_RAPI_VERSION, constants.RAPI_VERSION)
     self.assertEqual(client.HTTP_APP_JSON, http.HTTP_APP_JSON)
     self.assertEqual(client._REQ_DATA_VERSION_FIELD, rlib2._REQ_DATA_VERSION)
     self.assertEqual(client.GANETI_RAPI_VERSION, constants.RAPI_VERSION)
     self.assertEqual(client.HTTP_APP_JSON, http.HTTP_APP_JSON)
     self.assertEqual(client._REQ_DATA_VERSION_FIELD, rlib2._REQ_DATA_VERSION)
-    self.assertEqual(client._INST_CREATE_REQV1, rlib2._INST_CREATE_REQV1)
-    self.assertEqual(client._INST_REINSTALL_REQV1, rlib2._INST_REINSTALL_REQV1)
-    self.assertEqual(client._INST_NIC_PARAMS, constants.INIC_PARAMS)
     self.assertEqual(client.JOB_STATUS_QUEUED, constants.JOB_STATUS_QUEUED)
     self.assertEqual(client.JOB_STATUS_QUEUED, constants.JOB_STATUS_QUEUED)
-    self.assertEqual(client.JOB_STATUS_WAITLOCK, constants.JOB_STATUS_WAITLOCK)
+    self.assertEqual(client.JOB_STATUS_WAITING, constants.JOB_STATUS_WAITING)
     self.assertEqual(client.JOB_STATUS_CANCELING,
                      constants.JOB_STATUS_CANCELING)
     self.assertEqual(client.JOB_STATUS_RUNNING, constants.JOB_STATUS_RUNNING)
     self.assertEqual(client.JOB_STATUS_CANCELING,
                      constants.JOB_STATUS_CANCELING)
     self.assertEqual(client.JOB_STATUS_RUNNING, constants.JOB_STATUS_RUNNING)
@@ -160,16 +117,34 @@ class TestConstants(unittest.TestCase):
     self.assertEqual(client.JOB_STATUS_FINALIZED, constants.JOBS_FINALIZED)
     self.assertEqual(client.JOB_STATUS_ALL, constants.JOB_STATUS_ALL)
 
     self.assertEqual(client.JOB_STATUS_FINALIZED, constants.JOBS_FINALIZED)
     self.assertEqual(client.JOB_STATUS_ALL, constants.JOB_STATUS_ALL)
 
+    # Node evacuation
+    self.assertEqual(client.NODE_EVAC_PRI, constants.NODE_EVAC_PRI)
+    self.assertEqual(client.NODE_EVAC_SEC, constants.NODE_EVAC_SEC)
+    self.assertEqual(client.NODE_EVAC_ALL, constants.NODE_EVAC_ALL)
+
+    # Legacy name
+    self.assertEqual(client.JOB_STATUS_WAITLOCK, constants.JOB_STATUS_WAITING)
+
+    # RAPI feature strings
+    self.assertEqual(client._INST_CREATE_REQV1, rlib2._INST_CREATE_REQV1)
+    self.assertEqual(client.INST_CREATE_REQV1, rlib2._INST_CREATE_REQV1)
+    self.assertEqual(client._INST_REINSTALL_REQV1, rlib2._INST_REINSTALL_REQV1)
+    self.assertEqual(client.INST_REINSTALL_REQV1, rlib2._INST_REINSTALL_REQV1)
+    self.assertEqual(client._NODE_MIGRATE_REQV1, rlib2._NODE_MIGRATE_REQV1)
+    self.assertEqual(client.NODE_MIGRATE_REQV1, rlib2._NODE_MIGRATE_REQV1)
+    self.assertEqual(client._NODE_EVAC_RES1, rlib2._NODE_EVAC_RES1)
+    self.assertEqual(client.NODE_EVAC_RES1, rlib2._NODE_EVAC_RES1)
+
 
 class RapiMockTest(unittest.TestCase):
   def test(self):
     rapi = RapiMock()
     path = "/version"
 
 class RapiMockTest(unittest.TestCase):
   def test(self):
     rapi = RapiMock()
     path = "/version"
-    self.assertEqual((404, None), rapi.FetchResponse("/foo", "GET", None))
+    self.assertEqual((404, None), rapi.FetchResponse("/foo", "GET", None, None))
     self.assertEqual((501, "Method not implemented"),
     self.assertEqual((501, "Method not implemented"),
-                     rapi.FetchResponse("/version", "POST", None))
+                     rapi.FetchResponse("/version", "POST", None, None))
     rapi.AddResponse("2")
     rapi.AddResponse("2")
-    code, response = rapi.FetchResponse("/version", "GET", None)
+    code, response = rapi.FetchResponse("/version", "GET", None, None)
     self.assertEqual(200, code)
     self.assertEqual("2", response)
     self.failUnless(isinstance(rapi.GetLastHandler(), rlib2.R_version))
     self.assertEqual(200, code)
     self.assertEqual("2", response)
     self.failUnless(isinstance(rapi.GetLastHandler(), rlib2.R_version))
@@ -198,8 +173,8 @@ def _FakeGnuTlsPycurlVersion():
 class TestExtendedConfig(unittest.TestCase):
   def testAuth(self):
     cl = client.GanetiRapiClient("master.example.com",
 class TestExtendedConfig(unittest.TestCase):
   def testAuth(self):
     cl = client.GanetiRapiClient("master.example.com",
-                                 username="user", password="pw",
-                                 curl_factory=lambda: FakeCurl(RapiMock()))
+      username="user", password="pw",
+      curl_factory=lambda: rapi.testutils.FakeCurl(RapiMock()))
 
     curl = cl._CreateCurl()
     self.assertEqual(curl.getopt(pycurl.HTTPAUTH), pycurl.HTTPAUTH_BASIC)
 
     curl = cl._CreateCurl()
     self.assertEqual(curl.getopt(pycurl.HTTPAUTH), pycurl.HTTPAUTH_BASIC)
@@ -236,7 +211,7 @@ class TestExtendedConfig(unittest.TestCase):
                                              verify_hostname=verify_hostname,
                                              _pycurl_version_fn=pcverfn)
 
                                              verify_hostname=verify_hostname,
                                              _pycurl_version_fn=pcverfn)
 
-            curl_factory = lambda: FakeCurl(RapiMock())
+            curl_factory = lambda: rapi.testutils.FakeCurl(RapiMock())
             cl = client.GanetiRapiClient("master.example.com",
                                          curl_config_fn=cfgfn,
                                          curl_factory=curl_factory)
             cl = client.GanetiRapiClient("master.example.com",
                                          curl_config_fn=cfgfn,
                                          curl_factory=curl_factory)
@@ -253,7 +228,7 @@ class TestExtendedConfig(unittest.TestCase):
   def testNoCertVerify(self):
     cfgfn = client.GenericCurlConfig()
 
   def testNoCertVerify(self):
     cfgfn = client.GenericCurlConfig()
 
-    curl_factory = lambda: FakeCurl(RapiMock())
+    curl_factory = lambda: rapi.testutils.FakeCurl(RapiMock())
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
@@ -265,7 +240,7 @@ class TestExtendedConfig(unittest.TestCase):
   def testCertVerifyCurlBundle(self):
     cfgfn = client.GenericCurlConfig(use_curl_cabundle=True)
 
   def testCertVerifyCurlBundle(self):
     cfgfn = client.GenericCurlConfig(use_curl_cabundle=True)
 
-    curl_factory = lambda: FakeCurl(RapiMock())
+    curl_factory = lambda: rapi.testutils.FakeCurl(RapiMock())
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
@@ -278,7 +253,7 @@ class TestExtendedConfig(unittest.TestCase):
     mycert = "/tmp/some/UNUSED/cert/file.pem"
     cfgfn = client.GenericCurlConfig(cafile=mycert)
 
     mycert = "/tmp/some/UNUSED/cert/file.pem"
     cfgfn = client.GenericCurlConfig(cafile=mycert)
 
-    curl_factory = lambda: FakeCurl(RapiMock())
+    curl_factory = lambda: rapi.testutils.FakeCurl(RapiMock())
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
@@ -293,7 +268,7 @@ class TestExtendedConfig(unittest.TestCase):
     cfgfn = client.GenericCurlConfig(capath=certdir,
                                      _pycurl_version_fn=pcverfn)
 
     cfgfn = client.GenericCurlConfig(capath=certdir,
                                      _pycurl_version_fn=pcverfn)
 
-    curl_factory = lambda: FakeCurl(RapiMock())
+    curl_factory = lambda: rapi.testutils.FakeCurl(RapiMock())
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
@@ -308,7 +283,7 @@ class TestExtendedConfig(unittest.TestCase):
     cfgfn = client.GenericCurlConfig(capath=certdir,
                                      _pycurl_version_fn=pcverfn)
 
     cfgfn = client.GenericCurlConfig(capath=certdir,
                                      _pycurl_version_fn=pcverfn)
 
-    curl_factory = lambda: FakeCurl(RapiMock())
+    curl_factory = lambda: rapi.testutils.FakeCurl(RapiMock())
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
@@ -320,7 +295,7 @@ class TestExtendedConfig(unittest.TestCase):
     cfgfn = client.GenericCurlConfig(capath=certdir,
                                      _pycurl_version_fn=pcverfn)
 
     cfgfn = client.GenericCurlConfig(capath=certdir,
                                      _pycurl_version_fn=pcverfn)
 
-    curl_factory = lambda: FakeCurl(RapiMock())
+    curl_factory = lambda: rapi.testutils.FakeCurl(RapiMock())
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
@@ -332,7 +307,7 @@ class TestExtendedConfig(unittest.TestCase):
     cfgfn = client.GenericCurlConfig(capath=certdir,
                                      _pycurl_version_fn=pcverfn)
 
     cfgfn = client.GenericCurlConfig(capath=certdir,
                                      _pycurl_version_fn=pcverfn)
 
-    curl_factory = lambda: FakeCurl(RapiMock())
+    curl_factory = lambda: rapi.testutils.FakeCurl(RapiMock())
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
     cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                  curl_factory=curl_factory)
 
@@ -344,7 +319,7 @@ class TestExtendedConfig(unittest.TestCase):
         cfgfn = client.GenericCurlConfig(connect_timeout=connect_timeout,
                                          timeout=timeout)
 
         cfgfn = client.GenericCurlConfig(connect_timeout=connect_timeout,
                                          timeout=timeout)
 
-        curl_factory = lambda: FakeCurl(RapiMock())
+        curl_factory = lambda: rapi.testutils.FakeCurl(RapiMock())
         cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                      curl_factory=curl_factory)
 
         cl = client.GanetiRapiClient("master.example.com", curl_config_fn=cfgfn,
                                      curl_factory=curl_factory)
 
@@ -358,7 +333,7 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
     testutils.GanetiTestCase.setUp(self)
 
     self.rapi = RapiMock()
     testutils.GanetiTestCase.setUp(self)
 
     self.rapi = RapiMock()
-    self.curl = FakeCurl(self.rapi)
+    self.curl = rapi.testutils.FakeCurl(self.rapi)
     self.client = client.GanetiRapiClient("master.example.com",
                                           curl_factory=lambda: self.curl)
 
     self.client = client.GanetiRapiClient("master.example.com",
                                           curl_factory=lambda: self.curl)
 
@@ -506,93 +481,28 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
     self.assertItems(["inst32"])
     self.assertQuery("static", ["1"])
 
     self.assertItems(["inst32"])
     self.assertQuery("static", ["1"])
 
+  def testInstancesMultiAlloc(self):
+    response = {
+      constants.JOB_IDS_KEY: ["23423"],
+      opcodes.OpInstanceMultiAlloc.ALLOCATABLE_KEY: ["foobar"],
+      opcodes.OpInstanceMultiAlloc.FAILED_KEY: ["foobar2"],
+      }
+    self.rapi.AddResponse(serializer.DumpJson(response))
+    insts = [self.client.InstanceAllocation("create", "foobar",
+                                            "plain", [], []),
+             self.client.InstanceAllocation("create", "foobar2",
+                                            "drbd8", [{"size": 100}], [])]
+    resp = self.client.InstancesMultiAlloc(insts)
+    self.assertEqual(resp, response)
+    self.assertHandler(rlib2.R_2_instances_multi_alloc)
+
   def testCreateInstanceOldVersion(self):
   def testCreateInstanceOldVersion(self):
-    # No NICs
+    # The old request format, version 0, is no longer supported
     self.rapi.AddResponse(None, code=404)
     self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
                       "create", "inst1.example.com", "plain", [], [])
     self.assertEqual(self.rapi.CountPending(), 0)
 
     self.rapi.AddResponse(None, code=404)
     self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
                       "create", "inst1.example.com", "plain", [], [])
     self.assertEqual(self.rapi.CountPending(), 0)
 
-    # More than one NIC
-    self.rapi.AddResponse(None, code=404)
-    self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
-                      "create", "inst1.example.com", "plain", [],
-                      [{}, {}, {}])
-    self.assertEqual(self.rapi.CountPending(), 0)
-
-    # Unsupported NIC fields
-    self.rapi.AddResponse(None, code=404)
-    self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
-                      "create", "inst1.example.com", "plain", [],
-                      [{"x": True, "y": False}])
-    self.assertEqual(self.rapi.CountPending(), 0)
-
-    # Unsupported disk fields
-    self.rapi.AddResponse(None, code=404)
-    self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
-                      "create", "inst1.example.com", "plain",
-                      [{}, {"moo": "foo",}], [{}])
-    self.assertEqual(self.rapi.CountPending(), 0)
-
-    # Unsupported fields
-    self.rapi.AddResponse(None, code=404)
-    self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
-                      "create", "inst1.example.com", "plain", [], [{}],
-                      hello_world=123)
-    self.assertEqual(self.rapi.CountPending(), 0)
-
-    self.rapi.AddResponse(None, code=404)
-    self.assertRaises(client.GanetiApiError, self.client.CreateInstance,
-                      "create", "inst1.example.com", "plain", [], [{}],
-                      memory=128)
-    self.assertEqual(self.rapi.CountPending(), 0)
-
-    # Normal creation
-    testnics = [
-      [{}],
-      [{ "mac": constants.VALUE_AUTO, }],
-      [{ "ip": "192.0.2.99", "mode": constants.NIC_MODE_ROUTED, }],
-      ]
-
-    testdisks = [
-      [],
-      [{ "size": 128, }],
-      [{ "size": 321, }, { "size": 4096, }],
-      ]
-
-    for idx, nics in enumerate(testnics):
-      for disks in testdisks:
-        beparams = {
-          constants.BE_MEMORY: 512,
-          constants.BE_AUTO_BALANCE: False,
-          }
-        hvparams = {
-          constants.HV_MIGRATION_PORT: 9876,
-          constants.HV_VNC_TLS: True,
-          }
-
-        self.rapi.AddResponse(None, code=404)
-        self.rapi.AddResponse(serializer.DumpJson(3122617 + idx))
-        job_id = self.client.CreateInstance("create", "inst1.example.com",
-                                            "plain", disks, nics,
-                                            pnode="node99", dry_run=True,
-                                            hvparams=hvparams,
-                                            beparams=beparams)
-        self.assertEqual(job_id, 3122617 + idx)
-        self.assertHandler(rlib2.R_2_instances)
-        self.assertDryRun()
-        self.assertEqual(self.rapi.CountPending(), 0)
-
-        data = serializer.LoadJson(self.rapi.GetLastRequestData())
-        self.assertEqual(data["name"], "inst1.example.com")
-        self.assertEqual(data["disk_template"], "plain")
-        self.assertEqual(data["pnode"], "node99")
-        self.assertEqual(data[constants.BE_MEMORY], 512)
-        self.assertEqual(data[constants.BE_AUTO_BALANCE], False)
-        self.assertEqual(data[constants.HV_MIGRATION_PORT], 9876)
-        self.assertEqual(data[constants.HV_VNC_TLS], True)
-        self.assertEqual(data["disks"], [disk["size"] for disk in disks])
-
   def testCreateInstance(self):
     self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_CREATE_REQV1]))
     self.rapi.AddResponse("23030")
   def testCreateInstance(self):
     self.rapi.AddResponse(serializer.DumpJson([rlib2._INST_CREATE_REQV1]))
     self.rapi.AddResponse("23030")
@@ -741,24 +651,21 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
   def testReplaceInstanceDisks(self):
     self.rapi.AddResponse("999")
     job_id = self.client.ReplaceInstanceDisks("instance-name",
   def testReplaceInstanceDisks(self):
     self.rapi.AddResponse("999")
     job_id = self.client.ReplaceInstanceDisks("instance-name",
-        disks=[0, 1], dry_run=True, iallocator="hail")
+        disks=[0, 1], iallocator="hail")
     self.assertEqual(999, job_id)
     self.assertHandler(rlib2.R_2_instances_name_replace_disks)
     self.assertItems(["instance-name"])
     self.assertQuery("disks", ["0,1"])
     self.assertQuery("mode", ["replace_auto"])
     self.assertQuery("iallocator", ["hail"])
     self.assertEqual(999, job_id)
     self.assertHandler(rlib2.R_2_instances_name_replace_disks)
     self.assertItems(["instance-name"])
     self.assertQuery("disks", ["0,1"])
     self.assertQuery("mode", ["replace_auto"])
     self.assertQuery("iallocator", ["hail"])
-    self.assertDryRun()
 
     self.rapi.AddResponse("1000")
     job_id = self.client.ReplaceInstanceDisks("instance-bar",
 
     self.rapi.AddResponse("1000")
     job_id = self.client.ReplaceInstanceDisks("instance-bar",
-        disks=[1], mode="replace_on_secondary", remote_node="foo-node",
-        dry_run=True)
+        disks=[1], mode="replace_on_secondary", remote_node="foo-node")
     self.assertEqual(1000, job_id)
     self.assertItems(["instance-bar"])
     self.assertQuery("disks", ["1"])
     self.assertQuery("remote_node", ["foo-node"])
     self.assertEqual(1000, job_id)
     self.assertItems(["instance-bar"])
     self.assertQuery("disks", ["1"])
     self.assertQuery("remote_node", ["foo-node"])
-    self.assertDryRun()
 
     self.rapi.AddResponse("5175")
     self.assertEqual(5175, self.client.ReplaceInstanceDisks("instance-moo"))
 
     self.rapi.AddResponse("5175")
     self.assertEqual(5175, self.client.ReplaceInstanceDisks("instance-moo"))
@@ -810,6 +717,36 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
         self.assertEqual(data["mode"], mode)
         self.assertEqual(data["cleanup"], cleanup)
 
         self.assertEqual(data["mode"], mode)
         self.assertEqual(data["cleanup"], cleanup)
 
+  def testFailoverInstanceDefaults(self):
+    self.rapi.AddResponse("7639")
+    job_id = self.client.FailoverInstance("inst13579")
+    self.assertEqual(job_id, 7639)
+    self.assertHandler(rlib2.R_2_instances_name_failover)
+    self.assertItems(["inst13579"])
+
+    data = serializer.LoadJson(self.rapi.GetLastRequestData())
+    self.assertFalse(data)
+
+  def testFailoverInstance(self):
+    for iallocator in ["dumb", "hail"]:
+      for ignore_consistency in [False, True]:
+        for target_node in ["node-a", "node2"]:
+          self.rapi.AddResponse("19161")
+          job_id = \
+            self.client.FailoverInstance("inst251", iallocator=iallocator,
+                                         ignore_consistency=ignore_consistency,
+                                         target_node=target_node)
+          self.assertEqual(job_id, 19161)
+          self.assertHandler(rlib2.R_2_instances_name_failover)
+          self.assertItems(["inst251"])
+
+          data = serializer.LoadJson(self.rapi.GetLastRequestData())
+          self.assertEqual(len(data), 3)
+          self.assertEqual(data["iallocator"], iallocator)
+          self.assertEqual(data["ignore_consistency"], ignore_consistency)
+          self.assertEqual(data["target_node"], target_node)
+          self.assertEqual(self.rapi.CountPending(), 0)
+
   def testRenameInstanceDefaults(self):
     new_name = "newnametha7euqu"
     self.rapi.AddResponse("8791")
   def testRenameInstanceDefaults(self):
     new_name = "newnametha7euqu"
     self.rapi.AddResponse("8791")
@@ -893,32 +830,72 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
     self.assertItems(["node-foo"])
 
   def testEvacuateNode(self):
     self.assertItems(["node-foo"])
 
   def testEvacuateNode(self):
+    self.rapi.AddResponse(serializer.DumpJson([rlib2._NODE_EVAC_RES1]))
     self.rapi.AddResponse("9876")
     job_id = self.client.EvacuateNode("node-1", remote_node="node-2")
     self.assertEqual(9876, job_id)
     self.assertHandler(rlib2.R_2_nodes_name_evacuate)
     self.assertItems(["node-1"])
     self.rapi.AddResponse("9876")
     job_id = self.client.EvacuateNode("node-1", remote_node="node-2")
     self.assertEqual(9876, job_id)
     self.assertHandler(rlib2.R_2_nodes_name_evacuate)
     self.assertItems(["node-1"])
-    self.assertQuery("remote_node", ["node-2"])
+    self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()),
+                     { "remote_node": "node-2", })
+    self.assertEqual(self.rapi.CountPending(), 0)
 
 
+    self.rapi.AddResponse(serializer.DumpJson([rlib2._NODE_EVAC_RES1]))
     self.rapi.AddResponse("8888")
     self.rapi.AddResponse("8888")
-    job_id = self.client.EvacuateNode("node-3", iallocator="hail", dry_run=True)
+    job_id = self.client.EvacuateNode("node-3", iallocator="hail", dry_run=True,
+                                      mode=constants.NODE_EVAC_ALL,
+                                      early_release=True)
     self.assertEqual(8888, job_id)
     self.assertItems(["node-3"])
     self.assertEqual(8888, job_id)
     self.assertItems(["node-3"])
-    self.assertQuery("iallocator", ["hail"])
+    self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()), {
+      "iallocator": "hail",
+      "mode": "all",
+      "early_release": True,
+      })
     self.assertDryRun()
 
     self.assertRaises(client.GanetiApiError,
                       self.client.EvacuateNode,
                       "node-4", iallocator="hail", remote_node="node-5")
     self.assertDryRun()
 
     self.assertRaises(client.GanetiApiError,
                       self.client.EvacuateNode,
                       "node-4", iallocator="hail", remote_node="node-5")
+    self.assertEqual(self.rapi.CountPending(), 0)
+
+  def testEvacuateNodeOldResponse(self):
+    self.rapi.AddResponse(serializer.DumpJson([]))
+    self.assertRaises(client.GanetiApiError, self.client.EvacuateNode,
+                      "node-4", accept_old=False)
+    self.assertEqual(self.rapi.CountPending(), 0)
+
+    for mode in [client.NODE_EVAC_PRI, client.NODE_EVAC_ALL]:
+      self.rapi.AddResponse(serializer.DumpJson([]))
+      self.assertRaises(client.GanetiApiError, self.client.EvacuateNode,
+                        "node-4", accept_old=True, mode=mode)
+      self.assertEqual(self.rapi.CountPending(), 0)
+
+    self.rapi.AddResponse(serializer.DumpJson([]))
+    self.rapi.AddResponse(serializer.DumpJson("21533"))
+    result = self.client.EvacuateNode("node-3", iallocator="hail",
+                                      dry_run=True, accept_old=True,
+                                      mode=client.NODE_EVAC_SEC,
+                                      early_release=True)
+    self.assertEqual(result, "21533")
+    self.assertItems(["node-3"])
+    self.assertQuery("iallocator", ["hail"])
+    self.assertQuery("early_release", ["1"])
+    self.assertFalse(self.rapi.GetLastRequestData())
+    self.assertDryRun()
+    self.assertEqual(self.rapi.CountPending(), 0)
 
   def testMigrateNode(self):
 
   def testMigrateNode(self):
+    self.rapi.AddResponse(serializer.DumpJson([]))
     self.rapi.AddResponse("1111")
     self.assertEqual(1111, self.client.MigrateNode("node-a", dry_run=True))
     self.assertHandler(rlib2.R_2_nodes_name_migrate)
     self.assertItems(["node-a"])
     self.assert_("mode" not in self.rapi.GetLastHandler().queryargs)
     self.assertDryRun()
     self.rapi.AddResponse("1111")
     self.assertEqual(1111, self.client.MigrateNode("node-a", dry_run=True))
     self.assertHandler(rlib2.R_2_nodes_name_migrate)
     self.assertItems(["node-a"])
     self.assert_("mode" not in self.rapi.GetLastHandler().queryargs)
     self.assertDryRun()
+    self.assertFalse(self.rapi.GetLastRequestData())
 
 
+    self.rapi.AddResponse(serializer.DumpJson([]))
     self.rapi.AddResponse("1112")
     self.assertEqual(1112, self.client.MigrateNode("node-a", dry_run=True,
                                                    mode="live"))
     self.rapi.AddResponse("1112")
     self.assertEqual(1112, self.client.MigrateNode("node-a", dry_run=True,
                                                    mode="live"))
@@ -926,6 +903,36 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
     self.assertItems(["node-a"])
     self.assertQuery("mode", ["live"])
     self.assertDryRun()
     self.assertItems(["node-a"])
     self.assertQuery("mode", ["live"])
     self.assertDryRun()
+    self.assertFalse(self.rapi.GetLastRequestData())
+
+    self.rapi.AddResponse(serializer.DumpJson([]))
+    self.assertRaises(client.GanetiApiError, self.client.MigrateNode,
+                      "node-c", target_node="foonode")
+    self.assertEqual(self.rapi.CountPending(), 0)
+
+  def testMigrateNodeBodyData(self):
+    self.rapi.AddResponse(serializer.DumpJson([rlib2._NODE_MIGRATE_REQV1]))
+    self.rapi.AddResponse("27539")
+    self.assertEqual(27539, self.client.MigrateNode("node-a", dry_run=False,
+                                                    mode="live"))
+    self.assertHandler(rlib2.R_2_nodes_name_migrate)
+    self.assertItems(["node-a"])
+    self.assertFalse(self.rapi.GetLastHandler().queryargs)
+    self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()),
+                     { "mode": "live", })
+
+    self.rapi.AddResponse(serializer.DumpJson([rlib2._NODE_MIGRATE_REQV1]))
+    self.rapi.AddResponse("14219")
+    self.assertEqual(14219, self.client.MigrateNode("node-x", dry_run=True,
+                                                    target_node="node9",
+                                                    iallocator="ial"))
+    self.assertHandler(rlib2.R_2_nodes_name_migrate)
+    self.assertItems(["node-x"])
+    self.assertDryRun()
+    self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()),
+                     { "target_node": "node9", "iallocator": "ial", })
+
+    self.assertEqual(self.rapi.CountPending(), 0)
 
   def testGetNodeRole(self):
     self.rapi.AddResponse("\"master\"")
 
   def testGetNodeRole(self):
     self.rapi.AddResponse("\"master\"")
@@ -942,6 +949,24 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
     self.assertQuery("force", ["1"])
     self.assertEqual("\"master-candidate\"", self.rapi.GetLastRequestData())
 
     self.assertQuery("force", ["1"])
     self.assertEqual("\"master-candidate\"", self.rapi.GetLastRequestData())
 
+  def testPowercycleNode(self):
+    self.rapi.AddResponse("23051")
+    self.assertEqual(23051,
+        self.client.PowercycleNode("node5468", force=True))
+    self.assertHandler(rlib2.R_2_nodes_name_powercycle)
+    self.assertItems(["node5468"])
+    self.assertQuery("force", ["1"])
+    self.assertFalse(self.rapi.GetLastRequestData())
+    self.assertEqual(self.rapi.CountPending(), 0)
+
+  def testModifyNode(self):
+    self.rapi.AddResponse("3783")
+    job_id = self.client.ModifyNode("node16979.example.com", drained=True)
+    self.assertEqual(job_id, 3783)
+    self.assertHandler(rlib2.R_2_nodes_name_modify)
+    self.assertItems(["node16979.example.com"])
+    self.assertEqual(self.rapi.CountPending(), 0)
+
   def testGetNodeStorageUnits(self):
     self.rapi.AddResponse("42")
     self.assertEqual(42,
   def testGetNodeStorageUnits(self):
     self.rapi.AddResponse("42")
     self.assertEqual(42,
@@ -1135,6 +1160,14 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
     self.assertHandler(rlib2.R_2_instances_name_deactivate_disks)
     self.assertFalse(self.rapi.GetLastHandler().queryargs)
 
     self.assertHandler(rlib2.R_2_instances_name_deactivate_disks)
     self.assertFalse(self.rapi.GetLastHandler().queryargs)
 
+  def testRecreateInstanceDisks(self):
+    self.rapi.AddResponse("13553")
+    job_id = self.client.RecreateInstanceDisks("inst23153")
+    self.assertEqual(job_id, 13553)
+    self.assertItems(["inst23153"])
+    self.assertHandler(rlib2.R_2_instances_name_recreate_disks)
+    self.assertFalse(self.rapi.GetLastHandler().queryargs)
+
   def testGetInstanceConsole(self):
     self.rapi.AddResponse("26876")
     job_id = self.client.GetInstanceConsole("inst21491")
   def testGetInstanceConsole(self):
     self.rapi.AddResponse("26876")
     job_id = self.client.GetInstanceConsole("inst21491")
@@ -1164,24 +1197,48 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
       self.assertEqual(data["amount"], amount)
       self.assertEqual(self.rapi.CountPending(), 0)
 
       self.assertEqual(data["amount"], amount)
       self.assertEqual(self.rapi.CountPending(), 0)
 
+  def testGetGroupTags(self):
+    self.rapi.AddResponse("[]")
+    self.assertEqual([], self.client.GetGroupTags("fooGroup"))
+    self.assertHandler(rlib2.R_2_groups_name_tags)
+    self.assertItems(["fooGroup"])
+
+  def testAddGroupTags(self):
+    self.rapi.AddResponse("1234")
+    self.assertEqual(1234,
+        self.client.AddGroupTags("fooGroup", ["awesome"], dry_run=True))
+    self.assertHandler(rlib2.R_2_groups_name_tags)
+    self.assertItems(["fooGroup"])
+    self.assertDryRun()
+    self.assertQuery("tag", ["awesome"])
+
+  def testDeleteGroupTags(self):
+    self.rapi.AddResponse("25826")
+    self.assertEqual(25826, self.client.DeleteGroupTags("foo", ["awesome"],
+                                                        dry_run=True))
+    self.assertHandler(rlib2.R_2_groups_name_tags)
+    self.assertItems(["foo"])
+    self.assertDryRun()
+    self.assertQuery("tag", ["awesome"])
+
   def testQuery(self):
     for idx, what in enumerate(constants.QR_VIA_RAPI):
   def testQuery(self):
     for idx, what in enumerate(constants.QR_VIA_RAPI):
-      for idx2, filter_ in enumerate([None, ["?", "name"]]):
+      for idx2, qfilter in enumerate([None, ["?", "name"]]):
         job_id = 11010 + (idx << 4) + (idx2 << 16)
         fields = sorted(query.ALL_FIELDS[what].keys())[:10]
 
         self.rapi.AddResponse(str(job_id))
         job_id = 11010 + (idx << 4) + (idx2 << 16)
         fields = sorted(query.ALL_FIELDS[what].keys())[:10]
 
         self.rapi.AddResponse(str(job_id))
-        self.assertEqual(self.client.Query(what, fields, filter_=filter_),
+        self.assertEqual(self.client.Query(what, fields, qfilter=qfilter),
                          job_id)
         self.assertItems([what])
         self.assertHandler(rlib2.R_2_query)
         self.assertFalse(self.rapi.GetLastHandler().queryargs)
         data = serializer.LoadJson(self.rapi.GetLastRequestData())
         self.assertEqual(data["fields"], fields)
                          job_id)
         self.assertItems([what])
         self.assertHandler(rlib2.R_2_query)
         self.assertFalse(self.rapi.GetLastHandler().queryargs)
         data = serializer.LoadJson(self.rapi.GetLastRequestData())
         self.assertEqual(data["fields"], fields)
-        if filter_ is None:
-          self.assertTrue("filter" not in data)
+        if qfilter is None:
+          self.assertTrue("qfilter" not in data)
         else:
         else:
-          self.assertEqual(data["filter"], filter_)
+          self.assertEqual(data["qfilter"], qfilter)
         self.assertEqual(self.rapi.CountPending(), 0)
 
   def testQueryFields(self):
         self.assertEqual(self.rapi.CountPending(), 0)
 
   def testQueryFields(self):
@@ -1213,6 +1270,79 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
 
         self.assertEqual(self.rapi.CountPending(), 0)
 
 
         self.assertEqual(self.rapi.CountPending(), 0)
 
+  def testWaitForJobCompletionNoChange(self):
+    resp = serializer.DumpJson({
+      "status": constants.JOB_STATUS_WAITING,
+      })
+
+    for retries in [1, 5, 25]:
+      for _ in range(retries):
+        self.rapi.AddResponse(resp)
+
+      self.assertFalse(self.client.WaitForJobCompletion(22789, period=None,
+                                                        retries=retries))
+      self.assertHandler(rlib2.R_2_jobs_id)
+      self.assertItems(["22789"])
+
+      self.assertEqual(self.rapi.CountPending(), 0)
+
+  def testWaitForJobCompletionAlreadyFinished(self):
+    self.rapi.AddResponse(serializer.DumpJson({
+      "status": constants.JOB_STATUS_SUCCESS,
+      }))
+
+    self.assertTrue(self.client.WaitForJobCompletion(22793, period=None,
+                                                     retries=1))
+    self.assertHandler(rlib2.R_2_jobs_id)
+    self.assertItems(["22793"])
+
+    self.assertEqual(self.rapi.CountPending(), 0)
+
+  def testWaitForJobCompletionEmptyResponse(self):
+    self.rapi.AddResponse("{}")
+    self.assertFalse(self.client.WaitForJobCompletion(22793, period=None,
+                                                     retries=10))
+    self.assertHandler(rlib2.R_2_jobs_id)
+    self.assertItems(["22793"])
+
+    self.assertEqual(self.rapi.CountPending(), 0)
+
+  def testWaitForJobCompletionOutOfRetries(self):
+    for retries in [3, 10, 21]:
+      for _ in range(retries):
+        self.rapi.AddResponse(serializer.DumpJson({
+          "status": constants.JOB_STATUS_RUNNING,
+          }))
+
+      self.assertFalse(self.client.WaitForJobCompletion(30948, period=None,
+                                                        retries=retries - 1))
+      self.assertHandler(rlib2.R_2_jobs_id)
+      self.assertItems(["30948"])
+
+      self.assertEqual(self.rapi.CountPending(), 1)
+      self.rapi.ResetResponses()
+
+  def testWaitForJobCompletionSuccessAndFailure(self):
+    for retries in [1, 4, 13]:
+      for (success, end_status) in [(False, constants.JOB_STATUS_ERROR),
+                                    (True, constants.JOB_STATUS_SUCCESS)]:
+        for _ in range(retries):
+          self.rapi.AddResponse(serializer.DumpJson({
+            "status": constants.JOB_STATUS_RUNNING,
+            }))
+
+        self.rapi.AddResponse(serializer.DumpJson({
+          "status": end_status,
+          }))
+
+        result = self.client.WaitForJobCompletion(3187, period=None,
+                                                  retries=retries + 1)
+        self.assertEqual(result, success)
+        self.assertHandler(rlib2.R_2_jobs_id)
+        self.assertItems(["3187"])
+
+        self.assertEqual(self.rapi.CountPending(), 0)
+
 
 class RapiTestRunner(unittest.TextTestRunner):
   def run(self, *args):
 
 class RapiTestRunner(unittest.TextTestRunner):
   def run(self, *args):