Add reason parameter to RAPI client functions
[ganeti-github.git] / lib / rapi / client.py
index 35b1191..4ad1d16 100644 (file)
@@ -219,6 +219,15 @@ def _AppendForceIf(container, condition):
   return _AppendIf(container, condition, (_QPARAM_FORCE, 1))
 
 
+def _AppendReason(container, reason):
+  """Appends an element to the reason trail.
+
+  If the user provided a reason, it is added to the reason trail.
+
+  """
+  return _AppendIf(container, reason, ("reason", reason))
+
+
 def _SetItemIf(container, condition, item, value):
   """Sets an item if a condition evaluates to truth.
 
@@ -577,68 +586,91 @@ class GanetiRapiClient(object): # pylint: disable=R0904
 
       raise
 
-  def GetOperatingSystems(self):
+  def GetOperatingSystems(self, reason=None):
     """Gets the Operating Systems running in the Ganeti cluster.
 
     @rtype: list of str
     @return: operating systems
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     """
+    query = []
+    _AppendReason(query, reason)
     return self._SendRequest(HTTP_GET, "/%s/os" % GANETI_RAPI_VERSION,
-                             None, None)
+                             query, None)
 
-  def GetInfo(self):
+  def GetInfo(self, reason=None):
     """Gets info about the cluster.
 
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: dict
     @return: information about the cluster
 
     """
+    query = []
+    _AppendReason(query, reason)
     return self._SendRequest(HTTP_GET, "/%s/info" % GANETI_RAPI_VERSION,
-                             None, None)
+                             query, None)
 
-  def RedistributeConfig(self):
+  def RedistributeConfig(self, reason=None):
     """Tells the cluster to redistribute its configuration files.
 
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
+    query = []
+    _AppendReason(query, reason)
     return self._SendRequest(HTTP_PUT,
                              "/%s/redistribute-config" % GANETI_RAPI_VERSION,
-                             None, None)
+                             query, None)
 
-  def ModifyCluster(self, **kwargs):
+  def ModifyCluster(self, reason=None, **kwargs):
     """Modifies cluster parameters.
 
     More details for parameters can be found in the RAPI documentation.
 
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     body = kwargs
 
     return self._SendRequest(HTTP_PUT,
-                             "/%s/modify" % GANETI_RAPI_VERSION, None, body)
+                             "/%s/modify" % GANETI_RAPI_VERSION, query, body)
 
-  def GetClusterTags(self):
+  def GetClusterTags(self, reason=None):
     """Gets the cluster tags.
 
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: list of str
     @return: cluster tags
 
     """
+    query = []
+    _AppendReason(query, reason)
     return self._SendRequest(HTTP_GET, "/%s/tags" % GANETI_RAPI_VERSION,
-                             None, None)
+                             query, None)
 
-  def AddClusterTags(self, tags, dry_run=False):
+  def AddClusterTags(self, tags, dry_run=False, reason=None):
     """Adds tags to the cluster.
 
     @type tags: list of str
     @param tags: tags to add to the cluster
     @type dry_run: bool
     @param dry_run: whether to perform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -646,32 +678,38 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = [("tag", t) for t in tags]
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT, "/%s/tags" % GANETI_RAPI_VERSION,
                              query, None)
 
-  def DeleteClusterTags(self, tags, dry_run=False):
+  def DeleteClusterTags(self, tags, dry_run=False, reason=None):
     """Deletes tags from the cluster.
 
     @type tags: list of str
     @param tags: tags to delete
     @type dry_run: bool
     @param dry_run: whether to perform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
     query = [("tag", t) for t in tags]
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_DELETE, "/%s/tags" % GANETI_RAPI_VERSION,
                              query, None)
 
-  def GetInstances(self, bulk=False):
+  def GetInstances(self, bulk=False, reason=None):
     """Gets information about instances on the cluster.
 
     @type bulk: bool
     @param bulk: whether to return all information about all instances
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: list of dict or list of str
     @return: if bulk is True, info about the instances, else a list of instances
@@ -679,6 +717,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = []
     _AppendIf(query, bulk, ("bulk", 1))
