Statistics
| Branch: | Tag: | Revision:

root / lib / bootstrap.py @ 1fe93c75

History | View | Annotate | Download (19.1 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 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 a0c9f010 Michael Hanselmann
  f = open(pub_key, 'r')
66 a0c9f010 Michael Hanselmann
  try:
67 a0c9f010 Michael Hanselmann
    utils.AddAuthorizedKey(auth_keys, f.read(8192))
68 a0c9f010 Michael Hanselmann
  finally:
69 a0c9f010 Michael Hanselmann
    f.close()
70 a0c9f010 Michael Hanselmann
71 a0c9f010 Michael Hanselmann
72 40a97d80 Michael Hanselmann
def _GenerateSelfSignedSslCert(file_name, validity=(365 * 5)):
73 40a97d80 Michael Hanselmann
  """Generates a self-signed SSL certificate.
74 a0c9f010 Michael Hanselmann

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

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

109 40a97d80 Michael Hanselmann
  This creates the nodepass file containing the shared password for
110 40a97d80 Michael Hanselmann
  the cluster and also generates the SSL certificate.
111 40a97d80 Michael Hanselmann

112 40a97d80 Michael Hanselmann
  """
113 40a97d80 Michael Hanselmann
  _GenerateSelfSignedSslCert(constants.SSL_CERT_FILE)
114 a0c9f010 Michael Hanselmann
115 61a08fa3 Michael Hanselmann
  # Don't overwrite existing file
116 61a08fa3 Michael Hanselmann
  if not os.path.exists(constants.RAPI_CERT_FILE):
117 61a08fa3 Michael Hanselmann
    _GenerateSelfSignedSslCert(constants.RAPI_CERT_FILE)
118 61a08fa3 Michael Hanselmann
119 4a34c5cf Guido Trotter
  if not os.path.exists(constants.HMAC_CLUSTER_KEY):
120 4a34c5cf Guido Trotter
    utils.WriteFile(constants.HMAC_CLUSTER_KEY,
121 4a34c5cf Guido Trotter
                    data=utils.GenerateSecret(),
122 4a34c5cf Guido Trotter
                    mode=0400)
123 4a34c5cf Guido Trotter
124 a0c9f010 Michael Hanselmann
  result = utils.RunCmd([constants.NODE_INITD_SCRIPT, "restart"])
125 a0c9f010 Michael Hanselmann
126 a0c9f010 Michael Hanselmann
  if result.failed:
127 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("Could not start the node daemon, command %s"
128 a0c9f010 Michael Hanselmann
                             " had exitcode %s and error %s" %
129 a0c9f010 Michael Hanselmann
                             (result.cmd, result.exit_code, result.output))
130 a0c9f010 Michael Hanselmann
131 a0c9f010 Michael Hanselmann
132 ec0652ad Guido Trotter
def InitCluster(cluster_name, mac_prefix,
133 ce735215 Guido Trotter
                master_netdev, file_storage_dir, candidate_pool_size,
134 b6a30b0d Guido Trotter
                secondary_ip=None, vg_name=None, beparams=None,
135 b6a30b0d Guido Trotter
                nicparams=None, hvparams=None, enabled_hypervisors=None,
136 066f465d Guido Trotter
                modify_etc_hosts=True):
137 a0c9f010 Michael Hanselmann
  """Initialise the cluster.
138 a0c9f010 Michael Hanselmann

139 ce735215 Guido Trotter
  @type candidate_pool_size: int
140 ce735215 Guido Trotter
  @param candidate_pool_size: master candidate pool size
141 ce735215 Guido Trotter

142 a0c9f010 Michael Hanselmann
  """
143 ce735215 Guido Trotter
  # TODO: complete the docstring
144 a0c9f010 Michael Hanselmann
  if config.ConfigWriter.IsCluster():
145 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Cluster is already initialised")
146 a0c9f010 Michael Hanselmann
147 b119bccb Guido Trotter
  if not enabled_hypervisors:
148 b119bccb Guido Trotter
    raise errors.OpPrereqError("Enabled hypervisors list must contain at"
149 b119bccb Guido Trotter
                               " least one member")
150 b119bccb Guido Trotter
  invalid_hvs = set(enabled_hypervisors) - constants.HYPER_TYPES
151 b119bccb Guido Trotter
  if invalid_hvs:
152 b119bccb Guido Trotter
    raise errors.OpPrereqError("Enabled hypervisors contains invalid"
153 b119bccb Guido Trotter
                               " entries: %s" % invalid_hvs)
154 b119bccb Guido Trotter
155 a0c9f010 Michael Hanselmann
  hostname = utils.HostInfo()
156 a0c9f010 Michael Hanselmann
157 a0c9f010 Michael Hanselmann
  if hostname.ip.startswith("127."):
158 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("This host's IP resolves to the private"
159 a0c9f010 Michael Hanselmann
                               " range (%s). Please fix DNS or %s." %
160 a0c9f010 Michael Hanselmann
                               (hostname.ip, constants.ETC_HOSTS))
161 a0c9f010 Michael Hanselmann
162 caad16e2 Iustin Pop
  if not utils.OwnIpAddress(hostname.ip):
163 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Inconsistency: this host's name resolves"
164 a0c9f010 Michael Hanselmann
                               " to %s,\nbut this ip address does not"
165 a0c9f010 Michael Hanselmann
                               " belong to this host."
166 a0c9f010 Michael Hanselmann
                               " Aborting." % hostname.ip)
