Statistics
| Branch: | Tag: | Revision:

root / lib / bootstrap.py @ 1094acda

History | View | Annotate | Download (18.1 kB)

1 a0c9f010 Michael Hanselmann
#
2 a0c9f010 Michael Hanselmann
#
3 a0c9f010 Michael Hanselmann
4 a0c9f010 Michael Hanselmann
# Copyright (C) 2006, 2007, 2008 Google Inc.
5 a0c9f010 Michael Hanselmann
#
6 a0c9f010 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 a0c9f010 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 a0c9f010 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 a0c9f010 Michael Hanselmann
# (at your option) any later version.
10 a0c9f010 Michael Hanselmann
#
11 a0c9f010 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 a0c9f010 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 a0c9f010 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 a0c9f010 Michael Hanselmann
# General Public License for more details.
15 a0c9f010 Michael Hanselmann
#
16 a0c9f010 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 a0c9f010 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 a0c9f010 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 a0c9f010 Michael Hanselmann
# 02110-1301, USA.
20 a0c9f010 Michael Hanselmann
21 a0c9f010 Michael Hanselmann
22 a0c9f010 Michael Hanselmann
"""Functions to bootstrap a new cluster.
23 a0c9f010 Michael Hanselmann

24 a0c9f010 Michael Hanselmann
"""
25 a0c9f010 Michael Hanselmann
26 a0c9f010 Michael Hanselmann
import os
27 a0c9f010 Michael Hanselmann
import os.path
28 a0c9f010 Michael Hanselmann
import re
29 b1b6ea87 Iustin Pop
import logging
30 c4415fd5 Michael Hanselmann
import tempfile
31 a0c9f010 Michael Hanselmann
32 a0c9f010 Michael Hanselmann
from ganeti import rpc
33 a0c9f010 Michael Hanselmann
from ganeti import ssh
34 a0c9f010 Michael Hanselmann
from ganeti import utils
35 a0c9f010 Michael Hanselmann
from ganeti import errors
36 a0c9f010 Michael Hanselmann
from ganeti import config
37 a0c9f010 Michael Hanselmann
from ganeti import constants
38 b9eeeb02 Michael Hanselmann
from ganeti import objects
39 a0c9f010 Michael Hanselmann
from ganeti import ssconf
40 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 c4415fd5 Michael Hanselmann
    # Set permissions before writing key
83 c4415fd5 Michael Hanselmann
    os.chmod(tmp_file_name, 0600)
84 c4415fd5 Michael Hanselmann
85 c4415fd5 Michael Hanselmann
    result = utils.RunCmd(["openssl", "req", "-new", "-newkey", "rsa:1024",
86 c4415fd5 Michael Hanselmann
                           "-days", str(validity), "-nodes", "-x509",
87 c4415fd5 Michael Hanselmann
                           "-keyout", tmp_file_name, "-out", tmp_file_name,
88 c4415fd5 Michael Hanselmann
                           "-batch"])
89 c4415fd5 Michael Hanselmann
    if result.failed:
90 c4415fd5 Michael Hanselmann
      raise errors.OpExecError("Could not generate SSL certificate, command"
91 c4415fd5 Michael Hanselmann
                               " %s had exitcode %s and error message %s" %
92 c4415fd5 Michael Hanselmann
                               (result.cmd, result.exit_code, result.output))
93 c4415fd5 Michael Hanselmann
94 c4415fd5 Michael Hanselmann
    # Make read-only
95 c4415fd5 Michael Hanselmann
    os.chmod(tmp_file_name, 0400)
96 c4415fd5 Michael Hanselmann
97 c4415fd5 Michael Hanselmann
    os.rename(tmp_file_name, file_name)
98 c4415fd5 Michael Hanselmann
  finally:
99 c4415fd5 Michael Hanselmann
    utils.RemoveFile(tmp_file_name)