+    _AppendReason(query, reason)
 
     instances = self._SendRequest(HTTP_GET,
                                   "/%s/instances" % GANETI_RAPI_VERSION,
@@ -688,33 +727,40 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     else:
       return [i["id"] for i in instances]
 
-  def GetInstance(self, instance):
+  def GetInstance(self, instance, reason=None):
     """Gets information about an instance.
 
     @type instance: str
     @param instance: instance whose info to return
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: dict
     @return: info about the instance
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_GET,
                              ("/%s/instances/%s" %
-                              (GANETI_RAPI_VERSION, instance)), None, None)
+                              (GANETI_RAPI_VERSION, instance)), query, None)
 
-  def GetInstanceInfo(self, instance, static=None):
+  def GetInstanceInfo(self, instance, static=None, reason=None):
     """Gets information about an instance.
 
     @type instance: string
     @param instance: Instance name
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: Job ID
 
     """
+    query = []
     if static is not None:
-      query = [("static", static)]
-    else:
-      query = None
+      query.append(("static", static))
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_GET,
                              ("/%s/instances/%s/info" %
@@ -772,7 +818,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
 
     return alloc
 
-  def InstancesMultiAlloc(self, instances, **kwargs):
+  def InstancesMultiAlloc(self, instances, reason=None, **kwargs):
     """Tries to allocate multiple instances.
 
     More details for parameters can be found in the RAPI documentation.
@@ -787,13 +833,14 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     self._UpdateWithKwargs(body, **kwargs)
 
     _AppendDryRunIf(query, kwargs.get("dry_run"))
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_POST,
                              "/%s/instances-multi-alloc" % GANETI_RAPI_VERSION,
                              query, body)
 
   def CreateInstance(self, mode, name, disk_template, disks, nics,
-                     **kwargs):
+                     reason=None, **kwargs):
     """Creates a new instance.
 
     More details for parameters can be found in the RAPI documentation.
@@ -811,6 +858,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param nics: List of NIC definitions
     @type dry_run: bool
     @keyword dry_run: whether to perform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -819,6 +868,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     query = []
 
     _AppendDryRunIf(query, kwargs.get("dry_run"))
+    _AppendReason(query, reason)
 
     if _INST_CREATE_REQV1 in self.GetFeatures():
       body = self.InstanceAllocation(mode, name, disk_template, disks, nics,
@@ -831,11 +881,13 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     return self._SendRequest(HTTP_POST, "/%s/instances" % GANETI_RAPI_VERSION,
                              query, body)
 
-  def DeleteInstance(self, instance, dry_run=False):
+  def DeleteInstance(self, instance, dry_run=False, reason=None):
     """Deletes an instance.
 
     @type instance: str
     @param instance: the instance to delete
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -843,60 +895,73 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = []
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_DELETE,
                              ("/%s/instances/%s" %
                               (GANETI_RAPI_VERSION, instance)), query, None)
 
-  def ModifyInstance(self, instance, **kwargs):
+  def ModifyInstance(self, instance, reason=None, **kwargs):
     """Modifies an instance.
 
     More details for parameters can be found in the RAPI documentation.
 
     @type instance: string
     @param instance: Instance name
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
     body = kwargs
+    query = []
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/instances/%s/modify" %
-                              (GANETI_RAPI_VERSION, instance)), None, body)
+                              (GANETI_RAPI_VERSION, instance)), query, body)
 
-  def ActivateInstanceDisks(self, instance, ignore_size=None):
+  def ActivateInstanceDisks(self, instance, ignore_size=None, reason=None):
     """Activates an instance's disks.
 
     @type instance: string
     @param instance: Instance name
     @type ignore_size: bool
     @param ignore_size: Whether to ignore recorded size
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
     query = []
     _AppendIf(query, ignore_size, ("ignore_size", 1))
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/instances/%s/activate-disks" %
                               (GANETI_RAPI_VERSION, instance)), query, None)
 
-  def DeactivateInstanceDisks(self, instance):
+  def DeactivateInstanceDisks(self, instance, reason=None):
     """Deactivates an instance's disks.
 
     @type instance: string
     @param instance: Instance name
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
+    query = []
+    _AppendReason(query, reason)
     return self._SendRequest(HTTP_PUT,
                              ("/%s/instances/%s/deactivate-disks" %
-                              (GANETI_RAPI_VERSION, instance)), None, None)
+                              (GANETI_RAPI_VERSION, instance)), query, None)
 
-  def RecreateInstanceDisks(self, instance, disks=None, nodes=None):
+  def RecreateInstanceDisks(self, instance, disks=None, nodes=None,
+                            reason=None):
     """Recreate an instance's disks.
 
     @type instance: string
@@ -905,6 +970,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param disks: List of disk indexes
     @type nodes: list of string
     @param nodes: New instance nodes, if relocation is desired
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
@@ -913,11 +980,15 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     _SetItemIf(body, disks is not None, "disks", disks)
     _SetItemIf(body, nodes is not None, "nodes", nodes)
 
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_POST,
                              ("/%s/instances/%s/recreate-disks" %
-                              (GANETI_RAPI_VERSION, instance)), None, body)
+                              (GANETI_RAPI_VERSION, instance)), query, body)
 