167 a0c9f010 Michael Hanselmann
168 a0c9f010 Michael Hanselmann
  clustername = utils.HostInfo(cluster_name)
169 a0c9f010 Michael Hanselmann
170 a0c9f010 Michael Hanselmann
  if utils.TcpPing(clustername.ip, constants.DEFAULT_NODED_PORT,
171 a0c9f010 Michael Hanselmann
                   timeout=5):
172 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Cluster IP already active. Aborting.")
173 a0c9f010 Michael Hanselmann
174 a0c9f010 Michael Hanselmann
  if secondary_ip:
175 a0c9f010 Michael Hanselmann
    if not utils.IsValidIP(secondary_ip):
176 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("Invalid secondary ip given")
177 a0c9f010 Michael Hanselmann
    if (secondary_ip != hostname.ip and
178 caad16e2 Iustin Pop
        not utils.OwnIpAddress(secondary_ip)):
179 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("You gave %s as secondary IP,"
180 a0c9f010 Michael Hanselmann
                                 " but it does not belong to this host." %
181 a0c9f010 Michael Hanselmann
                                 secondary_ip)
182 b9eeeb02 Michael Hanselmann
  else:
183 b9eeeb02 Michael Hanselmann
    secondary_ip = hostname.ip
184 a0c9f010 Michael Hanselmann
185 a0c9f010 Michael Hanselmann
  if vg_name is not None:
186 a0c9f010 Michael Hanselmann
    # Check if volume group is valid
187 a0c9f010 Michael Hanselmann
    vgstatus = utils.CheckVolumeGroupSize(utils.ListVolumeGroups(), vg_name,
188 a0c9f010 Michael Hanselmann
                                          constants.MIN_VG_SIZE)
189 a0c9f010 Michael Hanselmann
    if vgstatus:
190 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("Error: %s\nspecify --no-lvm-storage if"
191 a0c9f010 Michael Hanselmann
                                 " you are not using lvm" % vgstatus)
192 a0c9f010 Michael Hanselmann
193 a0c9f010 Michael Hanselmann
  file_storage_dir = os.path.normpath(file_storage_dir)
194 a0c9f010 Michael Hanselmann
195 a0c9f010 Michael Hanselmann
  if not os.path.isabs(file_storage_dir):
196 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("The file storage directory you passed is"
197 a0c9f010 Michael Hanselmann
                               " not an absolute path.")
198 a0c9f010 Michael Hanselmann
199 a0c9f010 Michael Hanselmann
  if not os.path.exists(file_storage_dir):
200 a0c9f010 Michael Hanselmann
    try:
201 a0c9f010 Michael Hanselmann
      os.makedirs(file_storage_dir, 0750)
202 a0c9f010 Michael Hanselmann
    except OSError, err:
203 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("Cannot create file storage directory"
204 a0c9f010 Michael Hanselmann
                                 " '%s': %s" %
205 a0c9f010 Michael Hanselmann
                                 (file_storage_dir, err))
