4 # Copyright (C) 2006, 2007 Google Inc.
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.
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.
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
22 """Module implementing the logic behind the cluster operations
24 This module implements the logic for doing operations in the cluster. There
25 are two kinds of classes defined:
26 - logical units, which know how to deal with their specific opcode only
27 - the processor, which dispatches the opcodes to their logical units
32 from ganeti import opcodes
33 from ganeti import constants
34 from ganeti import errors
35 from ganeti import rpc
36 from ganeti import cmdlib
37 from ganeti import config
38 from ganeti import ssconf
39 from ganeti import logger
41 class Processor(object):
42 """Object which runs OpCodes"""
45 opcodes.OpInitCluster: cmdlib.LUInitCluster,
46 opcodes.OpDestroyCluster: cmdlib.LUDestroyCluster,
47 opcodes.OpQueryClusterInfo: cmdlib.LUQueryClusterInfo,
48 opcodes.OpClusterCopyFile: cmdlib.LUClusterCopyFile,
49 opcodes.OpRunClusterCommand: cmdlib.LURunClusterCommand,
50 opcodes.OpVerifyCluster: cmdlib.LUVerifyCluster,
51 opcodes.OpMasterFailover: cmdlib.LUMasterFailover,
52 opcodes.OpDumpClusterConfig: cmdlib.LUDumpClusterConfig,
53 opcodes.OpRenameCluster: cmdlib.LURenameCluster,
54 opcodes.OpVerifyDisks: cmdlib.LUVerifyDisks,
56 opcodes.OpAddNode: cmdlib.LUAddNode,
57 opcodes.OpQueryNodes: cmdlib.LUQueryNodes,
58 opcodes.OpQueryNodeVolumes: cmdlib.LUQueryNodeVolumes,
59 opcodes.OpRemoveNode: cmdlib.LURemoveNode,
61 opcodes.OpCreateInstance: cmdlib.LUCreateInstance,
62 opcodes.OpReinstallInstance: cmdlib.LUReinstallInstance,
63 opcodes.OpRemoveInstance: cmdlib.LURemoveInstance,
64 opcodes.OpRenameInstance: cmdlib.LURenameInstance,
65 opcodes.OpActivateInstanceDisks: cmdlib.LUActivateInstanceDisks,
66 opcodes.OpShutdownInstance: cmdlib.LUShutdownInstance,
67 opcodes.OpStartupInstance: cmdlib.LUStartupInstance,
68 opcodes.OpRebootInstance: cmdlib.LURebootInstance,
69 opcodes.OpDeactivateInstanceDisks: cmdlib.LUDeactivateInstanceDisks,
70 opcodes.OpAddMDDRBDComponent: cmdlib.LUAddMDDRBDComponent,
71 opcodes.OpRemoveMDDRBDComponent: cmdlib.LURemoveMDDRBDComponent,
72 opcodes.OpReplaceDisks: cmdlib.LUReplaceDisks,
73 opcodes.OpFailoverInstance: cmdlib.LUFailoverInstance,
74 opcodes.OpConnectConsole: cmdlib.LUConnectConsole,
75 opcodes.OpQueryInstances: cmdlib.LUQueryInstances,
76 opcodes.OpQueryInstanceData: cmdlib.LUQueryInstanceData,
77 opcodes.OpSetInstanceParms: cmdlib.LUSetInstanceParms,
79 opcodes.OpDiagnoseOS: cmdlib.LUDiagnoseOS,
81 opcodes.OpQueryExports: cmdlib.LUQueryExports,
82 opcodes.OpExportInstance: cmdlib.LUExportInstance,
84 opcodes.OpGetTags: cmdlib.LUGetTags,
85 opcodes.OpSearchTags: cmdlib.LUSearchTags,
86 opcodes.OpAddTags: cmdlib.LUAddTags,
87 opcodes.OpDelTags: cmdlib.LUDelTags,
89 opcodes.OpTestDelay: cmdlib.LUTestDelay,
92 def __init__(self, feedback=None):
93 """Constructor for Processor
96 - feedback_fn: the feedback function (taking one string) to be run when
97 interesting events are happening
101 self._feedback_fn = feedback
103 def ExecOpCode(self, op):
104 """Execute an opcode.
107 - cfg: the configuration in which we execute this opcode
108 - opcode: the opcode to be executed
111 if not isinstance(op, opcodes.OpCode):
112 raise errors.ProgrammerError("Non-opcode instance passed"
115 lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
117 raise errors.OpCodeUnknown("Unknown opcode")
119 if lu_class.REQ_CLUSTER and self.cfg is None:
120 self.cfg = config.ConfigWriter()
121 self.sstore = ssconf.SimpleStore()
122 if self.cfg is not None:
123 write_count = self.cfg.write_count
126 lu = lu_class(self, op, self.cfg, self.sstore)
128 hm = HooksMaster(rpc.call_hooks_runner, self, lu)
129 hm.RunPhase(constants.HOOKS_PHASE_PRE)
130 result = lu.Exec(self._feedback_fn)
131 hm.RunPhase(constants.HOOKS_PHASE_POST)
132 if lu.cfg is not None:
133 # we use lu.cfg and not self.cfg as for init cluster, self.cfg
134 # is None but lu.cfg has been recently initialized in the
136 if write_count != lu.cfg.write_count:
141 def ChainOpCode(self, op):
142 """Chain and execute an opcode.
144 This is used by LUs when they need to execute a child LU.
147 - opcode: the opcode to be executed
150 if not isinstance(op, opcodes.OpCode):
151 raise errors.ProgrammerError("Non-opcode instance passed"
154 lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
156 raise errors.OpCodeUnknown("Unknown opcode")
158 if lu_class.REQ_CLUSTER and self.cfg is None:
159 self.cfg = config.ConfigWriter()
160 self.sstore = ssconf.SimpleStore()
161 #do_hooks = lu_class.HPATH is not None
162 lu = lu_class(self, op, self.cfg, self.sstore)
165 # hm = HooksMaster(rpc.call_hooks_runner, self, lu)
166 # hm.RunPhase(constants.HOOKS_PHASE_PRE)
167 result = lu.Exec(self._feedback_fn)
169 # hm.RunPhase(constants.HOOKS_PHASE_POST)
172 def LogStep(self, current, total, message):
173 """Log a change in LU execution progress.
176 logger.Debug("Step %d/%d %s" % (current, total, message))
177 self._feedback_fn("STEP %d/%d %s" % (current, total, message))
179 def LogWarning(self, message, hint=None):
180 """Log a warning to the logs and the user.
183 logger.Error(message)
184 self._feedback_fn(" - WARNING: %s" % message)
186 self._feedback_fn(" Hint: %s" % hint)
188 def LogInfo(self, message):
189 """Log an informational message to the logs and the user.
193 self._feedback_fn(" - INFO: %s" % message)
196 class HooksMaster(object):
199 This class distributes the run commands to the nodes based on the
202 In order to remove the direct dependency on the rpc module, the
203 constructor needs a function which actually does the remote
204 call. This will usually be rpc.call_hooks_runner, but any function
205 which behaves the same works.
208 def __init__(self, callfn, proc, lu):
213 self.env, node_list_pre, node_list_post = self._BuildEnv()
214 self.node_list = {constants.HOOKS_PHASE_PRE: node_list_pre,
215 constants.HOOKS_PHASE_POST: node_list_post}
218 """Compute the environment and the target nodes.
220 Based on the opcode and the current node list, this builds the
221 environment for the hooks and the target node list for the run.
225 "PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
226 "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
227 "GANETI_OP_CODE": self.op.OP_ID,
228 "GANETI_OBJECT_TYPE": self.lu.HTYPE,
229 "GANETI_DATA_DIR": constants.DATA_DIR,
232 if self.lu.HPATH is not None:
233 lu_env, lu_nodes_pre, lu_nodes_post = self.lu.BuildHooksEnv()
236 env["GANETI_" + key] = lu_env[key]
238 lu_nodes_pre = lu_nodes_post = []
240 return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
242 def _RunWrapper(self, node_list, hpath, phase):
243 """Simple wrapper over self.callfn.
245 This method fixes the environment before doing the rpc call.
248 env = self.env.copy()
249 env["GANETI_HOOKS_PHASE"] = phase
250 env["GANETI_HOOKS_PATH"] = hpath
251 if self.lu.sstore is not None:
252 env["GANETI_CLUSTER"] = self.lu.sstore.GetClusterName()
253 env["GANETI_MASTER"] = self.lu.sstore.GetMasterNode()
255 env = dict([(str(key), str(val)) for key, val in env.iteritems()])
257 return self.callfn(node_list, hpath, phase, env)
259 def RunPhase(self, phase):
260 """Run all the scripts for a phase.
262 This is the main function of the HookMaster.
265 if not self.node_list[phase]:
266 # empty node list, we should not attempt to run this as either
267 # we're in the cluster init phase and the rpc client part can't
268 # even attempt to run, or this LU doesn't do hooks at all
270 hpath = self.lu.HPATH
271 results = self._RunWrapper(self.node_list[phase], hpath, phase)
272 if phase == constants.HOOKS_PHASE_PRE:
275 raise errors.HooksFailure("Communication failure")
276 for node_name in results:
277 res = results[node_name]
278 if res is False or not isinstance(res, list):
279 self.proc.LogWarning("Communication failure to node %s" % node_name)
281 for script, hkr, output in res:
282 if hkr == constants.HKR_FAIL:
283 output = output.strip().encode("string_escape")
284 errs.append((node_name, script, output))
286 raise errors.HooksAbort(errs)
288 def RunConfigUpdate(self):
289 """Run the special configuration update hook
291 This is a special hook that runs only on the master after each
292 top-level LI if the configuration has been updated.
295 phase = constants.HOOKS_PHASE_POST
296 hpath = constants.HOOKS_NAME_CFGUPDATE
297 if self.lu.sstore is None:
298 raise errors.ProgrammerError("Null sstore on config update hook")
299 nodes = [self.lu.sstore.GetMasterNode()]
300 results = self._RunWrapper(nodes, hpath, phase)