-
-
-class HooksMaster(object):
- """Hooks master.
-
- This class distributes the run commands to the nodes based on the
- specific LU class.
-
- In order to remove the direct dependency on the rpc module, the
- constructor needs a function which actually does the remote
- call. This will usually be rpc.call_hooks_runner, but any function
- which behaves the same works.
-
- """
- def __init__(self, callfn, lu):
- self.callfn = callfn
- self.lu = lu
- self.op = lu.op
- self.pre_env = self._BuildEnv(constants.HOOKS_PHASE_PRE)
-
- if self.lu.HPATH is None:
- nodes = (None, None)
- else:
- nodes = map(frozenset, self.lu.BuildHooksNodes())
-
- (self.pre_nodes, self.post_nodes) = nodes
-
- def _BuildEnv(self, phase):
- """Compute the environment and the target nodes.
-
- Based on the opcode and the current node list, this builds the
- environment for the hooks and the target node list for the run.
-
- """
- if phase == constants.HOOKS_PHASE_PRE:
- prefix = "GANETI_"
- elif phase == constants.HOOKS_PHASE_POST:
- prefix = "GANETI_POST_"
- else:
- raise AssertionError("Unknown phase '%s'" % phase)
-
- env = {}
-
- if self.lu.HPATH is not None:
- lu_env = self.lu.BuildHooksEnv()
- if lu_env:
- assert not compat.any(key.upper().startswith(prefix) for key in lu_env)
- env.update(("%s%s" % (prefix, key), value)
- for (key, value) in lu_env.items())
-
- if phase == constants.HOOKS_PHASE_PRE:
- assert compat.all((key.startswith("GANETI_") and
- not key.startswith("GANETI_POST_"))
- for key in env)
-
- elif phase == constants.HOOKS_PHASE_POST:
- assert compat.all(key.startswith("GANETI_POST_") for key in env)
- assert isinstance(self.pre_env, dict)
-
- # Merge with pre-phase environment
- assert not compat.any(key.startswith("GANETI_POST_")
- for key in self.pre_env)
- env.update(self.pre_env)
- else:
- raise AssertionError("Unknown phase '%s'" % phase)
-
- return env
-
- def _RunWrapper(self, node_list, hpath, phase, phase_env):
- """Simple wrapper over self.callfn.
-
- This method fixes the environment before doing the rpc call.
-
- """
- cfg = self.lu.cfg
-
- env = {
- "PATH": constants.HOOKS_PATH,
- "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
- "GANETI_OP_CODE": self.op.OP_ID,
- "GANETI_DATA_DIR": constants.DATA_DIR,
- "GANETI_HOOKS_PHASE": phase,
- "GANETI_HOOKS_PATH": hpath,
- }
-
- if self.lu.HTYPE:
- env["GANETI_OBJECT_TYPE"] = self.lu.HTYPE
-
- if cfg is not None:
- env["GANETI_CLUSTER"] = cfg.GetClusterName()
- env["GANETI_MASTER"] = cfg.GetMasterNode()
-
- if phase_env:
- assert not (set(env) & set(phase_env)), "Environment variables conflict"
- env.update(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)
-
- return self.callfn(node_list, hpath, phase, env)
-
- def RunPhase(self, phase, nodes=None):
- """Run all the scripts for a phase.
-
- This is the main function of the HookMaster.
-
- @param phase: one of L{constants.HOOKS_PHASE_POST} or
- L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
- @param nodes: overrides the predefined list of nodes for the given phase
- @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
-
- """
- if phase == constants.HOOKS_PHASE_PRE:
- if nodes is None:
- nodes = self.pre_nodes
- env = self.pre_env
- elif phase == constants.HOOKS_PHASE_POST:
- if nodes is None:
- nodes = self.post_nodes
- env = self._BuildEnv(phase)
- else:
- raise AssertionError("Unknown phase '%s'" % phase)
-
- if not nodes:
- # 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(nodes, self.lu.HPATH, phase, env)
- if not results:
- msg = "Communication Failure"
- if phase == constants.HOOKS_PHASE_PRE:
- raise errors.HooksFailure(msg)
- else:
- self.lu.LogWarning(msg)
- return results
-
- errs = []
- for node_name in results:
- res = results[node_name]
- if res.offline:
- continue
-
- msg = res.fail_msg
- if msg:
- self.lu.LogWarning("Communication failure to node %s: %s",
- node_name, msg)
- continue
-
- for script, hkr, output in res.payload:
- if hkr == constants.HKR_FAIL:
- if phase == constants.HOOKS_PHASE_PRE:
- errs.append((node_name, script, output))
- else:
- if not output:
- output = "(no output)"
- self.lu.LogWarning("On %s script %s failed, output: %s" %
- (node_name, script, output))
-
- if errs and phase == constants.HOOKS_PHASE_PRE:
- raise errors.HooksAbort(errs)
-
- return results
-
- def RunConfigUpdate(self):
- """Run the special configuration update hook
-
- This is a special hook that runs only on the master after each
- top-level LI if the configuration has been updated.
-
- """
- phase = constants.HOOKS_PHASE_POST
- hpath = constants.HOOKS_NAME_CFGUPDATE
- nodes = [self.lu.cfg.GetMasterNode()]
- self._RunWrapper(nodes, hpath, phase, self.pre_env)