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