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

Source Code for Module ganeti.tools.prepare_node_join

  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 prepare a node for joining a cluster. 
 31   
 32  """ 
 33   
 34  import os 
 35  import os.path 
 36  import optparse 
 37  import sys 
 38  import logging 
 39   
 40  from ganeti import cli 
 41  from ganeti import constants 
 42  from ganeti import errors 
 43  from ganeti import pathutils 
 44  from ganeti import utils 
 45  from ganeti import ht 
 46  from ganeti import ssh 
 47  from ganeti.tools import common 
 48   
 49   
 50  _SSH_KEY_LIST_ITEM = \ 
 51    ht.TAnd(ht.TIsLength(3), 
 52            ht.TItems([ 
 53              ht.TSshKeyType, 
 54              ht.Comment("public")(ht.TNonEmptyString), 
 55              ht.Comment("private")(ht.TNonEmptyString), 
 56            ])) 
 57   
 58  _SSH_KEY_LIST = ht.TListOf(_SSH_KEY_LIST_ITEM) 
 59   
 60  _DATA_CHECK = ht.TStrictDict(False, True, { 
 61    constants.SSHS_CLUSTER_NAME: ht.TNonEmptyString, 
 62    constants.SSHS_NODE_DAEMON_CERTIFICATE: ht.TNonEmptyString, 
 63    constants.SSHS_SSH_HOST_KEY: _SSH_KEY_LIST, 
 64    constants.SSHS_SSH_ROOT_KEY: _SSH_KEY_LIST, 
 65    constants.SSHS_SSH_AUTHORIZED_KEYS: 
 66      ht.TDictOf(ht.TNonEmptyString, ht.TListOf(ht.TNonEmptyString)), 
 67    constants.SSHS_SSH_KEY_TYPE: ht.TSshKeyType, 
 68    constants.SSHS_SSH_KEY_BITS: ht.TPositive, 
 69    }) 
 70   
 71   
72 -class JoinError(errors.GenericError):
73 """Local class for reporting errors. 74 75 """
76 77
78 -def ParseOptions():
79 """Parses the options passed to the program. 80 81 @return: Options and arguments 82 83 """ 84 program = os.path.basename(sys.argv[0]) 85 86 parser = optparse.OptionParser(usage="%prog [--dry-run]", 87 prog=program) 88 parser.add_option(cli.DEBUG_OPT) 89 parser.add_option(cli.VERBOSE_OPT) 90 parser.add_option(cli.DRY_RUN_OPT) 91 92 (opts, args) = parser.parse_args() 93 94 return common.VerifyOptions(parser, opts, args)
95 96
97 -def _UpdateKeyFiles(keys, dry_run, keyfiles):
98 """Updates SSH key files. 99 100 @type keys: sequence of tuple; (string, string, string) 101 @param keys: Keys to write, tuples consist of key type 102 (L{constants.SSHK_ALL}), public and private key 103 @type dry_run: boolean 104 @param dry_run: Whether to perform a dry run 105 @type keyfiles: dict; (string as key, tuple with (string, string) as values) 106 @param keyfiles: Mapping from key types (L{constants.SSHK_ALL}) to file 107 names; value tuples consist of public key filename and private key filename 108 109 """ 110 assert set(keyfiles) == constants.SSHK_ALL 111 112 for (kind, private_key, public_key) in keys: 113 (private_file, public_file) = keyfiles[kind] 114 115 logging.debug("Writing %s ...", private_file) 116 utils.WriteFile(private_file, data=private_key, mode=0600, 117 backup=True, dry_run=dry_run) 118 119 logging.debug("Writing %s ...", public_file) 120 utils.WriteFile(public_file, data=public_key, mode=0644, 121 backup=True, dry_run=dry_run)
122 123
124 -def UpdateSshDaemon(data, dry_run, _runcmd_fn=utils.RunCmd, 125 _keyfiles=None):
126 """Updates SSH daemon's keys. 127 128 Unless C{dry_run} is set, the daemon is restarted at the end. 129 130 @type data: dict 131 @param data: Input data 132 @type dry_run: boolean 133 @param dry_run: Whether to perform a dry run 134 135 """ 136 keys = data.get(constants.SSHS_SSH_HOST_KEY) 137 if not keys: 138 return 139 140 if _keyfiles is None: 141 _keyfiles = constants.SSH_DAEMON_KEYFILES 142 143 logging.info("Updating SSH daemon key files") 144 _UpdateKeyFiles(keys, dry_run, _keyfiles) 145 146 if dry_run: 147 logging.info("This is a dry run, not restarting SSH daemon") 148 else: 149 result = _runcmd_fn([pathutils.DAEMON_UTIL, "reload-ssh-keys"], 150 interactive=True) 151 if result.failed: 152 raise JoinError("Could not reload SSH keys, command '%s'" 153 " had exitcode %s and error %s" % 154 (result.cmd, result.exit_code, result.output))
155 156
157 -def UpdateSshRoot(data, dry_run, _homedir_fn=None):
158 """Updates root's SSH keys. 159 160 Root's C{authorized_keys} file is also updated with new public keys. 161 162 @type data: dict 163 @param data: Input data 164 @type dry_run: boolean 165 @param dry_run: Whether to perform a dry run 166 167 """ 168 authorized_keys = data.get(constants.SSHS_SSH_AUTHORIZED_KEYS) 169 170 (auth_keys_file, _) = \ 171 ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=True, 172 _homedir_fn=_homedir_fn) 173 174 if dry_run: 175 logging.info("This is a dry run, not replacing the SSH keys.") 176 else: 177 ssh_key_type = data.get(constants.SSHS_SSH_KEY_TYPE) 178 ssh_key_bits = data.get(constants.SSHS_SSH_KEY_BITS) 179 common.GenerateRootSshKeys(ssh_key_type, ssh_key_bits, error_fn=JoinError, 180 _homedir_fn=_homedir_fn) 181 182 if authorized_keys: 183 if dry_run: 184 logging.info("This is a dry run, not modifying %s", auth_keys_file) 185 else: 186 all_authorized_keys = [] 187 for keys in authorized_keys.values(): 188 all_authorized_keys += keys 189 ssh.AddAuthorizedKeys(auth_keys_file, all_authorized_keys)
190 191
192 -def Main():
193 """Main routine. 194 195 """ 196 opts = ParseOptions() 197 198 utils.SetupToolLogging( 199 opts.debug, opts.verbose, 200 toolname=os.path.splitext(os.path.basename(__file__))[0]) 201 202 try: 203 data = common.LoadData(sys.stdin.read(), _DATA_CHECK) 204 205 # Check if input data is correct 206 common.VerifyClusterName(data, JoinError, constants.SSHS_CLUSTER_NAME) 207 common.VerifyCertificateSoft(data, JoinError) 208 209 # Update SSH files 210 UpdateSshDaemon(data, opts.dry_run) 211 UpdateSshRoot(data, opts.dry_run) 212 213 logging.info("Setup finished successfully") 214 except Exception, err: # pylint: disable=W0703 215 logging.debug("Caught unhandled exception", exc_info=True) 216 217 (retcode, message) = cli.FormatError(err) 218 logging.error(message) 219 220 return retcode 221 else: 222 return constants.EXIT_SUCCESS
223