Statistics
| Branch: | Tag: | Revision:

root / lib / bootstrap.py @ cd195419

History | View | Annotate | Download (19.8 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 c008906b Michael Hanselmann
  utils.WriteFile(file_name, data=utils.GenerateSecret(), mode=0400)
111 c008906b Michael Hanselmann
112 c008906b Michael Hanselmann
113 40a97d80 Michael Hanselmann
def _InitGanetiServerSetup():
114 40a97d80 Michael Hanselmann
  """Setup the necessary configuration for the initial node daemon.
115 40a97d80 Michael Hanselmann

116 40a97d80 Michael Hanselmann
  This creates the nodepass file containing the shared password for
117 40a97d80 Michael Hanselmann
  the cluster and also generates the SSL certificate.
118 40a97d80 Michael Hanselmann

119 40a97d80 Michael Hanselmann
  """
120 cd34faf2 Michael Hanselmann
  GenerateSelfSignedSslCert(constants.SSL_CERT_FILE)
121 a0c9f010 Michael Hanselmann
122 61a08fa3 Michael Hanselmann
  # Don't overwrite existing file
123 61a08fa3 Michael Hanselmann
  if not os.path.exists(constants.RAPI_CERT_FILE):
124 cd34faf2 Michael Hanselmann
    GenerateSelfSignedSslCert(constants.RAPI_CERT_FILE)
125 61a08fa3 Michael Hanselmann
126 4a34c5cf Guido Trotter
  if not os.path.exists(constants.HMAC_CLUSTER_KEY):
127 c008906b Michael Hanselmann
    GenerateHmacKey(constants.HMAC_CLUSTER_KEY)
128 4a34c5cf Guido Trotter
129 a0c9f010 Michael Hanselmann
  result = utils.RunCmd([constants.NODE_INITD_SCRIPT, "restart"])
130 a0c9f010 Michael Hanselmann
131 a0c9f010 Michael Hanselmann
  if result.failed:
132 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("Could not start the node daemon, command %s"
133 a0c9f010 Michael Hanselmann
                             " had exitcode %s and error %s" %
134 a0c9f010 Michael Hanselmann
                             (result.cmd, result.exit_code, result.output))
135 a0c9f010 Michael Hanselmann
136 a0c9f010 Michael Hanselmann
137 ec0652ad Guido Trotter
def InitCluster(cluster_name, mac_prefix,
138 ce735215 Guido Trotter
                master_netdev, file_storage_dir, candidate_pool_size,
139 b6a30b0d Guido Trotter
                secondary_ip=None, vg_name=None, beparams=None,
140 b6a30b0d Guido Trotter
                nicparams=None, hvparams=None, enabled_hypervisors=None,
141 b989b9d9 Ken Wehr
                modify_etc_hosts=True, modify_ssh_setup=True):
142 a0c9f010 Michael Hanselmann
  """Initialise the cluster.
143 a0c9f010 Michael Hanselmann

144 ce735215 Guido Trotter
  @type candidate_pool_size: int
145 ce735215 Guido Trotter
  @param candidate_pool_size: master candidate pool size
146 ce735215 Guido Trotter

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

304 7b3a8fb5 Iustin Pop
  It will contain the current node, which will also be the master
305 7b3a8fb5 Iustin Pop
  node, and no instances.
306 7b3a8fb5 Iustin Pop

307 7b3a8fb5 Iustin Pop
  @type version: int
308 c41eea6e Iustin Pop
  @param version: configuration version
309 c41eea6e Iustin Pop
  @type cluster_config: L{objects.Cluster}
310 c41eea6e Iustin Pop
  @param cluster_config: cluster configuration
311 c41eea6e Iustin Pop
  @type master_node_config: L{objects.Node}
312 c41eea6e Iustin Pop
  @param master_node_config: master node configuration
313 c41eea6e Iustin Pop
  @type cfg_file: string
314 c41eea6e Iustin Pop
  @param cfg_file: configuration file path
315 c41eea6e Iustin Pop

316 7b3a8fb5 Iustin Pop
  """
317 7b3a8fb5 Iustin Pop
  nodes = {
318 7b3a8fb5 Iustin Pop
    master_node_config.name: master_node_config,
319 7b3a8fb5 Iustin Pop
    }
320 7b3a8fb5 Iustin Pop
321 d693c864 Iustin Pop
  now = time.time()
322 7b3a8fb5 Iustin Pop
  config_data = objects.ConfigData(version=version,
323 7b3a8fb5 Iustin Pop
                                   cluster=cluster_config,
324 7b3a8fb5 Iustin Pop
                                   nodes=nodes,
325 7b3a8fb5 Iustin Pop
                                   instances={},
326 d693c864 Iustin Pop
                                   serial_no=1,
327 d693c864 Iustin Pop
                                   ctime=now, mtime=now)
328 a33848a5 Guido Trotter
  utils.WriteFile(cfg_file,
329 a33848a5 Guido Trotter
                  data=serializer.Dump(config_data.ToDict()),
330 a33848a5 Guido Trotter
                  mode=0600)
331 02f99608 Oleksiy Mishchenko
332 02f99608 Oleksiy Mishchenko
333 140aa4a8 Iustin Pop
def FinalizeClusterDestroy(master):
334 140aa4a8 Iustin Pop
  """Execute the last steps of cluster destroy
335 140aa4a8 Iustin Pop

336 140aa4a8 Iustin Pop
  This function shuts down all the daemons, completing the destroy
337 140aa4a8 Iustin Pop
  begun in cmdlib.LUDestroyOpcode.
338 140aa4a8 Iustin Pop

339 140aa4a8 Iustin Pop
  """
340 b989b9d9 Ken Wehr
  cfg = config.ConfigWriter()
341 b989b9d9 Ken Wehr
  modify_ssh_setup = cfg.GetClusterInfo().modify_ssh_setup
342 781de953 Iustin Pop
  result = rpc.RpcRunner.call_node_stop_master(master, True)
343 3cebe102 Michael Hanselmann
  msg = result.fail_msg
344 6c00d19a Iustin Pop
  if msg:
345 6c00d19a Iustin Pop
    logging.warning("Could not disable the master role: %s" % msg)
346 b989b9d9 Ken Wehr
  result = rpc.RpcRunner.call_node_leave_cluster(master, modify_ssh_setup)
347 3cebe102 Michael Hanselmann
  msg = result.fail_msg
348 0623d351 Iustin Pop
  if msg:
349 0623d351 Iustin Pop
    logging.warning("Could not shutdown the node daemon and cleanup"
350 0623d351 Iustin Pop
                    " the node: %s", msg)
351 140aa4a8 Iustin Pop
352 140aa4a8 Iustin Pop
353 87622829 Iustin Pop
def SetupNodeDaemon(cluster_name, node, ssh_key_check):
354 827f753e Guido Trotter
  """Add a node to the cluster.
355 827f753e Guido Trotter

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

360 87622829 Iustin Pop
  @param cluster_name: the cluster name
361 87622829 Iustin Pop
  @param node: the name of the new node
362 87622829 Iustin Pop
  @param ssh_key_check: whether to do a strict key check
363 827f753e Guido Trotter

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

420 b1b6ea87 Iustin Pop
  This checks that we are not already the master, and will cause the
421 b1b6ea87 Iustin Pop
  current master to cease being master, and the non-master to become
422 b1b6ea87 Iustin Pop
  new master.
423 b1b6ea87 Iustin Pop

424 8e2524c3 Guido Trotter
  @type no_voting: boolean
425 8e2524c3 Guido Trotter
  @param no_voting: force the operation without remote nodes agreement
426 8e2524c3 Guido Trotter
                      (dangerous)
427 8e2524c3 Guido Trotter

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

500 8eb148ae Iustin Pop
  This is a separate function in bootstrap since it's needed by
501 8eb148ae Iustin Pop
  gnt-cluster, and instead of importing directly ssconf, it's better
502 8eb148ae Iustin Pop
  to abstract it in bootstrap, where we do use ssconf in other
503 8eb148ae Iustin Pop
  functions too.
504 8eb148ae Iustin Pop

505 8eb148ae Iustin Pop
  """
506 8eb148ae Iustin Pop
  sstore = ssconf.SimpleStore()
507 8eb148ae Iustin Pop
508 8eb148ae Iustin Pop
  old_master, _ = ssconf.GetMasterAndMyself(sstore)
509 8eb148ae Iustin Pop
510 8eb148ae Iustin Pop
  return old_master
511 8eb148ae Iustin Pop
512 8eb148ae Iustin Pop
513 d7cdb55d Iustin Pop
def GatherMasterVotes(node_list):
514 d7cdb55d Iustin Pop
  """Check the agreement on who is the master.
515 d7cdb55d Iustin Pop

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

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

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

527 d7cdb55d Iustin Pop
  @type node_list: list
528 d7cdb55d Iustin Pop
  @param node_list: the list of nodes to query for master info; the current
529 5bbd3f7f Michael Hanselmann
      node will be removed if it is in the list
530 d7cdb55d Iustin Pop
  @rtype: list
531 d7cdb55d Iustin Pop
  @return: list of (node, votes)
532 d7cdb55d Iustin Pop

533 d7cdb55d Iustin Pop
  """
534 d7cdb55d Iustin Pop
  myself = utils.HostInfo().name
535 d7cdb55d Iustin Pop
  try:
536 d7cdb55d Iustin Pop
    node_list.remove(myself)
537 d7cdb55d Iustin Pop
  except ValueError:
538 d7cdb55d Iustin Pop
    pass
539 d7cdb55d Iustin Pop
  if not node_list:
540 d7cdb55d Iustin Pop
    # no nodes left (eventually after removing myself)
541 d7cdb55d Iustin Pop
    return []
542 d7cdb55d Iustin Pop
  results = rpc.RpcRunner.call_master_info(node_list)
543 d7cdb55d Iustin Pop
  if not isinstance(results, dict):
544 d7cdb55d Iustin Pop
    # this should not happen (unless internal error in rpc)
545 d7cdb55d Iustin Pop
    logging.critical("Can't complete rpc call, aborting master startup")
546 d7cdb55d Iustin Pop
    return [(None, len(node_list))]
547 d7cdb55d Iustin Pop
  votes = {}
548 d7cdb55d Iustin Pop
  for node in results:
549 781de953 Iustin Pop
    nres = results[node]
550 2a52a064 Iustin Pop
    data = nres.payload
551 3cebe102 Michael Hanselmann
    msg = nres.fail_msg
552 2a52a064 Iustin Pop
    fail = False
553 2a52a064 Iustin Pop
    if msg:
554 2a52a064 Iustin Pop
      logging.warning("Error contacting node %s: %s", node, msg)
555 2a52a064 Iustin Pop
      fail = True
556 2a52a064 Iustin Pop
    elif not isinstance(data, (tuple, list)) or len(data) < 3:
557 2a52a064 Iustin Pop
      logging.warning("Invalid data received from node %s: %s", node, data)
558 2a52a064 Iustin Pop
      fail = True
559 2a52a064 Iustin Pop
    if fail:
560 d7cdb55d Iustin Pop
      if None not in votes:
561 d7cdb55d Iustin Pop
        votes[None] = 0
562 d7cdb55d Iustin Pop
      votes[None] += 1
563 d7cdb55d Iustin Pop
      continue
564 781de953 Iustin Pop
    master_node = data[2]
565 d7cdb55d Iustin Pop
    if master_node not in votes:
566 d7cdb55d Iustin Pop
      votes[master_node] = 0
567 d7cdb55d Iustin Pop
    votes[master_node] += 1
568 d7cdb55d Iustin Pop
569 d7cdb55d Iustin Pop
  vote_list = [v for v in votes.items()]
570 d7cdb55d Iustin Pop
  # sort first on number of votes then on name, since we want None
571 d7cdb55d Iustin Pop
  # sorted later if we have the half of the nodes not responding, and
572 d7cdb55d Iustin Pop
  # half voting all for the same master
573 d7cdb55d Iustin Pop
  vote_list.sort(key=lambda x: (x[1], x[0]), reverse=True)
574 d7cdb55d Iustin Pop
575 d7cdb55d Iustin Pop
  return vote_list