Statistics
| Branch: | Tag: | Revision:

root / lib / bootstrap.py @ 39b0f0c2

History | View | Annotate | Download (23.2 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 re
29 b1b6ea87 Iustin Pop
import logging
30 c4415fd5 Michael Hanselmann
import tempfile
31 d693c864 Iustin Pop
import time
32 a0c9f010 Michael Hanselmann
33 a0c9f010 Michael Hanselmann
from ganeti import rpc
34 a0c9f010 Michael Hanselmann
from ganeti import ssh
35 a0c9f010 Michael Hanselmann
from ganeti import utils
36 a0c9f010 Michael Hanselmann
from ganeti import errors
37 a0c9f010 Michael Hanselmann
from ganeti import config
38 a0c9f010 Michael Hanselmann
from ganeti import constants
39 b9eeeb02 Michael Hanselmann
from ganeti import objects
40 a0c9f010 Michael Hanselmann
from ganeti import ssconf
41 a33848a5 Guido Trotter
from ganeti import serializer
42 a5728081 Guido Trotter
from ganeti import hypervisor
43 a0c9f010 Michael Hanselmann
44 e38220e4 Michael Hanselmann
45 531baf8e Iustin Pop
def _InitSSHSetup():
46 a0c9f010 Michael Hanselmann
  """Setup the SSH configuration for the cluster.
47 a0c9f010 Michael Hanselmann

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

51 a0c9f010 Michael Hanselmann
  """
52 a0c9f010 Michael Hanselmann
  priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
53 a0c9f010 Michael Hanselmann
54 a0c9f010 Michael Hanselmann
  for name in priv_key, pub_key:
55 a0c9f010 Michael Hanselmann
    if os.path.exists(name):
56 a0c9f010 Michael Hanselmann
      utils.CreateBackup(name)
57 a0c9f010 Michael Hanselmann
    utils.RemoveFile(name)
58 a0c9f010 Michael Hanselmann
59 a0c9f010 Michael Hanselmann
  result = utils.RunCmd(["ssh-keygen", "-t", "dsa",
60 a0c9f010 Michael Hanselmann
                         "-f", priv_key,
61 a0c9f010 Michael Hanselmann
                         "-q", "-N", ""])
62 a0c9f010 Michael Hanselmann
  if result.failed:
63 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("Could not generate ssh keypair, error %s" %
64 a0c9f010 Michael Hanselmann
                             result.output)
65 a0c9f010 Michael Hanselmann
66 7a0156dc Luca Bigliardi
  utils.AddAuthorizedKey(auth_keys, utils.ReadFile(pub_key))
67 a0c9f010 Michael Hanselmann
68 a0c9f010 Michael Hanselmann
69 cd34faf2 Michael Hanselmann
def GenerateSelfSignedSslCert(file_name, validity=(365 * 5)):
70 40a97d80 Michael Hanselmann
  """Generates a self-signed SSL certificate.
71 a0c9f010 Michael Hanselmann

72 40a97d80 Michael Hanselmann
  @type file_name: str
73 40a97d80 Michael Hanselmann
  @param file_name: Path to output file
74 40a97d80 Michael Hanselmann
  @type validity: int
75 40a97d80 Michael Hanselmann
  @param validity: Validity for certificate in days
76 a0c9f010 Michael Hanselmann

77 a0c9f010 Michael Hanselmann
  """
78 c4415fd5 Michael Hanselmann
  (fd, tmp_file_name) = tempfile.mkstemp(dir=os.path.dirname(file_name))
79 c4415fd5 Michael Hanselmann
  try:
80 88828491 Michael Hanselmann
    try:
81 88828491 Michael Hanselmann
      # Set permissions before writing key
82 88828491 Michael Hanselmann
      os.chmod(tmp_file_name, 0600)
83 88828491 Michael Hanselmann
84 88828491 Michael Hanselmann
      result = utils.RunCmd(["openssl", "req", "-new", "-newkey", "rsa:1024",
85 88828491 Michael Hanselmann
                             "-days", str(validity), "-nodes", "-x509",
86 88828491 Michael Hanselmann
                             "-keyout", tmp_file_name, "-out", tmp_file_name,
87 88828491 Michael Hanselmann
                             "-batch"])
88 88828491 Michael Hanselmann
      if result.failed:
89 88828491 Michael Hanselmann
        raise errors.OpExecError("Could not generate SSL certificate, command"
90 88828491 Michael Hanselmann
                                 " %s had exitcode %s and error message %s" %
91 88828491 Michael Hanselmann
                                 (result.cmd, result.exit_code, result.output))
92 88828491 Michael Hanselmann
93 88828491 Michael Hanselmann
      # Make read-only
94 88828491 Michael Hanselmann
      os.chmod(tmp_file_name, 0400)
95 88828491 Michael Hanselmann
96 88828491 Michael Hanselmann
      os.rename(tmp_file_name, file_name)
97 88828491 Michael Hanselmann
    finally:
98 88828491 Michael Hanselmann
      utils.RemoveFile(tmp_file_name)
99 c4415fd5 Michael Hanselmann
  finally:
100 88828491 Michael Hanselmann
    os.close(fd)
101 40a97d80 Michael Hanselmann
102 40a97d80 Michael Hanselmann
103 c008906b Michael Hanselmann
def GenerateHmacKey(file_name):
104 c008906b Michael Hanselmann
  """Writes a new HMAC key.
105 c008906b Michael Hanselmann

106 c008906b Michael Hanselmann
  @type file_name: str
107 c008906b Michael Hanselmann
  @param file_name: Path to output file
108 c008906b Michael Hanselmann

109 c008906b Michael Hanselmann
  """
110 43575108 Michael Hanselmann
  utils.WriteFile(file_name, data="%s\n" % utils.GenerateSecret(), mode=0400,
111 43575108 Michael Hanselmann
                  backup=True)
112 43575108 Michael Hanselmann
113 43575108 Michael Hanselmann
114 6b7d5878 Michael Hanselmann
def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_confd_hmac_key,
115 aeefe835 Iustin Pop
                          rapi_cert_pem=None,
116 aeefe835 Iustin Pop
                          nodecert_file=constants.NODED_CERT_FILE,
117 aeefe835 Iustin Pop
                          rapicert_file=constants.RAPI_CERT_FILE,
118 aeefe835 Iustin Pop
                          hmackey_file=constants.CONFD_HMAC_KEY):
