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  # 
  6  # This program is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, but 
 12  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 14  # General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the Free Software 
 18  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
 19  # 02110-1301, USA. 
 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 
31 32 33 -def _RpcResultsToHooksResults(rpc_results):
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
46 47 -class HooksMaster(object):
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
97 - def _BuildEnv(self, phase):
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 # Merge with pre-phase environment 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 # Convert everything to strings 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
174 - def RunPhase(self, phase, nodes=None):
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 # empty node list, we should not attempt to run this as either 203 # we're in the cluster init phase and the rpc client part can't 204 # even attempt to run, or this LU doesn't do hooks at all 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
244 - def RunConfigUpdate(self):
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
257 - def BuildFromLu(hooks_execution_fn, lu):
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