1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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
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
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
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
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
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
203
204
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
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
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)
271