119 43575108 Michael Hanselmann
  """Updates the cluster certificates, keys and secrets.
120 43575108 Michael Hanselmann

121 43575108 Michael Hanselmann
  @type new_cluster_cert: bool
122 43575108 Michael Hanselmann
  @param new_cluster_cert: Whether to generate a new cluster certificate
123 43575108 Michael Hanselmann
  @type new_rapi_cert: bool
124 43575108 Michael Hanselmann
  @param new_rapi_cert: Whether to generate a new RAPI certificate
125 6b7d5878 Michael Hanselmann
  @type new_confd_hmac_key: bool
126 6b7d5878 Michael Hanselmann
  @param new_confd_hmac_key: Whether to generate a new HMAC key
127 43575108 Michael Hanselmann
  @type rapi_cert_pem: string
128 43575108 Michael Hanselmann
  @param rapi_cert_pem: New RAPI certificate in PEM format
129 aeefe835 Iustin Pop
  @type nodecert_file: string
130 aeefe835 Iustin Pop
  @param nodecert_file: optional override of the node cert file path
131 aeefe835 Iustin Pop
  @type rapicert_file: string
132 aeefe835 Iustin Pop
  @param rapicert_file: optional override of the rapi cert file path
133 aeefe835 Iustin Pop
  @type hmackey_file: string
134 aeefe835 Iustin Pop
  @param hmackey_file: optional override of the hmac key file path
135 43575108 Michael Hanselmann

136 43575108 Michael Hanselmann
  """
137 168c1de2 Michael Hanselmann
  # noded SSL certificate
138 aeefe835 Iustin Pop
  cluster_cert_exists = os.path.exists(nodecert_file)
139 43575108 Michael Hanselmann
  if new_cluster_cert or not cluster_cert_exists:
140 43575108 Michael Hanselmann
    if cluster_cert_exists:
141 aeefe835 Iustin Pop
      utils.CreateBackup(nodecert_file)
142 43575108 Michael Hanselmann
143 aeefe835 Iustin Pop
    logging.debug("Generating new cluster certificate at %s", nodecert_file)
144 aeefe835 Iustin Pop
    GenerateSelfSignedSslCert(nodecert_file)
145 43575108 Michael Hanselmann
146 6b7d5878 Michael Hanselmann
  # confd HMAC key
147 aeefe835 Iustin Pop
  if new_confd_hmac_key or not os.path.exists(hmackey_file):
148 aeefe835 Iustin Pop
    logging.debug("Writing new confd HMAC key to %s", hmackey_file)
149 aeefe835 Iustin Pop
    GenerateHmacKey(hmackey_file)
150 43575108 Michael Hanselmann
151 43575108 Michael Hanselmann
  # RAPI
152 aeefe835 Iustin Pop
  rapi_cert_exists = os.path.exists(rapicert_file)
153 43575108 Michael Hanselmann
154 43575108 Michael Hanselmann
  if rapi_cert_pem:
155 43575108 Michael Hanselmann
    # Assume rapi_pem contains a valid PEM-formatted certificate and key
156 aeefe835 Iustin Pop
    logging.debug("Writing RAPI certificate at %s", rapicert_file)
157 aeefe835 Iustin Pop
    utils.WriteFile(rapicert_file, data=rapi_cert_pem, backup=True)
158 43575108 Michael Hanselmann
159 43575108 Michael Hanselmann
  elif new_rapi_cert or not rapi_cert_exists:
160 43575108 Michael Hanselmann
    if rapi_cert_exists:
161 aeefe835 Iustin Pop
      utils.CreateBackup(rapicert_file)
162 43575108 Michael Hanselmann
163 aeefe835 Iustin Pop
    logging.debug("Generating new RAPI certificate at %s", rapicert_file)
164 aeefe835 Iustin Pop
    GenerateSelfSignedSslCert(rapicert_file)
165 c008906b Michael Hanselmann
166 c008906b Michael Hanselmann
167 8f215968 Michael Hanselmann
def _InitGanetiServerSetup(master_name):
168 40a97d80 Michael Hanselmann
  """Setup the necessary configuration for the initial node daemon.
169 40a97d80 Michael Hanselmann

170 40a97d80 Michael Hanselmann
  This creates the nodepass file containing the shared password for
171 40a97d80 Michael Hanselmann
  the cluster and also generates the SSL certificate.
172 40a97d80 Michael Hanselmann

173 40a97d80 Michael Hanselmann
  """
174 43575108 Michael Hanselmann
  # Generate cluster secrets
175 43575108 Michael Hanselmann
  GenerateClusterCrypto(True, False, False)
176 4a34c5cf Guido Trotter
177 f154a7a3 Michael Hanselmann
  result = utils.RunCmd([constants.DAEMON_UTIL, "start", constants.NODED])
178 a0c9f010 Michael Hanselmann
  if result.failed:
179 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("Could not start the node daemon, command %s"
180 a0c9f010 Michael Hanselmann
                             " had exitcode %s and error %s" %
181 a0c9f010 Michael Hanselmann
                             (result.cmd, result.exit_code, result.output))
182 a0c9f010 Michael Hanselmann
183 5627f375 Michael Hanselmann
  _WaitForNodeDaemon(master_name)
184 5627f375 Michael Hanselmann
185 5627f375 Michael Hanselmann
186 5627f375 Michael Hanselmann
def _WaitForNodeDaemon(node_name):
187 5627f375 Michael Hanselmann
  """Wait for node daemon to become responsive.
188 5627f375 Michael Hanselmann

189 5627f375 Michael Hanselmann
  """
