Statistics
| Branch: | Tag: | Revision:

root / tools / cluster-merge @ 4fe43605

History | View | Annotate | Download (30.1 kB)

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