Statistics
| Branch: | Tag: | Revision:

root / tools / cluster-merge @ e687ec01

History | View | Annotate | Download (29.3 kB)

1 1f7d3f7d René Nussbaumer
#!/usr/bin/python
2 1f7d3f7d René Nussbaumer
#
3 1f7d3f7d René Nussbaumer
4 3d8f154f Stephen Shirley
# Copyright (C) 2010 Google Inc.
5 1f7d3f7d René Nussbaumer
#
6 1f7d3f7d René Nussbaumer
# This program is free software; you can redistribute it and/or modify
7 1f7d3f7d René Nussbaumer
# it under the terms of the GNU General Public License as published by
8 1f7d3f7d René Nussbaumer
# the Free Software Foundation; either version 2 of the License, or
9 1f7d3f7d René Nussbaumer
# (at your option) any later version.
10 1f7d3f7d René Nussbaumer
#
11 1f7d3f7d René Nussbaumer
# This program is distributed in the hope that it will be useful, but
12 1f7d3f7d René Nussbaumer
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 1f7d3f7d René Nussbaumer
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 1f7d3f7d René Nussbaumer
# General Public License for more details.
15 1f7d3f7d René Nussbaumer
#
16 1f7d3f7d René Nussbaumer
# You should have received a copy of the GNU General Public License
17 1f7d3f7d René Nussbaumer
# along with this program; if not, write to the Free Software
18 1f7d3f7d René Nussbaumer
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 1f7d3f7d René Nussbaumer
# 02110-1301, USA.
20 1f7d3f7d René Nussbaumer
21 1f7d3f7d René Nussbaumer
"""Tool to merge two or more clusters together.
22 1f7d3f7d René Nussbaumer
23 1f7d3f7d René Nussbaumer
The clusters have to run the same version of Ganeti!
24 1f7d3f7d René Nussbaumer
25 1f7d3f7d René Nussbaumer
"""
26 1f7d3f7d René Nussbaumer
27 1f7d3f7d René Nussbaumer
# pylint: disable-msg=C0103
28 1f7d3f7d René Nussbaumer
# C0103: Invalid name cluster-merge
29 1f7d3f7d René Nussbaumer
30 1f7d3f7d René Nussbaumer
import logging
31 1f7d3f7d René Nussbaumer
import os
32 1f7d3f7d René Nussbaumer
import optparse
33 1f7d3f7d René Nussbaumer
import shutil
34 1f7d3f7d René Nussbaumer
import sys
35 1f7d3f7d René Nussbaumer
import tempfile
36 1f7d3f7d René Nussbaumer
37 1f7d3f7d René Nussbaumer
from ganeti import cli
38 1f7d3f7d René Nussbaumer
from ganeti import config
39 1f7d3f7d René Nussbaumer
from ganeti import constants
40 1f7d3f7d René Nussbaumer
from ganeti import errors
41 1f7d3f7d René Nussbaumer
from ganeti import ssh
42 1f7d3f7d René Nussbaumer
from ganeti import utils
43 1f7d3f7d René Nussbaumer
44 1f7d3f7d René Nussbaumer
45 1a615be0 Stephen Shirley
_GROUPS_MERGE = "merge"
46 1a615be0 Stephen Shirley
_GROUPS_RENAME = "rename"
47 1a615be0 Stephen Shirley
_CLUSTERMERGE_ECID = "clustermerge-ecid"
48 620a9c62 Guido Trotter
_RESTART_ALL = "all"
49 620a9c62 Guido Trotter
_RESTART_UP = "up"
50 620a9c62 Guido Trotter
_RESTART_NONE = "none"
51 620a9c62 Guido Trotter
_RESTART_CHOICES = (_RESTART_ALL, _RESTART_UP, _RESTART_NONE)
52 1fcd3b81 Guido Trotter
_PARAMS_STRICT = "strict"
53 1fcd3b81 Guido Trotter
_PARAMS_WARN = "warn"
54 1fcd3b81 Guido Trotter
_PARAMS_CHOICES = (_PARAMS_STRICT, _PARAMS_WARN)
55 620a9c62 Guido Trotter
56 1a615be0 Stephen Shirley
57 1f7d3f7d René Nussbaumer
PAUSE_PERIOD_OPT = cli.cli_option("-p", "--watcher-pause-period", default=1800,
58 1f7d3f7d René Nussbaumer
                                  action="store", type="int",
59 1f7d3f7d René Nussbaumer
                                  dest="pause_period",
60 1f7d3f7d René Nussbaumer
                                  help=("Amount of time in seconds watcher"
61 1f7d3f7d René Nussbaumer
                                        " should be suspended from running"))
62 1a615be0 Stephen Shirley
GROUPS_OPT = cli.cli_option("--groups", default=None, metavar="STRATEGY",
63 9b945588 Stephen Shirley
                            choices=(_GROUPS_MERGE, _GROUPS_RENAME),
64 9b945588 Stephen Shirley
                            dest="groups",
65 1a615be0 Stephen Shirley
                            help=("How to handle groups that have the"
66 1a615be0 Stephen Shirley
                                  " same name (One of: %s/%s)" %
67 1a615be0 Stephen Shirley
                                  (_GROUPS_MERGE, _GROUPS_RENAME)))
68 1fcd3b81 Guido Trotter
PARAMS_OPT = cli.cli_option("--parameter-conflicts", default=_PARAMS_STRICT,
69 1fcd3b81 Guido Trotter
                            metavar="STRATEGY",
70 1fcd3b81 Guido Trotter
                            choices=_PARAMS_CHOICES,
71 1fcd3b81 Guido Trotter
                            dest="params",
72 1fcd3b81 Guido Trotter
                            help=("How to handle params that have"
73 1fcd3b81 Guido Trotter
                                  " different values (One of: %s/%s)" %
74 1fcd3b81 Guido Trotter
                                  _PARAMS_CHOICES))
75 1fcd3b81 Guido Trotter
76 620a9c62 Guido Trotter
RESTART_OPT = cli.cli_option("--restart", default=_RESTART_ALL,
77 620a9c62 Guido Trotter
                             metavar="STRATEGY",
78 620a9c62 Guido Trotter
                             choices=_RESTART_CHOICES,
79 620a9c62 Guido Trotter
                             dest="restart",
80 620a9c62 Guido Trotter
                             help=("How to handle restarting instances"
81 620a9c62 Guido Trotter
                                   " same name (One of: %s/%s/%s)" %
82 620a9c62 Guido Trotter
                                   _RESTART_CHOICES))
83 1f7d3f7d René Nussbaumer
84 b7bf8b58 Guido Trotter
SKIP_STOP_INSTANCES_OPT = \
85 b7bf8b58 Guido Trotter
  cli.cli_option("--skip-stop-instances", default=True, action="store_false",
86 b7bf8b58 Guido Trotter
                 dest="stop_instances",
87 b7bf8b58 Guido Trotter
                 help=("Don't stop the instances on the clusters, just check "
88 b7bf8b58 Guido Trotter
                       "that none is running"))
89 1fb5f905 Andrea Spadaccini
90 1f7d3f7d René Nussbaumer
91 71bbe910 Stephen Shirley
def Flatten(unflattened_list):
92 1f7d3f7d René Nussbaumer
  """Flattens a list.
93 1f7d3f7d René Nussbaumer
94 71bbe910 Stephen Shirley
  @param unflattened_list: A list of unflattened list objects.
95 71bbe910 Stephen Shirley
  @return: A flattened list
96 1f7d3f7d René Nussbaumer
97 1f7d3f7d René Nussbaumer
  """
98 71bbe910 Stephen Shirley
  flattened_list = []
99 1f7d3f7d René Nussbaumer
100 71bbe910 Stephen Shirley
  for item in unflattened_list:
101 1f7d3f7d René Nussbaumer
    if isinstance(item, list):
102 71bbe910 Stephen Shirley
      flattened_list.extend(Flatten(item))
103 1f7d3f7d René Nussbaumer
    else:
104 71bbe910 Stephen Shirley
      flattened_list.append(item)
105 71bbe910 Stephen Shirley
  return flattened_list
