On Fri, Nov 20, 2015 at 5:11 PM, 'Oleg Ponomarev' via ganeti-devel <
[email protected]> wrote:

> 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.
>

Thanks for the rename of "glob" - please modify the commit message as well.


>
> Global hooks run separately for the master node and for the others

because enviromental variable IS_MASTER should be set properly.
>

As more than one global hook can be triggered by the same Ganeti job in a
very unpredictable way, we set the IS_MASTER environmental variable to
allow hooks to make sure that exactly one action gets executed per hook
activation.


>
> 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)
>

The assert being triggered before the env finalization is a bit messy.


>
> -    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
>

How come? Where do the different value types come from?

I would rather fix this at the source rather than here.


> +        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


Urgh, a sideeffect of this code spilling over to the next invocation is a
bit yucky.


> +
> +    if node_list:
> +      ret.update(self.hooks_execution_fn(node_list, hpath, phase, env))

+    return ret
>

I have to admit I do not like the way this code is structured.

I would suggest using a local helper function that will operate on a set of
nodes, set the environmental value if is_global is used, update ret, and
optionally not invoke the execution function if the set of nodes is empty -
we can also leave that bit to the execution function.

This would allow us to separate the set calculation from the invocation,
resulting in a more readable execution flow.


> +
> +  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
>

for global post hooks

Please add a @type declaration as well, as the type is not apparent from
the code.


>      @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
>
>
Hrvoje Ribicic
Ganeti Engineering
Google Germany GmbH
Dienerstr. 12, 80331, München

Geschäftsführer: Matthew Scott Sucherman, Paul Terence Manicle
Registergericht und -nummer: Hamburg, HRB 86891
Sitz der Gesellschaft: Hamburg

Diese E-Mail ist vertraulich. Wenn Sie nicht der richtige Adressat sind,
leiten Sie diese bitte nicht weiter, informieren Sie den Absender und
löschen Sie die E-Mail und alle Anhänge. Vielen Dank.

This e-mail is confidential. If you are not the right addressee please do
not forward it, please inform the sender, and please erase this e-mail
including any attachments. Thanks.

Reply via email to