diff --git a/doc/rapi.rst b/doc/rapi.rst index d278b2975b028cc34cb5aaf40a87f2d704f59949..fbc9970c3c9bda2d77efe1b79f15e7efad725652 100644 --- a/doc/rapi.rst +++ b/doc/rapi.rst @@ -497,6 +497,46 @@ Body parameters: :exclude: group_name, force, dry_run +``/2/groups/[group_name]/tags`` ++++++++++++++++++++++++++++++++ + +Manages per-nodegroup tags. + +Supports the following commands: ``GET``, ``PUT``, ``DELETE``. + +``GET`` +~~~~~~~ + +Returns a list of tags. + +Example:: + + ["tag1", "tag2", "tag3"] + +``PUT`` +~~~~~~~ + +Add a set of tags. + +The request as a list of strings should be ``PUT`` to this URI. The +result will be a job id. + +It supports the ``dry-run`` argument. + + +``DELETE`` +~~~~~~~~~~ + +Delete a tag. + +In order to delete a set of tags, the DELETE request should be addressed +to URI like:: + + /tags?tag=[tag]&tag=[tag] + +It supports the ``dry-run`` argument. + + ``/2/instances`` ++++++++++++++++ diff --git a/lib/rapi/baserlib.py b/lib/rapi/baserlib.py index 66f69b28bce55b678626d3661dffd91b77395f88..534ebae19e18687d2a1c8550ce61cfeaea46c0bc 100644 --- a/lib/rapi/baserlib.py +++ b/lib/rapi/baserlib.py @@ -92,12 +92,16 @@ def _Tags_GET(kind, name): """Helper function to retrieve tags. """ - if kind == constants.TAG_INSTANCE or kind == constants.TAG_NODE: + if kind in (constants.TAG_INSTANCE, + constants.TAG_NODEGROUP, + constants.TAG_NODE): if not name: raise http.HttpBadRequest("Missing name on tag request") cl = GetClient() if kind == constants.TAG_INSTANCE: fn = cl.QueryInstances + elif kind == constants.TAG_NODEGROUP: + fn = cl.QueryGroups else: fn = cl.QueryNodes result = fn(names=[name], fields=["tags"], use_locking=False) diff --git a/lib/rapi/client.py b/lib/rapi/client.py index f1a157d3a1b78ac7e5577bf513a3d8607621426a..546b58cbdd1268d76bbe258f17b5952743adac3c 100644 --- a/lib/rapi/client.py +++ b/lib/rapi/client.py @@ -1620,6 +1620,63 @@ class GanetiRapiClient(object): # pylint: disable-msg=R0904 ("/%s/groups/%s/assign-nodes" % (GANETI_RAPI_VERSION, group)), query, body) + def GetGroupTags(self, group): + """Gets tags for a node group. + + @type group: string + @param group: Node group whose tags to return + + @rtype: list of strings + @return: tags for the group + + """ + return self._SendRequest(HTTP_GET, + ("/%s/groups/%s/tags" % + (GANETI_RAPI_VERSION, group)), None, None) + + def AddGroupTags(self, group, tags, dry_run=False): + """Adds tags to a node group. + + @type group: str + @param group: group to add tags to + @type tags: list of string + @param tags: tags to add to the group + @type dry_run: bool + @param dry_run: whether to perform a dry run + + @rtype: string + @return: job id + + """ + query = [("tag", t) for t in tags] + if dry_run: + query.append(("dry-run", 1)) + + return self._SendRequest(HTTP_PUT, + ("/%s/groups/%s/tags" % + (GANETI_RAPI_VERSION, group)), query, None) + + def DeleteGroupTags(self, group, tags, dry_run=False): + """Deletes tags from a node group. + + @type group: str + @param group: group to delete tags from + @type tags: list of string + @param tags: tags to delete + @type dry_run: bool + @param dry_run: whether to perform a dry run + @rtype: string + @return: job id + + """ + query = [("tag", t) for t in tags] + if dry_run: + query.append(("dry-run", 1)) + + return self._SendRequest(HTTP_DELETE, + ("/%s/groups/%s/tags" % + (GANETI_RAPI_VERSION, group)), query, None) + def Query(self, what, fields, filter_=None): """Retrieves information about resources. diff --git a/lib/rapi/connector.py b/lib/rapi/connector.py index 6243f4a1bf2ff12a9f1d70ebc521897ce0c7bc50..cb4bb296cc445f965e59a1869ee0d7828859ab30 100644 --- a/lib/rapi/connector.py +++ b/lib/rapi/connector.py @@ -230,6 +230,8 @@ def GetHandlers(node_name_pattern, instance_name_pattern, rlib2.R_2_groups_name_rename, re.compile(r'^/2/groups/(%s)/assign-nodes$' % group_name_pattern): rlib2.R_2_groups_name_assign_nodes, + re.compile(r'^/2/groups/(%s)/tags$' % group_name_pattern): + rlib2.R_2_groups_name_tags, "/2/jobs": rlib2.R_2_jobs, re.compile(r"^/2/jobs/(%s)$" % job_id_pattern): diff --git a/lib/rapi/rlib2.py b/lib/rapi/rlib2.py index 66b0ecec82bc18eb36b6fd9f807aab773afe5faf..1e8c50cfd1fbf439af80325ae0994f3dd63196bf 100644 --- a/lib/rapi/rlib2.py +++ b/lib/rapi/rlib2.py @@ -1375,6 +1375,15 @@ class R_2_nodes_name_tags(_R_Tags): TAG_LEVEL = constants.TAG_NODE +class R_2_groups_name_tags(_R_Tags): + """ /2/groups/[group_name]/tags resource. + + Manages per-nodegroup tags. + + """ + TAG_LEVEL = constants.TAG_NODEGROUP + + class R_2_tags(_R_Tags): """ /2/tags resource. diff --git a/test/ganeti.rapi.client_unittest.py b/test/ganeti.rapi.client_unittest.py index 3f9f2b8f6208e9c154a6c3056b1373f70e587a11..464d451780f1987cd0c2231e81e835f1bbd7cc38 100755 --- a/test/ganeti.rapi.client_unittest.py +++ b/test/ganeti.rapi.client_unittest.py @@ -1087,6 +1087,30 @@ class GanetiRapiClientTests(testutils.GanetiTestCase): self.assertEqual(data["amount"], amount) self.assertEqual(self.rapi.CountPending(), 0) + def testGetGroupTags(self): + self.rapi.AddResponse("[]") + self.assertEqual([], self.client.GetGroupTags("fooGroup")) + self.assertHandler(rlib2.R_2_groups_name_tags) + self.assertItems(["fooGroup"]) + + def testAddGroupTags(self): + self.rapi.AddResponse("1234") + self.assertEqual(1234, + self.client.AddGroupTags("fooGroup", ["awesome"], dry_run=True)) + self.assertHandler(rlib2.R_2_groups_name_tags) + self.assertItems(["fooGroup"]) + self.assertDryRun() + self.assertQuery("tag", ["awesome"]) + + def testDeleteGroupTags(self): + self.rapi.AddResponse("25826") + self.assertEqual(25826, self.client.DeleteGroupTags("foo", ["awesome"], + dry_run=True)) + self.assertHandler(rlib2.R_2_groups_name_tags) + self.assertItems(["foo"]) + self.assertDryRun() + self.assertQuery("tag", ["awesome"]) + def testQuery(self): for idx, what in enumerate(constants.QR_VIA_RAPI): for idx2, filter_ in enumerate([None, ["?", "name"]]):