RAPI: Add support for tagging node groups
authorMichael Hanselmann <hansmi@google.com>
Thu, 21 Apr 2011 09:29:31 +0000 (11:29 +0200)
committerMichael Hanselmann <hansmi@google.com>
Thu, 21 Apr 2011 13:08:37 +0000 (15:08 +0200)
Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>

doc/rapi.rst
lib/rapi/baserlib.py
lib/rapi/client.py
lib/rapi/connector.py
lib/rapi/rlib2.py
test/ganeti.rapi.client_unittest.py

index d278b29..fbc9970 100644 (file)
@@ -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``
 ++++++++++++++++
 
index 66f69b2..534ebae 100644 (file)
@@ -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)
index f1a157d..546b58c 100644 (file)
@@ -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.
 
index 6243f4a..cb4bb29 100644 (file)
@@ -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):
index 66b0ece..1e8c50c 100644 (file)
@@ -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.
 
index 3f9f2b8..464d451 100755 (executable)
@@ -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"]]):