Split the hooks env building in two parts
[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     lu = lu_class(self, op, self.cfg, self.sstore)
116     lu.CheckPrereq()
117     hm = HooksMaster(rpc.call_hooks_runner, lu)
118     hm.RunPhase(constants.HOOKS_PHASE_PRE)
119     result = lu.Exec(feedback_fn)
120     hm.RunPhase(constants.HOOKS_PHASE_POST)
121     return result
122
123   def ChainOpCode(self, op, feedback_fn):
124     """Chain and execute an opcode.
125
126     This is used by LUs when they need to execute a child LU.
127
128     Args:
129      - opcode: the opcode to be executed
130      - feedback_fn: the feedback function (taking one string) to be run when
131                     interesting events are happening
132
133     """
134     if not isinstance(op, opcodes.OpCode):
135       raise errors.ProgrammerError("Non-opcode instance passed"
136                                    " to ExecOpcode")
137
138     lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
139     if lu_class is None:
140       raise errors.OpCodeUnknown("Unknown opcode")
141
142     if lu_class.REQ_CLUSTER and self.cfg is None:
143       self.cfg = config.ConfigWriter()
144       self.sstore = ssconf.SimpleStore()
145     #do_hooks = lu_class.HPATH is not None
146     lu = lu_class(self, op, self.cfg, self.sstore)
147     lu.CheckPrereq()
148     #if do_hooks:
149     #  hm = HooksMaster(rpc.call_hooks_runner, lu)
150     #  hm.RunPhase(constants.HOOKS_PHASE_PRE)
151     result = lu.Exec(feedback_fn)
152     #if do_hooks:
153     #  hm.RunPhase(constants.HOOKS_PHASE_POST)
154     return result
155
156
157 class HooksMaster(object):
158   """Hooks master.
159
160   This class distributes the run commands to the nodes based on the
161   specific LU class.
162
163   In order to remove the direct dependency on the rpc module, the
164   constructor needs a function which actually does the remote
165   call. This will usually be rpc.call_hooks_runner, but any function
166   which behaves the same works.
167
168   """
169   def __init__(self, callfn, lu):
170     self.callfn = callfn
171     self.lu = lu
172     self.op = lu.op
173     self.env, node_list_pre, node_list_post = self._BuildEnv()
174     self.node_list = {constants.HOOKS_PHASE_PRE: node_list_pre,
175                       constants.HOOKS_PHASE_POST: node_list_post}
176
177   def _BuildEnv(self):
178     """Compute the environment and the target nodes.
179
180     Based on the opcode and the current node list, this builds the
181     environment for the hooks and the target node list for the run.
182
183     """
184     env = {
185       "PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
186       "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
187       "GANETI_OP_CODE": self.op.OP_ID,
188       "GANETI_OBJECT_TYPE": self.lu.HTYPE,
189       }
190
191     if self.lu.HPATH is not None:
192       lu_env, lu_nodes_pre, lu_nodes_post = self.lu.BuildHooksEnv()
193       if lu_env:
194         for key in lu_env:
195           env["GANETI_" + key] = lu_env[key]
196     else:
197       lu_nodes_pre = lu_nodes_post = []
198
199     return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
200
201   def _RunWrapper(self, node_list, hpath, phase):
202     """Simple wrapper over self.callfn.
203
204     This method fixes the environment before doing the rpc call.
205
206     """
207     env = self.env.copy()
208     env["GANETI_HOOKS_PHASE"] = phase
209     env["GANETI_HOOKS_PATH"] = hpath
210     if self.lu.sstore is not None:
211       env["GANETI_CLUSTER"] = self.lu.sstore.GetClusterName()
212       env["GANETI_MASTER"] = self.lu.sstore.GetMasterNode()
213
214     env = dict([(str(key), str(val)) for key, val in env.iteritems()])
215
216     return self.callfn(node_list, hpath, phase, env)
217
218   def RunPhase(self, phase):
219     """Run all the scripts for a phase.
220
221     This is the main function of the HookMaster.
222
223     """
224     if not self.node_list[phase]:
225       # empty node list, we should not attempt to run this as either
226       # we're in the cluster init phase and the rpc client part can't
227       # even attempt to run, or this LU doesn't do hooks at all
228       return
229     hpath = self.lu.HPATH
230     results = self._RunWrapper(self.node_list[phase], hpath, phase)
231     if phase == constants.HOOKS_PHASE_PRE:
232       errs = []
233       if not results:
234         raise errors.HooksFailure("Communication failure")
235       for node_name in results:
236         res = results[node_name]
237         if res is False or not isinstance(res, list):
238           raise errors.HooksFailure("Communication failure to node %s" %
239                                     node_name)
240         for script, hkr, output in res:
241           if hkr == constants.HKR_FAIL:
242             output = output.strip().encode("string_escape")
243             errs.append((node_name, script, output))
244       if errs:
245         raise errors.HooksAbort(errs)