Move InitCluster opcode into a single function
[ganeti-local] / lib / bootstrap.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Functions to bootstrap a new cluster.
23
24 """
25
26 import os
27 import os.path
28 import sha
29 import re
30
31 from ganeti import rpc
32 from ganeti import ssh
33 from ganeti import utils
34 from ganeti import errors
35 from ganeti import config
36 from ganeti import constants
37 from ganeti import ssconf
38
39
40 def _InitSSHSetup(node):
41   """Setup the SSH configuration for the cluster.
42
43
44   This generates a dsa keypair for root, adds the pub key to the
45   permitted hosts and adds the hostkey to its own known hosts.
46
47   Args:
48     node: the name of this host as a fqdn
49
50   """
51   priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
52
53   for name in priv_key, pub_key:
54     if os.path.exists(name):
55       utils.CreateBackup(name)
56     utils.RemoveFile(name)
57
58   result = utils.RunCmd(["ssh-keygen", "-t", "dsa",
59                          "-f", priv_key,
60                          "-q", "-N", ""])
61   if result.failed:
62     raise errors.OpExecError("Could not generate ssh keypair, error %s" %
63                              result.output)
64
65   f = open(pub_key, 'r')
66   try:
67     utils.AddAuthorizedKey(auth_keys, f.read(8192))
68   finally:
69     f.close()
70
71
72 def _InitGanetiServerSetup(ss):
73   """Setup the necessary configuration for the initial node daemon.
74
75   This creates the nodepass file containing the shared password for
76   the cluster and also generates the SSL certificate.
77
78   """
79   # Create pseudo random password
80   randpass = sha.new(os.urandom(64)).hexdigest()
81   # and write it into sstore
82   ss.SetKey(ss.SS_NODED_PASS, randpass)
83
84   result = utils.RunCmd(["openssl", "req", "-new", "-newkey", "rsa:1024",
85                          "-days", str(365*5), "-nodes", "-x509",
86                          "-keyout", constants.SSL_CERT_FILE,
87                          "-out", constants.SSL_CERT_FILE, "-batch"])
88   if result.failed:
89     raise errors.OpExecError("could not generate server ssl cert, command"
90                              " %s had exitcode %s and error message %s" %
91                              (result.cmd, result.exit_code, result.output))
92
93   os.chmod(constants.SSL_CERT_FILE, 0400)
94
95   result = utils.RunCmd([constants.NODE_INITD_SCRIPT, "restart"])
96
97   if result.failed:
98     raise errors.OpExecError("Could not start the node daemon, command %s"
99                              " had exitcode %s and error %s" %
100                              (result.cmd, result.exit_code, result.output))
101
102
103 def InitCluster(cluster_name, hypervisor_type, mac_prefix, def_bridge,
104                 master_netdev, file_storage_dir,
105                 secondary_ip=None,
106                 vg_name=None):
107   """Initialise the cluster.
108
109   """
110   if config.ConfigWriter.IsCluster():
111     raise errors.OpPrereqError("Cluster is already initialised")
112
113   if hypervisor_type == constants.HT_XEN_HVM31:
114     if not os.path.exists(constants.VNC_PASSWORD_FILE):
115       raise errors.OpPrereqError("Please prepare the cluster VNC"
116                                  "password file %s" %
117                                  constants.VNC_PASSWORD_FILE)
118
119   hostname = utils.HostInfo()
120
121   if hostname.ip.startswith("127."):
122     raise errors.OpPrereqError("This host's IP resolves to the private"
123                                " range (%s). Please fix DNS or %s." %
124                                (hostname.ip, constants.ETC_HOSTS))
125
126   if not utils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT,
127                        source=constants.LOCALHOST_IP_ADDRESS):
128     raise errors.OpPrereqError("Inconsistency: this host's name resolves"
129                                " to %s,\nbut this ip address does not"
130                                " belong to this host."
131                                " Aborting." % hostname.ip)
132
133   clustername = utils.HostInfo(cluster_name)
134
135   if utils.TcpPing(clustername.ip, constants.DEFAULT_NODED_PORT,
136                    timeout=5):
137     raise errors.OpPrereqError("Cluster IP already active. Aborting.")
138
139   if secondary_ip:
140     if not utils.IsValidIP(secondary_ip):
141       raise errors.OpPrereqError("Invalid secondary ip given")
142     if (secondary_ip != hostname.ip and
143         (not utils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
144                            source=constants.LOCALHOST_IP_ADDRESS))):
145       raise errors.OpPrereqError("You gave %s as secondary IP,"
146                                  " but it does not belong to this host." %
147                                  secondary_ip)
148
149   if vg_name is not None:
150     # Check if volume group is valid
151     vgstatus = utils.CheckVolumeGroupSize(utils.ListVolumeGroups(), vg_name,
152                                           constants.MIN_VG_SIZE)
153     if vgstatus:
154       raise errors.OpPrereqError("Error: %s\nspecify --no-lvm-storage if"
155                                  " you are not using lvm" % vgstatus)
156
157   file_storage_dir = os.path.normpath(file_storage_dir)
158
159   if not os.path.isabs(file_storage_dir):
160     raise errors.OpPrereqError("The file storage directory you passed is"
161                                " not an absolute path.")
162
163   if not os.path.exists(file_storage_dir):
164     try:
165       os.makedirs(file_storage_dir, 0750)
166     except OSError, err:
167       raise errors.OpPrereqError("Cannot create file storage directory"
168                                  " '%s': %s" %
169                                  (file_storage_dir, err))
170
171   if not os.path.isdir(file_storage_dir):
172     raise errors.OpPrereqError("The file storage directory '%s' is not"
173                                " a directory." % file_storage_dir)
174
175   if not re.match("^[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}$", mac_prefix):
176     raise errors.OpPrereqError("Invalid mac prefix given '%s'" % mac_prefix)
177
178   if hypervisor_type not in constants.HYPER_TYPES:
179     raise errors.OpPrereqError("Invalid hypervisor type given '%s'" %
180                                hypervisor_type)
181
182   result = utils.RunCmd(["ip", "link", "show", "dev", master_netdev])
183   if result.failed:
184     raise errors.OpPrereqError("Invalid master netdev given (%s): '%s'" %
185                                (master_netdev,
186                                 result.output.strip()))
187
188   if not (os.path.isfile(constants.NODE_INITD_SCRIPT) and
189           os.access(constants.NODE_INITD_SCRIPT, os.X_OK)):
190     raise errors.OpPrereqError("Init.d script '%s' missing or not"
191                                " executable." % constants.NODE_INITD_SCRIPT)
192
193   # set up the simple store
194   ss = ssconf.SimpleStore()
195   ss.SetKey(ss.SS_HYPERVISOR, hypervisor_type)
196   ss.SetKey(ss.SS_MASTER_NODE, hostname.name)
197   ss.SetKey(ss.SS_MASTER_IP, clustername.ip)
198   ss.SetKey(ss.SS_MASTER_NETDEV, master_netdev)
199   ss.SetKey(ss.SS_CLUSTER_NAME, clustername.name)
200   ss.SetKey(ss.SS_FILE_STORAGE_DIR, file_storage_dir)
201   ss.SetKey(ss.SS_CONFIG_VERSION, constants.CONFIG_VERSION)
202
203   # set up the inter-node password and certificate
204   _InitGanetiServerSetup(ss)
205
206   # start the master ip
207   # TODO: Review rpc call from bootstrap
208   rpc.call_node_start_master(hostname.name)
209
210   # set up ssh config and /etc/hosts
211   f = open(constants.SSH_HOST_RSA_PUB, 'r')
212   try:
213     sshline = f.read()
214   finally:
215     f.close()
216   sshkey = sshline.split(" ")[1]
217
218   utils.AddHostToEtcHosts(hostname.name)
219   _InitSSHSetup(hostname.name)
220
221   # init of cluster config file
222   cfg = config.ConfigWriter()
223   cfg.InitConfig(hostname.name, hostname.ip, secondary_ip, sshkey,
224                  mac_prefix, vg_name, def_bridge)
225
226   ssh.WriteKnownHostsFile(cfg, ss, constants.SSH_KNOWN_HOSTS_FILE)