190 d3833ebd Michael Hanselmann
  def _CheckNodeDaemon():
191 5627f375 Michael Hanselmann
    result = rpc.RpcRunner.call_version([node_name])[node_name]
192 d3833ebd Michael Hanselmann
    if result.fail_msg:
193 d3833ebd Michael Hanselmann
      raise utils.RetryAgain()
194 8f215968 Michael Hanselmann
195 d3833ebd Michael Hanselmann
  try:
196 d3833ebd Michael Hanselmann
    utils.Retry(_CheckNodeDaemon, 1.0, 10.0)
197 d3833ebd Michael Hanselmann
  except utils.RetryTimeout:
198 5627f375 Michael Hanselmann
    raise errors.OpExecError("Node daemon on %s didn't answer queries within"
199 5627f375 Michael Hanselmann
                             " 10 seconds" % node_name)
200 5627f375 Michael Hanselmann
201 a0c9f010 Michael Hanselmann
202 0e3baaf3 Iustin Pop
def _InitFileStorage(file_storage_dir):
203 0e3baaf3 Iustin Pop
  """Initialize if needed the file storage.
204 0e3baaf3 Iustin Pop

205 0e3baaf3 Iustin Pop
  @param file_storage_dir: the user-supplied value
206 0e3baaf3 Iustin Pop
  @return: either empty string (if file storage was disabled at build
207 0e3baaf3 Iustin Pop
      time) or the normalized path to the storage directory
208 0e3baaf3 Iustin Pop

209 0e3baaf3 Iustin Pop
  """
210 0e3baaf3 Iustin Pop
  if not constants.ENABLE_FILE_STORAGE:
211 0e3baaf3 Iustin Pop
    return ""
212 0e3baaf3 Iustin Pop
213 0e3baaf3 Iustin Pop
  file_storage_dir = os.path.normpath(file_storage_dir)
214 0e3baaf3 Iustin Pop
215 0e3baaf3 Iustin Pop
  if not os.path.isabs(file_storage_dir):
216 0e3baaf3 Iustin Pop
    raise errors.OpPrereqError("The file storage directory you passed is"
217 0e3baaf3 Iustin Pop
                               " not an absolute path.", errors.ECODE_INVAL)
218 0e3baaf3 Iustin Pop
219 0e3baaf3 Iustin Pop
  if not os.path.exists(file_storage_dir):
220 0e3baaf3 Iustin Pop
    try:
221 0e3baaf3 Iustin Pop
      os.makedirs(file_storage_dir, 0750)
222 0e3baaf3 Iustin Pop
    except OSError, err:
223 0e3baaf3 Iustin Pop
      raise errors.OpPrereqError("Cannot create file storage directory"
224 0e3baaf3 Iustin Pop
                                 " '%s': %s" % (file_storage_dir, err),
225 0e3baaf3 Iustin Pop
                                 errors.ECODE_ENVIRON)
226 0e3baaf3 Iustin Pop
227 0e3baaf3 Iustin Pop
  if not os.path.isdir(file_storage_dir):
228 0e3baaf3 Iustin Pop
    raise errors.OpPrereqError("The file storage directory '%s' is not"
229 0e3baaf3 Iustin Pop
                               " a directory." % file_storage_dir,
230 0e3baaf3 Iustin Pop
                               errors.ECODE_ENVIRON)
231 0e3baaf3 Iustin Pop
  return file_storage_dir
232 0e3baaf3 Iustin Pop
233 0e3baaf3 Iustin Pop
234 ec0652ad Guido Trotter
def InitCluster(cluster_name, mac_prefix,
235 ce735215 Guido Trotter
                master_netdev, file_storage_dir, candidate_pool_size,
236 b6a30b0d Guido Trotter
                secondary_ip=None, vg_name=None, beparams=None,
237 b6a30b0d Guido Trotter
                nicparams=None, hvparams=None, enabled_hypervisors=None,
238 3953242f Iustin Pop
                modify_etc_hosts=True, modify_ssh_setup=True,
239 39b0f0c2 Balazs Lecz
                maintain_node_health=False,
240 39b0f0c2 Balazs Lecz
                uid_pool=None):
241 a0c9f010 Michael Hanselmann
  """Initialise the cluster.
242 a0c9f010 Michael Hanselmann

243 ce735215 Guido Trotter
  @type candidate_pool_size: int
244 ce735215 Guido Trotter
  @param candidate_pool_size: master candidate pool size
245 ce735215 Guido Trotter

246 a0c9f010 Michael Hanselmann
  """
247 ce735215 Guido Trotter
  # TODO: complete the docstring
248 a0c9f010 Michael Hanselmann
  if config.ConfigWriter.IsCluster():
249 debac808 Iustin Pop
    raise errors.OpPrereqError("Cluster is already initialised",
250 debac808 Iustin Pop
                               errors.ECODE_STATE)
251 a0c9f010 Michael Hanselmann
252 b119bccb Guido Trotter
  if not enabled_hypervisors:
253 b119bccb Guido Trotter
    raise errors.OpPrereqError("Enabled hypervisors list must contain at"
254 debac808 Iustin Pop
                               " least one member", errors.ECODE_INVAL)
255 b119bccb Guido Trotter
  invalid_hvs = set(enabled_hypervisors) - constants.HYPER_TYPES
256 b119bccb Guido Trotter
  if invalid_hvs:
257 b119bccb Guido Trotter
    raise errors.OpPrereqError("Enabled hypervisors contains invalid"
258 debac808 Iustin Pop
                               " entries: %s" % invalid_hvs,
259 debac808 Iustin Pop
                               errors.ECODE_INVAL)
260 b119bccb Guido Trotter
261 104f4ca1 Iustin Pop
  hostname = utils.GetHostInfo()
262 a0c9f010 Michael Hanselmann
263 a0c9f010 Michael Hanselmann
  if hostname.ip.startswith("127."):
