Statistics
| Branch: | Tag: | Revision:

root / lib / tools / node_daemon_setup.py @ d08a8359

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