Statistics
| Branch: | Tag: | Revision:

root / lib / bootstrap.py @ 43575108

History | View | Annotate | Download (22.3 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 43575108 Michael Hanselmann
def GenerateClusterCrypto(new_cluster_cert, new_rapi_cert, new_hmac_key,
115 43575108 Michael Hanselmann
                          rapi_cert_pem=None):
116 43575108 Michael Hanselmann
  """Updates the cluster certificates, keys and secrets.
117 43575108 Michael Hanselmann

118 43575108 Michael Hanselmann
  @type new_cluster_cert: bool
119 43575108 Michael Hanselmann
  @param new_cluster_cert: Whether to generate a new cluster certificate
120 43575108 Michael Hanselmann
  @type new_rapi_cert: bool
121 43575108 Michael Hanselmann
  @param new_rapi_cert: Whether to generate a new RAPI certificate
122 43575108 Michael Hanselmann
  @type new_hmac_key: bool
123 43575108 Michael Hanselmann
  @param new_hmac_key: Whether to generate a new HMAC key
124 43575108 Michael Hanselmann
  @type rapi_cert_pem: string
125 43575108 Michael Hanselmann
  @param rapi_cert_pem: New RAPI certificate in PEM format
126 43575108 Michael Hanselmann

127 43575108 Michael Hanselmann
  """
128 43575108 Michael Hanselmann
  # SSL certificate
129 43575108 Michael Hanselmann
  cluster_cert_exists = os.path.exists(constants.SSL_CERT_FILE)
130 43575108 Michael Hanselmann
  if new_cluster_cert or not cluster_cert_exists:
131 43575108 Michael Hanselmann
    if cluster_cert_exists:
132 43575108 Michael Hanselmann
      utils.CreateBackup(constants.SSL_CERT_FILE)
133 43575108 Michael Hanselmann
134 43575108 Michael Hanselmann
    logging.debug("Generating new cluster certificate at %s",
135 43575108 Michael Hanselmann
                  constants.SSL_CERT_FILE)
136 43575108 Michael Hanselmann
    GenerateSelfSignedSslCert(constants.SSL_CERT_FILE)
137 43575108 Michael Hanselmann
138 43575108 Michael Hanselmann
  # HMAC key
139 43575108 Michael Hanselmann
  if new_hmac_key or not os.path.exists(constants.HMAC_CLUSTER_KEY):
140 43575108 Michael Hanselmann
    logging.debug("Writing new HMAC key to %s", constants.HMAC_CLUSTER_KEY)
141 43575108 Michael Hanselmann
    GenerateHmacKey(constants.HMAC_CLUSTER_KEY)
142 43575108 Michael Hanselmann
143 43575108 Michael Hanselmann
  # RAPI
144 43575108 Michael Hanselmann
  rapi_cert_exists = os.path.exists(constants.RAPI_CERT_FILE)
145 43575108 Michael Hanselmann
146 43575108 Michael Hanselmann
  if rapi_cert_pem:
147 43575108 Michael Hanselmann
    # Assume rapi_pem contains a valid PEM-formatted certificate and key
148 43575108 Michael Hanselmann
    logging.debug("Writing RAPI certificate at %s",
149 43575108 Michael Hanselmann
                  constants.RAPI_CERT_FILE)
150 43575108 Michael Hanselmann
    utils.WriteFile(constants.RAPI_CERT_FILE, data=rapi_cert_pem, backup=True)
151 43575108 Michael Hanselmann
152 43575108 Michael Hanselmann
  elif new_rapi_cert or not rapi_cert_exists:
153 43575108 Michael Hanselmann
    if rapi_cert_exists:
154 43575108 Michael Hanselmann
      utils.CreateBackup(constants.RAPI_CERT_FILE)
155 43575108 Michael Hanselmann
156 43575108 Michael Hanselmann
    logging.debug("Generating new RAPI certificate at %s",
157 43575108 Michael Hanselmann
                  constants.RAPI_CERT_FILE)
158 43575108 Michael Hanselmann
    GenerateSelfSignedSslCert(constants.RAPI_CERT_FILE)
159 c008906b Michael Hanselmann
160 c008906b Michael Hanselmann
161 8f215968 Michael Hanselmann
def _InitGanetiServerSetup(master_name):
162 40a97d80 Michael Hanselmann
  """Setup the necessary configuration for the initial node daemon.
163 40a97d80 Michael Hanselmann

164 40a97d80 Michael Hanselmann
  This creates the nodepass file containing the shared password for
165 40a97d80 Michael Hanselmann
  the cluster and also generates the SSL certificate.
166 40a97d80 Michael Hanselmann

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

183 5627f375 Michael Hanselmann
  """
184 d3833ebd Michael Hanselmann
  def _CheckNodeDaemon():
185 5627f375 Michael Hanselmann
    result = rpc.RpcRunner.call_version([node_name])[node_name]
186 d3833ebd Michael Hanselmann
    if result.fail_msg:
187 d3833ebd Michael Hanselmann
      raise utils.RetryAgain()
188 8f215968 Michael Hanselmann
189 d3833ebd Michael Hanselmann
  try:
190 d3833ebd Michael Hanselmann
    utils.Retry(_CheckNodeDaemon, 1.0, 10.0)
191 d3833ebd Michael Hanselmann
  except utils.RetryTimeout:
192 5627f375 Michael Hanselmann
    raise errors.OpExecError("Node daemon on %s didn't answer queries within"
193 5627f375 Michael Hanselmann
                             " 10 seconds" % node_name)
194 5627f375 Michael Hanselmann
195 a0c9f010 Michael Hanselmann
196 ec0652ad Guido Trotter
def InitCluster(cluster_name, mac_prefix,
197 ce735215 Guido Trotter
                master_netdev, file_storage_dir, candidate_pool_size,
198 b6a30b0d Guido Trotter
                secondary_ip=None, vg_name=None, beparams=None,
199 b6a30b0d Guido Trotter
                nicparams=None, hvparams=None, enabled_hypervisors=None,
200 b989b9d9 Ken Wehr
                modify_etc_hosts=True, modify_ssh_setup=True):
201 a0c9f010 Michael Hanselmann
  """Initialise the cluster.
202 a0c9f010 Michael Hanselmann

203 ce735215 Guido Trotter
  @type candidate_pool_size: int
204 ce735215 Guido Trotter
  @param candidate_pool_size: master candidate pool size
205 ce735215 Guido Trotter

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

366 7b3a8fb5 Iustin Pop
  It will contain the current node, which will also be the master
367 7b3a8fb5 Iustin Pop
  node, and no instances.
368 7b3a8fb5 Iustin Pop

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

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

398 140aa4a8 Iustin Pop
  This function shuts down all the daemons, completing the destroy
399 140aa4a8 Iustin Pop
  begun in cmdlib.LUDestroyOpcode.
400 140aa4a8 Iustin Pop

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

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

422 87622829 Iustin Pop
  @param cluster_name: the cluster name
423 87622829 Iustin Pop
  @param node: the name of the new node
424 87622829 Iustin Pop
  @param ssh_key_check: whether to do a strict key check
425 827f753e Guido Trotter

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

484 b1b6ea87 Iustin Pop
  This checks that we are not already the master, and will cause the
485 b1b6ea87 Iustin Pop
  current master to cease being master, and the non-master to become
486 b1b6ea87 Iustin Pop
  new master.
487 b1b6ea87 Iustin Pop

488 8e2524c3 Guido Trotter
  @type no_voting: boolean
489 8e2524c3 Guido Trotter
  @param no_voting: force the operation without remote nodes agreement
490 8e2524c3 Guido Trotter
                      (dangerous)
491 8e2524c3 Guido Trotter

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

566 8eb148ae Iustin Pop
  This is a separate function in bootstrap since it's needed by
567 8eb148ae Iustin Pop
  gnt-cluster, and instead of importing directly ssconf, it's better
568 8eb148ae Iustin Pop
  to abstract it in bootstrap, where we do use ssconf in other
569 8eb148ae Iustin Pop
  functions too.
570 8eb148ae Iustin Pop

571 8eb148ae Iustin Pop
  """
572 8eb148ae Iustin Pop
  sstore = ssconf.SimpleStore()
573 8eb148ae Iustin Pop
574 8eb148ae Iustin Pop
  old_master, _ = ssconf.GetMasterAndMyself(sstore)
575 8eb148ae Iustin Pop
576 8eb148ae Iustin Pop
  return old_master
577 8eb148ae Iustin Pop
578 8eb148ae Iustin Pop
579 d7cdb55d Iustin Pop
def GatherMasterVotes(node_list):
580 d7cdb55d Iustin Pop
  """Check the agreement on who is the master.
581 d7cdb55d Iustin Pop

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

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

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

593 d7cdb55d Iustin Pop
  @type node_list: list
594 d7cdb55d Iustin Pop
  @param node_list: the list of nodes to query for master info; the current
595 5bbd3f7f Michael Hanselmann
      node will be removed if it is in the list
596 d7cdb55d Iustin Pop
  @rtype: list
597 d7cdb55d Iustin Pop
  @return: list of (node, votes)
598 d7cdb55d Iustin Pop

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