root / lib / hooksmaster.py @ bfca72bc
History | View | Annotate | Download (9.2 kB)
1 | 68d95757 | Guido Trotter | #
|
---|---|---|---|
2 | 68d95757 | Guido Trotter | #
|
3 | 68d95757 | Guido Trotter | |
4 | 68d95757 | Guido Trotter | # Copyright (C) 2006, 2007, 2011, 2012 Google Inc.
|
5 | 68d95757 | Guido Trotter | #
|
6 | 68d95757 | Guido Trotter | # This program is free software; you can redistribute it and/or modify
|
7 | 68d95757 | Guido Trotter | # it under the terms of the GNU General Public License as published by
|
8 | 68d95757 | Guido Trotter | # the Free Software Foundation; either version 2 of the License, or
|
9 | 68d95757 | Guido Trotter | # (at your option) any later version.
|
10 | 68d95757 | Guido Trotter | #
|
11 | 68d95757 | Guido Trotter | # This program is distributed in the hope that it will be useful, but
|
12 | 68d95757 | Guido Trotter | # WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 | 68d95757 | Guido Trotter | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 | 68d95757 | Guido Trotter | # General Public License for more details.
|
15 | 68d95757 | Guido Trotter | #
|
16 | 68d95757 | Guido Trotter | # You should have received a copy of the GNU General Public License
|
17 | 68d95757 | Guido Trotter | # along with this program; if not, write to the Free Software
|
18 | 68d95757 | Guido Trotter | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19 | 68d95757 | Guido Trotter | # 02110-1301, USA.
|
20 | 68d95757 | Guido Trotter | |
21 | 68d95757 | Guido Trotter | |
22 | 68d95757 | Guido Trotter | """Module implementing the logic for running hooks.
|
23 | 68d95757 | Guido Trotter |
|
24 | 68d95757 | Guido Trotter | """
|
25 | 68d95757 | Guido Trotter | |
26 | 68d95757 | Guido Trotter | from ganeti import constants |
27 | 68d95757 | Guido Trotter | from ganeti import errors |
28 | 68d95757 | Guido Trotter | from ganeti import utils |
29 | 68d95757 | Guido Trotter | from ganeti import compat |
30 | 68d95757 | Guido Trotter | from ganeti import pathutils |
31 | 68d95757 | Guido Trotter | |
32 | 68d95757 | Guido Trotter | |
33 | 68d95757 | Guido Trotter | def _RpcResultsToHooksResults(rpc_results): |
34 | 68d95757 | Guido Trotter | """Function to convert RPC results to the format expected by HooksMaster.
|
35 | 68d95757 | Guido Trotter |
|
36 | 68d95757 | Guido Trotter | @type rpc_results: dict(node: L{rpc.RpcResult})
|
37 | 68d95757 | Guido Trotter | @param rpc_results: RPC results
|
38 | 68d95757 | Guido Trotter | @rtype: dict(node: (fail_msg, offline, hooks_results))
|
39 | 68d95757 | Guido Trotter | @return: RPC results unpacked according to the format expected by
|
40 | 68d95757 | Guido Trotter | L({hooksmaster.HooksMaster}
|
41 | 68d95757 | Guido Trotter |
|
42 | 68d95757 | Guido Trotter | """
|
43 | 68d95757 | Guido Trotter | return dict((node, (rpc_res.fail_msg, rpc_res.offline, rpc_res.payload)) |
44 | 68d95757 | Guido Trotter | for (node, rpc_res) in rpc_results.items()) |
45 | 68d95757 | Guido Trotter | |
46 | 68d95757 | Guido Trotter | |
47 | 68d95757 | Guido Trotter | class HooksMaster(object): |
48 | 68d95757 | Guido Trotter | def __init__(self, opcode, hooks_path, nodes, hooks_execution_fn, |
49 | 68d95757 | Guido Trotter | hooks_results_adapt_fn, build_env_fn, log_fn, htype=None,
|
50 | 68d95757 | Guido Trotter | cluster_name=None, master_name=None): |
51 | 68d95757 | Guido Trotter | """Base class for hooks masters.
|
52 | 68d95757 | Guido Trotter |
|
53 | 68d95757 | Guido Trotter | This class invokes the execution of hooks according to the behaviour
|
54 | 68d95757 | Guido Trotter | specified by its parameters.
|
55 | 68d95757 | Guido Trotter |
|
56 | 68d95757 | Guido Trotter | @type opcode: string
|
57 | 68d95757 | Guido Trotter | @param opcode: opcode of the operation to which the hooks are tied
|
58 | 68d95757 | Guido Trotter | @type hooks_path: string
|
59 | 68d95757 | Guido Trotter | @param hooks_path: prefix of the hooks directories
|
60 | 68d95757 | Guido Trotter | @type nodes: 2-tuple of lists
|
61 | 68d95757 | Guido Trotter | @param nodes: 2-tuple of lists containing nodes on which pre-hooks must be
|
62 | 68d95757 | Guido Trotter | run and nodes on which post-hooks must be run
|
63 | 68d95757 | Guido Trotter | @type hooks_execution_fn: function that accepts the following parameters:
|
64 | 68d95757 | Guido Trotter | (node_list, hooks_path, phase, environment)
|
65 | 68d95757 | Guido Trotter | @param hooks_execution_fn: function that will execute the hooks; can be
|
66 | 68d95757 | Guido Trotter | None, indicating that no conversion is necessary.
|
67 | 68d95757 | Guido Trotter | @type hooks_results_adapt_fn: function
|
68 | 68d95757 | Guido Trotter | @param hooks_results_adapt_fn: function that will adapt the return value of
|
69 | 68d95757 | Guido Trotter | hooks_execution_fn to the format expected by RunPhase
|
70 | 68d95757 | Guido Trotter | @type build_env_fn: function that returns a dictionary having strings as
|
71 | 68d95757 | Guido Trotter | keys
|
72 | 68d95757 | Guido Trotter | @param build_env_fn: function that builds the environment for the hooks
|
73 | 68d95757 | Guido Trotter | @type log_fn: function that accepts a string
|
74 | 68d95757 | Guido Trotter | @param log_fn: logging function
|
75 | 68d95757 | Guido Trotter | @type htype: string or None
|
76 | 68d95757 | Guido Trotter | @param htype: None or one of L{constants.HTYPE_CLUSTER},
|
77 | 68d95757 | Guido Trotter | L{constants.HTYPE_NODE}, L{constants.HTYPE_INSTANCE}
|
78 | 68d95757 | Guido Trotter | @type cluster_name: string
|
79 | 68d95757 | Guido Trotter | @param cluster_name: name of the cluster
|
80 | 68d95757 | Guido Trotter | @type master_name: string
|
81 | 68d95757 | Guido Trotter | @param master_name: name of the master
|
82 | 68d95757 | Guido Trotter |
|
83 | 68d95757 | Guido Trotter | """
|
84 | 68d95757 | Guido Trotter | self.opcode = opcode
|
85 | 68d95757 | Guido Trotter | self.hooks_path = hooks_path
|
86 | 68d95757 | Guido Trotter | self.hooks_execution_fn = hooks_execution_fn
|
87 | 68d95757 | Guido Trotter | self.hooks_results_adapt_fn = hooks_results_adapt_fn
|
88 | 68d95757 | Guido Trotter | self.build_env_fn = build_env_fn
|
89 | 68d95757 | Guido Trotter | self.log_fn = log_fn
|
90 | 68d95757 | Guido Trotter | self.htype = htype
|
91 | 68d95757 | Guido Trotter | self.cluster_name = cluster_name
|
92 | 68d95757 | Guido Trotter | self.master_name = master_name
|
93 | 68d95757 | Guido Trotter | |
94 | 68d95757 | Guido Trotter | self.pre_env = self._BuildEnv(constants.HOOKS_PHASE_PRE) |
95 | 68d95757 | Guido Trotter | (self.pre_nodes, self.post_nodes) = nodes |
96 | 68d95757 | Guido Trotter | |
97 | 68d95757 | Guido Trotter | def _BuildEnv(self, phase): |
98 | 68d95757 | Guido Trotter | """Compute the environment and the target nodes.
|
99 | 68d95757 | Guido Trotter |
|
100 | 68d95757 | Guido Trotter | Based on the opcode and the current node list, this builds the
|
101 | 68d95757 | Guido Trotter | environment for the hooks and the target node list for the run.
|
102 | 68d95757 | Guido Trotter |
|
103 | 68d95757 | Guido Trotter | """
|
104 | 68d95757 | Guido Trotter | if phase == constants.HOOKS_PHASE_PRE:
|
105 | 68d95757 | Guido Trotter | prefix = "GANETI_"
|
106 | 68d95757 | Guido Trotter | elif phase == constants.HOOKS_PHASE_POST:
|
107 | 68d95757 | Guido Trotter | prefix = "GANETI_POST_"
|
108 | 68d95757 | Guido Trotter | else:
|
109 | 68d95757 | Guido Trotter | raise AssertionError("Unknown phase '%s'" % phase) |
110 | 68d95757 | Guido Trotter | |
111 | 68d95757 | Guido Trotter | env = {} |
112 | 68d95757 | Guido Trotter | |
113 | 68d95757 | Guido Trotter | if self.hooks_path is not None: |
114 | 68d95757 | Guido Trotter | phase_env = self.build_env_fn()
|
115 | 68d95757 | Guido Trotter | if phase_env:
|
116 | 68d95757 | Guido Trotter | assert not compat.any(key.upper().startswith(prefix) |
117 | 68d95757 | Guido Trotter | for key in phase_env) |
118 | 68d95757 | Guido Trotter | env.update(("%s%s" % (prefix, key), value)
|
119 | 68d95757 | Guido Trotter | for (key, value) in phase_env.items()) |
120 | 68d95757 | Guido Trotter | |
121 | 68d95757 | Guido Trotter | if phase == constants.HOOKS_PHASE_PRE:
|
122 | 68d95757 | Guido Trotter | assert compat.all((key.startswith("GANETI_") and |
123 | 68d95757 | Guido Trotter | not key.startswith("GANETI_POST_")) |
124 | 68d95757 | Guido Trotter | for key in env) |
125 | 68d95757 | Guido Trotter | |
126 | 68d95757 | Guido Trotter | elif phase == constants.HOOKS_PHASE_POST:
|
127 | 68d95757 | Guido Trotter | assert compat.all(key.startswith("GANETI_POST_") for key in env) |
128 | 68d95757 | Guido Trotter | assert isinstance(self.pre_env, dict) |
129 | 68d95757 | Guido Trotter | |
130 | 68d95757 | Guido Trotter | # Merge with pre-phase environment
|
131 | 68d95757 | Guido Trotter | assert not compat.any(key.startswith("GANETI_POST_") |
132 | 68d95757 | Guido Trotter | for key in self.pre_env) |
133 | 68d95757 | Guido Trotter | env.update(self.pre_env)
|
134 | 68d95757 | Guido Trotter | else:
|
135 | 68d95757 | Guido Trotter | raise AssertionError("Unknown phase '%s'" % phase) |
136 | 68d95757 | Guido Trotter | |
137 | 68d95757 | Guido Trotter | return env
|
138 | 68d95757 | Guido Trotter | |
139 | 68d95757 | Guido Trotter | def _RunWrapper(self, node_list, hpath, phase, phase_env): |
140 | 68d95757 | Guido Trotter | """Simple wrapper over self.callfn.
|
141 | 68d95757 | Guido Trotter |
|
142 | 68d95757 | Guido Trotter | This method fixes the environment before executing the hooks.
|
143 | 68d95757 | Guido Trotter |
|
144 | 68d95757 | Guido Trotter | """
|
145 | 68d95757 | Guido Trotter | env = { |
146 | 68d95757 | Guido Trotter | "PATH": constants.HOOKS_PATH,
|
147 | 68d95757 | Guido Trotter | "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
|
148 | 68d95757 | Guido Trotter | "GANETI_OP_CODE": self.opcode, |
149 | 68d95757 | Guido Trotter | "GANETI_DATA_DIR": pathutils.DATA_DIR,
|
150 | 68d95757 | Guido Trotter | "GANETI_HOOKS_PHASE": phase,
|
151 | 68d95757 | Guido Trotter | "GANETI_HOOKS_PATH": hpath,
|
152 | 68d95757 | Guido Trotter | } |
153 | 68d95757 | Guido Trotter | |
154 | 68d95757 | Guido Trotter | if self.htype: |
155 | 68d95757 | Guido Trotter | env["GANETI_OBJECT_TYPE"] = self.htype |
156 | 68d95757 | Guido Trotter | |
157 | 68d95757 | Guido Trotter | if self.cluster_name is not None: |
158 | 68d95757 | Guido Trotter | env["GANETI_CLUSTER"] = self.cluster_name |
159 | 68d95757 | Guido Trotter | |
160 | 68d95757 | Guido Trotter | if self.master_name is not None: |
161 | 68d95757 | Guido Trotter | env["GANETI_MASTER"] = self.master_name |
162 | 68d95757 | Guido Trotter | |
163 | 68d95757 | Guido Trotter | if phase_env:
|
164 | 68d95757 | Guido Trotter | env = utils.algo.JoinDisjointDicts(env, phase_env) |
165 | 68d95757 | Guido Trotter | |
166 | 68d95757 | Guido Trotter | # Convert everything to strings
|
167 | 68d95757 | Guido Trotter | env = dict([(str(key), str(val)) for key, val in env.iteritems()]) |
168 | 68d95757 | Guido Trotter | |
169 | 68d95757 | Guido Trotter | assert compat.all(key == "PATH" or key.startswith("GANETI_") |
170 | 68d95757 | Guido Trotter | for key in env) |
171 | 68d95757 | Guido Trotter | |
172 | 68d95757 | Guido Trotter | return self.hooks_execution_fn(node_list, hpath, phase, env) |
173 | 68d95757 | Guido Trotter | |
174 | 68d95757 | Guido Trotter | def RunPhase(self, phase, nodes=None): |
175 | 68d95757 | Guido Trotter | """Run all the scripts for a phase.
|
176 | 68d95757 | Guido Trotter |
|
177 | 68d95757 | Guido Trotter | This is the main function of the HookMaster.
|
178 | 68d95757 | Guido Trotter | It executes self.hooks_execution_fn, and after running
|
179 | 68d95757 | Guido Trotter | self.hooks_results_adapt_fn on its results it expects them to be in the form
|
180 | 68d95757 | Guido Trotter | {node_name: (fail_msg, [(script, result, output), ...]}).
|
181 | 68d95757 | Guido Trotter |
|
182 | 68d95757 | Guido Trotter | @param phase: one of L{constants.HOOKS_PHASE_POST} or
|
183 | 68d95757 | Guido Trotter | L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
|
184 | 68d95757 | Guido Trotter | @param nodes: overrides the predefined list of nodes for the given phase
|
185 | 68d95757 | Guido Trotter | @return: the processed results of the hooks multi-node rpc call
|
186 | 68d95757 | Guido Trotter | @raise errors.HooksFailure: on communication failure to the nodes
|
187 | 68d95757 | Guido Trotter | @raise errors.HooksAbort: on failure of one of the hooks
|
188 | 68d95757 | Guido Trotter |
|
189 | 68d95757 | Guido Trotter | """
|
190 | 68d95757 | Guido Trotter | if phase == constants.HOOKS_PHASE_PRE:
|
191 | 68d95757 | Guido Trotter | if nodes is None: |
192 | 68d95757 | Guido Trotter | nodes = self.pre_nodes
|
193 | 68d95757 | Guido Trotter | env = self.pre_env
|
194 | 68d95757 | Guido Trotter | elif phase == constants.HOOKS_PHASE_POST:
|
195 | 68d95757 | Guido Trotter | if nodes is None: |
196 | 68d95757 | Guido Trotter | nodes = self.post_nodes
|
197 | 68d95757 | Guido Trotter | env = self._BuildEnv(phase)
|
198 | 68d95757 | Guido Trotter | else:
|
199 | 68d95757 | Guido Trotter | raise AssertionError("Unknown phase '%s'" % phase) |
200 | 68d95757 | Guido Trotter | |
201 | 68d95757 | Guido Trotter | if not nodes: |
202 | 68d95757 | Guido Trotter | # empty node list, we should not attempt to run this as either
|
203 | 68d95757 | Guido Trotter | # we're in the cluster init phase and the rpc client part can't
|
204 | 68d95757 | Guido Trotter | # even attempt to run, or this LU doesn't do hooks at all
|
205 | 68d95757 | Guido Trotter | return
|
206 | 68d95757 | Guido Trotter | |
207 | 68d95757 | Guido Trotter | results = self._RunWrapper(nodes, self.hooks_path, phase, env) |
208 | 68d95757 | Guido Trotter | if not results: |
209 | 68d95757 | Guido Trotter | msg = "Communication Failure"
|
210 | 68d95757 | Guido Trotter | if phase == constants.HOOKS_PHASE_PRE:
|
211 | 68d95757 | Guido Trotter | raise errors.HooksFailure(msg)
|
212 | 68d95757 | Guido Trotter | else:
|
213 | 68d95757 | Guido Trotter | self.log_fn(msg)
|
214 | 68d95757 | Guido Trotter | return results
|
215 | 68d95757 | Guido Trotter | |
216 | 68d95757 | Guido Trotter | converted_res = results |
217 | 68d95757 | Guido Trotter | if self.hooks_results_adapt_fn: |
218 | 68d95757 | Guido Trotter | converted_res = self.hooks_results_adapt_fn(results)
|
219 | 68d95757 | Guido Trotter | |
220 | 68d95757 | Guido Trotter | errs = [] |
221 | 68d95757 | Guido Trotter | for node_name, (fail_msg, offline, hooks_results) in converted_res.items(): |
222 | 68d95757 | Guido Trotter | if offline:
|
223 | 68d95757 | Guido Trotter | continue
|
224 | 68d95757 | Guido Trotter | |
225 | 68d95757 | Guido Trotter | if fail_msg:
|
226 | 68d95757 | Guido Trotter | self.log_fn("Communication failure to node %s: %s", node_name, fail_msg) |
227 | 68d95757 | Guido Trotter | continue
|
228 | 68d95757 | Guido Trotter | |
229 | 68d95757 | Guido Trotter | for script, hkr, output in hooks_results: |
230 | 68d95757 | Guido Trotter | if hkr == constants.HKR_FAIL:
|
231 | 68d95757 | Guido Trotter | if phase == constants.HOOKS_PHASE_PRE:
|
232 | 68d95757 | Guido Trotter | errs.append((node_name, script, output)) |
233 | 68d95757 | Guido Trotter | else:
|
234 | 68d95757 | Guido Trotter | if not output: |
235 | 68d95757 | Guido Trotter | output = "(no output)"
|
236 | 68d95757 | Guido Trotter | self.log_fn("On %s script %s failed, output: %s" % |
237 | 68d95757 | Guido Trotter | (node_name, script, output)) |
238 | 68d95757 | Guido Trotter | |
239 | 68d95757 | Guido Trotter | if errs and phase == constants.HOOKS_PHASE_PRE: |
240 | 68d95757 | Guido Trotter | raise errors.HooksAbort(errs)
|
241 | 68d95757 | Guido Trotter | |
242 | 68d95757 | Guido Trotter | return results
|
243 | 68d95757 | Guido Trotter | |
244 | 68d95757 | Guido Trotter | def RunConfigUpdate(self): |
245 | 68d95757 | Guido Trotter | """Run the special configuration update hook
|
246 | 68d95757 | Guido Trotter |
|
247 | 68d95757 | Guido Trotter | This is a special hook that runs only on the master after each
|
248 | 68d95757 | Guido Trotter | top-level LI if the configuration has been updated.
|
249 | 68d95757 | Guido Trotter |
|
250 | 68d95757 | Guido Trotter | """
|
251 | 68d95757 | Guido Trotter | phase = constants.HOOKS_PHASE_POST |
252 | 68d95757 | Guido Trotter | hpath = constants.HOOKS_NAME_CFGUPDATE |
253 | 68d95757 | Guido Trotter | nodes = [self.master_name]
|
254 | 68d95757 | Guido Trotter | self._RunWrapper(nodes, hpath, phase, self.pre_env) |
255 | 68d95757 | Guido Trotter | |
256 | 68d95757 | Guido Trotter | @staticmethod
|
257 | 68d95757 | Guido Trotter | def BuildFromLu(hooks_execution_fn, lu): |
258 | 68d95757 | Guido Trotter | if lu.HPATH is None: |
259 | 68d95757 | Guido Trotter | nodes = (None, None) |
260 | 68d95757 | Guido Trotter | else:
|
261 | 68d95757 | Guido Trotter | nodes = map(frozenset, lu.BuildHooksNodes()) |
262 | 68d95757 | Guido Trotter | |
263 | 68d95757 | Guido Trotter | master_name = cluster_name = None
|
264 | 68d95757 | Guido Trotter | if lu.cfg:
|
265 | 68d95757 | Guido Trotter | master_name = lu.cfg.GetMasterNode() |
266 | 68d95757 | Guido Trotter | cluster_name = lu.cfg.GetClusterName() |
267 | 68d95757 | Guido Trotter | |
268 | 68d95757 | Guido Trotter | return HooksMaster(lu.op.OP_ID, lu.HPATH, nodes, hooks_execution_fn,
|
269 | 68d95757 | Guido Trotter | _RpcResultsToHooksResults, lu.BuildHooksEnv, |
270 | 68d95757 | Guido Trotter | lu.LogWarning, lu.HTYPE, cluster_name, master_name) |