-  def GrowInstanceDisk(self, instance, disk, amount, wait_for_sync=None):
+  def GrowInstanceDisk(self, instance, disk, amount, wait_for_sync=None,
+                       reason=None):
     """Grows a disk of an instance.
 
     More details for parameters can be found in the RAPI documentation.
@@ -930,6 +1001,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param amount: Grow disk by this amount (MiB)
     @type wait_for_sync: bool
     @param wait_for_sync: Wait for disk to synchronize
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
@@ -940,26 +1013,33 @@ class GanetiRapiClient(object): # pylint: disable=R0904
 
     _SetItemIf(body, wait_for_sync is not None, "wait_for_sync", wait_for_sync)
 
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_POST,
                              ("/%s/instances/%s/disk/%s/grow" %
                               (GANETI_RAPI_VERSION, instance, disk)),
-                             None, body)
+                             query, body)
 
-  def GetInstanceTags(self, instance):
+  def GetInstanceTags(self, instance, reason=None):
     """Gets tags for an instance.
 
     @type instance: str
     @param instance: instance whose tags to return
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: list of str
     @return: tags for the instance
 
     """
+    query = []
+    _AppendReason(query, reason)
     return self._SendRequest(HTTP_GET,
                              ("/%s/instances/%s/tags" %
-                              (GANETI_RAPI_VERSION, instance)), None, None)
+                              (GANETI_RAPI_VERSION, instance)), query, None)
 
-  def AddInstanceTags(self, instance, tags, dry_run=False):
+  def AddInstanceTags(self, instance, tags, dry_run=False, reason=None):
     """Adds tags to an instance.
 
     @type instance: str
@@ -968,6 +1048,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param tags: tags to add to the instance
     @type dry_run: bool
     @param dry_run: whether to perform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -975,12 +1057,13 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = [("tag", t) for t in tags]
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/instances/%s/tags" %
                               (GANETI_RAPI_VERSION, instance)), query, None)
 
-  def DeleteInstanceTags(self, instance, tags, dry_run=False):
+  def DeleteInstanceTags(self, instance, tags, dry_run=False, reason=None):
     """Deletes tags from an instance.
 
     @type instance: str
@@ -989,12 +1072,15 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param tags: tags to delete
     @type dry_run: bool
     @param dry_run: whether to perform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
     query = [("tag", t) for t in tags]
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_DELETE,
                              ("/%s/instances/%s/tags" %
@@ -1024,7 +1110,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     _AppendIf(query, reboot_type, ("type", reboot_type))
     _AppendIf(query, ignore_secondaries is not None,
               ("ignore_secondaries", ignore_secondaries))
-    _AppendIf(query, reason, ("reason", reason))
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_POST,
                              ("/%s/instances/%s/reboot" %
@@ -1051,7 +1137,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
 
     _AppendDryRunIf(query, dry_run)
     _AppendIf(query, no_remember, ("no_remember", 1))
-    _AppendIf(query, reason, ("reason", reason))
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/instances/%s/shutdown" %
@@ -1076,14 +1162,14 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     query = []
     _AppendDryRunIf(query, dry_run)
     _AppendIf(query, no_remember, ("no_remember", 1))
-    _AppendIf(query, reason, ("reason", reason))
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/instances/%s/startup" %
                               (GANETI_RAPI_VERSION, instance)), query, None)
 
   def ReinstallInstance(self, instance, os=None, no_startup=False,
-                        osparams=None):
+                        osparams=None, reason=None):
     """Reinstalls an instance.
 
     @type instance: str
@@ -1093,10 +1179,15 @@ class GanetiRapiClient(object): # pylint: disable=R0904
         current operating system will be installed again
     @type no_startup: bool
     @param no_startup: Whether to start the instance automatically
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     if _INST_REINSTALL_REQV1 in self.GetFeatures():
       body = {
         "start": not no_startup,
@@ -1105,7 +1196,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
       _SetItemIf(body, osparams is not None, "osparams", osparams)
       return self._SendRequest(HTTP_POST,
                                ("/%s/instances/%s/reinstall" %
-                                (GANETI_RAPI_VERSION, instance)), None, body)
+                                (GANETI_RAPI_VERSION, instance)), query, body)
 
     # Use old request format
     if osparams:
@@ -1121,7 +1212,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
                               (GANETI_RAPI_VERSION, instance)), query, None)
 
   def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO,
-                           remote_node=None, iallocator=None):
+                           remote_node=None, iallocator=None, reason=None):
     """Replaces disks on an instance.
 
     @type instance: str
@@ -1136,6 +1227,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @type iallocator: str or None
     @param iallocator: instance allocator plugin to use (for use with
                        replace_auto mode)
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -1153,36 +1246,42 @@ class GanetiRapiClient(object): # pylint: disable=R0904
 
     _AppendIf(query, remote_node is not None, ("remote_node", remote_node))
     _AppendIf(query, iallocator is not None, ("iallocator", iallocator))
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_POST,
                              ("/%s/instances/%s/replace-disks" %
                               (GANETI_RAPI_VERSION, instance)), query, None)
 
-  def PrepareExport(self, instance, mode):
+  def PrepareExport(self, instance, mode, reason=None):
     """Prepares an instance for an export.
 
     @type instance: string
     @param instance: Instance name
     @type mode: string
     @param mode: Export mode
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: Job ID
 
     """
     query = [("mode", mode)]
+    _AppendReason(query, reason)
     return self._SendRequest(HTTP_PUT,
                              ("/%s/instances/%s/prepare-export" %
                               (GANETI_RAPI_VERSION, instance)), query, None)
 
   def ExportInstance(self, instance, mode, destination, shutdown=None,
                      remove_instance=None,
-                     x509_key_name=None, destination_x509_ca=None):
+                     x509_key_name=None, destination_x509_ca=None, reason=None):
     """Exports an instance.
 
     @type instance: string
     @param instance: Instance name
     @type mode: string
     @param mode: Export mode
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: Job ID
 
