Statistics
| Branch: | Tag: | Revision:

root / lib / bootstrap.py @ bec0522b

History | View | Annotate | Download (18.6 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 a5728081 Guido Trotter
from ganeti import hypervisor
41 a0c9f010 Michael Hanselmann
42 e38220e4 Michael Hanselmann
43 531baf8e Iustin Pop
def _InitSSHSetup():
44 a0c9f010 Michael Hanselmann
  """Setup the SSH configuration for the cluster.
45 a0c9f010 Michael Hanselmann

46 a0c9f010 Michael Hanselmann
  This generates a dsa keypair for root, adds the pub key to the
47 a0c9f010 Michael Hanselmann
  permitted hosts and adds the hostkey to its own known hosts.
48 a0c9f010 Michael Hanselmann

49 a0c9f010 Michael Hanselmann
  """
50 a0c9f010 Michael Hanselmann
  priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
51 a0c9f010 Michael Hanselmann
52 a0c9f010 Michael Hanselmann
  for name in priv_key, pub_key:
53 a0c9f010 Michael Hanselmann
    if os.path.exists(name):
54 a0c9f010 Michael Hanselmann
      utils.CreateBackup(name)
55 a0c9f010 Michael Hanselmann
    utils.RemoveFile(name)
56 a0c9f010 Michael Hanselmann
57 a0c9f010 Michael Hanselmann
  result = utils.RunCmd(["ssh-keygen", "-t", "dsa",
58 a0c9f010 Michael Hanselmann
                         "-f", priv_key,
59 a0c9f010 Michael Hanselmann
                         "-q", "-N", ""])
60 a0c9f010 Michael Hanselmann
  if result.failed:
61 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("Could not generate ssh keypair, error %s" %
62 a0c9f010 Michael Hanselmann
                             result.output)
63 a0c9f010 Michael Hanselmann
64 a0c9f010 Michael Hanselmann
  f = open(pub_key, 'r')
65 a0c9f010 Michael Hanselmann
  try:
66 a0c9f010 Michael Hanselmann
    utils.AddAuthorizedKey(auth_keys, f.read(8192))
67 a0c9f010 Michael Hanselmann
  finally:
68 a0c9f010 Michael Hanselmann
    f.close()
69 a0c9f010 Michael Hanselmann
70 a0c9f010 Michael Hanselmann
71 40a97d80 Michael Hanselmann
def _GenerateSelfSignedSslCert(file_name, validity=(365 * 5)):
72 40a97d80 Michael Hanselmann
  """Generates a self-signed SSL certificate.
73 a0c9f010 Michael Hanselmann

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

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

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

111 40a97d80 Michael Hanselmann
  """
112 40a97d80 Michael Hanselmann
  _GenerateSelfSignedSslCert(constants.SSL_CERT_FILE)
113 a0c9f010 Michael Hanselmann
114 61a08fa3 Michael Hanselmann
  # Don't overwrite existing file
115 61a08fa3 Michael Hanselmann
  if not os.path.exists(constants.RAPI_CERT_FILE):
116 61a08fa3 Michael Hanselmann
    _GenerateSelfSignedSslCert(constants.RAPI_CERT_FILE)
117 61a08fa3 Michael Hanselmann
118 a0c9f010 Michael Hanselmann
  result = utils.RunCmd([constants.NODE_INITD_SCRIPT, "restart"])
119 a0c9f010 Michael Hanselmann
120 a0c9f010 Michael Hanselmann
  if result.failed:
121 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("Could not start the node daemon, command %s"
122 a0c9f010 Michael Hanselmann
                             " had exitcode %s and error %s" %
123 a0c9f010 Michael Hanselmann
                             (result.cmd, result.exit_code, result.output))
124 a0c9f010 Michael Hanselmann
125 a0c9f010 Michael Hanselmann
126 4342e89b Alexander Schreiber
def InitCluster(cluster_name, mac_prefix, def_bridge,
127 ce735215 Guido Trotter
                master_netdev, file_storage_dir, candidate_pool_size,
128 ce735215 Guido Trotter
                secondary_ip=None, vg_name=None, beparams=None, hvparams=None,
129 aafb303d Guido Trotter
                enabled_hypervisors=None, default_hypervisor=None,
130 aafb303d Guido Trotter
                modify_etc_hosts=True):
131 a0c9f010 Michael Hanselmann
  """Initialise the cluster.
132 a0c9f010 Michael Hanselmann

133 ce735215 Guido Trotter
  @type candidate_pool_size: int
134 ce735215 Guido Trotter
  @param candidate_pool_size: master candidate pool size
135 ce735215 Guido Trotter

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

304 7b3a8fb5 Iustin Pop
  """
305 7b3a8fb5 Iustin Pop
  nodes = {
306 7b3a8fb5 Iustin Pop
    master_node_config.name: master_node_config,
307 7b3a8fb5 Iustin Pop
    }
308 7b3a8fb5 Iustin Pop
309 7b3a8fb5 Iustin Pop
  config_data = objects.ConfigData(version=version,
310 7b3a8fb5 Iustin Pop
                                   cluster=cluster_config,
311 7b3a8fb5 Iustin Pop
                                   nodes=nodes,
312 7b3a8fb5 Iustin Pop
                                   instances={},
313 7b3a8fb5 Iustin Pop
                                   serial_no=1)
314 7b3a8fb5 Iustin Pop
  cfg = ssconf.SimpleConfigWriter.FromDict(config_data.ToDict(), cfg_file)
315 7b3a8fb5 Iustin Pop
  cfg.Save()
316 7b3a8fb5 Iustin Pop
317 7b3a8fb5 Iustin Pop
  return cfg
318 02f99608 Oleksiy Mishchenko
319 02f99608 Oleksiy Mishchenko
320 140aa4a8 Iustin Pop
def FinalizeClusterDestroy(master):
321 140aa4a8 Iustin Pop
  """Execute the last steps of cluster destroy
322 140aa4a8 Iustin Pop

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

326 140aa4a8 Iustin Pop
  """
327 781de953 Iustin Pop
  result = rpc.RpcRunner.call_node_stop_master(master, True)
328 781de953 Iustin Pop
  if result.failed or not result.data:
329 140aa4a8 Iustin Pop
    logging.warning("Could not disable the master role")
330 781de953 Iustin Pop
  result = rpc.RpcRunner.call_node_leave_cluster(master)
331 781de953 Iustin Pop
  if result.failed or not result.data:
332 140aa4a8 Iustin Pop
    logging.warning("Could not shutdown the node daemon and cleanup the node")
333 140aa4a8 Iustin Pop
334 140aa4a8 Iustin Pop
335 87622829 Iustin Pop
def SetupNodeDaemon(cluster_name, node, ssh_key_check):
336 827f753e Guido Trotter
  """Add a node to the cluster.
337 827f753e Guido Trotter

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

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

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

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

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

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

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

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

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

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

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

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

505 d7cdb55d Iustin Pop
  """
506 d7cdb55d Iustin Pop
  myself = utils.HostInfo().name
507 d7cdb55d Iustin Pop
  try:
508 d7cdb55d Iustin Pop
    node_list.remove(myself)
509 d7cdb55d Iustin Pop
  except ValueError:
510 d7cdb55d Iustin Pop
    pass
511 d7cdb55d Iustin Pop
  if not node_list:
512 d7cdb55d Iustin Pop
    # no nodes left (eventually after removing myself)
513 d7cdb55d Iustin Pop
    return []
514 d7cdb55d Iustin Pop
  results = rpc.RpcRunner.call_master_info(node_list)
515 d7cdb55d Iustin Pop
  if not isinstance(results, dict):
516 d7cdb55d Iustin Pop
    # this should not happen (unless internal error in rpc)
517 d7cdb55d Iustin Pop
    logging.critical("Can't complete rpc call, aborting master startup")
518 d7cdb55d Iustin Pop
    return [(None, len(node_list))]
519 d7cdb55d Iustin Pop
  votes = {}
520 d7cdb55d Iustin Pop
  for node in results:
521 781de953 Iustin Pop
    nres = results[node]
522 781de953 Iustin Pop
    data = nres.data
523 781de953 Iustin Pop
    if nres.failed or not isinstance(data, (tuple, list)) or len(data) < 3:
524 d7cdb55d Iustin Pop
      # here the rpc layer should have already logged errors
525 d7cdb55d Iustin Pop
      if None not in votes:
526 d7cdb55d Iustin Pop
        votes[None] = 0
527 d7cdb55d Iustin Pop
      votes[None] += 1
528 d7cdb55d Iustin Pop
      continue
529 781de953 Iustin Pop
    master_node = data[2]
530 d7cdb55d Iustin Pop
    if master_node not in votes:
531 d7cdb55d Iustin Pop
      votes[master_node] = 0
532 d7cdb55d Iustin Pop
    votes[master_node] += 1
533 d7cdb55d Iustin Pop
534 d7cdb55d Iustin Pop
  vote_list = [v for v in votes.items()]
535 d7cdb55d Iustin Pop
  # sort first on number of votes then on name, since we want None
536 d7cdb55d Iustin Pop
  # sorted later if we have the half of the nodes not responding, and
537 d7cdb55d Iustin Pop
  # half voting all for the same master
538 d7cdb55d Iustin Pop
  vote_list.sort(key=lambda x: (x[1], x[0]), reverse=True)
539 d7cdb55d Iustin Pop
540 d7cdb55d Iustin Pop
  return vote_list