Statistics
| Branch: | Tag: | Revision:

root / lib / hooksmaster.py @ 83f54caa

History | View | Annotate | Download (10 kB)

1 68d95757 Guido Trotter
#
2 68d95757 Guido Trotter
#
3 68d95757 Guido Trotter
4 68d95757 Guido Trotter
# Copyright (C) 2006, 2007, 2011, 2012 Google Inc.
5 68d95757 Guido Trotter
#
6 68d95757 Guido Trotter
# This program is free software; you can redistribute it and/or modify
7 68d95757 Guido Trotter
# it under the terms of the GNU General Public License as published by
8 68d95757 Guido Trotter
# the Free Software Foundation; either version 2 of the License, or
9 68d95757 Guido Trotter
# (at your option) any later version.
10 68d95757 Guido Trotter
#
11 68d95757 Guido Trotter
# This program is distributed in the hope that it will be useful, but
12 68d95757 Guido Trotter
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 68d95757 Guido Trotter
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 68d95757 Guido Trotter
# General Public License for more details.
15 68d95757 Guido Trotter
#
16 68d95757 Guido Trotter
# You should have received a copy of the GNU General Public License
17 68d95757 Guido Trotter
# along with this program; if not, write to the Free Software
18 68d95757 Guido Trotter
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 68d95757 Guido Trotter
# 02110-1301, USA.
20 68d95757 Guido Trotter
21 68d95757 Guido Trotter
22 68d95757 Guido Trotter
"""Module implementing the logic for running hooks.
23 68d95757 Guido Trotter

24 68d95757 Guido Trotter
"""
25 68d95757 Guido Trotter
26 68d95757 Guido Trotter
from ganeti import constants
27 68d95757 Guido Trotter
from ganeti import errors
28 68d95757 Guido Trotter
from ganeti import utils
29 68d95757 Guido Trotter
from ganeti import compat
30 68d95757 Guido Trotter
from ganeti import pathutils
31 68d95757 Guido Trotter
32 68d95757 Guido Trotter
33 68d95757 Guido Trotter
def _RpcResultsToHooksResults(rpc_results):
34 68d95757 Guido Trotter
  """Function to convert RPC results to the format expected by HooksMaster.
35 68d95757 Guido Trotter

36 68d95757 Guido Trotter
  @type rpc_results: dict(node: L{rpc.RpcResult})
37 68d95757 Guido Trotter
  @param rpc_results: RPC results
38 68d95757 Guido Trotter
  @rtype: dict(node: (fail_msg, offline, hooks_results))
39 68d95757 Guido Trotter
  @return: RPC results unpacked according to the format expected by
40 68d95757 Guido Trotter
    L({hooksmaster.HooksMaster}
41 68d95757 Guido Trotter

42 68d95757 Guido Trotter
  """
43 68d95757 Guido Trotter
  return dict((node, (rpc_res.fail_msg, rpc_res.offline, rpc_res.payload))
44 68d95757 Guido Trotter
              for (node, rpc_res) in rpc_results.items())
45 68d95757 Guido Trotter
46 68d95757 Guido Trotter
47 68d95757 Guido Trotter
class HooksMaster(object):
48 68d95757 Guido Trotter
  def __init__(self, opcode, hooks_path, nodes, hooks_execution_fn,
49 237a833c Thomas Thrainer
               hooks_results_adapt_fn, build_env_fn, prepare_post_nodes_fn,
50 237a833c Thomas Thrainer
               log_fn, htype=None, cluster_name=None, master_name=None):