206 a0c9f010 Michael Hanselmann
207 a0c9f010 Michael Hanselmann
  if not os.path.isdir(file_storage_dir):
208 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("The file storage directory '%s' is not"
209 a0c9f010 Michael Hanselmann
                               " a directory." % file_storage_dir)
210 a0c9f010 Michael Hanselmann
211 a0c9f010 Michael Hanselmann
  if not re.match("^[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}$", mac_prefix):
212 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Invalid mac prefix given '%s'" % mac_prefix)
213 a0c9f010 Michael Hanselmann
214 a0c9f010 Michael Hanselmann
  result = utils.RunCmd(["ip", "link", "show", "dev", master_netdev])
215 a0c9f010 Michael Hanselmann
  if result.failed:
216 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Invalid master netdev given (%s): '%s'" %
217 a0c9f010 Michael Hanselmann
                               (master_netdev,
218 a0c9f010 Michael Hanselmann
                                result.output.strip()))
219 a0c9f010 Michael Hanselmann
220 a0c9f010 Michael Hanselmann
  if not (os.path.isfile(constants.NODE_INITD_SCRIPT) and
221 a0c9f010 Michael Hanselmann
          os.access(constants.NODE_INITD_SCRIPT, os.X_OK)):
222 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Init.d script '%s' missing or not"
223 a0c9f010 Michael Hanselmann
                               " executable." % constants.NODE_INITD_SCRIPT)
224 a0c9f010 Michael Hanselmann
225 9dae41ad Guido Trotter
  dirs = [(constants.RUN_GANETI_DIR, constants.RUN_DIRS_MODE)]
226 9dae41ad Guido Trotter
  utils.EnsureDirs(dirs)
227 9dae41ad Guido Trotter
228 a5728081 Guido Trotter
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
229 b6a30b0d Guido Trotter
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
230 b6a30b0d Guido Trotter
  objects.NIC.CheckParameterSyntax(nicparams)
231 b6a30b0d Guido Trotter
232 a5728081 Guido Trotter
  # hvparams is a mapping of hypervisor->hvparams dict
233 a5728081 Guido Trotter
  for hv_name, hv_params in hvparams.iteritems():
234 a5728081 Guido Trotter
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
235 a5728081 Guido Trotter
    hv_class = hypervisor.GetHypervisor(hv_name)
236 a5728081 Guido Trotter
    hv_class.CheckParameterSyntax(hv_params)
237 d4b72030 Guido Trotter
238 a0c9f010 Michael Hanselmann
  # set up the inter-node password and certificate
239 d23ef431 Michael Hanselmann
  _InitGanetiServerSetup()
240 a0c9f010 Michael Hanselmann
241 a0c9f010 Michael Hanselmann
  # set up ssh config and /etc/hosts
242 a0c9f010 Michael Hanselmann
  f = open(constants.SSH_HOST_RSA_PUB, 'r')
243 a0c9f010 Michael Hanselmann
  try:
244 a0c9f010 Michael Hanselmann
    sshline = f.read()
245 a0c9f010 Michael Hanselmann
  finally:
246 a0c9f010 Michael Hanselmann
    f.close()
247 a0c9f010 Michael Hanselmann
  sshkey = sshline.split(" ")[1]
248 a0c9f010 Michael Hanselmann
249 b86a6bcd Guido Trotter
  if modify_etc_hosts:
250 b86a6bcd Guido Trotter
    utils.AddHostToEtcHosts(hostname.name)
251 b86a6bcd Guido Trotter
252 531baf8e Iustin Pop
  _InitSSHSetup()
253 a0c9f010 Michael Hanselmann
254 a0c9f010 Michael Hanselmann
  # init of cluster config file
