Statistics
| Branch: | Tag: | Revision:

root / lib / bootstrap.py @ 04864530

History | View | Annotate | Download (9.9 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 sha
29 a0c9f010 Michael Hanselmann
import re
30 a0c9f010 Michael Hanselmann
31 a0c9f010 Michael Hanselmann
from ganeti import rpc
32 a0c9f010 Michael Hanselmann
from ganeti import ssh
33 a0c9f010 Michael Hanselmann
from ganeti import utils
34 a0c9f010 Michael Hanselmann
from ganeti import errors
35 a0c9f010 Michael Hanselmann
from ganeti import config
36 a0c9f010 Michael Hanselmann
from ganeti import constants
37 a0c9f010 Michael Hanselmann
from ganeti import ssconf
38 a0c9f010 Michael Hanselmann
39 a0c9f010 Michael Hanselmann
40 a0c9f010 Michael Hanselmann
def _InitSSHSetup(node):
41 a0c9f010 Michael Hanselmann
  """Setup the SSH configuration for the cluster.
42 a0c9f010 Michael Hanselmann

43 a0c9f010 Michael Hanselmann

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

47 a0c9f010 Michael Hanselmann
  Args:
48 a0c9f010 Michael Hanselmann
    node: the name of this host as a fqdn
49 a0c9f010 Michael Hanselmann

50 a0c9f010 Michael Hanselmann
  """
51 a0c9f010 Michael Hanselmann
  priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
52 a0c9f010 Michael Hanselmann
53 a0c9f010 Michael Hanselmann
  for name in priv_key, pub_key:
54 a0c9f010 Michael Hanselmann
    if os.path.exists(name):
55 a0c9f010 Michael Hanselmann
      utils.CreateBackup(name)
56 a0c9f010 Michael Hanselmann
    utils.RemoveFile(name)
57 a0c9f010 Michael Hanselmann
58 a0c9f010 Michael Hanselmann
  result = utils.RunCmd(["ssh-keygen", "-t", "dsa",
59 a0c9f010 Michael Hanselmann
                         "-f", priv_key,
60 a0c9f010 Michael Hanselmann
                         "-q", "-N", ""])
61 a0c9f010 Michael Hanselmann
  if result.failed:
62 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("Could not generate ssh keypair, error %s" %
63 a0c9f010 Michael Hanselmann
                             result.output)
64 a0c9f010 Michael Hanselmann
65 a0c9f010 Michael Hanselmann
  f = open(pub_key, 'r')
66 a0c9f010 Michael Hanselmann
  try:
67 a0c9f010 Michael Hanselmann
    utils.AddAuthorizedKey(auth_keys, f.read(8192))
68 a0c9f010 Michael Hanselmann
  finally:
69 a0c9f010 Michael Hanselmann
    f.close()
70 a0c9f010 Michael Hanselmann
71 a0c9f010 Michael Hanselmann
72 a0c9f010 Michael Hanselmann
def _InitGanetiServerSetup(ss):
73 a0c9f010 Michael Hanselmann
  """Setup the necessary configuration for the initial node daemon.
74 a0c9f010 Michael Hanselmann

75 a0c9f010 Michael Hanselmann
  This creates the nodepass file containing the shared password for
76 a0c9f010 Michael Hanselmann
  the cluster and also generates the SSL certificate.
77 a0c9f010 Michael Hanselmann

78 05f86716 Guido Trotter
  Args:
79 05f86716 Guido Trotter
    ss: A WritableSimpleStore
80 05f86716 Guido Trotter

81 a0c9f010 Michael Hanselmann
  """
82 a0c9f010 Michael Hanselmann
  # Create pseudo random password
83 a0c9f010 Michael Hanselmann
  randpass = sha.new(os.urandom(64)).hexdigest()
84 a0c9f010 Michael Hanselmann
  # and write it into sstore
85 a0c9f010 Michael Hanselmann
  ss.SetKey(ss.SS_NODED_PASS, randpass)
86 a0c9f010 Michael Hanselmann
87 a0c9f010 Michael Hanselmann
  result = utils.RunCmd(["openssl", "req", "-new", "-newkey", "rsa:1024",
88 a0c9f010 Michael Hanselmann
                         "-days", str(365*5), "-nodes", "-x509",
89 a0c9f010 Michael Hanselmann
                         "-keyout", constants.SSL_CERT_FILE,
90 a0c9f010 Michael Hanselmann
                         "-out", constants.SSL_CERT_FILE, "-batch"])
91 a0c9f010 Michael Hanselmann
  if result.failed:
92 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("could not generate server ssl cert, command"
93 a0c9f010 Michael Hanselmann
                             " %s had exitcode %s and error message %s" %
94 a0c9f010 Michael Hanselmann
                             (result.cmd, result.exit_code, result.output))
95 a0c9f010 Michael Hanselmann
96 a0c9f010 Michael Hanselmann
  os.chmod(constants.SSL_CERT_FILE, 0400)
97 a0c9f010 Michael Hanselmann
98 a0c9f010 Michael Hanselmann
  result = utils.RunCmd([constants.NODE_INITD_SCRIPT, "restart"])
99 a0c9f010 Michael Hanselmann
100 a0c9f010 Michael Hanselmann
  if result.failed:
101 a0c9f010 Michael Hanselmann
    raise errors.OpExecError("Could not start the node daemon, command %s"
102 a0c9f010 Michael Hanselmann
                             " had exitcode %s and error %s" %
103 a0c9f010 Michael Hanselmann
                             (result.cmd, result.exit_code, result.output))
104 a0c9f010 Michael Hanselmann
105 a0c9f010 Michael Hanselmann
106 a0c9f010 Michael Hanselmann
def InitCluster(cluster_name, hypervisor_type, mac_prefix, def_bridge,
107 a0c9f010 Michael Hanselmann
                master_netdev, file_storage_dir,
108 a0c9f010 Michael Hanselmann
                secondary_ip=None,
109 a0c9f010 Michael Hanselmann
                vg_name=None):
110 a0c9f010 Michael Hanselmann
  """Initialise the cluster.
111 a0c9f010 Michael Hanselmann

112 a0c9f010 Michael Hanselmann
  """
113 a0c9f010 Michael Hanselmann
  if config.ConfigWriter.IsCluster():
114 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Cluster is already initialised")
115 a0c9f010 Michael Hanselmann
116 a0c9f010 Michael Hanselmann
  if hypervisor_type == constants.HT_XEN_HVM31:
117 a0c9f010 Michael Hanselmann
    if not os.path.exists(constants.VNC_PASSWORD_FILE):
118 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("Please prepare the cluster VNC"
119 a0c9f010 Michael Hanselmann
                                 "password file %s" %
120 a0c9f010 Michael Hanselmann
                                 constants.VNC_PASSWORD_FILE)
121 a0c9f010 Michael Hanselmann
122 a0c9f010 Michael Hanselmann
  hostname = utils.HostInfo()
123 a0c9f010 Michael Hanselmann
124 a0c9f010 Michael Hanselmann
  if hostname.ip.startswith("127."):
125 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("This host's IP resolves to the private"
126 a0c9f010 Michael Hanselmann
                               " range (%s). Please fix DNS or %s." %
127 a0c9f010 Michael Hanselmann
                               (hostname.ip, constants.ETC_HOSTS))
128 a0c9f010 Michael Hanselmann
129 a0c9f010 Michael Hanselmann
  if not utils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT,
130 a0c9f010 Michael Hanselmann
                       source=constants.LOCALHOST_IP_ADDRESS):