106 1f7d3f7d René Nussbaumer
107 1f7d3f7d René Nussbaumer
108 1f7d3f7d René Nussbaumer
class MergerData(object):
109 1f7d3f7d René Nussbaumer
  """Container class to hold data used for merger.
110 1f7d3f7d René Nussbaumer
111 1f7d3f7d René Nussbaumer
  """
112 1f7d3f7d René Nussbaumer
  def __init__(self, cluster, key_path, nodes, instances, config_path=None):
113 1f7d3f7d René Nussbaumer
    """Initialize the container.
114 1f7d3f7d René Nussbaumer
115 1f7d3f7d René Nussbaumer
    @param cluster: The name of the cluster
116 1f7d3f7d René Nussbaumer
    @param key_path: Path to the ssh private key used for authentication
117 8697f0fa Guido Trotter
    @param nodes: List of online nodes in the merging cluster
118 1f7d3f7d René Nussbaumer
    @param instances: List of instances running on merging cluster
119 3d2e7a27 Stephen Shirley
    @param config_path: Path to the merging cluster config
120 1f7d3f7d René Nussbaumer
121 1f7d3f7d René Nussbaumer
    """
122 1f7d3f7d René Nussbaumer
    self.cluster = cluster
123 1f7d3f7d René Nussbaumer
    self.key_path = key_path
124 1f7d3f7d René Nussbaumer
    self.nodes = nodes
125 3d2e7a27 Stephen Shirley
    self.instances = instances
126 3d2e7a27 Stephen Shirley
    self.config_path = config_path
127 1f7d3f7d René Nussbaumer
128 1f7d3f7d René Nussbaumer
129 1f7d3f7d René Nussbaumer
class Merger(object):
130 1f7d3f7d René Nussbaumer
  """Handling the merge.
131 1f7d3f7d René Nussbaumer
132 1f7d3f7d René Nussbaumer
  """
133 1fb5f905 Andrea Spadaccini
  RUNNING_STATUSES = frozenset([
134 1fb5f905 Andrea Spadaccini
    constants.INSTST_RUNNING,
135 1fb5f905 Andrea Spadaccini
    constants.INSTST_ERRORUP,
136 1fb5f905 Andrea Spadaccini
    ])
137 e687ec01 Michael Hanselmann
138 1fb5f905 Andrea Spadaccini
  def __init__(self, clusters, pause_period, groups, restart, params,
139 1fb5f905 Andrea Spadaccini
               stop_instances):
140 1f7d3f7d René Nussbaumer
    """Initialize object with sane defaults and infos required.
141 1f7d3f7d René Nussbaumer
142 1f7d3f7d René Nussbaumer
    @param clusters: The list of clusters to merge in
143 1f7d3f7d René Nussbaumer
    @param pause_period: The time watcher shall be disabled for
144 1a615be0 Stephen Shirley
    @param groups: How to handle group conflicts
145 620a9c62 Guido Trotter
    @param restart: How to handle instance restart
146 1fb5f905 Andrea Spadaccini
    @param stop_instances: Indicates whether the instances must be stopped
147 1fb5f905 Andrea Spadaccini
                           (True) or if the Merger must only check if no
148 1fb5f905 Andrea Spadaccini
                           instances are running on the mergee clusters (False)
149 1f7d3f7d René Nussbaumer
150 1f7d3f7d René Nussbaumer
    """
151 1f7d3f7d René Nussbaumer
    self.merger_data = []
152 1f7d3f7d René Nussbaumer
    self.clusters = clusters
153 1f7d3f7d René Nussbaumer
    self.pause_period = pause_period
154 1f7d3f7d René Nussbaumer
    self.work_dir = tempfile.mkdtemp(suffix="cluster-merger")
155 be8aecab Stephen Shirley
    (self.cluster_name, ) = cli.GetClient().QueryConfigValues(["cluster_name"])
156 1f7d3f7d René Nussbaumer
    self.ssh_runner = ssh.SshRunner(self.cluster_name)
157 1a615be0 Stephen Shirley
    self.groups = groups
158 620a9c62 Guido Trotter
    self.restart = restart
159 1fcd3b81 Guido Trotter
    self.params = params
160 1fb5f905 Andrea Spadaccini
    self.stop_instances = stop_instances
161 620a9c62 Guido Trotter
    if self.restart == _RESTART_UP:
162 620a9c62 Guido Trotter
      raise NotImplementedError
163 620a9c62 Guido Trotter
164 1f7d3f7d René Nussbaumer
  def Setup(self):
165 1f7d3f7d René Nussbaumer
    """Sets up our end so we can do the merger.
166 1f7d3f7d René Nussbaumer
167 1f7d3f7d René Nussbaumer
    This method is setting us up as a preparation for the merger.
168 1f7d3f7d René Nussbaumer
    It makes the initial contact and gathers information needed.
169 1f7d3f7d René Nussbaumer
170 1f7d3f7d René Nussbaumer
    @raise errors.RemoteError: for errors in communication/grabbing
171 1f7d3f7d René Nussbaumer
172 1f7d3f7d René Nussbaumer
    """
173 1f7d3f7d René Nussbaumer
    (remote_path, _, _) = ssh.GetUserFiles("root")
174 1f7d3f7d René Nussbaumer
175 be8aecab Stephen Shirley
    if self.cluster_name in self.clusters:
176 be8aecab Stephen Shirley
      raise errors.CommandError("Cannot merge cluster %s with itself" %
177 be8aecab Stephen Shirley
                                self.cluster_name)
178 be8aecab Stephen Shirley
179 1f7d3f7d René Nussbaumer
    # Fetch remotes private key
180 1f7d3f7d René Nussbaumer
    for cluster in self.clusters:
181 1f7d3f7d René Nussbaumer
      result = self._RunCmd(cluster, "cat %s" % remote_path, batch=False,
182 1f7d3f7d René Nussbaumer
                            ask_key=False)
183 1f7d3f7d René Nussbaumer
      if result.failed:
184 1f7d3f7d René Nussbaumer
        raise errors.RemoteError("There was an error while grabbing ssh private"
185 1f7d3f7d René Nussbaumer
                                 " key from %s. Fail reason: %s; output: %s" %
186 1f7d3f7d René Nussbaumer
                                 (cluster, result.fail_reason, result.output))
187 1f7d3f7d René Nussbaumer
188 c4feafe8 Iustin Pop
      key_path = utils.PathJoin(self.work_dir, cluster)
189 1f7d3f7d René Nussbaumer
      utils.WriteFile(key_path, mode=0600, data=result.stdout)
190 1f7d3f7d René Nussbaumer
191 8697f0fa Guido Trotter
      result = self._RunCmd(cluster, "gnt-node list -o name,offline"
192 8697f0fa Guido Trotter
                            " --no-header --separator=,", private_key=key_path)
193 1f7d3f7d René Nussbaumer
      if result.failed:
194 1f7d3f7d René Nussbaumer
        raise errors.RemoteError("Unable to retrieve list of nodes from %s."
195 1f7d3f7d René Nussbaumer
                                 " Fail reason: %s; output: %s" %
196 1f7d3f7d René Nussbaumer
                                 (cluster, result.fail_reason, result.output))
197 8697f0fa Guido Trotter
      nodes_statuses = [line.split(',') for line in result.stdout.splitlines()]
198 8697f0fa Guido Trotter
      nodes = [node_status[0] for node_status in nodes_statuses
199 8697f0fa Guido Trotter
               if node_status[1] == "N"]
200 1f7d3f7d René Nussbaumer
201 1f7d3f7d René Nussbaumer
      result = self._RunCmd(cluster, "gnt-instance list -o name --no-header",
202 1f7d3f7d René Nussbaumer
                            private_key=key_path)
203 1f7d3f7d René Nussbaumer
      if result.failed:
204 1f7d3f7d René Nussbaumer
        raise errors.RemoteError("Unable to retrieve list of instances from"
205 1f7d3f7d René Nussbaumer
                                 " %s. Fail reason: %s; output: %s" %
206 1f7d3f7d René Nussbaumer
                                 (cluster, result.fail_reason, result.output))
