Statistics
| Branch: | Tag: | Revision:

root / lib / bootstrap.py @ d868edb4

History | View | Annotate | Download (15.4 kB)

1 a0c9f010 Michael Hanselmann
#
2 a0c9f010 Michael Hanselmann
#
3 a0c9f010 Michael Hanselmann
4 a0c9f010 Michael Hanselmann
# Copyright (C) 2006, 2007, 2008 Google Inc.
5 a0c9f010 Michael Hanselmann
#
6 a0c9f010 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 a0c9f010 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 a0c9f010 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 a0c9f010 Michael Hanselmann
# (at your option) any later version.
10 a0c9f010 Michael Hanselmann
#
11 a0c9f010 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 a0c9f010 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 a0c9f010 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 a0c9f010 Michael Hanselmann
# General Public License for more details.
15 a0c9f010 Michael Hanselmann
#
16 a0c9f010 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 a0c9f010 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 a0c9f010 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 a0c9f010 Michael Hanselmann
# 02110-1301, USA.
20 a0c9f010 Michael Hanselmann
21 a0c9f010 Michael Hanselmann
22 a0c9f010 Michael Hanselmann
"""Functions to bootstrap a new cluster.
23 a0c9f010 Michael Hanselmann

24 a0c9f010 Michael Hanselmann
"""
25 a0c9f010 Michael Hanselmann
26 a0c9f010 Michael Hanselmann
import os
27 a0c9f010 Michael Hanselmann
import os.path
28 a0c9f010 Michael Hanselmann
import sha
29 a0c9f010 Michael Hanselmann
import re
30 b1b6ea87 Iustin Pop
import logging
31 a0c9f010 Michael Hanselmann
32 a0c9f010 Michael Hanselmann
from ganeti import rpc
33 a0c9f010 Michael Hanselmann
from ganeti import ssh
34 a0c9f010 Michael Hanselmann
from ganeti import utils
35 a0c9f010 Michael Hanselmann
from ganeti import errors
36 a0c9f010 Michael Hanselmann
from ganeti import config
37 a0c9f010 Michael Hanselmann
from ganeti import constants
38 b9eeeb02 Michael Hanselmann
from ganeti import objects
39 a0c9f010 Michael Hanselmann
from ganeti import ssconf
40 a0c9f010 Michael Hanselmann
41 72737a7f Iustin Pop
from ganeti.rpc import RpcRunner
42 a0c9f010 Michael Hanselmann
43 a0c9f010 Michael Hanselmann
def _InitSSHSetup(node):
44 a0c9f010 Michael Hanselmann
  """Setup the SSH configuration for the cluster.
45 a0c9f010 Michael Hanselmann

46 a0c9f010 Michael Hanselmann

47 a0c9f010 Michael Hanselmann
  This generates a dsa keypair for root, adds the pub key to the
48 a0c9f010 Michael Hanselmann
  permitted hosts and adds the hostkey to its own known hosts.
49 a0c9f010 Michael Hanselmann

50 a0c9f010 Michael Hanselmann
  Args:
51 a0c9f010 Michael Hanselmann
    node: the name of this host as a fqdn
52 a0c9f010 Michael Hanselmann

53 a0c9f010 Michael Hanselmann
  """
54 a0c9f010 Michael Hanselmann
  priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
55 a0c9f010 Michael Hanselmann
56 a0c9f010 Michael Hanselmann
  for name in priv_key, pub_key:
57 a0c9f010 Michael Hanselmann
    if os.path.exists(name):
58 a0c9f010 Michael Hanselmann
      utils.CreateBackup(name)
59 a0c9f010 Michael Hanselmann
    utils.RemoveFile(name)
60 a0c9f010 Michael Hanselmann
61 a0c9f010 Michael Hanselmann
  result = utils.RunCmd(["ssh-keygen", "-t", "dsa",
62 a0c9f010 Michael Hanselmann
                         "-f", priv_key,
63 a0c9f010 Michael Hanselmann
                         "-q", "-N", ""])
64 a0c9f010 Michael Hanselmann
  if result.failed:
65 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("Could not generate ssh keypair, error %s" %
66 a0c9f010 Michael Hanselmann
                             result.output)