@@ -1199,12 +1298,15 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     _SetItemIf(body, destination_x509_ca is not None,
                "destination_x509_ca", destination_x509_ca)
 
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_PUT,
                              ("/%s/instances/%s/export" %
-                              (GANETI_RAPI_VERSION, instance)), None, body)
+                              (GANETI_RAPI_VERSION, instance)), query, body)
 
   def MigrateInstance(self, instance, mode=None, cleanup=None,
-                      target_node=None):
+                      target_node=None, reason=None):
     """Migrates an instance.
 
     @type instance: string
@@ -1215,6 +1317,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param cleanup: Whether to clean up a previously failed migration
     @type target_node: string
     @param target_node: Target Node for externally mirrored instances
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
@@ -1224,12 +1328,15 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     _SetItemIf(body, cleanup is not None, "cleanup", cleanup)
     _SetItemIf(body, target_node is not None, "target_node", target_node)
 
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_PUT,
                              ("/%s/instances/%s/migrate" %
-                              (GANETI_RAPI_VERSION, instance)), None, body)
+                              (GANETI_RAPI_VERSION, instance)), query, body)
 
   def FailoverInstance(self, instance, iallocator=None,
-                       ignore_consistency=None, target_node=None):
+                       ignore_consistency=None, target_node=None, reason=None):
     """Does a failover of an instance.
 
     @type instance: string
@@ -1241,6 +1348,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param ignore_consistency: Whether to ignore disk consistency
     @type target_node: string
     @param target_node: Target node for shared-storage instances
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
@@ -1251,11 +1360,15 @@ class GanetiRapiClient(object): # pylint: disable=R0904
                "ignore_consistency", ignore_consistency)
     _SetItemIf(body, target_node is not None, "target_node", target_node)
 
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_PUT,
                              ("/%s/instances/%s/failover" %
-                              (GANETI_RAPI_VERSION, instance)), None, body)
+                              (GANETI_RAPI_VERSION, instance)), query, body)
 
-  def RenameInstance(self, instance, new_name, ip_check=None, name_check=None):
+  def RenameInstance(self, instance, new_name, ip_check=None, name_check=None,
+                     reason=None):
     """Changes the name of an instance.
 
     @type instance: string
@@ -1266,6 +1379,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param ip_check: Whether to ensure instance's IP address is inactive
     @type name_check: bool
     @param name_check: Whether to ensure instance's name is resolvable
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
@@ -1277,22 +1392,29 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     _SetItemIf(body, ip_check is not None, "ip_check", ip_check)
     _SetItemIf(body, name_check is not None, "name_check", name_check)
 
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_PUT,
                              ("/%s/instances/%s/rename" %
-                              (GANETI_RAPI_VERSION, instance)), None, body)
+                              (GANETI_RAPI_VERSION, instance)), query, body)
 
-  def GetInstanceConsole(self, instance):
+  def GetInstanceConsole(self, instance, reason=None):
     """Request information for connecting to instance's console.
 
     @type instance: string
     @param instance: Instance name
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: dict
     @return: dictionary containing information about instance's console
 
     """
+    query = []
+    _AppendReason(query, reason)
     return self._SendRequest(HTTP_GET,
                              ("/%s/instances/%s/console" %
-                              (GANETI_RAPI_VERSION, instance)), None, None)
+                              (GANETI_RAPI_VERSION, instance)), query, None)
 
   def GetJobs(self, bulk=False):
     """Gets all jobs for the cluster.
@@ -1406,11 +1528,13 @@ class GanetiRapiClient(object): # pylint: disable=R0904
                              "/%s/jobs/%s" % (GANETI_RAPI_VERSION, job_id),
                              query, None)
 