207 1f7d3f7d René Nussbaumer
      instances = result.stdout.splitlines()
208 1f7d3f7d René Nussbaumer
209 1f7d3f7d René Nussbaumer
      self.merger_data.append(MergerData(cluster, key_path, nodes, instances))
210 1f7d3f7d René Nussbaumer
211 8864d152 Guido Trotter
  def _PrepareAuthorizedKeys(self):
212 8864d152 Guido Trotter
    """Prepare the authorized_keys on every merging node.
213 1f7d3f7d René Nussbaumer
214 1f7d3f7d René Nussbaumer
    This method add our public key to remotes authorized_key for further
215 1f7d3f7d René Nussbaumer
    communication.
216 1f7d3f7d René Nussbaumer
217 1f7d3f7d René Nussbaumer
    """
218 1f7d3f7d René Nussbaumer
    (_, pub_key_file, auth_keys) = ssh.GetUserFiles("root")
219 1f7d3f7d René Nussbaumer
    pub_key = utils.ReadFile(pub_key_file)
220 1f7d3f7d René Nussbaumer
221 8864d152 Guido Trotter
    for data in self.merger_data:
222 8864d152 Guido Trotter
      for node in data.nodes:
223 8864d152 Guido Trotter
        result = self._RunCmd(node, ("cat >> %s << '!EOF.'\n%s!EOF.\n" %
224 8864d152 Guido Trotter
                                     (auth_keys, pub_key)),
225 503cc75f Guido Trotter
                              private_key=data.key_path, max_attempts=3)
226 1f7d3f7d René Nussbaumer
227 8864d152 Guido Trotter
        if result.failed:
228 8864d152 Guido Trotter
          raise errors.RemoteError("Unable to add our public key to %s in %s."
229 8864d152 Guido Trotter
                                   " Fail reason: %s; output: %s" %
230 8864d152 Guido Trotter
                                   (node, data.cluster, result.fail_reason,
231 8864d152 Guido Trotter
                                    result.output))
232 1f7d3f7d René Nussbaumer
233 1f7d3f7d René Nussbaumer
  def _RunCmd(self, hostname, command, user="root", use_cluster_key=False,
234 1f7d3f7d René Nussbaumer
              strict_host_check=False, private_key=None, batch=True,
235 07ff0a78 Andrea Spadaccini
              ask_key=False, max_attempts=1):
236 1f7d3f7d René Nussbaumer
    """Wrapping SshRunner.Run with default parameters.
237 1f7d3f7d René Nussbaumer
238 454723b5 Iustin Pop
    For explanation of parameters see L{ganeti.ssh.SshRunner.Run}.
239 1f7d3f7d René Nussbaumer
240 1f7d3f7d René Nussbaumer
    """
241 07ff0a78 Andrea Spadaccini
    for _ in range(max_attempts):
242 07ff0a78 Andrea Spadaccini
      result = self.ssh_runner.Run(hostname=hostname, command=command,
243 07ff0a78 Andrea Spadaccini
                                 user=user, use_cluster_key=use_cluster_key,
244 07ff0a78 Andrea Spadaccini
                                 strict_host_check=strict_host_check,
245 07ff0a78 Andrea Spadaccini
                                 private_key=private_key, batch=batch,
246 07ff0a78 Andrea Spadaccini
                                 ask_key=ask_key)
247 07ff0a78 Andrea Spadaccini
      if not result.failed:
248 07ff0a78 Andrea Spadaccini
        break
249 07ff0a78 Andrea Spadaccini
250 07ff0a78 Andrea Spadaccini
    return result
251 1f7d3f7d René Nussbaumer
252 1fb5f905 Andrea Spadaccini
  def _CheckRunningInstances(self):
253 1fb5f905 Andrea Spadaccini
    """Checks if on the clusters to be merged there are running instances
254 1fb5f905 Andrea Spadaccini
255 1fb5f905 Andrea Spadaccini
    @rtype: boolean
256 1fb5f905 Andrea Spadaccini
    @return: True if there are running instances, False otherwise
257 1fb5f905 Andrea Spadaccini
258 1fb5f905 Andrea Spadaccini
    """
259 1fb5f905 Andrea Spadaccini
    for cluster in self.clusters:
260 1fb5f905 Andrea Spadaccini
      result = self._RunCmd(cluster, "gnt-instance list -o status")
261 006a51b3 Andrea Spadaccini
      if self.RUNNING_STATUSES.intersection(result.output.splitlines()):
262 1fb5f905 Andrea Spadaccini
        return True
263 1fb5f905 Andrea Spadaccini
264 1fb5f905 Andrea Spadaccini
    return False
265 1fb5f905 Andrea Spadaccini
266 1f7d3f7d René Nussbaumer
  def _StopMergingInstances(self):
267 1f7d3f7d René Nussbaumer
    """Stop instances on merging clusters.
268 1f7d3f7d René Nussbaumer
269 1f7d3f7d René Nussbaumer
    """
270 1f7d3f7d René Nussbaumer
    for cluster in self.clusters:
271 1f7d3f7d René Nussbaumer
      result = self._RunCmd(cluster, "gnt-instance shutdown --all"
272 1f7d3f7d René Nussbaumer
                                     " --force-multiple")
273 1f7d3f7d René Nussbaumer
274 1f7d3f7d René Nussbaumer
      if result.failed:
275 1f7d3f7d René Nussbaumer
        raise errors.RemoteError("Unable to stop instances on %s."
276 1f7d3f7d René Nussbaumer
                                 " Fail reason: %s; output: %s" %
277 1f7d3f7d René Nussbaumer
                                 (cluster, result.fail_reason, result.output))
278 1f7d3f7d René Nussbaumer
279 1f7d3f7d René Nussbaumer
  def _DisableWatcher(self):
280 1f7d3f7d René Nussbaumer
    """Disable watch on all merging clusters, including ourself.
281 1f7d3f7d René Nussbaumer
282 1f7d3f7d René Nussbaumer
    """
283 1f7d3f7d René Nussbaumer
    for cluster in ["localhost"] + self.clusters:
284 1f7d3f7d René Nussbaumer
      result = self._RunCmd(cluster, "gnt-cluster watcher pause %d" %
285 1f7d3f7d René Nussbaumer
                                     self.pause_period)
286 1f7d3f7d René Nussbaumer
287 1f7d3f7d René Nussbaumer
      if result.failed:
288 1f7d3f7d René Nussbaumer
        raise errors.RemoteError("Unable to pause watcher on %s."
289 1f7d3f7d René Nussbaumer
                                 " Fail reason: %s; output: %s" %
290 1f7d3f7d René Nussbaumer
                                 (cluster, result.fail_reason, result.output))
291 1f7d3f7d René Nussbaumer
292 1f7d3f7d René Nussbaumer
  def _StopDaemons(self):
293 1f7d3f7d René Nussbaumer
    """Stop all daemons on merging nodes.
294 1f7d3f7d René Nussbaumer
295 1f7d3f7d René Nussbaumer
    """
296 d8aab233 René Nussbaumer
    cmd = "%s stop-all" % constants.DAEMON_UTIL
297 1f7d3f7d René Nussbaumer
    for data in self.merger_data:
298 1f7d3f7d René Nussbaumer
      for node in data.nodes:
299 503cc75f Guido Trotter
        result = self._RunCmd(node, cmd, max_attempts=3)
300 1f7d3f7d René Nussbaumer
301 1f7d3f7d René Nussbaumer
        if result.failed:
302 1f7d3f7d René Nussbaumer
          raise errors.RemoteError("Unable to stop daemons on %s."
303 1f7d3f7d René Nussbaumer
                                   " Fail reason: %s; output: %s." %
304 1f7d3f7d René Nussbaumer
                                   (node, result.fail_reason, result.output))
305 1f7d3f7d René Nussbaumer
306 1f7d3f7d René Nussbaumer
  def _FetchRemoteConfig(self):
307 1f7d3f7d René Nussbaumer
    """Fetches and stores remote cluster config from the master.
308 1f7d3f7d René Nussbaumer
309 1f7d3f7d René Nussbaumer
    This step is needed before we can merge the config.
310 1f7d3f7d René Nussbaumer
311 1f7d3f7d René Nussbaumer
    """
