Statistics
| Branch: | Tag: | Revision:

root / tools / cluster-merge @ d72ff6c3

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