Statistics
| Branch: | Tag: | Revision:

root / tools / cluster-merge @ 1a732a74

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 1f7d3f7d René Nussbaumer
          if dsk.dev_type in constants.LDS_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 1f7d3f7d René Nussbaumer
            physical_id = list(dsk.physical_id)
404 1f7d3f7d René Nussbaumer
            physical_id[1] = physical_id[3] = port
405 1f7d3f7d René Nussbaumer
            dsk.physical_id = tuple(physical_id)
406 1f7d3f7d René Nussbaumer
407 a536aaac Stephen Shirley
        my_config.AddInstance(instance_info,
408 a536aaac Stephen Shirley
                              _CLUSTERMERGE_ECID + str(fake_ec_id))
409 1f7d3f7d René Nussbaumer
        fake_ec_id += 1
410 1f7d3f7d René Nussbaumer
411 a6c8fd10 Stephen Shirley
  def _MergeClusterConfigs(self, my_config, other_config):
412 a6c8fd10 Stephen Shirley
    """Checks that all relevant cluster parameters are compatible
413 a6c8fd10 Stephen Shirley
414 a6c8fd10 Stephen Shirley
    """
415 a6c8fd10 Stephen Shirley
    my_cluster = my_config.GetClusterInfo()
416 a6c8fd10 Stephen Shirley
    other_cluster = other_config.GetClusterInfo()
417 a6c8fd10 Stephen Shirley
    err_count = 0
418 a6c8fd10 Stephen Shirley
419 a6c8fd10 Stephen Shirley
    #
420 a6c8fd10 Stephen Shirley
    # Generic checks
421 a6c8fd10 Stephen Shirley
    #
422 bb074298 Guido Trotter
    check_params = [
423 a6c8fd10 Stephen Shirley
      "beparams",
424 a6c8fd10 Stephen Shirley
      "default_iallocator",
425 a6c8fd10 Stephen Shirley
      "drbd_usermode_helper",
426 a6c8fd10 Stephen Shirley
      "hidden_os",
427 a6c8fd10 Stephen Shirley
      "maintain_node_health",
428 a6c8fd10 Stephen Shirley
      "master_netdev",
429 a6c8fd10 Stephen Shirley
      "ndparams",
430 a6c8fd10 Stephen Shirley
      "nicparams",
431 a6c8fd10 Stephen Shirley
      "primary_ip_family",
432 a6c8fd10 Stephen Shirley
      "tags",
433 a6c8fd10 Stephen Shirley
      "uid_pool",
434 bb074298 Guido Trotter
      ]
435 1fcd3b81 Guido Trotter
    check_params_strict = [
436 1fcd3b81 Guido Trotter
      "volume_group_name",
437 1fcd3b81 Guido Trotter
    ]
438 bb074298 Guido Trotter
    if constants.ENABLE_FILE_STORAGE:
439 1fcd3b81 Guido Trotter
      check_params_strict.append("file_storage_dir")
440 1fcd3b81 Guido Trotter
    if constants.ENABLE_SHARED_FILE_STORAGE:
441 1fcd3b81 Guido Trotter
      check_params_strict.append("shared_file_storage_dir")
442 1fcd3b81 Guido Trotter
    check_params.extend(check_params_strict)
443 1fcd3b81 Guido Trotter
444 1fcd3b81 Guido Trotter
    if self.params == _PARAMS_STRICT:
445 1fcd3b81 Guido Trotter
      params_strict = True
446 1fcd3b81 Guido Trotter
    else:
447 1fcd3b81 Guido Trotter
      params_strict = False
448 bb074298 Guido Trotter
449 a6c8fd10 Stephen Shirley
    for param_name in check_params:
450 a6c8fd10 Stephen Shirley
      my_param = getattr(my_cluster, param_name)
451 a6c8fd10 Stephen Shirley
      other_param = getattr(other_cluster, param_name)
452 a6c8fd10 Stephen Shirley
      if my_param != other_param:
453 a6c8fd10 Stephen Shirley
        logging.error("The value (%s) of the cluster parameter %s on %s"
454 a6c8fd10 Stephen Shirley
                      " differs to this cluster's value (%s)",
455 a6c8fd10 Stephen Shirley
                      other_param, param_name, other_cluster.cluster_name,
456 a6c8fd10 Stephen Shirley
                      my_param)
