Statistics
| Branch: | Tag: | Revision:

root / lib / mcpu.py @ 0fbbf897

History | View | Annotate | Download (10 kB)

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
from ganeti import logger
40

    
41
class Processor(object):
42
  """Object which runs OpCodes"""
43
  DISPATCH_TABLE = {
44
    # Cluster
45
    opcodes.OpInitCluster: cmdlib.LUInitCluster,
46
    opcodes.OpDestroyCluster: cmdlib.LUDestroyCluster,
47
    opcodes.OpQueryClusterInfo: cmdlib.LUQueryClusterInfo,
48
    opcodes.OpClusterCopyFile: cmdlib.LUClusterCopyFile,
49
    opcodes.OpRunClusterCommand: cmdlib.LURunClusterCommand,
50
    opcodes.OpVerifyCluster: cmdlib.LUVerifyCluster,
51
    opcodes.OpMasterFailover: cmdlib.LUMasterFailover,
52
    opcodes.OpDumpClusterConfig: cmdlib.LUDumpClusterConfig,
53
    opcodes.OpRenameCluster: cmdlib.LURenameCluster,
54
    # node lu
55
    opcodes.OpAddNode: cmdlib.LUAddNode,
56
    opcodes.OpQueryNodes: cmdlib.LUQueryNodes,
57
    opcodes.OpQueryNodeVolumes: cmdlib.LUQueryNodeVolumes,
58
    opcodes.OpRemoveNode: cmdlib.LURemoveNode,
59
    # instance lu
60
    opcodes.OpCreateInstance: cmdlib.LUCreateInstance,
61
    opcodes.OpReinstallInstance: cmdlib.LUReinstallInstance,
62
    opcodes.OpRemoveInstance: cmdlib.LURemoveInstance,
63
    opcodes.OpRenameInstance: cmdlib.LURenameInstance,
64
    opcodes.OpActivateInstanceDisks: cmdlib.LUActivateInstanceDisks,
65
    opcodes.OpShutdownInstance: cmdlib.LUShutdownInstance,
66
    opcodes.OpStartupInstance: cmdlib.LUStartupInstance,
67
    opcodes.OpRebootInstance: cmdlib.LURebootInstance,
68
    opcodes.OpDeactivateInstanceDisks: cmdlib.LUDeactivateInstanceDisks,
69
    opcodes.OpAddMDDRBDComponent: cmdlib.LUAddMDDRBDComponent,
70
    opcodes.OpRemoveMDDRBDComponent: cmdlib.LURemoveMDDRBDComponent,
71
    opcodes.OpReplaceDisks: cmdlib.LUReplaceDisks,
72
    opcodes.OpFailoverInstance: cmdlib.LUFailoverInstance,
73
    opcodes.OpConnectConsole: cmdlib.LUConnectConsole,
74
    opcodes.OpQueryInstances: cmdlib.LUQueryInstances,
75
    opcodes.OpQueryInstanceData: cmdlib.LUQueryInstanceData,
76
    opcodes.OpSetInstanceParms: cmdlib.LUSetInstanceParms,
77
    # os lu
78
    opcodes.OpDiagnoseOS: cmdlib.LUDiagnoseOS,
79
    # exports lu
80
    opcodes.OpQueryExports: cmdlib.LUQueryExports,
81
    opcodes.OpExportInstance: cmdlib.LUExportInstance,
82
    # tags lu
83
    opcodes.OpGetTags: cmdlib.LUGetTags,
84
    opcodes.OpSearchTags: cmdlib.LUSearchTags,
85
    opcodes.OpAddTags: cmdlib.LUAddTags,
86
    opcodes.OpDelTags: cmdlib.LUDelTags,
87
    }
88

    
89
  def __init__(self, feedback=None):
90
    """Constructor for Processor
91

92
    Args:
93
     - feedback_fn: the feedback function (taking one string) to be run when
94
                    interesting events are happening
95
    """
