Statistics
| Branch: | Tag: | Revision:

root / lib / mcpu.py @ 249069a1

History | View | Annotate | Download (9.9 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

    
42
class Processor(object):
43
  """Object which runs OpCodes"""
44
  DISPATCH_TABLE = {
45
    # Cluster
46
    opcodes.OpInitCluster: cmdlib.LUInitCluster,
47
    opcodes.OpDestroyCluster: cmdlib.LUDestroyCluster,
48
    opcodes.OpQueryClusterInfo: cmdlib.LUQueryClusterInfo,
49
    opcodes.OpClusterCopyFile: cmdlib.LUClusterCopyFile,
50
    opcodes.OpRunClusterCommand: cmdlib.LURunClusterCommand,
51
    opcodes.OpVerifyCluster: cmdlib.LUVerifyCluster,
52
    opcodes.OpMasterFailover: cmdlib.LUMasterFailover,
53
    opcodes.OpDumpClusterConfig: cmdlib.LUDumpClusterConfig,
54
    opcodes.OpRenameCluster: cmdlib.LURenameCluster,
55
    opcodes.OpVerifyDisks: cmdlib.LUVerifyDisks,
56
    # node lu
57
    opcodes.OpAddNode: cmdlib.LUAddNode,
58
    opcodes.OpQueryNodes: cmdlib.LUQueryNodes,
59
    opcodes.OpQueryNodeVolumes: cmdlib.LUQueryNodeVolumes,
60
    opcodes.OpRemoveNode: cmdlib.LURemoveNode,
61
    # instance lu
62
    opcodes.OpCreateInstance: cmdlib.LUCreateInstance,
63
    opcodes.OpReinstallInstance: cmdlib.LUReinstallInstance,
64
    opcodes.OpRemoveInstance: cmdlib.LURemoveInstance,
65
    opcodes.OpRenameInstance: cmdlib.LURenameInstance,
66
    opcodes.OpActivateInstanceDisks: cmdlib.LUActivateInstanceDisks,
67
    opcodes.OpShutdownInstance: cmdlib.LUShutdownInstance,
68
    opcodes.OpStartupInstance: cmdlib.LUStartupInstance,
69
    opcodes.OpRebootInstance: cmdlib.LURebootInstance,
70
    opcodes.OpDeactivateInstanceDisks: cmdlib.LUDeactivateInstanceDisks,
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
    # test lu
88
    opcodes.OpTestDelay: cmdlib.LUTestDelay,
89
    }
90

    
91
  def __init__(self, feedback=None):
92
    """Constructor for Processor
93

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

    
102
  def ExecOpCode(self, op):
103
    """Execute an opcode.
104

105
    Args:
106
      op: the opcode to be executed
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
    if self.cfg is not None:
121
      write_count = self.cfg.write_count
122
    else:
123
      write_count = 0
124
    lu = lu_class(self, op, self.cfg, self.sstore)
125
    lu.CheckPrereq()
126
    hm = HooksMaster(rpc.call_hooks_runner, self, lu)
127
    hm.RunPhase(constants.HOOKS_PHASE_PRE)
128
    result = lu.Exec(self._feedback_fn)
129
    hm.RunPhase(constants.HOOKS_PHASE_POST)
130
    if lu.cfg is not None:
131
      # we use lu.cfg and not self.cfg as for init cluster, self.cfg
132
      # is None but lu.cfg has been recently initialized in the
133
      # lu.Exec method
134
      if write_count != lu.cfg.write_count:
135
        hm.RunConfigUpdate()
136

    
137
    return result
138

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

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

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

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

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

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

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

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

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

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

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

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

    
193

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

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

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

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

    
215
  def _BuildEnv(self):
216
    """Compute the environment and the target nodes.
217

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

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

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

    
238
    return env, frozenset(lu_nodes_pre), frozenset(lu_nodes_post)
239

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

243
    This method fixes the environment before doing the rpc call.
244

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

    
253
    env = dict([(str(key), str(val)) for key, val in env.iteritems()])
254

    
255
    return self.callfn(node_list, hpath, phase, env)
256

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

260
    This is the main function of the HookMaster.
261

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

    
286
  def RunConfigUpdate(self):
287
    """Run the special configuration update hook
288

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

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