Simplify QuitGanetiException instantiation
[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
42 class Processor(object):
43   """Object which runs OpCodes"""
44   DISPATCH_TABLE = {
45     # Cluster
46     opcodes.OpDestroyCluster: cmdlib.LUDestroyCluster,
47     opcodes.OpQueryClusterInfo: cmdlib.LUQueryClusterInfo,
48     opcodes.OpVerifyCluster: cmdlib.LUVerifyCluster,
49     opcodes.OpMasterFailover: cmdlib.LUMasterFailover,
50     opcodes.OpDumpClusterConfig: cmdlib.LUDumpClusterConfig,
51     opcodes.OpRenameCluster: cmdlib.LURenameCluster,
52     opcodes.OpVerifyDisks: cmdlib.LUVerifyDisks,
53     opcodes.OpSetClusterParams: cmdlib.LUSetClusterParams,
54     # node lu
55     opcodes.OpAddNode: cmdlib.LUAddNode,
56     opcodes.OpQueryNodes: cmdlib.LUQueryNodes,
57     opcodes.OpQueryNodeVolumes: cmdlib.LUQueryNodeVolumes,
58     opcodes.OpRemoveNode: cmdlib.LURemoveNode,
59     # instance lu
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.OpReplaceDisks: cmdlib.LUReplaceDisks,
70     opcodes.OpFailoverInstance: cmdlib.LUFailoverInstance,
71     opcodes.OpConnectConsole: cmdlib.LUConnectConsole,
72     opcodes.OpQueryInstances: cmdlib.LUQueryInstances,
73     opcodes.OpQueryInstanceData: cmdlib.LUQueryInstanceData,
74     opcodes.OpSetInstanceParams: cmdlib.LUSetInstanceParams,
75     opcodes.OpGrowDisk: cmdlib.LUGrowDisk,
76     # os lu
77     opcodes.OpDiagnoseOS: cmdlib.LUDiagnoseOS,
78     # exports lu
79     opcodes.OpQueryExports: cmdlib.LUQueryExports,
80     opcodes.OpExportInstance: cmdlib.LUExportInstance,
81     opcodes.OpRemoveExport: cmdlib.LURemoveExport,
82     # tags lu
83     opcodes.OpGetTags: cmdlib.LUGetTags,
84     opcodes.OpSearchTags: cmdlib.LUSearchTags,
85     opcodes.OpAddTags: cmdlib.LUAddTags,
86     opcodes.OpDelTags: cmdlib.LUDelTags,
87     # test lu
88     opcodes.OpTestDelay: cmdlib.LUTestDelay,
89     opcodes.OpTestAllocator: cmdlib.LUTestAllocator,
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 self.cfg is None:
119       self.cfg = config.ConfigWriter()
120       if lu_class.REQ_WSSTORE:
121         self.sstore = ssconf.WritableSimpleStore()
122       else:
123         self.sstore = ssconf.SimpleStore()
124     if self.cfg is not None:
125       write_count = self.cfg.write_count
126     else:
127       write_count = 0
128     lu = lu_class(self, op, self.cfg, self.sstore)
129     lu.CheckPrereq()
130     hm = HooksMaster(rpc.call_hooks_runner, self, lu)
131     h_results = hm.RunPhase(constants.HOOKS_PHASE_PRE)
132     lu.HooksCallBack(constants.HOOKS_PHASE_PRE,
133                      h_results, self._feedback_fn, None)
134     try:
135       result = lu.Exec(self._feedback_fn)
136       h_results = hm.RunPhase(constants.HOOKS_PHASE_POST)
137       result = lu.HooksCallBack(constants.HOOKS_PHASE_POST,
138                        h_results, self._feedback_fn, result)
139     finally:
140       if lu.cfg is not None:
141         # we use lu.cfg and not self.cfg as for init cluster, self.cfg
142         # is None but lu.cfg has been recently initialized in the
143         # lu.Exec method
144         if write_count != lu.cfg.write_count:
145           hm.RunConfigUpdate()
146
147     return result
148
149   def ChainOpCode(self, op):
150     """Chain and execute an opcode.
151
152     This is used by LUs when they need to execute a child LU.
153
154     Args:
155      - opcode: the opcode to be executed
156
157     """
158     if not isinstance(op, opcodes.OpCode):
159       raise errors.ProgrammerError("Non-opcode instance passed"
160                                    " to ExecOpcode")
161
162     lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
163     if lu_class is None:
164       raise errors.OpCodeUnknown("Unknown opcode")
165
166     if self.cfg is None:
167       self.cfg = config.ConfigWriter()
168       self.sstore = ssconf.SimpleStore()
169     #do_hooks = lu_class.HPATH is not None
170     lu = lu_class(self, op, self.cfg, self.sstore)
171     lu.CheckPrereq()
172     #if do_hooks:
173     #  hm = HooksMaster(rpc.call_hooks_runner, self, lu)
174     #  h_results = hm.RunPhase(constants.HOOKS_PHASE_PRE)
175     #  lu.HooksCallBack(constants.HOOKS_PHASE_PRE,
176     #                   h_results, self._feedback_fn, None)
177     result = lu.Exec(self._feedback_fn)
178     #if do_hooks:
179     #  h_results = hm.RunPhase(constants.HOOKS_PHASE_POST)
180     #  result = lu.HooksCallBack(constants.HOOKS_PHASE_POST,
181     #                   h_results, self._feedback_fn, result)
182     return result
183
184   def LogStep(self, current, total, message):
185     """Log a change in LU execution progress.
186
187     """
188     logger.Debug("Step %d/%d %s" % (current, total, message))
189     self._feedback_fn("STEP %d/%d %s" % (current, total, message))
190
191   def LogWarning(self, message, hint=None):
192     """Log a warning to the logs and the user.
193
194     """
195     logger.Error(message)
196     self._feedback_fn(" - WARNING: %s" % message)
197     if hint:
198       self._feedback_fn("      Hint: %s" % hint)
199
200   def LogInfo(self, message):
201     """Log an informational message to the logs and the user.
202
203     """
204     logger.Info(message)
205     self._feedback_fn(" - INFO: %s" % message)
206
207
208 class HooksMaster(object):
209   """Hooks master.
210
211   This class distributes the run commands to the nodes based on the
212   specific LU class.
213
214   In order to remove the direct dependency on the rpc module, the
215   constructor needs a function which actually does the remote
216   call. This will usually be rpc.call_hooks_runner, but any function
217   which behaves the same works.
218
219   """
220   def __init__(self, callfn, proc, lu):
221     self.callfn = callfn
222     self.proc = proc
223     self.lu = lu
224     self.op = lu.op
225     self.env, node_list_pre, node_list_post = self._BuildEnv()
226     self.node_list = {constants.HOOKS_PHASE_PRE: node_list_pre,
227                       constants.HOOKS_PHASE_POST: node_list_post}
228
229   def _BuildEnv(self):
230     """Compute the environment and the target nodes.
231
232     Based on the opcode and the current node list, this builds the
233     environment for the hooks and the target node list for the run.
234
235     """
236     env = {
237       "PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
238       "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
239       "GANETI_OP_CODE": self.op.OP_ID,
240       "GANETI_OBJECT_TYPE": self.lu.HTYPE,
241       "GANETI_DATA_DIR": constants.DATA_DIR,
242       }
243
244     if self.lu.HPATH is not None:
245       lu_env, lu_nodes_pre, lu_nodes_post = self.lu.BuildHooksEnv()
246       if lu_env:
247         for key in lu_env:
248           env["GANETI_" + key] = lu_env[key]
249     else:
250       lu_nodes_pre = lu_nodes_post = []
251
252     return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
253
254   def _RunWrapper(self, node_list, hpath, phase):
255     """Simple wrapper over self.callfn.
256
257     This method fixes the environment before doing the rpc call.
258
259     """
260     env = self.env.copy()
261     env["GANETI_HOOKS_PHASE"] = phase
262     env["GANETI_HOOKS_PATH"] = hpath
263     if self.lu.sstore is not None:
264       env["GANETI_CLUSTER"] = self.lu.sstore.GetClusterName()
265       env["GANETI_MASTER"] = self.lu.sstore.GetMasterNode()
266
267     env = dict([(str(key), str(val)) for key, val in env.iteritems()])
268
269     return self.callfn(node_list, hpath, phase, env)
270
271   def RunPhase(self, phase):
272     """Run all the scripts for a phase.
273
274     This is the main function of the HookMaster.
275
276     Args:
277       phase: the hooks phase to run
278
279     Returns:
280       the result of the hooks multi-node rpc call
281
282     """
283     if not self.node_list[phase]:
284       # empty node list, we should not attempt to run this as either
285       # we're in the cluster init phase and the rpc client part can't
286       # even attempt to run, or this LU doesn't do hooks at all
287       return
288     hpath = self.lu.HPATH
289     results = self._RunWrapper(self.node_list[phase], hpath, phase)
290     if phase == constants.HOOKS_PHASE_PRE:
291       errs = []
292       if not results:
293         raise errors.HooksFailure("Communication failure")
294       for node_name in results:
295         res = results[node_name]
296         if res is False or not isinstance(res, list):
297           self.proc.LogWarning("Communication failure to node %s" % node_name)
298           continue
299         for script, hkr, output in res:
300           if hkr == constants.HKR_FAIL:
301             output = output.strip().encode("string_escape")
302             errs.append((node_name, script, output))
303       if errs:
304         raise errors.HooksAbort(errs)
305     return results
306
307   def RunConfigUpdate(self):
308     """Run the special configuration update hook
309
310     This is a special hook that runs only on the master after each
311     top-level LI if the configuration has been updated.
312
313     """
314     phase = constants.HOOKS_PHASE_POST
315     hpath = constants.HOOKS_NAME_CFGUPDATE
316     if self.lu.sstore is None:
317       raise errors.ProgrammerError("Null sstore on config update hook")
318     nodes = [self.lu.sstore.GetMasterNode()]
319     results = self._RunWrapper(nodes, hpath, phase)