Add the *glob* argument to the RunPhase function. With *glob* set to
True, HooksMaster runs global hooks instead of the opcode's hooks. The
global hooks should be placed in the "global" subdirectory of the hooks
directory.

Global hooks run separately for the master node and for the others
because enviromental variable IS_MASTER should be set properly.

Signed-off-by: Oleg Ponomarev <[email protected]>
---
 lib/hooksmaster.py      | 55 +++++++++++++++++++++++++++++++++++++++++--------
 src/Ganeti/Constants.hs | 11 ++++++++++
 2 files changed, 57 insertions(+), 9 deletions(-)

diff --git a/lib/hooksmaster.py b/lib/hooksmaster.py
index c23d857..1472ea7 100644
--- a/lib/hooksmaster.py
+++ b/lib/hooksmaster.py
@@ -56,7 +56,8 @@ def _RpcResultsToHooksResults(rpc_results):
 class HooksMaster(object):
   def __init__(self, opcode, hooks_path, nodes, hooks_execution_fn,
                hooks_results_adapt_fn, build_env_fn, prepare_post_nodes_fn,
-               log_fn, htype=None, cluster_name=None, master_name=None):
+               log_fn, htype=None, cluster_name=None, master_name=None,
+               master_uuid=None, job_id=None):
     """Base class for hooks masters.
 
     This class invokes the execution of hooks according to the behaviour
@@ -93,6 +94,11 @@ class HooksMaster(object):
     @param cluster_name: name of the cluster
     @type master_name: string
     @param master_name: name of the master
+    @type master_uuid: string
+    @param master_uuid: uuid of the master (used in global hooks in order
+      to distinguish between the master node and the others)
+    @type job_id: int
+    @param job_id: the id of the job process (used in global post hooks)
 
     """
     self.opcode = opcode
@@ -105,6 +111,8 @@ class HooksMaster(object):
     self.htype = htype
     self.cluster_name = cluster_name
     self.master_name = master_name
+    self.master_uuid = master_uuid
+    self.job_id = job_id
 
     self.pre_env = self._BuildEnv(constants.HOOKS_PHASE_PRE)
     (self.pre_nodes, self.post_nodes) = nodes
@@ -151,7 +159,8 @@ class HooksMaster(object):
 
     return env
 
-  def _RunWrapper(self, node_list, hpath, phase, phase_env):
+  def _RunWrapper(self, node_list, hpath, phase, phase_env, is_global=False,
+                  post_status=None):
     """Simple wrapper over self.callfn.
 
     This method fixes the environment before executing the hooks.
@@ -175,6 +184,12 @@ class HooksMaster(object):
     if self.master_name is not None:
       env["GANETI_MASTER"] = self.master_name
 
+    if self.job_id and is_global:
+      env["GANETI_JOB_ID"] = self.job_id
+    if phase == constants.HOOKS_PHASE_POST and is_global:
+      assert post_status is not None
+      env["GANETI_POST_STATUS"] = post_status
+
     if phase_env:
       env = utils.algo.JoinDisjointDicts(env, phase_env)
 
@@ -184,9 +199,26 @@ class HooksMaster(object):
     assert compat.all(key == "PATH" or key.startswith("GANETI_")
                       for key in env)
 
-    return self.hooks_execution_fn(node_list, hpath, phase, env)
-
-  def RunPhase(self, phase, node_names=None):
+    ret = dict()
+    if is_global:
+      env["GANETI_IS_MASTER"] = constants.GLOBAL_HOOKS_MASTER
+      master_set = set([self.master_name])
+      ret.update(self.hooks_execution_fn(master_set, hpath, phase, env))
+
+      if node_list:
+        # Master node might be either listed by the uuid or by the name
+        master_set.add(self.master_uuid)
+        node_list = frozenset(set(node_list) - master_set)
+      if not node_list:
+        return ret
+      env["GANETI_IS_MASTER"] = constants.GLOBAL_HOOKS_NOT_MASTER
+
+    if node_list:
+      ret.update(self.hooks_execution_fn(node_list, hpath, phase, env))
+    return ret
+
+  def RunPhase(self, phase, node_names=None, is_global=False,
+               post_status=None):
     """Run all the scripts for a phase.
 
     This is the main function of the HookMaster.
@@ -198,6 +230,8 @@ class HooksMaster(object):
         L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
     @param node_names: overrides the predefined list of nodes for the given
         phase
+    @param is_global: whether global or per-opcode hooks should be executed
+    @param post_status: the job execution status for the global post hooks
     @return: the processed results of the hooks multi-node rpc call
     @raise errors.HooksFailure: on communication failure to the nodes
     @raise errors.HooksAbort: on failure of one of the hooks
@@ -216,13 +250,15 @@ class HooksMaster(object):
     else:
       raise AssertionError("Unknown phase '%s'" % phase)
 
-    if not node_names:
+    if not node_names and not is_global:
       # empty node list, we should not attempt to run this as either
       # we're in the cluster init phase and the rpc client part can't
       # even attempt to run, or this LU doesn't do hooks at all
       return
 
-    results = self._RunWrapper(node_names, self.hooks_path, phase, env)
+    hooks_path = constants.GLOBAL_HOOKS_DIR if is_global else self.hooks_path
+    results = self._RunWrapper(node_names, hooks_path, phase, env, is_global,
+                               post_status)
     if not results:
       msg = "Communication Failure"
       if phase == constants.HOOKS_PHASE_PRE:
@@ -272,7 +308,7 @@ class HooksMaster(object):
     self._RunWrapper(nodes, hpath, phase, self.pre_env)
 
   @staticmethod
-  def BuildFromLu(hooks_execution_fn, lu):
+  def BuildFromLu(hooks_execution_fn, lu, job_id=None):
     if lu.HPATH is None:
       nodes = (None, None)
     else:
@@ -285,9 +321,10 @@ class HooksMaster(object):
     master_name = cluster_name = None
     if lu.cfg:
       master_name = lu.cfg.GetMasterNodeName()
+      master_uuid = lu.cfg.GetMasterNode()
       cluster_name = lu.cfg.GetClusterName()
 
     return HooksMaster(lu.op.OP_ID, lu.HPATH, nodes, hooks_execution_fn,
                        _RpcResultsToHooksResults, lu.BuildHooksEnv,
                        lu.PreparePostHookNodes, lu.LogWarning, lu.HTYPE,
-                       cluster_name, master_name)
+                       cluster_name, master_name, master_uuid, job_id)
diff --git a/src/Ganeti/Constants.hs b/src/Ganeti/Constants.hs
index 86c8c95..1f96c0d 100644
--- a/src/Ganeti/Constants.hs
+++ b/src/Ganeti/Constants.hs
@@ -724,6 +724,17 @@ hooksPhasePre = "pre"
 hooksVersion :: Int
 hooksVersion = 2
 
+-- * Global hooks related constants
+
+globalHooksDir :: String
+globalHooksDir = "global"
+
+globalHooksMaster :: String
+globalHooksMaster = "master"
+
+globalHooksNotMaster :: String
+globalHooksNotMaster = "not_master"
+
 -- * Hooks subject type (what object type does the LU deal with)
 
 htypeCluster :: String
-- 
2.6.0.rc2.230.g3dd15c0

Reply via email to