Fixes small spell mistakes and comments
[ganeti-local] / lib / mcpu.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 """Module implementing the logic behind the cluster operations
23
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
28
29 """
30
31
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
40
41 class Processor(object):
42   """Object which runs OpCodes"""
43   DISPATCH_TABLE = {
44     # Cluster
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,
55     # node lu
56     opcodes.OpAddNode: cmdlib.LUAddNode,
57     opcodes.OpQueryNodes: cmdlib.LUQueryNodes,
58     opcodes.OpQueryNodeVolumes: cmdlib.LUQueryNodeVolumes,
59     opcodes.OpRemoveNode: cmdlib.LURemoveNode,
60     # instance lu
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,
78     # os lu
79     opcodes.OpDiagnoseOS: cmdlib.LUDiagnoseOS,
80     # exports lu
81     opcodes.OpQueryExports: cmdlib.LUQueryExports,
82     opcodes.OpExportInstance: cmdlib.LUExportInstance,
83     # tags lu
84     opcodes.OpGetTags: cmdlib.LUGetTags,
85     opcodes.OpSearchTags: cmdlib.LUSearchTags,
86     opcodes.OpAddTags: cmdlib.LUAddTags,
87     opcodes.OpDelTags: cmdlib.LUDelTags,
88     # test lu
89     opcodes.OpTestDelay: cmdlib.LUTestDelay,
90     }
91
92   def __init__(self, feedback=None):
93     """Constructor for Processor
94
95     Args:
96      - feedback_fn: the feedback function (taking one string) to be run when
97                     interesting events are happening
98     """
99     self.cfg = None
100     self.sstore = None
101     self._feedback_fn = feedback
102
103   def ExecOpCode(self, op):
104     """Execute an opcode.
105
106     Args:
107       op: the opcode to be executed
108
109     """
110     if not isinstance(op, opcodes.OpCode):
111       raise errors.ProgrammerError("Non-opcode instance passed"
112                                    " to ExecOpcode")
113
114     lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
115     if lu_class is None:
116       raise errors.OpCodeUnknown("Unknown opcode")
117
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
123     else:
124       write_count = 0
125     lu = lu_class(self, op, self.cfg, self.sstore)
126     lu.CheckPrereq()
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
134       # lu.Exec method
135       if write_count != lu.cfg.write_count:
136         hm.RunConfigUpdate()
137
138     return result
139
140   def ChainOpCode(self, op):
141     """Chain and execute an opcode.
142
143     This is used by LUs when they need to execute a child LU.
144
145     Args:
146      - opcode: the opcode to be executed
147
148     """
149     if not isinstance(op, opcodes.OpCode):
150       raise errors.ProgrammerError("Non-opcode instance passed"
151                                    " to ExecOpcode")
152
153     lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
154     if lu_class is None:
155       raise errors.OpCodeUnknown("Unknown opcode")
156
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)
162     lu.CheckPrereq()
163     #if do_hooks:
164     #  hm = HooksMaster(rpc.call_hooks_runner, self, lu)
165     #  hm.RunPhase(constants.HOOKS_PHASE_PRE)
166     result = lu.Exec(self._feedback_fn)
167     #if do_hooks:
168     #  hm.RunPhase(constants.HOOKS_PHASE_POST)
169     return result
170
171   def LogStep(self, current, total, message):
172     """Log a change in LU execution progress.
173
174     """
175     logger.Debug("Step %d/%d %s" % (current, total, message))
176     self._feedback_fn("STEP %d/%d %s" % (current, total, message))
177
178   def LogWarning(self, message, hint=None):
179     """Log a warning to the logs and the user.
180
181     """
182     logger.Error(message)
183     self._feedback_fn(" - WARNING: %s" % message)
184     if hint:
185       self._feedback_fn("      Hint: %s" % hint)
186
187   def LogInfo(self, message):
188     """Log an informational message to the logs and the user.
189
190     """
191     logger.Info(message)
192     self._feedback_fn(" - INFO: %s" % message)
193
194
195 class HooksMaster(object):
196   """Hooks master.
197
198   This class distributes the run commands to the nodes based on the
199   specific LU class.
200
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.
205
206   """
207   def __init__(self, callfn, proc, lu):
208     self.callfn = callfn
209     self.proc = proc
210     self.lu = lu
211     self.op = lu.op
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}
215
216   def _BuildEnv(self):
217     """Compute the environment and the target nodes.
218
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.
221
222     """
223     env = {
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,
229       }
230
231     if self.lu.HPATH is not None:
232       lu_env, lu_nodes_pre, lu_nodes_post = self.lu.BuildHooksEnv()
233       if lu_env:
234         for key in lu_env:
235           env["GANETI_" + key] = lu_env[key]
236     else:
237       lu_nodes_pre = lu_nodes_post = []
238
239     return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
240
241   def _RunWrapper(self, node_list, hpath, phase):
242     """Simple wrapper over self.callfn.
243
244     This method fixes the environment before doing the rpc call.
245
246     """
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()
253
254     env = dict([(str(key), str(val)) for key, val in env.iteritems()])
255
256     return self.callfn(node_list, hpath, phase, env)
257
258   def RunPhase(self, phase):
259     """Run all the scripts for a phase.
260
261     This is the main function of the HookMaster.
262
263     """
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
268       return
269     hpath = self.lu.HPATH
270     results = self._RunWrapper(self.node_list[phase], hpath, phase)
271     if phase == constants.HOOKS_PHASE_PRE:
272       errs = []
273       if not results:
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)
279           continue
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))
284       if errs:
285         raise errors.HooksAbort(errs)
286
287   def RunConfigUpdate(self):
288     """Run the special configuration update hook
289
290     This is a special hook that runs only on the master after each
291     top-level LI if the configuration has been updated.
292
293     """
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)