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 """Base class for hooks masters. 61 62 This class invokes the execution of hooks according to the behaviour 63 specified by its parameters. 64 65 @type opcode: string 66 @param opcode: opcode of the operation to which the hooks are tied 67 @type hooks_path: string 68 @param hooks_path: prefix of the hooks directories 69 @type nodes: 2-tuple of lists 70 @param nodes: 2-tuple of lists containing nodes on which pre-hooks must be 71 run and nodes on which post-hooks must be run 72 @type hooks_execution_fn: function that accepts the following parameters: 73 (node_list, hooks_path, phase, environment) 74 @param hooks_execution_fn: function that will execute the hooks; can be 75 None, indicating that no conversion is necessary. 76 @type hooks_results_adapt_fn: function 77 @param hooks_results_adapt_fn: function that will adapt the return value of 78 hooks_execution_fn to the format expected by RunPhase 79 @type build_env_fn: function that returns a dictionary having strings as 80 keys 81 @param build_env_fn: function that builds the environment for the hooks 82 @type prepare_post_nodes_fn: function that take a list of node UUIDs and 83 returns a list of node UUIDs 84 @param prepare_post_nodes_fn: function that is invoked right before 85 executing post hooks and can change the list of node UUIDs to run the post 86 hooks on 87 @type log_fn: function that accepts a string 88 @param log_fn: logging function 89 @type htype: string or None 90 @param htype: None or one of L{constants.HTYPE_CLUSTER}, 91 L{constants.HTYPE_NODE}, L{constants.HTYPE_INSTANCE} 92 @type cluster_name: string 93 @param cluster_name: name of the cluster 94 @type master_name: string 95 @param master_name: name of the master 96 97 """ 98 self.opcode = opcode 99 self.hooks_path = hooks_path 100 self.hooks_execution_fn = hooks_execution_fn 101 self.hooks_results_adapt_fn = hooks_results_adapt_fn 102 self.build_env_fn = build_env_fn 103 self.prepare_post_nodes_fn = prepare_post_nodes_fn 104 self.log_fn = log_fn 105 self.htype = htype 106 self.cluster_name = cluster_name 107 self.master_name = master_name 108 109 self.pre_env = self._BuildEnv(constants.HOOKS_PHASE_PRE) 110 (self.pre_nodes, self.post_nodes) = nodes
111
112 - def _BuildEnv(self, phase):
113 """Compute the environment and the target nodes. 114 115 Based on the opcode and the current node list, this builds the 116 environment for the hooks and the target node list for the run. 117 118 """ 119 if phase == constants.HOOKS_PHASE_PRE: 120 prefix = "GANETI_" 121 elif phase == constants.HOOKS_PHASE_POST: 122 prefix = "GANETI_POST_" 123 else: 124 raise AssertionError("Unknown phase '%s'" % phase) 125 126 env = {} 127 128 if self.hooks_path is not None: 129 phase_env = self.build_env_fn() 130 if phase_env: 131 assert not compat.any(key.upper().startswith(prefix) 132 for key in phase_env) 133 env.update(("%s%s" % (prefix, key), value) 134 for (key, value) in phase_env.items()) 135 136 if phase == constants.HOOKS_PHASE_PRE: 137 assert compat.all((key.startswith("GANETI_") and 138 not key.startswith("GANETI_POST_")) 139 for key in env) 140 141 elif phase == constants.HOOKS_PHASE_POST: 142 assert compat.all(key.startswith("GANETI_POST_") for key in env) 143 assert isinstance(self.pre_env, dict) 144 145 # Merge with pre-phase environment 146 assert not compat.any(key.startswith("GANETI_POST_") 147 for key in self.pre_env) 148 env.update(self.pre_env) 149 else: 150 raise AssertionError("Unknown phase '%s'" % phase) 151 152 return env
153
154 - def _RunWrapper(self, node_list, hpath, phase, phase_env):
155 """Simple wrapper over self.callfn. 156 157 This method fixes the environment before executing the hooks. 158 159 """ 160 env = { 161 "PATH": constants.HOOKS_PATH, 162 "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION, 163 "GANETI_OP_CODE": self.opcode, 164 "GANETI_DATA_DIR": pathutils.DATA_DIR, 165 "GANETI_HOOKS_PHASE": phase, 166 "GANETI_HOOKS_PATH": hpath, 167 } 168 169 if self.htype: 170 env["GANETI_OBJECT_TYPE"] = self.htype 171 172 if self.cluster_name is not None: 173 env["GANETI_CLUSTER"] = self.cluster_name 174 175 if self.master_name is not None: 176 env["GANETI_MASTER"] = self.master_name 177 178 if phase_env: 179 env = utils.algo.JoinDisjointDicts(env, phase_env) 180 181 # Convert everything to strings 182 env = dict([(str(key), str(val)) for key, val in env.iteritems()]) 183 184 assert compat.all(key == "PATH" or key.startswith("GANETI_") 185 for key in env) 186 187 return self.hooks_execution_fn(node_list, hpath, phase, env)
188
189 - def RunPhase(self, phase, node_names=None):
190 """Run all the scripts for a phase. 191 192 This is the main function of the HookMaster. 193 It executes self.hooks_execution_fn, and after running 194 self.hooks_results_adapt_fn on its results it expects them to be in the 195 form {node_name: (fail_msg, [(script, result, output), ...]}). 196 197 @param phase: one of L{constants.HOOKS_PHASE_POST} or 198 L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase 199 @param node_names: overrides the predefined list of nodes for the given 200 phase 201 @return: the processed results of the hooks multi-node rpc call 202 @raise errors.HooksFailure: on communication failure to the nodes 203 @raise errors.HooksAbort: on failure of one of the hooks 204 205 """ 206 if phase == constants.HOOKS_PHASE_PRE: 207 if node_names is None: 208 node_names = self.pre_nodes 209 env = self.pre_env 210 elif phase == constants.HOOKS_PHASE_POST: 211 if node_names is None: 212 node_names = self.post_nodes 213 if node_names is not None and self.prepare_post_nodes_fn is not None: 214 node_names = frozenset(self.prepare_post_nodes_fn(list(node_names))) 215 env = self._BuildEnv(phase) 216 else: 217 raise AssertionError("Unknown phase '%s'" % phase) 218 219 if not node_names: 220 # empty node list, we should not attempt to run this as either 221 # we're in the cluster init phase and the rpc client part can't 222 # even attempt to run, or this LU doesn't do hooks at all 223 return 224 225 results = self._RunWrapper(node_names, self.hooks_path, phase, env) 226 if not results: 227 msg = "Communication Failure" 228 if phase == constants.HOOKS_PHASE_PRE: 229 raise errors.HooksFailure(msg) 230 else: 231 self.log_fn(msg) 232 return results 233 234 converted_res = results 235 if self.hooks_results_adapt_fn: 236 converted_res = self.hooks_results_adapt_fn(results) 237 238 errs = [] 239 for node_name, (fail_msg, offline, hooks_results) in converted_res.items(): 240 if offline: 241 continue 242 243 if fail_msg: 244 self.log_fn("Communication failure to node %s: %s", node_name, fail_msg) 245 continue 246 247 for script, hkr, output in hooks_results: 248 if hkr == constants.HKR_FAIL: 249 if phase == constants.HOOKS_PHASE_PRE: 250 errs.append((node_name, script, output)) 251 else: 252 if not output: 253 output = "(no output)" 254 self.log_fn("On %s script %s failed, output: %s" % 255 (node_name, script, output)) 256 257 if errs and phase == constants.HOOKS_PHASE_PRE: 258 raise errors.HooksAbort(errs) 259 260 return results
261
262 - def RunConfigUpdate(self):
263 """Run the special configuration update hook 264 265 This is a special hook that runs only on the master after each 266 top-level LI if the configuration has been updated. 267 268 """ 269 phase = constants.HOOKS_PHASE_POST 270 hpath = constants.HOOKS_NAME_CFGUPDATE 271 nodes = [self.master_name] 272 self._RunWrapper(nodes, hpath, phase, self.pre_env)
273 274 @staticmethod
275 - def BuildFromLu(hooks_execution_fn, lu):
276 if lu.HPATH is None: 277 nodes = (None, None) 278 else: 279 hooks_nodes = lu.BuildHooksNodes() 280 if len(hooks_nodes) != 2: 281 raise errors.ProgrammerError( 282 "LogicalUnit.BuildHooksNodes must return a 2-tuple") 283 nodes = (frozenset(hooks_nodes[0]), frozenset(hooks_nodes[1])) 284 285 master_name = cluster_name = None 286 if lu.cfg: 287 master_name = lu.cfg.GetMasterNodeName() 288 cluster_name = lu.cfg.GetClusterName() 289 290 return HooksMaster(lu.op.OP_ID, lu.HPATH, nodes, hooks_execution_fn, 291 _RpcResultsToHooksResults, lu.BuildHooksEnv, 292 lu.PreparePostHookNodes, lu.LogWarning, lu.HTYPE, 293 cluster_name, master_name)
294