diff --git a/doc/rapi.rst b/doc/rapi.rst index 956ecf7b91f577447aa7824a0475a5d55e504b3f..f994bd3b46a256febc222a327e99e17353eaf4d0 100644 --- a/doc/rapi.rst +++ b/doc/rapi.rst @@ -824,6 +824,26 @@ It supports the following commands: ``PUT``. Takes no parameters. +``/2/instances/[instance_name]/disk/[disk_index]/grow`` ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Grows one disk of an instance. + +Supports the following commands: ``POST``. + +``POST`` +~~~~~~~~ + +Returns a job ID. + +Body parameters: + +``amount`` (int, required) + Amount of disk space to add. +``wait_for_sync`` (bool) + Whether to wait for the disk to synchronize. + + ``/2/instances/[instance_name]/prepare-export`` +++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/lib/rapi/client.py b/lib/rapi/client.py index f6fcb42640e5e518b6ab1496e5948e44c7ddfeb9..8e22afbe4192b3725f348d179b8eeaa8bc9afc62 100644 --- a/lib/rapi/client.py +++ b/lib/rapi/client.py @@ -757,6 +757,35 @@ class GanetiRapiClient(object): ("/%s/instances/%s/modify" % (GANETI_RAPI_VERSION, instance)), None, body) + def GrowInstanceDisk(self, instance, disk, amount, wait_for_sync=None): + """Grows a disk of an instance. + + More details for parameters can be found in the RAPI documentation. + + @type instance: string + @param instance: Instance name + @type disk: integer + @param disk: Disk index + @type amount: integer + @param amount: Grow disk by this amount (MiB) + @type wait_for_sync: bool + @param wait_for_sync: Wait for disk to synchronize + @rtype: int + @return: job id + + """ + body = { + "amount": amount, + } + + if wait_for_sync is not None: + body["wait_for_sync"] = wait_for_sync + + return self._SendRequest(HTTP_POST, + ("/%s/instances/%s/disk/%s/grow" % + (GANETI_RAPI_VERSION, instance, disk)), + None, body) + def GetInstanceTags(self, instance): """Gets tags for an instance. diff --git a/lib/rapi/connector.py b/lib/rapi/connector.py index 17e36622f46d5f7714b12a100e5bdfa43228f183..f6f2e11b1fde98843970e6a839e161cff0723ab6 100644 --- a/lib/rapi/connector.py +++ b/lib/rapi/connector.py @@ -38,6 +38,7 @@ from ganeti.rapi import rlib2 _NAME_PATTERN = r"[\w\._-]+" +_DISK_PATTERN = r"\d+" # the connection map is created at the end of this file CONNECTOR = {} @@ -147,7 +148,7 @@ class R_2(baserlib.R_Generic): def GetHandlers(node_name_pattern, instance_name_pattern, - group_name_pattern, job_id_pattern): + group_name_pattern, job_id_pattern, disk_pattern): """Returns all supported resources and their handlers. """ @@ -211,6 +212,9 @@ def GetHandlers(node_name_pattern, instance_name_pattern, rlib2.R_2_instances_name_rename, re.compile(r'^/2/instances/(%s)/modify$' % instance_name_pattern): rlib2.R_2_instances_name_modify, + re.compile(r"^/2/instances/(%s)/disk/(%s)/grow$" % + (instance_name_pattern, disk_pattern)): + rlib2.R_2_instances_name_disk_grow, "/2/groups": rlib2.R_2_groups, re.compile(r'^/2/groups/(%s)$' % group_name_pattern): @@ -236,4 +240,4 @@ def GetHandlers(node_name_pattern, instance_name_pattern, CONNECTOR.update(GetHandlers(_NAME_PATTERN, _NAME_PATTERN, _NAME_PATTERN, - constants.JOB_ID_TEMPLATE)) + constants.JOB_ID_TEMPLATE, _DISK_PATTERN)) diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py index 9fe73dbbf17c36d6be5b6420d4e74e99d159b483..94a11937240fb5d91e132da627391bf06a90111f 100644 --- a/lib/rapi/rlib2.py +++ b/lib/rapi/rlib2.py @@ -1299,6 +1299,24 @@ class R_2_instances_name_modify(baserlib.R_Generic): return baserlib.SubmitJob([op]) +class R_2_instances_name_disk_grow(baserlib.R_Generic): + """/2/instances/[instance_name]/disk/[index]/grow resource. + + """ + def POST(self): + """Increases the size of an instance disk. + + @return: a job id + + """ + op = baserlib.FillOpcode(opcodes.OpGrowDisk, self.request_body, { + "instance_name": self.items[0], + "disk": int(self.items[1]), + }) + + return baserlib.SubmitJob([op]) + + class _R_Tags(baserlib.R_Generic): """ Quasiclass for tagging resources diff --git a/test/docs_unittest.py b/test/docs_unittest.py index a2a33b8cf5bc0275c415a251b116a373743131c6..075ec391129943d1be8684cf4910ed6c87b84535 100755 --- a/test/docs_unittest.py +++ b/test/docs_unittest.py @@ -79,11 +79,13 @@ class TestDocs(unittest.TestCase): instance_name = "[instance_name]" group_name = "[group_name]" job_id = "[job_id]" + disk_index = "[disk_index]" resources = connector.GetHandlers(re.escape(node_name), re.escape(instance_name), re.escape(group_name), - re.escape(job_id)) + re.escape(job_id), + re.escape(disk_index)) titles = [] diff --git a/test/ganeti.rapi.client_unittest.py b/test/ganeti.rapi.client_unittest.py index de4f996bcf6abfb20965c2a5c5d69fcbda032126..dffbd05fb2e20337077afaf981252490edcda803 100755 --- a/test/ganeti.rapi.client_unittest.py +++ b/test/ganeti.rapi.client_unittest.py @@ -1051,6 +1051,26 @@ class GanetiRapiClientTests(testutils.GanetiTestCase): self.assertEqual(data["maintain_node_health"], mnh) self.assertEqual(self.rapi.CountPending(), 0) + def testGrowInstanceDisk(self): + for idx, wait_for_sync in enumerate([None, False, True]): + amount = 128 + (512 * idx) + self.assertEqual(self.rapi.CountPending(), 0) + self.rapi.AddResponse("30783") + self.assertEqual(30783, + self.client.GrowInstanceDisk("eze8ch", idx, amount, + wait_for_sync=wait_for_sync)) + self.assertHandler(rlib2.R_2_instances_name_disk_grow) + self.assertItems(["eze8ch", str(idx)]) + data = serializer.LoadJson(self.rapi.GetLastRequestData()) + if wait_for_sync is None: + self.assertEqual(len(data), 1) + self.assert_("wait_for_sync" not in data) + else: + self.assertEqual(len(data), 2) + self.assertEqual(data["wait_for_sync"], wait_for_sync) + self.assertEqual(data["amount"], amount) + self.assertEqual(self.rapi.CountPending(), 0) + if __name__ == '__main__': client.UsesRapiClient(testutils.GanetiTestProgram)()