51 68d95757 Guido Trotter
    """Base class for hooks masters.
52 68d95757 Guido Trotter

53 68d95757 Guido Trotter
    This class invokes the execution of hooks according to the behaviour
54 68d95757 Guido Trotter
    specified by its parameters.
55 68d95757 Guido Trotter

56 68d95757 Guido Trotter
    @type opcode: string
57 68d95757 Guido Trotter
    @param opcode: opcode of the operation to which the hooks are tied
58 68d95757 Guido Trotter
    @type hooks_path: string
59 68d95757 Guido Trotter
    @param hooks_path: prefix of the hooks directories
60 68d95757 Guido Trotter
    @type nodes: 2-tuple of lists
61 68d95757 Guido Trotter
    @param nodes: 2-tuple of lists containing nodes on which pre-hooks must be
62 68d95757 Guido Trotter
      run and nodes on which post-hooks must be run
63 68d95757 Guido Trotter
    @type hooks_execution_fn: function that accepts the following parameters:
64 68d95757 Guido Trotter
      (node_list, hooks_path, phase, environment)
65 68d95757 Guido Trotter
    @param hooks_execution_fn: function that will execute the hooks; can be
66 68d95757 Guido Trotter
      None, indicating that no conversion is necessary.
67 68d95757 Guido Trotter
    @type hooks_results_adapt_fn: function
68 68d95757 Guido Trotter
    @param hooks_results_adapt_fn: function that will adapt the return value of
69 68d95757 Guido Trotter
      hooks_execution_fn to the format expected by RunPhase
70 68d95757 Guido Trotter
    @type build_env_fn: function that returns a dictionary having strings as
71 68d95757 Guido Trotter
      keys
72 68d95757 Guido Trotter
    @param build_env_fn: function that builds the environment for the hooks
73 237a833c Thomas Thrainer
    @type prepare_post_nodes_fn: function that take a list of node UUIDs and
74 237a833c Thomas Thrainer
      returns a list of node UUIDs
75 237a833c Thomas Thrainer
    @param prepare_post_nodes_fn: function that is invoked right before
76 237a833c Thomas Thrainer
      executing post hooks and can change the list of node UUIDs to run the post
77 237a833c Thomas Thrainer
      hooks on
78 68d95757 Guido Trotter
    @type log_fn: function that accepts a string
79 68d95757 Guido Trotter
    @param log_fn: logging function
80 68d95757 Guido Trotter
    @type htype: string or None
81 68d95757 Guido Trotter
    @param htype: None or one of L{constants.HTYPE_CLUSTER},
82 68d95757 Guido Trotter
     L{constants.HTYPE_NODE}, L{constants.HTYPE_INSTANCE}
83 68d95757 Guido Trotter
    @type cluster_name: string
84 68d95757 Guido Trotter
    @param cluster_name: name of the cluster
85 68d95757 Guido Trotter
    @type master_name: string
86 68d95757 Guido Trotter
    @param master_name: name of the master
87 68d95757 Guido Trotter

88 68d95757 Guido Trotter
    """
89 68d95757 Guido Trotter
    self.opcode = opcode
90 68d95757 Guido Trotter
    self.hooks_path = hooks_path
91 68d95757 Guido Trotter
    self.hooks_execution_fn = hooks_execution_fn
92 68d95757 Guido Trotter
    self.hooks_results_adapt_fn = hooks_results_adapt_fn
93 68d95757 Guido Trotter
    self.build_env_fn = build_env_fn
94 237a833c Thomas Thrainer
    self.prepare_post_nodes_fn = prepare_post_nodes_fn
95 68d95757 Guido Trotter
    self.log_fn = log_fn
96 68d95757 Guido Trotter
    self.htype = htype
97 68d95757 Guido Trotter
    self.cluster_name = cluster_name
98 68d95757 Guido Trotter
    self.master_name = master_name
99 68d95757 Guido Trotter
100 68d95757 Guido Trotter
    self.pre_env = self._BuildEnv(constants.HOOKS_PHASE_PRE)
101 68d95757 Guido Trotter
    (self.pre_nodes, self.post_nodes) = nodes
102 68d95757 Guido Trotter
103 68d95757 Guido Trotter
  def _BuildEnv(self, phase):
104 68d95757 Guido Trotter
    """Compute the environment and the target nodes.
105 68d95757 Guido Trotter

106 68d95757 Guido Trotter
    Based on the opcode and the current node list, this builds the
107 68d95757 Guido Trotter
    environment for the hooks and the target node list for the run.
108 68d95757 Guido Trotter

109 68d95757 Guido Trotter
    """
110 68d95757 Guido Trotter
    if phase == constants.HOOKS_PHASE_PRE:
111 68d95757 Guido Trotter
      prefix = "GANETI_"
112 68d95757 Guido Trotter
    elif phase == constants.HOOKS_PHASE_POST:
113 68d95757 Guido Trotter
      prefix = "GANETI_POST_"
114 68d95757 Guido Trotter
    else:
115 68d95757 Guido Trotter
      raise AssertionError("Unknown phase '%s'" % phase)
116 68d95757 Guido Trotter
117 68d95757 Guido Trotter
    env = {}
118 68d95757 Guido Trotter
119 68d95757 Guido Trotter
    if self.hooks_path is not None:
120 68d95757 Guido Trotter
      phase_env = self.build_env_fn()
121 68d95757 Guido Trotter
      if phase_env:
122 68d95757 Guido Trotter
        assert not compat.any(key.upper().startswith(prefix)
123 68d95757 Guido Trotter
                              for key in phase_env)
124 68d95757 Guido Trotter
        env.update(("%s%s" % (prefix, key), value)
125 68d95757 Guido Trotter
                   for (key, value) in phase_env.items())
126 68d95757 Guido Trotter
127 68d95757 Guido Trotter
    if phase == constants.HOOKS_PHASE_PRE:
128 68d95757 Guido Trotter
      assert compat.all((key.startswith("GANETI_") and
129 68d95757 Guido Trotter
                         not key.startswith("GANETI_POST_"))
130 68d95757 Guido Trotter
                        for key in env)
131 68d95757 Guido Trotter
132 68d95757 Guido Trotter
    elif phase == constants.HOOKS_PHASE_POST:
133 68d95757 Guido Trotter
      assert compat.all(key.startswith("GANETI_POST_") for key in env)
134 68d95757 Guido Trotter
      assert isinstance(self.pre_env, dict)
135 68d95757 Guido Trotter
136 68d95757 Guido Trotter
      # Merge with pre-phase environment
137 68d95757 Guido Trotter
      assert not compat.any(key.startswith("GANETI_POST_")
138 68d95757 Guido Trotter
                            for key in self.pre_env)
139 68d95757 Guido Trotter
      env.update(self.pre_env)
140 68d95757 Guido Trotter
    else:
141 68d95757 Guido Trotter
      raise AssertionError("Unknown phase '%s'" % phase)
142 68d95757 Guido Trotter
143 68d95757 Guido Trotter
    return env
144 68d95757 Guido Trotter
145 68d95757 Guido Trotter
  def _RunWrapper(self, node_list, hpath, phase, phase_env):
146 68d95757 Guido Trotter
    """Simple wrapper over self.callfn.
147 68d95757 Guido Trotter

148 68d95757 Guido Trotter
    This method fixes the environment before executing the hooks.
149 68d95757 Guido Trotter

150 68d95757 Guido Trotter
    """
151 68d95757 Guido Trotter
    env = {
152 68d95757 Guido Trotter
      "PATH": constants.HOOKS_PATH,
153 68d95757 Guido Trotter
      "GANETI_HOOKS_VERSION": constants.HOOKS_VERSION,
154 68d95757 Guido Trotter
      "GANETI_OP_CODE": self.opcode,
155 68d95757 Guido Trotter
      "GANETI_DATA_DIR": pathutils.DATA_DIR,
156 68d95757 Guido Trotter
      "GANETI_HOOKS_PHASE": phase,
157 68d95757 Guido Trotter
      "GANETI_HOOKS_PATH": hpath,
158 68d95757 Guido Trotter
      }
159 68d95757 Guido Trotter
160 68d95757 Guido Trotter
    if self.htype:
161 68d95757 Guido Trotter
      env["GANETI_OBJECT_TYPE"] = self.htype
162 68d95757 Guido Trotter
163 68d95757 Guido Trotter
    if self.cluster_name is not None:
164 68d95757 Guido Trotter
      env["GANETI_CLUSTER"] = self.cluster_name
165 68d95757 Guido Trotter
166 68d95757 Guido Trotter
    if self.master_name is not None:
167 68d95757 Guido Trotter
      env["GANETI_MASTER"] = self.master_name
168 68d95757 Guido Trotter
169 68d95757 Guido Trotter
    if phase_env:
170 68d95757 Guido Trotter
      env = utils.algo.JoinDisjointDicts(env, phase_env)
171 68d95757 Guido Trotter
172 68d95757 Guido Trotter
    # Convert everything to strings
