]> git.apps.os.sepia.ceph.com Git - teuthology.git/commitdiff
teuthology: remove previous test ssh keys
authorJoe Buck <jbbuck@gmail.com>
Wed, 20 Mar 2013 04:26:16 +0000 (21:26 -0700)
committerJoe Buck <jbbuck@gmail.com>
Thu, 4 Apr 2013 22:55:54 +0000 (15:55 -0700)
Updated the ssh-keys task to cleanup
any left-over keys from previous tasks
(indicated by the user being 'ssh-keys-user').
Also, some of the functions in the ssh_keys task seem
like they could be useful in general.
This patch refactors them into misc.py.

Signed-off-by: Joe Buck <jbbuck@gmail.com>
Reviewd-by: Sam Lang <sam.lang@inktank.com>
teuthology/misc.py
teuthology/task/ssh_keys.py

index 1fdeadf4aa5aa015f8c56c689213a1c80e5798a8..1838dcc3a3769aeb59984b21d0a48fe3f4c91bca 100644 (file)
@@ -270,6 +270,152 @@ def sudo_write_file(remote, path, data, perms=None):
         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.
index c2637d01eab44b7ff475ef32439987c4e4e9c77d..60fcacfd6c28970298cdd8069d7c3e5de15f9f3e 100644 (file)
@@ -6,10 +6,12 @@ import re
 
 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():
@@ -18,66 +20,53 @@ 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):   
@@ -107,46 +96,39 @@ 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
@@ -154,7 +136,7 @@ def push_keys_to_host(ctx, config, public_key, private_key):
     finally:
         # cleanup the keys
         log.info("Cleaning up SSH keys")
-        cleanup_keys(ctx, public_key)
+        cleanup_added_key(ctx, public_key)
 
 
 @contextlib.contextmanager
@@ -178,6 +160,9 @@ def task(ctx, config):
     # 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),