Statistics
| Branch: | Tag: | Revision:

root / tools / cluster-merge @ 2be7c4cc

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