67 a0c9f010 Michael Hanselmann
68 a0c9f010 Michael Hanselmann
  f = open(pub_key, 'r')
69 a0c9f010 Michael Hanselmann
  try:
70 a0c9f010 Michael Hanselmann
    utils.AddAuthorizedKey(auth_keys, f.read(8192))
71 a0c9f010 Michael Hanselmann
  finally:
72 a0c9f010 Michael Hanselmann
    f.close()
73 a0c9f010 Michael Hanselmann
74 a0c9f010 Michael Hanselmann
75 d23ef431 Michael Hanselmann
def _InitGanetiServerSetup():
76 a0c9f010 Michael Hanselmann
  """Setup the necessary configuration for the initial node daemon.
77 a0c9f010 Michael Hanselmann

78 a0c9f010 Michael Hanselmann
  This creates the nodepass file containing the shared password for
79 a0c9f010 Michael Hanselmann
  the cluster and also generates the SSL certificate.
80 a0c9f010 Michael Hanselmann

81 a0c9f010 Michael Hanselmann
  """
82 a0c9f010 Michael Hanselmann
  # Create pseudo random password
83 33081d90 Iustin Pop
  randpass = utils.GenerateSecret()
84 d23ef431 Michael Hanselmann
85 d23ef431 Michael Hanselmann
  # and write it into the config file
86 d23ef431 Michael Hanselmann
  utils.WriteFile(constants.CLUSTER_PASSWORD_FILE,
87 d23ef431 Michael Hanselmann
                  data="%s\n" % randpass, mode=0400)
88 a0c9f010 Michael Hanselmann
89 a0c9f010 Michael Hanselmann
  result = utils.RunCmd(["openssl", "req", "-new", "-newkey", "rsa:1024",
90 a0c9f010 Michael Hanselmann
                         "-days", str(365*5), "-nodes", "-x509",
91 a0c9f010 Michael Hanselmann
                         "-keyout", constants.SSL_CERT_FILE,
92 a0c9f010 Michael Hanselmann
                         "-out", constants.SSL_CERT_FILE, "-batch"])
93 a0c9f010 Michael Hanselmann
  if result.failed:
94 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("could not generate server ssl cert, command"
95 a0c9f010 Michael Hanselmann
                             " %s had exitcode %s and error message %s" %
96 a0c9f010 Michael Hanselmann
                             (result.cmd, result.exit_code, result.output))
97 a0c9f010 Michael Hanselmann
98 a0c9f010 Michael Hanselmann
  os.chmod(constants.SSL_CERT_FILE, 0400)
99 a0c9f010 Michael Hanselmann
100 a0c9f010 Michael Hanselmann
  result = utils.RunCmd([constants.NODE_INITD_SCRIPT, "restart"])
101 a0c9f010 Michael Hanselmann
102 a0c9f010 Michael Hanselmann
  if result.failed:
103 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("Could not start the node daemon, command %s"
104 a0c9f010 Michael Hanselmann
                             " had exitcode %s and error %s" %
105 a0c9f010 Michael Hanselmann
                             (result.cmd, result.exit_code, result.output))
106 a0c9f010 Michael Hanselmann
107 a0c9f010 Michael Hanselmann
108 4342e89b Alexander Schreiber
def InitCluster(cluster_name, mac_prefix, def_bridge,
109 a0c9f010 Michael Hanselmann
                master_netdev, file_storage_dir,
110 a0c9f010 Michael Hanselmann
                secondary_ip=None,
111 ea3a925f Alexander Schreiber
                vg_name=None, beparams=None, hvparams=None,
112 02691904 Alexander Schreiber
                enabled_hypervisors=None, default_hypervisor=None):
113 a0c9f010 Michael Hanselmann
  """Initialise the cluster.
114 a0c9f010 Michael Hanselmann

115 a0c9f010 Michael Hanselmann
  """
116 a0c9f010 Michael Hanselmann
  if config.ConfigWriter.IsCluster():
117 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Cluster is already initialised")
118 a0c9f010 Michael Hanselmann
119 a0c9f010 Michael Hanselmann
  hostname = utils.HostInfo()
120 a0c9f010 Michael Hanselmann
121 a0c9f010 Michael Hanselmann
  if hostname.ip.startswith("127."):