255 b9eeeb02 Michael Hanselmann
  cluster_config = objects.Cluster(
256 b9eeeb02 Michael Hanselmann
    serial_no=1,
257 b9eeeb02 Michael Hanselmann
    rsahostkeypub=sshkey,
258 b9eeeb02 Michael Hanselmann
    highest_used_port=(constants.FIRST_DRBD_PORT - 1),
259 b9eeeb02 Michael Hanselmann
    mac_prefix=mac_prefix,
260 b9eeeb02 Michael Hanselmann
    volume_group_name=vg_name,
261 b9eeeb02 Michael Hanselmann
    tcpudp_port_pool=set(),
262 f6bd6e98 Michael Hanselmann
    master_node=hostname.name,
263 f6bd6e98 Michael Hanselmann
    master_ip=clustername.ip,
264 f6bd6e98 Michael Hanselmann
    master_netdev=master_netdev,
265 f6bd6e98 Michael Hanselmann
    cluster_name=clustername.name,
266 f6bd6e98 Michael Hanselmann
    file_storage_dir=file_storage_dir,
267 ea3a925f Alexander Schreiber
    enabled_hypervisors=enabled_hypervisors,
268 4ef7f423 Guido Trotter
    beparams={constants.PP_DEFAULT: beparams},
269 b6a30b0d Guido Trotter
    nicparams={constants.PP_DEFAULT: nicparams},
270 ea3a925f Alexander Schreiber
    hvparams=hvparams,
271 ce735215 Guido Trotter
    candidate_pool_size=candidate_pool_size,
272 022c3a0b Guido Trotter
    modify_etc_hosts=modify_etc_hosts,
273 b9eeeb02 Michael Hanselmann
    )
274 b9eeeb02 Michael Hanselmann
  master_node_config = objects.Node(name=hostname.name,
275 b9eeeb02 Michael Hanselmann
                                    primary_ip=hostname.ip,
276 b9222f32 Guido Trotter
                                    secondary_ip=secondary_ip,
277 c044f32c Guido Trotter
                                    serial_no=1,
278 c044f32c Guido Trotter
                                    master_candidate=True,
279 af64c0ea Iustin Pop
                                    offline=False, drained=False,
280 c044f32c Guido Trotter
                                    )
281 9e1333b9 Guido Trotter
  InitConfig(constants.CONFIG_VERSION, cluster_config, master_node_config)
282 05cc153f Guido Trotter
  cfg = config.ConfigWriter()
283 9e1333b9 Guido Trotter
  ssh.WriteKnownHostsFile(cfg, constants.SSH_KNOWN_HOSTS_FILE)
284 05cc153f Guido Trotter
  cfg.Update(cfg.GetClusterInfo())
285 827f753e Guido Trotter
286 b3f1cf6f Iustin Pop
  # start the master ip
287 b3f1cf6f Iustin Pop
  # TODO: Review rpc call from bootstrap
288 b726aff0 Iustin Pop
  # TODO: Warn on failed start master
289 3583908a Guido Trotter
  rpc.RpcRunner.call_node_start_master(hostname.name, True, False)
290 b3f1cf6f Iustin Pop
291 b1b6ea87 Iustin Pop
292 02f99608 Oleksiy Mishchenko
def InitConfig(version, cluster_config, master_node_config,
293 02f99608 Oleksiy Mishchenko
               cfg_file=constants.CLUSTER_CONF_FILE):
294 7b3a8fb5 Iustin Pop
  """Create the initial cluster configuration.
295 7b3a8fb5 Iustin Pop

296 7b3a8fb5 Iustin Pop
  It will contain the current node, which will also be the master
297 7b3a8fb5 Iustin Pop
  node, and no instances.
298 7b3a8fb5 Iustin Pop

299 7b3a8fb5 Iustin Pop
  @type version: int
300 c41eea6e Iustin Pop
  @param version: configuration version
301 c41eea6e Iustin Pop
  @type cluster_config: L{objects.Cluster}
302 c41eea6e Iustin Pop
  @param cluster_config: cluster configuration
303 c41eea6e Iustin Pop
  @type master_node_config: L{objects.Node}
304 c41eea6e Iustin Pop
  @param master_node_config: master node configuration
305 c41eea6e Iustin Pop
  @type cfg_file: string
306 c41eea6e Iustin Pop
  @param cfg_file: configuration file path
307 c41eea6e Iustin Pop

308 7b3a8fb5 Iustin Pop
  """
309 7b3a8fb5 Iustin Pop
  nodes = {
310 7b3a8fb5 Iustin Pop
    master_node_config.name: master_node_config,
311 7b3a8fb5 Iustin Pop
    }
