Statistics
| Branch: | Tag: | Revision:

root / tools / cluster-merge @ 58ea8d17

History | View | Annotate | Download (30.6 kB)

1 1f7d3f7d René Nussbaumer
#!/usr/bin/python
2 1f7d3f7d René Nussbaumer
#
3 1f7d3f7d René Nussbaumer
4 3d8f154f Stephen Shirley
# Copyright (C) 2010 Google Inc.
5 1f7d3f7d René Nussbaumer
#
6 1f7d3f7d René Nussbaumer
# This program is free software; you can redistribute it and/or modify
7 1f7d3f7d René Nussbaumer
# it under the terms of the GNU General Public License as published by
8 1f7d3f7d René Nussbaumer
# the Free Software Foundation; either version 2 of the License, or
9 1f7d3f7d René Nussbaumer
# (at your option) any later version.
10 1f7d3f7d René Nussbaumer
#
11 1f7d3f7d René Nussbaumer
# This program is distributed in the hope that it will be useful, but
12 1f7d3f7d René Nussbaumer
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 1f7d3f7d René Nussbaumer
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 1f7d3f7d René Nussbaumer
# General Public License for more details.
15 1f7d3f7d René Nussbaumer
#
16 1f7d3f7d René Nussbaumer
# You should have received a copy of the GNU General Public License
17 1f7d3f7d René Nussbaumer
# along with this program; if not, write to the Free Software
18 1f7d3f7d René Nussbaumer
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 1f7d3f7d René Nussbaumer
# 02110-1301, USA.
20 1f7d3f7d René Nussbaumer
21 1f7d3f7d René Nussbaumer
"""Tool to merge two or more clusters together.
22 1f7d3f7d René Nussbaumer
23 1f7d3f7d René Nussbaumer
The clusters have to run the same version of Ganeti!
24 1f7d3f7d René Nussbaumer
25 1f7d3f7d René Nussbaumer
"""
26 1f7d3f7d René Nussbaumer
27 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 1f7d3f7d René Nussbaumer
44 1f7d3f7d René Nussbaumer
45 1a615be0 Stephen Shirley
_GROUPS_MERGE = "merge"
46 1a615be0 Stephen Shirley
_GROUPS_RENAME = "rename"
47 1a615be0 Stephen Shirley
_CLUSTERMERGE_ECID = "clustermerge-ecid"
48 620a9c62 Guido Trotter
_RESTART_ALL = "all"
49 620a9c62 Guido Trotter
_RESTART_UP = "up"
50 620a9c62 Guido Trotter
_RESTART_NONE = "none"
51 620a9c62 Guido Trotter
_RESTART_CHOICES = (_RESTART_ALL, _RESTART_UP, _RESTART_NONE)
52 1fcd3b81 Guido Trotter
_PARAMS_STRICT = "strict"
53 1fcd3b81 Guido Trotter
_PARAMS_WARN = "warn"
54 1fcd3b81 Guido Trotter
_PARAMS_CHOICES = (_PARAMS_STRICT, _PARAMS_WARN)
55 620a9c62 Guido Trotter
56 1a615be0 Stephen Shirley
57 1f7d3f7d René Nussbaumer
PAUSE_PERIOD_OPT = cli.cli_option("-p", "--watcher-pause-period", default=1800,
58 1f7d3f7d René Nussbaumer
                                  action="store", type="int",
59 1f7d3f7d René Nussbaumer
                                  dest="pause_period",
60 1f7d3f7d René Nussbaumer
                                  help=("Amount of time in seconds watcher"
61 1f7d3f7d René Nussbaumer
                                        " should be suspended from running"))
62 1a615be0 Stephen Shirley
GROUPS_OPT = cli.cli_option("--groups", default=None, metavar="STRATEGY",
63 9b945588 Stephen Shirley
                            choices=(_GROUPS_MERGE, _GROUPS_RENAME),
64 9b945588 Stephen Shirley
                            dest="groups",
65 1a615be0 Stephen Shirley
                            help=("How to handle groups that have the"
66 1a615be0 Stephen Shirley
                                  " same name (One of: %s/%s)" %
67 1a615be0 Stephen Shirley
                                  (_GROUPS_MERGE, _GROUPS_RENAME)))
68 1fcd3b81 Guido Trotter
PARAMS_OPT = cli.cli_option("--parameter-conflicts", default=_PARAMS_STRICT,
69 1fcd3b81 Guido Trotter
                            metavar="STRATEGY",
70 1fcd3b81 Guido Trotter
                            choices=_PARAMS_CHOICES,
71 1fcd3b81 Guido Trotter
                            dest="params",
72 1fcd3b81 Guido Trotter
                            help=("How to handle params that have"
73 1fcd3b81 Guido Trotter
                                  " different values (One of: %s/%s)" %
74 1fcd3b81 Guido Trotter
                                  _PARAMS_CHOICES))