457 1fcd3b81 Guido Trotter
        if params_strict or param_name in check_params_strict:
458 1fcd3b81 Guido Trotter
          err_count += 1
459 a6c8fd10 Stephen Shirley
460 a6c8fd10 Stephen Shirley
    #
461 a6c8fd10 Stephen Shirley
    # Custom checks
462 a6c8fd10 Stephen Shirley
    #
463 a6c8fd10 Stephen Shirley
464 a6c8fd10 Stephen Shirley
    # Check default hypervisor
465 a6c8fd10 Stephen Shirley
    my_defhyp = my_cluster.enabled_hypervisors[0]
466 a6c8fd10 Stephen Shirley
    other_defhyp = other_cluster.enabled_hypervisors[0]
467 a6c8fd10 Stephen Shirley
    if my_defhyp != other_defhyp:
468 a6c8fd10 Stephen Shirley
      logging.warning("The default hypervisor (%s) differs on %s, new"
469 a6c8fd10 Stephen Shirley
                      " instances will be created with this cluster's"
470 a6c8fd10 Stephen Shirley
                      " default hypervisor (%s)", other_defhyp,
471 a6c8fd10 Stephen Shirley
                      other_cluster.cluster_name, my_defhyp)
472 a6c8fd10 Stephen Shirley
473 a6c8fd10 Stephen Shirley
    if (set(my_cluster.enabled_hypervisors) !=
474 a6c8fd10 Stephen Shirley
        set(other_cluster.enabled_hypervisors)):
475 a6c8fd10 Stephen Shirley
      logging.error("The set of enabled hypervisors (%s) on %s differs to"
476 a6c8fd10 Stephen Shirley
                    " this cluster's set (%s)",
477 a6c8fd10 Stephen Shirley
                    other_cluster.enabled_hypervisors,
478 a6c8fd10 Stephen Shirley
                    other_cluster.cluster_name, my_cluster.enabled_hypervisors)
479 a6c8fd10 Stephen Shirley
      err_count += 1
480 a6c8fd10 Stephen Shirley
481 a6c8fd10 Stephen Shirley
    # Check hypervisor params for hypervisors we care about
482 a6c8fd10 Stephen Shirley
    for hyp in my_cluster.enabled_hypervisors:
483 a6c8fd10 Stephen Shirley
      for param in my_cluster.hvparams[hyp]:
484 a6c8fd10 Stephen Shirley
        my_value = my_cluster.hvparams[hyp][param]
485 a6c8fd10 Stephen Shirley
        other_value = other_cluster.hvparams[hyp][param]
486 a6c8fd10 Stephen Shirley
        if my_value != other_value:
487 a6c8fd10 Stephen Shirley
          logging.error("The value (%s) of the %s parameter of the %s"
488 a6c8fd10 Stephen Shirley
                        " hypervisor on %s differs to this cluster's parameter"
489 a6c8fd10 Stephen Shirley
                        " (%s)",
490 a6c8fd10 Stephen Shirley
                        other_value, param, hyp, other_cluster.cluster_name,
491 a6c8fd10 Stephen Shirley
                        my_value)
492 1fcd3b81 Guido Trotter
          if params_strict:
493 1fcd3b81 Guido Trotter
            err_count += 1
494 a6c8fd10 Stephen Shirley
495 a6c8fd10 Stephen Shirley
    # Check os hypervisor params for hypervisors we care about
496 a6c8fd10 Stephen Shirley
    for os_name in set(my_cluster.os_hvp.keys() + other_cluster.os_hvp.keys()):
497 a6c8fd10 Stephen Shirley
      for hyp in my_cluster.enabled_hypervisors:
498 a6c8fd10 Stephen Shirley
        my_os_hvp = self._GetOsHypervisor(my_cluster, os_name, hyp)
499 a6c8fd10 Stephen Shirley
        other_os_hvp = self._GetOsHypervisor(other_cluster, os_name, hyp)
500 a6c8fd10 Stephen Shirley
        if my_os_hvp != other_os_hvp:
501 a6c8fd10 Stephen Shirley
          logging.error("The OS parameters (%s) for the %s OS for the %s"
502 a6c8fd10 Stephen Shirley
                        " hypervisor on %s differs to this cluster's parameters"
503 a6c8fd10 Stephen Shirley
                        " (%s)",
504 a6c8fd10 Stephen Shirley
                        other_os_hvp, os_name, hyp, other_cluster.cluster_name,
505 a6c8fd10 Stephen Shirley
                        my_os_hvp)