122 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("This host's IP resolves to the private"
123 a0c9f010 Michael Hanselmann
                               " range (%s). Please fix DNS or %s." %
124 a0c9f010 Michael Hanselmann
                               (hostname.ip, constants.ETC_HOSTS))
125 a0c9f010 Michael Hanselmann
126 caad16e2 Iustin Pop
  if not utils.OwnIpAddress(hostname.ip):
127 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Inconsistency: this host's name resolves"
128 a0c9f010 Michael Hanselmann
                               " to %s,\nbut this ip address does not"
129 a0c9f010 Michael Hanselmann
                               " belong to this host."
130 a0c9f010 Michael Hanselmann
                               " Aborting." % hostname.ip)
131 a0c9f010 Michael Hanselmann
132 a0c9f010 Michael Hanselmann
  clustername = utils.HostInfo(cluster_name)
133 a0c9f010 Michael Hanselmann
134 a0c9f010 Michael Hanselmann
  if utils.TcpPing(clustername.ip, constants.DEFAULT_NODED_PORT,
135 a0c9f010 Michael Hanselmann
                   timeout=5):
136 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Cluster IP already active. Aborting.")
137 a0c9f010 Michael Hanselmann
138 a0c9f010 Michael Hanselmann
  if secondary_ip:
139 a0c9f010 Michael Hanselmann
    if not utils.IsValidIP(secondary_ip):
140 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("Invalid secondary ip given")
141 a0c9f010 Michael Hanselmann
    if (secondary_ip != hostname.ip and
142 caad16e2 Iustin Pop
        not utils.OwnIpAddress(secondary_ip)):
143 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("You gave %s as secondary IP,"
144 a0c9f010 Michael Hanselmann
                                 " but it does not belong to this host." %
145 a0c9f010 Michael Hanselmann
                                 secondary_ip)
146 b9eeeb02 Michael Hanselmann
  else:
147 b9eeeb02 Michael Hanselmann
    secondary_ip = hostname.ip
148 a0c9f010 Michael Hanselmann
149 a0c9f010 Michael Hanselmann
  if vg_name is not None:
150 a0c9f010 Michael Hanselmann
    # Check if volume group is valid
151 a0c9f010 Michael Hanselmann
    vgstatus = utils.CheckVolumeGroupSize(utils.ListVolumeGroups(), vg_name,
152 a0c9f010 Michael Hanselmann
                                          constants.MIN_VG_SIZE)
153 a0c9f010 Michael Hanselmann
    if vgstatus:
154 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("Error: %s\nspecify --no-lvm-storage if"
155 a0c9f010 Michael Hanselmann
                                 " you are not using lvm" % vgstatus)
156 a0c9f010 Michael Hanselmann
157 a0c9f010 Michael Hanselmann
  file_storage_dir = os.path.normpath(file_storage_dir)
158 a0c9f010 Michael Hanselmann
159 a0c9f010 Michael Hanselmann
  if not os.path.isabs(file_storage_dir):
160 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("The file storage directory you passed is"
161 a0c9f010 Michael Hanselmann
                               " not an absolute path.")
162 a0c9f010 Michael Hanselmann
163 a0c9f010 Michael Hanselmann
  if not os.path.exists(file_storage_dir):
164 a0c9f010 Michael Hanselmann
    try:
165 a0c9f010 Michael Hanselmann
      os.makedirs(file_storage_dir, 0750)
166 a0c9f010 Michael Hanselmann
    except OSError, err:
167 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("Cannot create file storage directory"
168 a0c9f010 Michael Hanselmann
                                 " '%s': %s" %
169 a0c9f010 Michael Hanselmann
                                 (file_storage_dir, err))
170 a0c9f010 Michael Hanselmann
171 a0c9f010 Michael Hanselmann
  if not os.path.isdir(file_storage_dir):
172 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("The file storage directory '%s' is not"
173 a0c9f010 Michael Hanselmann
                               " a directory." % file_storage_dir)
174 a0c9f010 Michael Hanselmann
175 a0c9f010 Michael Hanselmann
  if not re.match("^[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}$", mac_prefix):
176 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Invalid mac prefix given '%s'" % mac_prefix)
177 a0c9f010 Michael Hanselmann
178 a0c9f010 Michael Hanselmann
  result = utils.RunCmd(["ip", "link", "show", "dev", master_netdev])
