Statistics
| Branch: | Tag: | Revision:

root / lib / bootstrap.py @ adb6d685

History | View | Annotate | Download (22.7 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 d693c864 Iustin Pop
import time
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 a33848a5 Guido Trotter
from ganeti import serializer
41 a5728081 Guido Trotter
from ganeti import hypervisor
42 a0c9f010 Michael Hanselmann
43 e38220e4 Michael Hanselmann
44 531baf8e Iustin Pop
def _InitSSHSetup():
45 a0c9f010 Michael Hanselmann
  """Setup the SSH configuration for the cluster.
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
  """
51 a0c9f010 Michael Hanselmann
  priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
52 a0c9f010 Michael Hanselmann
53 a0c9f010 Michael Hanselmann
  for name in priv_key, pub_key:
54 a0c9f010 Michael Hanselmann
    if os.path.exists(name):
55 a0c9f010 Michael Hanselmann
      utils.CreateBackup(name)
56 a0c9f010 Michael Hanselmann
    utils.RemoveFile(name)
57 a0c9f010 Michael Hanselmann
58 a0c9f010 Michael Hanselmann
  result = utils.RunCmd(["ssh-keygen", "-t", "dsa",
59 a0c9f010 Michael Hanselmann
                         "-f", priv_key,
60 a0c9f010 Michael Hanselmann
                         "-q", "-N", ""])
61 a0c9f010 Michael Hanselmann
  if result.failed:
62 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("Could not generate ssh keypair, error %s" %
63 a0c9f010 Michael Hanselmann
                             result.output)
64 a0c9f010 Michael Hanselmann
65 7a0156dc Luca Bigliardi
  utils.AddAuthorizedKey(auth_keys, utils.ReadFile(pub_key))
66 a0c9f010 Michael Hanselmann
67 a0c9f010 Michael Hanselmann
68 c008906b Michael Hanselmann
def GenerateHmacKey(file_name):
69 c008906b Michael Hanselmann
  """Writes a new HMAC key.
70 c008906b Michael Hanselmann

71 c008906b Michael Hanselmann
  @type file_name: str
72 c008906b Michael Hanselmann
  @param file_name: Path to output file
73 c008906b Michael Hanselmann

74 c008906b Michael Hanselmann
  """
75 43575108 Michael Hanselmann
  utils.WriteFile(file_name, data="%s\n" % utils.GenerateSecret(), mode=0400,
76 43575108 Michael Hanselmann
                  backup=True)
77 43575108 Michael Hanselmann
78 43575108 Michael Hanselmann
79 6b7d5878 Michael Hanselmann
def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_confd_hmac_key,
80 af2ae1c0 Iustin Pop
                          new_cds, rapi_cert_pem=None, cds=None,
81 aeefe835 Iustin Pop
                          nodecert_file=constants.NODED_CERT_FILE,
82 aeefe835 Iustin Pop
                          rapicert_file=constants.RAPI_CERT_FILE,
83 fc0726b9 Michael Hanselmann
                          hmackey_file=constants.CONFD_HMAC_KEY,
84 fc0726b9 Michael Hanselmann
                          cds_file=constants.CLUSTER_DOMAIN_SECRET_FILE):
