From 539d65ba090ea0c10336d40774d59fbf67b07b2d Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Mon, 17 Oct 2011 15:58:19 +0200 Subject: [PATCH] RAPI: Fix resource for replacing disks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit d1c172deb4f inadvertently changes the β/2/instances/[instance_name]/replace-disksβ resource to use body parameters. There were no QA tests and the issue wasn't noticed. This patch re-introduces support for query parameters and adds a QA test. Signed-off-by: Michael Hanselmann <hansmi@google.com> Reviewed-by: Andrea Spadaccini <spadaccio@google.com> --- doc/rapi.rst | 15 +++++++------- lib/rapi/client.py | 15 ++++++-------- lib/rapi/rlib2.py | 32 +++++++++++++++++++++-------- qa/ganeti-qa.py | 1 + qa/qa_rapi.py | 8 ++++++++ test/ganeti.rapi.client_unittest.py | 7 ++----- test/ganeti.rapi.rlib2_unittest.py | 8 ++++++++ 7 files changed, 56 insertions(+), 30 deletions(-) diff --git a/doc/rapi.rst b/doc/rapi.rst index 7ed29a803..97a1112ff 100644 --- a/doc/rapi.rst +++ b/doc/rapi.rst @@ -789,16 +789,15 @@ It supports the following commands: ``POST``. ``POST`` ~~~~~~~~ -Takes the parameters ``mode`` (one of ``replace_on_primary``, -``replace_on_secondary``, ``replace_new_secondary`` or -``replace_auto``), ``disks`` (comma separated list of disk indexes), -``remote_node`` and ``iallocator``. +Returns a job ID. + +Body parameters: -Either ``remote_node`` or ``iallocator`` needs to be defined when using -``mode=replace_new_secondary``. +.. opcode_params:: OP_INSTANCE_REPLACE_DISKS + :exclude: instance_name -``mode`` is a mandatory parameter. ``replace_auto`` tries to determine -the broken disk(s) on its own and replacing it. +Ganeti 2.4 and below used query parameters. Those are deprecated and +should no longer be used. ``/2/instances/[instance_name]/activate-disks`` diff --git a/lib/rapi/client.py b/lib/rapi/client.py index dac670b1b..be39202c7 100644 --- a/lib/rapi/client.py +++ b/lib/rapi/client.py @@ -956,7 +956,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904 (GANETI_RAPI_VERSION, instance)), query, None) def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO, - remote_node=None, iallocator=None, dry_run=False): + remote_node=None, iallocator=None): """Replaces disks on an instance. @type instance: str @@ -971,8 +971,6 @@ class GanetiRapiClient(object): # pylint: disable=R0904 @type iallocator: str or None @param iallocator: instance allocator plugin to use (for use with replace_auto mode) - @type dry_run: bool - @param dry_run: whether to perform a dry run @rtype: string @return: job id @@ -982,18 +980,17 @@ class GanetiRapiClient(object): # pylint: disable=R0904 ("mode", mode), ] - if disks: + # TODO: Convert to body parameters + + if disks is not None: query.append(("disks", ",".join(str(idx) for idx in disks))) - if remote_node: + if remote_node is not None: query.append(("remote_node", remote_node)) - if iallocator: + if iallocator is not None: query.append(("iallocator", iallocator)) - if dry_run: - query.append(("dry-run", 1)) - return self._SendRequest(HTTP_POST, ("/%s/instances/%s/replace-disks" % (GANETI_RAPI_VERSION, instance)), query, None) diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py index 0a958da11..97ad7cd42 100644 --- a/lib/rapi/rlib2.py +++ b/lib/rapi/rlib2.py @@ -999,16 +999,19 @@ def _ParseInstanceReplaceDisksRequest(name, data): # Parse disks try: - raw_disks = data["disks"] + raw_disks = data.pop("disks") except KeyError: pass else: - if not ht.TListOf(ht.TInt)(raw_disks): # pylint: disable=E1102 - # Backwards compatibility for strings of the format "1, 2, 3" - try: - data["disks"] = [int(part) for part in raw_disks.split(",")] - except (TypeError, ValueError), err: - raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err)) + if raw_disks: + if ht.TListOf(ht.TInt)(raw_disks): # pylint: disable=E1102 + data["disks"] = raw_disks + else: + # Backwards compatibility for strings of the format "1, 2, 3" + try: + data["disks"] = [int(part) for part in raw_disks.split(",")] + except (TypeError, ValueError), err: + raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err)) return baserlib.FillOpcode(opcodes.OpInstanceReplaceDisks, data, override) @@ -1021,7 +1024,20 @@ class R_2_instances_name_replace_disks(baserlib.R_Generic): """Replaces disks on an instance. """ - op = _ParseInstanceReplaceDisksRequest(self.items[0], self.request_body) + if self.request_body: + body = self.request_body + elif self.queryargs: + # Legacy interface, do not modify/extend + body = { + "remote_node": self._checkStringVariable("remote_node", default=None), + "mode": self._checkStringVariable("mode", default=None), + "disks": self._checkStringVariable("disks", default=None), + "iallocator": self._checkStringVariable("iallocator", default=None), + } + else: + body = {} + + op = _ParseInstanceReplaceDisksRequest(self.items[0], body) return baserlib.SubmitJob([op]) diff --git a/qa/ganeti-qa.py b/qa/ganeti-qa.py index 8b0c33889..a2a4eb058 100755 --- a/qa/ganeti-qa.py +++ b/qa/ganeti-qa.py @@ -382,6 +382,7 @@ def RunHardwareFailureTests(instance, pnode, snode): if qa_config.TestEnabled("instance-replace-disks"): othernode = qa_config.AcquireNode(exclude=[pnode, snode]) try: + RunTestIf("rapi", qa_rapi.TestRapiInstanceReplaceDisks, instance) RunTest(qa_instance.TestReplaceDisks, instance, pnode, snode, othernode) finally: diff --git a/qa/qa_rapi.py b/qa/qa_rapi.py index 02218463c..a4c5921ce 100644 --- a/qa/qa_rapi.py +++ b/qa/qa_rapi.py @@ -618,6 +618,14 @@ def TestRapiInstanceReinstall(instance): _WaitForRapiJob(_rapi_client.ReinstallInstance(instance["name"])) +def TestRapiInstanceReplaceDisks(instance): + """Test replacing instance disks via RAPI""" + _WaitForRapiJob(_rapi_client.ReplaceInstanceDisks(instance["name"], + mode=constants.REPLACE_DISK_AUTO, disks=[])) + _WaitForRapiJob(_rapi_client.ReplaceInstanceDisks(instance["name"], + mode=constants.REPLACE_DISK_SEC, disks="0")) + + def TestRapiInstanceModify(instance): """Test modifying instance via RAPI""" def _ModifyInstance(**kwargs): diff --git a/test/ganeti.rapi.client_unittest.py b/test/ganeti.rapi.client_unittest.py index d7af45ad3..d441c1abc 100755 --- a/test/ganeti.rapi.client_unittest.py +++ b/test/ganeti.rapi.client_unittest.py @@ -669,24 +669,21 @@ class GanetiRapiClientTests(testutils.GanetiTestCase): 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.assertDryRun() 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.assertDryRun() self.rapi.AddResponse("5175") self.assertEqual(5175, self.client.ReplaceInstanceDisks("instance-moo")) diff --git a/test/ganeti.rapi.rlib2_unittest.py b/test/ganeti.rapi.rlib2_unittest.py index 6584c38e8..de8274556 100755 --- a/test/ganeti.rapi.rlib2_unittest.py +++ b/test/ganeti.rapi.rlib2_unittest.py @@ -529,6 +529,14 @@ class TestParseInstanceReplaceDisksRequest(unittest.TestCase): self.assertFalse(hasattr(op, "iallocator")) self.assertFalse(hasattr(op, "disks")) + def testNoDisks(self): + self.assertRaises(http.HttpBadRequest, self.Parse, "inst20661", {}) + + for disks in [None, "", {}]: + self.assertRaises(http.HttpBadRequest, self.Parse, "inst20661", { + "disks": disks, + }) + def testWrong(self): self.assertRaises(http.HttpBadRequest, self.Parse, "inst", { "mode": constants.REPLACE_DISK_AUTO, -- GitLab