312 7b3a8fb5 Iustin Pop
313 7b3a8fb5 Iustin Pop
  config_data = objects.ConfigData(version=version,
314 7b3a8fb5 Iustin Pop
                                   cluster=cluster_config,
315 7b3a8fb5 Iustin Pop
                                   nodes=nodes,
316 7b3a8fb5 Iustin Pop
                                   instances={},
317 7b3a8fb5 Iustin Pop
                                   serial_no=1)
318 a33848a5 Guido Trotter
  utils.WriteFile(cfg_file,
319 a33848a5 Guido Trotter
                  data=serializer.Dump(config_data.ToDict()),
320 a33848a5 Guido Trotter
                  mode=0600)
321 02f99608 Oleksiy Mishchenko
322 02f99608 Oleksiy Mishchenko
323 140aa4a8 Iustin Pop
def FinalizeClusterDestroy(master):
324 140aa4a8 Iustin Pop
  """Execute the last steps of cluster destroy
325 140aa4a8 Iustin Pop

326 140aa4a8 Iustin Pop
  This function shuts down all the daemons, completing the destroy
327 140aa4a8 Iustin Pop
  begun in cmdlib.LUDestroyOpcode.
328 140aa4a8 Iustin Pop

329 140aa4a8 Iustin Pop
  """
330 781de953 Iustin Pop
  result = rpc.RpcRunner.call_node_stop_master(master, True)
331 6c00d19a Iustin Pop
  msg = result.RemoteFailMsg()
332 6c00d19a Iustin Pop
  if msg:
333 6c00d19a Iustin Pop
    logging.warning("Could not disable the master role: %s" % msg)
334 781de953 Iustin Pop
  result = rpc.RpcRunner.call_node_leave_cluster(master)
335 0623d351 Iustin Pop
  msg = result.RemoteFailMsg()
336 0623d351 Iustin Pop
  if msg:
337 0623d351 Iustin Pop
    logging.warning("Could not shutdown the node daemon and cleanup"
338 0623d351 Iustin Pop
                    " the node: %s", msg)
339 140aa4a8 Iustin Pop
340 140aa4a8 Iustin Pop
341 87622829 Iustin Pop
def SetupNodeDaemon(cluster_name, node, ssh_key_check):
342 827f753e Guido Trotter
  """Add a node to the cluster.
343 827f753e Guido Trotter

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

348 87622829 Iustin Pop
  @param cluster_name: the cluster name
349 87622829 Iustin Pop
  @param node: the name of the new node
350 87622829 Iustin Pop
  @param ssh_key_check: whether to do a strict key check
351 827f753e Guido Trotter

352 827f753e Guido Trotter
  """
353 87622829 Iustin Pop
  sshrunner = ssh.SshRunner(cluster_name)
354 5557b04c Michael Hanselmann
355 5557b04c Michael Hanselmann
  noded_cert = utils.ReadFile(constants.SSL_CERT_FILE)
356 2438c157 Michael Hanselmann
  rapi_cert = utils.ReadFile(constants.RAPI_CERT_FILE)
357 5557b04c Michael Hanselmann
358 827f753e Guido Trotter
  # in the base64 pem encoding, neither '!' nor '.' are valid chars,
359 827f753e Guido Trotter
  # so we use this to detect an invalid certificate; as long as the
360 827f753e Guido Trotter
  # cert doesn't contain this, the here-document will be correctly
361 827f753e Guido Trotter
  # parsed by the shell sequence below
362 2438c157 Michael Hanselmann
  if (re.search('^!EOF\.', noded_cert, re.MULTILINE) or
363 2438c157 Michael Hanselmann
      re.search('^!EOF\.', rapi_cert, re.MULTILINE)):
364 827f753e Guido Trotter
    raise errors.OpExecError("invalid PEM encoding in the SSL certificate")
365 5557b04c Michael Hanselmann
366 5557b04c Michael Hanselmann
  if not noded_cert.endswith("\n"):
367 5557b04c Michael Hanselmann
    noded_cert += "\n"
368 2438c157 Michael Hanselmann
  if not rapi_cert.endswith("\n"):
369 2438c157 Michael Hanselmann
    rapi_cert += "\n"
370 827f753e Guido Trotter
371 827f753e Guido Trotter
  # set up inter-node password and certificate and restarts the node daemon
