Package ganeti :: Module hooksmaster
[hide private]
[frames] | no frames]

Source Code for Module ganeti.hooksmaster

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2006, 2007, 2011, 2012 Google Inc. 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are 
  9  # met: 
 10  # 
 11  # 1. Redistributions of source code must retain the above copyright notice, 
 12  # this list of conditions and the following disclaimer. 
 13  # 
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 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 
40 41 42 -def RpcResultsToHooksResults(rpc_results):
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
55 56 -class HooksMaster(object):
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
119 - def _BuildEnv(self, phase):
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 # Merge with pre-phase environment 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
161 - def _CheckParamsAndExecHooks(self, node_list, hpath, phase, env):
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 # Convert everything to strings 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 # For global hooks, we need to send different env values to master and 215 # to the others 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 # empty node list, we should not attempt to run this as either 263 # we're in the cluster init phase and the rpc client part can't 264 # even attempt to run, or this LU doesn't do hooks at all 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
306 - def RunConfigUpdate(self):
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
319 - def BuildFromLu(hooks_execution_fn, lu, job_id=None):
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