312 1f7d3f7d René Nussbaumer
    for data in self.merger_data:
313 1f7d3f7d René Nussbaumer
      result = self._RunCmd(data.cluster, "cat %s" %
314 1f7d3f7d René Nussbaumer
                                          constants.CLUSTER_CONF_FILE)
315 1f7d3f7d René Nussbaumer
316 1f7d3f7d René Nussbaumer
      if result.failed:
317 1f7d3f7d René Nussbaumer
        raise errors.RemoteError("Unable to retrieve remote config on %s."
318 1f7d3f7d René Nussbaumer
                                 " Fail reason: %s; output %s" %
319 1f7d3f7d René Nussbaumer
                                 (data.cluster, result.fail_reason,
320 1f7d3f7d René Nussbaumer
                                  result.output))
321 1f7d3f7d René Nussbaumer
322 c4feafe8 Iustin Pop
      data.config_path = utils.PathJoin(self.work_dir, "%s_config.data" %
323 c4feafe8 Iustin Pop
                                        data.cluster)
324 1f7d3f7d René Nussbaumer
      utils.WriteFile(data.config_path, data=result.stdout)
325 1f7d3f7d René Nussbaumer
326 1f7d3f7d René Nussbaumer
  # R0201: Method could be a function
327 1f7d3f7d René Nussbaumer
  def _KillMasterDaemon(self): # pylint: disable-msg=R0201
328 1f7d3f7d René Nussbaumer
    """Kills the local master daemon.
329 1f7d3f7d René Nussbaumer
330 1f7d3f7d René Nussbaumer
    @raise errors.CommandError: If unable to kill
331 1f7d3f7d René Nussbaumer
332 1f7d3f7d René Nussbaumer
    """
333 1f7d3f7d René Nussbaumer
    result = utils.RunCmd([constants.DAEMON_UTIL, "stop-master"])
334 1f7d3f7d René Nussbaumer
    if result.failed:
335 1f7d3f7d René Nussbaumer
      raise errors.CommandError("Unable to stop master daemons."
336 1f7d3f7d René Nussbaumer
                                " Fail reason: %s; output: %s" %
337 1f7d3f7d René Nussbaumer
                                (result.fail_reason, result.output))
338 1f7d3f7d René Nussbaumer
339 1f7d3f7d René Nussbaumer
  def _MergeConfig(self):
340 1f7d3f7d René Nussbaumer
    """Merges all foreign config into our own config.
341 1f7d3f7d René Nussbaumer
342 1f7d3f7d René Nussbaumer
    """
343 1f7d3f7d René Nussbaumer
    my_config = config.ConfigWriter(offline=True)
344 1f7d3f7d René Nussbaumer
    fake_ec_id = 0 # Needs to be uniq over the whole config merge
345 1f7d3f7d René Nussbaumer
346 1f7d3f7d René Nussbaumer
    for data in self.merger_data:
347 e1ab08db Stephen Shirley
      other_config = config.ConfigWriter(data.config_path, accept_foreign=True)
348 a6c8fd10 Stephen Shirley
      self._MergeClusterConfigs(my_config, other_config)
349 8f44674f Stephen Shirley
      self._MergeNodeGroups(my_config, other_config)
350 1f7d3f7d René Nussbaumer
351 1f7d3f7d René Nussbaumer
      for node in other_config.GetNodeList():
352 1f7d3f7d René Nussbaumer
        node_info = other_config.GetNodeInfo(node)
353 3e22caed Guido Trotter
        # Offline the node, it will be reonlined later at node readd
354 3e22caed Guido Trotter
        node_info.master_candidate = False
355 3e22caed Guido Trotter
        node_info.drained = False
356 3e22caed Guido Trotter
        node_info.offline = True
357 8864d152 Guido Trotter
        my_config.AddNode(node_info, _CLUSTERMERGE_ECID + str(fake_ec_id))
358 1f7d3f7d René Nussbaumer
        fake_ec_id += 1
359 1f7d3f7d René Nussbaumer
360 1f7d3f7d René Nussbaumer
      for instance in other_config.GetInstanceList():
361 1f7d3f7d René Nussbaumer
        instance_info = other_config.GetInstanceInfo(instance)
362 1f7d3f7d René Nussbaumer
363 1f7d3f7d René Nussbaumer
        # Update the DRBD port assignments
364 1f7d3f7d René Nussbaumer
        # This is a little bit hackish
365 1f7d3f7d René Nussbaumer
        for dsk in instance_info.disks:
366 1f7d3f7d René Nussbaumer
          if dsk.dev_type in constants.LDS_DRBD:
367 1f7d3f7d René Nussbaumer
            port = my_config.AllocatePort()
368 1f7d3f7d René Nussbaumer
369 1f7d3f7d René Nussbaumer
            logical_id = list(dsk.logical_id)
370 1f7d3f7d René Nussbaumer
            logical_id[2] = port
371 1f7d3f7d René Nussbaumer
            dsk.logical_id = tuple(logical_id)
372 1f7d3f7d René Nussbaumer
373 1f7d3f7d René Nussbaumer
            physical_id = list(dsk.physical_id)
374 1f7d3f7d René Nussbaumer
            physical_id[1] = physical_id[3] = port
375 1f7d3f7d René Nussbaumer
            dsk.physical_id = tuple(physical_id)
376 1f7d3f7d René Nussbaumer
377 a536aaac Stephen Shirley
        my_config.AddInstance(instance_info,
378 a536aaac Stephen Shirley
                              _CLUSTERMERGE_ECID + str(fake_ec_id))
379 1f7d3f7d René Nussbaumer
        fake_ec_id += 1
380 1f7d3f7d René Nussbaumer
381 a6c8fd10 Stephen Shirley
  def _MergeClusterConfigs(self, my_config, other_config):
382 a6c8fd10 Stephen Shirley
    """Checks that all relevant cluster parameters are compatible
383 a6c8fd10 Stephen Shirley
384 a6c8fd10 Stephen Shirley
    """
385 a6c8fd10 Stephen Shirley
    my_cluster = my_config.GetClusterInfo()
386 a6c8fd10 Stephen Shirley
    other_cluster = other_config.GetClusterInfo()
387 a6c8fd10 Stephen Shirley
    err_count = 0
388 a6c8fd10 Stephen Shirley
389 a6c8fd10 Stephen Shirley
    #
390 a6c8fd10 Stephen Shirley
    # Generic checks
391 a6c8fd10 Stephen Shirley
    #
392 bb074298 Guido Trotter
    check_params = [
393 a6c8fd10 Stephen Shirley
      "beparams",
394 a6c8fd10 Stephen Shirley
      "default_iallocator",
395 a6c8fd10 Stephen Shirley
      "drbd_usermode_helper",
396 a6c8fd10 Stephen Shirley
      "hidden_os",
397 a6c8fd10 Stephen Shirley
      "maintain_node_health",
398 a6c8fd10 Stephen Shirley
      "master_netdev",
399 a6c8fd10 Stephen Shirley
      "ndparams",
400 a6c8fd10 Stephen Shirley
      "nicparams",
401 a6c8fd10 Stephen Shirley
      "primary_ip_family",
402 a6c8fd10 Stephen Shirley
      "tags",
403 a6c8fd10 Stephen Shirley
      "uid_pool",
404 bb074298 Guido Trotter
      ]
405 1fcd3b81 Guido Trotter
    check_params_strict = [
406 1fcd3b81 Guido Trotter
      "volume_group_name",
407 1fcd3b81 Guido Trotter
    ]
408 bb074298 Guido Trotter
    if constants.ENABLE_FILE_STORAGE:
409 1fcd3b81 Guido Trotter
      check_params_strict.append("file_storage_dir")
410 1fcd3b81 Guido Trotter
    if constants.ENABLE_SHARED_FILE_STORAGE:
411 1fcd3b81 Guido Trotter
      check_params_strict.append("shared_file_storage_dir")