179 a0c9f010 Michael Hanselmann
  if result.failed:
180 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Invalid master netdev given (%s): '%s'" %
181 a0c9f010 Michael Hanselmann
                               (master_netdev,
182 a0c9f010 Michael Hanselmann
                                result.output.strip()))
183 a0c9f010 Michael Hanselmann
184 a0c9f010 Michael Hanselmann
  if not (os.path.isfile(constants.NODE_INITD_SCRIPT) and
185 a0c9f010 Michael Hanselmann
          os.access(constants.NODE_INITD_SCRIPT, os.X_OK)):
186 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Init.d script '%s' missing or not"
187 a0c9f010 Michael Hanselmann
                               " executable." % constants.NODE_INITD_SCRIPT)
188 a0c9f010 Michael Hanselmann
189 a0c9f010 Michael Hanselmann
  # set up the inter-node password and certificate
190 d23ef431 Michael Hanselmann
  _InitGanetiServerSetup()
191 a0c9f010 Michael Hanselmann
192 a0c9f010 Michael Hanselmann
  # set up ssh config and /etc/hosts
193 a0c9f010 Michael Hanselmann
  f = open(constants.SSH_HOST_RSA_PUB, 'r')
194 a0c9f010 Michael Hanselmann
  try:
195 a0c9f010 Michael Hanselmann
    sshline = f.read()
196 a0c9f010 Michael Hanselmann
  finally:
197 a0c9f010 Michael Hanselmann
    f.close()
198 a0c9f010 Michael Hanselmann
  sshkey = sshline.split(" ")[1]
199 a0c9f010 Michael Hanselmann
200 a0c9f010 Michael Hanselmann
  utils.AddHostToEtcHosts(hostname.name)
201 a0c9f010 Michael Hanselmann
  _InitSSHSetup(hostname.name)
202 a0c9f010 Michael Hanselmann
203 a0c9f010 Michael Hanselmann
  # init of cluster config file
204 b9eeeb02 Michael Hanselmann
  cluster_config = objects.Cluster(
205 b9eeeb02 Michael Hanselmann
    serial_no=1,
206 b9eeeb02 Michael Hanselmann
    rsahostkeypub=sshkey,
207 b9eeeb02 Michael Hanselmann
    highest_used_port=(constants.FIRST_DRBD_PORT - 1),
208 b9eeeb02 Michael Hanselmann
    mac_prefix=mac_prefix,
209 b9eeeb02 Michael Hanselmann
    volume_group_name=vg_name,
210 b9eeeb02 Michael Hanselmann
    default_bridge=def_bridge,
211 b9eeeb02 Michael Hanselmann
    tcpudp_port_pool=set(),
212 f6bd6e98 Michael Hanselmann
    master_node=hostname.name,
213 f6bd6e98 Michael Hanselmann
    master_ip=clustername.ip,
214 f6bd6e98 Michael Hanselmann
    master_netdev=master_netdev,
215 f6bd6e98 Michael Hanselmann
    cluster_name=clustername.name,
216 f6bd6e98 Michael Hanselmann
    file_storage_dir=file_storage_dir,
217 ea3a925f Alexander Schreiber
    enabled_hypervisors=enabled_hypervisors,
218 02691904 Alexander Schreiber
    default_hypervisor=default_hypervisor,
219 ea3a925f Alexander Schreiber
    beparams={constants.BEGR_DEFAULT: beparams},
220 ea3a925f Alexander Schreiber
    hvparams=hvparams,
221 b9eeeb02 Michael Hanselmann
    )
222 b9eeeb02 Michael Hanselmann
  master_node_config = objects.Node(name=hostname.name,
223 b9eeeb02 Michael Hanselmann
                                    primary_ip=hostname.ip,
224 b9eeeb02 Michael Hanselmann
                                    secondary_ip=secondary_ip)
225 a0c9f010 Michael Hanselmann
226 02f99608 Oleksiy Mishchenko
  cfg = InitConfig(constants.CONFIG_VERSION,
227 02f99608 Oleksiy Mishchenko
                   cluster_config, master_node_config)
