Revision 68d95757 lib/mcpu.py
b/lib/mcpu.py | ||
---|---|---|
38 | 38 |
from ganeti import opcodes |
39 | 39 |
from ganeti import constants |
40 | 40 |
from ganeti import errors |
41 |
from ganeti import hooksmaster |
|
41 | 42 |
from ganeti import cmdlib |
42 | 43 |
from ganeti import locking |
43 | 44 |
from ganeti import utils |
44 | 45 |
from ganeti import compat |
45 |
from ganeti import pathutils |
|
46 | 46 |
|
47 | 47 |
|
48 | 48 |
_OP_PREFIX = "Op" |
... | ... | |
245 | 245 |
" queries) can not submit jobs") |
246 | 246 |
|
247 | 247 |
|
248 |
def _RpcResultsToHooksResults(rpc_results): |
|
249 |
"""Function to convert RPC results to the format expected by HooksMaster. |
|
250 |
|
|
251 |
@type rpc_results: dict(node: L{rpc.RpcResult}) |
|
252 |
@param rpc_results: RPC results |
|
253 |
@rtype: dict(node: (fail_msg, offline, hooks_results)) |
|
254 |
@return: RPC results unpacked according to the format expected by |
|
255 |
L({mcpu.HooksMaster} |
|
256 |
|
|
257 |
""" |
|
258 |
return dict((node, (rpc_res.fail_msg, rpc_res.offline, rpc_res.payload)) |
|
259 |
for (node, rpc_res) in rpc_results.items()) |
|
260 |
|
|
261 |
|
|
262 | 248 |
def _VerifyLocks(lu, glm, _mode_whitelist=_NODE_ALLOC_MODE_WHITELIST, |
263 | 249 |
_nal_whitelist=_NODE_ALLOC_WHITELIST): |
264 | 250 |
"""Performs consistency checks on locks acquired by a logical unit. |
... | ... | |
314 | 300 |
self._ec_id = ec_id |
315 | 301 |
self._cbs = None |
316 | 302 |
self.rpc = context.rpc |
317 |
self.hmclass = HooksMaster |
|
303 |
self.hmclass = hooksmaster.HooksMaster
|
|
318 | 304 |
self._enable_locks = enable_locks |
319 | 305 |
|
320 | 306 |
def _CheckLocksEnabled(self): |
... | ... | |
603 | 589 |
raise errors.ProgrammerError("Tried to use execution context id when" |
604 | 590 |
" not set") |
605 | 591 |
return self._ec_id |
606 |
|
|
607 |
|
|
608 |
class HooksMaster(object): |
|
609 |
def __init__(self, opcode, hooks_path, nodes, hooks_execution_fn, |
|
610 |
hooks_results_adapt_fn, build_env_fn, log_fn, htype=None, |
|
611 |
cluster_name=None, master_name=None): |
|
612 |
"""Base class for hooks masters. |
|
613 |
|
|
614 |
This class invokes the execution of hooks according to the behaviour |
|
615 |
specified by its parameters. |
|
616 |
|
|
617 |
@type opcode: string |
|
618 |
@param opcode: opcode of the operation to which the hooks are tied |
|
619 |
@type hooks_path: string |
|
620 |
@param hooks_path: prefix of the hooks directories |
|
621 |
@type nodes: 2-tuple of lists |
|
622 |
@param nodes: 2-tuple of lists containing nodes on which pre-hooks must be |
|
623 |
run and nodes on which post-hooks must be run |
|
624 |
@type hooks_execution_fn: function that accepts the following parameters: |
|
625 |
(node_list, hooks_path, phase, environment) |
|
626 |
@param hooks_execution_fn: function that will execute the hooks; can be |
|
627 |
None, indicating that no conversion is necessary. |
|
628 |
@type hooks_results_adapt_fn: function |
|
629 |
@param hooks_results_adapt_fn: function that will adapt the return value of |
|
630 |
hooks_execution_fn to the format expected by RunPhase |
|
631 |
@type build_env_fn: function that returns a dictionary having strings as |
|
632 |
keys |
|
633 |
@param build_env_fn: function that builds the environment for the hooks |
|
634 |
@type log_fn: function that accepts a string |
|
635 |
@param log_fn: logging function |
|
636 |
@type htype: string or None |
|
637 |
@param htype: None or one of L{constants.HTYPE_CLUSTER}, |
|
638 |
L{constants.HTYPE_NODE}, L{constants.HTYPE_INSTANCE} |
|
639 |
@type cluster_name: string |
|
640 |
@param cluster_name: name of the cluster |
|
641 |
@type master_name: string |
|
642 |
@param master_name: name of the master |
|
643 |
|
|
644 |
""" |
|
645 |
self.opcode = opcode |
|
646 |
self.hooks_path = hooks_path |
|
647 |
self.hooks_execution_fn = hooks_execution_fn |
|
648 |
self.hooks_results_adapt_fn = hooks_results_adapt_fn |
|
649 |
self.build_env_fn = build_env_fn |
|
650 |
self.log_fn = log_fn |
|
651 |
self.htype = htype |
|
652 |
self.cluster_name = cluster_name |
|
653 |
self.master_name = master_name |
|
654 |
|
|
655 |
self.pre_env = self._BuildEnv(constants.HOOKS_PHASE_PRE) |
|
656 |
(self.pre_nodes, self.post_nodes) = nodes |
|
657 |
|
|
658 |
def _BuildEnv(self, phase): |
|
659 |
"""Compute the environment and the target nodes. |
|
660 |
|
|
661 |
Based on the opcode and the current node list, this builds the |
|
662 |
environment for the hooks and the target node list for the run. |
|
663 |
|
|
664 |
""" |
|
665 |
if phase == constants.HOOKS_PHASE_PRE: |
|
666 |
prefix = "GANETI_" |
|
667 |
elif phase == constants.HOOKS_PHASE_POST: |
|
668 |
prefix = "GANETI_POST_" |
|
669 |
else: |
|
670 |
raise AssertionError("Unknown phase '%s'" % phase) |
|
671 |
|
|
672 |
env = {} |
|
673 |
|
|
674 |
if self.hooks_path is not None: |
|
675 |
phase_env = self.build_env_fn() |
|
676 |
if phase_env: |
|
677 |
assert not compat.any(key.upper().startswith(prefix) |
|
678 |
for key in phase_env) |
|
679 |
env.update(("%s%s" % (prefix, key), value) |
|
680 |
for (key, value) in phase_env.items()) |
|
681 |
|
|
682 |
if phase == constants.HOOKS_PHASE_PRE: |
|
683 |
assert compat.all((key.startswith("GANETI_") and |
|
684 |
not key.startswith("GANETI_POST_")) |
|
685 |
for key in env) |
|
686 |
|
|
687 |
elif phase == constants.HOOKS_PHASE_POST: |
|
688 |
assert compat.all(key.startswith("GANETI_POST_") for key in env) |
|
689 |
assert isinstance(self.pre_env, dict) |
|
690 |
|
|
691 |
# Merge with pre-phase environment |
|
692 |
assert not compat.any(key.startswith("GANETI_POST_") |
|
693 |
for key in self.pre_env) |
|
694 |
env.update(self.pre_env) |
|
695 |
else: |
|
696 |
raise AssertionError("Unknown phase '%s'" % phase) |
|
697 |
|
|
698 |
return env |
|
699 |
|
|
700 |
def _RunWrapper(self, node_list, hpath, phase, phase_env): |
|
701 |
"""Simple wrapper over self.callfn. |
|
702 |
|
|
703 |
This method fixes the environment before executing the hooks. |
|
704 |
|
|
705 |
""" |
|
706 |
env = { |
|
707 |
"PATH": constants.HOOKS_PATH, |
|
708 |
"GANETI_HOOKS_VERSION": constants.HOOKS_VERSION, |
|
709 |
"GANETI_OP_CODE": self.opcode, |
|
710 |
"GANETI_DATA_DIR": pathutils.DATA_DIR, |
|
711 |
"GANETI_HOOKS_PHASE": phase, |
|
712 |
"GANETI_HOOKS_PATH": hpath, |
|
713 |
} |
|
714 |
|
|
715 |
if self.htype: |
|
716 |
env["GANETI_OBJECT_TYPE"] = self.htype |
|
717 |
|
|
718 |
if self.cluster_name is not None: |
|
719 |
env["GANETI_CLUSTER"] = self.cluster_name |
|
720 |
|
|
721 |
if self.master_name is not None: |
|
722 |
env["GANETI_MASTER"] = self.master_name |
|
723 |
|
|
724 |
if phase_env: |
|
725 |
env = utils.algo.JoinDisjointDicts(env, phase_env) |
|
726 |
|
|
727 |
# Convert everything to strings |
|
728 |
env = dict([(str(key), str(val)) for key, val in env.iteritems()]) |
|
729 |
|
|
730 |
assert compat.all(key == "PATH" or key.startswith("GANETI_") |
|
731 |
for key in env) |
|
732 |
|
|
733 |
return self.hooks_execution_fn(node_list, hpath, phase, env) |
|
734 |
|
|
735 |
def RunPhase(self, phase, nodes=None): |
|
736 |
"""Run all the scripts for a phase. |
|
737 |
|
|
738 |
This is the main function of the HookMaster. |
|
739 |
It executes self.hooks_execution_fn, and after running |
|
740 |
self.hooks_results_adapt_fn on its results it expects them to be in the form |
|
741 |
{node_name: (fail_msg, [(script, result, output), ...]}). |
|
742 |
|
|
743 |
@param phase: one of L{constants.HOOKS_PHASE_POST} or |
|
744 |
L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase |
|
745 |
@param nodes: overrides the predefined list of nodes for the given phase |
|
746 |
@return: the processed results of the hooks multi-node rpc call |
|
747 |
@raise errors.HooksFailure: on communication failure to the nodes |
|
748 |
@raise errors.HooksAbort: on failure of one of the hooks |
|
749 |
|
|
750 |
""" |
|
751 |
if phase == constants.HOOKS_PHASE_PRE: |
|
752 |
if nodes is None: |
|
753 |
nodes = self.pre_nodes |
|
754 |
env = self.pre_env |
|
755 |
elif phase == constants.HOOKS_PHASE_POST: |
|
756 |
if nodes is None: |
|
757 |
nodes = self.post_nodes |
|
758 |
env = self._BuildEnv(phase) |
|
759 |
else: |
|
760 |
raise AssertionError("Unknown phase '%s'" % phase) |
|
761 |
|
|
762 |
if not nodes: |
|
763 |
# empty node list, we should not attempt to run this as either |
|
764 |
# we're in the cluster init phase and the rpc client part can't |
|
765 |
# even attempt to run, or this LU doesn't do hooks at all |
|
766 |
return |
|
767 |
|
|
768 |
results = self._RunWrapper(nodes, self.hooks_path, phase, env) |
|
769 |
if not results: |
|
770 |
msg = "Communication Failure" |
|
771 |
if phase == constants.HOOKS_PHASE_PRE: |
|
772 |
raise errors.HooksFailure(msg) |
|
773 |
else: |
|
774 |
self.log_fn(msg) |
|
775 |
return results |
|
776 |
|
|
777 |
converted_res = results |
|
778 |
if self.hooks_results_adapt_fn: |
|
779 |
converted_res = self.hooks_results_adapt_fn(results) |
|
780 |
|
|
781 |
errs = [] |
|
782 |
for node_name, (fail_msg, offline, hooks_results) in converted_res.items(): |
|
783 |
if offline: |
|
784 |
continue |
|
785 |
|
|
786 |
if fail_msg: |
|
787 |
self.log_fn("Communication failure to node %s: %s", node_name, fail_msg) |
|
788 |
continue |
|
789 |
|
|
790 |
for script, hkr, output in hooks_results: |
|
791 |
if hkr == constants.HKR_FAIL: |
|
792 |
if phase == constants.HOOKS_PHASE_PRE: |
|
793 |
errs.append((node_name, script, output)) |
|
794 |
else: |
|
795 |
if not output: |
|
796 |
output = "(no output)" |
|
797 |
self.log_fn("On %s script %s failed, output: %s" % |
|
798 |
(node_name, script, output)) |
|
799 |
|
|
800 |
if errs and phase == constants.HOOKS_PHASE_PRE: |
|
801 |
raise errors.HooksAbort(errs) |
|
802 |
|
|
803 |
return results |
|
804 |
|
|
805 |
def RunConfigUpdate(self): |
|
806 |
"""Run the special configuration update hook |
|
807 |
|
|
808 |
This is a special hook that runs only on the master after each |
|
809 |
top-level LI if the configuration has been updated. |
|
810 |
|
|
811 |
""" |
|
812 |
phase = constants.HOOKS_PHASE_POST |
|
813 |
hpath = constants.HOOKS_NAME_CFGUPDATE |
|
814 |
nodes = [self.master_name] |
|
815 |
self._RunWrapper(nodes, hpath, phase, self.pre_env) |
|
816 |
|
|
817 |
@staticmethod |
|
818 |
def BuildFromLu(hooks_execution_fn, lu): |
|
819 |
if lu.HPATH is None: |
|
820 |
nodes = (None, None) |
|
821 |
else: |
|
822 |
nodes = map(frozenset, lu.BuildHooksNodes()) |
|
823 |
|
|
824 |
master_name = cluster_name = None |
|
825 |
if lu.cfg: |
|
826 |
master_name = lu.cfg.GetMasterNode() |
|
827 |
cluster_name = lu.cfg.GetClusterName() |
|
828 |
|
|
829 |
return HooksMaster(lu.op.OP_ID, lu.HPATH, nodes, hooks_execution_fn, |
|
830 |
_RpcResultsToHooksResults, lu.BuildHooksEnv, |
|
831 |
lu.LogWarning, lu.HTYPE, cluster_name, master_name) |
Also available in: Unified diff