And a small interdiff to the interdiff:

diff --git a/lib/hooksmaster.py b/lib/hooksmaster.py
index 1289dfa..e60a132 100644
--- a/lib/hooksmaster.py
+++ b/lib/hooksmaster.py
@@ -94,8 +94,8 @@ class HooksMaster(object):
     @param cluster_name: name of the cluster
     @type master_name: string
     @param master_name: name of the master
-    @type master_name: string
-    @param master_name: uuid of the master
+    @type master_uuid: string
+    @param master_uuid: uuid of the master

     """
     self.opcode = opcode


On 11/24/2015 02:45 PM, Oleg Ponomarev wrote:
Interdiff for the hooksmaster:

diff --git a/lib/hooksmaster.py b/lib/hooksmaster.py
index d1b4adb..d2de4b8 100644
--- a/lib/hooksmaster.py
+++ b/lib/hooksmaster.py
@@ -158,6 +158,17 @@ class HooksMaster(object):

     return env

+  def _CheckParamsAndExecHooks(self, node_list, hpath, phase, env):
+    if node_list is None or not node_list:
+      return
+
+    # Convert everything to strings
+    env = dict([(str(key), str(val)) for key, val in env.iteritems()])
+    assert compat.all(key == "PATH" or key.startswith("GANETI_")
+                      for key in env)
+
+    return self.hooks_execution_fn(node_list, hpath, phase, env)
+
def _RunWrapper(self, node_list, hpath, phase, phase_env, is_global=False,
                   post_status=None):
     """Simple wrapper over self.callfn.
@@ -192,26 +203,18 @@ class HooksMaster(object):
     if phase_env:
       env = utils.algo.JoinDisjointDicts(env, phase_env)

-    # Convert everything to strings
-    env = dict([(str(key), str(val)) for key, val in env.iteritems()])
-
-    assert compat.all(key == "PATH" or key.startswith("GANETI_")
-                      for key in env)
+    if not is_global:
+      return self._CheckParamsAndExecHooks(node_list, hpath, phase, env)

     ret = dict()
-    if is_global:
-      env["GANETI_IS_MASTER"] = constants.GLOBAL_HOOKS_MASTER
-      master_set = frozenset([self.master_uuid])
-      ret.update(self.hooks_execution_fn(master_set, hpath, phase, env))
-
-      if node_list:
-        node_list = frozenset(set(node_list) - master_set)
-      if not node_list:
-        return ret
-      env["GANETI_IS_MASTER"] = constants.GLOBAL_HOOKS_NOT_MASTER
+    env["GANETI_IS_MASTER"] = constants.GLOBAL_HOOKS_MASTER
+    master_set = frozenset([self.master_uuid])
+ ret.update(self._CheckParamsAndExecHooks(master_set, hpath, phase, env))

     if node_list:
-      ret.update(self.hooks_execution_fn(node_list, hpath, phase, env))
+      node_list = frozenset(set(node_list) - master_set)
+    env["GANETI_IS_MASTER"] = constants.GLOBAL_HOOKS_NOT_MASTER
+ ret.update(self._CheckParamsAndExecHooks(node_list, hpath, phase, env))
     return ret

   def RunPhase(self, phase, node_uuids=None, is_global=False,

On 11/23/2015 06:21 PM, Hrvoje Ribicic wrote:


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