85 43575108 Michael Hanselmann
  """Updates the cluster certificates, keys and secrets.
86 43575108 Michael Hanselmann

87 43575108 Michael Hanselmann
  @type new_cluster_cert: bool
88 43575108 Michael Hanselmann
  @param new_cluster_cert: Whether to generate a new cluster certificate
89 43575108 Michael Hanselmann
  @type new_rapi_cert: bool
90 43575108 Michael Hanselmann
  @param new_rapi_cert: Whether to generate a new RAPI certificate
91 6b7d5878 Michael Hanselmann
  @type new_confd_hmac_key: bool
92 6b7d5878 Michael Hanselmann
  @param new_confd_hmac_key: Whether to generate a new HMAC key
93 3db3eb2a Michael Hanselmann
  @type new_cds: bool
94 3db3eb2a Michael Hanselmann
  @param new_cds: Whether to generate a new cluster domain secret
95 43575108 Michael Hanselmann
  @type rapi_cert_pem: string
96 43575108 Michael Hanselmann
  @param rapi_cert_pem: New RAPI certificate in PEM format
97 3db3eb2a Michael Hanselmann
  @type cds: string
98 3db3eb2a Michael Hanselmann
  @param cds: New cluster domain secret
99 aeefe835 Iustin Pop
  @type nodecert_file: string
100 aeefe835 Iustin Pop
  @param nodecert_file: optional override of the node cert file path
101 aeefe835 Iustin Pop
  @type rapicert_file: string
102 aeefe835 Iustin Pop
  @param rapicert_file: optional override of the rapi cert file path
103 aeefe835 Iustin Pop
  @type hmackey_file: string
104 aeefe835 Iustin Pop
  @param hmackey_file: optional override of the hmac key file path
105 43575108 Michael Hanselmann

106 43575108 Michael Hanselmann
  """
107 168c1de2 Michael Hanselmann
  # noded SSL certificate
108 aeefe835 Iustin Pop
  cluster_cert_exists = os.path.exists(nodecert_file)
109 43575108 Michael Hanselmann
  if new_cluster_cert or not cluster_cert_exists:
110 43575108 Michael Hanselmann
    if cluster_cert_exists:
111 aeefe835 Iustin Pop
      utils.CreateBackup(nodecert_file)
112 43575108 Michael Hanselmann
113 aeefe835 Iustin Pop
    logging.debug("Generating new cluster certificate at %s", nodecert_file)
114 af2ae1c0 Iustin Pop
    utils.GenerateSelfSignedSslCert(nodecert_file)
115 43575108 Michael Hanselmann
116 6b7d5878 Michael Hanselmann
  # confd HMAC key
117 aeefe835 Iustin Pop
  if new_confd_hmac_key or not os.path.exists(hmackey_file):
118 aeefe835 Iustin Pop
    logging.debug("Writing new confd HMAC key to %s", hmackey_file)
119 aeefe835 Iustin Pop
    GenerateHmacKey(hmackey_file)
120 43575108 Michael Hanselmann
121 43575108 Michael Hanselmann
  # RAPI
122 aeefe835 Iustin Pop
  rapi_cert_exists = os.path.exists(rapicert_file)
123 43575108 Michael Hanselmann
124 43575108 Michael Hanselmann
  if rapi_cert_pem:
125 43575108 Michael Hanselmann
    # Assume rapi_pem contains a valid PEM-formatted certificate and key
126 aeefe835 Iustin Pop
    logging.debug("Writing RAPI certificate at %s", rapicert_file)
127 aeefe835 Iustin Pop
    utils.WriteFile(rapicert_file, data=rapi_cert_pem, backup=True)
128 43575108 Michael Hanselmann
129 43575108 Michael Hanselmann
  elif new_rapi_cert or not rapi_cert_exists:
130 43575108 Michael Hanselmann
    if rapi_cert_exists:
131 aeefe835 Iustin Pop
      utils.CreateBackup(rapicert_file)
132 43575108 Michael Hanselmann
133 aeefe835 Iustin Pop
    logging.debug("Generating new RAPI certificate at %s", rapicert_file)
134 af2ae1c0 Iustin Pop
    utils.GenerateSelfSignedSslCert(rapicert_file)
135 c008906b Michael Hanselmann
136 3db3eb2a Michael Hanselmann
  # Cluster domain secret
137 3db3eb2a Michael Hanselmann
  if cds:
138 fc0726b9 Michael Hanselmann
    logging.debug("Writing cluster domain secret to %s", cds_file)
139 fc0726b9 Michael Hanselmann
    utils.WriteFile(cds_file, data=cds, backup=True)
