diff --git a/lib/rapi/client.py b/lib/rapi/client.py
index 0e8ea37e676c0a4e3c8a1381c7257bc96e199e4a..47ac422991f0e65b5fa28d2bbb75f399cf9b953b 100644
--- a/lib/rapi/client.py
+++ b/lib/rapi/client.py
@@ -1371,3 +1371,102 @@ class GanetiRapiClient(object):
     return self._SendRequest(HTTP_DELETE,
                              ("/%s/nodes/%s/tags" %
                               (GANETI_RAPI_VERSION, node)), query, None)
+
+  def GetGroups(self, bulk=False):
+    """Gets all node groups in the cluster.
+
+    @type bulk: bool
+    @param bulk: whether to return all information about the groups
+
+    @rtype: list of dict or str
+    @return: if bulk is true, a list of dictionaries with info about all node
+        groups in the cluster, else a list of names of those node groups
+
+    """
+    query = []
+    if bulk:
+      query.append(("bulk", 1))
+
+    groups = self._SendRequest(HTTP_GET, "/%s/groups" % GANETI_RAPI_VERSION,
+                               query, None)
+    if bulk:
+      return groups
+    else:
+      return [g["name"] for g in groups]
+
+  def GetGroup(self, group):
+    """Gets information about a node group.
+
+    @type group: str
+    @param group: name of the node group whose info to return
+
+    @rtype: dict
+    @return: info about the node group
+
+    """
+    return self._SendRequest(HTTP_GET,
+                             "/%s/groups/%s" % (GANETI_RAPI_VERSION, group),
+                             None, None)
+
+  def CreateGroup(self, name, dry_run=False):
+    """Creates a new node group.
+
+    @type name: str
+    @param name: the name of node group to create
+    @type dry_run: bool
+    @param dry_run: whether to peform a dry run
+
+    @rtype: int
+    @return: job id
+
+    """
+    query = []
+    if dry_run:
+      query.append(("dry-run", 1))
+
+    body = {
+      "name": name,
+      }
+
+    return self._SendRequest(HTTP_POST, "/%s/groups" % GANETI_RAPI_VERSION,
+                             query, body)
+
+  def DeleteGroup(self, group, dry_run=False):
+    """Deletes a node group.
+
+    @type group: str
+    @param group: the node group to delete
+    @type dry_run: bool
+    @param dry_run: whether to peform a dry run
+
+    @rtype: int
+    @return: job id
+
+    """
+    query = []
+    if dry_run:
+      query.append(("dry-run", 1))
+
+    return self._SendRequest(HTTP_DELETE,
+                             ("/%s/groups/%s" %
+                              (GANETI_RAPI_VERSION, group)), query, None)
+
+  def RenameGroup(self, group, new_name):
+    """Changes the name of a node group.
+
+    @type group: string
+    @param group: Node group name
+    @type new_name: string
+    @param new_name: New node group name
+
+    @rtype: int
+    @return: job id
+
+    """
+    body = {
+      "new_name": new_name,
+      }
+
+    return self._SendRequest(HTTP_PUT,
+                             ("/%s/groups/%s/rename" %
+                              (GANETI_RAPI_VERSION, group)), None, body)
diff --git a/test/ganeti.rapi.client_unittest.py b/test/ganeti.rapi.client_unittest.py
index 781aff4b4ea435033582318f4b6dd5b8c96d673c..544551bf24cefed553408714e98a6c5cadc1b6bc 100755
--- a/test/ganeti.rapi.client_unittest.py
+++ b/test/ganeti.rapi.client_unittest.py
@@ -976,6 +976,69 @@ class GanetiRapiClientTests(testutils.GanetiTestCase):
     self.assertDryRun()
     self.assertQuery("tag", ["awesome"])
 
+  def testGetGroups(self):
+    groups = [{"name": "group1",
+               "uri": "/2/groups/group1",
+               },
+              {"name": "group2",
+               "uri": "/2/groups/group2",
+               },
+              ]
+    self.rapi.AddResponse(serializer.DumpJson(groups))
+    self.assertEqual(["group1", "group2"], self.client.GetGroups())
+    self.assertHandler(rlib2.R_2_groups)
+
+  def testGetGroupsBulk(self):
+    groups = [{"name": "group1",
+               "uri": "/2/groups/group1",
+               "node_cnt": 2,
+               "node_list": ["gnt1.test",
+                             "gnt2.test",
+                             ],
+               },
+              {"name": "group2",
+               "uri": "/2/groups/group2",
+               "node_cnt": 1,
+               "node_list": ["gnt3.test",
+                             ],
+               },
+              ]
+    self.rapi.AddResponse(serializer.DumpJson(groups))
+
+    self.assertEqual(groups, self.client.GetGroups(bulk=True))
+    self.assertHandler(rlib2.R_2_groups)
+    self.assertBulk()
+
+  def testGetGroup(self):
+    group = {"ctime": None,
+             "name": "default",
+             }
+    self.rapi.AddResponse(serializer.DumpJson(group))
+    self.assertEqual({"ctime": None, "name": "default"},
+                     self.client.GetGroup("default"))
+    self.assertHandler(rlib2.R_2_groups_name)
+    self.assertItems(["default"])
+
+  def testCreateGroup(self):
+    self.rapi.AddResponse("12345")
+    job_id = self.client.CreateGroup("newgroup", dry_run=True)
+    self.assertEqual(job_id, 12345)
+    self.assertHandler(rlib2.R_2_groups)
+    self.assertDryRun()
+
+  def testDeleteGroup(self):
+    self.rapi.AddResponse("12346")
+    job_id = self.client.DeleteGroup("newgroup", dry_run=True)
+    self.assertEqual(job_id, 12346)
+    self.assertHandler(rlib2.R_2_groups_name)
+    self.assertDryRun()
+
+  def testRenameGroup(self):
+    self.rapi.AddResponse("12347")
+    job_id = self.client.RenameGroup("oldname", "newname")
+    self.assertEqual(job_id, 12347)
+    self.assertHandler(rlib2.R_2_groups_name_rename)
+
 
 if __name__ == '__main__':
   client.UsesRapiClient(testutils.GanetiTestProgram)()