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,
55 opcodes.OpAddNode: cmdlib.LUAddNode,
56 opcodes.OpQueryNodes: cmdlib.LUQueryNodes,
57 opcodes.OpQueryNodeVolumes: cmdlib.LUQueryNodeVolumes,
58 opcodes.OpRemoveNode: cmdlib.LURemoveNode,
60 opcodes.OpCreateInstance: cmdlib.LUCreateInstance,
61 opcodes.OpReinstallInstance: cmdlib.LUReinstallInstance,
62 opcodes.OpRemoveInstance: cmdlib.LURemoveInstance,
63 opcodes.OpRenameInstance: cmdlib.LURenameInstance,
64 opcodes.OpActivateInstanceDisks: cmdlib.LUActivateInstanceDisks,
65 opcodes.OpShutdownInstance: cmdlib.LUShutdownInstance,
66 opcodes.OpStartupInstance: cmdlib.LUStartupInstance,
67 opcodes.OpRebootInstance: cmdlib.LURebootInstance,
68 opcodes.OpDeactivateInstanceDisks: cmdlib.LUDeactivateInstanceDisks,
69 opcodes.OpAddMDDRBDComponent: cmdlib.LUAddMDDRBDComponent,
70 opcodes.OpRemoveMDDRBDComponent: cmdlib.LURemoveMDDRBDComponent,
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.OpSetInstanceParms: cmdlib.LUSetInstanceParms,
78 opcodes.OpDiagnoseOS: cmdlib.LUDiagnoseOS,
80 opcodes.OpQueryExports: cmdlib.LUQueryExports,
81 opcodes.OpExportInstance: cmdlib.LUExportInstance,
83 opcodes.OpGetTags: cmdlib.LUGetTags,
84 opcodes.OpSearchTags: cmdlib.LUSearchTags,
85 opcodes.OpAddTags: cmdlib.LUAddTags,
86 opcodes.OpDelTags: cmdlib.LUDelTags,
89 def __init__(self, feedback=None):
90 """Constructor for Processor
93 - feedback_fn: the feedback function (taking one string) to be run when
94 interesting events are happening
98 self._feedback_fn = feedback
100 def ExecOpCode(self, op):
101 """Execute an opcode.
104 - cfg: the configuration in which we execute this opcode
105 - opcode: the opcode to be executed
108 if not isinstance(op, opcodes.OpCode):
109 raise errors.ProgrammerError("Non-opcode instance passed"
112 lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
114 raise errors.OpCodeUnknown("Unknown opcode")
116 if lu_class.REQ_CLUSTER and self.cfg is None:
117 self.cfg = config.ConfigWriter()
118 self.sstore = ssconf.SimpleStore()
119 if self.cfg is not None:
120 write_count = self.cfg.write_count
123 lu = lu_class(self, op, self.cfg, self.sstore)
125 hm = HooksMaster(rpc.call_hooks_runner, self, lu)
126 hm.RunPhase(constants.HOOKS_PHASE_PRE)
127 result = lu.Exec(self._feedback_fn)
128 hm.RunPhase(constants.HOOKS_PHASE_POST)
129 if lu.cfg is not None:
130 # we use lu.cfg and not self.cfg as for init cluster, self.cfg
131 # is None but lu.cfg has been recently initialized in the
133 if write_count != lu.cfg.write_count:
138 def ChainOpCode(self, op):
139 """Chain and execute an opcode.
141 This is used by LUs when they need to execute a child LU.
144 - opcode: the opcode to be executed
147 if not isinstance(op, opcodes.OpCode):
148 raise errors.ProgrammerError("Non-opcode instance passed"
151 lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
153 raise errors.OpCodeUnknown("Unknown opcode")
155 if lu_class.REQ_CLUSTER and self.cfg is None:
156 self.cfg = config.ConfigWriter()
157 self.sstore = ssconf.SimpleStore()
158 #do_hooks = lu_class.HPATH is not None
159 lu = lu_class(self, op, self.cfg, self.sstore)
162 # hm = HooksMaster(rpc.call_hooks_runner, self, lu)
163 # hm.RunPhase(constants.HOOKS_PHASE_PRE)
164 result = lu.Exec(self._feedback_fn)
166 # hm.RunPhase(constants.HOOKS_PHASE_POST)
169 def LogStep(self, current, total, message):
170 """Log a change in LU execution progress.
173 logger.Debug("Step %d/%d %s" % (current, total, message))
174 self._feedback_fn("STEP %d/%d %s" % (current, total, message))
176 def LogWarning(self, message, hint=None):
177 """Log a warning to the logs and the user.
180 logger.Error(message)
181 self._feedback_fn(" - WARNING: %s" % message)
183 self._feedback_fn(" Hint: %s" % hint)
185 def LogInfo(self, message):
186 """Log an informational message to the logs and the user.
190 self._feedback_fn(" - INFO: %s" % message)
193 class HooksMaster(object):
196 This class distributes the run commands to the nodes based on the
199 In order to remove the direct dependency on the rpc module, the
200 constructor needs a function which actually does the remote
201 call. This will usually be rpc.call_hooks_runner, but any function
202 which behaves the same works.
205 def __init__(self, callfn, proc, lu):
210 self.env, node_list_pre, node_list_post = self._BuildEnv()
211 self.node_list = {constants.HOOKS_PHASE_PRE: node_list_pre,
212 constants.HOOKS_PHASE_POST: node_list_post}
215 """Compute the environment and the target nodes.
217 Based on the opcode and the current node list, this builds the
218 environment for the hooks and the target node list for the run.
222 "PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
223 "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
224 "GANETI_OP_CODE": self.op.OP_ID,
225 "GANETI_OBJECT_TYPE": self.lu.HTYPE,
226 "GANETI_DATA_DIR": constants.DATA_DIR,
229 if self.lu.HPATH is not None:
230 lu_env, lu_nodes_pre, lu_nodes_post = self.lu.BuildHooksEnv()
233 env["GANETI_" + key] = lu_env[key]
235 lu_nodes_pre = lu_nodes_post = []
237 return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
239 def _RunWrapper(self, node_list, hpath, phase):
240 """Simple wrapper over self.callfn.
242 This method fixes the environment before doing the rpc call.
245 env = self.env.copy()
246 env["GANETI_HOOKS_PHASE"] = phase
247 env["GANETI_HOOKS_PATH"] = hpath
248 if self.lu.sstore is not None:
249 env["GANETI_CLUSTER"] = self.lu.sstore.GetClusterName()
250 env["GANETI_MASTER"] = self.lu.sstore.GetMasterNode()
252 env = dict([(str(key), str(val)) for key, val in env.iteritems()])
254 return self.callfn(node_list, hpath, phase, env)
256 def RunPhase(self, phase):
257 """Run all the scripts for a phase.
259 This is the main function of the HookMaster.
262 if not self.node_list[phase]:
263 # empty node list, we should not attempt to run this as either
264 # we're in the cluster init phase and the rpc client part can't
265 # even attempt to run, or this LU doesn't do hooks at all
267 hpath = self.lu.HPATH
268 results = self._RunWrapper(self.node_list[phase], hpath, phase)
269 if phase == constants.HOOKS_PHASE_PRE:
272 raise errors.HooksFailure("Communication failure")
273 for node_name in results:
274 res = results[node_name]
275 if res is False or not isinstance(res, list):
276 self.proc.LogWarning("Communication failure to node %s" % node_name)
278 for script, hkr, output in res:
279 if hkr == constants.HKR_FAIL:
280 output = output.strip().encode("string_escape")
281 errs.append((node_name, script, output))
283 raise errors.HooksAbort(errs)
285 def RunConfigUpdate(self):
286 """Run the special configuration update hook
288 This is a special hook that runs only on the master after each
289 top-level LI if the configuration has been updated.
292 phase = constants.HOOKS_PHASE_POST
293 hpath = constants.HOOKS_NAME_CFGUPDATE
294 if self.lu.sstore is None:
295 raise errors.ProgrammerError("Null sstore on config update hook")
296 nodes = [self.lu.sstore.GetMasterNode()]
297 results = self._RunWrapper(nodes, hpath, phase)