140 fc0726b9 Michael Hanselmann
141 fc0726b9 Michael Hanselmann
  elif new_cds or not os.path.exists(cds_file):
142 fc0726b9 Michael Hanselmann
    logging.debug("Generating new cluster domain secret at %s", cds_file)
143 fc0726b9 Michael Hanselmann
    GenerateHmacKey(cds_file)
144 3db3eb2a Michael Hanselmann
145 c008906b Michael Hanselmann
146 8f215968 Michael Hanselmann
def _InitGanetiServerSetup(master_name):
147 40a97d80 Michael Hanselmann
  """Setup the necessary configuration for the initial node daemon.
148 40a97d80 Michael Hanselmann

149 40a97d80 Michael Hanselmann
  This creates the nodepass file containing the shared password for
150 40a97d80 Michael Hanselmann
  the cluster and also generates the SSL certificate.
151 40a97d80 Michael Hanselmann

152 40a97d80 Michael Hanselmann
  """
153 43575108 Michael Hanselmann
  # Generate cluster secrets
154 3db3eb2a Michael Hanselmann
  GenerateClusterCrypto(True, False, False, False)
155 4a34c5cf Guido Trotter
156 f154a7a3 Michael Hanselmann
  result = utils.RunCmd([constants.DAEMON_UTIL, "start", constants.NODED])
157 a0c9f010 Michael Hanselmann
  if result.failed:
158 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("Could not start the node daemon, command %s"
159 a0c9f010 Michael Hanselmann
                             " had exitcode %s and error %s" %
160 a0c9f010 Michael Hanselmann
                             (result.cmd, result.exit_code, result.output))
161 a0c9f010 Michael Hanselmann
162 5627f375 Michael Hanselmann
  _WaitForNodeDaemon(master_name)
163 5627f375 Michael Hanselmann
164 5627f375 Michael Hanselmann
165 5627f375 Michael Hanselmann
def _WaitForNodeDaemon(node_name):
166 5627f375 Michael Hanselmann
  """Wait for node daemon to become responsive.
167 5627f375 Michael Hanselmann

168 5627f375 Michael Hanselmann
  """
169 d3833ebd Michael Hanselmann
  def _CheckNodeDaemon():
170 5627f375 Michael Hanselmann
    result = rpc.RpcRunner.call_version([node_name])[node_name]
171 d3833ebd Michael Hanselmann
    if result.fail_msg:
172 d3833ebd Michael Hanselmann
      raise utils.RetryAgain()
173 8f215968 Michael Hanselmann
174 d3833ebd Michael Hanselmann
  try:
175 d3833ebd Michael Hanselmann
    utils.Retry(_CheckNodeDaemon, 1.0, 10.0)
176 d3833ebd Michael Hanselmann
  except utils.RetryTimeout:
177 5627f375 Michael Hanselmann
    raise errors.OpExecError("Node daemon on %s didn't answer queries within"
178 5627f375 Michael Hanselmann
                             " 10 seconds" % node_name)
179 5627f375 Michael Hanselmann
180 a0c9f010 Michael Hanselmann
181 0e3baaf3 Iustin Pop
def _InitFileStorage(file_storage_dir):
182 0e3baaf3 Iustin Pop
  """Initialize if needed the file storage.
183 0e3baaf3 Iustin Pop

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

188 0e3baaf3 Iustin Pop
  """
189 0e3baaf3 Iustin Pop
  if not constants.ENABLE_FILE_STORAGE:
190 0e3baaf3 Iustin Pop
    return ""
191 0e3baaf3 Iustin Pop
192 0e3baaf3 Iustin Pop
  file_storage_dir = os.path.normpath(file_storage_dir)
193 0e3baaf3 Iustin Pop
194 0e3baaf3 Iustin Pop
  if not os.path.isabs(file_storage_dir):
