1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Module implementing the logic for running hooks.
32
33 """
34
35 from ganeti import constants
36 from ganeti import errors
37 from ganeti import utils
38 from ganeti import compat
39 from ganeti import pathutils
43 """Function to convert RPC results to the format expected by HooksMaster.
44
45 @type rpc_results: dict(node: L{rpc.RpcResult})
46 @param rpc_results: RPC results
47 @rtype: dict(node: (fail_msg, offline, hooks_results))
48 @return: RPC results unpacked according to the format expected by
49 L({hooksmaster.HooksMaster}
50
51 """
52 return dict((node, (rpc_res.fail_msg, rpc_res.offline, rpc_res.payload))
53 for (node, rpc_res) in rpc_results.items())
54
57 - def __init__(self, opcode, hooks_path, nodes, hooks_execution_fn,
58 hooks_results_adapt_fn, build_env_fn, prepare_post_nodes_fn,
59 log_fn, htype=None, cluster_name=None, master_name=None,
60 master_uuid=None, job_id=None):
61 """Base class for hooks masters.
62
63 This class invokes the execution of hooks according to the behaviour
64 specified by its parameters.
65
66 @type opcode: string
67 @param opcode: opcode of the operation to which the hooks are tied
68 @type hooks_path: string
69 @param hooks_path: prefix of the hooks directories
70 @type nodes: 2-tuple of lists
71 @param nodes: 2-tuple of lists containing nodes on which pre-hooks must be
72 run and nodes on which post-hooks must be run
73 @type hooks_execution_fn: function that accepts the following parameters:
74 (node_list, hooks_path, phase, environment)
75 @param hooks_execution_fn: function that will execute the hooks; can be
76 None, indicating that no conversion is necessary.
77 @type hooks_results_adapt_fn: function
78 @param hooks_results_adapt_fn: function that will adapt the return value of
79 hooks_execution_fn to the format expected by RunPhase
80 @type build_env_fn: function that returns a dictionary having strings as
81 keys
82 @param build_env_fn: function that builds the environment for the hooks
83 @type prepare_post_nodes_fn: function that take a list of node UUIDs and
84 returns a list of node UUIDs
85 @param prepare_post_nodes_fn: function that is invoked right before
86 executing post hooks and can change the list of node UUIDs to run the post
87 hooks on
88 @type log_fn: function that accepts a string
89 @param log_fn: logging function
90 @type htype: string or None
91 @param htype: None or one of L{constants.HTYPE_CLUSTER},
92 L{constants.HTYPE_NODE}, L{constants.HTYPE_INSTANCE}
93 @type cluster_name: string
94 @param cluster_name: name of the cluster
95 @type master_name: string
96 @param master_name: name of the master
97 @type master_uuid: string
98 @param master_uuid: uuid of the master
99 @type job_id: int
100 @param job_id: the id of the job process (used in global post hooks)
101
102 """
103 self.opcode = opcode
104 self.hooks_path = hooks_path
105 self.hooks_execution_fn = hooks_execution_fn
106 self.hooks_results_adapt_fn = hooks_results_adapt_fn
107 self.build_env_fn = build_env_fn
108 self.prepare_post_nodes_fn = prepare_post_nodes_fn
109 self.log_fn = log_fn
110 self.htype = htype
111 self.cluster_name = cluster_name
112 self.master_name = master_name
113 self.master_uuid = master_uuid
114 self.job_id = job_id
115
116 self.pre_env = self._BuildEnv(constants.HOOKS_PHASE_PRE)
117 (self.pre_nodes, self.post_nodes) = nodes
118
120 """Compute the environment and the target nodes.
121
122 Based on the opcode and the current node list, this builds the
123 environment for the hooks and the target node list for the run.
124
125 """
126 if phase == constants.HOOKS_PHASE_PRE:
127 prefix = "GANETI_"
128 elif phase == constants.HOOKS_PHASE_POST:
129 prefix = "GANETI_POST_"
130 else:
131 raise AssertionError("Unknown phase '%s'" % phase)
132
133 env = {}
134
135 if self.hooks_path is not None:
136 phase_env = self.build_env_fn()
137 if phase_env:
138 assert not compat.any(key.upper().startswith(prefix)
139 for key in phase_env)
140 env.update(("%s%s" % (prefix, key), value)
141 for (key, value) in phase_env.items())
142
143 if phase == constants.HOOKS_PHASE_PRE:
144 assert compat.all((key.startswith("GANETI_") and
145 not key.startswith("GANETI_POST_"))
146 for key in env)
147
148 elif phase == constants.HOOKS_PHASE_POST:
149 assert compat.all(key.startswith("GANETI_POST_") for key in env)
150 assert isinstance(self.pre_env, dict)
151
152
153 assert not compat.any(key.startswith("GANETI_POST_")
154 for key in self.pre_env)
155 env.update(self.pre_env)
156 else:
157 raise AssertionError("Unknown phase '%s'" % phase)
158
159 return env
160
162 """Check rpc parameters and call hooks_execution_fn (rpc).
163
164 """
165 if node_list is None or not node_list:
166 return {}
167
168
169 env = dict([(str(key), str(val)) for key, val in env.iteritems()])
170 assert compat.all(key == "PATH" or key.startswith("GANETI_")
171 for key in env)
172 for node in node_list:
173 assert utils.UUID_RE.match(node), "Invalid node uuid %s" % node
174
175 return self.hooks_execution_fn(node_list, hpath, phase, env)
176
177 - def _RunWrapper(self, node_list, hpath, phase, phase_env, is_global=False,
178 post_status=None):
179 """Simple wrapper over self.callfn.
180
181 This method fixes the environment before executing the hooks.
182
183 """
184 env = {
185 "PATH": constants.HOOKS_PATH,
186 "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
187 "GANETI_OP_CODE": self.opcode,
188 "GANETI_DATA_DIR": pathutils.DATA_DIR,
189 "GANETI_HOOKS_PHASE": phase,
190 "GANETI_HOOKS_PATH": hpath,
191 }
192
193 if self.htype:
194 env["GANETI_OBJECT_TYPE"] = self.htype
195
196 if self.cluster_name is not None:
197 env["GANETI_CLUSTER"] = self.cluster_name
198
199 if self.master_name is not None:
200 env["GANETI_MASTER"] = self.master_name
201
202 if self.job_id and is_global:
203 env["GANETI_JOB_ID"] = self.job_id
204 if phase == constants.HOOKS_PHASE_POST and is_global:
205 assert post_status is not None
206 env["GANETI_POST_STATUS"] = post_status
207
208 if phase_env:
209 env = utils.algo.JoinDisjointDicts(env, phase_env)
210
211 if not is_global:
212 return self._CheckParamsAndExecHooks(node_list, hpath, phase, env)
213
214
215
216 ret = dict()
217 env["GANETI_IS_MASTER"] = constants.GLOBAL_HOOKS_MASTER
218 master_set = frozenset([self.master_uuid])
219 ret.update(self._CheckParamsAndExecHooks(master_set, hpath, phase, env))
220
221 if node_list:
222 node_list = frozenset(set(node_list) - master_set)
223 env["GANETI_IS_MASTER"] = constants.GLOBAL_HOOKS_NOT_MASTER
224 ret.update(self._CheckParamsAndExecHooks(node_list, hpath, phase, env))
225
226 return ret
227
228 - def RunPhase(self, phase, node_uuids=None, is_global=False,
229 post_status=None):
230 """Run all the scripts for a phase.
231
232 This is the main function of the HookMaster.
233 It executes self.hooks_execution_fn, and after running
234 self.hooks_results_adapt_fn on its results it expects them to be in the
235 form {node_name: (fail_msg, [(script, result, output), ...]}).
236
237 @param phase: one of L{constants.HOOKS_PHASE_POST} or
238 L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
239 @param node_uuids: overrides the predefined list of nodes for the given
240 phase
241 @param is_global: whether global or per-opcode hooks should be executed
242 @param post_status: the job execution status for the global post hooks
243 @return: the processed results of the hooks multi-node rpc call
244 @raise errors.HooksFailure: on communication failure to the nodes
245 @raise errors.HooksAbort: on failure of one of the hooks
246
247 """
248 if phase == constants.HOOKS_PHASE_PRE:
249 if node_uuids is None:
250 node_uuids = self.pre_nodes
251 env = self.pre_env
252 elif phase == constants.HOOKS_PHASE_POST:
253 if node_uuids is None:
254 node_uuids = self.post_nodes
255 if node_uuids is not None and self.prepare_post_nodes_fn is not None:
256 node_uuids = frozenset(self.prepare_post_nodes_fn(list(node_uuids)))
257 env = self._BuildEnv(phase)
258 else:
259 raise AssertionError("Unknown phase '%s'" % phase)
260
261 if not node_uuids and not is_global:
262
263
264
265 return
266
267 hooks_path = constants.GLOBAL_HOOKS_DIR if is_global else self.hooks_path
268 results = self._RunWrapper(node_uuids, hooks_path, phase, env, is_global,
269 post_status)
270 if not results:
271 msg = "Communication Failure"
272 if phase == constants.HOOKS_PHASE_PRE:
273 raise errors.HooksFailure(msg)
274 else:
275 self.log_fn(msg)
276 return results
277
278 converted_res = results
279 if self.hooks_results_adapt_fn:
280 converted_res = self.hooks_results_adapt_fn(results)
281
282 errs = []
283 for node_name, (fail_msg, offline, hooks_results) in converted_res.items():
284 if offline:
285 continue
286
287 if fail_msg:
288 self.log_fn("Communication failure to node %s: %s", node_name, fail_msg)
289 continue
290
291 for script, hkr, output in hooks_results:
292 if hkr == constants.HKR_FAIL:
293 if phase == constants.HOOKS_PHASE_PRE:
294 errs.append((node_name, script, output))
295 else:
296 if not output:
297 output = "(no output)"
298 self.log_fn("On %s script %s failed, output: %s" %
299 (node_name, script, output))
300
301 if errs and phase == constants.HOOKS_PHASE_PRE:
302 raise errors.HooksAbort(errs)
303
304 return results
305
307 """Run the special configuration update hook
308
309 This is a special hook that runs only on the master after each
310 top-level LI if the configuration has been updated.
311
312 """
313 phase = constants.HOOKS_PHASE_POST
314 hpath = constants.HOOKS_NAME_CFGUPDATE
315 nodes = [self.master_uuid]
316 self._RunWrapper(nodes, hpath, phase, self.pre_env)
317
318 @staticmethod
320 if lu.HPATH is None:
321 nodes = (None, None)
322 else:
323 hooks_nodes = lu.BuildHooksNodes()
324 if len(hooks_nodes) != 2:
325 raise errors.ProgrammerError(
326 "LogicalUnit.BuildHooksNodes must return a 2-tuple")
327 nodes = (frozenset(hooks_nodes[0]), frozenset(hooks_nodes[1]))
328
329 master_name = cluster_name = None
330 if lu.cfg:
331 master_name = lu.cfg.GetMasterNodeName()
332 master_uuid = lu.cfg.GetMasterNode()
333 cluster_name = lu.cfg.GetClusterName()
334
335 return HooksMaster(lu.op.OP_ID, lu.HPATH, nodes, hooks_execution_fn,
336 RpcResultsToHooksResults, lu.BuildHooksEnv,
337 lu.PreparePostHookNodes, lu.LogWarning, lu.HTYPE,
338 cluster_name, master_name, master_uuid, job_id)
339
340
341 -def ExecGlobalPostHooks(opcode, master_name, rpc_runner, log_fn,
342 cluster_name, master_uuid, job_id, status):
343 """ Build hooks manager and execute global post hooks just on the master
344
345 """
346 hm = HooksMaster(opcode, hooks_path=None, nodes=([], [master_uuid]),
347 hooks_execution_fn=rpc_runner,
348 hooks_results_adapt_fn=RpcResultsToHooksResults,
349 build_env_fn=None, prepare_post_nodes_fn=None,
350 log_fn=log_fn, htype=None, cluster_name=cluster_name,
351 master_name=master_name, master_uuid=master_uuid,
352 job_id=job_id)
353 hm.RunPhase(constants.HOOKS_PHASE_POST, is_global=True, post_status=status)
354