131 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Inconsistency: this host's name resolves"
132 a0c9f010 Michael Hanselmann
                               " to %s,\nbut this ip address does not"
133 a0c9f010 Michael Hanselmann
                               " belong to this host."
134 a0c9f010 Michael Hanselmann
                               " Aborting." % hostname.ip)
135 a0c9f010 Michael Hanselmann
136 a0c9f010 Michael Hanselmann
  clustername = utils.HostInfo(cluster_name)
137 a0c9f010 Michael Hanselmann
138 a0c9f010 Michael Hanselmann
  if utils.TcpPing(clustername.ip, constants.DEFAULT_NODED_PORT,
139 a0c9f010 Michael Hanselmann
                   timeout=5):
140 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Cluster IP already active. Aborting.")
141 a0c9f010 Michael Hanselmann
142 a0c9f010 Michael Hanselmann
  if secondary_ip:
143 a0c9f010 Michael Hanselmann
    if not utils.IsValidIP(secondary_ip):
144 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("Invalid secondary ip given")
145 a0c9f010 Michael Hanselmann
    if (secondary_ip != hostname.ip and
146 a0c9f010 Michael Hanselmann
        (not utils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
147 a0c9f010 Michael Hanselmann
                           source=constants.LOCALHOST_IP_ADDRESS))):