195 0e3baaf3 Iustin Pop
    raise errors.OpPrereqError("The file storage directory you passed is"
196 0e3baaf3 Iustin Pop
                               " not an absolute path.", errors.ECODE_INVAL)
197 0e3baaf3 Iustin Pop
198 0e3baaf3 Iustin Pop
  if not os.path.exists(file_storage_dir):
199 0e3baaf3 Iustin Pop
    try:
200 0e3baaf3 Iustin Pop
      os.makedirs(file_storage_dir, 0750)
201 0e3baaf3 Iustin Pop
    except OSError, err:
202 0e3baaf3 Iustin Pop
      raise errors.OpPrereqError("Cannot create file storage directory"
203 0e3baaf3 Iustin Pop
                                 " '%s': %s" % (file_storage_dir, err),
204 0e3baaf3 Iustin Pop
                                 errors.ECODE_ENVIRON)
205 0e3baaf3 Iustin Pop
206 0e3baaf3 Iustin Pop
  if not os.path.isdir(file_storage_dir):
207 0e3baaf3 Iustin Pop
    raise errors.OpPrereqError("The file storage directory '%s' is not"
208 0e3baaf3 Iustin Pop
                               " a directory." % file_storage_dir,
209 0e3baaf3 Iustin Pop
                               errors.ECODE_ENVIRON)
210 0e3baaf3 Iustin Pop
  return file_storage_dir
211 0e3baaf3 Iustin Pop
212 0e3baaf3 Iustin Pop
213 ec0652ad Guido Trotter
def InitCluster(cluster_name, mac_prefix,
214 ce735215 Guido Trotter
                master_netdev, file_storage_dir, candidate_pool_size,
215 b6a30b0d Guido Trotter
                secondary_ip=None, vg_name=None, beparams=None,
216 b6a30b0d Guido Trotter
                nicparams=None, hvparams=None, enabled_hypervisors=None,
217 3953242f Iustin Pop
                modify_etc_hosts=True, modify_ssh_setup=True,
218 39b0f0c2 Balazs Lecz
                maintain_node_health=False,
219 39b0f0c2 Balazs Lecz
                uid_pool=None):
220 a0c9f010 Michael Hanselmann
  """Initialise the cluster.
221 a0c9f010 Michael Hanselmann

222 ce735215 Guido Trotter
  @type candidate_pool_size: int
223 ce735215 Guido Trotter
  @param candidate_pool_size: master candidate pool size
224 ce735215 Guido Trotter

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

370 7b3a8fb5 Iustin Pop
  It will contain the current node, which will also be the master
371 7b3a8fb5 Iustin Pop
  node, and no instances.
372 7b3a8fb5 Iustin Pop

373 7b3a8fb5 Iustin Pop
  @type version: int
374 c41eea6e Iustin Pop
  @param version: configuration version
375 c41eea6e Iustin Pop
  @type cluster_config: L{objects.Cluster}
376 c41eea6e Iustin Pop
  @param cluster_config: cluster configuration
377 c41eea6e Iustin Pop
  @type master_node_config: L{objects.Node}
378 c41eea6e Iustin Pop
  @param master_node_config: master node configuration
379 c41eea6e Iustin Pop
  @type cfg_file: string
380 c41eea6e Iustin Pop
  @param cfg_file: configuration file path
381 c41eea6e Iustin Pop

382 7b3a8fb5 Iustin Pop
  """
383 7b3a8fb5 Iustin Pop
  nodes = {
384 7b3a8fb5 Iustin Pop
    master_node_config.name: master_node_config,
385 7b3a8fb5 Iustin Pop
    }
386 7b3a8fb5 Iustin Pop
387 d693c864 Iustin Pop
  now = time.time()
388 7b3a8fb5 Iustin Pop
  config_data = objects.ConfigData(version=version,
389 7b3a8fb5 Iustin Pop
                                   cluster=cluster_config,
390 7b3a8fb5 Iustin Pop
                                   nodes=nodes,
391 7b3a8fb5 Iustin Pop
                                   instances={},
392 d693c864 Iustin Pop
                                   serial_no=1,
393 d693c864 Iustin Pop
                                   ctime=now, mtime=now)