173 68d95757 Guido Trotter
    env = dict([(str(key), str(val)) for key, val in env.iteritems()])
174 68d95757 Guido Trotter
175 68d95757 Guido Trotter
    assert compat.all(key == "PATH" or key.startswith("GANETI_")
176 68d95757 Guido Trotter
                      for key in env)
177 68d95757 Guido Trotter
178 68d95757 Guido Trotter
    return self.hooks_execution_fn(node_list, hpath, phase, env)
179 68d95757 Guido Trotter
180 1c3231aa Thomas Thrainer
  def RunPhase(self, phase, node_names=None):
181 68d95757 Guido Trotter
    """Run all the scripts for a phase.
182 68d95757 Guido Trotter

183 68d95757 Guido Trotter
    This is the main function of the HookMaster.
184 68d95757 Guido Trotter
    It executes self.hooks_execution_fn, and after running
185 1c3231aa Thomas Thrainer
    self.hooks_results_adapt_fn on its results it expects them to be in the
186 1c3231aa Thomas Thrainer
    form {node_name: (fail_msg, [(script, result, output), ...]}).
187 68d95757 Guido Trotter

188 68d95757 Guido Trotter
    @param phase: one of L{constants.HOOKS_PHASE_POST} or
189 68d95757 Guido Trotter
        L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
190 1c3231aa Thomas Thrainer
    @param node_names: overrides the predefined list of nodes for the given
191 1c3231aa Thomas Thrainer
        phase
192 68d95757 Guido Trotter
    @return: the processed results of the hooks multi-node rpc call
193 68d95757 Guido Trotter
    @raise errors.HooksFailure: on communication failure to the nodes
194 68d95757 Guido Trotter
    @raise errors.HooksAbort: on failure of one of the hooks
195 68d95757 Guido Trotter

196 68d95757 Guido Trotter
    """
197 68d95757 Guido Trotter
    if phase == constants.HOOKS_PHASE_PRE:
198 1c3231aa Thomas Thrainer
      if node_names is None:
199 1c3231aa Thomas Thrainer
        node_names = self.pre_nodes
200 68d95757 Guido Trotter
      env = self.pre_env
201 68d95757 Guido Trotter
    elif phase == constants.HOOKS_PHASE_POST:
202 1c3231aa Thomas Thrainer
      if node_names is None:
203 1c3231aa Thomas Thrainer
        node_names = self.post_nodes
204 237a833c Thomas Thrainer
        if node_names is not None and self.prepare_post_nodes_fn is not None:
205 237a833c Thomas Thrainer
          node_names = frozenset(self.prepare_post_nodes_fn(list(node_names)))
206 68d95757 Guido Trotter
      env = self._BuildEnv(phase)
207 68d95757 Guido Trotter
    else:
208 68d95757 Guido Trotter
      raise AssertionError("Unknown phase '%s'" % phase)
209 68d95757 Guido Trotter
210 1c3231aa Thomas Thrainer
    if not node_names:
211 68d95757 Guido Trotter
      # empty node list, we should not attempt to run this as either
212 68d95757 Guido Trotter
      # we're in the cluster init phase and the rpc client part can't
213 68d95757 Guido Trotter
      # even attempt to run, or this LU doesn't do hooks at all
214 68d95757 Guido Trotter
      return
215 68d95757 Guido Trotter
216 1c3231aa Thomas Thrainer
    results = self._RunWrapper(node_names, self.hooks_path, phase, env)
217 68d95757 Guido Trotter
    if not results:
218 68d95757 Guido Trotter
      msg = "Communication Failure"
219 68d95757 Guido Trotter
      if phase == constants.HOOKS_PHASE_PRE:
220 68d95757 Guido Trotter
        raise errors.HooksFailure(msg)
221 68d95757 Guido Trotter
      else:
222 68d95757 Guido Trotter
        self.log_fn(msg)
223 68d95757 Guido Trotter
        return results
224 68d95757 Guido Trotter
225 68d95757 Guido Trotter
    converted_res = results
226 68d95757 Guido Trotter
    if self.hooks_results_adapt_fn:
227 68d95757 Guido Trotter
      converted_res = self.hooks_results_adapt_fn(results)