100 40a97d80 Michael Hanselmann
101 40a97d80 Michael Hanselmann
102 40a97d80 Michael Hanselmann
def _InitGanetiServerSetup():
103 40a97d80 Michael Hanselmann
  """Setup the necessary configuration for the initial node daemon.
104 40a97d80 Michael Hanselmann

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

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

130 ce735215 Guido Trotter
  @type candidate_pool_size: int
131 ce735215 Guido Trotter
  @param candidate_pool_size: master candidate pool size
132 ce735215 Guido Trotter

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

282 7b3a8fb5 Iustin Pop
  It will contain the current node, which will also be the master
283 7b3a8fb5 Iustin Pop
  node, and no instances.
284 7b3a8fb5 Iustin Pop

285 7b3a8fb5 Iustin Pop
  @type version: int
286 c41eea6e Iustin Pop
  @param version: configuration version
287 c41eea6e Iustin Pop
  @type cluster_config: L{objects.Cluster}
288 c41eea6e Iustin Pop
  @param cluster_config: cluster configuration
289 c41eea6e Iustin Pop
  @type master_node_config: L{objects.Node}
290 c41eea6e Iustin Pop
  @param master_node_config: master node configuration
291 c41eea6e Iustin Pop
  @type cfg_file: string
292 c41eea6e Iustin Pop
  @param cfg_file: configuration file path
293 c41eea6e Iustin Pop

294 c41eea6e Iustin Pop
  @rtype: L{ssconf.SimpleConfigWriter}
295 5fcc718f Iustin Pop
  @return: initialized config instance
296 7b3a8fb5 Iustin Pop

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

316 140aa4a8 Iustin Pop
  This function shuts down all the daemons, completing the destroy
317 140aa4a8 Iustin Pop
  begun in cmdlib.LUDestroyOpcode.
318 140aa4a8 Iustin Pop

319 140aa4a8 Iustin Pop
  """
320 781de953 Iustin Pop
  result = rpc.RpcRunner.call_node_stop_master(master, True)
321 781de953 Iustin Pop
  if result.failed or not result.data:
322 140aa4a8 Iustin Pop
    logging.warning("Could not disable the master role")
323 781de953 Iustin Pop
  result = rpc.RpcRunner.call_node_leave_cluster(master)
324 781de953 Iustin Pop
  if result.failed or not result.data:
325 140aa4a8 Iustin Pop
    logging.warning("Could not shutdown the node daemon and cleanup the node")
326 140aa4a8 Iustin Pop
327 140aa4a8 Iustin Pop
328 87622829 Iustin Pop
def SetupNodeDaemon(cluster_name, node, ssh_key_check):
329 827f753e Guido Trotter
  """Add a node to the cluster.
330 827f753e Guido Trotter

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

335 87622829 Iustin Pop
  @param cluster_name: the cluster name
336 87622829 Iustin Pop
  @param node: the name of the new node
337 87622829 Iustin Pop
  @param ssh_key_check: whether to do a strict key check
338 827f753e Guido Trotter

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

387 b1b6ea87 Iustin Pop
  This checks that we are not already the master, and will cause the
388 b1b6ea87 Iustin Pop
  current master to cease being master, and the non-master to become
389 b1b6ea87 Iustin Pop
  new master.
390 b1b6ea87 Iustin Pop

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

459 8eb148ae Iustin Pop
  This is a separate function in bootstrap since it's needed by
460 8eb148ae Iustin Pop
  gnt-cluster, and instead of importing directly ssconf, it's better
461 8eb148ae Iustin Pop
  to abstract it in bootstrap, where we do use ssconf in other
462 8eb148ae Iustin Pop
  functions too.
463 8eb148ae Iustin Pop

464 8eb148ae Iustin Pop
  """
465 8eb148ae Iustin Pop
  sstore = ssconf.SimpleStore()
466 8eb148ae Iustin Pop
467 8eb148ae Iustin Pop
  old_master, _ = ssconf.GetMasterAndMyself(sstore)
468 8eb148ae Iustin Pop
469 8eb148ae Iustin Pop
  return old_master
470 8eb148ae Iustin Pop
471 8eb148ae Iustin Pop
472 d7cdb55d Iustin Pop
def GatherMasterVotes(node_list):
473 d7cdb55d Iustin Pop
  """Check the agreement on who is the master.
474 d7cdb55d Iustin Pop

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

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

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

486 d7cdb55d Iustin Pop
  @type node_list: list
487 d7cdb55d Iustin Pop
  @param node_list: the list of nodes to query for master info; the current
488 d7cdb55d Iustin Pop
      node wil be removed if it is in the list
489 d7cdb55d Iustin Pop
  @rtype: list
490 d7cdb55d Iustin Pop
  @return: list of (node, votes)
491 d7cdb55d Iustin Pop

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