228 7688d0d3 Michael Hanselmann
  ssh.WriteKnownHostsFile(cfg, constants.SSH_KNOWN_HOSTS_FILE)
229 827f753e Guido Trotter
230 b3f1cf6f Iustin Pop
  # start the master ip
231 b3f1cf6f Iustin Pop
  # TODO: Review rpc call from bootstrap
232 72737a7f Iustin Pop
  RpcRunner.call_node_start_master(hostname.name, True)
233 b3f1cf6f Iustin Pop
234 b1b6ea87 Iustin Pop
235 02f99608 Oleksiy Mishchenko
def InitConfig(version, cluster_config, master_node_config,
236 02f99608 Oleksiy Mishchenko
               cfg_file=constants.CLUSTER_CONF_FILE):
237 7b3a8fb5 Iustin Pop
  """Create the initial cluster configuration.
238 7b3a8fb5 Iustin Pop

239 7b3a8fb5 Iustin Pop
  It will contain the current node, which will also be the master
240 7b3a8fb5 Iustin Pop
  node, and no instances.
241 7b3a8fb5 Iustin Pop

242 7b3a8fb5 Iustin Pop
  @type version: int
243 7b3a8fb5 Iustin Pop
  @param version: Configuration version
244 7b3a8fb5 Iustin Pop
  @type cluster_config: objects.Cluster
245 7b3a8fb5 Iustin Pop
  @param cluster_config: Cluster configuration
246 7b3a8fb5 Iustin Pop
  @type master_node_config: objects.Node
247 7b3a8fb5 Iustin Pop
  @param master_node_config: Master node configuration
248 7b3a8fb5 Iustin Pop
  @type file_name: string
249 7b3a8fb5 Iustin Pop
  @param file_name: Configuration file path
250 7b3a8fb5 Iustin Pop

251 7b3a8fb5 Iustin Pop
  @rtype: ssconf.SimpleConfigWriter
252 7b3a8fb5 Iustin Pop
  @returns: Initialized config instance
253 7b3a8fb5 Iustin Pop

254 7b3a8fb5 Iustin Pop
  """
255 7b3a8fb5 Iustin Pop
  nodes = {
256 7b3a8fb5 Iustin Pop
    master_node_config.name: master_node_config,
257 7b3a8fb5 Iustin Pop
    }
258 7b3a8fb5 Iustin Pop
259 7b3a8fb5 Iustin Pop
  config_data = objects.ConfigData(version=version,
260 7b3a8fb5 Iustin Pop
                                   cluster=cluster_config,
261 7b3a8fb5 Iustin Pop
                                   nodes=nodes,
262 7b3a8fb5 Iustin Pop
                                   instances={},
263 7b3a8fb5 Iustin Pop
                                   serial_no=1)
264 7b3a8fb5 Iustin Pop
  cfg = ssconf.SimpleConfigWriter.FromDict(config_data.ToDict(), cfg_file)
265 7b3a8fb5 Iustin Pop
  cfg.Save()
266 7b3a8fb5 Iustin Pop
267 7b3a8fb5 Iustin Pop
  return cfg
268 02f99608 Oleksiy Mishchenko
269 02f99608 Oleksiy Mishchenko
270 140aa4a8 Iustin Pop
def FinalizeClusterDestroy(master):
271 140aa4a8 Iustin Pop
  """Execute the last steps of cluster destroy
272 140aa4a8 Iustin Pop

273 140aa4a8 Iustin Pop
  This function shuts down all the daemons, completing the destroy
274 140aa4a8 Iustin Pop
  begun in cmdlib.LUDestroyOpcode.
275 140aa4a8 Iustin Pop

276 140aa4a8 Iustin Pop
  """
277 72737a7f Iustin Pop
  if not RpcRunner.call_node_stop_master(master, True):
278 140aa4a8 Iustin Pop
    logging.warning("Could not disable the master role")
279 72737a7f Iustin Pop
  if not RpcRunner.call_node_leave_cluster(master):
280 140aa4a8 Iustin Pop
    logging.warning("Could not shutdown the node daemon and cleanup the node")