148 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("You gave %s as secondary IP,"
149 a0c9f010 Michael Hanselmann
                                 " but it does not belong to this host." %
150 a0c9f010 Michael Hanselmann
                                 secondary_ip)
151 a0c9f010 Michael Hanselmann
152 a0c9f010 Michael Hanselmann
  if vg_name is not None:
153 a0c9f010 Michael Hanselmann
    # Check if volume group is valid
154 a0c9f010 Michael Hanselmann
    vgstatus = utils.CheckVolumeGroupSize(utils.ListVolumeGroups(), vg_name,
155 a0c9f010 Michael Hanselmann
                                          constants.MIN_VG_SIZE)
156 a0c9f010 Michael Hanselmann
    if vgstatus:
157 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("Error: %s\nspecify --no-lvm-storage if"
158 a0c9f010 Michael Hanselmann
                                 " you are not using lvm" % vgstatus)
159 a0c9f010 Michael Hanselmann
160 a0c9f010 Michael Hanselmann
  file_storage_dir = os.path.normpath(file_storage_dir)
161 a0c9f010 Michael Hanselmann
162 a0c9f010 Michael Hanselmann
  if not os.path.isabs(file_storage_dir):
163 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("The file storage directory you passed is"
164 a0c9f010 Michael Hanselmann
                               " not an absolute path.")
165 a0c9f010 Michael Hanselmann
166 a0c9f010 Michael Hanselmann
  if not os.path.exists(file_storage_dir):
167 a0c9f010 Michael Hanselmann
    try:
168 a0c9f010 Michael Hanselmann
      os.makedirs(file_storage_dir, 0750)
169 a0c9f010 Michael Hanselmann
    except OSError, err:
170 a0c9f010 Michael Hanselmann
      raise errors.OpPrereqError("Cannot create file storage directory"
171 a0c9f010 Michael Hanselmann
                                 " '%s': %s" %
172 a0c9f010 Michael Hanselmann
                                 (file_storage_dir, err))
173 a0c9f010 Michael Hanselmann
174 a0c9f010 Michael Hanselmann
  if not os.path.isdir(file_storage_dir):
175 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("The file storage directory '%s' is not"
176 a0c9f010 Michael Hanselmann
                               " a directory." % file_storage_dir)
177 a0c9f010 Michael Hanselmann
178 a0c9f010 Michael Hanselmann
  if not re.match("^[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}$", mac_prefix):
179 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Invalid mac prefix given '%s'" % mac_prefix)
180 a0c9f010 Michael Hanselmann
181 a0c9f010 Michael Hanselmann
  if hypervisor_type not in constants.HYPER_TYPES:
182 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Invalid hypervisor type given '%s'" %
183 a0c9f010 Michael Hanselmann
                               hypervisor_type)
184 a0c9f010 Michael Hanselmann
185 a0c9f010 Michael Hanselmann
  result = utils.RunCmd(["ip", "link", "show", "dev", master_netdev])