372 827f753e Guido Trotter
  # and then connect with ssh to set password and start ganeti-noded
373 827f753e Guido Trotter
  # note that all the below variables are sanitized at this point,
374 827f753e Guido Trotter
  # either by being constants or by the checks above
375 827f753e Guido Trotter
  mycommand = ("umask 077 && "
376 827f753e Guido Trotter
               "cat > '%s' << '!EOF.' && \n"
377 2438c157 Michael Hanselmann
               "%s!EOF.\n"
378 2438c157 Michael Hanselmann
               "cat > '%s' << '!EOF.' && \n"
379 2438c157 Michael Hanselmann
               "%s!EOF.\n"
380 5b099da9 Michael Hanselmann
               "chmod 0400 %s %s && "
381 2438c157 Michael Hanselmann
               "%s restart" %
382 5557b04c Michael Hanselmann
               (constants.SSL_CERT_FILE, noded_cert,
383 2438c157 Michael Hanselmann
                constants.RAPI_CERT_FILE, rapi_cert,
384 5b099da9 Michael Hanselmann
                constants.SSL_CERT_FILE, constants.RAPI_CERT_FILE,
385 827f753e Guido Trotter
                constants.NODE_INITD_SCRIPT))
386 827f753e Guido Trotter
387 c4b6c29c Michael Hanselmann
  result = sshrunner.Run(node, 'root', mycommand, batch=False,
388 c4b6c29c Michael Hanselmann
                         ask_key=ssh_key_check,
389 c4b6c29c Michael Hanselmann
                         use_cluster_key=False,
390 c4b6c29c Michael Hanselmann
                         strict_host_check=ssh_key_check)
391 827f753e Guido Trotter
  if result.failed:
392 827f753e Guido Trotter
    raise errors.OpExecError("Remote command on node %s, error: %s,"
393 827f753e Guido Trotter
                             " output: %s" %
394 827f753e Guido Trotter
                             (node, result.fail_reason, result.output))
395 827f753e Guido Trotter
396 b1b6ea87 Iustin Pop
397 8e2524c3 Guido Trotter
def MasterFailover(no_voting=False):
398 b1b6ea87 Iustin Pop
  """Failover the master node.
399 b1b6ea87 Iustin Pop

400 b1b6ea87 Iustin Pop
  This checks that we are not already the master, and will cause the
401 b1b6ea87 Iustin Pop
  current master to cease being master, and the non-master to become
402 b1b6ea87 Iustin Pop
  new master.
403 b1b6ea87 Iustin Pop

404 8e2524c3 Guido Trotter
  @type no_voting: boolean
405 8e2524c3 Guido Trotter
  @param no_voting: force the operation without remote nodes agreement
406 8e2524c3 Guido Trotter
                      (dangerous)
407 8e2524c3 Guido Trotter

408 b1b6ea87 Iustin Pop
  """
409 8135a2db Iustin Pop
  sstore = ssconf.SimpleStore()
410 b1b6ea87 Iustin Pop
411 8135a2db Iustin Pop
  old_master, new_master = ssconf.GetMasterAndMyself(sstore)
412 8135a2db Iustin Pop
  node_list = sstore.GetNodeList()
413 8135a2db Iustin Pop
  mc_list = sstore.GetMasterCandidates()
414 b1b6ea87 Iustin Pop
415 b1b6ea87 Iustin Pop
  if old_master == new_master:
416 b1b6ea87 Iustin Pop
    raise errors.OpPrereqError("This commands must be run on the node"
417 b1b6ea87 Iustin Pop
                               " where you want the new master to be."
418 b1b6ea87 Iustin Pop
                               " %s is already the master" %
419 b1b6ea87 Iustin Pop
                               old_master)
420 d5927e48 Iustin Pop
421 8135a2db Iustin Pop
  if new_master not in mc_list:
422 8135a2db Iustin Pop
    mc_no_master = [name for name in mc_list if name != old_master]
423 8135a2db Iustin Pop
    raise errors.OpPrereqError("This node is not among the nodes marked"
424 8135a2db Iustin Pop
                               " as master candidates. Only these nodes"
425 8135a2db Iustin Pop
                               " can become masters. Current list of"
426 8135a2db Iustin Pop
                               " master candidates is:\n"
427 8135a2db Iustin Pop
                               "%s" % ('\n'.join(mc_no_master)))
