4 # Copyright (C) 2012 Google Inc.
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.
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.
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
21 """Script to configure the node daemon.
31 from cStringIO import StringIO
33 from ganeti import cli
34 from ganeti import constants
35 from ganeti import errors
36 from ganeti import pathutils
37 from ganeti import utils
38 from ganeti import serializer
39 from ganeti import runtime
41 from ganeti import ssconf
44 _DATA_CHECK = ht.TStrictDict(False, True, {
45 constants.NDS_CLUSTER_NAME: ht.TNonEmptyString,
46 constants.NDS_NODE_DAEMON_CERTIFICATE: ht.TNonEmptyString,
47 constants.NDS_SSCONF: ht.TDictOf(ht.TNonEmptyString, ht.TString),
48 constants.NDS_START_NODE_DAEMON: ht.TBool,
52 class SetupError(errors.GenericError):
53 """Local class for reporting errors.
59 """Parses the options passed to the program.
61 @return: Options and arguments
64 parser = optparse.OptionParser(usage="%prog [--dry-run]",
65 prog=os.path.basename(sys.argv[0]))
66 parser.add_option(cli.DEBUG_OPT)
67 parser.add_option(cli.VERBOSE_OPT)
68 parser.add_option(cli.DRY_RUN_OPT)
70 (opts, args) = parser.parse_args()
72 return VerifyOptions(parser, opts, args)
75 def VerifyOptions(parser, opts, args):
76 """Verifies options and arguments for correctness.
80 parser.error("No arguments are expected")
85 def _VerifyCertificate(cert_pem, _check_fn=utils.CheckNodeCertificate):
86 """Verifies a certificate against the local node daemon certificate.
88 @type cert_pem: string
89 @param cert_pem: Certificate and key in PEM format
91 @return: Formatted key and certificate
96 OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
97 except Exception, err:
98 raise errors.X509CertError("(stdin)",
99 "Unable to load certificate: %s" % err)
102 key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
103 except OpenSSL.crypto.Error, err:
104 raise errors.X509CertError("(stdin)",
105 "Unable to load private key: %s" % err)
107 # Check certificate with given key; this detects cases where the key given on
108 # stdin doesn't match the certificate also given on stdin
109 x509_check_fn = utils.PrepareX509CertKeyCheck(cert, key)
112 except OpenSSL.SSL.Error:
113 raise errors.X509CertError("(stdin)",
114 "Certificate is not signed with given key")
116 # Standard checks, including check against an existing local certificate
117 # (no-op if that doesn't exist)
120 # Format for storing on disk
122 buf.write(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key))
123 buf.write(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert))
124 return buf.getvalue()
127 def VerifyCertificate(data, _verify_fn=_VerifyCertificate):
128 """Verifies cluster certificate.
132 @return: Formatted key and certificate
135 cert = data.get(constants.NDS_NODE_DAEMON_CERTIFICATE)
137 raise SetupError("Node daemon certificate must be specified")
139 return _verify_fn(cert)
142 def VerifyClusterName(data, _verify_fn=ssconf.VerifyClusterName):
143 """Verifies cluster name.
147 @return: Cluster name
150 name = data.get(constants.NDS_CLUSTER_NAME)
152 raise SetupError("Cluster name must be specified")
159 def VerifySsconf(data, cluster_name, _verify_fn=ssconf.VerifyKeys):
160 """Verifies ssconf names.
165 items = data.get(constants.NDS_SSCONF)
168 raise SetupError("Ssconf values must be specified")
170 # TODO: Should all keys be required? Right now any subset of valid keys is
172 _verify_fn(items.keys())
174 if items.get(constants.SS_CLUSTER_NAME) != cluster_name:
175 raise SetupError("Cluster name in ssconf does not match")
181 """Parses and verifies input data.
186 return serializer.LoadAndVerifyJson(raw, _DATA_CHECK)
193 opts = ParseOptions()
195 utils.SetupToolLogging(opts.debug, opts.verbose)
198 getent = runtime.GetEnts()
200 data = LoadData(sys.stdin.read())
202 cluster_name = VerifyClusterName(data)
203 cert_pem = VerifyCertificate(data)
204 ssdata = VerifySsconf(data, cluster_name)
206 logging.info("Writing ssconf files ...")
207 ssconf.WriteSsconfFiles(ssdata, dry_run=opts.dry_run)
209 logging.info("Writing node daemon certificate ...")
210 utils.WriteFile(pathutils.NODED_CERT_FILE, data=cert_pem,
211 mode=pathutils.NODED_CERT_MODE,
212 uid=getent.masterd_uid, gid=getent.masterd_gid,
213 dry_run=opts.dry_run)
215 if (data.get(constants.NDS_START_NODE_DAEMON) and # pylint: disable=E1103
217 logging.info("Restarting node daemon ...")
219 stop_cmd = "%s stop-all" % pathutils.DAEMON_UTIL
220 noded_cmd = "%s start %s" % (pathutils.DAEMON_UTIL, constants.NODED)
222 if constants.ENABLE_MOND:
223 mond_cmd = "%s start %s" % (pathutils.DAEMON_UTIL, constants.MOND)
225 cmd = "; ".join([stop_cmd, noded_cmd, mond_cmd])
227 result = utils.RunCmd(cmd, interactive=True)
229 raise SetupError("Could not start the node daemons, command '%s'"
230 " failed: %s" % (result.cmd, result.fail_reason))
232 logging.info("Node daemon successfully configured")
233 except Exception, err: # pylint: disable=W0703
234 logging.debug("Caught unhandled exception", exc_info=True)
236 (retcode, message) = cli.FormatError(err)
237 logging.error(message)
241 return constants.EXIT_SUCCESS