- Move --force option to cli.py
[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.cfg is not None:
205       env["GANETI_CLUSTER"] = self.cfg.GetClusterName()
206     if self.sstore is not None:
207       env["GANETI_MASTER"] = self.sstore.GetMasterNode()
208
209     for key in env:
210       if not isinstance(env[key], str):
211         env[key] = str(env[key])
212
213     return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
214
215   def RunPhase(self, phase):
216     """Run all the scripts for a phase.
217
218     This is the main function of the HookMaster.
219
220     """
221     if not self.node_list[phase]:
222       # empty node list, we should not attempt to run this
223       # as most probably we're in the cluster init phase and the rpc client
224       # part can't even attempt to run
225       return
226     self.env["GANETI_HOOKS_PHASE"] = str(phase)
227     results = self.callfn(self.node_list[phase], self.hpath, phase, self.env)
228     if phase == constants.HOOKS_PHASE_PRE:
229       errs = []
230       if not results:
231         raise errors.HooksFailure, "Communication failure"
232       for node_name in results:
233         res = results[node_name]
234         if res is False or not isinstance(res, list):
235           raise errors.HooksFailure, ("Communication failure to node %s" %
236                                       node_name)
237         for script, hkr, output in res:
238           if hkr == constants.HKR_FAIL:
239             output = output.strip().encode("string_escape")
240             errs.append((node_name, script, output))
241       if errs:
242         raise errors.HooksAbort, errs