75 1fcd3b81 Guido Trotter
76 620a9c62 Guido Trotter
RESTART_OPT = cli.cli_option("--restart", default=_RESTART_ALL,
77 620a9c62 Guido Trotter
                             metavar="STRATEGY",
78 620a9c62 Guido Trotter
                             choices=_RESTART_CHOICES,
79 620a9c62 Guido Trotter
                             dest="restart",
80 620a9c62 Guido Trotter
                             help=("How to handle restarting instances"
81 620a9c62 Guido Trotter
                                   " same name (One of: %s/%s/%s)" %
82 620a9c62 Guido Trotter
                                   _RESTART_CHOICES))
83 1f7d3f7d René Nussbaumer
84 b7bf8b58 Guido Trotter
SKIP_STOP_INSTANCES_OPT = \
85 b7bf8b58 Guido Trotter
  cli.cli_option("--skip-stop-instances", default=True, action="store_false",
86 b7bf8b58 Guido Trotter
                 dest="stop_instances",
87 b7bf8b58 Guido Trotter
                 help=("Don't stop the instances on the clusters, just check "
88 b7bf8b58 Guido Trotter
                       "that none is running"))
89 1fb5f905 Andrea Spadaccini
90 1f7d3f7d René Nussbaumer
91 71bbe910 Stephen Shirley
def Flatten(unflattened_list):
92 1f7d3f7d René Nussbaumer
  """Flattens a list.
93 1f7d3f7d René Nussbaumer
94 71bbe910 Stephen Shirley
  @param unflattened_list: A list of unflattened list objects.
95 71bbe910 Stephen Shirley
  @return: A flattened list
96 1f7d3f7d René Nussbaumer
97 1f7d3f7d René Nussbaumer
  """
98 71bbe910 Stephen Shirley
  flattened_list = []
99 1f7d3f7d René Nussbaumer
100 71bbe910 Stephen Shirley
  for item in unflattened_list:
101 1f7d3f7d René Nussbaumer
    if isinstance(item, list):
102 71bbe910 Stephen Shirley
      flattened_list.extend(Flatten(item))
103 1f7d3f7d René Nussbaumer
    else:
104 71bbe910 Stephen Shirley
      flattened_list.append(item)
105 71bbe910 Stephen Shirley
  return flattened_list
106 1f7d3f7d René Nussbaumer
107 1f7d3f7d René Nussbaumer
108 1f7d3f7d René Nussbaumer
class MergerData(object):
109 1f7d3f7d René Nussbaumer
  """Container class to hold data used for merger.
110 1f7d3f7d René Nussbaumer
111 1f7d3f7d René Nussbaumer
  """
112 b972c223 Andrea Spadaccini
  def __init__(self, cluster, key_path, nodes, instances, master_node,
113 a3fad332 Andrea Spadaccini
               config_path=None):
114 1f7d3f7d René Nussbaumer
    """Initialize the container.
115 1f7d3f7d René Nussbaumer
116 1f7d3f7d René Nussbaumer
    @param cluster: The name of the cluster
117 1f7d3f7d René Nussbaumer
    @param key_path: Path to the ssh private key used for authentication
118 8697f0fa Guido Trotter
    @param nodes: List of online nodes in the merging cluster
119 1f7d3f7d René Nussbaumer
    @param instances: List of instances running on merging cluster
120 b972c223 Andrea Spadaccini
    @param master_node: Name of the master node
121 3d2e7a27 Stephen Shirley
    @param config_path: Path to the merging cluster config
122 1f7d3f7d René Nussbaumer
123 1f7d3f7d René Nussbaumer
    """
124 1f7d3f7d René Nussbaumer
    self.cluster = cluster
125 1f7d3f7d René Nussbaumer
    self.key_path = key_path
126 1f7d3f7d René Nussbaumer
    self.nodes = nodes
127 3d2e7a27 Stephen Shirley
    self.instances = instances
128 b972c223 Andrea Spadaccini
    self.master_node = master_node
129 3d2e7a27 Stephen Shirley
    self.config_path = config_path
130 1f7d3f7d René Nussbaumer
131 1f7d3f7d René Nussbaumer
132 1f7d3f7d René Nussbaumer
class Merger(object):
133 1f7d3f7d René Nussbaumer
  """Handling the merge.
134 1f7d3f7d René Nussbaumer
135 1f7d3f7d René Nussbaumer
  """
136 1fb5f905 Andrea Spadaccini
  RUNNING_STATUSES = frozenset([
137 1fb5f905 Andrea Spadaccini
    constants.INSTST_RUNNING,
138 1fb5f905 Andrea Spadaccini
    constants.INSTST_ERRORUP,
139 1fb5f905 Andrea Spadaccini
    ])
140 e687ec01 Michael Hanselmann
141 1fb5f905 Andrea Spadaccini
  def __init__(self, clusters, pause_period, groups, restart, params,
142 1fb5f905 Andrea Spadaccini
               stop_instances):