506 1fcd3b81 Guido Trotter
          if params_strict:
507 1fcd3b81 Guido Trotter
            err_count += 1
508 a6c8fd10 Stephen Shirley
509 a6c8fd10 Stephen Shirley
    #
510 a6c8fd10 Stephen Shirley
    # Warnings
511 a6c8fd10 Stephen Shirley
    #
512 a6c8fd10 Stephen Shirley
    if my_cluster.modify_etc_hosts != other_cluster.modify_etc_hosts:
513 a6c8fd10 Stephen Shirley
      logging.warning("The modify_etc_hosts value (%s) differs on %s,"
514 a6c8fd10 Stephen Shirley
                      " this cluster's value (%s) will take precedence",
515 a6c8fd10 Stephen Shirley
                      other_cluster.modify_etc_hosts,
516 a6c8fd10 Stephen Shirley
                      other_cluster.cluster_name,
517 a6c8fd10 Stephen Shirley
                      my_cluster.modify_etc_hosts)
518 a6c8fd10 Stephen Shirley
519 a6c8fd10 Stephen Shirley
    if my_cluster.modify_ssh_setup != other_cluster.modify_ssh_setup:
520 a6c8fd10 Stephen Shirley
      logging.warning("The modify_ssh_setup value (%s) differs on %s,"
521 a6c8fd10 Stephen Shirley
                      " this cluster's value (%s) will take precedence",
522 a6c8fd10 Stephen Shirley
                      other_cluster.modify_ssh_setup,
523 a6c8fd10 Stephen Shirley
                      other_cluster.cluster_name,
524 a6c8fd10 Stephen Shirley
                      my_cluster.modify_ssh_setup)
525 a6c8fd10 Stephen Shirley
526 a6c8fd10 Stephen Shirley
    #
527 a6c8fd10 Stephen Shirley
    # Actual merging
528 a6c8fd10 Stephen Shirley
    #
529 a6c8fd10 Stephen Shirley
    my_cluster.reserved_lvs = list(set(my_cluster.reserved_lvs +
530 a6c8fd10 Stephen Shirley
                                       other_cluster.reserved_lvs))
531 a6c8fd10 Stephen Shirley
532 a6c8fd10 Stephen Shirley
    if my_cluster.prealloc_wipe_disks != other_cluster.prealloc_wipe_disks:
533 a6c8fd10 Stephen Shirley
      logging.warning("The prealloc_wipe_disks value (%s) on %s differs to this"
534 a6c8fd10 Stephen Shirley
                      " cluster's value (%s). The least permissive value (%s)"
535 a6c8fd10 Stephen Shirley
                      " will be used", other_cluster.prealloc_wipe_disks,
536 a6c8fd10 Stephen Shirley
                      other_cluster.cluster_name,
537 a6c8fd10 Stephen Shirley
                      my_cluster.prealloc_wipe_disks, True)
538 a6c8fd10 Stephen Shirley
      my_cluster.prealloc_wipe_disks = True
539 a6c8fd10 Stephen Shirley
540 a6c8fd10 Stephen Shirley
    for os_, osparams in other_cluster.osparams.items():
541 a6c8fd10 Stephen Shirley
      if os_ not in my_cluster.osparams:
542 a6c8fd10 Stephen Shirley
        my_cluster.osparams[os_] = osparams
543 a6c8fd10 Stephen Shirley
      elif my_cluster.osparams[os_] != osparams:
544 a6c8fd10 Stephen Shirley
        logging.error("The OS parameters (%s) for the %s OS on %s differs to"
545 a6c8fd10 Stephen Shirley
                      " this cluster's parameters (%s)",
546 a6c8fd10 Stephen Shirley
                      osparams, os_, other_cluster.cluster_name,
547 a6c8fd10 Stephen Shirley
                      my_cluster.osparams[os_])
548 1fcd3b81 Guido Trotter
        if params_strict:
549 1fcd3b81 Guido Trotter
          err_count += 1
550 a6c8fd10 Stephen Shirley
551 a6c8fd10 Stephen Shirley
    if err_count:
