Commit 539d65ba authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

RAPI: Fix resource for replacing disks

Commit d1c172de

 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: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarAndrea Spadaccini <spadaccio@google.com>
parent 58f6738c
...@@ -789,16 +789,15 @@ It supports the following commands: ``POST``. ...@@ -789,16 +789,15 @@ It supports the following commands: ``POST``.
``POST`` ``POST``
~~~~~~~~ ~~~~~~~~
Takes the parameters ``mode`` (one of ``replace_on_primary``, Returns a job ID.
``replace_on_secondary``, ``replace_new_secondary`` or
``replace_auto``), ``disks`` (comma separated list of disk indexes), Body parameters:
``remote_node`` and ``iallocator``.
Either ``remote_node`` or ``iallocator`` needs to be defined when using .. opcode_params:: OP_INSTANCE_REPLACE_DISKS
``mode=replace_new_secondary``. :exclude: instance_name
``mode`` is a mandatory parameter. ``replace_auto`` tries to determine Ganeti 2.4 and below used query parameters. Those are deprecated and
the broken disk(s) on its own and replacing it. should no longer be used.
``/2/instances/[instance_name]/activate-disks`` ``/2/instances/[instance_name]/activate-disks``
......
...@@ -956,7 +956,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904 ...@@ -956,7 +956,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
(GANETI_RAPI_VERSION, instance)), query, None) (GANETI_RAPI_VERSION, instance)), query, None)
def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO, 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. """Replaces disks on an instance.
@type instance: str @type instance: str
...@@ -971,8 +971,6 @@ class GanetiRapiClient(object): # pylint: disable=R0904 ...@@ -971,8 +971,6 @@ class GanetiRapiClient(object): # pylint: disable=R0904
@type iallocator: str or None @type iallocator: str or None
@param iallocator: instance allocator plugin to use (for use with @param iallocator: instance allocator plugin to use (for use with
replace_auto mode) replace_auto mode)
@type dry_run: bool
@param dry_run: whether to perform a dry run
@rtype: string @rtype: string
@return: job id @return: job id
...@@ -982,18 +980,17 @@ class GanetiRapiClient(object): # pylint: disable=R0904 ...@@ -982,18 +980,17 @@ class GanetiRapiClient(object): # pylint: disable=R0904
("mode", mode), ("mode", mode),
] ]
if disks: # TODO: Convert to body parameters
if disks is not None:
query.append(("disks", ",".join(str(idx) for idx in disks))) 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)) query.append(("remote_node", remote_node))
if iallocator: if iallocator is not None:
query.append(("iallocator", iallocator)) query.append(("iallocator", iallocator))
if dry_run:
query.append(("dry-run", 1))
return self._SendRequest(HTTP_POST, return self._SendRequest(HTTP_POST,
("/%s/instances/%s/replace-disks" % ("/%s/instances/%s/replace-disks" %
(GANETI_RAPI_VERSION, instance)), query, None) (GANETI_RAPI_VERSION, instance)), query, None)
......
...@@ -999,16 +999,19 @@ def _ParseInstanceReplaceDisksRequest(name, data): ...@@ -999,16 +999,19 @@ def _ParseInstanceReplaceDisksRequest(name, data):
# Parse disks # Parse disks
try: try:
raw_disks = data["disks"] raw_disks = data.pop("disks")
except KeyError: except KeyError:
pass pass
else: else:
if not ht.TListOf(ht.TInt)(raw_disks): # pylint: disable=E1102 if raw_disks:
# Backwards compatibility for strings of the format "1, 2, 3" if ht.TListOf(ht.TInt)(raw_disks): # pylint: disable=E1102
try: data["disks"] = raw_disks
data["disks"] = [int(part) for part in raw_disks.split(",")] else:
except (TypeError, ValueError), err: # Backwards compatibility for strings of the format "1, 2, 3"
raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err)) 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) return baserlib.FillOpcode(opcodes.OpInstanceReplaceDisks, data, override)
...@@ -1021,7 +1024,20 @@ class R_2_instances_name_replace_disks(baserlib.R_Generic): ...@@ -1021,7 +1024,20 @@ class R_2_instances_name_replace_disks(baserlib.R_Generic):
"""Replaces disks on an instance. """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]) return baserlib.SubmitJob([op])
......
...@@ -382,6 +382,7 @@ def RunHardwareFailureTests(instance, pnode, snode): ...@@ -382,6 +382,7 @@ def RunHardwareFailureTests(instance, pnode, snode):
if qa_config.TestEnabled("instance-replace-disks"): if qa_config.TestEnabled("instance-replace-disks"):
othernode = qa_config.AcquireNode(exclude=[pnode, snode]) othernode = qa_config.AcquireNode(exclude=[pnode, snode])
try: try:
RunTestIf("rapi", qa_rapi.TestRapiInstanceReplaceDisks, instance)
RunTest(qa_instance.TestReplaceDisks, RunTest(qa_instance.TestReplaceDisks,
instance, pnode, snode, othernode) instance, pnode, snode, othernode)
finally: finally:
......
...@@ -618,6 +618,14 @@ def TestRapiInstanceReinstall(instance): ...@@ -618,6 +618,14 @@ def TestRapiInstanceReinstall(instance):
_WaitForRapiJob(_rapi_client.ReinstallInstance(instance["name"])) _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): def TestRapiInstanceModify(instance):
"""Test modifying instance via RAPI""" """Test modifying instance via RAPI"""
def _ModifyInstance(**kwargs): def _ModifyInstance(**kwargs):
......
...@@ -669,24 +669,21 @@ class GanetiRapiClientTests(testutils.GanetiTestCase): ...@@ -669,24 +669,21 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
def testReplaceInstanceDisks(self): def testReplaceInstanceDisks(self):
self.rapi.AddResponse("999") self.rapi.AddResponse("999")
job_id = self.client.ReplaceInstanceDisks("instance-name", 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.assertEqual(999, job_id)
self.assertHandler(rlib2.R_2_instances_name_replace_disks) self.assertHandler(rlib2.R_2_instances_name_replace_disks)
self.assertItems(["instance-name"]) self.assertItems(["instance-name"])
self.assertQuery("disks", ["0,1"]) self.assertQuery("disks", ["0,1"])
self.assertQuery("mode", ["replace_auto"]) self.assertQuery("mode", ["replace_auto"])
self.assertQuery("iallocator", ["hail"]) self.assertQuery("iallocator", ["hail"])
self.assertDryRun()
self.rapi.AddResponse("1000") self.rapi.AddResponse("1000")
job_id = self.client.ReplaceInstanceDisks("instance-bar", job_id = self.client.ReplaceInstanceDisks("instance-bar",
disks=[1], mode="replace_on_secondary", remote_node="foo-node", disks=[1], mode="replace_on_secondary", remote_node="foo-node")
dry_run=True)
self.assertEqual(1000, job_id) self.assertEqual(1000, job_id)
self.assertItems(["instance-bar"]) self.assertItems(["instance-bar"])
self.assertQuery("disks", ["1"]) self.assertQuery("disks", ["1"])
self.assertQuery("remote_node", ["foo-node"]) self.assertQuery("remote_node", ["foo-node"])
self.assertDryRun()
self.rapi.AddResponse("5175") self.rapi.AddResponse("5175")
self.assertEqual(5175, self.client.ReplaceInstanceDisks("instance-moo")) self.assertEqual(5175, self.client.ReplaceInstanceDisks("instance-moo"))
......
...@@ -529,6 +529,14 @@ class TestParseInstanceReplaceDisksRequest(unittest.TestCase): ...@@ -529,6 +529,14 @@ class TestParseInstanceReplaceDisksRequest(unittest.TestCase):
self.assertFalse(hasattr(op, "iallocator")) self.assertFalse(hasattr(op, "iallocator"))
self.assertFalse(hasattr(op, "disks")) 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): def testWrong(self):
self.assertRaises(http.HttpBadRequest, self.Parse, "inst", self.assertRaises(http.HttpBadRequest, self.Parse, "inst",
{ "mode": constants.REPLACE_DISK_AUTO, { "mode": constants.REPLACE_DISK_AUTO,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment