Package ganeti :: Package tools :: Module node_daemon_setup
[hide private]
[frames] | no frames]

Source Code for Module ganeti.tools.node_daemon_setup

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2012 Google Inc. 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are 
  9  # met: 
 10  # 
 11  # 1. Redistributions of source code must retain the above copyright notice, 
 12  # this list of conditions and the following disclaimer. 
 13  # 
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 29   
 30  """Script to configure the node daemon. 
 31   
 32  """ 
 33   
 34  import os 
 35  import os.path 
 36  import optparse 
 37  import sys 
 38  import logging 
 39  import OpenSSL 
 40  from cStringIO import StringIO 
 41   
 42  from ganeti import cli 
 43  from ganeti import constants 
 44  from ganeti import errors 
 45  from ganeti import pathutils 
 46  from ganeti import utils 
 47  from ganeti import serializer 
 48  from ganeti import runtime 
 49  from ganeti import ht 
 50  from ganeti import ssconf 
 51  from ganeti.tools import common 
 52   
 53   
 54  _DATA_CHECK = ht.TStrictDict(False, True, { 
 55    constants.NDS_CLUSTER_NAME: ht.TNonEmptyString, 
 56    constants.NDS_NODE_DAEMON_CERTIFICATE: ht.TNonEmptyString, 
 57    constants.NDS_SSCONF: ht.TDictOf(ht.TNonEmptyString, ht.TString), 
 58    constants.NDS_START_NODE_DAEMON: ht.TBool, 
 59    constants.NDS_NODE_NAME: ht.TString, 
 60    }) 
 61   
 62   
