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