281 140aa4a8 Iustin Pop
282 140aa4a8 Iustin Pop
283 c4b6c29c Michael Hanselmann
def SetupNodeDaemon(node, ssh_key_check):
284 827f753e Guido Trotter
  """Add a node to the cluster.
285 827f753e Guido Trotter

286 b1b6ea87 Iustin Pop
  This function must be called before the actual opcode, and will ssh
287 b1b6ea87 Iustin Pop
  to the remote node, copy the needed files, and start ganeti-noded,
288 b1b6ea87 Iustin Pop
  allowing the master to do the rest via normal rpc calls.
289 827f753e Guido Trotter

290 827f753e Guido Trotter
  Args:
291 827f753e Guido Trotter
    node: fully qualified domain name for the new node
292 827f753e Guido Trotter

293 827f753e Guido Trotter
  """
294 7688d0d3 Michael Hanselmann
  cfg = ssconf.SimpleConfigReader()
295 6b0469d2 Iustin Pop
  sshrunner = ssh.SshRunner(cfg.GetClusterName())
296 d23ef431 Michael Hanselmann
  gntpass = utils.GetNodeDaemonPassword()
297 827f753e Guido Trotter
  if not re.match('^[a-zA-Z0-9.]{1,64}$', gntpass):
298 827f753e Guido Trotter
    raise errors.OpExecError("ganeti password corruption detected")
299 827f753e Guido Trotter
  f = open(constants.SSL_CERT_FILE)
300 827f753e Guido Trotter
  try:
301 827f753e Guido Trotter
    gntpem = f.read(8192)
302 827f753e Guido Trotter
  finally:
303 827f753e Guido Trotter
    f.close()
304 827f753e Guido Trotter
  # in the base64 pem encoding, neither '!' nor '.' are valid chars,
305 827f753e Guido Trotter
  # so we use this to detect an invalid certificate; as long as the
306 827f753e Guido Trotter
  # cert doesn't contain this, the here-document will be correctly
307 827f753e Guido Trotter
  # parsed by the shell sequence below
308 827f753e Guido Trotter
  if re.search('^!EOF\.', gntpem, re.MULTILINE):
309 827f753e Guido Trotter
    raise errors.OpExecError("invalid PEM encoding in the SSL certificate")
310 827f753e Guido Trotter
  if not gntpem.endswith("\n"):
311 827f753e Guido Trotter
    raise errors.OpExecError("PEM must end with newline")
312 827f753e Guido Trotter
313 827f753e Guido Trotter
  # set up inter-node password and certificate and restarts the node daemon
314 827f753e Guido Trotter
  # and then connect with ssh to set password and start ganeti-noded
315 827f753e Guido Trotter
  # note that all the below variables are sanitized at this point,
316 827f753e Guido Trotter
  # either by being constants or by the checks above
317 827f753e Guido Trotter
  mycommand = ("umask 077 && "
318 827f753e Guido Trotter
               "echo '%s' > '%s' && "
319 827f753e Guido Trotter
               "cat > '%s' << '!EOF.' && \n"
320 827f753e Guido Trotter
               "%s!EOF.\n%s restart" %
321 d23ef431 Michael Hanselmann
               (gntpass, constants.CLUSTER_PASSWORD_FILE,
322 827f753e Guido Trotter
                constants.SSL_CERT_FILE, gntpem,
323 827f753e Guido Trotter
                constants.NODE_INITD_SCRIPT))
324 827f753e Guido Trotter
325 c4b6c29c Michael Hanselmann
  result = sshrunner.Run(node, 'root', mycommand, batch=False,
326 c4b6c29c Michael Hanselmann
                         ask_key=ssh_key_check,
327 c4b6c29c Michael Hanselmann
                         use_cluster_key=False,
328 c4b6c29c Michael Hanselmann
                         strict_host_check=ssh_key_check)
329 827f753e Guido Trotter
  if result.failed:
330 827f753e Guido Trotter
    raise errors.OpExecError("Remote command on node %s, error: %s,"
331 827f753e Guido Trotter
                             " output: %s" %
332 827f753e Guido Trotter
                             (node, result.fail_reason, result.output))
333 827f753e Guido Trotter
334 827f753e Guido Trotter
  return 0