552 a6c8fd10 Stephen Shirley
      raise errors.ConfigurationError("Cluster config for %s has incompatible"
553 a6c8fd10 Stephen Shirley
                                      " values, please fix and re-run" %
554 a6c8fd10 Stephen Shirley
                                      other_cluster.cluster_name)
555 a6c8fd10 Stephen Shirley
556 a6c8fd10 Stephen Shirley
  # R0201: Method could be a function
557 b459a848 Andrea Spadaccini
  def _GetOsHypervisor(self, cluster, os_name, hyp): # pylint: disable=R0201
558 a6c8fd10 Stephen Shirley
    if os_name in cluster.os_hvp:
559 a6c8fd10 Stephen Shirley
      return cluster.os_hvp[os_name].get(hyp, None)
560 a6c8fd10 Stephen Shirley
    else:
561 a6c8fd10 Stephen Shirley
      return None
562 a6c8fd10 Stephen Shirley
563 a6c8fd10 Stephen Shirley
  # R0201: Method could be a function
564 8f44674f Stephen Shirley
  def _MergeNodeGroups(self, my_config, other_config):
565 8f44674f Stephen Shirley
    """Adds foreign node groups
566 8f44674f Stephen Shirley
567 8f44674f Stephen Shirley
    ConfigWriter.AddNodeGroup takes care of making sure there are no conflicts.
568 8f44674f Stephen Shirley
    """
569 b459a848 Andrea Spadaccini
    # pylint: disable=R0201
570 9b945588 Stephen Shirley
    logging.info("Node group conflict strategy: %s", self.groups)
571 1a615be0 Stephen Shirley
572 1a615be0 Stephen Shirley
    my_grps = my_config.GetAllNodeGroupsInfo().values()
573 1a615be0 Stephen Shirley
    other_grps = other_config.GetAllNodeGroupsInfo().values()
574 1a615be0 Stephen Shirley
575 1a615be0 Stephen Shirley
    # Check for node group naming conflicts:
576 1a615be0 Stephen Shirley
    conflicts = []
577 1a615be0 Stephen Shirley
    for other_grp in other_grps:
578 1a615be0 Stephen Shirley
      for my_grp in my_grps:
579 1a615be0 Stephen Shirley
        if other_grp.name == my_grp.name:
580 1a615be0 Stephen Shirley
          conflicts.append(other_grp)
581 1a615be0 Stephen Shirley
582 1a615be0 Stephen Shirley
    if conflicts:
583 1a615be0 Stephen Shirley
      conflict_names = utils.CommaJoin([g.name for g in conflicts])
584 9b945588 Stephen Shirley
      logging.info("Node groups in both local and remote cluster: %s",
585 1a615be0 Stephen Shirley
                   conflict_names)
586 1a615be0 Stephen Shirley
587 1a615be0 Stephen Shirley
      # User hasn't specified how to handle conflicts
588 1a615be0 Stephen Shirley
      if not self.groups:
589 1a615be0 Stephen Shirley
        raise errors.CommandError("The following node group(s) are in both"
590 1a615be0 Stephen Shirley
                                  " clusters, and no merge strategy has been"
591 1a615be0 Stephen Shirley
                                  " supplied (see the --groups option): %s" %
592 1a615be0 Stephen Shirley
                                  conflict_names)
593 1a615be0 Stephen Shirley
594 1a615be0 Stephen Shirley
      # User wants to rename conflicts
595 3a969900 Stephen Shirley
      elif self.groups == _GROUPS_RENAME:
596 1a615be0 Stephen Shirley
        for grp in conflicts:
597 1a615be0 Stephen Shirley
          new_name = "%s-%s" % (grp.name, other_config.GetClusterName())
598 1a615be0 Stephen Shirley
          logging.info("Renaming remote node group from %s to %s"
599 9b945588 Stephen Shirley
                       " to resolve conflict", grp.name, new_name)
600 1a615be0 Stephen Shirley
          grp.name = new_name
601 1a615be0 Stephen Shirley
602 3a969900 Stephen Shirley
      # User wants to merge conflicting groups
603 2be7c4cc Guido Trotter
      elif self.groups == _GROUPS_MERGE:
604 3a969900 Stephen Shirley
        for other_grp in conflicts:
605 9b945588 Stephen Shirley
          logging.info("Merging local and remote '%s' groups", other_grp.name)