-  def GetNodes(self, bulk=False):
+  def GetNodes(self, bulk=False, reason=None):
     """Gets all nodes in the cluster.
 
     @type bulk: bool
     @param bulk: whether to return all information about all instances
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: list of dict or str
     @return: if bulk is true, info about nodes in the cluster,
@@ -1419,6 +1543,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = []
     _AppendIf(query, bulk, ("bulk", 1))
+    _AppendReason(query, reason)
 
     nodes = self._SendRequest(HTTP_GET, "/%s/nodes" % GANETI_RAPI_VERSION,
                               query, None)
@@ -1427,23 +1552,28 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     else:
       return [n["id"] for n in nodes]
 
-  def GetNode(self, node):
+  def GetNode(self, node, reason=None):
     """Gets information about a node.
 
     @type node: str
     @param node: node whose info to return
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: dict
     @return: info about the node
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_GET,
                              "/%s/nodes/%s" % (GANETI_RAPI_VERSION, node),
-                             None, None)
+                             query, None)
 
   def EvacuateNode(self, node, iallocator=None, remote_node=None,
                    dry_run=False, early_release=None,
-                   mode=None, accept_old=False):
+                   mode=None, accept_old=False, reason=None):
     """Evacuates instances from a Ganeti node.
 
     @type node: str
@@ -1461,6 +1591,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @type accept_old: bool
     @param accept_old: Whether caller is ready to accept old-style (pre-2.5)
         results
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string, or a list for pre-2.5 results
     @return: Job ID or, if C{accept_old} is set and server is pre-2.5,
@@ -1477,6 +1609,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
 
     query = []
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     if _NODE_EVAC_RES1 in self.GetFeatures():
       # Server supports body parameters
@@ -1509,7 +1642,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
                               (GANETI_RAPI_VERSION, node)), query, body)
 
   def MigrateNode(self, node, mode=None, dry_run=False, iallocator=None,
-                  target_node=None):
+                  target_node=None, reason=None):
     """Migrates all primary instances from a node.
 
     @type node: str
@@ -1523,6 +1656,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param iallocator: instance allocator to use
     @type target_node: string
     @param target_node: Target node for shared-storage instances
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -1530,6 +1665,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = []
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     if _NODE_MIGRATE_REQV1 in self.GetFeatures():
       body = {}
@@ -1555,21 +1691,27 @@ class GanetiRapiClient(object): # pylint: disable=R0904
                                ("/%s/nodes/%s/migrate" %
                                 (GANETI_RAPI_VERSION, node)), query, None)
 
-  def GetNodeRole(self, node):
+  def GetNodeRole(self, node, reason=None):
     """Gets the current role for a node.
 
     @type node: str
     @param node: node whose role to return
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: str
     @return: the current role for a node
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_GET,
                              ("/%s/nodes/%s/role" %
-                              (GANETI_RAPI_VERSION, node)), None, None)
+                              (GANETI_RAPI_VERSION, node)), query, None)
 
-  def SetNodeRole(self, node, role, force=False, auto_promote=None):
+  def SetNodeRole(self, node, role, force=False, auto_promote=None,
+                  reason=None):
     """Sets the role for a node.
 
     @type node: str
@@ -1581,6 +1723,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @type auto_promote: bool
     @param auto_promote: Whether node(s) should be promoted to master candidate
                          if necessary
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -1589,45 +1733,54 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     query = []
     _AppendForceIf(query, force)
     _AppendIf(query, auto_promote is not None, ("auto-promote", auto_promote))
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/nodes/%s/role" %
                               (GANETI_RAPI_VERSION, node)), query, role)
 
-  def PowercycleNode(self, node, force=False):
+  def PowercycleNode(self, node, force=False, reason=None):
     """Powercycles a node.
 
     @type node: string
     @param node: Node name
     @type force: bool
     @param force: Whether to force the operation
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
     query = []
     _AppendForceIf(query, force)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_POST,
                              ("/%s/nodes/%s/powercycle" %
                               (GANETI_RAPI_VERSION, node)), query, None)
 
-  def ModifyNode(self, node, **kwargs):
+  def ModifyNode(self, node, reason=None, **kwargs):
     """Modifies a node.
 
     More details for parameters can be found in the RAPI documentation.
 
     @type node: string
     @param node: Node name
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_POST,
                              ("/%s/nodes/%s/modify" %
-                              (GANETI_RAPI_VERSION, node)), None, kwargs)
+                              (GANETI_RAPI_VERSION, node)), query, kwargs)
 
-  def GetNodeStorageUnits(self, node, storage_type, output_fields):
+  def GetNodeStorageUnits(self, node, storage_type, output_fields, reason=None):
     """Gets the storage units for a node.
 
     @type node: str
@@ -1636,6 +1789,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param storage_type: storage type whose units to return
     @type output_fields: str
     @param output_fields: storage type fields to return
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id where results can be retrieved
@@ -1645,12 +1800,14 @@ class GanetiRapiClient(object): # pylint: disable=R0904
       ("storage_type", storage_type),
       ("output_fields", output_fields),
       ]
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_GET,
                              ("/%s/nodes/%s/storage" %
                               (GANETI_RAPI_VERSION, node)), query, None)
 
-  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None):
+  def ModifyNodeStorageUnits(self, node, storage_type, name, allocatable=None,
+                             reason=None):
     """Modifies parameters of storage units on the node.
 
     @type node: str
