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,
90 def __init__(self, feedback=None):
91 """Constructor for Processor
94 - feedback_fn: the feedback function (taking one string) to be run when
95 interesting events are happening
99 self._feedback_fn = feedback
101 def ExecOpCode(self, op):
102 """Execute an opcode.
105 - cfg: the configuration in which we execute this opcode
106 - opcode: the opcode to be executed
109 if not isinstance(op, opcodes.OpCode):
110 raise errors.ProgrammerError("Non-opcode instance passed"
113 lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
115 raise errors.OpCodeUnknown("Unknown opcode")
117 if lu_class.REQ_CLUSTER and self.cfg is None:
118 self.cfg = config.ConfigWriter()
119 self.sstore = ssconf.SimpleStore()
120 if self.cfg is not None:
121 write_count = self.cfg.write_count
124 lu = lu_class(self, op, self.cfg, self.sstore)
126 hm = HooksMaster(rpc.call_hooks_runner, self, lu)
127 hm.RunPhase(constants.HOOKS_PHASE_PRE)
128 result = lu.Exec(self._feedback_fn)
129 hm.RunPhase(constants.HOOKS_PHASE_POST)
130 if lu.cfg is not None:
131 # we use lu.cfg and not self.cfg as for init cluster, self.cfg
132 # is None but lu.cfg has been recently initialized in the
134 if write_count != lu.cfg.write_count:
139 def ChainOpCode(self, op):
140 """Chain and execute an opcode.
142 This is used by LUs when they need to execute a child LU.
145 - opcode: the opcode to be executed
148 if not isinstance(op, opcodes.OpCode):
149 raise errors.ProgrammerError("Non-opcode instance passed"
152 lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
154 raise errors.OpCodeUnknown("Unknown opcode")
156 if lu_class.REQ_CLUSTER and self.cfg is None:
157 self.cfg = config.ConfigWriter()
158 self.sstore = ssconf.SimpleStore()
159 #do_hooks = lu_class.HPATH is not None
160 lu = lu_class(self, op, self.cfg, self.sstore)
163 # hm = HooksMaster(rpc.call_hooks_runner, self, lu)
164 # hm.RunPhase(constants.HOOKS_PHASE_PRE)
165 result = lu.Exec(self._feedback_fn)
167 # hm.RunPhase(constants.HOOKS_PHASE_POST)
170 def LogStep(self, current, total, message):
171 """Log a change in LU execution progress.
174 logger.Debug("Step %d/%d %s" % (current, total, message))
175 self._feedback_fn("STEP %d/%d %s" % (current, total, message))
177 def LogWarning(self, message, hint=None):
178 """Log a warning to the logs and the user.
181 logger.Error(message)
182 self._feedback_fn(" - WARNING: %s" % message)
184 self._feedback_fn(" Hint: %s" % hint)
186 def LogInfo(self, message):
187 """Log an informational message to the logs and the user.
191 self._feedback_fn(" - INFO: %s" % message)
194 class HooksMaster(object):
197 This class distributes the run commands to the nodes based on the
200 In order to remove the direct dependency on the rpc module, the
201 constructor needs a function which actually does the remote
202 call. This will usually be rpc.call_hooks_runner, but any function
203 which behaves the same works.
206 def __init__(self, callfn, proc, lu):
211 self.env, node_list_pre, node_list_post = self._BuildEnv()
212 self.node_list = {constants.HOOKS_PHASE_PRE: node_list_pre,
213 constants.HOOKS_PHASE_POST: node_list_post}
216 """Compute the environment and the target nodes.
218 Based on the opcode and the current node list, this builds the
219 environment for the hooks and the target node list for the run.
223 "PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
224 "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
225 "GANETI_OP_CODE": self.op.OP_ID,
226 "GANETI_OBJECT_TYPE": self.lu.HTYPE,
227 "GANETI_DATA_DIR": constants.DATA_DIR,
230 if self.lu.HPATH is not None:
231 lu_env, lu_nodes_pre, lu_nodes_post = self.lu.BuildHooksEnv()
234 env["GANETI_" + key] = lu_env[key]
236 lu_nodes_pre = lu_nodes_post = []
238 return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
240 def _RunWrapper(self, node_list, hpath, phase):
241 """Simple wrapper over self.callfn.
243 This method fixes the environment before doing the rpc call.
246 env = self.env.copy()
247 env["GANETI_HOOKS_PHASE"] = phase
248 env["GANETI_HOOKS_PATH"] = hpath
249 if self.lu.sstore is not None:
250 env["GANETI_CLUSTER"] = self.lu.sstore.GetClusterName()
251 env["GANETI_MASTER"] = self.lu.sstore.GetMasterNode()
253 env = dict([(str(key), str(val)) for key, val in env.iteritems()])
255 return self.callfn(node_list, hpath, phase, env)
257 def RunPhase(self, phase):
258 """Run all the scripts for a phase.
260 This is the main function of the HookMaster.
263 if not self.node_list[phase]:
264 # empty node list, we should not attempt to run this as either
265 # we're in the cluster init phase and the rpc client part can't
266 # even attempt to run, or this LU doesn't do hooks at all
268 hpath = self.lu.HPATH
269 results = self._RunWrapper(self.node_list[phase], hpath, phase)
270 if phase == constants.HOOKS_PHASE_PRE:
273 raise errors.HooksFailure("Communication failure")
274 for node_name in results:
275 res = results[node_name]
276 if res is False or not isinstance(res, list):
277 self.proc.LogWarning("Communication failure to node %s" % node_name)
279 for script, hkr, output in res:
280 if hkr == constants.HKR_FAIL:
281 output = output.strip().encode("string_escape")
282 errs.append((node_name, script, output))
284 raise errors.HooksAbort(errs)
286 def RunConfigUpdate(self):
287 """Run the special configuration update hook
289 This is a special hook that runs only on the master after each
290 top-level LI if the configuration has been updated.
293 phase = constants.HOOKS_PHASE_POST
294 hpath = constants.HOOKS_NAME_CFGUPDATE
295 if self.lu.sstore is None:
296 raise errors.ProgrammerError("Null sstore on config update hook")
297 nodes = [self.lu.sstore.GetMasterNode()]
298 results = self._RunWrapper(nodes, hpath, phase)