606 3a969900 Stephen Shirley
          for node_name in other_grp.members[:]:
607 3a969900 Stephen Shirley
            node = other_config.GetNodeInfo(node_name)
608 9b945588 Stephen Shirley
            # Access to a protected member of a client class
609 b459a848 Andrea Spadaccini
            # pylint: disable=W0212
610 3a969900 Stephen Shirley
            other_config._UnlockedRemoveNodeFromGroup(node)
611 3a969900 Stephen Shirley
612 9b945588 Stephen Shirley
            # Access to a protected member of a client class
613 b459a848 Andrea Spadaccini
            # pylint: disable=W0212
614 3a969900 Stephen Shirley
            my_grp_uuid = my_config._UnlockedLookupNodeGroup(other_grp.name)
615 9b945588 Stephen Shirley
616 9b945588 Stephen Shirley
            # Access to a protected member of a client class
617 b459a848 Andrea Spadaccini
            # pylint: disable=W0212
618 3a969900 Stephen Shirley
            my_config._UnlockedAddNodeToGroup(node, my_grp_uuid)
619 3a969900 Stephen Shirley
            node.group = my_grp_uuid
620 3a969900 Stephen Shirley
          # Remove from list of groups to add
621 3a969900 Stephen Shirley
          other_grps.remove(other_grp)
622 3a969900 Stephen Shirley
623 1a615be0 Stephen Shirley
    for grp in other_grps:
624 8f44674f Stephen Shirley
      #TODO: handle node group conflicts
625 8f44674f Stephen Shirley
      my_config.AddNodeGroup(grp, _CLUSTERMERGE_ECID)
626 8f44674f Stephen Shirley
627 8f44674f Stephen Shirley
  # R0201: Method could be a function
628 b459a848 Andrea Spadaccini
  def _StartMasterDaemon(self, no_vote=False): # pylint: disable=R0201
629 1f7d3f7d René Nussbaumer
    """Starts the local master daemon.
630 1f7d3f7d René Nussbaumer
631 1f7d3f7d René Nussbaumer
    @param no_vote: Should the masterd started without voting? default: False
632 1f7d3f7d René Nussbaumer
    @raise errors.CommandError: If unable to start daemon.
633 1f7d3f7d René Nussbaumer
634 1f7d3f7d René Nussbaumer
    """
635 1f7d3f7d René Nussbaumer
    env = {}
636 1f7d3f7d René Nussbaumer
    if no_vote:
637 1f7d3f7d René Nussbaumer
      env["EXTRA_MASTERD_ARGS"] = "--no-voting --yes-do-it"
638 1f7d3f7d René Nussbaumer
639 09bf5d24 Michael Hanselmann
    result = utils.RunCmd([pathutils.DAEMON_UTIL, "start-master"], env=env)
640 1f7d3f7d René Nussbaumer
    if result.failed:
641 1f7d3f7d René Nussbaumer
      raise errors.CommandError("Couldn't start ganeti master."
642 1f7d3f7d René Nussbaumer
                                " Fail reason: %s; output: %s" %
643 1f7d3f7d René Nussbaumer
                                (result.fail_reason, result.output))
644 1f7d3f7d René Nussbaumer
645 1f7d3f7d René Nussbaumer
  def _ReaddMergedNodesAndRedist(self):
646 1f7d3f7d René Nussbaumer
    """Readds all merging nodes and make sure their config is up-to-date.
647 1f7d3f7d René Nussbaumer
648 1f7d3f7d René Nussbaumer
    @raise errors.CommandError: If anything fails.
649 1f7d3f7d René Nussbaumer
650 1f7d3f7d René Nussbaumer
    """
651 1f7d3f7d René Nussbaumer
    for data in self.merger_data:
652 1f7d3f7d René Nussbaumer
      for node in data.nodes:
653 419bb2ef Guido Trotter
        logging.info("Readding node %s", node)
654 1f7d3f7d René Nussbaumer
        result = utils.RunCmd(["gnt-node", "add", "--readd",
655 224ff0f7 Michael Hanselmann
                               "--no-ssh-key-check", node])
656 1f7d3f7d René Nussbaumer
        if result.failed:
657 d47319e2 Guido Trotter
          logging.error("%s failed to be readded. Reason: %s, output: %s",
658 d47319e2 Guido Trotter
                         node, result.fail_reason, result.output)