@@ -1662,6 +1819,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @type allocatable: bool or None
     @param allocatable: Whether to set the "allocatable" flag on the storage
                         unit (None=no modification, True=set, False=unset)
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -1673,12 +1832,13 @@ class GanetiRapiClient(object): # pylint: disable=R0904
       ]
 
     _AppendIf(query, allocatable is not None, ("allocatable", allocatable))
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/nodes/%s/storage/modify" %
                               (GANETI_RAPI_VERSION, node)), query, None)
 
-  def RepairNodeStorageUnits(self, node, storage_type, name):
+  def RepairNodeStorageUnits(self, node, storage_type, name, reason=None):
     """Repairs a storage unit on the node.
 
     @type node: str
@@ -1687,6 +1847,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param storage_type: storage type to repair
     @type name: str
     @param name: name of the storage unit to repair
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -1696,26 +1858,32 @@ class GanetiRapiClient(object): # pylint: disable=R0904
       ("storage_type", storage_type),
       ("name", name),
       ]
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/nodes/%s/storage/repair" %
                               (GANETI_RAPI_VERSION, node)), query, None)
 
-  def GetNodeTags(self, node):
+  def GetNodeTags(self, node, reason=None):
     """Gets the tags for a node.
 
     @type node: str
     @param node: node whose tags to return
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: list of str
     @return: tags for the node
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_GET,
                              ("/%s/nodes/%s/tags" %
-                              (GANETI_RAPI_VERSION, node)), None, None)
+                              (GANETI_RAPI_VERSION, node)), query, None)
 
-  def AddNodeTags(self, node, tags, dry_run=False):
+  def AddNodeTags(self, node, tags, dry_run=False, reason=None):
     """Adds tags to a node.
 
     @type node: str
@@ -1724,6 +1892,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param tags: tags to add to the node
     @type dry_run: bool
     @param dry_run: whether to perform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -1731,12 +1901,13 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = [("tag", t) for t in tags]
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/nodes/%s/tags" %
                               (GANETI_RAPI_VERSION, node)), query, tags)
 
-  def DeleteNodeTags(self, node, tags, dry_run=False):
+  def DeleteNodeTags(self, node, tags, dry_run=False, reason=None):
     """Delete tags from a node.
 
     @type node: str
@@ -1745,6 +1916,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param tags: tags to remove from the node
     @type dry_run: bool
     @param dry_run: whether to perform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -1752,12 +1925,13 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = [("tag", t) for t in tags]
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_DELETE,
                              ("/%s/nodes/%s/tags" %
                               (GANETI_RAPI_VERSION, node)), query, None)
 
-  def GetNetworks(self, bulk=False):
+  def GetNetworks(self, bulk=False, reason=None):
     """Gets all networks in the cluster.
 
     @type bulk: bool
@@ -1770,6 +1944,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = []
     _AppendIf(query, bulk, ("bulk", 1))
+    _AppendReason(query, reason)
 
     networks = self._SendRequest(HTTP_GET, "/%s/networks" % GANETI_RAPI_VERSION,
                                  query, None)
@@ -1778,29 +1953,37 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     else:
       return [n["name"] for n in networks]
 
-  def GetNetwork(self, network):
+  def GetNetwork(self, network, reason=None):
     """Gets information about a network.
 
     @type network: str
     @param network: name of the network whose info to return
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: dict
     @return: info about the network
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_GET,
                              "/%s/networks/%s" % (GANETI_RAPI_VERSION, network),
-                             None, None)
+                             query, None)
 
   def CreateNetwork(self, network_name, network, gateway=None, network6=None,
                     gateway6=None, mac_prefix=None,
-                    add_reserved_ips=None, tags=None, dry_run=False):
+                    add_reserved_ips=None, tags=None, dry_run=False,
+                    reason=None):
     """Creates a new network.
 
     @type network_name: str
     @param network_name: the name of network to create
     @type dry_run: bool
     @param dry_run: whether to peform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -1808,6 +1991,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = []
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     if add_reserved_ips:
       add_reserved_ips = add_reserved_ips.split(",")
@@ -1829,7 +2013,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     return self._SendRequest(HTTP_POST, "/%s/networks" % GANETI_RAPI_VERSION,
                              query, body)
 
-  def ConnectNetwork(self, network_name, group_name, mode, link, dry_run=False):
+  def ConnectNetwork(self, network_name, group_name, mode, link, dry_run=False,
+                     reason=None):
     """Connects a Network to a NodeGroup with the given netparams
 
     """
@@ -1841,12 +2026,14 @@ class GanetiRapiClient(object): # pylint: disable=R0904
 
     query = []
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/networks/%s/connect" %
                              (GANETI_RAPI_VERSION, network_name)), query, body)
 
-  def DisconnectNetwork(self, network_name, group_name, dry_run=False):
+  def DisconnectNetwork(self, network_name, group_name, dry_run=False,
+                        reason=None):
     """Connects a Network to a NodeGroup with the given netparams
 
     """
@@ -1856,33 +2043,41 @@ class GanetiRapiClient(object): # pylint: disable=R0904
 
     query = []
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/networks/%s/disconnect" %
                              (GANETI_RAPI_VERSION, network_name)), query, body)
 
-  def ModifyNetwork(self, network, **kwargs):
+  def ModifyNetwork(self, network, reason=None, **kwargs):
     """Modifies a network.
 
     More details for parameters can be found in the RAPI documentation.
 
     @type network: string
     @param network: Network name
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_PUT,
                              ("/%s/networks/%s/modify" %
                               (GANETI_RAPI_VERSION, network)), None, kwargs)
 
