root / lib / tools / node_daemon_setup.py @ 66234263
History | View | Annotate | Download (6.5 kB)
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
|