659 1f7d3f7d René Nussbaumer
660 1f7d3f7d René Nussbaumer
    result = utils.RunCmd(["gnt-cluster", "redist-conf"])
661 1f7d3f7d René Nussbaumer
    if result.failed:
662 1f7d3f7d René Nussbaumer
      raise errors.CommandError("Redistribution failed. Fail reason: %s;"
663 1f7d3f7d René Nussbaumer
                                " output: %s" % (result.fail_reason,
664 5ae4945a Iustin Pop
                                                 result.output))
665 1f7d3f7d René Nussbaumer
666 1f7d3f7d René Nussbaumer
  # R0201: Method could be a function
667 b459a848 Andrea Spadaccini
  def _StartupAllInstances(self): # pylint: disable=R0201
668 1f7d3f7d René Nussbaumer
    """Starts up all instances (locally).
669 1f7d3f7d René Nussbaumer
670 1f7d3f7d René Nussbaumer
    @raise errors.CommandError: If unable to start clusters
671 1f7d3f7d René Nussbaumer
672 1f7d3f7d René Nussbaumer
    """
673 1f7d3f7d René Nussbaumer
    result = utils.RunCmd(["gnt-instance", "startup", "--all",
674 1f7d3f7d René Nussbaumer
                           "--force-multiple"])
675 1f7d3f7d René Nussbaumer
    if result.failed:
676 1f7d3f7d René Nussbaumer
      raise errors.CommandError("Unable to start all instances."
677 1f7d3f7d René Nussbaumer
                                " Fail reason: %s; output: %s" %
678 1f7d3f7d René Nussbaumer
                                (result.fail_reason, result.output))
679 1f7d3f7d René Nussbaumer
680 1f7d3f7d René Nussbaumer
  # R0201: Method could be a function
681 cc2cccfc Guido Trotter
  # TODO: make this overridable, for some verify errors
682 b459a848 Andrea Spadaccini
  def _VerifyCluster(self): # pylint: disable=R0201
683 1f7d3f7d René Nussbaumer
    """Runs gnt-cluster verify to verify the health.
684 1f7d3f7d René Nussbaumer
685 1f7d3f7d René Nussbaumer
    @raise errors.ProgrammError: If cluster fails on verification
686 1f7d3f7d René Nussbaumer
687 1f7d3f7d René Nussbaumer
    """
688 1f7d3f7d René Nussbaumer
    result = utils.RunCmd(["gnt-cluster", "verify"])
689 1f7d3f7d René Nussbaumer
    if result.failed:
690 1f7d3f7d René Nussbaumer
      raise errors.CommandError("Verification of cluster failed."
691 1f7d3f7d René Nussbaumer
                                " Fail reason: %s; output: %s" %
692 1f7d3f7d René Nussbaumer
                                (result.fail_reason, result.output))
693 1f7d3f7d René Nussbaumer
694 1f7d3f7d René Nussbaumer
  def Merge(self):
695 1f7d3f7d René Nussbaumer
    """Does the actual merge.
696 1f7d3f7d René Nussbaumer
697 1f7d3f7d René Nussbaumer
    It runs all the steps in the right order and updates the user about steps
698 1f7d3f7d René Nussbaumer
    taken. Also it keeps track of rollback_steps to undo everything.
699 1f7d3f7d René Nussbaumer
700 1f7d3f7d René Nussbaumer
    """
701 1f7d3f7d René Nussbaumer
    rbsteps = []
702 1f7d3f7d René Nussbaumer
    try:
703 1f7d3f7d René Nussbaumer
      logging.info("Pre cluster verification")
704 1f7d3f7d René Nussbaumer
      self._VerifyCluster()
705 1f7d3f7d René Nussbaumer
706 8864d152 Guido Trotter
      logging.info("Prepare authorized_keys")
707 8864d152 Guido Trotter
      rbsteps.append("Remove our key from authorized_keys on nodes:"
708 8864d152 Guido Trotter
                     " %(nodes)s")
709 8864d152 Guido Trotter
      self._PrepareAuthorizedKeys()
710 8864d152 Guido Trotter
711 1f7d3f7d René Nussbaumer
      rbsteps.append("Start all instances again on the merging"
712 1f7d3f7d René Nussbaumer
                     " clusters: %(clusters)s")
