Statistics
| Branch: | Tag: | Revision:

root / lib / bootstrap.py @ 4a25828c

History | View | Annotate | Download (19 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 40a97d80 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 40a97d80 Michael Hanselmann
def _InitGanetiServerSetup():
104 40a97d80 Michael Hanselmann
  """Setup the necessary configuration for the initial node daemon.
105 40a97d80 Michael Hanselmann

106 40a97d80 Michael Hanselmann
  This creates the nodepass file containing the shared password for
107 40a97d80 Michael Hanselmann
  the cluster and also generates the SSL certificate.
108 40a97d80 Michael Hanselmann

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

136 ce735215 Guido Trotter
  @type candidate_pool_size: int
137 ce735215 Guido Trotter
  @param candidate_pool_size: master candidate pool size
138 ce735215 Guido Trotter

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

289 7b3a8fb5 Iustin Pop
  It will contain the current node, which will also be the master
290 7b3a8fb5 Iustin Pop
  node, and no instances.
291 7b3a8fb5 Iustin Pop

292 7b3a8fb5 Iustin Pop
  @type version: int
293 c41eea6e Iustin Pop
  @param version: configuration version
294 c41eea6e Iustin Pop
  @type cluster_config: L{objects.Cluster}
295 c41eea6e Iustin Pop
  @param cluster_config: cluster configuration
296 c41eea6e Iustin Pop
  @type master_node_config: L{objects.Node}
297 c41eea6e Iustin Pop
  @param master_node_config: master node configuration
298 c41eea6e Iustin Pop
  @type cfg_file: string
299 c41eea6e Iustin Pop
  @param cfg_file: configuration file path
300 c41eea6e Iustin Pop

301 7b3a8fb5 Iustin Pop
  """
302 7b3a8fb5 Iustin Pop
  nodes = {
303 7b3a8fb5 Iustin Pop
    master_node_config.name: master_node_config,
304 7b3a8fb5 Iustin Pop
    }
305 7b3a8fb5 Iustin Pop
306 d693c864 Iustin Pop
  now = time.time()
307 7b3a8fb5 Iustin Pop
  config_data = objects.ConfigData(version=version,
308 7b3a8fb5 Iustin Pop
                                   cluster=cluster_config,
309 7b3a8fb5 Iustin Pop
                                   nodes=nodes,
310 7b3a8fb5 Iustin Pop
                                   instances={},
311 d693c864 Iustin Pop
                                   serial_no=1,
312 d693c864 Iustin Pop
                                   ctime=now, mtime=now)
313 a33848a5 Guido Trotter
  utils.WriteFile(cfg_file,
314 a33848a5 Guido Trotter
                  data=serializer.Dump(config_data.ToDict()),
315 a33848a5 Guido Trotter
                  mode=0600)
316 02f99608 Oleksiy Mishchenko
317 02f99608 Oleksiy Mishchenko
318 140aa4a8 Iustin Pop
def FinalizeClusterDestroy(master):
319 140aa4a8 Iustin Pop
  """Execute the last steps of cluster destroy
320 140aa4a8 Iustin Pop

321 140aa4a8 Iustin Pop
  This function shuts down all the daemons, completing the destroy
322 140aa4a8 Iustin Pop
  begun in cmdlib.LUDestroyOpcode.
323 140aa4a8 Iustin Pop

324 140aa4a8 Iustin Pop
  """
325 781de953 Iustin Pop
  result = rpc.RpcRunner.call_node_stop_master(master, True)
326 3cebe102 Michael Hanselmann
  msg = result.fail_msg
327 6c00d19a Iustin Pop
  if msg:
328 6c00d19a Iustin Pop
    logging.warning("Could not disable the master role: %s" % msg)
329 781de953 Iustin Pop
  result = rpc.RpcRunner.call_node_leave_cluster(master)
330 3cebe102 Michael Hanselmann
  msg = result.fail_msg
331 0623d351 Iustin Pop
  if msg:
332 0623d351 Iustin Pop
    logging.warning("Could not shutdown the node daemon and cleanup"
333 0623d351 Iustin Pop
                    " the node: %s", msg)
334 140aa4a8 Iustin Pop
335 140aa4a8 Iustin Pop
336 87622829 Iustin Pop
def SetupNodeDaemon(cluster_name, node, ssh_key_check):
337 827f753e Guido Trotter
  """Add a node to the cluster.
338 827f753e Guido Trotter

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

343 87622829 Iustin Pop
  @param cluster_name: the cluster name
344 87622829 Iustin Pop
  @param node: the name of the new node
345 87622829 Iustin Pop
  @param ssh_key_check: whether to do a strict key check
346 827f753e Guido Trotter

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

395 b1b6ea87 Iustin Pop
  This checks that we are not already the master, and will cause the
396 b1b6ea87 Iustin Pop
  current master to cease being master, and the non-master to become
397 b1b6ea87 Iustin Pop
  new master.
398 b1b6ea87 Iustin Pop

399 8e2524c3 Guido Trotter
  @type no_voting: boolean
400 8e2524c3 Guido Trotter
  @param no_voting: force the operation without remote nodes agreement
401 8e2524c3 Guido Trotter
                      (dangerous)
402 8e2524c3 Guido Trotter

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

475 8eb148ae Iustin Pop
  This is a separate function in bootstrap since it's needed by
476 8eb148ae Iustin Pop
  gnt-cluster, and instead of importing directly ssconf, it's better
477 8eb148ae Iustin Pop
  to abstract it in bootstrap, where we do use ssconf in other
478 8eb148ae Iustin Pop
  functions too.
479 8eb148ae Iustin Pop

480 8eb148ae Iustin Pop
  """
481 8eb148ae Iustin Pop
  sstore = ssconf.SimpleStore()
482 8eb148ae Iustin Pop
483 8eb148ae Iustin Pop
  old_master, _ = ssconf.GetMasterAndMyself(sstore)
484 8eb148ae Iustin Pop
485 8eb148ae Iustin Pop
  return old_master
486 8eb148ae Iustin Pop
487 8eb148ae Iustin Pop
488 d7cdb55d Iustin Pop
def GatherMasterVotes(node_list):
489 d7cdb55d Iustin Pop
  """Check the agreement on who is the master.
490 d7cdb55d Iustin Pop

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

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

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

502 d7cdb55d Iustin Pop
  @type node_list: list
503 d7cdb55d Iustin Pop
  @param node_list: the list of nodes to query for master info; the current
504 5bbd3f7f Michael Hanselmann
      node will be removed if it is in the list
505 d7cdb55d Iustin Pop
  @rtype: list
506 d7cdb55d Iustin Pop
  @return: list of (node, votes)
507 d7cdb55d Iustin Pop

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