143 1f7d3f7d René Nussbaumer
    """Initialize object with sane defaults and infos required.
144 1f7d3f7d René Nussbaumer
145 1f7d3f7d René Nussbaumer
    @param clusters: The list of clusters to merge in
146 1f7d3f7d René Nussbaumer
    @param pause_period: The time watcher shall be disabled for
147 1a615be0 Stephen Shirley
    @param groups: How to handle group conflicts
148 620a9c62 Guido Trotter
    @param restart: How to handle instance restart
149 1fb5f905 Andrea Spadaccini
    @param stop_instances: Indicates whether the instances must be stopped
150 1fb5f905 Andrea Spadaccini
                           (True) or if the Merger must only check if no
151 1fb5f905 Andrea Spadaccini
                           instances are running on the mergee clusters (False)
152 1f7d3f7d René Nussbaumer
153 1f7d3f7d René Nussbaumer
    """
154 1f7d3f7d René Nussbaumer
    self.merger_data = []
155 1f7d3f7d René Nussbaumer
    self.clusters = clusters
156 1f7d3f7d René Nussbaumer
    self.pause_period = pause_period
157 1f7d3f7d René Nussbaumer
    self.work_dir = tempfile.mkdtemp(suffix="cluster-merger")
158 be8aecab Stephen Shirley
    (self.cluster_name, ) = cli.GetClient().QueryConfigValues(["cluster_name"])
159 1f7d3f7d René Nussbaumer
    self.ssh_runner = ssh.SshRunner(self.cluster_name)
160 1a615be0 Stephen Shirley
    self.groups = groups
161 620a9c62 Guido Trotter
    self.restart = restart
162 1fcd3b81 Guido Trotter
    self.params = params
163 1fb5f905 Andrea Spadaccini
    self.stop_instances = stop_instances
164 620a9c62 Guido Trotter
    if self.restart == _RESTART_UP:
165 620a9c62 Guido Trotter
      raise NotImplementedError
166 620a9c62 Guido Trotter
167 1f7d3f7d René Nussbaumer
  def Setup(self):
168 1f7d3f7d René Nussbaumer
    """Sets up our end so we can do the merger.
169 1f7d3f7d René Nussbaumer
170 1f7d3f7d René Nussbaumer
    This method is setting us up as a preparation for the merger.
171 1f7d3f7d René Nussbaumer
    It makes the initial contact and gathers information needed.
172 1f7d3f7d René Nussbaumer
173 1f7d3f7d René Nussbaumer
    @raise errors.RemoteError: for errors in communication/grabbing
174 1f7d3f7d René Nussbaumer
175 1f7d3f7d René Nussbaumer
    """
176 1f7d3f7d René Nussbaumer
    (remote_path, _, _) = ssh.GetUserFiles("root")
177 1f7d3f7d René Nussbaumer
178 be8aecab Stephen Shirley
    if self.cluster_name in self.clusters:
179 be8aecab Stephen Shirley
      raise errors.CommandError("Cannot merge cluster %s with itself" %
180 be8aecab Stephen Shirley
                                self.cluster_name)
181 be8aecab Stephen Shirley
182 1f7d3f7d René Nussbaumer
    # Fetch remotes private key
183 1f7d3f7d René Nussbaumer
    for cluster in self.clusters:
184 1f7d3f7d René Nussbaumer
      result = self._RunCmd(cluster, "cat %s" % remote_path, batch=False,
185 1f7d3f7d René Nussbaumer
                            ask_key=False)
186 1f7d3f7d René Nussbaumer
      if result.failed:
187 1f7d3f7d René Nussbaumer
        raise errors.RemoteError("There was an error while grabbing ssh private"
188 1f7d3f7d René Nussbaumer
                                 " key from %s. Fail reason: %s; output: %s" %
189 1f7d3f7d René Nussbaumer
                                 (cluster, result.fail_reason, result.output))
190 1f7d3f7d René Nussbaumer
191 c4feafe8 Iustin Pop
      key_path = utils.PathJoin(self.work_dir, cluster)
192 1f7d3f7d René Nussbaumer
      utils.WriteFile(key_path, mode=0600, data=result.stdout)
193 1f7d3f7d René Nussbaumer
194 8697f0fa Guido Trotter
      result = self._RunCmd(cluster, "gnt-node list -o name,offline"
195 58ea8d17 Michael Hanselmann
                            " --no-headers --separator=,", private_key=key_path)
196 1f7d3f7d René Nussbaumer
      if result.failed:
197 1f7d3f7d René Nussbaumer
        raise errors.RemoteError("Unable to retrieve list of nodes from %s."
198 1f7d3f7d René Nussbaumer
                                 " Fail reason: %s; output: %s" %
199 1f7d3f7d René Nussbaumer
                                 (cluster, result.fail_reason, result.output))