412 1fcd3b81 Guido Trotter
    check_params.extend(check_params_strict)
413 1fcd3b81 Guido Trotter
414 1fcd3b81 Guido Trotter
    if self.params == _PARAMS_STRICT:
415 1fcd3b81 Guido Trotter
      params_strict = True
416 1fcd3b81 Guido Trotter
    else:
417 1fcd3b81 Guido Trotter
      params_strict = False
418 bb074298 Guido Trotter
419 a6c8fd10 Stephen Shirley
    for param_name in check_params:
420 a6c8fd10 Stephen Shirley
      my_param = getattr(my_cluster, param_name)
421 a6c8fd10 Stephen Shirley
      other_param = getattr(other_cluster, param_name)
422 a6c8fd10 Stephen Shirley
      if my_param != other_param:
423 a6c8fd10 Stephen Shirley
        logging.error("The value (%s) of the cluster parameter %s on %s"
424 a6c8fd10 Stephen Shirley
                      " differs to this cluster's value (%s)",
425 a6c8fd10 Stephen Shirley
                      other_param, param_name, other_cluster.cluster_name,
426 a6c8fd10 Stephen Shirley
                      my_param)
427 1fcd3b81 Guido Trotter
        if params_strict or param_name in check_params_strict:
428 1fcd3b81 Guido Trotter
          err_count += 1
429 a6c8fd10 Stephen Shirley
430 a6c8fd10 Stephen Shirley
    #
431 a6c8fd10 Stephen Shirley
    # Custom checks
432 a6c8fd10 Stephen Shirley
    #
433 a6c8fd10 Stephen Shirley
434 a6c8fd10 Stephen Shirley
    # Check default hypervisor
435 a6c8fd10 Stephen Shirley
    my_defhyp = my_cluster.enabled_hypervisors[0]
436 a6c8fd10 Stephen Shirley
    other_defhyp = other_cluster.enabled_hypervisors[0]
437 a6c8fd10 Stephen Shirley
    if my_defhyp != other_defhyp:
438 a6c8fd10 Stephen Shirley
      logging.warning("The default hypervisor (%s) differs on %s, new"
439 a6c8fd10 Stephen Shirley
                      " instances will be created with this cluster's"
440 a6c8fd10 Stephen Shirley
                      " default hypervisor (%s)", other_defhyp,
441 a6c8fd10 Stephen Shirley
                      other_cluster.cluster_name, my_defhyp)
442 a6c8fd10 Stephen Shirley
443 a6c8fd10 Stephen Shirley
    if (set(my_cluster.enabled_hypervisors) !=
444 a6c8fd10 Stephen Shirley
        set(other_cluster.enabled_hypervisors)):
445 a6c8fd10 Stephen Shirley
      logging.error("The set of enabled hypervisors (%s) on %s differs to"
446 a6c8fd10 Stephen Shirley
                    " this cluster's set (%s)",
447 a6c8fd10 Stephen Shirley
                    other_cluster.enabled_hypervisors,
448 a6c8fd10 Stephen Shirley
                    other_cluster.cluster_name, my_cluster.enabled_hypervisors)
449 a6c8fd10 Stephen Shirley
      err_count += 1
450 a6c8fd10 Stephen Shirley
451 a6c8fd10 Stephen Shirley
    # Check hypervisor params for hypervisors we care about
452 a6c8fd10 Stephen Shirley
    for hyp in my_cluster.enabled_hypervisors:
453 a6c8fd10 Stephen Shirley
      for param in my_cluster.hvparams[hyp]:
454 a6c8fd10 Stephen Shirley
        my_value = my_cluster.hvparams[hyp][param]
455 a6c8fd10 Stephen Shirley
        other_value = other_cluster.hvparams[hyp][param]
456 a6c8fd10 Stephen Shirley
        if my_value != other_value:
457 a6c8fd10 Stephen Shirley
          logging.error("The value (%s) of the %s parameter of the %s"
458 a6c8fd10 Stephen Shirley
                        " hypervisor on %s differs to this cluster's parameter"
459 a6c8fd10 Stephen Shirley
                        " (%s)",
460 a6c8fd10 Stephen Shirley
                        other_value, param, hyp, other_cluster.cluster_name,
461 a6c8fd10 Stephen Shirley
                        my_value)
462 1fcd3b81 Guido Trotter
          if params_strict:
463 1fcd3b81 Guido Trotter
            err_count += 1
464 a6c8fd10 Stephen Shirley
465 a6c8fd10 Stephen Shirley
    # Check os hypervisor params for hypervisors we care about
466 a6c8fd10 Stephen Shirley
    for os_name in set(my_cluster.os_hvp.keys() + other_cluster.os_hvp.keys()):
467 a6c8fd10 Stephen Shirley
      for hyp in my_cluster.enabled_hypervisors:
468 a6c8fd10 Stephen Shirley
        my_os_hvp = self._GetOsHypervisor(my_cluster, os_name, hyp)
469 a6c8fd10 Stephen Shirley
        other_os_hvp = self._GetOsHypervisor(other_cluster, os_name, hyp)
470 a6c8fd10 Stephen Shirley
        if my_os_hvp != other_os_hvp:
471 a6c8fd10 Stephen Shirley
          logging.error("The OS parameters (%s) for the %s OS for the %s"
472 a6c8fd10 Stephen Shirley
                        " hypervisor on %s differs to this cluster's parameters"
473 a6c8fd10 Stephen Shirley
                        " (%s)",
474 a6c8fd10 Stephen Shirley
                        other_os_hvp, os_name, hyp, other_cluster.cluster_name,
475 a6c8fd10 Stephen Shirley
                        my_os_hvp)
476 1fcd3b81 Guido Trotter
          if params_strict:
477 1fcd3b81 Guido Trotter
            err_count += 1
478 a6c8fd10 Stephen Shirley
479 a6c8fd10 Stephen Shirley
    #
480 a6c8fd10 Stephen Shirley
    # Warnings
481 a6c8fd10 Stephen Shirley
    #
482 a6c8fd10 Stephen Shirley
    if my_cluster.modify_etc_hosts != other_cluster.modify_etc_hosts:
483 a6c8fd10 Stephen Shirley
      logging.warning("The modify_etc_hosts value (%s) differs on %s,"
484 a6c8fd10 Stephen Shirley
                      " this cluster's value (%s) will take precedence",
485 a6c8fd10 Stephen Shirley
                      other_cluster.modify_etc_hosts,
486 a6c8fd10 Stephen Shirley
                      other_cluster.cluster_name,
487 a6c8fd10 Stephen Shirley
                      my_cluster.modify_etc_hosts)
488 a6c8fd10 Stephen Shirley
489 a6c8fd10 Stephen Shirley
    if my_cluster.modify_ssh_setup != other_cluster.modify_ssh_setup:
490 a6c8fd10 Stephen Shirley
      logging.warning("The modify_ssh_setup value (%s) differs on %s,"
491 a6c8fd10 Stephen Shirley
                      " this cluster's value (%s) will take precedence",
492 a6c8fd10 Stephen Shirley
                      other_cluster.modify_ssh_setup,
493 a6c8fd10 Stephen Shirley
                      other_cluster.cluster_name,
494 a6c8fd10 Stephen Shirley
                      my_cluster.modify_ssh_setup)
495 a6c8fd10 Stephen Shirley
496 a6c8fd10 Stephen Shirley
    #
497 a6c8fd10 Stephen Shirley
    # Actual merging
498 a6c8fd10 Stephen Shirley
    #
499 a6c8fd10 Stephen Shirley
    my_cluster.reserved_lvs = list(set(my_cluster.reserved_lvs +
500 a6c8fd10 Stephen Shirley
                                       other_cluster.reserved_lvs))
501 a6c8fd10 Stephen Shirley
502 a6c8fd10 Stephen Shirley
    if my_cluster.prealloc_wipe_disks != other_cluster.prealloc_wipe_disks:
503 a6c8fd10 Stephen Shirley
      logging.warning("The prealloc_wipe_disks value (%s) on %s differs to this"
504 a6c8fd10 Stephen Shirley
                      " cluster's value (%s). The least permissive value (%s)"
505 a6c8fd10 Stephen Shirley
                      " will be used", other_cluster.prealloc_wipe_disks,
506 a6c8fd10 Stephen Shirley
                      other_cluster.cluster_name,
507 a6c8fd10 Stephen Shirley
                      my_cluster.prealloc_wipe_disks, True)
508 a6c8fd10 Stephen Shirley
      my_cluster.prealloc_wipe_disks = True
509 a6c8fd10 Stephen Shirley
510 a6c8fd10 Stephen Shirley
    for os_, osparams in other_cluster.osparams.items():
511 a6c8fd10 Stephen Shirley
      if os_ not in my_cluster.osparams:
512 a6c8fd10 Stephen Shirley
        my_cluster.osparams[os_] = osparams
513 a6c8fd10 Stephen Shirley
      elif my_cluster.osparams[os_] != osparams:
514 a6c8fd10 Stephen Shirley
        logging.error("The OS parameters (%s) for the %s OS on %s differs to"
515 a6c8fd10 Stephen Shirley
                      " this cluster's parameters (%s)",
516 a6c8fd10 Stephen Shirley
                      osparams, os_, other_cluster.cluster_name,
517 a6c8fd10 Stephen Shirley
                      my_cluster.osparams[os_])
518 1fcd3b81 Guido Trotter
        if params_strict:
519 1fcd3b81 Guido Trotter
          err_count += 1
520 a6c8fd10 Stephen Shirley
521 a6c8fd10 Stephen Shirley
    if err_count:
522 a6c8fd10 Stephen Shirley
      raise errors.ConfigurationError("Cluster config for %s has incompatible"
523 a6c8fd10 Stephen Shirley
                                      " values, please fix and re-run" %
524 a6c8fd10 Stephen Shirley
                                      other_cluster.cluster_name)
525 a6c8fd10 Stephen Shirley
526 a6c8fd10 Stephen Shirley
  # R0201: Method could be a function
527 a6c8fd10 Stephen Shirley
  def _GetOsHypervisor(self, cluster, os_name, hyp): # pylint: disable-msg=R0201
528 a6c8fd10 Stephen Shirley
    if os_name in cluster.os_hvp:
529 a6c8fd10 Stephen Shirley
      return cluster.os_hvp[os_name].get(hyp, None)
530 a6c8fd10 Stephen Shirley
    else:
531 a6c8fd10 Stephen Shirley
      return None
532 a6c8fd10 Stephen Shirley
533 a6c8fd10 Stephen Shirley
  # R0201: Method could be a function
534 8f44674f Stephen Shirley
  def _MergeNodeGroups(self, my_config, other_config):
535 8f44674f Stephen Shirley
    """Adds foreign node groups
536 8f44674f Stephen Shirley
537 8f44674f Stephen Shirley
    ConfigWriter.AddNodeGroup takes care of making sure there are no conflicts.
538 8f44674f Stephen Shirley
    """
539 8f44674f Stephen Shirley
    # pylint: disable-msg=R0201
540 9b945588 Stephen Shirley
    logging.info("Node group conflict strategy: %s", self.groups)
541 1a615be0 Stephen Shirley
542 1a615be0 Stephen Shirley
    my_grps = my_config.GetAllNodeGroupsInfo().values()
543 1a615be0 Stephen Shirley
    other_grps = other_config.GetAllNodeGroupsInfo().values()
544 1a615be0 Stephen Shirley
545 1a615be0 Stephen Shirley
    # Check for node group naming conflicts:
546 1a615be0 Stephen Shirley
    conflicts = []
547 1a615be0 Stephen Shirley
    for other_grp in other_grps:
548 1a615be0 Stephen Shirley
      for my_grp in my_grps:
549 1a615be0 Stephen Shirley
        if other_grp.name == my_grp.name:
550 1a615be0 Stephen Shirley
          conflicts.append(other_grp)
551 1a615be0 Stephen Shirley
552 1a615be0 Stephen Shirley
    if conflicts:
553 1a615be0 Stephen Shirley
      conflict_names = utils.CommaJoin([g.name for g in conflicts])
554 9b945588 Stephen Shirley
      logging.info("Node groups in both local and remote cluster: %s",
555 1a615be0 Stephen Shirley
                   conflict_names)
556 1a615be0 Stephen Shirley
557 1a615be0 Stephen Shirley
      # User hasn't specified how to handle conflicts
558 1a615be0 Stephen Shirley
      if not self.groups:
559 1a615be0 Stephen Shirley
        raise errors.CommandError("The following node group(s) are in both"
560 1a615be0 Stephen Shirley
                                  " clusters, and no merge strategy has been"
561 1a615be0 Stephen Shirley
                                  " supplied (see the --groups option): %s" %
562 1a615be0 Stephen Shirley
                                  conflict_names)
563 1a615be0 Stephen Shirley
564 1a615be0 Stephen Shirley
      # User wants to rename conflicts
565 3a969900 Stephen Shirley
      elif self.groups == _GROUPS_RENAME:
566 1a615be0 Stephen Shirley
        for grp in conflicts:
567 1a615be0 Stephen Shirley
          new_name = "%s-%s" % (grp.name, other_config.GetClusterName())
568 1a615be0 Stephen Shirley
          logging.info("Renaming remote node group from %s to %s"
569 9b945588 Stephen Shirley
                       " to resolve conflict", grp.name, new_name)
570 1a615be0 Stephen Shirley
          grp.name = new_name
571 1a615be0 Stephen Shirley
572 3a969900 Stephen Shirley
      # User wants to merge conflicting groups
573 2be7c4cc Guido Trotter
      elif self.groups == _GROUPS_MERGE:
574 3a969900 Stephen Shirley
        for other_grp in conflicts:
575 9b945588 Stephen Shirley
          logging.info("Merging local and remote '%s' groups", other_grp.name)
576 3a969900 Stephen Shirley
          for node_name in other_grp.members[:]:
577 3a969900 Stephen Shirley
            node = other_config.GetNodeInfo(node_name)
578 9b945588 Stephen Shirley
            # Access to a protected member of a client class
579 9b945588 Stephen Shirley
            # pylint: disable-msg=W0212
580 3a969900 Stephen Shirley
            other_config._UnlockedRemoveNodeFromGroup(node)
581 3a969900 Stephen Shirley
582 9b945588 Stephen Shirley
            # Access to a protected member of a client class
583 9b945588 Stephen Shirley
            # pylint: disable-msg=W0212
584 3a969900 Stephen Shirley
            my_grp_uuid = my_config._UnlockedLookupNodeGroup(other_grp.name)
585 9b945588 Stephen Shirley
586 9b945588 Stephen Shirley
            # Access to a protected member of a client class
587 9b945588 Stephen Shirley
            # pylint: disable-msg=W0212
588 3a969900 Stephen Shirley
            my_config._UnlockedAddNodeToGroup(node, my_grp_uuid)
589 3a969900 Stephen Shirley
            node.group = my_grp_uuid
590 3a969900 Stephen Shirley
          # Remove from list of groups to add
591 3a969900 Stephen Shirley
          other_grps.remove(other_grp)
592 3a969900 Stephen Shirley
593 1a615be0 Stephen Shirley
    for grp in other_grps:
594 8f44674f Stephen Shirley
      #TODO: handle node group conflicts
595 8f44674f Stephen Shirley
      my_config.AddNodeGroup(grp, _CLUSTERMERGE_ECID)
596 8f44674f Stephen Shirley
597 8f44674f Stephen Shirley
  # R0201: Method could be a function
598 1f7d3f7d René Nussbaumer
  def _StartMasterDaemon(self, no_vote=False): # pylint: disable-msg=R0201
599 1f7d3f7d René Nussbaumer
    """Starts the local master daemon.
600 1f7d3f7d René Nussbaumer
601 1f7d3f7d René Nussbaumer
    @param no_vote: Should the masterd started without voting? default: False
602 1f7d3f7d René Nussbaumer
    @raise errors.CommandError: If unable to start daemon.
603 1f7d3f7d René Nussbaumer
604 1f7d3f7d René Nussbaumer
    """
