Rename queryd to luxid
[ganeti-local] / lib / tools / node_daemon_setup.py
1 #
2 #
3
4 # Copyright (C) 2012 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 """Script to configure the node daemon.
22
23 """
24
25 import os
26 import os.path
27 import optparse
28 import sys
29 import logging
30 import OpenSSL
31 from cStringIO import StringIO
32
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
40 from ganeti import ht
41 from ganeti import ssconf
42
43
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,
49   })
50
51
52 class SetupError(errors.GenericError):
53   """Local class for reporting errors.
54
55   """
56
57
58 def ParseOptions():
59   """Parses the options passed to the program.
60
61   @return: Options and arguments
62
63   """
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)
69
70   (opts, args) = parser.parse_args()
71
72   return VerifyOptions(parser, opts, args)
73
74
75 def VerifyOptions(parser, opts, args):
76   """Verifies options and arguments for correctness.
77
78   """
79   if args:
80     parser.error("No arguments are expected")
81
82   return opts
83
84
85 def _VerifyCertificate(cert_pem, _check_fn=utils.CheckNodeCertificate):
86   """Verifies a certificate against the local node daemon certificate.
87
88   @type cert_pem: string
89   @param cert_pem: Certificate and key in PEM format
90   @rtype: string
91   @return: Formatted key and certificate
92
93   """
94   try:
95     cert = \
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)
100
101   try:
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)
106
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)
110   try:
111     x509_check_fn()
112   except OpenSSL.SSL.Error:
113     raise errors.X509CertError("(stdin)",
114                                "Certificate is not signed with given key")
115
116   # Standard checks, including check against an existing local certificate
117   # (no-op if that doesn't exist)
118   _check_fn(cert)
119
120   # Format for storing on disk
121   buf = StringIO()
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()
125
126
127 def VerifyCertificate(data, _verify_fn=_VerifyCertificate):
128   """Verifies cluster certificate.
129
130   @type data: dict
131   @rtype: string
132   @return: Formatted key and certificate
133
134   """
135   cert = data.get(constants.NDS_NODE_DAEMON_CERTIFICATE)
136   if not cert:
137     raise SetupError("Node daemon certificate must be specified")
138
139   return _verify_fn(cert)
140
141
142 def VerifyClusterName(data, _verify_fn=ssconf.VerifyClusterName):
143   """Verifies cluster name.
144
145   @type data: dict
146   @rtype: string
147   @return: Cluster name
148
149   """
150   name = data.get(constants.NDS_CLUSTER_NAME)
151   if not name:
152     raise SetupError("Cluster name must be specified")
153
154   _verify_fn(name)
155
156   return name
157
158
159 def VerifySsconf(data, cluster_name, _verify_fn=ssconf.VerifyKeys):
160   """Verifies ssconf names.
161
162   @type data: dict
163
164   """
165   items = data.get(constants.NDS_SSCONF)
166
167   if not items:
168     raise SetupError("Ssconf values must be specified")
169
170   # TODO: Should all keys be required? Right now any subset of valid keys is
171   # accepted.
172   _verify_fn(items.keys())
173
174   if items.get(constants.SS_CLUSTER_NAME) != cluster_name:
175     raise SetupError("Cluster name in ssconf does not match")
176
177   return items
178
179
180 def LoadData(raw):
181   """Parses and verifies input data.
182
183   @rtype: dict
184
185   """
186   return serializer.LoadAndVerifyJson(raw, _DATA_CHECK)
187
188
189 def Main():
190   """Main routine.
191
192   """
193   opts = ParseOptions()
194
195   utils.SetupToolLogging(opts.debug, opts.verbose)
196
197   try:
198     getent = runtime.GetEnts()
199
200     data = LoadData(sys.stdin.read())
201
202     cluster_name = VerifyClusterName(data)
203     cert_pem = VerifyCertificate(data)
204     ssdata = VerifySsconf(data, cluster_name)
205
206     logging.info("Writing ssconf files ...")
207     ssconf.WriteSsconfFiles(ssdata, dry_run=opts.dry_run)
208
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)
214
215     if (data.get(constants.NDS_START_NODE_DAEMON) and # pylint: disable=E1103
216         not opts.dry_run):
217       logging.info("Restarting node daemon ...")
218
219       stop_cmd = "%s stop-all" % pathutils.DAEMON_UTIL
220       noded_cmd = "%s start %s" % (pathutils.DAEMON_UTIL, constants.NODED)
221       mond_cmd = ""
222       if constants.ENABLE_MOND:
223         mond_cmd = "%s start %s" % (pathutils.DAEMON_UTIL, constants.MOND)
224
225       cmd = "; ".join([stop_cmd, noded_cmd, mond_cmd])
226
227       result = utils.RunCmd(cmd, interactive=True)
228       if result.failed:
229         raise SetupError("Could not start the node daemons, command '%s'"
230                          " failed: %s" % (result.cmd, result.fail_reason))
231
232     logging.info("Node daemon successfully configured")
233   except Exception, err: # pylint: disable=W0703
234     logging.debug("Caught unhandled exception", exc_info=True)
235
236     (retcode, message) = cli.FormatError(err)
237     logging.error(message)
238
239     return retcode
240   else:
241     return constants.EXIT_SUCCESS