96
    self.cfg = None
97
    self.sstore = None
98
    self._feedback_fn = feedback
99

    
100
  def ExecOpCode(self, op):
101
    """Execute an opcode.
102

103
    Args:
104
     - cfg: the configuration in which we execute this opcode
105
     - opcode: the opcode to be executed
106

107
    """
108
    if not isinstance(op, opcodes.OpCode):
109
      raise errors.ProgrammerError("Non-opcode instance passed"
110
                                   " to ExecOpcode")
111

    
112
    lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
113
    if lu_class is None:
114
      raise errors.OpCodeUnknown("Unknown opcode")
115

    
116
    if lu_class.REQ_CLUSTER and self.cfg is None:
117
      self.cfg = config.ConfigWriter()
118
      self.sstore = ssconf.SimpleStore()
119
    if self.cfg is not None:
120
      write_count = self.cfg.write_count
121
    else:
122
      write_count = 0
123
    lu = lu_class(self, op, self.cfg, self.sstore)
124
    lu.CheckPrereq()
125
    hm = HooksMaster(rpc.call_hooks_runner, lu)
126
    hm.RunPhase(constants.HOOKS_PHASE_PRE)
127
    result = lu.Exec(self._feedback_fn)
128
    hm.RunPhase(constants.HOOKS_PHASE_POST)
129
    if lu.cfg is not None:
130
      # we use lu.cfg and not self.cfg as for init cluster, self.cfg
131
      # is None but lu.cfg has been recently initialized in the
132
      # lu.Exec method
133
      if write_count != lu.cfg.write_count:
134
        hm.RunConfigUpdate()
135

    
136
    return result
137

    
138
  def ChainOpCode(self, op):
139
    """Chain and execute an opcode.
140

141
    This is used by LUs when they need to execute a child LU.
142

143
    Args:
144
     - opcode: the opcode to be executed
145

146
    """
147
    if not isinstance(op, opcodes.OpCode):
148
      raise errors.ProgrammerError("Non-opcode instance passed"
149
                                   " to ExecOpcode")
150

    
151
    lu_class = self.DISPATCH_TABLE.get(op.__class__, None)
152
    if lu_class is None:
153
      raise errors.OpCodeUnknown("Unknown opcode")
154

    
155
    if lu_class.REQ_CLUSTER and self.cfg is None:
156
      self.cfg = config.ConfigWriter()
157
      self.sstore = ssconf.SimpleStore()
158
    #do_hooks = lu_class.HPATH is not None
159
    lu = lu_class(self, op, self.cfg, self.sstore)
160
    lu.CheckPrereq()
161
    #if do_hooks:
162
    #  hm = HooksMaster(rpc.call_hooks_runner, lu)
163
    #  hm.RunPhase(constants.HOOKS_PHASE_PRE)
164
    result = lu.Exec(self._feedback_fn)
165
    #if do_hooks:
166
    #  hm.RunPhase(constants.HOOKS_PHASE_POST)
167
    return result
168

    
169
  def LogStep(self, current, total, message):
170
    """Log a change in LU execution progress.
171

172
    """
173
    logger.Debug("Step %d/%d %s" % (current, total, message))
174
    self._feedback_fn("STEP %d/%d %s" % (current, total, message))
175

    
176
  def LogWarning(self, message, hint):
177
    """Log a warning to the logs and the user.
178

179
    """
180
    logger.Error(message)
181
    self._feedback_fn(" - WARNING: %s" % message)
182
    self._feedback_fn("      Hint: %s" % hint)
183

    
184
  def LogInfo(self, message):
185
    """Log an informational message to the logs and the user.
186

187
    """
188
    logger.Info(message)
189
    self._feedback_fn(" - INFO: %s" % message)