228 68d95757 Guido Trotter
229 68d95757 Guido Trotter
    errs = []
230 68d95757 Guido Trotter
    for node_name, (fail_msg, offline, hooks_results) in converted_res.items():
231 68d95757 Guido Trotter
      if offline:
232 68d95757 Guido Trotter
        continue
233 68d95757 Guido Trotter
234 68d95757 Guido Trotter
      if fail_msg:
235 68d95757 Guido Trotter
        self.log_fn("Communication failure to node %s: %s", node_name, fail_msg)
236 68d95757 Guido Trotter
        continue
237 68d95757 Guido Trotter
238 68d95757 Guido Trotter
      for script, hkr, output in hooks_results:
239 68d95757 Guido Trotter
        if hkr == constants.HKR_FAIL:
240 68d95757 Guido Trotter
          if phase == constants.HOOKS_PHASE_PRE:
241 68d95757 Guido Trotter
            errs.append((node_name, script, output))
242 68d95757 Guido Trotter
          else:
243 68d95757 Guido Trotter
            if not output:
244 68d95757 Guido Trotter
              output = "(no output)"
245 68d95757 Guido Trotter
            self.log_fn("On %s script %s failed, output: %s" %
246 68d95757 Guido Trotter
                        (node_name, script, output))
247 68d95757 Guido Trotter
248 68d95757 Guido Trotter
    if errs and phase == constants.HOOKS_PHASE_PRE:
249 68d95757 Guido Trotter
      raise errors.HooksAbort(errs)
250 68d95757 Guido Trotter
251 68d95757 Guido Trotter
    return results
252 68d95757 Guido Trotter
253 68d95757 Guido Trotter
  def RunConfigUpdate(self):
254 68d95757 Guido Trotter
    """Run the special configuration update hook
255 68d95757 Guido Trotter

256 68d95757 Guido Trotter
    This is a special hook that runs only on the master after each
257 68d95757 Guido Trotter
    top-level LI if the configuration has been updated.
258 68d95757 Guido Trotter

259 68d95757 Guido Trotter
    """
260 68d95757 Guido Trotter
    phase = constants.HOOKS_PHASE_POST
261 68d95757 Guido Trotter
    hpath = constants.HOOKS_NAME_CFGUPDATE
262 68d95757 Guido Trotter
    nodes = [self.master_name]
263 68d95757 Guido Trotter
    self._RunWrapper(nodes, hpath, phase, self.pre_env)
264 68d95757 Guido Trotter
265 68d95757 Guido Trotter
  @staticmethod
266 68d95757 Guido Trotter
  def BuildFromLu(hooks_execution_fn, lu):
267 68d95757 Guido Trotter
    if lu.HPATH is None:
268 68d95757 Guido Trotter
      nodes = (None, None)
269 68d95757 Guido Trotter
    else:
270 1c3231aa Thomas Thrainer
      hooks_nodes = lu.BuildHooksNodes()
271 ff1c051b Thomas Thrainer
      if len(hooks_nodes) != 2:
272 1c3231aa Thomas Thrainer
        raise errors.ProgrammerError(
273 ff1c051b Thomas Thrainer
          "LogicalUnit.BuildHooksNodes must return a 2-tuple")
274 ff1c051b Thomas Thrainer
      nodes = (frozenset(hooks_nodes[0]), frozenset(hooks_nodes[1]))
275 68d95757 Guido Trotter
276 68d95757 Guido Trotter
    master_name = cluster_name = None
277 68d95757 Guido Trotter
    if lu.cfg:
278 1c3231aa Thomas Thrainer
      master_name = lu.cfg.GetMasterNodeName()
279 68d95757 Guido Trotter
      cluster_name = lu.cfg.GetClusterName()
280 68d95757 Guido Trotter
281 68d95757 Guido Trotter
    return HooksMaster(lu.op.OP_ID, lu.HPATH, nodes, hooks_execution_fn,
282 68d95757 Guido Trotter
                       _RpcResultsToHooksResults, lu.BuildHooksEnv,
283 237a833c Thomas Thrainer
                       lu.PreparePostHookNodes, lu.LogWarning, lu.HTYPE,
284 237a833c Thomas Thrainer
                       cluster_name, master_name)