stdin=data,
)
+def move_file(remote, from_path, to_path, sudo=False):
+
+ # need to stat the file first, to make sure we
+ # maintain the same permissions
+ args = []
+ if sudo:
+ args.append('sudo')
+ args.extend([
+ 'stat',
+ '-c',
+ '\"%a\"',
+ to_path
+ ])
+ proc = remote.run(
+ args=args,
+ stdout=StringIO(),
+ )
+ perms = proc.stdout.getvalue().rstrip().strip('\"')
+
+ args = []
+ if sudo:
+ args.append('sudo')
+ args.extend([
+ 'mv',
+ '--',
+ from_path,
+ to_path,
+ ])
+ proc = remote.run(
+ args=args,
+ stdout=StringIO(),
+ )
+
+ # reset the file back to the original permissions
+ args = []
+ if sudo:
+ args.append('sudo')
+ args.extend([
+ 'chmod',
+ perms,
+ to_path,
+ ])
+ proc = remote.run(
+ args=args,
+ stdout=StringIO(),
+ )
+
+def delete_file(remote, path, sudo=False):
+ args = []
+ if sudo:
+ args.append('sudo')
+ args.extend([
+ 'rm',
+ '--',
+ path,
+ ])
+ proc = remote.run(
+ args=args,
+ stdout=StringIO(),
+ )
+
+def remove_lines_from_file(remote, path, line_is_valid_test, string_to_test_for):
+ # read in the specified file
+ in_data = get_file(remote, path, False)
+ out_data = ""
+
+ first_line = True
+ # use the 'line_is_valid_test' function to remove unwanted lines
+ for line in in_data.split('\n'):
+ if line_is_valid_test(line, string_to_test_for):
+ if not first_line:
+ out_data += '\n'
+ else:
+ first_line = False
+
+ out_data += '{line}'.format(line=line)
+
+ else:
+ log.info('removing line: {bad_line}'.format(bad_line=line))
+
+ # get a temp file path on the remote host to write to,
+ # we don't want to blow away the remote file and then have the
+ # network drop out
+ temp_file_path = get_remote_tempnam(remote)
+
+ # write out the data to a temp file
+ write_file(remote, temp_file_path, out_data)
+
+ # then do a 'mv' to the actual file location
+ move_file(remote, temp_file_path, path)
+
+def append_lines_to_file(remote, path, lines, sudo=False):
+ temp_file_path = get_remote_tempnam(remote)
+
+ data = get_file(remote, path, sudo)
+
+ # add the additional data and write it back out, using a temp file
+ # in case of connectivity of loss, and then mv it to the
+ # actual desired location
+ data += lines
+ temp_file_path
+ write_file(remote, temp_file_path, data)
+
+ # then do a 'mv' to the actual file location
+ move_file(remote, temp_file_path, path)
+
+def get_remote_tempnam(remote, sudo=False):
+ args = []
+ if sudo:
+ args.append('sudo')
+ args.extend([
+ 'python',
+ '-c',
+ 'import os; print os.tempnam()'
+ ])
+ proc = remote.run(
+ args=args,
+ stdout=StringIO(),
+ )
+ data = proc.stdout.getvalue()
+ return data
+
+def create_file(remote, path, data="", permissions=str(644), sudo=False):
+ """
+ Create a file on the remote host.
+ """
+ args = []
+ if sudo:
+ args.append('sudo')
+ args.extend([
+ 'touch',
+ path,
+ run.Raw('&&'),
+ 'chmod',
+ permissions,
+ '--',
+ path
+ ])
+ proc = remote.run(
+ args=args,
+ stdout=StringIO(),
+ )
+ # now write out the data if any was passed in
+ if "" != data:
+ append_lines_to_file(remote, path, data, sudo)
+
def get_file(remote, path, sudo=False):
"""
Read a file from remote host into memory.
from cStringIO import StringIO
from teuthology import contextutil
+import teuthology.misc as misc
from ..orchestra import run
from ..orchestra.connection import create_key
log = logging.getLogger(__name__)
+ssh_keys_user = 'ssh-keys-user'
# generatees a public and private key
def generate_keys():
key.write_private_key(privateString)
return key.get_base64(), privateString.getvalue()
+def particular_ssh_key_test(line_to_test, ssh_key):
+ match = re.match('[\w-]+ {key} \S+@\S+'.format(key=ssh_key), line_to_test)
+
+ if match:
+ log.info('found matching ssh_key line: {line}'.format(line=line_to_test))
+ return False
+ else:
+ return True
+
+def ssh_keys_user_line_test(line_to_test, username ):
+ match = re.match('[\w-]+ \S+ {username}@\S+'.format(username=username), line_to_test)
+
+ if match:
+ log.info('found a ssh-keys-user line: {line}'.format(line=line_to_test))
+ return False
+ else:
+ return True
+
+# deletes keys that were previously generated
+def pre_run_cleanup_keys(ctx):
+ log.info('cleaning up any left-over keys from previous tests')
+
+ for remote in ctx.cluster.remotes:
+ username, hostname = str(remote).split('@')
+ if "" == username or "" == hostname:
+ continue
+ else:
+ path = '/home/{user}/.ssh/authorized_keys'.format(user=username)
+
+ misc.remove_lines_from_file(remote, path, ssh_keys_user_line_test, ssh_keys_user)
+
# deletes the keys and removes ~/.ssh/authorized_keys entries we added
-def cleanup_keys(ctx, public_key):
- client = paramiko.SSHClient()
- client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-
- for host in ctx.cluster.remotes.iterkeys():
- username, hostname = str(host).split('@')
- log.info('cleaning up keys on {host}'.format(host=hostname, user=username))
-
- # try to extract a public key for the host from the ctx.config entries
- host_key_found = False
- for t, host_key in ctx.config['targets'].iteritems():
-
- if str(t) == str(host):
- keytype, key = host_key.split(' ',1)
- client.get_host_keys().add(
- hostname=hostname,
- keytype=keytype,
- key=create_key(keytype,key)
- )
- host_key_found = True
- log.info('ssh key found in ctx')
-
- # if we did not find a key, load the system keys
- if False == host_key_found:
- client.load_system_host_keys()
- log.info('no key found in ctx, using system host keys')
-
- client.connect(hostname, username=username)
- client.exec_command('rm ~/.ssh/id_rsa')
- client.exec_command('rm ~/.ssh/id_rsa.pub')
-
- # get the absolute path for authorized_keys
- stdin, stdout, stderr = client.exec_command('ls ~/.ssh/authorized_keys')
- auth_keys_file = stdout.readlines()[0].rstrip()
-
- mySftp = client.open_sftp()
-
- # write to a different authorized_keys file in case something
- # fails 1/2 way through (don't want to break ssh on the vm)
- old_auth_keys_file = mySftp.open(auth_keys_file)
- new_auth_keys_file = mySftp.open(auth_keys_file + '.new', 'w')
-
- for line in old_auth_keys_file.readlines():
- match = re.search(re.escape(public_key), line)
-
- if match:
- pass
- else:
- new_auth_keys_file.write(line)
-
- # close the files
- old_auth_keys_file.close()
- new_auth_keys_file.close()
-
- # now try to do an atomic-ish rename. If we botch this, it's bad news
- stdin, stdout, stderr = client.exec_command('mv ~/.ssh/authorized_keys.new ~/.ssh/authorized_keys')
-
- mySftp.close()
- client.close()
+def cleanup_added_key(ctx, public_key):
+ log.info('cleaning up keys added for testing')
+
+ for remote in ctx.cluster.remotes:
+ username, hostname = str(remote).split('@')
+ if "" == username or "" == hostname:
+ continue
+ else:
+ log.info(' cleaning up keys for user {user} on {host}'.format(host=hostname, user=username))
+
+ misc.delete_file(remote, '/home/{user}/.ssh/id_rsa'.format(user=username))
+ misc.delete_file(remote, '/home/{user}/.ssh/id_rsa.pub'.format(user=username))
+ path = '/home/{user}/.ssh/authorized_keys'.format(user=username)
+
+ misc.remove_lines_from_file(remote, path, particular_ssh_key_test, public_key)
@contextlib.contextmanager
def tweak_ssh_config(ctx, config):
@contextlib.contextmanager
def push_keys_to_host(ctx, config, public_key, private_key):
- client = paramiko.SSHClient()
- client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-
- for host in ctx.cluster.remotes.iterkeys():
- log.info('host: {host}'.format(host=host))
- username, hostname = str(host).split('@')
-
- # try to extract a public key for the host from the ctx.config entries
- host_key_found = False
- for t, host_key in ctx.config['targets'].iteritems():
-
- if str(t) == str(host):
- keytype, key = host_key.split(' ',1)
- client.get_host_keys().add(
- hostname=hostname,
- keytype=keytype,
- key=create_key(keytype,key)
- )
- host_key_found = True
- log.info('ssh key found in ctx')
-
- # if we did not find a key, load the system keys
- if False == host_key_found:
- client.load_system_host_keys()
- log.info('no key found in ctx, using system host keys')
-
- log.info('pushing keys to {host} for {user}'.format(host=hostname, user=username))
-
- client.connect(hostname, username=username)
- client.exec_command('echo "{priv_key}" > ~/.ssh/id_rsa'.format(priv_key=private_key))
- # the default file permissions cause ssh to balk
- client.exec_command('chmod 500 ~/.ssh/id_rsa')
- client.exec_command('echo "ssh-rsa {pub_key} {user_host}" > ~/.ssh/id_rsa.pub'.format(pub_key=public_key,user_host=host))
-
- # for this host, add all hosts to the ~/.ssh/authorized_keys file
- for inner_host in ctx.cluster.remotes.iterkeys():
- client.exec_command('echo "ssh-rsa {pub_key} {user_host}" >> ~/.ssh/authorized_keys'.format(pub_key=public_key,user_host=str(inner_host)))
-
-
- client.close()
+ # add an entry for all hosts in ctx to auth_keys_data
+ auth_keys_data = ''
+
+ for inner_host in ctx.cluster.remotes.iterkeys():
+ inner_username, inner_hostname = str(inner_host).split('@')
+ # create a 'user@hostname' string using our fake hostname
+ fake_hostname = '{user}@{host}'.format(user=ssh_keys_user,host=str(inner_hostname))
+ auth_keys_data += '\nssh-rsa {pub_key} {user_host}'.format(pub_key=public_key,user_host=fake_hostname)
+
+ # for each host in ctx, add keys for all other hosts
+ for remote in ctx.cluster.remotes:
+ username, hostname = str(remote).split('@')
+ if "" == username or "" == hostname:
+ continue
+ else:
+ log.info('pushing keys to {host} for {user}'.format(host=hostname, user=username))
+
+ # adding a private key
+ priv_key_file = '/home/{user}/.ssh/id_rsa'.format(user=username)
+ priv_key_data = '{priv_key}'.format(priv_key=private_key)
+ # Hadoop requires that .ssh/id_rsa have permissions of '500'
+ misc.create_file(remote, priv_key_file, priv_key_data, str(500))
+
+ # then a private key
+ pub_key_file = '/home/{user}/.ssh/id_rsa.pub'.format(user=username)
+ pub_key_data = 'ssh-rsa {pub_key} {user_host}'.format(pub_key=public_key,user_host=str(remote))
+ misc.create_file(remote, pub_key_file, pub_key_data)
+
+ # adding appropriate entries to the authorized_keys file for this host
+ auth_keys_file = '/home/{user}/.ssh/authorized_keys'.format(user=username)
+
+ # now add the list of keys for hosts in ctx to ~/.ssh/authorized_keys
+ misc.append_lines_to_file(remote, auth_keys_file, auth_keys_data)
try:
yield
finally:
# cleanup the keys
log.info("Cleaning up SSH keys")
- cleanup_keys(ctx, public_key)
+ cleanup_added_key(ctx, public_key)
@contextlib.contextmanager
# ctx, so I'm keeping it outside of the nested calls
public_key_string, private_key_string = generate_keys()
+ # cleanup old keys if they were somehow left behind
+ pre_run_cleanup_keys(ctx)
+
with contextutil.nested(
lambda: push_keys_to_host(ctx, config, public_key_string, private_key_string),
lambda: tweak_ssh_config(ctx, config),