200 8697f0fa Guido Trotter
      nodes_statuses = [line.split(',') for line in result.stdout.splitlines()]
201 8697f0fa Guido Trotter
      nodes = [node_status[0] for node_status in nodes_statuses
202 8697f0fa Guido Trotter
               if node_status[1] == "N"]
203 1f7d3f7d René Nussbaumer
204 58ea8d17 Michael Hanselmann
      result = self._RunCmd(cluster, "gnt-instance list -o name --no-headers",
205 1f7d3f7d René Nussbaumer
                            private_key=key_path)
206 1f7d3f7d René Nussbaumer
      if result.failed:
207 1f7d3f7d René Nussbaumer
        raise errors.RemoteError("Unable to retrieve list of instances from"
208 1f7d3f7d René Nussbaumer
                                 " %s. Fail reason: %s; output: %s" %
209 1f7d3f7d René Nussbaumer
                                 (cluster, result.fail_reason, result.output))
210 1f7d3f7d René Nussbaumer
      instances = result.stdout.splitlines()
211 1f7d3f7d René Nussbaumer
212 b972c223 Andrea Spadaccini
      path = utils.PathJoin(constants.DATA_DIR, "ssconf_%s" %
213 b972c223 Andrea Spadaccini
                            constants.SS_MASTER_NODE)
214 b972c223 Andrea Spadaccini
      result = self._RunCmd(cluster, "cat %s" % path, private_key=key_path)
215 b972c223 Andrea Spadaccini
      if result.failed:
216 b972c223 Andrea Spadaccini
        raise errors.RemoteError("Unable to retrieve the master node name from"
217 b972c223 Andrea Spadaccini
                                 " %s. Fail reason: %s; output: %s" %
218 b972c223 Andrea Spadaccini
                                 (cluster, result.fail_reason, result.output))
219 b972c223 Andrea Spadaccini
      master_node = result.stdout.strip()
220 b972c223 Andrea Spadaccini
221 b972c223 Andrea Spadaccini
      self.merger_data.append(MergerData(cluster, key_path, nodes, instances,
222 a3fad332 Andrea Spadaccini
                                         master_node))
223 1f7d3f7d René Nussbaumer
224 8864d152 Guido Trotter
  def _PrepareAuthorizedKeys(self):
225 8864d152 Guido Trotter
    """Prepare the authorized_keys on every merging node.
226 1f7d3f7d René Nussbaumer
227 1f7d3f7d René Nussbaumer
    This method add our public key to remotes authorized_key for further
228 1f7d3f7d René Nussbaumer
    communication.
229 1f7d3f7d René Nussbaumer
230 1f7d3f7d René Nussbaumer
    """
231 1f7d3f7d René Nussbaumer
    (_, pub_key_file, auth_keys) = ssh.GetUserFiles("root")
232 1f7d3f7d René Nussbaumer
    pub_key = utils.ReadFile(pub_key_file)
233 1f7d3f7d René Nussbaumer
234 8864d152 Guido Trotter
    for data in self.merger_data:
235 8864d152 Guido Trotter
      for node in data.nodes:
236 8864d152 Guido Trotter
        result = self._RunCmd(node, ("cat >> %s << '!EOF.'\n%s!EOF.\n" %
237 8864d152 Guido Trotter
                                     (auth_keys, pub_key)),
238 503cc75f Guido Trotter
                              private_key=data.key_path, max_attempts=3)
239 1f7d3f7d René Nussbaumer
240 8864d152 Guido Trotter
        if result.failed:
241 8864d152 Guido Trotter
          raise errors.RemoteError("Unable to add our public key to %s in %s."
242 8864d152 Guido Trotter
                                   " Fail reason: %s; output: %s" %
243 8864d152 Guido Trotter
                                   (node, data.cluster, result.fail_reason,
244 8864d152 Guido Trotter
                                    result.output))
245 1f7d3f7d René Nussbaumer
246 1f7d3f7d René Nussbaumer
  def _RunCmd(self, hostname, command, user="root", use_cluster_key=False,
247 1f7d3f7d René Nussbaumer
              strict_host_check=False, private_key=None, batch=True,
248 07ff0a78 Andrea Spadaccini
              ask_key=False, max_attempts=1):
249 1f7d3f7d René Nussbaumer
    """Wrapping SshRunner.Run with default parameters.
250 1f7d3f7d René Nussbaumer
251 454723b5 Iustin Pop
    For explanation of parameters see L{ganeti.ssh.SshRunner.Run}.
252 1f7d3f7d René Nussbaumer
253 1f7d3f7d René Nussbaumer
    """
254 07ff0a78 Andrea Spadaccini
    for _ in range(max_attempts):