264 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("This host's IP resolves to the private"
265 a0c9f010 Michael Hanselmann
                               " range (%s). Please fix DNS or %s." %
266 debac808 Iustin Pop
                               (hostname.ip, constants.ETC_HOSTS),
267 debac808 Iustin Pop
                               errors.ECODE_ENVIRON)
268 a0c9f010 Michael Hanselmann
269 caad16e2 Iustin Pop
  if not utils.OwnIpAddress(hostname.ip):
270 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Inconsistency: this host's name resolves"
271 a0c9f010 Michael Hanselmann
                               " to %s,\nbut this ip address does not"
272 debac808 Iustin Pop
                               " belong to this host. Aborting." %
273 debac808 Iustin Pop
                               hostname.ip, errors.ECODE_ENVIRON)
274 a0c9f010 Michael Hanselmann
275 44caf5a8 Iustin Pop
  clustername = utils.GetHostInfo(utils.HostInfo.NormalizeName(cluster_name))
276 a0c9f010 Michael Hanselmann
277 a0c9f010 Michael Hanselmann
  if utils.TcpPing(clustername.ip, constants.DEFAULT_NODED_PORT,
278 a0c9f010 Michael Hanselmann
                   timeout=5):
279 debac808 Iustin Pop
    raise errors.OpPrereqError("Cluster IP already active. Aborting.",
280 debac808 Iustin Pop
                               errors.ECODE_NOTUNIQUE)
281 a0c9f010 Michael Hanselmann
282 a0c9f010 Michael Hanselmann
  if secondary_ip:
283 a0c9f010 Michael Hanselmann
    if not utils.IsValidIP(secondary_ip):
284 debac808 Iustin Pop
      raise errors.OpPrereqError("Invalid secondary ip given",
285 debac808 Iustin Pop
                                 errors.ECODE_INVAL)
286 a0c9f010 Michael Hanselmann
    if (secondary_ip != hostname.ip and
287 caad16e2 Iustin Pop
        not utils.OwnIpAddress(secondary_ip)):
288 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("You gave %s as secondary IP,"
289 a0c9f010 Michael Hanselmann
                                 " but it does not belong to this host." %
290 debac808 Iustin Pop
                                 secondary_ip, errors.ECODE_ENVIRON)
291 b9eeeb02 Michael Hanselmann
  else:
292 b9eeeb02 Michael Hanselmann
    secondary_ip = hostname.ip
293 a0c9f010 Michael Hanselmann
294 a0c9f010 Michael Hanselmann
  if vg_name is not None:
295 a0c9f010 Michael Hanselmann
    # Check if volume group is valid
296 a0c9f010 Michael Hanselmann
    vgstatus = utils.CheckVolumeGroupSize(utils.ListVolumeGroups(), vg_name,
297 a0c9f010 Michael Hanselmann
                                          constants.MIN_VG_SIZE)
298 a0c9f010 Michael Hanselmann
    if vgstatus:
299 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("Error: %s\nspecify --no-lvm-storage if"
300 debac808 Iustin Pop
                                 " you are not using lvm" % vgstatus,
301 debac808 Iustin Pop
                                 errors.ECODE_INVAL)
302 a0c9f010 Michael Hanselmann
303 0e3baaf3 Iustin Pop
  file_storage_dir = _InitFileStorage(file_storage_dir)
304 a0c9f010 Michael Hanselmann
305 a0c9f010 Michael Hanselmann
  if not re.match("^[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}$", mac_prefix):
306 debac808 Iustin Pop
    raise errors.OpPrereqError("Invalid mac prefix given '%s'" % mac_prefix,
307 debac808 Iustin Pop
                               errors.ECODE_INVAL)
308 a0c9f010 Michael Hanselmann
309 a0c9f010 Michael Hanselmann
  result = utils.RunCmd(["ip", "link", "show", "dev", master_netdev])
310 a0c9f010 Michael Hanselmann
  if result.failed:
311 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Invalid master netdev given (%s): '%s'" %
312 a0c9f010 Michael Hanselmann
                               (master_netdev,
313 debac808 Iustin Pop
                                result.output.strip()), errors.ECODE_INVAL)
314 a0c9f010 Michael Hanselmann
315 9dae41ad Guido Trotter
  dirs = [(constants.RUN_GANETI_DIR, constants.RUN_DIRS_MODE)]
316 9dae41ad Guido Trotter
  utils.EnsureDirs(dirs)
317 9dae41ad Guido Trotter
318 a5728081 Guido Trotter
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
319 b6a30b0d Guido Trotter
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
320 b6a30b0d Guido Trotter
  objects.NIC.CheckParameterSyntax(nicparams)
321 b6a30b0d Guido Trotter
322 a5728081 Guido Trotter
  # hvparams is a mapping of hypervisor->hvparams dict
323 a5728081 Guido Trotter
  for hv_name, hv_params in hvparams.iteritems():
324 a5728081 Guido Trotter
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
325 a5728081 Guido Trotter
    hv_class = hypervisor.GetHypervisor(hv_name)
326 a5728081 Guido Trotter
    hv_class.CheckParameterSyntax(hv_params)
327 d4b72030 Guido Trotter
328 a0c9f010 Michael Hanselmann
  # set up the inter-node password and certificate
329 8f215968 Michael Hanselmann
  _InitGanetiServerSetup(hostname.name)
330 a0c9f010 Michael Hanselmann
331 a0c9f010 Michael Hanselmann
  # set up ssh config and /etc/hosts
332 13998ef2 Michael Hanselmann
  sshline = utils.ReadFile(constants.SSH_HOST_RSA_PUB)
333 a0c9f010 Michael Hanselmann
  sshkey = sshline.split(" ")[1]
334 a0c9f010 Michael Hanselmann
335 b86a6bcd Guido Trotter
  if modify_etc_hosts:
336 b86a6bcd Guido Trotter
    utils.AddHostToEtcHosts(hostname.name)
337 b86a6bcd Guido Trotter
338 b989b9d9 Ken Wehr
  if modify_ssh_setup:
339 b989b9d9 Ken Wehr
    _InitSSHSetup()
340 a0c9f010 Michael Hanselmann
341 430b923c Iustin Pop
  now = time.time()
342 430b923c Iustin Pop
343 a0c9f010 Michael Hanselmann
  # init of cluster config file
344 b9eeeb02 Michael Hanselmann
  cluster_config = objects.Cluster(
345 b9eeeb02 Michael Hanselmann
    serial_no=1,
346 b9eeeb02 Michael Hanselmann
    rsahostkeypub=sshkey,
347 b9eeeb02 Michael Hanselmann
    highest_used_port=(constants.FIRST_DRBD_PORT - 1),
348 b9eeeb02 Michael Hanselmann
    mac_prefix=mac_prefix,
349 b9eeeb02 Michael Hanselmann
    volume_group_name=vg_name,
350 b9eeeb02 Michael Hanselmann
    tcpudp_port_pool=set(),
351 f6bd6e98 Michael Hanselmann
    master_node=hostname.name,
352 f6bd6e98 Michael Hanselmann
    master_ip=clustername.ip,
353 f6bd6e98 Michael Hanselmann
    master_netdev=master_netdev,
354 f6bd6e98 Michael Hanselmann
    cluster_name=clustername.name,
355 f6bd6e98 Michael Hanselmann
    file_storage_dir=file_storage_dir,
356 ea3a925f Alexander Schreiber
    enabled_hypervisors=enabled_hypervisors,
357 4ef7f423 Guido Trotter
    beparams={constants.PP_DEFAULT: beparams},
358 b6a30b0d Guido Trotter
    nicparams={constants.PP_DEFAULT: nicparams},
359 ea3a925f Alexander Schreiber
    hvparams=hvparams,
360 ce735215 Guido Trotter
    candidate_pool_size=candidate_pool_size,
361 022c3a0b Guido Trotter
    modify_etc_hosts=modify_etc_hosts,
362 b989b9d9 Ken Wehr
    modify_ssh_setup=modify_ssh_setup,
363 39b0f0c2 Balazs Lecz
    uid_pool=uid_pool,
364 430b923c Iustin Pop
    ctime=now,
365 430b923c Iustin Pop
    mtime=now,
366 430b923c Iustin Pop
    uuid=utils.NewUUID(),
367 3953242f Iustin Pop
    maintain_node_health=maintain_node_health,
368 b9eeeb02 Michael Hanselmann
    )
369 b9eeeb02 Michael Hanselmann
  master_node_config = objects.Node(name=hostname.name,
370 b9eeeb02 Michael Hanselmann
                                    primary_ip=hostname.ip,
371 b9222f32 Guido Trotter
                                    secondary_ip=secondary_ip,
372 c044f32c Guido Trotter
                                    serial_no=1,
373 c044f32c Guido Trotter
                                    master_candidate=True,
374 af64c0ea Iustin Pop
                                    offline=False, drained=False,
375 c044f32c Guido Trotter
                                    )
376 9e1333b9 Guido Trotter
  InitConfig(constants.CONFIG_VERSION, cluster_config, master_node_config)
377 05cc153f Guido Trotter
  cfg = config.ConfigWriter()
378 9e1333b9 Guido Trotter
  ssh.WriteKnownHostsFile(cfg, constants.SSH_KNOWN_HOSTS_FILE)
379 a4eae71f Michael Hanselmann
  cfg.Update(cfg.GetClusterInfo(), logging.error)
380 827f753e Guido Trotter
381 b3f1cf6f Iustin Pop
  # start the master ip
382 b3f1cf6f Iustin Pop
  # TODO: Review rpc call from bootstrap
383 b726aff0 Iustin Pop
  # TODO: Warn on failed start master
384 3583908a Guido Trotter
  rpc.RpcRunner.call_node_start_master(hostname.name, True, False)
385 b3f1cf6f Iustin Pop
386 b1b6ea87 Iustin Pop
387 02f99608 Oleksiy Mishchenko
def InitConfig(version, cluster_config, master_node_config,
388 02f99608 Oleksiy Mishchenko
               cfg_file=constants.CLUSTER_CONF_FILE):
389 7b3a8fb5 Iustin Pop
  """Create the initial cluster configuration.
390 7b3a8fb5 Iustin Pop

391 7b3a8fb5 Iustin Pop
  It will contain the current node, which will also be the master
392 7b3a8fb5 Iustin Pop
  node, and no instances.
393 7b3a8fb5 Iustin Pop

394 7b3a8fb5 Iustin Pop
  @type version: int
395 c41eea6e Iustin Pop
  @param version: configuration version
396 c41eea6e Iustin Pop
  @type cluster_config: L{objects.Cluster}
397 c41eea6e Iustin Pop
  @param cluster_config: cluster configuration
398 c41eea6e Iustin Pop
  @type master_node_config: L{objects.Node}
399 c41eea6e Iustin Pop
  @param master_node_config: master node configuration
400 c41eea6e Iustin Pop
  @type cfg_file: string
401 c41eea6e Iustin Pop
  @param cfg_file: configuration file path
402 c41eea6e Iustin Pop

403 7b3a8fb5 Iustin Pop
  """
404 7b3a8fb5 Iustin Pop
  nodes = {
405 7b3a8fb5 Iustin Pop
    master_node_config.name: master_node_config,
406 7b3a8fb5 Iustin Pop
    }
407 7b3a8fb5 Iustin Pop
408 d693c864 Iustin Pop
  now = time.time()
409 7b3a8fb5 Iustin Pop
  config_data = objects.ConfigData(version=version,
410 7b3a8fb5 Iustin Pop
                                   cluster=cluster_config,
411 7b3a8fb5 Iustin Pop
                                   nodes=nodes,
412 7b3a8fb5 Iustin Pop
                                   instances={},
413 d693c864 Iustin Pop
                                   serial_no=1,
414 d693c864 Iustin Pop
                                   ctime=now, mtime=now)
