From c0a146a129f7081a5f24dcbc0c60028d41c6c004 Mon Sep 17 00:00:00 2001
From: Michael Hanselmann <hansmi@google.com>
Date: Thu, 21 Jul 2011 15:20:45 +0200
Subject: [PATCH] Implement instance failover via RAPI

No idea why this was missed before.

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>
---
 doc/rapi.rst                        | 18 ++++++++++++++++
 lib/rapi/client.py                  | 32 +++++++++++++++++++++++++++++
 lib/rapi/connector.py               |  2 ++
 lib/rapi/rlib2.py                   | 19 +++++++++++++++++
 qa/ganeti-qa.py                     |  2 ++
 qa/qa_rapi.py                       |  8 ++++++++
 test/ganeti.rapi.client_unittest.py | 30 +++++++++++++++++++++++++++
 7 files changed, 111 insertions(+)

diff --git a/doc/rapi.rst b/doc/rapi.rst
index 49e82342c..f8853cbb9 100644
--- a/doc/rapi.rst
+++ b/doc/rapi.rst
@@ -876,6 +876,24 @@ Body parameters:
    :exclude: instance_name, live
 
 
+``/2/instances/[instance_name]/failover``
++++++++++++++++++++++++++++++++++++++++++
+
+Does a failover of an instance.
+
+Supports the following commands: ``PUT``.
+
+``PUT``
+~~~~~~~
+
+Returns a job ID.
+
+Body parameters:
+
+.. opcode_params:: OP_INSTANCE_FAILOVER
+   :exclude: instance_name
+
+
 ``/2/instances/[instance_name]/rename``
 ++++++++++++++++++++++++++++++++++++++++
 
diff --git a/lib/rapi/client.py b/lib/rapi/client.py
index 953c1ddd3..a4be072db 100644
--- a/lib/rapi/client.py
+++ b/lib/rapi/client.py
@@ -1073,6 +1073,38 @@ class GanetiRapiClient(object): # pylint: disable-msg=R0904
                              ("/%s/instances/%s/migrate" %
                               (GANETI_RAPI_VERSION, instance)), None, body)
 
+  def FailoverInstance(self, instance, iallocator=None,
+                       ignore_consistency=None, target_node=None):
+    """Does a failover of an instance.
+
+    @type instance: string
+    @param instance: Instance name
+    @type iallocator: string
+    @param iallocator: Iallocator for deciding the target node for
+      shared-storage instances
+    @type ignore_consistency: bool
+    @param ignore_consistency: Whether to ignore disk consistency
+    @type target_node: string
+    @param target_node: Target node for shared-storage instances
+    @rtype: string
+    @return: job id
+
+    """
+    body = {}
+
+    if iallocator is not None:
+      body["iallocator"] = iallocator
+
+    if ignore_consistency is not None:
+      body["ignore_consistency"] = ignore_consistency
+
+    if target_node is not None:
+      body["target_node"] = target_node
+
+    return self._SendRequest(HTTP_PUT,
+                             ("/%s/instances/%s/failover" %
+                              (GANETI_RAPI_VERSION, instance)), None, body)
+
   def RenameInstance(self, instance, new_name, ip_check=None, name_check=None):
     """Changes the name of an instance.
 
diff --git a/lib/rapi/connector.py b/lib/rapi/connector.py
index cb4bb296c..55dfe86f3 100644
--- a/lib/rapi/connector.py
+++ b/lib/rapi/connector.py
@@ -211,6 +211,8 @@ def GetHandlers(node_name_pattern, instance_name_pattern,
       rlib2.R_2_instances_name_export,
     re.compile(r'^/2/instances/(%s)/migrate$' % instance_name_pattern):
       rlib2.R_2_instances_name_migrate,
+    re.compile(r'^/2/instances/(%s)/failover$' % instance_name_pattern):
+      rlib2.R_2_instances_name_failover,
     re.compile(r'^/2/instances/(%s)/rename$' % instance_name_pattern):
       rlib2.R_2_instances_name_rename,
     re.compile(r'^/2/instances/(%s)/modify$' % instance_name_pattern):
diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py
index 8d14ae48e..37df839e2 100644
--- a/lib/rapi/rlib2.py
+++ b/lib/rapi/rlib2.py
@@ -1122,6 +1122,25 @@ class R_2_instances_name_migrate(baserlib.R_Generic):
     return baserlib.SubmitJob([op])
 
 
+class R_2_instances_name_failover(baserlib.R_Generic):
+  """/2/instances/[instance_name]/failover resource.
+
+  """
+  def PUT(self):
+    """Does a failover of an instance.
+
+    @return: a job id
+
+    """
+    baserlib.CheckType(self.request_body, dict, "Body contents")
+
+    op = baserlib.FillOpcode(opcodes.OpInstanceFailover, self.request_body, {
+      "instance_name": self.items[0],
+      })
+
+    return baserlib.SubmitJob([op])
+
+
 def _ParseRenameInstanceRequest(name, data):
   """Parses a request for renaming an instance.
 
diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py
index 3da635172..8b3d8f93f 100755
--- a/qa/ganeti-qa.py
+++ b/qa/ganeti-qa.py
@@ -366,6 +366,8 @@ def RunHardwareFailureTests(instance, pnode, snode):
 
   """
   RunTestIf("instance-failover", qa_instance.TestInstanceFailover, instance)
+  RunTestIf(["instance-failover", "rapi"],
+            qa_rapi.TestRapiInstanceFailover, instance)
 
   RunTestIf("instance-migrate", qa_instance.TestInstanceMigrate, instance)
   RunTestIf(["instance-migrate", "rapi"],
diff --git a/qa/qa_rapi.py b/qa/qa_rapi.py
index cdf454a44..e06d5e8de 100644
--- a/qa/qa_rapi.py
+++ b/qa/qa_rapi.py
@@ -590,6 +590,14 @@ def TestRapiInstanceMigrate(instance):
   _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
 
 
+def TestRapiInstanceFailover(instance):
+  """Test failing over instance via RAPI"""
+  # Move to secondary node
+  _WaitForRapiJob(_rapi_client.FailoverInstance(instance["name"]))
+  # And back to previous primary
+  _WaitForRapiJob(_rapi_client.FailoverInstance(instance["name"]))
+
+
 def TestRapiInstanceRename(rename_source, rename_target):
   """Test renaming instance via RAPI"""
   _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
diff --git a/test/ganeti.rapi.client_unittest.py b/test/ganeti.rapi.client_unittest.py
index e512be28f..d7af45ad3 100755
--- a/test/ganeti.rapi.client_unittest.py
+++ b/test/ganeti.rapi.client_unittest.py
@@ -738,6 +738,36 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
         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")
-- 
GitLab