186 a0c9f010 Michael Hanselmann
  if result.failed:
187 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Invalid master netdev given (%s): '%s'" %
188 a0c9f010 Michael Hanselmann
                               (master_netdev,
189 a0c9f010 Michael Hanselmann
                                result.output.strip()))
190 a0c9f010 Michael Hanselmann
191 a0c9f010 Michael Hanselmann
  if not (os.path.isfile(constants.NODE_INITD_SCRIPT) and
192 a0c9f010 Michael Hanselmann
          os.access(constants.NODE_INITD_SCRIPT, os.X_OK)):
193 a0c9f010 Michael Hanselmann
    raise errors.OpPrereqError("Init.d script '%s' missing or not"
194 a0c9f010 Michael Hanselmann
                               " executable." % constants.NODE_INITD_SCRIPT)
195 a0c9f010 Michael Hanselmann
196 a0c9f010 Michael Hanselmann
  # set up the simple store
197 05f86716 Guido Trotter
  ss = ssconf.WritableSimpleStore()
198 a0c9f010 Michael Hanselmann
  ss.SetKey(ss.SS_HYPERVISOR, hypervisor_type)
199 a0c9f010 Michael Hanselmann
  ss.SetKey(ss.SS_MASTER_NODE, hostname.name)
200 a0c9f010 Michael Hanselmann
  ss.SetKey(ss.SS_MASTER_IP, clustername.ip)
201 a0c9f010 Michael Hanselmann
  ss.SetKey(ss.SS_MASTER_NETDEV, master_netdev)
202 a0c9f010 Michael Hanselmann
  ss.SetKey(ss.SS_CLUSTER_NAME, clustername.name)
203 a0c9f010 Michael Hanselmann
  ss.SetKey(ss.SS_FILE_STORAGE_DIR, file_storage_dir)
204 a0c9f010 Michael Hanselmann
  ss.SetKey(ss.SS_CONFIG_VERSION, constants.CONFIG_VERSION)
205 a0c9f010 Michael Hanselmann
206 a0c9f010 Michael Hanselmann
  # set up the inter-node password and certificate
207 a0c9f010 Michael Hanselmann
  _InitGanetiServerSetup(ss)
208 a0c9f010 Michael Hanselmann
209 a0c9f010 Michael Hanselmann
  # start the master ip
210 a0c9f010 Michael Hanselmann
  # TODO: Review rpc call from bootstrap
211 a0c9f010 Michael Hanselmann
  rpc.call_node_start_master(hostname.name)
212 a0c9f010 Michael Hanselmann
213 a0c9f010 Michael Hanselmann
  # set up ssh config and /etc/hosts
214 a0c9f010 Michael Hanselmann
  f = open(constants.SSH_HOST_RSA_PUB, 'r')
215 a0c9f010 Michael Hanselmann
  try:
216 a0c9f010 Michael Hanselmann
    sshline = f.read()
217 a0c9f010 Michael Hanselmann
  finally:
218 a0c9f010 Michael Hanselmann
    f.close()
219 a0c9f010 Michael Hanselmann
  sshkey = sshline.split(" ")[1]
220 a0c9f010 Michael Hanselmann
221 a0c9f010 Michael Hanselmann
  utils.AddHostToEtcHosts(hostname.name)
222 a0c9f010 Michael Hanselmann
  _InitSSHSetup(hostname.name)
223 a0c9f010 Michael Hanselmann
224 a0c9f010 Michael Hanselmann
  # init of cluster config file
225 a0c9f010 Michael Hanselmann
  cfg = config.ConfigWriter()
226 a0c9f010 Michael Hanselmann
  cfg.InitConfig(hostname.name, hostname.ip, secondary_ip, sshkey,
227 a0c9f010 Michael Hanselmann
                 mac_prefix, vg_name, def_bridge)