415 a33848a5 Guido Trotter
  utils.WriteFile(cfg_file,
416 a33848a5 Guido Trotter
                  data=serializer.Dump(config_data.ToDict()),
417 a33848a5 Guido Trotter
                  mode=0600)
418 02f99608 Oleksiy Mishchenko
419 02f99608 Oleksiy Mishchenko
420 140aa4a8 Iustin Pop
def FinalizeClusterDestroy(master):
421 140aa4a8 Iustin Pop
  """Execute the last steps of cluster destroy
422 140aa4a8 Iustin Pop

423 140aa4a8 Iustin Pop
  This function shuts down all the daemons, completing the destroy
424 140aa4a8 Iustin Pop
  begun in cmdlib.LUDestroyOpcode.
425 140aa4a8 Iustin Pop

426 140aa4a8 Iustin Pop
  """
427 b989b9d9 Ken Wehr
  cfg = config.ConfigWriter()
428 b989b9d9 Ken Wehr
  modify_ssh_setup = cfg.GetClusterInfo().modify_ssh_setup
429 781de953 Iustin Pop
  result = rpc.RpcRunner.call_node_stop_master(master, True)
430 3cebe102 Michael Hanselmann
  msg = result.fail_msg
431 6c00d19a Iustin Pop
  if msg:
432 099c52ad Iustin Pop
    logging.warning("Could not disable the master role: %s", msg)
433 b989b9d9 Ken Wehr
  result = rpc.RpcRunner.call_node_leave_cluster(master, modify_ssh_setup)
434 3cebe102 Michael Hanselmann
  msg = result.fail_msg
435 0623d351 Iustin Pop
  if msg:
436 0623d351 Iustin Pop
    logging.warning("Could not shutdown the node daemon and cleanup"
437 0623d351 Iustin Pop
                    " the node: %s", msg)
438 140aa4a8 Iustin Pop
439 140aa4a8 Iustin Pop
440 87622829 Iustin Pop
def SetupNodeDaemon(cluster_name, node, ssh_key_check):
441 827f753e Guido Trotter
  """Add a node to the cluster.
442 827f753e Guido Trotter

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

447 87622829 Iustin Pop
  @param cluster_name: the cluster name
448 87622829 Iustin Pop
  @param node: the name of the new node
449 87622829 Iustin Pop
  @param ssh_key_check: whether to do a strict key check
450 827f753e Guido Trotter

451 827f753e Guido Trotter
  """
452 87622829 Iustin Pop
  sshrunner = ssh.SshRunner(cluster_name)
453 5557b04c Michael Hanselmann
454 168c1de2 Michael Hanselmann
  noded_cert = utils.ReadFile(constants.NODED_CERT_FILE)
455 2438c157 Michael Hanselmann
  rapi_cert = utils.ReadFile(constants.RAPI_CERT_FILE)
456 6b7d5878 Michael Hanselmann
  confd_hmac_key = utils.ReadFile(constants.CONFD_HMAC_KEY)
457 5557b04c Michael Hanselmann
458 827f753e Guido Trotter
  # in the base64 pem encoding, neither '!' nor '.' are valid chars,
459 827f753e Guido Trotter
  # so we use this to detect an invalid certificate; as long as the
460 827f753e Guido Trotter
  # cert doesn't contain this, the here-document will be correctly
461 77b076ca Guido Trotter
  # parsed by the shell sequence below. HMAC keys are hexadecimal strings,
462 77b076ca Guido Trotter
  # so the same restrictions apply.
463 6b7d5878 Michael Hanselmann
  for content in (noded_cert, rapi_cert, confd_hmac_key):
464 77b076ca Guido Trotter
    if re.search('^!EOF\.', content, re.MULTILINE):
465 77b076ca Guido Trotter
      raise errors.OpExecError("invalid SSL certificate or HMAC key")
466 5557b04c Michael Hanselmann
467 5557b04c Michael Hanselmann
  if not noded_cert.endswith("\n"):
468 5557b04c Michael Hanselmann
    noded_cert += "\n"
469 2438c157 Michael Hanselmann
  if not rapi_cert.endswith("\n"):
470 2438c157 Michael Hanselmann
    rapi_cert += "\n"
471 6b7d5878 Michael Hanselmann
  if not confd_hmac_key.endswith("\n"):
472 6b7d5878 Michael Hanselmann
    confd_hmac_key += "\n"
473 827f753e Guido Trotter
474 827f753e Guido Trotter
  # set up inter-node password and certificate and restarts the node daemon
475 827f753e Guido Trotter
  # and then connect with ssh to set password and start ganeti-noded
476 827f753e Guido Trotter
  # note that all the below variables are sanitized at this point,
477 827f753e Guido Trotter
  # either by being constants or by the checks above
478 827f753e Guido Trotter
  mycommand = ("umask 077 && "
479 827f753e Guido Trotter
               "cat > '%s' << '!EOF.' && \n"
480 2438c157 Michael Hanselmann
               "%s!EOF.\n"
481 2438c157 Michael Hanselmann
               "cat > '%s' << '!EOF.' && \n"
482 2438c157 Michael Hanselmann
               "%s!EOF.\n"
483 77b076ca Guido Trotter
               "cat > '%s' << '!EOF.' && \n"
484 77b076ca Guido Trotter
               "%s!EOF.\n"
485 77b076ca Guido Trotter
               "chmod 0400 %s %s %s && "
486 f154a7a3 Michael Hanselmann
               "%s start %s" %
487 168c1de2 Michael Hanselmann
               (constants.NODED_CERT_FILE, noded_cert,
488 2438c157 Michael Hanselmann
                constants.RAPI_CERT_FILE, rapi_cert,
489 6b7d5878 Michael Hanselmann
                constants.CONFD_HMAC_KEY, confd_hmac_key,
490 168c1de2 Michael Hanselmann
                constants.NODED_CERT_FILE, constants.RAPI_CERT_FILE,
491 6b7d5878 Michael Hanselmann
                constants.CONFD_HMAC_KEY,
492 f154a7a3 Michael Hanselmann
                constants.DAEMON_UTIL, constants.NODED))
