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