394 a33848a5 Guido Trotter
  utils.WriteFile(cfg_file,
395 a33848a5 Guido Trotter
                  data=serializer.Dump(config_data.ToDict()),
396 a33848a5 Guido Trotter
                  mode=0600)
397 02f99608 Oleksiy Mishchenko
398 02f99608 Oleksiy Mishchenko
399 140aa4a8 Iustin Pop
def FinalizeClusterDestroy(master):
400 140aa4a8 Iustin Pop
  """Execute the last steps of cluster destroy
401 140aa4a8 Iustin Pop

402 140aa4a8 Iustin Pop
  This function shuts down all the daemons, completing the destroy
403 140aa4a8 Iustin Pop
  begun in cmdlib.LUDestroyOpcode.
404 140aa4a8 Iustin Pop

405 140aa4a8 Iustin Pop
  """
406 b989b9d9 Ken Wehr
  cfg = config.ConfigWriter()
407 b989b9d9 Ken Wehr
  modify_ssh_setup = cfg.GetClusterInfo().modify_ssh_setup
408 781de953 Iustin Pop
  result = rpc.RpcRunner.call_node_stop_master(master, True)
409 3cebe102 Michael Hanselmann
  msg = result.fail_msg
410 6c00d19a Iustin Pop
  if msg:
411 099c52ad Iustin Pop
    logging.warning("Could not disable the master role: %s", msg)
412 b989b9d9 Ken Wehr
  result = rpc.RpcRunner.call_node_leave_cluster(master, modify_ssh_setup)
413 3cebe102 Michael Hanselmann
  msg = result.fail_msg
414 0623d351 Iustin Pop
  if msg:
415 0623d351 Iustin Pop
    logging.warning("Could not shutdown the node daemon and cleanup"
416 0623d351 Iustin Pop
                    " the node: %s", msg)
417 140aa4a8 Iustin Pop
418 140aa4a8 Iustin Pop
419 87622829 Iustin Pop
def SetupNodeDaemon(cluster_name, node, ssh_key_check):
420 827f753e Guido Trotter
  """Add a node to the cluster.
421 827f753e Guido Trotter

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

426 87622829 Iustin Pop
  @param cluster_name: the cluster name
427 87622829 Iustin Pop
  @param node: the name of the new node
428 87622829 Iustin Pop
  @param ssh_key_check: whether to do a strict key check
429 827f753e Guido Trotter

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

489 b1b6ea87 Iustin Pop
  This checks that we are not already the master, and will cause the
490 b1b6ea87 Iustin Pop
  current master to cease being master, and the non-master to become
491 b1b6ea87 Iustin Pop
  new master.
492 b1b6ea87 Iustin Pop

493 8e2524c3 Guido Trotter
  @type no_voting: boolean
494 8e2524c3 Guido Trotter
  @param no_voting: force the operation without remote nodes agreement
495 8e2524c3 Guido Trotter
                      (dangerous)
496 8e2524c3 Guido Trotter

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

571 8eb148ae Iustin Pop
  This is a separate function in bootstrap since it's needed by
572 8eb148ae Iustin Pop
  gnt-cluster, and instead of importing directly ssconf, it's better
573 8eb148ae Iustin Pop
  to abstract it in bootstrap, where we do use ssconf in other
574 8eb148ae Iustin Pop
  functions too.
575 8eb148ae Iustin Pop

576 8eb148ae Iustin Pop
  """
577 8eb148ae Iustin Pop
  sstore = ssconf.SimpleStore()
578 8eb148ae Iustin Pop
579 8eb148ae Iustin Pop
  old_master, _ = ssconf.GetMasterAndMyself(sstore)