255 07ff0a78 Andrea Spadaccini
      result = self.ssh_runner.Run(hostname=hostname, command=command,
256 07ff0a78 Andrea Spadaccini
                                 user=user, use_cluster_key=use_cluster_key,
257 07ff0a78 Andrea Spadaccini
                                 strict_host_check=strict_host_check,
258 07ff0a78 Andrea Spadaccini
                                 private_key=private_key, batch=batch,
259 07ff0a78 Andrea Spadaccini
                                 ask_key=ask_key)
260 07ff0a78 Andrea Spadaccini
      if not result.failed:
261 07ff0a78 Andrea Spadaccini
        break
262 07ff0a78 Andrea Spadaccini
263 07ff0a78 Andrea Spadaccini
    return result
264 1f7d3f7d René Nussbaumer
265 1fb5f905 Andrea Spadaccini
  def _CheckRunningInstances(self):
266 1fb5f905 Andrea Spadaccini
    """Checks if on the clusters to be merged there are running instances
267 1fb5f905 Andrea Spadaccini
268 1fb5f905 Andrea Spadaccini
    @rtype: boolean
269 1fb5f905 Andrea Spadaccini
    @return: True if there are running instances, False otherwise
270 1fb5f905 Andrea Spadaccini
271 1fb5f905 Andrea Spadaccini
    """
272 1fb5f905 Andrea Spadaccini
    for cluster in self.clusters:
273 1fb5f905 Andrea Spadaccini
      result = self._RunCmd(cluster, "gnt-instance list -o status")
274 006a51b3 Andrea Spadaccini
      if self.RUNNING_STATUSES.intersection(result.output.splitlines()):
275 1fb5f905 Andrea Spadaccini
        return True
276 1fb5f905 Andrea Spadaccini
277 1fb5f905 Andrea Spadaccini
    return False
278 1fb5f905 Andrea Spadaccini
279 1f7d3f7d René Nussbaumer
  def _StopMergingInstances(self):
280 1f7d3f7d René Nussbaumer
    """Stop instances on merging clusters.
281 1f7d3f7d René Nussbaumer
282 1f7d3f7d René Nussbaumer
    """
283 1f7d3f7d René Nussbaumer
    for cluster in self.clusters:
284 1f7d3f7d René Nussbaumer
      result = self._RunCmd(cluster, "gnt-instance shutdown --all"
285 1f7d3f7d René Nussbaumer
                                     " --force-multiple")
286 1f7d3f7d René Nussbaumer
287 1f7d3f7d René Nussbaumer
      if result.failed:
288 1f7d3f7d René Nussbaumer
        raise errors.RemoteError("Unable to stop instances on %s."
289 1f7d3f7d René Nussbaumer
                                 " Fail reason: %s; output: %s" %
290 1f7d3f7d René Nussbaumer
                                 (cluster, result.fail_reason, result.output))
291 1f7d3f7d René Nussbaumer
292 1f7d3f7d René Nussbaumer
  def _DisableWatcher(self):
293 1f7d3f7d René Nussbaumer
    """Disable watch on all merging clusters, including ourself.
294 1f7d3f7d René Nussbaumer
295 1f7d3f7d René Nussbaumer
    """
296 1f7d3f7d René Nussbaumer
    for cluster in ["localhost"] + self.clusters:
297 1f7d3f7d René Nussbaumer
      result = self._RunCmd(cluster, "gnt-cluster watcher pause %d" %
298 1f7d3f7d René Nussbaumer
                                     self.pause_period)
299 1f7d3f7d René Nussbaumer
300 1f7d3f7d René Nussbaumer
      if result.failed:
301 1f7d3f7d René Nussbaumer
        raise errors.RemoteError("Unable to pause watcher on %s."
302 1f7d3f7d René Nussbaumer
                                 " Fail reason: %s; output: %s" %
303 1f7d3f7d René Nussbaumer
                                 (cluster, result.fail_reason, result.output))
304 1f7d3f7d René Nussbaumer
305 b972c223 Andrea Spadaccini
  def _RemoveMasterIps(self):
306 b972c223 Andrea Spadaccini
    """Removes the master IPs from the master nodes of each cluster.
307 b972c223 Andrea Spadaccini
308 b972c223 Andrea Spadaccini
    """
309 b972c223 Andrea Spadaccini
    for data in self.merger_data:
310 a3fad332 Andrea Spadaccini
      result = self._RunCmd(data.master_node,
311 aeb24d97 Guido Trotter
                            "gnt-cluster deactivate-master-ip --yes")
312 a3fad332 Andrea Spadaccini
313 b972c223 Andrea Spadaccini
      if result.failed:
314 b972c223 Andrea Spadaccini
        raise errors.RemoteError("Unable to remove master IP on %s."
315 b972c223 Andrea Spadaccini
                                 " Fail reason: %s; output: %s" %
316 b972c223 Andrea Spadaccini
                                 (data.master_node,
317 b972c223 Andrea Spadaccini
                                  result.fail_reason,
318 b972c223 Andrea Spadaccini
                                  result.output))