190

    
191

    
192
class HooksMaster(object):
193
  """Hooks master.
194

195
  This class distributes the run commands to the nodes based on the
196
  specific LU class.
197

198
  In order to remove the direct dependency on the rpc module, the
199
  constructor needs a function which actually does the remote
200
  call. This will usually be rpc.call_hooks_runner, but any function
201
  which behaves the same works.
202

203
  """
204
  def __init__(self, callfn, lu):
205
    self.callfn = callfn
206
    self.lu = lu
207
    self.op = lu.op
208
    self.env, node_list_pre, node_list_post = self._BuildEnv()
209
    self.node_list = {constants.HOOKS_PHASE_PRE: node_list_pre,
210
                      constants.HOOKS_PHASE_POST: node_list_post}
211

    
212
  def _BuildEnv(self):
213
    """Compute the environment and the target nodes.
214

215
    Based on the opcode and the current node list, this builds the
216
    environment for the hooks and the target node list for the run.
217

218
    """
219
    env = {
220
      "PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
221
      "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
222
      "GANETI_OP_CODE": self.op.OP_ID,
223
      "GANETI_OBJECT_TYPE": self.lu.HTYPE,
224
      "GANETI_DATA_DIR": constants.DATA_DIR,
225
      }
226

    
227
    if self.lu.HPATH is not None:
228
      lu_env, lu_nodes_pre, lu_nodes_post = self.lu.BuildHooksEnv()
229
      if lu_env:
230
        for key in lu_env:
231
          env["GANETI_" + key] = lu_env[key]
232
    else:
233
      lu_nodes_pre = lu_nodes_post = []
234

    
235
    return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
236

    
237
  def _RunWrapper(self, node_list, hpath, phase):
238
    """Simple wrapper over self.callfn.
239

240
    This method fixes the environment before doing the rpc call.
241

242
    """
243
    env = self.env.copy()
244
    env["GANETI_HOOKS_PHASE"] = phase
245
    env["GANETI_HOOKS_PATH"] = hpath
246
    if self.lu.sstore is not None:
247
      env["GANETI_CLUSTER"] = self.lu.sstore.GetClusterName()
248
      env["GANETI_MASTER"] = self.lu.sstore.GetMasterNode()
249

    
250
    env = dict([(str(key), str(val)) for key, val in env.iteritems()])
251

    
252
    return self.callfn(node_list, hpath, phase, env)
253

    
254
  def RunPhase(self, phase):
255
    """Run all the scripts for a phase.
256

257
    This is the main function of the HookMaster.
258

259
    """
260
    if not self.node_list[phase]:
261
      # empty node list, we should not attempt to run this as either
262
      # we're in the cluster init phase and the rpc client part can't
263
      # even attempt to run, or this LU doesn't do hooks at all
264
      return
265
    hpath = self.lu.HPATH
266
    results = self._RunWrapper(self.node_list[phase], hpath, phase)
267
    if phase == constants.HOOKS_PHASE_PRE:
268
      errs = []
269
      if not results:
270
        raise errors.HooksFailure("Communication failure")
271
      for node_name in results:
272
        res = results[node_name]
273
        if res is False or not isinstance(res, list):
274
          raise errors.HooksFailure("Communication failure to node %s" %
275
                                    node_name)
276
        for script, hkr, output in res:
277
          if hkr == constants.HKR_FAIL:
278
            output = output.strip().encode("string_escape")
279
            errs.append((node_name, script, output))
280
      if errs:
281
        raise errors.HooksAbort(errs)
282

    
283
  def RunConfigUpdate(self):
284
    """Run the special configuration update hook
285

286
    This is a special hook that runs only on the master after each
287
    top-level LI if the configuration has been updated.
288

289
    """
290
    phase = constants.HOOKS_PHASE_POST
291
    hpath = constants.HOOKS_NAME_CFGUPDATE
292
    if self.lu.sstore is None:
293
      raise errors.ProgrammerError("Null sstore on config update hook")
294
    nodes = [self.lu.sstore.GetMasterNode()]
295
    results = self._RunWrapper(nodes, hpath, phase)