335 827f753e Guido Trotter
336 b1b6ea87 Iustin Pop
337 b1b6ea87 Iustin Pop
def MasterFailover():
338 b1b6ea87 Iustin Pop
  """Failover the master node.
339 b1b6ea87 Iustin Pop

340 b1b6ea87 Iustin Pop
  This checks that we are not already the master, and will cause the
341 b1b6ea87 Iustin Pop
  current master to cease being master, and the non-master to become
342 b1b6ea87 Iustin Pop
  new master.
343 b1b6ea87 Iustin Pop

344 b1b6ea87 Iustin Pop
  """
345 d23ef431 Michael Hanselmann
  cfg = ssconf.SimpleConfigWriter()
346 b1b6ea87 Iustin Pop
347 b1b6ea87 Iustin Pop
  new_master = utils.HostInfo().name
348 d23ef431 Michael Hanselmann
  old_master = cfg.GetMasterNode()
349 d5927e48 Iustin Pop
  node_list = cfg.GetNodeList()
350 b1b6ea87 Iustin Pop
351 b1b6ea87 Iustin Pop
  if old_master == new_master:
352 b1b6ea87 Iustin Pop
    raise errors.OpPrereqError("This commands must be run on the node"
353 b1b6ea87 Iustin Pop
                               " where you want the new master to be."
354 b1b6ea87 Iustin Pop
                               " %s is already the master" %
355 b1b6ea87 Iustin Pop
                               old_master)
356 d5927e48 Iustin Pop
357 d5927e48 Iustin Pop
  vote_list = GatherMasterVotes(node_list)
358 d5927e48 Iustin Pop
359 d5927e48 Iustin Pop
  if vote_list:
360 d5927e48 Iustin Pop
    voted_master = vote_list[0][0]
361 d5927e48 Iustin Pop
    if voted_master is None:
362 d5927e48 Iustin Pop
      raise errors.OpPrereqError("Cluster is inconsistent, most nodes did not"
363 d5927e48 Iustin Pop
                                 " respond.")
364 d5927e48 Iustin Pop
    elif voted_master != old_master:
365 d5927e48 Iustin Pop
      raise errors.OpPrereqError("I have wrong configuration, I believe the"
366 d5927e48 Iustin Pop
                                 " master is %s but the other nodes voted for"
367 d5927e48 Iustin Pop
                                 " %s. Please resync the configuration of"
368 d5927e48 Iustin Pop
                                 " this node." % (old_master, voted_master))
369 b1b6ea87 Iustin Pop
  # end checks
370 b1b6ea87 Iustin Pop
371 b1b6ea87 Iustin Pop
  rcode = 0
372 b1b6ea87 Iustin Pop
373 d5927e48 Iustin Pop
  logging.info("Setting master to %s, old master: %s", new_master, old_master)
374 b1b6ea87 Iustin Pop
375 72737a7f Iustin Pop
  if not RpcRunner.call_node_stop_master(old_master, True):
376 d5927e48 Iustin Pop
    logging.error("Could not disable the master role on the old master"
377 b1b6ea87 Iustin Pop
                 " %s, please disable manually", old_master)
378 b1b6ea87 Iustin Pop
379 d23ef431 Michael Hanselmann
  cfg.SetMasterNode(new_master)
380 d23ef431 Michael Hanselmann
  cfg.Save()
381 b1b6ea87 Iustin Pop
382 d23ef431 Michael Hanselmann
  # Here we have a phase where no master should be running
383 b1b6ea87 Iustin Pop
384 72737a7f Iustin Pop
  if not RpcRunner.call_upload_file(cfg.GetNodeList(),
385 72737a7f Iustin Pop
                                    constants.CLUSTER_CONF_FILE):
386 d5927e48 Iustin Pop
    logging.error("Could not distribute the new configuration"
387 3b9e6a30 Iustin Pop
                  " to the other nodes, please check.")
388 b1b6ea87 Iustin Pop
389 d5927e48 Iustin Pop
390 72737a7f Iustin Pop
  if not RpcRunner.call_node_start_master(new_master, True):
391 d5927e48 Iustin Pop
    logging.error("Could not start the master role on the new master"
392 b1b6ea87 Iustin Pop
                  " %s, please check", new_master)
393 b1b6ea87 Iustin Pop
    rcode = 1
394 b1b6ea87 Iustin Pop
395 b1b6ea87 Iustin Pop
  return rcode