319 b972c223 Andrea Spadaccini
320 1f7d3f7d René Nussbaumer
  def _StopDaemons(self):
321 1f7d3f7d René Nussbaumer
    """Stop all daemons on merging nodes.
322 1f7d3f7d René Nussbaumer
323 1f7d3f7d René Nussbaumer
    """
324 d8aab233 René Nussbaumer
    cmd = "%s stop-all" % constants.DAEMON_UTIL
325 1f7d3f7d René Nussbaumer
    for data in self.merger_data:
326 1f7d3f7d René Nussbaumer
      for node in data.nodes:
327 503cc75f Guido Trotter
        result = self._RunCmd(node, cmd, max_attempts=3)
328 1f7d3f7d René Nussbaumer
329 1f7d3f7d René Nussbaumer
        if result.failed:
330 1f7d3f7d René Nussbaumer
          raise errors.RemoteError("Unable to stop daemons on %s."
331 1f7d3f7d René Nussbaumer
                                   " Fail reason: %s; output: %s." %
332 1f7d3f7d René Nussbaumer
                                   (node, result.fail_reason, result.output))
333 1f7d3f7d René Nussbaumer
334 1f7d3f7d René Nussbaumer
  def _FetchRemoteConfig(self):
335 1f7d3f7d René Nussbaumer
    """Fetches and stores remote cluster config from the master.
336 1f7d3f7d René Nussbaumer
337 1f7d3f7d René Nussbaumer
    This step is needed before we can merge the config.
338 1f7d3f7d René Nussbaumer
339 1f7d3f7d René Nussbaumer
    """
340 1f7d3f7d René Nussbaumer
    for data in self.merger_data:
341 1f7d3f7d René Nussbaumer
      result = self._RunCmd(data.cluster, "cat %s" %
342 1f7d3f7d René Nussbaumer
                                          constants.CLUSTER_CONF_FILE)
343 1f7d3f7d René Nussbaumer
344 1f7d3f7d René Nussbaumer
      if result.failed:
345 1f7d3f7d René Nussbaumer
        raise errors.RemoteError("Unable to retrieve remote config on %s."
346 1f7d3f7d René Nussbaumer
                                 " Fail reason: %s; output %s" %
347 1f7d3f7d René Nussbaumer
                                 (data.cluster, result.fail_reason,
348 1f7d3f7d René Nussbaumer
                                  result.output))
349 1f7d3f7d René Nussbaumer
350 c4feafe8 Iustin Pop
      data.config_path = utils.PathJoin(self.work_dir, "%s_config.data" %
351 c4feafe8 Iustin Pop
                                        data.cluster)
352 1f7d3f7d René Nussbaumer
      utils.WriteFile(data.config_path, data=result.stdout)
353 1f7d3f7d René Nussbaumer
354 1f7d3f7d René Nussbaumer
  # R0201: Method could be a function
355 b459a848 Andrea Spadaccini
  def _KillMasterDaemon(self): # pylint: disable=R0201
356 1f7d3f7d René Nussbaumer
    """Kills the local master daemon.
357 1f7d3f7d René Nussbaumer
358 1f7d3f7d René Nussbaumer
    @raise errors.CommandError: If unable to kill
359 1f7d3f7d René Nussbaumer
360 1f7d3f7d René Nussbaumer
    """
361 1f7d3f7d René Nussbaumer
    result = utils.RunCmd([constants.DAEMON_UTIL, "stop-master"])
362 1f7d3f7d René Nussbaumer
    if result.failed:
363 1f7d3f7d René Nussbaumer
      raise errors.CommandError("Unable to stop master daemons."
364 1f7d3f7d René Nussbaumer
                                " Fail reason: %s; output: %s" %
365 1f7d3f7d René Nussbaumer
                                (result.fail_reason, result.output))
366 1f7d3f7d René Nussbaumer
367 1f7d3f7d René Nussbaumer
  def _MergeConfig(self):
368 1f7d3f7d René Nussbaumer
    """Merges all foreign config into our own config.
369 1f7d3f7d René Nussbaumer
370 1f7d3f7d René Nussbaumer
    """
371 1f7d3f7d René Nussbaumer
    my_config = config.ConfigWriter(offline=True)
372 1f7d3f7d René Nussbaumer
    fake_ec_id = 0 # Needs to be uniq over the whole config merge
373 1f7d3f7d René Nussbaumer
374 1f7d3f7d René Nussbaumer
    for data in self.merger_data:
375 e1ab08db Stephen Shirley
      other_config = config.ConfigWriter(data.config_path, accept_foreign=True)
376 a6c8fd10 Stephen Shirley
      self._MergeClusterConfigs(my_config, other_config)
377 8f44674f Stephen Shirley
      self._MergeNodeGroups(my_config, other_config)
