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