Revision 68d95757
b/Makefile.am | ||
---|---|---|
252 | 252 |
lib/constants.py \ |
253 | 253 |
lib/daemon.py \ |
254 | 254 |
lib/errors.py \ |
255 |
lib/hooksmaster.py \ |
|
255 | 256 |
lib/ht.py \ |
256 | 257 |
lib/jqueue.py \ |
257 | 258 |
lib/jstore.py \ |
b/lib/backend.py | ||
---|---|---|
60 | 60 |
from ganeti import serializer |
61 | 61 |
from ganeti import netutils |
62 | 62 |
from ganeti import runtime |
63 |
from ganeti import mcpu |
|
64 | 63 |
from ganeti import compat |
65 | 64 |
from ganeti import pathutils |
66 | 65 |
from ganeti import vcluster |
67 | 66 |
from ganeti import ht |
67 |
from ganeti import hooksmaster |
|
68 | 68 |
|
69 | 69 |
|
70 | 70 |
_BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id" |
... | ... | |
297 | 297 |
|
298 | 298 |
cfg = _GetConfig() |
299 | 299 |
hr = HooksRunner() |
300 |
hm = mcpu.HooksMaster(hook_opcode, hooks_path, nodes, hr.RunLocalHooks,
|
|
301 |
None, env_fn, logging.warning, cfg.GetClusterName(),
|
|
302 |
cfg.GetMasterNode())
|
|
303 |
|
|
300 |
hm = hooksmaster.HooksMaster(hook_opcode, hooks_path, nodes,
|
|
301 |
hr.RunLocalHooks, None, env_fn,
|
|
302 |
logging.warning, cfg.GetClusterName(),
|
|
303 |
cfg.GetMasterNode()) |
|
304 | 304 |
hm.RunPhase(constants.HOOKS_PHASE_PRE) |
305 | 305 |
result = fn(*args, **kwargs) |
306 | 306 |
hm.RunPhase(constants.HOOKS_PHASE_POST) |
b/lib/hooksmaster.py | ||
---|---|---|
1 |
# |
|
2 |
# |
|
3 |
|
|
4 |
# Copyright (C) 2006, 2007, 2011, 2012 Google Inc. |
|
5 |
# |
|
6 |
# This program is free software; you can redistribute it and/or modify |
|
7 |
# it under the terms of the GNU General Public License as published by |
|
8 |
# the Free Software Foundation; either version 2 of the License, or |
|
9 |
# (at your option) any later version. |
|
10 |
# |
|
11 |
# This program is distributed in the hope that it will be useful, but |
|
12 |
# WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 |
# General Public License for more details. |
|
15 |
# |
|
16 |
# You should have received a copy of the GNU General Public License |
|
17 |
# along with this program; if not, write to the Free Software |
|
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
19 |
# 02110-1301, USA. |
|
20 |
|
|
21 |
|
|
22 |
"""Module implementing the logic for running hooks. |
|
23 |
|
|
24 |
""" |
|
25 |
|
|
26 |
from ganeti import constants |
|
27 |
from ganeti import errors |
|
28 |
from ganeti import utils |
|
29 |
from ganeti import compat |
|
30 |
from ganeti import pathutils |
|
31 |
|
|
32 |
|
|
33 |
def _RpcResultsToHooksResults(rpc_results): |
|
34 |
"""Function to convert RPC results to the format expected by HooksMaster. |
|
35 |
|
|
36 |
@type rpc_results: dict(node: L{rpc.RpcResult}) |
|
37 |
@param rpc_results: RPC results |
|
38 |
@rtype: dict(node: (fail_msg, offline, hooks_results)) |
|
39 |
@return: RPC results unpacked according to the format expected by |
|
40 |
L({hooksmaster.HooksMaster} |
|
41 |
|
|
42 |
""" |
|
43 |
return dict((node, (rpc_res.fail_msg, rpc_res.offline, rpc_res.payload)) |
|
44 |
for (node, rpc_res) in rpc_results.items()) |
|
45 |
|
|
46 |
|
|
47 |
class HooksMaster(object): |
|
48 |
def __init__(self, opcode, hooks_path, nodes, hooks_execution_fn, |
|
49 |
hooks_results_adapt_fn, build_env_fn, log_fn, htype=None, |
|
50 |
cluster_name=None, master_name=None): |
|
51 |
"""Base class for hooks masters. |
|
52 |
|
|
53 |
This class invokes the execution of hooks according to the behaviour |
|
54 |
specified by its parameters. |
|
55 |
|
|
56 |
@type opcode: string |
|
57 |
@param opcode: opcode of the operation to which the hooks are tied |
|
58 |
@type hooks_path: string |
|
59 |
@param hooks_path: prefix of the hooks directories |
|
60 |
@type nodes: 2-tuple of lists |
|
61 |
@param nodes: 2-tuple of lists containing nodes on which pre-hooks must be |
|
62 |
run and nodes on which post-hooks must be run |
|
63 |
@type hooks_execution_fn: function that accepts the following parameters: |
|
64 |
(node_list, hooks_path, phase, environment) |
|
65 |
@param hooks_execution_fn: function that will execute the hooks; can be |
|
66 |
None, indicating that no conversion is necessary. |
|
67 |
@type hooks_results_adapt_fn: function |
|
68 |
@param hooks_results_adapt_fn: function that will adapt the return value of |
|
69 |
hooks_execution_fn to the format expected by RunPhase |
|
70 |
@type build_env_fn: function that returns a dictionary having strings as |
|
71 |
keys |
|
72 |
@param build_env_fn: function that builds the environment for the hooks |
|
73 |
@type log_fn: function that accepts a string |
|
74 |
@param log_fn: logging function |
|
75 |
@type htype: string or None |
|
76 |
@param htype: None or one of L{constants.HTYPE_CLUSTER}, |
|
77 |
L{constants.HTYPE_NODE}, L{constants.HTYPE_INSTANCE} |
|
78 |
@type cluster_name: string |
|
79 |
@param cluster_name: name of the cluster |
|
80 |
@type master_name: string |
|
81 |
@param master_name: name of the master |
|
82 |
|
|
83 |
""" |
|
84 |
self.opcode = opcode |
|
85 |
self.hooks_path = hooks_path |
|
86 |
self.hooks_execution_fn = hooks_execution_fn |
|
87 |
self.hooks_results_adapt_fn = hooks_results_adapt_fn |
|
88 |
self.build_env_fn = build_env_fn |
|
89 |
self.log_fn = log_fn |
|
90 |
self.htype = htype |
|
91 |
self.cluster_name = cluster_name |
|
92 |
self.master_name = master_name |
|
93 |
|
|
94 |
self.pre_env = self._BuildEnv(constants.HOOKS_PHASE_PRE) |
|
95 |
(self.pre_nodes, self.post_nodes) = nodes |
|
96 |
|
|
97 |
def _BuildEnv(self, phase): |
|
98 |
"""Compute the environment and the target nodes. |
|
99 |
|
|
100 |
Based on the opcode and the current node list, this builds the |
|
101 |
environment for the hooks and the target node list for the run. |
|
102 |
|
|
103 |
""" |
|
104 |
if phase == constants.HOOKS_PHASE_PRE: |
|
105 |
prefix = "GANETI_" |
|
106 |
elif phase == constants.HOOKS_PHASE_POST: |
|
107 |
prefix = "GANETI_POST_" |
|
108 |
else: |
|
109 |
raise AssertionError("Unknown phase '%s'" % phase) |
|
110 |
|
|
111 |
env = {} |
|
112 |
|
|
113 |
if self.hooks_path is not None: |
|
114 |
phase_env = self.build_env_fn() |
|
115 |
if phase_env: |
|
116 |
assert not compat.any(key.upper().startswith(prefix) |
|
117 |
for key in phase_env) |
|
118 |
env.update(("%s%s" % (prefix, key), value) |
|
119 |
for (key, value) in phase_env.items()) |
|
120 |
|
|
121 |
if phase == constants.HOOKS_PHASE_PRE: |
|
122 |
assert compat.all((key.startswith("GANETI_") and |
|
123 |
not key.startswith("GANETI_POST_")) |
|
124 |
for key in env) |
|
125 |
|
|
126 |
elif phase == constants.HOOKS_PHASE_POST: |
|
127 |
assert compat.all(key.startswith("GANETI_POST_") for key in env) |
|
128 |
assert isinstance(self.pre_env, dict) |
|
129 |
|
|
130 |
# Merge with pre-phase environment |
|
131 |
assert not compat.any(key.startswith("GANETI_POST_") |
|
132 |
for key in self.pre_env) |
|
133 |
env.update(self.pre_env) |
|
134 |
else: |
|
135 |
raise AssertionError("Unknown phase '%s'" % phase) |
|
136 |
|
|
137 |
return env |
|
138 |
|
|
139 |
def _RunWrapper(self, node_list, hpath, phase, phase_env): |
|
140 |
"""Simple wrapper over self.callfn. |
|
141 |
|
|
142 |
This method fixes the environment before executing the hooks. |
|
143 |
|
|
144 |
""" |
|
145 |
env = { |
|
146 |
"PATH": constants.HOOKS_PATH, |
|
147 |
"GANETI_HOOKS_VERSION": constants.HOOKS_VERSION, |
|
148 |
"GANETI_OP_CODE": self.opcode, |
|
149 |
"GANETI_DATA_DIR": pathutils.DATA_DIR, |
|
150 |
"GANETI_HOOKS_PHASE": phase, |
|
151 |
"GANETI_HOOKS_PATH": hpath, |
|
152 |
} |
|
153 |
|
|
154 |
if self.htype: |
|
155 |
env["GANETI_OBJECT_TYPE"] = self.htype |
|
156 |
|
|
157 |
if self.cluster_name is not None: |
|
158 |
env["GANETI_CLUSTER"] = self.cluster_name |
|
159 |
|
|
160 |
if self.master_name is not None: |
|
161 |
env["GANETI_MASTER"] = self.master_name |
|
162 |
|
|
163 |
if phase_env: |
|
164 |
env = utils.algo.JoinDisjointDicts(env, phase_env) |
|
165 |
|
|
166 |
# Convert everything to strings |
|
167 |
env = dict([(str(key), str(val)) for key, val in env.iteritems()]) |
|
168 |
|
|
169 |
assert compat.all(key == "PATH" or key.startswith("GANETI_") |
|
170 |
for key in env) |
|
171 |
|
|
172 |
return self.hooks_execution_fn(node_list, hpath, phase, env) |
|
173 |
|
|
174 |
def RunPhase(self, phase, nodes=None): |
|
175 |
"""Run all the scripts for a phase. |
|
176 |
|
|
177 |
This is the main function of the HookMaster. |
|
178 |
It executes self.hooks_execution_fn, and after running |
|
179 |
self.hooks_results_adapt_fn on its results it expects them to be in the form |
|
180 |
{node_name: (fail_msg, [(script, result, output), ...]}). |
|
181 |
|
|
182 |
@param phase: one of L{constants.HOOKS_PHASE_POST} or |
|
183 |
L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase |
|
184 |
@param nodes: overrides the predefined list of nodes for the given phase |
|
185 |
@return: the processed results of the hooks multi-node rpc call |
|
186 |
@raise errors.HooksFailure: on communication failure to the nodes |
|
187 |
@raise errors.HooksAbort: on failure of one of the hooks |
|
188 |
|
|
189 |
""" |
|
190 |
if phase == constants.HOOKS_PHASE_PRE: |
|
191 |
if nodes is None: |
|
192 |
nodes = self.pre_nodes |
|
193 |
env = self.pre_env |
|
194 |
elif phase == constants.HOOKS_PHASE_POST: |
|
195 |
if nodes is None: |
|
196 |
nodes = self.post_nodes |
|
197 |
env = self._BuildEnv(phase) |
|
198 |
else: |
|
199 |
raise AssertionError("Unknown phase '%s'" % phase) |
|
200 |
|
|
201 |
if not nodes: |
|
202 |
# empty node list, we should not attempt to run this as either |
|
203 |
# we're in the cluster init phase and the rpc client part can't |
|
204 |
# even attempt to run, or this LU doesn't do hooks at all |
|
205 |
return |
|
206 |
|
|
207 |
results = self._RunWrapper(nodes, self.hooks_path, phase, env) |
|
208 |
if not results: |
|
209 |
msg = "Communication Failure" |
|
210 |
if phase == constants.HOOKS_PHASE_PRE: |
|
211 |
raise errors.HooksFailure(msg) |
|
212 |
else: |
|
213 |
self.log_fn(msg) |
|
214 |
return results |
|
215 |
|
|
216 |
converted_res = results |
|
217 |
if self.hooks_results_adapt_fn: |
|
218 |
converted_res = self.hooks_results_adapt_fn(results) |
|
219 |
|
|
220 |
errs = [] |
|
221 |
for node_name, (fail_msg, offline, hooks_results) in converted_res.items(): |
|
222 |
if offline: |
|
223 |
continue |
|
224 |
|
|
225 |
if fail_msg: |
|
226 |
self.log_fn("Communication failure to node %s: %s", node_name, fail_msg) |
|
227 |
continue |
|
228 |
|
|
229 |
for script, hkr, output in hooks_results: |
|
230 |
if hkr == constants.HKR_FAIL: |
|
231 |
if phase == constants.HOOKS_PHASE_PRE: |
|
232 |
errs.append((node_name, script, output)) |
|
233 |
else: |
|
234 |
if not output: |
|
235 |
output = "(no output)" |
|
236 |
self.log_fn("On %s script %s failed, output: %s" % |
|
237 |
(node_name, script, output)) |
|
238 |
|
|
239 |
if errs and phase == constants.HOOKS_PHASE_PRE: |
|
240 |
raise errors.HooksAbort(errs) |
|
241 |
|
|
242 |
return results |
|
243 |
|
|
244 |
def RunConfigUpdate(self): |
|
245 |
"""Run the special configuration update hook |
|
246 |
|
|
247 |
This is a special hook that runs only on the master after each |
|
248 |
top-level LI if the configuration has been updated. |
|
249 |
|
|
250 |
""" |
|
251 |
phase = constants.HOOKS_PHASE_POST |
|
252 |
hpath = constants.HOOKS_NAME_CFGUPDATE |
|
253 |
nodes = [self.master_name] |
|
254 |
self._RunWrapper(nodes, hpath, phase, self.pre_env) |
|
255 |
|
|
256 |
@staticmethod |
|
257 |
def BuildFromLu(hooks_execution_fn, lu): |
|
258 |
if lu.HPATH is None: |
|
259 |
nodes = (None, None) |
|
260 |
else: |
|
261 |
nodes = map(frozenset, lu.BuildHooksNodes()) |
|
262 |
|
|
263 |
master_name = cluster_name = None |
|
264 |
if lu.cfg: |
|
265 |
master_name = lu.cfg.GetMasterNode() |
|
266 |
cluster_name = lu.cfg.GetClusterName() |
|
267 |
|
|
268 |
return HooksMaster(lu.op.OP_ID, lu.HPATH, nodes, hooks_execution_fn, |
|
269 |
_RpcResultsToHooksResults, lu.BuildHooksEnv, |
|
270 |
lu.LogWarning, lu.HTYPE, cluster_name, master_name) |
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) |
b/test/py/ganeti.hooks_unittest.py | ||
---|---|---|
30 | 30 |
|
31 | 31 |
from ganeti import errors |
32 | 32 |
from ganeti import opcodes |
33 |
from ganeti import mcpu
|
|
33 |
from ganeti import hooksmaster
|
|
34 | 34 |
from ganeti import backend |
35 | 35 |
from ganeti import constants |
36 | 36 |
from ganeti import cmdlib |
... | ... | |
250 | 250 |
|
251 | 251 |
def testTotalFalse(self): |
252 | 252 |
"""Test complete rpc failure""" |
253 |
hm = mcpu.HooksMaster.BuildFromLu(self._call_false, self.lu)
|
|
253 |
hm = hooksmaster.HooksMaster.BuildFromLu(self._call_false, self.lu)
|
|
254 | 254 |
self.failUnlessRaises(errors.HooksFailure, |
255 | 255 |
hm.RunPhase, constants.HOOKS_PHASE_PRE) |
256 | 256 |
hm.RunPhase(constants.HOOKS_PHASE_POST) |
257 | 257 |
|
258 | 258 |
def testIndividualFalse(self): |
259 | 259 |
"""Test individual node failure""" |
260 |
hm = mcpu.HooksMaster.BuildFromLu(self._call_nodes_false, self.lu)
|
|
260 |
hm = hooksmaster.HooksMaster.BuildFromLu(self._call_nodes_false, self.lu)
|
|
261 | 261 |
hm.RunPhase(constants.HOOKS_PHASE_PRE) |
262 | 262 |
#self.failUnlessRaises(errors.HooksFailure, |
263 | 263 |
# hm.RunPhase, constants.HOOKS_PHASE_PRE) |
... | ... | |
265 | 265 |
|
266 | 266 |
def testScriptFalse(self): |
267 | 267 |
"""Test individual rpc failure""" |
268 |
hm = mcpu.HooksMaster.BuildFromLu(self._call_script_fail, self.lu)
|
|
268 |
hm = hooksmaster.HooksMaster.BuildFromLu(self._call_script_fail, self.lu)
|
|
269 | 269 |
self.failUnlessRaises(errors.HooksAbort, |
270 | 270 |
hm.RunPhase, constants.HOOKS_PHASE_PRE) |
271 | 271 |
hm.RunPhase(constants.HOOKS_PHASE_POST) |
272 | 272 |
|
273 | 273 |
def testScriptSucceed(self): |
274 | 274 |
"""Test individual rpc failure""" |
275 |
hm = mcpu.HooksMaster.BuildFromLu(FakeHooksRpcSuccess, self.lu)
|
|
275 |
hm = hooksmaster.HooksMaster.BuildFromLu(FakeHooksRpcSuccess, self.lu)
|
|
276 | 276 |
for phase in (constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST): |
277 | 277 |
hm.RunPhase(phase) |
278 | 278 |
|
... | ... | |
323 | 323 |
def testEmptyEnv(self): |
324 | 324 |
# Check pre-phase hook |
325 | 325 |
self.lu.hook_env = {} |
326 |
hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
326 |
hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
327 | 327 |
hm.RunPhase(constants.HOOKS_PHASE_PRE) |
328 | 328 |
|
329 | 329 |
(node_list, hpath, phase, env) = self._rpcs.pop(0) |
... | ... | |
349 | 349 |
self.lu.hook_env = { |
350 | 350 |
"FOO": "pre-foo-value", |
351 | 351 |
} |
352 |
hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
352 |
hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
353 | 353 |
hm.RunPhase(constants.HOOKS_PHASE_PRE) |
354 | 354 |
|
355 | 355 |
(node_list, hpath, phase, env) = self._rpcs.pop(0) |
... | ... | |
396 | 396 |
self.lu.hook_env = { name: "value" } |
397 | 397 |
|
398 | 398 |
# Test using a clean HooksMaster instance |
399 |
hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
399 |
hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
400 | 400 |
|
401 | 401 |
for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]: |
402 | 402 |
self.assertRaises(AssertionError, hm.RunPhase, phase) |
... | ... | |
404 | 404 |
|
405 | 405 |
def testNoNodes(self): |
406 | 406 |
self.lu.hook_env = {} |
407 |
hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
407 |
hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
408 | 408 |
hm.RunPhase(constants.HOOKS_PHASE_PRE, nodes=[]) |
409 | 409 |
self.assertRaises(IndexError, self._rpcs.pop) |
410 | 410 |
|
... | ... | |
416 | 416 |
"node93782.example.net", |
417 | 417 |
] |
418 | 418 |
|
419 |
hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
419 |
hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
420 | 420 |
|
421 | 421 |
for phase in [constants.HOOKS_PHASE_PRE, constants.HOOKS_PHASE_POST]: |
422 | 422 |
hm.RunPhase(phase, nodes=nodes) |
... | ... | |
434 | 434 |
"FOO": "value", |
435 | 435 |
} |
436 | 436 |
|
437 |
hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
437 |
hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
438 | 438 |
hm.RunConfigUpdate() |
439 | 439 |
|
440 | 440 |
(node_list, hpath, phase, env) = self._rpcs.pop(0) |
... | ... | |
453 | 453 |
"FOO": "value", |
454 | 454 |
} |
455 | 455 |
|
456 |
hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
456 |
hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
457 | 457 |
hm.RunPhase(constants.HOOKS_PHASE_POST) |
458 | 458 |
|
459 | 459 |
(node_list, hpath, phase, env) = self._rpcs.pop(0) |
... | ... | |
471 | 471 |
self.assertRaises(AssertionError, self.lu.BuildHooksEnv) |
472 | 472 |
self.assertRaises(AssertionError, self.lu.BuildHooksNodes) |
473 | 473 |
|
474 |
hm = mcpu.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
474 |
hm = hooksmaster.HooksMaster.BuildFromLu(self._HooksRpc, self.lu)
|
|
475 | 475 |
self.assertEqual(hm.pre_env, {}) |
476 | 476 |
self.assertRaises(IndexError, self._rpcs.pop) |
477 | 477 |
|
Also available in: Unified diff