Statistics
| Branch: | Tag: | Revision:

root / lib / mcpu.py @ 5bfac263

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=None):
177
    """Log a warning to the logs and the user.
178

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

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

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

    
192

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

258
    This is the main function of the HookMaster.
259

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

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

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

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