713 1fb5f905 Andrea Spadaccini
      if self.stop_instances:
714 1fb5f905 Andrea Spadaccini
        logging.info("Stopping merging instances (takes a while)")
715 1fb5f905 Andrea Spadaccini
        self._StopMergingInstances()
716 1fb5f905 Andrea Spadaccini
      logging.info("Checking that no instances are running on the mergees")
717 1fb5f905 Andrea Spadaccini
      instances_running = self._CheckRunningInstances()
718 1fb5f905 Andrea Spadaccini
      if instances_running:
719 1fb5f905 Andrea Spadaccini
        raise errors.CommandError("Some instances are still running on the"
720 1fb5f905 Andrea Spadaccini
                                  " mergees")
721 1f7d3f7d René Nussbaumer
      logging.info("Disable watcher")
722 1f7d3f7d René Nussbaumer
      self._DisableWatcher()
723 1f7d3f7d René Nussbaumer
      logging.info("Merging config")
724 1f7d3f7d René Nussbaumer
      self._FetchRemoteConfig()
725 b972c223 Andrea Spadaccini
      logging.info("Removing master IPs on mergee master nodes")
726 b972c223 Andrea Spadaccini
      self._RemoveMasterIps()
727 a3fad332 Andrea Spadaccini
      logging.info("Stop daemons on merging nodes")
728 a3fad332 Andrea Spadaccini
      self._StopDaemons()
729 d8aab233 René Nussbaumer
730 9c03a67a Stephen Shirley
      logging.info("Stopping master daemon")
731 9c03a67a Stephen Shirley
      self._KillMasterDaemon()
732 9c03a67a Stephen Shirley
733 9c03a67a Stephen Shirley
      rbsteps.append("Restore %s from another master candidate"
734 9c03a67a Stephen Shirley
                     " and restart master daemon" %
735 09bf5d24 Michael Hanselmann
                     pathutils.CLUSTER_CONF_FILE)
736 9c03a67a Stephen Shirley
      self._MergeConfig()
737 9c03a67a Stephen Shirley
      self._StartMasterDaemon(no_vote=True)
738 9c03a67a Stephen Shirley
739 9c03a67a Stephen Shirley
      # Point of no return, delete rbsteps
740 9c03a67a Stephen Shirley
      del rbsteps[:]
741 9c03a67a Stephen Shirley
742 9c03a67a Stephen Shirley
      logging.warning("We are at the point of no return. Merge can not easily"
743 9c03a67a Stephen Shirley
                      " be undone after this point.")
744 9c03a67a Stephen Shirley
      logging.info("Readd nodes")
745 9c03a67a Stephen Shirley
      self._ReaddMergedNodesAndRedist()
746 9c03a67a Stephen Shirley
747 9c03a67a Stephen Shirley
      logging.info("Merge done, restart master daemon normally")
748 9c03a67a Stephen Shirley
      self._KillMasterDaemon()
749 9c03a67a Stephen Shirley
      self._StartMasterDaemon()
750 d8aab233 René Nussbaumer
751 620a9c62 Guido Trotter
      if self.restart == _RESTART_ALL:
752 620a9c62 Guido Trotter
        logging.info("Starting instances again")
753 620a9c62 Guido Trotter
        self._StartupAllInstances()
754 620a9c62 Guido Trotter
      else:
755 620a9c62 Guido Trotter
        logging.info("Not starting instances again")
756 1f7d3f7d René Nussbaumer
      logging.info("Post cluster verification")
757 1f7d3f7d René Nussbaumer
      self._VerifyCluster()
758 1f7d3f7d René Nussbaumer
    except errors.GenericError, e:
759 1f7d3f7d René Nussbaumer
      logging.exception(e)
760 1f7d3f7d René Nussbaumer
761 1f7d3f7d René Nussbaumer
      if rbsteps:
762 1f7d3f7d René Nussbaumer
        nodes = Flatten([data.nodes for data in self.merger_data])
763 1f7d3f7d René Nussbaumer
        info = {
764 1f7d3f7d René Nussbaumer
          "clusters": self.clusters,
765 1f7d3f7d René Nussbaumer
          "nodes": nodes,
766 1f7d3f7d René Nussbaumer
          }
767 1f7d3f7d René Nussbaumer
        logging.critical("In order to rollback do the following:")