-  def DeleteNetwork(self, network, dry_run=False):
+  def DeleteNetwork(self, network, dry_run=False, reason=None):
     """Deletes a network.
 
     @type network: str
     @param network: the network to delete
     @type dry_run: bool
     @param dry_run: whether to peform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -1890,26 +2085,32 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = []
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_DELETE,
                              ("/%s/networks/%s" %
                               (GANETI_RAPI_VERSION, network)), query, None)
 
-  def GetNetworkTags(self, network):
+  def GetNetworkTags(self, network, reason=None):
     """Gets tags for a network.
 
     @type network: string
     @param network: Node group whose tags to return
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: list of strings
     @return: tags for the network
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_GET,
                              ("/%s/networks/%s/tags" %
-                              (GANETI_RAPI_VERSION, network)), None, None)
+                              (GANETI_RAPI_VERSION, network)), query, None)
 
-  def AddNetworkTags(self, network, tags, dry_run=False):
+  def AddNetworkTags(self, network, tags, dry_run=False, reason=None):
     """Adds tags to a network.
 
     @type network: str
@@ -1918,6 +2119,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param tags: tags to add to the network
     @type dry_run: bool
     @param dry_run: whether to perform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -1925,12 +2128,13 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = [("tag", t) for t in tags]
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/networks/%s/tags" %
                               (GANETI_RAPI_VERSION, network)), query, None)
 
-  def DeleteNetworkTags(self, network, tags, dry_run=False):
+  def DeleteNetworkTags(self, network, tags, dry_run=False, reason=None):
     """Deletes tags from a network.
 
     @type network: str
@@ -1939,22 +2143,27 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param tags: tags to delete
     @type dry_run: bool
     @param dry_run: whether to perform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
     query = [("tag", t) for t in tags]
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_DELETE,
                              ("/%s/networks/%s/tags" %
                               (GANETI_RAPI_VERSION, network)), query, None)
 
-  def GetGroups(self, bulk=False):
+  def GetGroups(self, bulk=False, reason=None):
     """Gets all node groups in the cluster.
 
     @type bulk: bool
     @param bulk: whether to return all information about the groups
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: list of dict or str
     @return: if bulk is true, a list of dictionaries with info about all node
@@ -1963,6 +2172,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = []
     _AppendIf(query, bulk, ("bulk", 1))
+    _AppendReason(query, reason)
 
     groups = self._SendRequest(HTTP_GET, "/%s/groups" % GANETI_RAPI_VERSION,
                                query, None)
@@ -1971,21 +2181,26 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     else:
       return [g["name"] for g in groups]
 
-  def GetGroup(self, group):
+  def GetGroup(self, group, reason=None):
     """Gets information about a node group.
 
     @type group: str
     @param group: name of the node group whose info to return
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: dict
     @return: info about the node group
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_GET,
                              "/%s/groups/%s" % (GANETI_RAPI_VERSION, group),
-                             None, None)
+                             query, None)
 
-  def CreateGroup(self, name, alloc_policy=None, dry_run=False):
+  def CreateGroup(self, name, alloc_policy=None, dry_run=False, reason=None):
     """Creates a new node group.
 
     @type name: str
@@ -1994,6 +2209,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param alloc_policy: the desired allocation policy for the group, if any
     @type dry_run: bool
     @param dry_run: whether to peform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -2001,6 +2218,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = []
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     body = {
       "name": name,
@@ -2010,28 +2228,35 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     return self._SendRequest(HTTP_POST, "/%s/groups" % GANETI_RAPI_VERSION,
                              query, body)
 
-  def ModifyGroup(self, group, **kwargs):
+  def ModifyGroup(self, group, reason=None, **kwargs):
     """Modifies a node group.
 
     More details for parameters can be found in the RAPI documentation.
 
     @type group: string
     @param group: Node group name
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_PUT,
                              ("/%s/groups/%s/modify" %
-                              (GANETI_RAPI_VERSION, group)), None, kwargs)
+                              (GANETI_RAPI_VERSION, group)), query, kwargs)
 