493 827f753e Guido Trotter
494 c4b6c29c Michael Hanselmann
  result = sshrunner.Run(node, 'root', mycommand, batch=False,
495 c4b6c29c Michael Hanselmann
                         ask_key=ssh_key_check,
496 c4b6c29c Michael Hanselmann
                         use_cluster_key=False,
497 c4b6c29c Michael Hanselmann
                         strict_host_check=ssh_key_check)
498 827f753e Guido Trotter
  if result.failed:
499 827f753e Guido Trotter
    raise errors.OpExecError("Remote command on node %s, error: %s,"
500 827f753e Guido Trotter
                             " output: %s" %
501 827f753e Guido Trotter
                             (node, result.fail_reason, result.output))
502 827f753e Guido Trotter
503 5627f375 Michael Hanselmann
  _WaitForNodeDaemon(node)
504 5627f375 Michael Hanselmann
505 b1b6ea87 Iustin Pop
506 8e2524c3 Guido Trotter
def MasterFailover(no_voting=False):
507 b1b6ea87 Iustin Pop
  """Failover the master node.
508 b1b6ea87 Iustin Pop

509 b1b6ea87 Iustin Pop
  This checks that we are not already the master, and will cause the
510 b1b6ea87 Iustin Pop
  current master to cease being master, and the non-master to become
511 b1b6ea87 Iustin Pop
  new master.
512 b1b6ea87 Iustin Pop

513 8e2524c3 Guido Trotter
  @type no_voting: boolean
514 8e2524c3 Guido Trotter
  @param no_voting: force the operation without remote nodes agreement
515 8e2524c3 Guido Trotter
                      (dangerous)
516 8e2524c3 Guido Trotter

517 b1b6ea87 Iustin Pop
  """
518 8135a2db Iustin Pop
  sstore = ssconf.SimpleStore()
519 b1b6ea87 Iustin Pop
520 8135a2db Iustin Pop
  old_master, new_master = ssconf.GetMasterAndMyself(sstore)
521 8135a2db Iustin Pop
  node_list = sstore.GetNodeList()
522 8135a2db Iustin Pop
  mc_list = sstore.GetMasterCandidates()
523 b1b6ea87 Iustin Pop
524 b1b6ea87 Iustin Pop
  if old_master == new_master:
525 b1b6ea87 Iustin Pop
    raise errors.OpPrereqError("This commands must be run on the node"
526 b1b6ea87 Iustin Pop
                               " where you want the new master to be."
527 b1b6ea87 Iustin Pop
                               " %s is already the master" %
528 debac808 Iustin Pop
                               old_master, errors.ECODE_INVAL)
529 d5927e48 Iustin Pop
530 8135a2db Iustin Pop
  if new_master not in mc_list:
531 8135a2db Iustin Pop
    mc_no_master = [name for name in mc_list if name != old_master]
532 8135a2db Iustin Pop
    raise errors.OpPrereqError("This node is not among the nodes marked"
533 8135a2db Iustin Pop
                               " as master candidates. Only these nodes"
534 8135a2db Iustin Pop
                               " can become masters. Current list of"
535 8135a2db Iustin Pop
                               " master candidates is:\n"
536 debac808 Iustin Pop
                               "%s" % ('\n'.join(mc_no_master)),
537 debac808 Iustin Pop
                               errors.ECODE_STATE)
538 8135a2db Iustin Pop
539 8e2524c3 Guido Trotter
  if not no_voting:
540 8e2524c3 Guido Trotter
    vote_list = GatherMasterVotes(node_list)
541 8e2524c3 Guido Trotter
542 8e2524c3 Guido Trotter
    if vote_list:
543 8e2524c3 Guido Trotter
      voted_master = vote_list[0][0]
544 8e2524c3 Guido Trotter
      if voted_master is None:
545 8e2524c3 Guido Trotter
        raise errors.OpPrereqError("Cluster is inconsistent, most nodes did"
546 debac808 Iustin Pop
                                   " not respond.", errors.ECODE_ENVIRON)
547 8e2524c3 Guido Trotter
      elif voted_master != old_master:
548 8e2524c3 Guido Trotter
        raise errors.OpPrereqError("I have a wrong configuration, I believe"
549 8e2524c3 Guido Trotter
                                   " the master is %s but the other nodes"
550 8e2524c3 Guido Trotter
                                   " voted %s. Please resync the configuration"
551 8e2524c3 Guido Trotter
                                   " of this node." %
552 debac808 Iustin Pop
                                   (old_master, voted_master),
553 debac808 Iustin Pop
                                   errors.ECODE_STATE)
554 b1b6ea87 Iustin Pop
  # end checks
555 b1b6ea87 Iustin Pop
556 b1b6ea87 Iustin Pop
  rcode = 0
557 b1b6ea87 Iustin Pop
558 d5927e48 Iustin Pop
  logging.info("Setting master to %s, old master: %s", new_master, old_master)
559 b1b6ea87 Iustin Pop
560 781de953 Iustin Pop
  result = rpc.RpcRunner.call_node_stop_master(old_master, True)
561 3cebe102 Michael Hanselmann
  msg = result.fail_msg
562 6c00d19a Iustin Pop
  if msg:
563 d5927e48 Iustin Pop
    logging.error("Could not disable the master role on the old master"
564 6c00d19a Iustin Pop
                 " %s, please disable manually: %s", old_master, msg)
565 b1b6ea87 Iustin Pop
566 d23ef431 Michael Hanselmann
  # Here we have a phase where no master should be running
567 b1b6ea87 Iustin Pop
568 bbe19c17 Iustin Pop
  # instantiate a real config writer, as we now know we have the
569 bbe19c17 Iustin Pop
  # configuration data
570 bbe19c17 Iustin Pop
  cfg = config.ConfigWriter()