605 1f7d3f7d René Nussbaumer
    env = {}
606 1f7d3f7d René Nussbaumer
    if no_vote:
607 1f7d3f7d René Nussbaumer
      env["EXTRA_MASTERD_ARGS"] = "--no-voting --yes-do-it"
608 1f7d3f7d René Nussbaumer
609 1f7d3f7d René Nussbaumer
    result = utils.RunCmd([constants.DAEMON_UTIL, "start-master"], env=env)
610 1f7d3f7d René Nussbaumer
    if result.failed:
611 1f7d3f7d René Nussbaumer
      raise errors.CommandError("Couldn't start ganeti master."
612 1f7d3f7d René Nussbaumer
                                " Fail reason: %s; output: %s" %
613 1f7d3f7d René Nussbaumer
                                (result.fail_reason, result.output))
614 1f7d3f7d René Nussbaumer
615 1f7d3f7d René Nussbaumer
  def _ReaddMergedNodesAndRedist(self):
616 1f7d3f7d René Nussbaumer
    """Readds all merging nodes and make sure their config is up-to-date.
617 1f7d3f7d René Nussbaumer
618 1f7d3f7d René Nussbaumer
    @raise errors.CommandError: If anything fails.
619 1f7d3f7d René Nussbaumer
620 1f7d3f7d René Nussbaumer
    """
621 1f7d3f7d René Nussbaumer
    for data in self.merger_data:
622 1f7d3f7d René Nussbaumer
      for node in data.nodes:
623 1f7d3f7d René Nussbaumer
        result = utils.RunCmd(["gnt-node", "add", "--readd",
624 53991408 Stephen Shirley
                               "--no-ssh-key-check", "--force-join", node])
625 1f7d3f7d René Nussbaumer
        if result.failed:
626 d47319e2 Guido Trotter
          logging.error("%s failed to be readded. Reason: %s, output: %s",
627 d47319e2 Guido Trotter
                         node, result.fail_reason, result.output)
628 1f7d3f7d René Nussbaumer
629 1f7d3f7d René Nussbaumer
    result = utils.RunCmd(["gnt-cluster", "redist-conf"])
630 1f7d3f7d René Nussbaumer
    if result.failed:
631 1f7d3f7d René Nussbaumer
      raise errors.CommandError("Redistribution failed. Fail reason: %s;"
632 1f7d3f7d René Nussbaumer
                                " output: %s" % (result.fail_reason,
633 1f7d3f7d René Nussbaumer
                                                result.output))
634 1f7d3f7d René Nussbaumer
635 1f7d3f7d René Nussbaumer
  # R0201: Method could be a function
636 1f7d3f7d René Nussbaumer
  def _StartupAllInstances(self): # pylint: disable-msg=R0201
637 1f7d3f7d René Nussbaumer
    """Starts up all instances (locally).
638 1f7d3f7d René Nussbaumer
639 1f7d3f7d René Nussbaumer
    @raise errors.CommandError: If unable to start clusters
640 1f7d3f7d René Nussbaumer
641 1f7d3f7d René Nussbaumer
    """
642 1f7d3f7d René Nussbaumer
    result = utils.RunCmd(["gnt-instance", "startup", "--all",
643 1f7d3f7d René Nussbaumer
                           "--force-multiple"])
644 1f7d3f7d René Nussbaumer
    if result.failed:
645 1f7d3f7d René Nussbaumer
      raise errors.CommandError("Unable to start all instances."
646 1f7d3f7d René Nussbaumer
                                " Fail reason: %s; output: %s" %
647 1f7d3f7d René Nussbaumer
                                (result.fail_reason, result.output))
648 1f7d3f7d René Nussbaumer
649 1f7d3f7d René Nussbaumer
  # R0201: Method could be a function
650 cc2cccfc Guido Trotter
  # TODO: make this overridable, for some verify errors
651 1f7d3f7d René Nussbaumer
  def _VerifyCluster(self): # pylint: disable-msg=R0201
652 1f7d3f7d René Nussbaumer
    """Runs gnt-cluster verify to verify the health.
653 1f7d3f7d René Nussbaumer
654 1f7d3f7d René Nussbaumer
    @raise errors.ProgrammError: If cluster fails on verification
655 1f7d3f7d René Nussbaumer
656 1f7d3f7d René Nussbaumer
    """
657 1f7d3f7d René Nussbaumer
    result = utils.RunCmd(["gnt-cluster", "verify"])
658 1f7d3f7d René Nussbaumer
    if result.failed:
659 1f7d3f7d René Nussbaumer
      raise errors.CommandError("Verification of cluster failed."
660 1f7d3f7d René Nussbaumer
                                " Fail reason: %s; output: %s" %
661 1f7d3f7d René Nussbaumer
                                (result.fail_reason, result.output))
662 1f7d3f7d René Nussbaumer
663 1f7d3f7d René Nussbaumer
  def Merge(self):
664 1f7d3f7d René Nussbaumer
    """Does the actual merge.
665 1f7d3f7d René Nussbaumer
666 1f7d3f7d René Nussbaumer
    It runs all the steps in the right order and updates the user about steps
667 1f7d3f7d René Nussbaumer
    taken. Also it keeps track of rollback_steps to undo everything.
668 1f7d3f7d René Nussbaumer
669 1f7d3f7d René Nussbaumer
    """
670 1f7d3f7d René Nussbaumer
    rbsteps = []
671 1f7d3f7d René Nussbaumer
    try:
672 1f7d3f7d René Nussbaumer
      logging.info("Pre cluster verification")
673 1f7d3f7d René Nussbaumer
      self._VerifyCluster()
674 1f7d3f7d René Nussbaumer
675 8864d152 Guido Trotter
      logging.info("Prepare authorized_keys")
676 8864d152 Guido Trotter
      rbsteps.append("Remove our key from authorized_keys on nodes:"
677 8864d152 Guido Trotter
                     " %(nodes)s")
678 8864d152 Guido Trotter
      self._PrepareAuthorizedKeys()
679 8864d152 Guido Trotter
680 1f7d3f7d René Nussbaumer
      rbsteps.append("Start all instances again on the merging"
681 1f7d3f7d René Nussbaumer
                     " clusters: %(clusters)s")
682 1fb5f905 Andrea Spadaccini
      if self.stop_instances:
683 1fb5f905 Andrea Spadaccini
        logging.info("Stopping merging instances (takes a while)")
684 1fb5f905 Andrea Spadaccini
        self._StopMergingInstances()
685 1fb5f905 Andrea Spadaccini
      logging.info("Checking that no instances are running on the mergees")
686 1fb5f905 Andrea Spadaccini
      instances_running = self._CheckRunningInstances()
687 1fb5f905 Andrea Spadaccini
      if instances_running:
688 1fb5f905 Andrea Spadaccini
        raise errors.CommandError("Some instances are still running on the"
689 1fb5f905 Andrea Spadaccini
                                  " mergees")
690 1f7d3f7d René Nussbaumer
      logging.info("Disable watcher")
691 1f7d3f7d René Nussbaumer
      self._DisableWatcher()
692 1f7d3f7d René Nussbaumer
      logging.info("Stop daemons on merging nodes")
693 1f7d3f7d René Nussbaumer
      self._StopDaemons()
694 1f7d3f7d René Nussbaumer
      logging.info("Merging config")
695 1f7d3f7d René Nussbaumer
      self._FetchRemoteConfig()
696 d8aab233 René Nussbaumer
697 9c03a67a Stephen Shirley
      logging.info("Stopping master daemon")
698 9c03a67a Stephen Shirley
      self._KillMasterDaemon()
699 9c03a67a Stephen Shirley
700 9c03a67a Stephen Shirley
      rbsteps.append("Restore %s from another master candidate"
701 9c03a67a Stephen Shirley
                     " and restart master daemon" %
702 9c03a67a Stephen Shirley
                     constants.CLUSTER_CONF_FILE)
703 9c03a67a Stephen Shirley
      self._MergeConfig()
704 9c03a67a Stephen Shirley
      self._StartMasterDaemon(no_vote=True)
