Map OpSetClusterParams to correponding LU
[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.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     try:
130       result = lu.Exec(self._feedback_fn)
131       hm.RunPhase(constants.HOOKS_PHASE_POST)
132     finally:
133       if lu.cfg is not None:
134         # we use lu.cfg and not self.cfg as for init cluster, self.cfg
135         # is None but lu.cfg has been recently initialized in the
136         # lu.Exec method
137         if write_count != lu.cfg.write_count:
138           hm.RunConfigUpdate()
139
140     return result
141
142   def ChainOpCode(self, op):
143     """Chain and execute an opcode.
144
145     This is used by LUs when they need to execute a child LU.
146
147     Args:
148      - opcode: the opcode to be executed
149
150     """
151     if not isinstance(op, opcodes.OpCode):
152       raise errors.ProgrammerError("Non-opcode instance passed"
153                                    " to ExecOpcode")
154
155     lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
156     if lu_class is None:
157       raise errors.OpCodeUnknown("Unknown opcode")
158
159     if lu_class.REQ_CLUSTER and self.cfg is None:
160       self.cfg = config.ConfigWriter()
161       self.sstore = ssconf.SimpleStore()
162     #do_hooks = lu_class.HPATH is not None
163     lu = lu_class(self, op, self.cfg, self.sstore)
164     lu.CheckPrereq()
165     #if do_hooks:
166     #  hm = HooksMaster(rpc.call_hooks_runner, self, lu)
167     #  hm.RunPhase(constants.HOOKS_PHASE_PRE)
168     result = lu.Exec(self._feedback_fn)
169     #if do_hooks:
170     #  hm.RunPhase(constants.HOOKS_PHASE_POST)
171     return result
172
173   def LogStep(self, current, total, message):
174     """Log a change in LU execution progress.
175
176     """
177     logger.Debug("Step %d/%d %s" % (current, total, message))
178     self._feedback_fn("STEP %d/%d %s" % (current, total, message))
179
180   def LogWarning(self, message, hint=None):
181     """Log a warning to the logs and the user.
182
183     """
184     logger.Error(message)
185     self._feedback_fn(" - WARNING: %s" % message)
186     if hint:
187       self._feedback_fn("      Hint: %s" % hint)
188
189   def LogInfo(self, message):
190     """Log an informational message to the logs and the user.
191
192     """
193     logger.Info(message)
194     self._feedback_fn(" - INFO: %s" % message)
195
196
197 class HooksMaster(object):
198   """Hooks master.
199
200   This class distributes the run commands to the nodes based on the
201   specific LU class.
202
203   In order to remove the direct dependency on the rpc module, the
204   constructor needs a function which actually does the remote
205   call. This will usually be rpc.call_hooks_runner, but any function
206   which behaves the same works.
207
208   """
209   def __init__(self, callfn, proc, lu):
210     self.callfn = callfn
211     self.proc = proc
212     self.lu = lu
213     self.op = lu.op
214     self.env, node_list_pre, node_list_post = self._BuildEnv()
215     self.node_list = {constants.HOOKS_PHASE_PRE: node_list_pre,
216                       constants.HOOKS_PHASE_POST: node_list_post}
217
218   def _BuildEnv(self):
219     """Compute the environment and the target nodes.
220
221     Based on the opcode and the current node list, this builds the
222     environment for the hooks and the target node list for the run.
223
224     """
225     env = {
226       "PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
227       "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
228       "GANETI_OP_CODE": self.op.OP_ID,
229       "GANETI_OBJECT_TYPE": self.lu.HTYPE,
230       "GANETI_DATA_DIR": constants.DATA_DIR,
231       }
232
233     if self.lu.HPATH is not None:
234       lu_env, lu_nodes_pre, lu_nodes_post = self.lu.BuildHooksEnv()
235       if lu_env:
236         for key in lu_env:
237           env["GANETI_" + key] = lu_env[key]
238     else:
239       lu_nodes_pre = lu_nodes_post = []
240
241     return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
242
243   def _RunWrapper(self, node_list, hpath, phase):
244     """Simple wrapper over self.callfn.
245
246     This method fixes the environment before doing the rpc call.
247
248     """
249     env = self.env.copy()
250     env["GANETI_HOOKS_PHASE"] = phase
251     env["GANETI_HOOKS_PATH"] = hpath
252     if self.lu.sstore is not None:
253       env["GANETI_CLUSTER"] = self.lu.sstore.GetClusterName()
254       env["GANETI_MASTER"] = self.lu.sstore.GetMasterNode()
255
256     env = dict([(str(key), str(val)) for key, val in env.iteritems()])
257
258     return self.callfn(node_list, hpath, phase, env)
259
260   def RunPhase(self, phase):
261     """Run all the scripts for a phase.
262
263     This is the main function of the HookMaster.
264
265     """
266     if not self.node_list[phase]:
267       # empty node list, we should not attempt to run this as either
268       # we're in the cluster init phase and the rpc client part can't
269       # even attempt to run, or this LU doesn't do hooks at all
270       return
271     hpath = self.lu.HPATH
272     results = self._RunWrapper(self.node_list[phase], hpath, phase)
273     if phase == constants.HOOKS_PHASE_PRE:
274       errs = []
275       if not results:
276         raise errors.HooksFailure("Communication failure")
277       for node_name in results:
278         res = results[node_name]
279         if res is False or not isinstance(res, list):
280           self.proc.LogWarning("Communication failure to node %s" % node_name)
281           continue
282         for script, hkr, output in res:
283           if hkr == constants.HKR_FAIL:
284             output = output.strip().encode("string_escape")
285             errs.append((node_name, script, output))
286       if errs:
287         raise errors.HooksAbort(errs)
288
289   def RunConfigUpdate(self):
290     """Run the special configuration update hook
291
292     This is a special hook that runs only on the master after each
293     top-level LI if the configuration has been updated.
294
295     """
296     phase = constants.HOOKS_PHASE_POST
297     hpath = constants.HOOKS_NAME_CFGUPDATE
298     if self.lu.sstore is None:
299       raise errors.ProgrammerError("Null sstore on config update hook")
300     nodes = [self.lu.sstore.GetMasterNode()]
301     results = self._RunWrapper(nodes, hpath, phase)