378 1f7d3f7d René Nussbaumer
379 1f7d3f7d René Nussbaumer
      for node in other_config.GetNodeList():
380 1f7d3f7d René Nussbaumer
        node_info = other_config.GetNodeInfo(node)
381 3e22caed Guido Trotter
        # Offline the node, it will be reonlined later at node readd
382 3e22caed Guido Trotter
        node_info.master_candidate = False
383 3e22caed Guido Trotter
        node_info.drained = False
384 3e22caed Guido Trotter
        node_info.offline = True
385 8864d152 Guido Trotter
        my_config.AddNode(node_info, _CLUSTERMERGE_ECID + str(fake_ec_id))
386 1f7d3f7d René Nussbaumer
        fake_ec_id += 1
387 1f7d3f7d René Nussbaumer
388 1f7d3f7d René Nussbaumer
      for instance in other_config.GetInstanceList():
389 1f7d3f7d René Nussbaumer
        instance_info = other_config.GetInstanceInfo(instance)
390 1f7d3f7d René Nussbaumer
391 1f7d3f7d René Nussbaumer
        # Update the DRBD port assignments
392 1f7d3f7d René Nussbaumer
        # This is a little bit hackish
393 1f7d3f7d René Nussbaumer
        for dsk in instance_info.disks:
394 1f7d3f7d René Nussbaumer
          if dsk.dev_type in constants.LDS_DRBD:
395 1f7d3f7d René Nussbaumer
            port = my_config.AllocatePort()
396 1f7d3f7d René Nussbaumer
397 1f7d3f7d René Nussbaumer
            logical_id = list(dsk.logical_id)
398 1f7d3f7d René Nussbaumer
            logical_id[2] = port
399 1f7d3f7d René Nussbaumer
            dsk.logical_id = tuple(logical_id)
400 1f7d3f7d René Nussbaumer
401 1f7d3f7d René Nussbaumer
            physical_id = list(dsk.physical_id)
402 1f7d3f7d René Nussbaumer
            physical_id[1] = physical_id[3] = port
403 1f7d3f7d René Nussbaumer
            dsk.physical_id = tuple(physical_id)
404 1f7d3f7d René Nussbaumer
405 a536aaac Stephen Shirley
        my_config.AddInstance(instance_info,
406 a536aaac Stephen Shirley
                              _CLUSTERMERGE_ECID + str(fake_ec_id))
407 1f7d3f7d René Nussbaumer
        fake_ec_id += 1
408 1f7d3f7d René Nussbaumer
409 a6c8fd10 Stephen Shirley
  def _MergeClusterConfigs(self, my_config, other_config):
410 a6c8fd10 Stephen Shirley
    """Checks that all relevant cluster parameters are compatible
411 a6c8fd10 Stephen Shirley
412 a6c8fd10 Stephen Shirley
    """
413 a6c8fd10 Stephen Shirley
    my_cluster = my_config.GetClusterInfo()
414 a6c8fd10 Stephen Shirley
    other_cluster = other_config.GetClusterInfo()
415 a6c8fd10 Stephen Shirley
    err_count = 0
416 a6c8fd10 Stephen Shirley
417 a6c8fd10 Stephen Shirley
    #
418 a6c8fd10 Stephen Shirley
    # Generic checks
419 a6c8fd10 Stephen Shirley
    #
420 bb074298 Guido Trotter
    check_params = [
421 a6c8fd10 Stephen Shirley
      "beparams",
422 a6c8fd10 Stephen Shirley
      "default_iallocator",
423 a6c8fd10 Stephen Shirley
      "drbd_usermode_helper",
424 a6c8fd10 Stephen Shirley
      "hidden_os",
425 a6c8fd10 Stephen Shirley
      "maintain_node_health",
426 a6c8fd10 Stephen Shirley
      "master_netdev",
427 a6c8fd10 Stephen Shirley
      "ndparams",
428 a6c8fd10 Stephen Shirley
      "nicparams",
429 a6c8fd10 Stephen Shirley
      "primary_ip_family",
430 a6c8fd10 Stephen Shirley
      "tags",
431 a6c8fd10 Stephen Shirley
      "uid_pool",
432 bb074298 Guido Trotter
      ]
433 1fcd3b81 Guido Trotter
    check_params_strict = [
434 1fcd3b81 Guido Trotter
      "volume_group_name",
435 1fcd3b81 Guido Trotter
    ]
436 bb074298 Guido Trotter
    if constants.ENABLE_FILE_STORAGE:
437 1fcd3b81 Guido Trotter
      check_params_strict.append("file_storage_dir")
