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