-  def DeleteGroup(self, group, dry_run=False):
+  def DeleteGroup(self, group, dry_run=False, reason=None):
     """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
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -2039,18 +2264,21 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = []
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_DELETE,
                              ("/%s/groups/%s" %
                               (GANETI_RAPI_VERSION, group)), query, None)
 
-  def RenameGroup(self, group, new_name):
+  def RenameGroup(self, group, new_name, reason=None):
     """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
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -2060,17 +2288,23 @@ class GanetiRapiClient(object): # pylint: disable=R0904
       "new_name": new_name,
       }
 
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_PUT,
                              ("/%s/groups/%s/rename" %
-                              (GANETI_RAPI_VERSION, group)), None, body)
+                              (GANETI_RAPI_VERSION, group)), query, body)
 
-  def AssignGroupNodes(self, group, nodes, force=False, dry_run=False):
+  def AssignGroupNodes(self, group, nodes, force=False, dry_run=False,
+                       reason=None):
     """Assigns nodes to a group.
 
     @type group: string
     @param group: Node group name
     @type nodes: list of strings
     @param nodes: List of nodes to assign to the group
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -2079,6 +2313,7 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     query = []
     _AppendForceIf(query, force)
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     body = {
       "nodes": nodes,
@@ -2088,21 +2323,26 @@ class GanetiRapiClient(object): # pylint: disable=R0904
                              ("/%s/groups/%s/assign-nodes" %
                              (GANETI_RAPI_VERSION, group)), query, body)
 
-  def GetGroupTags(self, group):
+  def GetGroupTags(self, group, reason=None):
     """Gets tags for a node group.
 
     @type group: string
     @param group: Node group whose tags to return
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: list of strings
     @return: tags for the group
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     return self._SendRequest(HTTP_GET,
                              ("/%s/groups/%s/tags" %
-                              (GANETI_RAPI_VERSION, group)), None, None)
+                              (GANETI_RAPI_VERSION, group)), query, None)
 
-  def AddGroupTags(self, group, tags, dry_run=False):
+  def AddGroupTags(self, group, tags, dry_run=False, reason=None):
     """Adds tags to a node group.
 
     @type group: str
@@ -2111,6 +2351,8 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param tags: tags to add to the group
     @type dry_run: bool
     @param dry_run: whether to perform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
@@ -2118,12 +2360,13 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     """
     query = [("tag", t) for t in tags]
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/groups/%s/tags" %
                               (GANETI_RAPI_VERSION, group)), query, None)
 
-  def DeleteGroupTags(self, group, tags, dry_run=False):
+  def DeleteGroupTags(self, group, tags, dry_run=False, reason=None):
     """Deletes tags from a node group.
 
     @type group: str
@@ -2132,18 +2375,21 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param tags: tags to delete
     @type dry_run: bool
     @param dry_run: whether to perform a dry run
+    @type reason: string
+    @param reason: the reason for executing this operation
     @rtype: string
     @return: job id
 
     """
     query = [("tag", t) for t in tags]
     _AppendDryRunIf(query, dry_run)
+    _AppendReason(query, reason)
 
     return self._SendRequest(HTTP_DELETE,
                              ("/%s/groups/%s/tags" %
                               (GANETI_RAPI_VERSION, group)), query, None)
 
-  def Query(self, what, fields, qfilter=None):
+  def Query(self, what, fields, qfilter=None, reason=None):
     """Retrieves information about resources.
 
     @type what: string
@@ -2152,11 +2398,16 @@ class GanetiRapiClient(object): # pylint: disable=R0904
     @param fields: Requested fields
     @type qfilter: None or list
     @param qfilter: Query filter
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
 
     """
+    query = []
+    _AppendReason(query, reason)
+
     body = {
       "fields": fields,
       }
@@ -2167,21 +2418,24 @@ class GanetiRapiClient(object): # pylint: disable=R0904
 
     return self._SendRequest(HTTP_PUT,
                              ("/%s/query/%s" %
-                              (GANETI_RAPI_VERSION, what)), None, body)
+                              (GANETI_RAPI_VERSION, what)), query, body)
 
-  def QueryFields(self, what, fields=None):
+  def QueryFields(self, what, fields=None, reason=None):
     """Retrieves available fields for a resource.
 
     @type what: string
     @param what: Resource name, one of L{constants.QR_VIA_RAPI}
     @type fields: list of string
     @param fields: Requested fields
+    @type reason: string
+    @param reason: the reason for executing this operation
 
     @rtype: string
     @return: job id
 
     """
     query = []
+    _AppendReason(query, reason)
 
     if fields is not None:
       _AppendIf(query, True, ("fields", ",".join(fields)))