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