438 1fcd3b81 Guido Trotter
    if constants.ENABLE_SHARED_FILE_STORAGE:
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 1f7d3f7d René Nussbaumer
    result = utils.RunCmd([constants.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 53991408 Stephen Shirley
                               "--no-ssh-key-check", "--force-join", 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 1f7d3f7d René Nussbaumer
                                                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 9c03a67a Stephen Shirley
                     constants.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 SetupLogging(options):
784 1f7d3f7d René Nussbaumer
  """Setting up logging infrastructure.
785 1f7d3f7d René Nussbaumer
786 1f7d3f7d René Nussbaumer
  @param options: Parsed command line options
787 1f7d3f7d René Nussbaumer
788 1f7d3f7d René Nussbaumer
  """
789 1f7d3f7d René Nussbaumer
  formatter = logging.Formatter("%(asctime)s: %(levelname)s %(message)s")
790 1f7d3f7d René Nussbaumer
791 1f7d3f7d René Nussbaumer
  stderr_handler = logging.StreamHandler()
792 1f7d3f7d René Nussbaumer
  stderr_handler.setFormatter(formatter)
793 1f7d3f7d René Nussbaumer
  if options.debug:
794 1f7d3f7d René Nussbaumer
    stderr_handler.setLevel(logging.NOTSET)
795 1f7d3f7d René Nussbaumer
  elif options.verbose:
796 1f7d3f7d René Nussbaumer
    stderr_handler.setLevel(logging.INFO)
797 1f7d3f7d René Nussbaumer
  else:
798 a6c8fd10 Stephen Shirley
    stderr_handler.setLevel(logging.WARNING)
799 1f7d3f7d René Nussbaumer
800 1f7d3f7d René Nussbaumer
  root_logger = logging.getLogger("")
801 1f7d3f7d René Nussbaumer
  root_logger.setLevel(logging.NOTSET)
802 1f7d3f7d René Nussbaumer
  root_logger.addHandler(stderr_handler)
803 1f7d3f7d René Nussbaumer
804 1f7d3f7d René Nussbaumer
805 1f7d3f7d René Nussbaumer
def main():
806 1f7d3f7d René Nussbaumer
  """Main routine.
807 1f7d3f7d René Nussbaumer
808 1f7d3f7d René Nussbaumer
  """
809 1f7d3f7d René Nussbaumer
  program = os.path.basename(sys.argv[0])
810 1f7d3f7d René Nussbaumer
811 d2b17e37 Guido Trotter
  parser = optparse.OptionParser(usage="%%prog [options...] <cluster...>",
812 d2b17e37 Guido Trotter
                                 prog=program)
813 1f7d3f7d René Nussbaumer
  parser.add_option(cli.DEBUG_OPT)
814 1f7d3f7d René Nussbaumer
  parser.add_option(cli.VERBOSE_OPT)
815 1f7d3f7d René Nussbaumer
  parser.add_option(PAUSE_PERIOD_OPT)
816 1a615be0 Stephen Shirley
  parser.add_option(GROUPS_OPT)
817 620a9c62 Guido Trotter
  parser.add_option(RESTART_OPT)
818 1fcd3b81 Guido Trotter
  parser.add_option(PARAMS_OPT)
819 1fb5f905 Andrea Spadaccini
  parser.add_option(SKIP_STOP_INSTANCES_OPT)
820 1f7d3f7d René Nussbaumer
821 1f7d3f7d René Nussbaumer
  (options, args) = parser.parse_args()
822 1f7d3f7d René Nussbaumer
823 1f7d3f7d René Nussbaumer
  SetupLogging(options)
824 1f7d3f7d René Nussbaumer
825 1f7d3f7d René Nussbaumer
  if not args:
826 1f7d3f7d René Nussbaumer
    parser.error("No clusters specified")
827 1f7d3f7d René Nussbaumer
828 1a615be0 Stephen Shirley
  cluster_merger = Merger(utils.UniqueSequence(args), options.pause_period,
829 1fb5f905 Andrea Spadaccini
                          options.groups, options.restart, options.params,
830 1fb5f905 Andrea Spadaccini
                          options.stop_instances)
831 1f7d3f7d René Nussbaumer
  try:
832 1f7d3f7d René Nussbaumer
    try:
833 1f7d3f7d René Nussbaumer
      cluster_merger.Setup()
834 1f7d3f7d René Nussbaumer
      cluster_merger.Merge()
835 1f7d3f7d René Nussbaumer
    except errors.GenericError, e:
836 1f7d3f7d René Nussbaumer
      logging.exception(e)
837 1f7d3f7d René Nussbaumer
      return constants.EXIT_FAILURE
838 1f7d3f7d René Nussbaumer
  finally:
839 1f7d3f7d René Nussbaumer
    cluster_merger.Cleanup()
840 1f7d3f7d René Nussbaumer
841 1f7d3f7d René Nussbaumer
  return constants.EXIT_SUCCESS
842 1f7d3f7d René Nussbaumer
843 1f7d3f7d René Nussbaumer
844 1f7d3f7d René Nussbaumer
if __name__ == "__main__":
845 1f7d3f7d René Nussbaumer
  sys.exit(main())