396 d7cdb55d Iustin Pop
397 d7cdb55d Iustin Pop
398 d7cdb55d Iustin Pop
def GatherMasterVotes(node_list):
399 d7cdb55d Iustin Pop
  """Check the agreement on who is the master.
400 d7cdb55d Iustin Pop

401 d7cdb55d Iustin Pop
  This function will return a list of (node, number of votes), ordered
402 d7cdb55d Iustin Pop
  by the number of votes. Errors will be denoted by the key 'None'.
403 d7cdb55d Iustin Pop

404 d7cdb55d Iustin Pop
  Note that the sum of votes is the number of nodes this machine
405 d7cdb55d Iustin Pop
  knows, whereas the number of entries in the list could be different
406 d7cdb55d Iustin Pop
  (if some nodes vote for another master).
407 d7cdb55d Iustin Pop

408 d7cdb55d Iustin Pop
  We remove ourselves from the list since we know that (bugs aside)
409 d7cdb55d Iustin Pop
  since we use the same source for configuration information for both
410 d7cdb55d Iustin Pop
  backend and boostrap, we'll always vote for ourselves.
411 d7cdb55d Iustin Pop

412 d7cdb55d Iustin Pop
  @type node_list: list
413 d7cdb55d Iustin Pop
  @param node_list: the list of nodes to query for master info; the current
414 d7cdb55d Iustin Pop
      node wil be removed if it is in the list
415 d7cdb55d Iustin Pop
  @rtype: list
416 d7cdb55d Iustin Pop
  @return: list of (node, votes)
417 d7cdb55d Iustin Pop

418 d7cdb55d Iustin Pop
  """
419 d7cdb55d Iustin Pop
  myself = utils.HostInfo().name
420 d7cdb55d Iustin Pop
  try:
421 d7cdb55d Iustin Pop
    node_list.remove(myself)
422 d7cdb55d Iustin Pop
  except ValueError:
423 d7cdb55d Iustin Pop
    pass
424 d7cdb55d Iustin Pop
  if not node_list:
425 d7cdb55d Iustin Pop
    # no nodes left (eventually after removing myself)
426 d7cdb55d Iustin Pop
    return []
427 d7cdb55d Iustin Pop
  results = rpc.RpcRunner.call_master_info(node_list)
428 d7cdb55d Iustin Pop
  if not isinstance(results, dict):
429 d7cdb55d Iustin Pop
    # this should not happen (unless internal error in rpc)
430 d7cdb55d Iustin Pop
    logging.critical("Can't complete rpc call, aborting master startup")
431 d7cdb55d Iustin Pop
    return [(None, len(node_list))]
432 d7cdb55d Iustin Pop
  positive = negative = 0
433 d7cdb55d Iustin Pop
  other_masters = {}
434 d7cdb55d Iustin Pop
  votes = {}
435 d7cdb55d Iustin Pop
  for node in results:
436 d7cdb55d Iustin Pop
    if not isinstance(results[node], (tuple, list)) or len(results[node]) < 3:
437 d7cdb55d Iustin Pop
      # here the rpc layer should have already logged errors
438 d7cdb55d Iustin Pop
      if None not in votes:
439 d7cdb55d Iustin Pop
        votes[None] = 0
440 d7cdb55d Iustin Pop
      votes[None] += 1
441 d7cdb55d Iustin Pop
      continue
442 d7cdb55d Iustin Pop
    master_node = results[node][2]
443 d7cdb55d Iustin Pop
    if master_node not in votes:
444 d7cdb55d Iustin Pop
      votes[master_node] = 0
445 d7cdb55d Iustin Pop
    votes[master_node] += 1
446 d7cdb55d Iustin Pop
447 d7cdb55d Iustin Pop
  vote_list = [v for v in votes.items()]
448 d7cdb55d Iustin Pop
  # sort first on number of votes then on name, since we want None
449 d7cdb55d Iustin Pop
  # sorted later if we have the half of the nodes not responding, and
450 d7cdb55d Iustin Pop
  # half voting all for the same master
451 d7cdb55d Iustin Pop
  vote_list.sort(key=lambda x: (x[1], x[0]), reverse=True)
452 d7cdb55d Iustin Pop
453 d7cdb55d Iustin Pop
  return vote_list