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