63 -class SetupError(errors.GenericError):
64 """Local class for reporting errors. 65 66 """
67 68
69 -def ParseOptions():
70 """Parses the options passed to the program. 71 72 @return: Options and arguments 73 74 """ 75 parser = optparse.OptionParser(usage="%prog [--dry-run]", 76 prog=os.path.basename(sys.argv[0])) 77 parser.add_option(cli.DEBUG_OPT) 78 parser.add_option(cli.VERBOSE_OPT) 79 parser.add_option(cli.DRY_RUN_OPT) 80 81 (opts, args) = parser.parse_args() 82 83 return VerifyOptions(parser, opts, args)
84 85
86 -def VerifyOptions(parser, opts, args):
87 """Verifies options and arguments for correctness. 88 89 """ 90 if args: 91 parser.error("No arguments are expected") 92 93 return opts
94 95
96 -def _VerifyCertificate(cert_pem, _check_fn=utils.CheckNodeCertificate):
97 """Verifies a certificate against the local node daemon certificate. 98 99 @type cert_pem: string 100 @param cert_pem: Certificate and key in PEM format 101 @rtype: string 102 @return: Formatted key and certificate 103 104 """ 105 try: 106 cert = \ 107 OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem) 108 except Exception, err: 109 raise errors.X509CertError("(stdin)", 110 "Unable to load certificate: %s" % err) 111 112 try: 113 key = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, cert_pem) 114 except OpenSSL.crypto.Error, err: 115 raise errors.X509CertError("(stdin)", 116 "Unable to load private key: %s" % err) 117 118 # Check certificate with given key; this detects cases where the key given on 119 # stdin doesn't match the certificate also given on stdin 120 try: 121 utils.X509CertKeyCheck(cert, key) 122 except OpenSSL.SSL.Error: 123 raise errors.X509CertError("(stdin)", 124 "Certificate is not signed with given key") 125 126 # Standard checks, including check against an existing local certificate 127 # (no-op if that doesn't exist) 128 _check_fn(cert) 129 130 key_encoded = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) 131 cert_encoded = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, 132 cert) 133 complete_cert_encoded = key_encoded + cert_encoded 134 if not cert_pem == complete_cert_encoded: 135 logging.error("The certificate differs after being reencoded. Please" 136 " renew the certificates cluster-wide to prevent future" 137 " inconsistencies.") 138 139 # Format for storing on disk 140 buf = StringIO() 141 buf.write(cert_pem) 142 return buf.getvalue()
143 144
145 -def VerifyCertificate(data, _verify_fn=_VerifyCertificate):
146 """Verifies cluster certificate. 147 148 @type data: dict 149 @rtype: string 150 @return: Formatted key and certificate 151 152 """ 153 cert = data.get(constants.NDS_NODE_DAEMON_CERTIFICATE) 154 if not cert: 155 raise SetupError("Node daemon certificate must be specified") 156 157 return _verify_fn(cert)
158 159
160 -def VerifyClusterName(data, _verify_fn=ssconf.VerifyClusterName):
161 """Verifies cluster name. 162 163 @type data: dict 164 @rtype: string 165 @return: Cluster name 166 167 """ 168 name = data.get(constants.NDS_CLUSTER_NAME) 169 if not name: 170 raise SetupError("Cluster name must be specified") 171 172 _verify_fn(name) 173 174 return name
175 176
177 -def VerifySsconf(data, cluster_name, _verify_fn=ssconf.VerifyKeys):
178 """Verifies ssconf names. 179 180 @type data: dict 181 182 """ 183 items = data.get(constants.NDS_SSCONF) 184 185 if not items: 186 raise SetupError("Ssconf values must be specified") 187 188 # TODO: Should all keys be required? Right now any subset of valid keys is 189 # accepted. 190 _verify_fn(items.keys()) 191 192 if items.get(constants.SS_CLUSTER_NAME) != cluster_name: 193 raise SetupError("Cluster name in ssconf does not match") 194 195 return items
196 197
198 -def LoadData(raw):
199 """Parses and verifies input data. 200 201 @rtype: dict 202 203 """ 204 return serializer.LoadAndVerifyJson(raw, _DATA_CHECK)
205 206
207 -def Main():
208 """Main routine. 209 210 """ 211 opts = ParseOptions() 212 213 utils.SetupToolLogging(opts.debug, opts.verbose) 214 215 try: 216 getent = runtime.GetEnts() 217 218 data = LoadData(sys.stdin.read()) 219 220 cluster_name = VerifyClusterName(data) 221 cert_pem = VerifyCertificate(data) 222 ssdata = VerifySsconf(data, cluster_name) 223 224 logging.info("Writing ssconf files ...") 225 ssconf.WriteSsconfFiles(ssdata, dry_run=opts.dry_run) 226 227 logging.info("Writing node daemon certificate ...") 228 utils.WriteFile(pathutils.NODED_CERT_FILE, data=cert_pem, 229 mode=pathutils.NODED_CERT_MODE, 230 uid=getent.masterd_uid, gid=getent.masterd_gid, 231 dry_run=opts.dry_run) 232 common.GenerateClientCertificate(data, SetupError) 233 234 if (data.get(constants.NDS_START_NODE_DAEMON) and # pylint: disable=E1103 235 not opts.dry_run): 236 logging.info("Restarting node daemon ...") 237 238 stop_cmd = "%s stop-all" % pathutils.DAEMON_UTIL 239 noded_cmd = "%s start %s" % (pathutils.DAEMON_UTIL, constants.NODED) 240 mond_cmd = "" 241 if constants.ENABLE_MOND: 242 mond_cmd = "%s start %s" % (pathutils.DAEMON_UTIL, constants.MOND) 243 244 cmd = "; ".join([stop_cmd, noded_cmd, mond_cmd]) 245 246 result = utils.RunCmd(cmd, interactive=True) 247 if result.failed: 248 raise SetupError("Could not start the node daemons, command '%s'" 249 " failed: %s" % (result.cmd, result.fail_reason)) 250 251 logging.info("Node daemon successfully configured") 252 except Exception, err: # pylint: disable=W0703 253 logging.debug("Caught unhandled exception", exc_info=True) 254 255 (retcode, message) = cli.FormatError(err) 256 logging.error(message) 257 258 return retcode 259 else: 260 return constants.EXIT_SUCCESS
261