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