428 8135a2db Iustin Pop
429 8e2524c3 Guido Trotter
  if not no_voting:
430 8e2524c3 Guido Trotter
    vote_list = GatherMasterVotes(node_list)
431 8e2524c3 Guido Trotter
432 8e2524c3 Guido Trotter
    if vote_list:
433 8e2524c3 Guido Trotter
      voted_master = vote_list[0][0]
434 8e2524c3 Guido Trotter
      if voted_master is None:
435 8e2524c3 Guido Trotter
        raise errors.OpPrereqError("Cluster is inconsistent, most nodes did"
436 8e2524c3 Guido Trotter
                                   " not respond.")
437 8e2524c3 Guido Trotter
      elif voted_master != old_master:
438 8e2524c3 Guido Trotter
        raise errors.OpPrereqError("I have a wrong configuration, I believe"
439 8e2524c3 Guido Trotter
                                   " the master is %s but the other nodes"
440 8e2524c3 Guido Trotter
                                   " voted %s. Please resync the configuration"
441 8e2524c3 Guido Trotter
                                   " of this node." %
442 8e2524c3 Guido Trotter
                                   (old_master, voted_master))
443 b1b6ea87 Iustin Pop
  # end checks
444 b1b6ea87 Iustin Pop
445 b1b6ea87 Iustin Pop
  rcode = 0
446 b1b6ea87 Iustin Pop
447 d5927e48 Iustin Pop
  logging.info("Setting master to %s, old master: %s", new_master, old_master)
448 b1b6ea87 Iustin Pop
449 781de953 Iustin Pop
  result = rpc.RpcRunner.call_node_stop_master(old_master, True)
450 6c00d19a Iustin Pop
  msg = result.RemoteFailMsg()
451 6c00d19a Iustin Pop
  if msg:
452 d5927e48 Iustin Pop
    logging.error("Could not disable the master role on the old master"
453 6c00d19a Iustin Pop
                 " %s, please disable manually: %s", old_master, msg)
454 b1b6ea87 Iustin Pop
455 d23ef431 Michael Hanselmann
  # Here we have a phase where no master should be running
456 b1b6ea87 Iustin Pop
457 bbe19c17 Iustin Pop
  # instantiate a real config writer, as we now know we have the
458 bbe19c17 Iustin Pop
  # configuration data
459 bbe19c17 Iustin Pop
  cfg = config.ConfigWriter()
460 b1b6ea87 Iustin Pop
461 bbe19c17 Iustin Pop
  cluster_info = cfg.GetClusterInfo()
462 bbe19c17 Iustin Pop
  cluster_info.master_node = new_master
463 bbe19c17 Iustin Pop
  # this will also regenerate the ssconf files, since we updated the
464 bbe19c17 Iustin Pop
  # cluster info
465 bbe19c17 Iustin Pop
  cfg.Update(cluster_info)
466 d5927e48 Iustin Pop
467 3583908a Guido Trotter
  result = rpc.RpcRunner.call_node_start_master(new_master, True, no_voting)
468 b726aff0 Iustin Pop
  msg = result.RemoteFailMsg()
469 b726aff0 Iustin Pop
  if msg:
470 d5927e48 Iustin Pop
    logging.error("Could not start the master role on the new master"
471 b726aff0 Iustin Pop
                  " %s, please check: %s", new_master, msg)
472 b1b6ea87 Iustin Pop
    rcode = 1
473 b1b6ea87 Iustin Pop
474 b1b6ea87 Iustin Pop
  return rcode
475 d7cdb55d Iustin Pop
476 d7cdb55d Iustin Pop
477 8eb148ae Iustin Pop
def GetMaster():
478 8eb148ae Iustin Pop
  """Returns the current master node.
479 8eb148ae Iustin Pop

480 8eb148ae Iustin Pop
  This is a separate function in bootstrap since it's needed by
481 8eb148ae Iustin Pop
  gnt-cluster, and instead of importing directly ssconf, it's better
482 8eb148ae Iustin Pop
  to abstract it in bootstrap, where we do use ssconf in other
483 8eb148ae Iustin Pop
  functions too.
484 8eb148ae Iustin Pop

485 8eb148ae Iustin Pop
  """