768 1f7d3f7d René Nussbaumer
        for step in rbsteps:
769 3f1cf151 René Nussbaumer
          logging.critical("  * %s", step % info)
770 1f7d3f7d René Nussbaumer
      else:
771 1f7d3f7d René Nussbaumer
        logging.critical("Nothing to rollback.")
772 1f7d3f7d René Nussbaumer
773 1f7d3f7d René Nussbaumer
      # TODO: Keep track of steps done for a flawless resume?
774 1f7d3f7d René Nussbaumer
775 1f7d3f7d René Nussbaumer
  def Cleanup(self):
776 1f7d3f7d René Nussbaumer
    """Clean up our environment.
777 1f7d3f7d René Nussbaumer
778 1f7d3f7d René Nussbaumer
    This cleans up remote private keys and configs and after that
779 1f7d3f7d René Nussbaumer
    deletes the temporary directory.
780 1f7d3f7d René Nussbaumer
781 1f7d3f7d René Nussbaumer
    """
782 1f7d3f7d René Nussbaumer
    shutil.rmtree(self.work_dir)
783 1f7d3f7d René Nussbaumer
784 1f7d3f7d René Nussbaumer
785 1f7d3f7d René Nussbaumer
def main():
786 1f7d3f7d René Nussbaumer
  """Main routine.
787 1f7d3f7d René Nussbaumer
788 1f7d3f7d René Nussbaumer
  """
789 1f7d3f7d René Nussbaumer
  program = os.path.basename(sys.argv[0])
790 1f7d3f7d René Nussbaumer
791 d2b17e37 Guido Trotter
  parser = optparse.OptionParser(usage="%%prog [options...] <cluster...>",
792 d2b17e37 Guido Trotter
                                 prog=program)
793 1f7d3f7d René Nussbaumer
  parser.add_option(cli.DEBUG_OPT)
794 1f7d3f7d René Nussbaumer
  parser.add_option(cli.VERBOSE_OPT)
795 1f7d3f7d René Nussbaumer
  parser.add_option(PAUSE_PERIOD_OPT)
796 1a615be0 Stephen Shirley
  parser.add_option(GROUPS_OPT)
797 620a9c62 Guido Trotter
  parser.add_option(RESTART_OPT)
798 1fcd3b81 Guido Trotter
  parser.add_option(PARAMS_OPT)
799 1fb5f905 Andrea Spadaccini
  parser.add_option(SKIP_STOP_INSTANCES_OPT)
800 1f7d3f7d René Nussbaumer
801 1f7d3f7d René Nussbaumer
  (options, args) = parser.parse_args()
802 1f7d3f7d René Nussbaumer
803 796b5152 Michael Hanselmann
  utils.SetupToolLogging(options.debug, options.verbose)
804 1f7d3f7d René Nussbaumer
805 1f7d3f7d René Nussbaumer
  if not args:
806 1f7d3f7d René Nussbaumer
    parser.error("No clusters specified")
807 1f7d3f7d René Nussbaumer
808 1a615be0 Stephen Shirley
  cluster_merger = Merger(utils.UniqueSequence(args), options.pause_period,
809 1fb5f905 Andrea Spadaccini
                          options.groups, options.restart, options.params,
810 1fb5f905 Andrea Spadaccini
                          options.stop_instances)
811 1f7d3f7d René Nussbaumer
  try:
812 1f7d3f7d René Nussbaumer
    try:
813 1f7d3f7d René Nussbaumer
      cluster_merger.Setup()
814 1f7d3f7d René Nussbaumer
      cluster_merger.Merge()
815 1f7d3f7d René Nussbaumer
    except errors.GenericError, e:
816 1f7d3f7d René Nussbaumer
      logging.exception(e)
817 1f7d3f7d René Nussbaumer
      return constants.EXIT_FAILURE
818 1f7d3f7d René Nussbaumer
  finally:
819 1f7d3f7d René Nussbaumer
    cluster_merger.Cleanup()
820 1f7d3f7d René Nussbaumer
821 1f7d3f7d René Nussbaumer
  return constants.EXIT_SUCCESS
822 1f7d3f7d René Nussbaumer
823 1f7d3f7d René Nussbaumer
824 1f7d3f7d René Nussbaumer
if __name__ == "__main__":
825 1f7d3f7d René Nussbaumer
  sys.exit(main())