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 op: the opcode to be executed
110 if not isinstance(op, opcodes.OpCode):
111 raise errors.ProgrammerError("Non-opcode instance passed"
114 lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
116 raise errors.OpCodeUnknown("Unknown opcode")
118 if lu_class.REQ_CLUSTER and self.cfg is None:
119 self.cfg = config.ConfigWriter()
120 self.sstore = ssconf.SimpleStore()
121 if self.cfg is not None:
122 write_count = self.cfg.write_count
125 lu = lu_class(self, op, self.cfg, self.sstore)
127 hm = HooksMaster(rpc.call_hooks_runner, self, lu)
128 hm.RunPhase(constants.HOOKS_PHASE_PRE)
129 result = lu.Exec(self._feedback_fn)
130 hm.RunPhase(constants.HOOKS_PHASE_POST)
131 if lu.cfg is not None:
132 # we use lu.cfg and not self.cfg as for init cluster, self.cfg
133 # is None but lu.cfg has been recently initialized in the
135 if write_count != lu.cfg.write_count:
140 def ChainOpCode(self, op):
141 """Chain and execute an opcode.
143 This is used by LUs when they need to execute a child LU.
146 - opcode: the opcode to be executed
149 if not isinstance(op, opcodes.OpCode):
150 raise errors.ProgrammerError("Non-opcode instance passed"
153 lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
155 raise errors.OpCodeUnknown("Unknown opcode")
157 if lu_class.REQ_CLUSTER and self.cfg is None:
158 self.cfg = config.ConfigWriter()
159 self.sstore = ssconf.SimpleStore()
160 #do_hooks = lu_class.HPATH is not None
161 lu = lu_class(self, op, self.cfg, self.sstore)
164 # hm = HooksMaster(rpc.call_hooks_runner, self, lu)
165 # hm.RunPhase(constants.HOOKS_PHASE_PRE)
166 result = lu.Exec(self._feedback_fn)
168 # hm.RunPhase(constants.HOOKS_PHASE_POST)
171 def LogStep(self, current, total, message):
172 """Log a change in LU execution progress.
175 logger.Debug("Step %d/%d %s" % (current, total, message))
176 self._feedback_fn("STEP %d/%d %s" % (current, total, message))
178 def LogWarning(self, message, hint=None):
179 """Log a warning to the logs and the user.
182 logger.Error(message)
183 self._feedback_fn(" - WARNING: %s" % message)
185 self._feedback_fn(" Hint: %s" % hint)
187 def LogInfo(self, message):
188 """Log an informational message to the logs and the user.
192 self._feedback_fn(" - INFO: %s" % message)
195 class HooksMaster(object):
198 This class distributes the run commands to the nodes based on the
201 In order to remove the direct dependency on the rpc module, the
202 constructor needs a function which actually does the remote
203 call. This will usually be rpc.call_hooks_runner, but any function
204 which behaves the same works.
207 def __init__(self, callfn, proc, lu):
212 self.env, node_list_pre, node_list_post = self._BuildEnv()
213 self.node_list = {constants.HOOKS_PHASE_PRE: node_list_pre,
214 constants.HOOKS_PHASE_POST: node_list_post}
217 """Compute the environment and the target nodes.
219 Based on the opcode and the current node list, this builds the
220 environment for the hooks and the target node list for the run.
224 "PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
225 "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
226 "GANETI_OP_CODE": self.op.OP_ID,
227 "GANETI_OBJECT_TYPE": self.lu.HTYPE,
228 "GANETI_DATA_DIR": constants.DATA_DIR,
231 if self.lu.HPATH is not None:
232 lu_env, lu_nodes_pre, lu_nodes_post = self.lu.BuildHooksEnv()
235 env["GANETI_" + key] = lu_env[key]
237 lu_nodes_pre = lu_nodes_post = []
239 return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
241 def _RunWrapper(self, node_list, hpath, phase):
242 """Simple wrapper over self.callfn.
244 This method fixes the environment before doing the rpc call.
247 env = self.env.copy()
248 env["GANETI_HOOKS_PHASE"] = phase
249 env["GANETI_HOOKS_PATH"] = hpath
250 if self.lu.sstore is not None:
251 env["GANETI_CLUSTER"] = self.lu.sstore.GetClusterName()
252 env["GANETI_MASTER"] = self.lu.sstore.GetMasterNode()
254 env = dict([(str(key), str(val)) for key, val in env.iteritems()])
256 return self.callfn(node_list, hpath, phase, env)
258 def RunPhase(self, phase):
259 """Run all the scripts for a phase.
261 This is the main function of the HookMaster.
264 if not self.node_list[phase]:
265 # empty node list, we should not attempt to run this as either
266 # we're in the cluster init phase and the rpc client part can't
267 # even attempt to run, or this LU doesn't do hooks at all
269 hpath = self.lu.HPATH
270 results = self._RunWrapper(self.node_list[phase], hpath, phase)
271 if phase == constants.HOOKS_PHASE_PRE:
274 raise errors.HooksFailure("Communication failure")
275 for node_name in results:
276 res = results[node_name]
277 if res is False or not isinstance(res, list):
278 self.proc.LogWarning("Communication failure to node %s" % node_name)
280 for script, hkr, output in res:
281 if hkr == constants.HKR_FAIL:
282 output = output.strip().encode("string_escape")
283 errs.append((node_name, script, output))
285 raise errors.HooksAbort(errs)
287 def RunConfigUpdate(self):
288 """Run the special configuration update hook
290 This is a special hook that runs only on the master after each
291 top-level LI if the configuration has been updated.
294 phase = constants.HOOKS_PHASE_POST
295 hpath = constants.HOOKS_NAME_CFGUPDATE
296 if self.lu.sstore is None:
297 raise errors.ProgrammerError("Null sstore on config update hook")
298 nodes = [self.lu.sstore.GetMasterNode()]
299 results = self._RunWrapper(nodes, hpath, phase)