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.OpMigrateInstance: cmdlib.LUMigrateInstance,
75 opcodes.OpConnectConsole: cmdlib.LUConnectConsole,
76 opcodes.OpQueryInstances: cmdlib.LUQueryInstances,
77 opcodes.OpQueryInstanceData: cmdlib.LUQueryInstanceData,
78 opcodes.OpSetInstanceParms: cmdlib.LUSetInstanceParms,
79 opcodes.OpGrowDisk: cmdlib.LUGrowDisk,
81 opcodes.OpDiagnoseOS: cmdlib.LUDiagnoseOS,
83 opcodes.OpQueryExports: cmdlib.LUQueryExports,
84 opcodes.OpExportInstance: cmdlib.LUExportInstance,
85 opcodes.OpRemoveExport: cmdlib.LURemoveExport,
87 opcodes.OpGetTags: cmdlib.LUGetTags,
88 opcodes.OpSearchTags: cmdlib.LUSearchTags,
89 opcodes.OpAddTags: cmdlib.LUAddTags,
90 opcodes.OpDelTags: cmdlib.LUDelTags,
92 opcodes.OpTestDelay: cmdlib.LUTestDelay,
93 opcodes.OpTestAllocator: cmdlib.LUTestAllocator,
96 def __init__(self, feedback=None):
97 """Constructor for Processor
100 - feedback_fn: the feedback function (taking one string) to be run when
101 interesting events are happening
105 self._feedback_fn = feedback
107 def ExecOpCode(self, op):
108 """Execute an opcode.
111 op: the opcode to be executed
114 if not isinstance(op, opcodes.OpCode):
115 raise errors.ProgrammerError("Non-opcode instance passed"
118 lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
120 raise errors.OpCodeUnknown("Unknown opcode")
122 if lu_class.REQ_CLUSTER and self.cfg is None:
123 self.cfg = config.ConfigWriter()
124 self.sstore = ssconf.SimpleStore()
125 if self.cfg is not None:
126 write_count = self.cfg.write_count
129 lu = lu_class(self, op, self.cfg, self.sstore)
131 hm = HooksMaster(rpc.call_hooks_runner, self, lu)
132 h_results = hm.RunPhase(constants.HOOKS_PHASE_PRE)
133 lu.HooksCallBack(constants.HOOKS_PHASE_PRE,
134 h_results, self._feedback_fn, None)
136 result = lu.Exec(self._feedback_fn)
137 h_results = hm.RunPhase(constants.HOOKS_PHASE_POST)
138 result = lu.HooksCallBack(constants.HOOKS_PHASE_POST,
139 h_results, self._feedback_fn, result)
141 if lu.cfg is not None:
142 # we use lu.cfg and not self.cfg as for init cluster, self.cfg
143 # is None but lu.cfg has been recently initialized in the
145 if write_count != lu.cfg.write_count:
150 def ChainOpCode(self, op):
151 """Chain and execute an opcode.
153 This is used by LUs when they need to execute a child LU.
156 - opcode: the opcode to be executed
159 if not isinstance(op, opcodes.OpCode):
160 raise errors.ProgrammerError("Non-opcode instance passed"
163 lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
165 raise errors.OpCodeUnknown("Unknown opcode")
167 if lu_class.REQ_CLUSTER and self.cfg is None:
168 self.cfg = config.ConfigWriter()
169 self.sstore = ssconf.SimpleStore()
170 #do_hooks = lu_class.HPATH is not None
171 lu = lu_class(self, op, self.cfg, self.sstore)
174 # hm = HooksMaster(rpc.call_hooks_runner, self, lu)
175 # h_results = hm.RunPhase(constants.HOOKS_PHASE_PRE)
176 # lu.HooksCallBack(constants.HOOKS_PHASE_PRE,
177 # h_results, self._feedback_fn, None)
178 result = lu.Exec(self._feedback_fn)
180 # h_results = hm.RunPhase(constants.HOOKS_PHASE_POST)
181 # result = lu.HooksCallBack(constants.HOOKS_PHASE_POST,
182 # h_results, self._feedback_fn, result)
185 def LogStep(self, current, total, message):
186 """Log a change in LU execution progress.
189 logger.Debug("Step %d/%d %s" % (current, total, message))
190 self._feedback_fn("STEP %d/%d %s" % (current, total, message))
192 def LogWarning(self, message, hint=None):
193 """Log a warning to the logs and the user.
196 logger.Error(message)
197 self._feedback_fn(" - WARNING: %s" % message)
199 self._feedback_fn(" Hint: %s" % hint)
201 def LogInfo(self, message):
202 """Log an informational message to the logs and the user.
206 self._feedback_fn(" - INFO: %s" % message)
209 class HooksMaster(object):
212 This class distributes the run commands to the nodes based on the
215 In order to remove the direct dependency on the rpc module, the
216 constructor needs a function which actually does the remote
217 call. This will usually be rpc.call_hooks_runner, but any function
218 which behaves the same works.
221 def __init__(self, callfn, proc, lu):
226 self.env, node_list_pre, node_list_post = self._BuildEnv()
227 self.node_list = {constants.HOOKS_PHASE_PRE: node_list_pre,
228 constants.HOOKS_PHASE_POST: node_list_post}
231 """Compute the environment and the target nodes.
233 Based on the opcode and the current node list, this builds the
234 environment for the hooks and the target node list for the run.
238 "PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
239 "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
240 "GANETI_OP_CODE": self.op.OP_ID,
241 "GANETI_OBJECT_TYPE": self.lu.HTYPE,
242 "GANETI_DATA_DIR": constants.DATA_DIR,
245 if self.lu.HPATH is not None:
246 lu_env, lu_nodes_pre, lu_nodes_post = self.lu.BuildHooksEnv()
249 env["GANETI_" + key] = lu_env[key]
251 lu_nodes_pre = lu_nodes_post = []
253 return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
255 def _RunWrapper(self, node_list, hpath, phase):
256 """Simple wrapper over self.callfn.
258 This method fixes the environment before doing the rpc call.
261 env = self.env.copy()
262 env["GANETI_HOOKS_PHASE"] = phase
263 env["GANETI_HOOKS_PATH"] = hpath
264 if self.lu.sstore is not None:
265 env["GANETI_CLUSTER"] = self.lu.sstore.GetClusterName()
266 env["GANETI_MASTER"] = self.lu.sstore.GetMasterNode()
268 env = dict([(str(key), str(val)) for key, val in env.iteritems()])
270 return self.callfn(node_list, hpath, phase, env)
272 def RunPhase(self, phase):
273 """Run all the scripts for a phase.
275 This is the main function of the HookMaster.
278 phase: the hooks phase to run
281 the result of the hooks multi-node rpc call
284 if not self.node_list[phase]:
285 # empty node list, we should not attempt to run this as either
286 # we're in the cluster init phase and the rpc client part can't
287 # even attempt to run, or this LU doesn't do hooks at all
289 hpath = self.lu.HPATH
290 results = self._RunWrapper(self.node_list[phase], hpath, phase)
291 if phase == constants.HOOKS_PHASE_PRE:
294 raise errors.HooksFailure("Communication failure")
295 for node_name in results:
296 res = results[node_name]
297 if res is False or not isinstance(res, list):
298 self.proc.LogWarning("Communication failure to node %s" % node_name)
300 for script, hkr, output in res:
301 if hkr == constants.HKR_FAIL:
302 output = output.strip().encode("string_escape")
303 errs.append((node_name, script, output))
305 raise errors.HooksAbort(errs)
308 def RunConfigUpdate(self):
309 """Run the special configuration update hook
311 This is a special hook that runs only on the master after each
312 top-level LI if the configuration has been updated.
315 phase = constants.HOOKS_PHASE_POST
316 hpath = constants.HOOKS_NAME_CFGUPDATE
317 if self.lu.sstore is None:
318 raise errors.ProgrammerError("Null sstore on config update hook")
319 nodes = [self.lu.sstore.GetMasterNode()]
320 results = self._RunWrapper(nodes, hpath, phase)