From 42d4d8b93a9df8db378f293e3bf068d76f032468 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Fri, 16 Sep 2011 11:38:15 +0200 Subject: [PATCH] RAPI: Add resource to powercycle node Signed-off-by: Michael Hanselmann <hansmi@google.com> Reviewed-by: Iustin Pop <iustin@google.com> --- doc/rapi.rst | 11 +++++++++++ lib/rapi/client.py | 20 ++++++++++++++++++++ lib/rapi/connector.py | 2 ++ lib/rapi/rlib2.py | 16 ++++++++++++++++ test/docs_unittest.py | 1 - test/ganeti.rapi.client_unittest.py | 10 ++++++++++ test/ganeti.rapi.rlib2_unittest.py | 20 ++++++++++++++++++++ 7 files changed, 79 insertions(+), 1 deletion(-) diff --git a/doc/rapi.rst b/doc/rapi.rst index 20602b052..23805d6f7 100644 --- a/doc/rapi.rst +++ b/doc/rapi.rst @@ -1241,6 +1241,17 @@ It supports the following commands: ``GET``. Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.N_FIELDS))` +``/2/nodes/[node_name]/powercycle`` ++++++++++++++++++++++++++++++++++++ + +Powercycles a node. Supports the following commands: ``POST``. + +``POST`` +~~~~~~~~ + +Returns a job ID. + + ``/2/nodes/[node_name]/evacuate`` +++++++++++++++++++++++++++++++++ diff --git a/lib/rapi/client.py b/lib/rapi/client.py index 8ad0d95a1..9f3002a80 100644 --- a/lib/rapi/client.py +++ b/lib/rapi/client.py @@ -1474,6 +1474,26 @@ class GanetiRapiClient(object): # pylint: disable=R0904 ("/%s/nodes/%s/role" % (GANETI_RAPI_VERSION, node)), query, role) + def PowercycleNode(self, node, force=False): + """Powercycles a node. + + @type node: string + @param node: Node name + @type force: bool + @param force: Whether to force the operation + + @rtype: string + @return: job id + + """ + query = [ + ("force", force), + ] + + return self._SendRequest(HTTP_POST, + ("/%s/nodes/%s/powercycle" % + (GANETI_RAPI_VERSION, node)), query, None) + def GetNodeStorageUnits(self, node, storage_type, output_fields): """Gets the storage units for a node. diff --git a/lib/rapi/connector.py b/lib/rapi/connector.py index 8d0d3951a..a20027bee 100644 --- a/lib/rapi/connector.py +++ b/lib/rapi/connector.py @@ -107,6 +107,8 @@ def GetHandlers(node_name_pattern, instance_name_pattern, "/2/nodes": rlib2.R_2_nodes, re.compile(r"^/2/nodes/(%s)$" % node_name_pattern): rlib2.R_2_nodes_name, + re.compile(r"^/2/nodes/(%s)/powercycle$" % node_name_pattern): + rlib2.R_2_nodes_name_powercycle, re.compile(r"^/2/nodes/(%s)/tags$" % node_name_pattern): rlib2.R_2_nodes_name_tags, re.compile(r"^/2/nodes/(%s)/role$" % node_name_pattern): diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py index 0f3f008e4..1ecf732a1 100644 --- a/lib/rapi/rlib2.py +++ b/lib/rapi/rlib2.py @@ -397,6 +397,22 @@ class R_2_nodes_name(baserlib.OpcodeResource): return baserlib.MapFields(N_FIELDS, result[0]) +class R_2_nodes_name_powercycle(baserlib.OpcodeResource): + """/2/nodes/[node_name]/powercycle resource. + + """ + POST_OPCODE = opcodes.OpNodePowercycle + + def GetPostOpInput(self): + """Tries to powercycle a node. + + """ + return (self.request_body, { + "node_name": self.items[0], + "force": self.useForce(), + }) + + class R_2_nodes_name_role(baserlib.OpcodeResource): """/2/nodes/[node_name]/role resource. diff --git a/test/docs_unittest.py b/test/docs_unittest.py index a695280a7..0db111f2f 100755 --- a/test/docs_unittest.py +++ b/test/docs_unittest.py @@ -53,7 +53,6 @@ RAPI_OPCODE_EXCLUDE = frozenset([ opcodes.OpClusterVerifyDisks, opcodes.OpInstanceChangeGroup, opcodes.OpInstanceMove, - opcodes.OpNodePowercycle, opcodes.OpNodeQueryvols, opcodes.OpOobCommand, opcodes.OpTagsSearch, diff --git a/test/ganeti.rapi.client_unittest.py b/test/ganeti.rapi.client_unittest.py index 81d6ea6da..758490c22 100755 --- a/test/ganeti.rapi.client_unittest.py +++ b/test/ganeti.rapi.client_unittest.py @@ -973,6 +973,16 @@ class GanetiRapiClientTests(testutils.GanetiTestCase): 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 testGetNodeStorageUnits(self): self.rapi.AddResponse("42") self.assertEqual(42, diff --git a/test/ganeti.rapi.rlib2_unittest.py b/test/ganeti.rapi.rlib2_unittest.py index 74176adc1..88a20a0aa 100755 --- a/test/ganeti.rapi.rlib2_unittest.py +++ b/test/ganeti.rapi.rlib2_unittest.py @@ -272,6 +272,26 @@ class TestNodeEvacuate(unittest.TestCase): self.assertRaises(IndexError, cl.GetNextSubmittedJob) +class TestNodePowercycle(unittest.TestCase): + def test(self): + clfactory = _FakeClientFactory(_FakeClient) + handler = _CreateHandler(rlib2.R_2_nodes_name_powercycle, ["node20744"], { + "force": ["1"], + }, None, clfactory) + job_id = handler.POST() + + cl = clfactory.GetNextClient() + self.assertRaises(IndexError, clfactory.GetNextClient) + + (exp_job_id, (op, )) = cl.GetNextSubmittedJob() + self.assertEqual(job_id, exp_job_id) + self.assertTrue(isinstance(op, opcodes.OpNodePowercycle)) + self.assertEqual(op.node_name, "node20744") + self.assertTrue(op.force) + + self.assertRaises(IndexError, cl.GetNextSubmittedJob) + + class TestGroupAssignNodes(unittest.TestCase): def test(self): clfactory = _FakeClientFactory(_FakeClient) -- GitLab