The change is not backwards compatible, see the updated NEWS file.

Signed-off-by: Michael Hanselmann <[email protected]>
---
 NEWS                                |    4 ++++
 doc/rapi.rst                        |   25 ++++++++-----------------
 lib/rapi/client.py                  |   33 +++++++++++++++++++++------------
 lib/rapi/rlib2.py                   |   35 ++++++-----------------------------
 test/ganeti.rapi.client_unittest.py |    6 ++++--
 5 files changed, 43 insertions(+), 60 deletions(-)

diff --git a/NEWS b/NEWS
index 172277a..56322fb 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,10 @@ Version 2.5.0 beta1
 - The default of the ``/2/instances/[instance_name]/rename`` RAPI
   resource's ``ip_check`` parameter changed from ``True`` to ``False``
   to match the underlying LUXI interface
+- The ``/2/nodes/[node_name]/evacuate`` RAPI resource was changed to use
+  body parameters, see :doc:`RAPI documentation <rapi>`. Neither server
+  nor RAPI client maintain backwards-compatibility as the underlying
+  operation changed in an incompatible way.
 - When creating file-based instances via RAPI, the ``file_driver``
   parameter no longer defaults to ``loop`` and must be specified
 - The deprecated "bridge" nic parameter is no longer supported. Use
diff --git a/doc/rapi.rst b/doc/rapi.rst
index fbc9970..f5aa245 100644
--- a/doc/rapi.rst
+++ b/doc/rapi.rst
@@ -1161,32 +1161,23 @@ It supports the following commands: ``GET``.
 ``/2/nodes/[node_name]/evacuate``
 +++++++++++++++++++++++++++++++++
 
-Evacuates all secondary instances off a node.
+Evacuates instances off a node.
 
 It supports the following commands: ``POST``.
 
 ``POST``
 ~~~~~~~~
 
-To evacuate a node, either one of the ``iallocator`` or ``remote_node``
-parameters must be passed::
+Returns a job ID. The result of the job will contain the IDs of the
+individual jobs submitted to evacuate the node.
 
-    evacuate?iallocator=[iallocator]
-    evacuate?remote_node=[nodeX.example.com]
-
-The result value will be a list, each element being a triple of the job
-id (for this specific evacuation), the instance which is being evacuated
-by this job, and the node to which it is being relocated. In case the
-node is already empty, the result will be an empty list (without any
-jobs being submitted).
+Body parameters:
 
-And additional parameter ``early_release`` signifies whether to try to
-parallelize the evacuations, at the risk of increasing I/O contention
-and increasing the chances of data loss, if the primary node of any of
-the instances being evacuated is not fully healthy.
+.. opcode_params:: OP_NODE_EVACUATE
+   :exclude: nodes
 
-If the dry-run parameter was specified, then the evacuation jobs were
-not actually submitted, and the job IDs will be null.
+Up to and including Ganeti 2.4 query arguments were used. Those are no
+longer supported.
 
 
 ``/2/nodes/[node_name]/migrate``
diff --git a/lib/rapi/client.py b/lib/rapi/client.py
index d2aa4ac..27dd89d 100644
--- a/lib/rapi/client.py
+++ b/lib/rapi/client.py
@@ -1250,7 +1250,8 @@ class GanetiRapiClient(object): # pylint: 
disable-msg=R0904
                              None, None)
 
   def EvacuateNode(self, node, iallocator=None, remote_node=None,
-                   dry_run=False, early_release=False):
+                   dry_run=False, early_release=None,
+                   primary=None, secondary=None):
     """Evacuates instances from a Ganeti node.
 
     @type node: str
@@ -1263,11 +1264,13 @@ class GanetiRapiClient(object): # pylint: 
disable-msg=R0904
     @param dry_run: whether to perform a dry run
     @type early_release: bool
     @param early_release: whether to enable parallelization
+    @type primary: bool
+    @param primary: Whether to evacuate primary instances
+    @type secondary: bool
+    @param secondary: Whether to evacuate secondary instances
 
-    @rtype: list
-    @return: list of (job ID, instance name, new secondary node); if
-        dry_run was specified, then the actual move jobs were not
-        submitted and the job IDs will be C{None}
+    @rtype: string
+    @return: job id
 
     @raises GanetiApiError: if an iallocator and remote_node are both
         specified
@@ -1277,18 +1280,24 @@ class GanetiRapiClient(object): # pylint: 
disable-msg=R0904
       raise GanetiApiError("Only one of iallocator or remote_node can be used")
 
     query = []
-    if iallocator:
-      query.append(("iallocator", iallocator))
-    if remote_node:
-      query.append(("remote_node", remote_node))
     if dry_run:
       query.append(("dry-run", 1))
-    if early_release:
-      query.append(("early_release", 1))
+
+    body = {}
+    if iallocator is not None:
+      body["iallocator"] = iallocator
+    if remote_node is not None:
+      body["remote_node"] = remote_node
+    if early_release is not None:
+      body["early_release"] = early_release
+    if primary is not None:
+      body["primary"] = primary
+    if secondary is not None:
+      body["secondary"] = secondary
 
     return self._SendRequest(HTTP_POST,
                              ("/%s/nodes/%s/evacuate" %
-                              (GANETI_RAPI_VERSION, node)), query, None)
+                              (GANETI_RAPI_VERSION, node)), query, body)
 
   def MigrateNode(self, node, mode=None, dry_run=False, iallocator=None,
                   target_node=None):
diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py
index d6bd9ce..b3c5a88 100644
--- a/lib/rapi/rlib2.py
+++ b/lib/rapi/rlib2.py
@@ -414,38 +414,15 @@ class R_2_nodes_name_evacuate(baserlib.R_Generic):
 
   """
   def POST(self):
-    """Evacuate all secondary instances off a node.
+    """Evacuate all instances off a node.
 
     """
-    node_name = self.items[0]
-    remote_node = self._checkStringVariable("remote_node", default=None)
-    iallocator = self._checkStringVariable("iallocator", default=None)
-    early_r = bool(self._checkIntVariable("early_release", default=0))
-    dry_run = bool(self.dryRun())
-
-    cl = baserlib.GetClient()
-
-    op = opcodes.OpNodeEvacStrategy(nodes=[node_name],
-                                    iallocator=iallocator,
-                                    remote_node=remote_node)
-
-    job_id = baserlib.SubmitJob([op], cl)
-    # we use custom feedback function, instead of print we log the status
-    result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
+    op = baserlib.FillOpcode(opcodes.OpNodeEvacuate, self.request_body, {
+      "nodes": [node_name],
+      "dry_run": self.dryRun(),
+      })
 
-    jobs = []
-    for iname, node in result[0]:
-      if dry_run:
-        jid = None
-      else:
-        op = opcodes.OpInstanceReplaceDisks(instance_name=iname,
-                                            remote_node=node, disks=[],
-                                            mode=constants.REPLACE_DISK_CHG,
-                                            early_release=early_r)
-        jid = baserlib.SubmitJob([op])
-      jobs.append((jid, iname, node))
-
-    return jobs
+    return baserlib.SubmitJob([op])
 
 
 class R_2_nodes_name_migrate(baserlib.R_Generic):
diff --git a/test/ganeti.rapi.client_unittest.py 
b/test/ganeti.rapi.client_unittest.py
index 23dacf8..844513e 100755
--- a/test/ganeti.rapi.client_unittest.py
+++ b/test/ganeti.rapi.client_unittest.py
@@ -822,13 +822,15 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
     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.rapi.AddResponse("8888")
     job_id = self.client.EvacuateNode("node-3", iallocator="hail", 
dry_run=True)
     self.assertEqual(8888, job_id)
     self.assertItems(["node-3"])
-    self.assertQuery("iallocator", ["hail"])
+    self.assertEqual(serializer.LoadJson(self.rapi.GetLastRequestData()),
+                     { "iallocator": "hail", })
     self.assertDryRun()
 
     self.assertRaises(client.GanetiApiError,
-- 
1.7.3.5

Reply via email to