Move the cluster name from ConfigWriter to SimpleStore.
[ganeti-local] / lib / mcpu.py
1 #!/usr/bin/python
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 import os
33 import os.path
34 import time
35
36 from ganeti import opcodes
37 from ganeti import logger
38 from ganeti import constants
39 from ganeti import utils
40 from ganeti import errors
41 from ganeti import rpc
42 from ganeti import cmdlib
43 from ganeti import config
44 from ganeti import ssconf
45
46 class Processor(object):
47   """Object which runs OpCodes"""
48   DISPATCH_TABLE = {
49     # Cluster
50     opcodes.OpInitCluster: cmdlib.LUInitCluster,
51     opcodes.OpDestroyCluster: cmdlib.LUDestroyCluster,
52     opcodes.OpQueryClusterInfo: cmdlib.LUQueryClusterInfo,
53     opcodes.OpClusterCopyFile: cmdlib.LUClusterCopyFile,
54     opcodes.OpRunClusterCommand: cmdlib.LURunClusterCommand,
55     opcodes.OpVerifyCluster: cmdlib.LUVerifyCluster,
56     opcodes.OpMasterFailover: cmdlib.LUMasterFailover,
57     opcodes.OpDumpClusterConfig: cmdlib.LUDumpClusterConfig,
58     # node lu
59     opcodes.OpAddNode: cmdlib.LUAddNode,
60     opcodes.OpQueryNodes: cmdlib.LUQueryNodes,
61     opcodes.OpQueryNodeData: cmdlib.LUQueryNodeData,
62     opcodes.OpQueryNodeVolumes: cmdlib.LUQueryNodeVolumes,
63     opcodes.OpRemoveNode: cmdlib.LURemoveNode,
64     # instance lu
65     opcodes.OpCreateInstance: cmdlib.LUCreateInstance,
66     opcodes.OpReinstallInstance: cmdlib.LUReinstallInstance,
67     opcodes.OpRemoveInstance: cmdlib.LURemoveInstance,
68     opcodes.OpActivateInstanceDisks: cmdlib.LUActivateInstanceDisks,
69     opcodes.OpShutdownInstance: cmdlib.LUShutdownInstance,
70     opcodes.OpStartupInstance: cmdlib.LUStartupInstance,
71     opcodes.OpDeactivateInstanceDisks: cmdlib.LUDeactivateInstanceDisks,
72     opcodes.OpAddMDDRBDComponent: cmdlib.LUAddMDDRBDComponent,
73     opcodes.OpRemoveMDDRBDComponent: cmdlib.LURemoveMDDRBDComponent,
74     opcodes.OpReplaceDisks: cmdlib.LUReplaceDisks,
75     opcodes.OpFailoverInstance: cmdlib.LUFailoverInstance,
76     opcodes.OpConnectConsole: cmdlib.LUConnectConsole,
77     opcodes.OpQueryInstances: cmdlib.LUQueryInstances,
78     opcodes.OpQueryInstanceData: cmdlib.LUQueryInstanceData,
79     opcodes.OpSetInstanceParms: cmdlib.LUSetInstanceParms,
80     # os lu
81     opcodes.OpDiagnoseOS: cmdlib.LUDiagnoseOS,
82     # exports lu
83     opcodes.OpQueryExports: cmdlib.LUQueryExports,
84     opcodes.OpExportInstance: cmdlib.LUExportInstance,
85     }
86
87
88   def __init__(self):
89     """Constructor for Processor
90
91     """
92     self.cfg = None
93     self.sstore = None
94
95   def ExecOpCode(self, op, feedback_fn):
96     """Execute an opcode.
97
98     Args:
99      - cfg: the configuration in which we execute this opcode
100      - opcode: the opcode to be executed
101      - feedback_fn: the feedback function (taking one string) to be run when
102                     interesting events are happening
103
104     """
105     if not isinstance(op, opcodes.OpCode):
106       raise errors.ProgrammerError, ("Non-opcode instance passed"
107                                      " to ExecOpcode")
108
109     lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
110     if lu_class is None:
111       raise errors.OpCodeUnknown, "Unknown opcode"
112
113     if lu_class.REQ_CLUSTER and self.cfg is None:
114       self.cfg = config.ConfigWriter()
115       self.sstore = ssconf.SimpleStore()
116     lu = lu_class(self, op, self.cfg, self.sstore)
117     lu.CheckPrereq()
118     do_hooks = lu_class.HPATH is not None
119     if do_hooks:
120       hm = HooksMaster(rpc.call_hooks_runner, self.cfg, self.sstore, lu)
121       hm.RunPhase(constants.HOOKS_PHASE_PRE)
122     result = lu.Exec(feedback_fn)
123     if do_hooks:
124       hm.RunPhase(constants.HOOKS_PHASE_POST)
125     return result
126
127   def ChainOpCode(self, op, feedback_fn):
128     """Chain and execute an opcode.
129
130     This is used by LUs when they need to execute a child LU.
131
132     Args:
133      - opcode: the opcode to be executed
134      - feedback_fn: the feedback function (taking one string) to be run when
135                     interesting events are happening
136
137     """
138     if not isinstance(op, opcodes.OpCode):
139       raise errors.ProgrammerError, ("Non-opcode instance passed"
140                                      " to ExecOpcode")
141
142     lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
143     if lu_class is None:
144       raise errors.OpCodeUnknown, "Unknown opcode"
145
146     if lu_class.REQ_CLUSTER and self.cfg is None:
147       self.cfg = config.ConfigWriter()
148       self.sstore = ssconf.SimpleStore()
149     do_hooks = lu_class.HPATH is not None
150     lu = lu_class(self, op, self.cfg, self.sstore)
151     lu.CheckPrereq()
152     #if do_hooks:
153     #  hm = HooksMaster(rpc.call_hooks_runner, self.cfg, self.sstore, lu)
154     #  hm.RunPhase(constants.HOOKS_PHASE_PRE)
155     result = lu.Exec(feedback_fn)
156     #if do_hooks:
157     #  hm.RunPhase(constants.HOOKS_PHASE_POST)
158     return result
159
160
161 class HooksMaster(object):
162   """Hooks master.
163
164   This class distributes the run commands to the nodes based on the
165   specific LU class.
166
167   In order to remove the direct dependency on the rpc module, the
168   constructor needs a function which actually does the remote
169   call. This will usually be rpc.call_hooks_runner, but any function
170   which behaves the same works.
171
172   """
173   def __init__(self, callfn, cfg, sstore, lu):
174     self.callfn = callfn
175     self.cfg = cfg
176     self.sstore = sstore
177     self.lu = lu
178     self.op = lu.op
179     self.hpath = self.lu.HPATH
180     self.env, node_list_pre, node_list_post = self._BuildEnv()
181
182     self.node_list = {constants.HOOKS_PHASE_PRE: node_list_pre,
183                       constants.HOOKS_PHASE_POST: node_list_post}
184
185   def _BuildEnv(self):
186     """Compute the environment and the target nodes.
187
188     Based on the opcode and the current node list, this builds the
189     environment for the hooks and the target node list for the run.
190
191     """
192     env = {
193       "PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
194       "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
195       "GANETI_OP_CODE": self.op.OP_ID,
196       "GANETI_OBJECT_TYPE": self.lu.HTYPE,
197       }
198
199     lu_env, lu_nodes_pre, lu_nodes_post = self.lu.BuildHooksEnv()
200     if lu_env:
201       for key in lu_env:
202         env["GANETI_" + key] = lu_env[key]
203
204     if self.sstore is not None:
205       env["GANETI_CLUSTER"] = self.sstore.GetClusterName()
206       env["GANETI_MASTER"] = self.sstore.GetMasterNode()
207
208     for key in env:
209       if not isinstance(env[key], str):
210         env[key] = str(env[key])
211
212     return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
213
214   def RunPhase(self, phase):
215     """Run all the scripts for a phase.
216
217     This is the main function of the HookMaster.
218
219     """
220     if not self.node_list[phase]:
221       # empty node list, we should not attempt to run this
222       # as most probably we're in the cluster init phase and the rpc client
223       # part can't even attempt to run
224       return
225     self.env["GANETI_HOOKS_PHASE"] = str(phase)
226     results = self.callfn(self.node_list[phase], self.hpath, phase, self.env)
227     if phase == constants.HOOKS_PHASE_PRE:
228       errs = []
229       if not results:
230         raise errors.HooksFailure, "Communication failure"
231       for node_name in results:
232         res = results[node_name]
233         if res is False or not isinstance(res, list):
234           raise errors.HooksFailure, ("Communication failure to node %s" %
235                                       node_name)
236         for script, hkr, output in res:
237           if hkr == constants.HKR_FAIL:
238             output = output.strip().encode("string_escape")
239             errs.append((node_name, script, output))
240       if errs:
241         raise errors.HooksAbort, errs