571 b1b6ea87 Iustin Pop
572 bbe19c17 Iustin Pop
  cluster_info = cfg.GetClusterInfo()
573 bbe19c17 Iustin Pop
  cluster_info.master_node = new_master
574 bbe19c17 Iustin Pop
  # this will also regenerate the ssconf files, since we updated the
575 bbe19c17 Iustin Pop
  # cluster info
576 a4eae71f Michael Hanselmann
  cfg.Update(cluster_info, logging.error)
577 d5927e48 Iustin Pop
578 3583908a Guido Trotter
  result = rpc.RpcRunner.call_node_start_master(new_master, True, no_voting)
579 3cebe102 Michael Hanselmann
  msg = result.fail_msg
580 b726aff0 Iustin Pop
  if msg:
581 d5927e48 Iustin Pop
    logging.error("Could not start the master role on the new master"
582 b726aff0 Iustin Pop
                  " %s, please check: %s", new_master, msg)
583 b1b6ea87 Iustin Pop
    rcode = 1
584 b1b6ea87 Iustin Pop
585 b1b6ea87 Iustin Pop
  return rcode
586 d7cdb55d Iustin Pop
587 d7cdb55d Iustin Pop
588 8eb148ae Iustin Pop
def GetMaster():
589 8eb148ae Iustin Pop
  """Returns the current master node.
590 8eb148ae Iustin Pop

591 8eb148ae Iustin Pop
  This is a separate function in bootstrap since it's needed by
592 8eb148ae Iustin Pop
  gnt-cluster, and instead of importing directly ssconf, it's better
593 8eb148ae Iustin Pop
  to abstract it in bootstrap, where we do use ssconf in other
594 8eb148ae Iustin Pop
  functions too.
595 8eb148ae Iustin Pop

596 8eb148ae Iustin Pop
  """
597 8eb148ae Iustin Pop
  sstore = ssconf.SimpleStore()
598 8eb148ae Iustin Pop
599 8eb148ae Iustin Pop
  old_master, _ = ssconf.GetMasterAndMyself(sstore)
600 8eb148ae Iustin Pop
601 8eb148ae Iustin Pop
  return old_master
602 8eb148ae Iustin Pop
603 8eb148ae Iustin Pop
604 d7cdb55d Iustin Pop
def GatherMasterVotes(node_list):
605 d7cdb55d Iustin Pop
  """Check the agreement on who is the master.
606 d7cdb55d Iustin Pop

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

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

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

618 d7cdb55d Iustin Pop
  @type node_list: list
619 d7cdb55d Iustin Pop
  @param node_list: the list of nodes to query for master info; the current
620 5bbd3f7f Michael Hanselmann
      node will be removed if it is in the list
621 d7cdb55d Iustin Pop
  @rtype: list
622 d7cdb55d Iustin Pop
  @return: list of (node, votes)
623 d7cdb55d Iustin Pop

624 d7cdb55d Iustin Pop
  """
625 d7cdb55d Iustin Pop
  myself = utils.HostInfo().name
626 d7cdb55d Iustin Pop
  try:
627 d7cdb55d Iustin Pop
    node_list.remove(myself)
628 d7cdb55d Iustin Pop
  except ValueError:
629 d7cdb55d Iustin Pop
    pass
630 d7cdb55d Iustin Pop
  if not node_list:
631 d7cdb55d Iustin Pop
    # no nodes left (eventually after removing myself)
632 d7cdb55d Iustin Pop
    return []
633 d7cdb55d Iustin Pop
  results = rpc.RpcRunner.call_master_info(node_list)
634 d7cdb55d Iustin Pop
  if not isinstance(results, dict):
635 d7cdb55d Iustin Pop
    # this should not happen (unless internal error in rpc)
636 d7cdb55d Iustin Pop
    logging.critical("Can't complete rpc call, aborting master startup")
637 d7cdb55d Iustin Pop
    return [(None, len(node_list))]
638 d7cdb55d Iustin Pop
  votes = {}
639 d7cdb55d Iustin Pop
  for node in results:
640 781de953 Iustin Pop
    nres = results[node]
641 2a52a064 Iustin Pop
    data = nres.payload
642 3cebe102 Michael Hanselmann
    msg = nres.fail_msg
643 2a52a064 Iustin Pop
    fail = False
644 2a52a064 Iustin Pop
    if msg:
645 2a52a064 Iustin Pop
      logging.warning("Error contacting node %s: %s", node, msg)
646 2a52a064 Iustin Pop
      fail = True
647 2a52a064 Iustin Pop
    elif not isinstance(data, (tuple, list)) or len(data) < 3:
648 2a52a064 Iustin Pop
      logging.warning("Invalid data received from node %s: %s", node, data)
649 2a52a064 Iustin Pop
      fail = True
650 2a52a064 Iustin Pop
    if fail:
651 d7cdb55d Iustin Pop
      if None not in votes:
652 d7cdb55d Iustin Pop
        votes[None] = 0
653 d7cdb55d Iustin Pop
      votes[None] += 1
654 d7cdb55d Iustin Pop
      continue
655 781de953 Iustin Pop
    master_node = data[2]
656 d7cdb55d Iustin Pop
    if master_node not in votes:
657 d7cdb55d Iustin Pop
      votes[master_node] = 0
658 d7cdb55d Iustin Pop
    votes[master_node] += 1
659 d7cdb55d Iustin Pop
660 d7cdb55d Iustin Pop
  vote_list = [v for v in votes.items()]
661 d7cdb55d Iustin Pop
  # sort first on number of votes then on name, since we want None
662 d7cdb55d Iustin Pop
  # sorted later if we have the half of the nodes not responding, and
663 d7cdb55d Iustin Pop
  # half voting all for the same master
664 d7cdb55d Iustin Pop
  vote_list.sort(key=lambda x: (x[1], x[0]), reverse=True)
665 d7cdb55d Iustin Pop
666 d7cdb55d Iustin Pop
  return vote_list