228 a0c9f010 Michael Hanselmann
229 a0c9f010 Michael Hanselmann
  ssh.WriteKnownHostsFile(cfg, ss, constants.SSH_KNOWN_HOSTS_FILE)
230 827f753e Guido Trotter
231 827f753e Guido Trotter
def SetupNodeDaemon(node):
232 827f753e Guido Trotter
  """Add a node to the cluster.
233 827f753e Guido Trotter

234 827f753e Guido Trotter
  This function must be called before the actual opcode, and will ssh to the
235 827f753e Guido Trotter
  remote node, copy the needed files, and start ganeti-noded, allowing the master
236 827f753e Guido Trotter
  to do the rest via normal rpc calls.
237 827f753e Guido Trotter

238 827f753e Guido Trotter
  Args:
239 827f753e Guido Trotter
    node: fully qualified domain name for the new node
240 827f753e Guido Trotter

241 827f753e Guido Trotter
  """
242 827f753e Guido Trotter
  ss = ssconf.SimpleStore()
243 827f753e Guido Trotter
  sshrunner = ssh.SshRunner(ss)
244 827f753e Guido Trotter
  gntpass = ss.GetNodeDaemonPassword()
245 827f753e Guido Trotter
  if not re.match('^[a-zA-Z0-9.]{1,64}$', gntpass):
246 827f753e Guido Trotter
    raise errors.OpExecError("ganeti password corruption detected")
247 827f753e Guido Trotter
  f = open(constants.SSL_CERT_FILE)
248 827f753e Guido Trotter
  try:
249 827f753e Guido Trotter
    gntpem = f.read(8192)
250 827f753e Guido Trotter
  finally:
251 827f753e Guido Trotter
    f.close()
252 827f753e Guido Trotter
  # in the base64 pem encoding, neither '!' nor '.' are valid chars,
253 827f753e Guido Trotter
  # so we use this to detect an invalid certificate; as long as the
254 827f753e Guido Trotter
  # cert doesn't contain this, the here-document will be correctly
255 827f753e Guido Trotter
  # parsed by the shell sequence below
256 827f753e Guido Trotter
  if re.search('^!EOF\.', gntpem, re.MULTILINE):
257 827f753e Guido Trotter
    raise errors.OpExecError("invalid PEM encoding in the SSL certificate")
258 827f753e Guido Trotter
  if not gntpem.endswith("\n"):
259 827f753e Guido Trotter
    raise errors.OpExecError("PEM must end with newline")
260 827f753e Guido Trotter
261 827f753e Guido Trotter
  # set up inter-node password and certificate and restarts the node daemon
262 827f753e Guido Trotter
  # and then connect with ssh to set password and start ganeti-noded
263 827f753e Guido Trotter
  # note that all the below variables are sanitized at this point,
264 827f753e Guido Trotter
  # either by being constants or by the checks above
265 827f753e Guido Trotter
  mycommand = ("umask 077 && "
266 827f753e Guido Trotter
               "echo '%s' > '%s' && "
267 827f753e Guido Trotter
               "cat > '%s' << '!EOF.' && \n"
268 827f753e Guido Trotter
               "%s!EOF.\n%s restart" %
269 827f753e Guido Trotter
               (gntpass, ss.KeyToFilename(ss.SS_NODED_PASS),
270 827f753e Guido Trotter
                constants.SSL_CERT_FILE, gntpem,
271 827f753e Guido Trotter
                constants.NODE_INITD_SCRIPT))
272 827f753e Guido Trotter
273 827f753e Guido Trotter
  result = sshrunner.Run(node, 'root', mycommand, batch=False, ask_key=True)
274 827f753e Guido Trotter
  if result.failed:
275 827f753e Guido Trotter
    raise errors.OpExecError("Remote command on node %s, error: %s,"
276 827f753e Guido Trotter
                             " output: %s" %
277 827f753e Guido Trotter
                             (node, result.fail_reason, result.output))
278 827f753e Guido Trotter
279 827f753e Guido Trotter
  return 0