580 8eb148ae Iustin Pop
581 8eb148ae Iustin Pop
  return old_master
582 8eb148ae Iustin Pop
583 8eb148ae Iustin Pop
584 d7cdb55d Iustin Pop
def GatherMasterVotes(node_list):
585 d7cdb55d Iustin Pop
  """Check the agreement on who is the master.
586 d7cdb55d Iustin Pop

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

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

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

598 d7cdb55d Iustin Pop
  @type node_list: list
599 d7cdb55d Iustin Pop
  @param node_list: the list of nodes to query for master info; the current
600 5bbd3f7f Michael Hanselmann
      node will be removed if it is in the list
601 d7cdb55d Iustin Pop
  @rtype: list
602 d7cdb55d Iustin Pop
  @return: list of (node, votes)
603 d7cdb55d Iustin Pop

604 d7cdb55d Iustin Pop
  """
605 d7cdb55d Iustin Pop
  myself = utils.HostInfo().name
606 d7cdb55d Iustin Pop
  try:
607 d7cdb55d Iustin Pop
    node_list.remove(myself)
608 d7cdb55d Iustin Pop
  except ValueError:
609 d7cdb55d Iustin Pop
    pass
610 d7cdb55d Iustin Pop
  if not node_list:
611 d7cdb55d Iustin Pop
    # no nodes left (eventually after removing myself)
612 d7cdb55d Iustin Pop
    return []
613 d7cdb55d Iustin Pop
  results = rpc.RpcRunner.call_master_info(node_list)
614 d7cdb55d Iustin Pop
  if not isinstance(results, dict):
615 d7cdb55d Iustin Pop
    # this should not happen (unless internal error in rpc)
616 d7cdb55d Iustin Pop
    logging.critical("Can't complete rpc call, aborting master startup")
617 d7cdb55d Iustin Pop
    return [(None, len(node_list))]
618 d7cdb55d Iustin Pop
  votes = {}
619 d7cdb55d Iustin Pop
  for node in results:
620 781de953 Iustin Pop
    nres = results[node]
621 2a52a064 Iustin Pop
    data = nres.payload
622 3cebe102 Michael Hanselmann
    msg = nres.fail_msg
623 2a52a064 Iustin Pop
    fail = False
624 2a52a064 Iustin Pop
    if msg:
625 2a52a064 Iustin Pop
      logging.warning("Error contacting node %s: %s", node, msg)
626 2a52a064 Iustin Pop
      fail = True
627 2a52a064 Iustin Pop
    elif not isinstance(data, (tuple, list)) or len(data) < 3:
628 2a52a064 Iustin Pop
      logging.warning("Invalid data received from node %s: %s", node, data)
629 2a52a064 Iustin Pop
      fail = True
630 2a52a064 Iustin Pop
    if fail:
631 d7cdb55d Iustin Pop
      if None not in votes:
632 d7cdb55d Iustin Pop
        votes[None] = 0
633 d7cdb55d Iustin Pop
      votes[None] += 1
634 d7cdb55d Iustin Pop
      continue
635 781de953 Iustin Pop
    master_node = data[2]
636 d7cdb55d Iustin Pop
    if master_node not in votes:
637 d7cdb55d Iustin Pop
      votes[master_node] = 0
638 d7cdb55d Iustin Pop
    votes[master_node] += 1
639 d7cdb55d Iustin Pop
640 d7cdb55d Iustin Pop
  vote_list = [v for v in votes.items()]
641 d7cdb55d Iustin Pop
  # sort first on number of votes then on name, since we want None
642 d7cdb55d Iustin Pop
  # sorted later if we have the half of the nodes not responding, and
643 d7cdb55d Iustin Pop
  # half voting all for the same master
644 d7cdb55d Iustin Pop
  vote_list.sort(key=lambda x: (x[1], x[0]), reverse=True)
645 d7cdb55d Iustin Pop
646 d7cdb55d Iustin Pop
  return vote_list