705 9c03a67a Stephen Shirley
706 9c03a67a Stephen Shirley
      # Point of no return, delete rbsteps
707 9c03a67a Stephen Shirley
      del rbsteps[:]
708 9c03a67a Stephen Shirley
709 9c03a67a Stephen Shirley
      logging.warning("We are at the point of no return. Merge can not easily"
710 9c03a67a Stephen Shirley
                      " be undone after this point.")
711 9c03a67a Stephen Shirley
      logging.info("Readd nodes")
712 9c03a67a Stephen Shirley
      self._ReaddMergedNodesAndRedist()
713 9c03a67a Stephen Shirley
714 9c03a67a Stephen Shirley
      logging.info("Merge done, restart master daemon normally")
715 9c03a67a Stephen Shirley
      self._KillMasterDaemon()
716 9c03a67a Stephen Shirley
      self._StartMasterDaemon()
717 d8aab233 René Nussbaumer
718 620a9c62 Guido Trotter
      if self.restart == _RESTART_ALL:
719 620a9c62 Guido Trotter
        logging.info("Starting instances again")
720 620a9c62 Guido Trotter
        self._StartupAllInstances()
721 620a9c62 Guido Trotter
      else:
722 620a9c62 Guido Trotter
        logging.info("Not starting instances again")
723 1f7d3f7d René Nussbaumer
      logging.info("Post cluster verification")
724 1f7d3f7d René Nussbaumer
      self._VerifyCluster()
725 1f7d3f7d René Nussbaumer
    except errors.GenericError, e:
726 1f7d3f7d René Nussbaumer
      logging.exception(e)
727 1f7d3f7d René Nussbaumer
728 1f7d3f7d René Nussbaumer
      if rbsteps:
729 1f7d3f7d René Nussbaumer
        nodes = Flatten([data.nodes for data in self.merger_data])
730 1f7d3f7d René Nussbaumer
        info = {
731 1f7d3f7d René Nussbaumer
          "clusters": self.clusters,
732 1f7d3f7d René Nussbaumer
          "nodes": nodes,
733 1f7d3f7d René Nussbaumer
          }
734 1f7d3f7d René Nussbaumer
        logging.critical("In order to rollback do the following:")
735 1f7d3f7d René Nussbaumer
        for step in rbsteps:
736 3f1cf151 René Nussbaumer
          logging.critical("  * %s", step % info)
737 1f7d3f7d René Nussbaumer
      else:
738 1f7d3f7d René Nussbaumer
        logging.critical("Nothing to rollback.")
739 1f7d3f7d René Nussbaumer
740 1f7d3f7d René Nussbaumer
      # TODO: Keep track of steps done for a flawless resume?
741 1f7d3f7d René Nussbaumer
742 1f7d3f7d René Nussbaumer
  def Cleanup(self):
743 1f7d3f7d René Nussbaumer
    """Clean up our environment.
744 1f7d3f7d René Nussbaumer
745 1f7d3f7d René Nussbaumer
    This cleans up remote private keys and configs and after that
746 1f7d3f7d René Nussbaumer
    deletes the temporary directory.
747 1f7d3f7d René Nussbaumer
748 1f7d3f7d René Nussbaumer
    """
749 1f7d3f7d René Nussbaumer
    shutil.rmtree(self.work_dir)
750 1f7d3f7d René Nussbaumer
751 1f7d3f7d René Nussbaumer
752 1f7d3f7d René Nussbaumer
def SetupLogging(options):
753 1f7d3f7d René Nussbaumer
  """Setting up logging infrastructure.
754 1f7d3f7d René Nussbaumer
755 1f7d3f7d René Nussbaumer
  @param options: Parsed command line options
756 1f7d3f7d René Nussbaumer
757 1f7d3f7d René Nussbaumer
  """
758 1f7d3f7d René Nussbaumer
  formatter = logging.Formatter("%(asctime)s: %(levelname)s %(message)s")
759 1f7d3f7d René Nussbaumer
760 1f7d3f7d René Nussbaumer
  stderr_handler = logging.StreamHandler()
761 1f7d3f7d René Nussbaumer
  stderr_handler.setFormatter(formatter)
762 1f7d3f7d René Nussbaumer
  if options.debug:
763 1f7d3f7d René Nussbaumer
    stderr_handler.setLevel(logging.NOTSET)
764 1f7d3f7d René Nussbaumer
  elif options.verbose:
765 1f7d3f7d René Nussbaumer
    stderr_handler.setLevel(logging.INFO)
766 1f7d3f7d René Nussbaumer
  else:
767 a6c8fd10 Stephen Shirley
    stderr_handler.setLevel(logging.WARNING)
768 1f7d3f7d René Nussbaumer
769 1f7d3f7d René Nussbaumer
  root_logger = logging.getLogger("")
770 1f7d3f7d René Nussbaumer
  root_logger.setLevel(logging.NOTSET)
771 1f7d3f7d René Nussbaumer
  root_logger.addHandler(stderr_handler)
772 1f7d3f7d René Nussbaumer
773 1f7d3f7d René Nussbaumer
774 1f7d3f7d René Nussbaumer
def main():
775 1f7d3f7d René Nussbaumer
  """Main routine.
776 1f7d3f7d René Nussbaumer
777 1f7d3f7d René Nussbaumer
  """
778 1f7d3f7d René Nussbaumer
  program = os.path.basename(sys.argv[0])
779 1f7d3f7d René Nussbaumer
780 d2b17e37 Guido Trotter
  parser = optparse.OptionParser(usage="%%prog [options...] <cluster...>",
781 d2b17e37 Guido Trotter
                                 prog=program)
782 1f7d3f7d René Nussbaumer
  parser.add_option(cli.DEBUG_OPT)
783 1f7d3f7d René Nussbaumer
  parser.add_option(cli.VERBOSE_OPT)
784 1f7d3f7d René Nussbaumer
  parser.add_option(PAUSE_PERIOD_OPT)
785 1a615be0 Stephen Shirley
  parser.add_option(GROUPS_OPT)
786 620a9c62 Guido Trotter
  parser.add_option(RESTART_OPT)
787 1fcd3b81 Guido Trotter
  parser.add_option(PARAMS_OPT)
788 1fb5f905 Andrea Spadaccini
  parser.add_option(SKIP_STOP_INSTANCES_OPT)
789 1f7d3f7d René Nussbaumer
790 1f7d3f7d René Nussbaumer
  (options, args) = parser.parse_args()
791 1f7d3f7d René Nussbaumer
792 1f7d3f7d René Nussbaumer
  SetupLogging(options)
793 1f7d3f7d René Nussbaumer
794 1f7d3f7d René Nussbaumer
  if not args:
795 1f7d3f7d René Nussbaumer
    parser.error("No clusters specified")
796 1f7d3f7d René Nussbaumer
797 1a615be0 Stephen Shirley
  cluster_merger = Merger(utils.UniqueSequence(args), options.pause_period,
798 1fb5f905 Andrea Spadaccini
                          options.groups, options.restart, options.params,
799 1fb5f905 Andrea Spadaccini
                          options.stop_instances)
800 1f7d3f7d René Nussbaumer
  try:
801 1f7d3f7d René Nussbaumer
    try:
802 1f7d3f7d René Nussbaumer
      cluster_merger.Setup()
803 1f7d3f7d René Nussbaumer
      cluster_merger.Merge()
804 1f7d3f7d René Nussbaumer
    except errors.GenericError, e:
805 1f7d3f7d René Nussbaumer
      logging.exception(e)
806 1f7d3f7d René Nussbaumer
      return constants.EXIT_FAILURE
807 1f7d3f7d René Nussbaumer
  finally:
808 1f7d3f7d René Nussbaumer
    cluster_merger.Cleanup()
809 1f7d3f7d René Nussbaumer
810 1f7d3f7d René Nussbaumer
  return constants.EXIT_SUCCESS
811 1f7d3f7d René Nussbaumer
812 1f7d3f7d René Nussbaumer
813 1f7d3f7d René Nussbaumer
if __name__ == "__main__":
814 1f7d3f7d René Nussbaumer
  sys.exit(main())