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
42 class Processor(object):
43 """Object which runs OpCodes"""
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,
55 opcodes.OpSetClusterParams: cmdlib.LUSetClusterParams,
57 opcodes.OpAddNode: cmdlib.LUAddNode,
58 opcodes.OpQueryNodes: cmdlib.LUQueryNodes,
59 opcodes.OpQueryNodeVolumes: cmdlib.LUQueryNodeVolumes,
60 opcodes.OpRemoveNode: cmdlib.LURemoveNode,
62 opcodes.OpCreateInstance: cmdlib.LUCreateInstance,
63 opcodes.OpReinstallInstance: cmdlib.LUReinstallInstance,
64 opcodes.OpRemoveInstance: cmdlib.LURemoveInstance,
65 opcodes.OpRenameInstance: cmdlib.LURenameInstance,
66 opcodes.OpActivateInstanceDisks: cmdlib.LUActivateInstanceDisks,
67 opcodes.OpShutdownInstance: cmdlib.LUShutdownInstance,
68 opcodes.OpStartupInstance: cmdlib.LUStartupInstance,
69 opcodes.OpRebootInstance: cmdlib.LURebootInstance,
70 opcodes.OpDeactivateInstanceDisks: cmdlib.LUDeactivateInstanceDisks,
71 opcodes.OpReplaceDisks: cmdlib.LUReplaceDisks,
72 opcodes.OpFailoverInstance: cmdlib.LUFailoverInstance,
73 opcodes.OpConnectConsole: cmdlib.LUConnectConsole,
74 opcodes.OpQueryInstances: cmdlib.LUQueryInstances,
75 opcodes.OpQueryInstanceData: cmdlib.LUQueryInstanceData,
76 opcodes.OpSetInstanceParams: cmdlib.LUSetInstanceParams,
78 opcodes.OpDiagnoseOS: cmdlib.LUDiagnoseOS,
80 opcodes.OpQueryExports: cmdlib.LUQueryExports,
81 opcodes.OpExportInstance: cmdlib.LUExportInstance,
82 opcodes.OpRemoveExport: cmdlib.LURemoveExport,
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,
90 opcodes.OpTestAllocator: cmdlib.LUTestAllocator,
93 def __init__(self, feedback=None):
94 """Constructor for Processor
97 - feedback_fn: the feedback function (taking one string) to be run when
98 interesting events are happening
102 self._feedback_fn = feedback
104 def ExecOpCode(self, op):
105 """Execute an opcode.
108 op: 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")
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 h_results = hm.RunPhase(constants.HOOKS_PHASE_PRE)
130 lu.HooksCallBack(constants.HOOKS_PHASE_PRE,
131 h_results, self._feedback_fn, None)
133 result = lu.Exec(self._feedback_fn)
134 h_results = hm.RunPhase(constants.HOOKS_PHASE_POST)
135 result = lu.HooksCallBack(constants.HOOKS_PHASE_POST,
136 h_results, self._feedback_fn, result)
138 if lu.cfg is not None:
139 # we use lu.cfg and not self.cfg as for init cluster, self.cfg
140 # is None but lu.cfg has been recently initialized in the
142 if write_count != lu.cfg.write_count:
147 def ChainOpCode(self, op):
148 """Chain and execute an opcode.
150 This is used by LUs when they need to execute a child LU.
153 - opcode: the opcode to be executed
156 if not isinstance(op, opcodes.OpCode):
157 raise errors.ProgrammerError("Non-opcode instance passed"
160 lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
162 raise errors.OpCodeUnknown("Unknown opcode")
165 self.cfg = config.ConfigWriter()
166 self.sstore = ssconf.SimpleStore()
167 #do_hooks = lu_class.HPATH is not None
168 lu = lu_class(self, op, self.cfg, self.sstore)
171 # hm = HooksMaster(rpc.call_hooks_runner, self, lu)
172 # h_results = hm.RunPhase(constants.HOOKS_PHASE_PRE)
173 # lu.HooksCallBack(constants.HOOKS_PHASE_PRE,
174 # h_results, self._feedback_fn, None)
175 result = lu.Exec(self._feedback_fn)
177 # h_results = hm.RunPhase(constants.HOOKS_PHASE_POST)
178 # result = lu.HooksCallBack(constants.HOOKS_PHASE_POST,
179 # h_results, self._feedback_fn, result)
182 def LogStep(self, current, total, message):
183 """Log a change in LU execution progress.
186 logger.Debug("Step %d/%d %s" % (current, total, message))
187 self._feedback_fn("STEP %d/%d %s" % (current, total, message))
189 def LogWarning(self, message, hint=None):
190 """Log a warning to the logs and the user.
193 logger.Error(message)
194 self._feedback_fn(" - WARNING: %s" % message)
196 self._feedback_fn(" Hint: %s" % hint)
198 def LogInfo(self, message):
199 """Log an informational message to the logs and the user.
203 self._feedback_fn(" - INFO: %s" % message)
206 class HooksMaster(object):
209 This class distributes the run commands to the nodes based on the
212 In order to remove the direct dependency on the rpc module, the
213 constructor needs a function which actually does the remote
214 call. This will usually be rpc.call_hooks_runner, but any function
215 which behaves the same works.
218 def __init__(self, callfn, proc, lu):
223 self.env, node_list_pre, node_list_post = self._BuildEnv()
224 self.node_list = {constants.HOOKS_PHASE_PRE: node_list_pre,
225 constants.HOOKS_PHASE_POST: node_list_post}
228 """Compute the environment and the target nodes.
230 Based on the opcode and the current node list, this builds the
231 environment for the hooks and the target node list for the run.
235 "PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
236 "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
237 "GANETI_OP_CODE": self.op.OP_ID,
238 "GANETI_OBJECT_TYPE": self.lu.HTYPE,
239 "GANETI_DATA_DIR": constants.DATA_DIR,
242 if self.lu.HPATH is not None:
243 lu_env, lu_nodes_pre, lu_nodes_post = self.lu.BuildHooksEnv()
246 env["GANETI_" + key] = lu_env[key]
248 lu_nodes_pre = lu_nodes_post = []
250 return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
252 def _RunWrapper(self, node_list, hpath, phase):
253 """Simple wrapper over self.callfn.
255 This method fixes the environment before doing the rpc call.
258 env = self.env.copy()
259 env["GANETI_HOOKS_PHASE"] = phase
260 env["GANETI_HOOKS_PATH"] = hpath
261 if self.lu.sstore is not None:
262 env["GANETI_CLUSTER"] = self.lu.sstore.GetClusterName()
263 env["GANETI_MASTER"] = self.lu.sstore.GetMasterNode()
265 env = dict([(str(key), str(val)) for key, val in env.iteritems()])
267 return self.callfn(node_list, hpath, phase, env)
269 def RunPhase(self, phase):
270 """Run all the scripts for a phase.
272 This is the main function of the HookMaster.
275 phase: the hooks phase to run
278 the result of the hooks multi-node rpc call
281 if not self.node_list[phase]:
282 # empty node list, we should not attempt to run this as either
283 # we're in the cluster init phase and the rpc client part can't
284 # even attempt to run, or this LU doesn't do hooks at all
286 hpath = self.lu.HPATH
287 results = self._RunWrapper(self.node_list[phase], hpath, phase)
288 if phase == constants.HOOKS_PHASE_PRE:
291 raise errors.HooksFailure("Communication failure")
292 for node_name in results:
293 res = results[node_name]
294 if res is False or not isinstance(res, list):
295 self.proc.LogWarning("Communication failure to node %s" % node_name)
297 for script, hkr, output in res:
298 if hkr == constants.HKR_FAIL:
299 output = output.strip().encode("string_escape")
300 errs.append((node_name, script, output))
302 raise errors.HooksAbort(errs)
305 def RunConfigUpdate(self):
306 """Run the special configuration update hook
308 This is a special hook that runs only on the master after each
309 top-level LI if the configuration has been updated.
312 phase = constants.HOOKS_PHASE_POST
313 hpath = constants.HOOKS_NAME_CFGUPDATE
314 if self.lu.sstore is None:
315 raise errors.ProgrammerError("Null sstore on config update hook")
316 nodes = [self.lu.sstore.GetMasterNode()]
317 results = self._RunWrapper(nodes, hpath, phase)