Implement tag support for cluster, nodes and instances.
[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     # tags lu
86     opcodes.OpGetTags: cmdlib.LUGetTags,
87     opcodes.OpSetTag: cmdlib.LUAddTag,
88     opcodes.OpDelTag: cmdlib.LUDelTag,
89     }
90
91
92   def __init__(self):
93     """Constructor for Processor
94
95     """
96     self.cfg = None
97     self.sstore = None
98
99   def ExecOpCode(self, op, feedback_fn):
100     """Execute an opcode.
101
102     Args:
103      - cfg: the configuration in which we execute this opcode
104      - opcode: the opcode to be executed
105      - feedback_fn: the feedback function (taking one string) to be run when
106                     interesting events are happening
107
108     """
109     if not isinstance(op, opcodes.OpCode):
110       raise errors.ProgrammerError, ("Non-opcode instance passed"
111                                      " to ExecOpcode")
112
113     lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
114     if lu_class is None:
115       raise errors.OpCodeUnknown, "Unknown opcode"
116
117     if lu_class.REQ_CLUSTER and self.cfg is None:
118       self.cfg = config.ConfigWriter()
119       self.sstore = ssconf.SimpleStore()
120     lu = lu_class(self, op, self.cfg, self.sstore)
121     lu.CheckPrereq()
122     do_hooks = lu_class.HPATH is not None
123     if do_hooks:
124       hm = HooksMaster(rpc.call_hooks_runner, self.cfg, self.sstore, lu)
125       hm.RunPhase(constants.HOOKS_PHASE_PRE)
126     result = lu.Exec(feedback_fn)
127     if do_hooks:
128       hm.RunPhase(constants.HOOKS_PHASE_POST)
129     return result
130
131   def ChainOpCode(self, op, feedback_fn):
132     """Chain and execute an opcode.
133
134     This is used by LUs when they need to execute a child LU.
135
136     Args:
137      - opcode: the opcode to be executed
138      - feedback_fn: the feedback function (taking one string) to be run when
139                     interesting events are happening
140
141     """
142     if not isinstance(op, opcodes.OpCode):
143       raise errors.ProgrammerError, ("Non-opcode instance passed"
144                                      " to ExecOpcode")
145
146     lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
147     if lu_class is None:
148       raise errors.OpCodeUnknown, "Unknown opcode"
149
150     if lu_class.REQ_CLUSTER and self.cfg is None:
151       self.cfg = config.ConfigWriter()
152       self.sstore = ssconf.SimpleStore()
153     do_hooks = lu_class.HPATH is not None
154     lu = lu_class(self, op, self.cfg, self.sstore)
155     lu.CheckPrereq()
156     #if do_hooks:
157     #  hm = HooksMaster(rpc.call_hooks_runner, self.cfg, self.sstore, lu)
158     #  hm.RunPhase(constants.HOOKS_PHASE_PRE)
159     result = lu.Exec(feedback_fn)
160     #if do_hooks:
161     #  hm.RunPhase(constants.HOOKS_PHASE_POST)
162     return result
163
164
165 class HooksMaster(object):
166   """Hooks master.
167
168   This class distributes the run commands to the nodes based on the
169   specific LU class.
170
171   In order to remove the direct dependency on the rpc module, the
172   constructor needs a function which actually does the remote
173   call. This will usually be rpc.call_hooks_runner, but any function
174   which behaves the same works.
175
176   """
177   def __init__(self, callfn, cfg, sstore, lu):
178     self.callfn = callfn
179     self.cfg = cfg
180     self.sstore = sstore
181     self.lu = lu
182     self.op = lu.op
183     self.hpath = self.lu.HPATH
184     self.env, node_list_pre, node_list_post = self._BuildEnv()
185
186     self.node_list = {constants.HOOKS_PHASE_PRE: node_list_pre,
187                       constants.HOOKS_PHASE_POST: node_list_post}
188
189   def _BuildEnv(self):
190     """Compute the environment and the target nodes.
191
192     Based on the opcode and the current node list, this builds the
193     environment for the hooks and the target node list for the run.
194
195     """
196     env = {
197       "PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
198       "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
199       "GANETI_OP_CODE": self.op.OP_ID,
200       "GANETI_OBJECT_TYPE": self.lu.HTYPE,
201       }
202
203     lu_env, lu_nodes_pre, lu_nodes_post = self.lu.BuildHooksEnv()
204     if lu_env:
205       for key in lu_env:
206         env["GANETI_" + key] = lu_env[key]
207
208     if self.sstore is not None:
209       env["GANETI_CLUSTER"] = self.sstore.GetClusterName()
210       env["GANETI_MASTER"] = self.sstore.GetMasterNode()
211
212     for key in env:
213       if not isinstance(env[key], str):
214         env[key] = str(env[key])
215
216     return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
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
226       # as most probably we're in the cluster init phase and the rpc client
227       # part can't even attempt to run
228       return
229     self.env["GANETI_HOOKS_PHASE"] = str(phase)
230     results = self.callfn(self.node_list[phase], self.hpath, phase, self.env)
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