486 8eb148ae Iustin Pop
  sstore = ssconf.SimpleStore()
487 8eb148ae Iustin Pop
488 8eb148ae Iustin Pop
  old_master, _ = ssconf.GetMasterAndMyself(sstore)
489 8eb148ae Iustin Pop
490 8eb148ae Iustin Pop
  return old_master
491 8eb148ae Iustin Pop
492 8eb148ae Iustin Pop
493 d7cdb55d Iustin Pop
def GatherMasterVotes(node_list):
494 d7cdb55d Iustin Pop
  """Check the agreement on who is the master.
495 d7cdb55d Iustin Pop

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

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

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

507 d7cdb55d Iustin Pop
  @type node_list: list
508 d7cdb55d Iustin Pop
  @param node_list: the list of nodes to query for master info; the current
509 5bbd3f7f Michael Hanselmann
      node will be removed if it is in the list
510 d7cdb55d Iustin Pop
  @rtype: list
511 d7cdb55d Iustin Pop
  @return: list of (node, votes)
512 d7cdb55d Iustin Pop

513 d7cdb55d Iustin Pop
  """
514 d7cdb55d Iustin Pop
  myself = utils.HostInfo().name
515 d7cdb55d Iustin Pop
  try:
516 d7cdb55d Iustin Pop
    node_list.remove(myself)
517 d7cdb55d Iustin Pop
  except ValueError:
518 d7cdb55d Iustin Pop
    pass
519 d7cdb55d Iustin Pop
  if not node_list:
520 d7cdb55d Iustin Pop
    # no nodes left (eventually after removing myself)
521 d7cdb55d Iustin Pop
    return []
522 d7cdb55d Iustin Pop
  results = rpc.RpcRunner.call_master_info(node_list)
523 d7cdb55d Iustin Pop
  if not isinstance(results, dict):
524 d7cdb55d Iustin Pop
    # this should not happen (unless internal error in rpc)
525 d7cdb55d Iustin Pop
    logging.critical("Can't complete rpc call, aborting master startup")
526 d7cdb55d Iustin Pop
    return [(None, len(node_list))]
527 d7cdb55d Iustin Pop
  votes = {}
528 d7cdb55d Iustin Pop
  for node in results:
529 781de953 Iustin Pop
    nres = results[node]
530 2a52a064 Iustin Pop
    data = nres.payload
531 2a52a064 Iustin Pop
    msg = nres.RemoteFailMsg()
532 2a52a064 Iustin Pop
    fail = False
533 2a52a064 Iustin Pop
    if msg:
534 2a52a064 Iustin Pop
      logging.warning("Error contacting node %s: %s", node, msg)
535 2a52a064 Iustin Pop
      fail = True
536 2a52a064 Iustin Pop
    elif not isinstance(data, (tuple, list)) or len(data) < 3:
537 2a52a064 Iustin Pop
      logging.warning("Invalid data received from node %s: %s", node, data)
538 2a52a064 Iustin Pop
      fail = True
539 2a52a064 Iustin Pop
    if fail:
540 d7cdb55d Iustin Pop
      if None not in votes:
541 d7cdb55d Iustin Pop
        votes[None] = 0
542 d7cdb55d Iustin Pop
      votes[None] += 1
543 d7cdb55d Iustin Pop
      continue
544 781de953 Iustin Pop
    master_node = data[2]
545 d7cdb55d Iustin Pop
    if master_node not in votes:
546 d7cdb55d Iustin Pop
      votes[master_node] = 0
547 d7cdb55d Iustin Pop
    votes[master_node] += 1
548 d7cdb55d Iustin Pop
549 d7cdb55d Iustin Pop
  vote_list = [v for v in votes.items()]
550 d7cdb55d Iustin Pop
  # sort first on number of votes then on name, since we want None
551 d7cdb55d Iustin Pop
  # sorted later if we have the half of the nodes not responding, and
552 d7cdb55d Iustin Pop
  # half voting all for the same master
553 d7cdb55d Iustin Pop
  vote_list.sort(key=lambda x: (x[1], x[0]), reverse=True)
554 d7cdb55d Iustin Pop
555 d7cdb55d Iustin Pop
  return vote_list