diff options
Diffstat (limited to 'openstack/tester')
-rwxr-xr-x | openstack/tester | 306 |
1 files changed, 0 insertions, 306 deletions
diff --git a/openstack/tester b/openstack/tester deleted file mode 100755 index cf60385..0000000 --- a/openstack/tester +++ /dev/null @@ -1,306 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2015 Codethink Ltd -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -'''openstack/tester - -This script tests an image on openstack. - -''' - -import os -import pipes -import socket -import time -import uuid -import yaml - -import cliapp - -from novaclient import client - -class TimeoutError(cliapp.AppException): - - """Error to be raised when a connection waits too long""" - - def __init__(self, msg): - super(TimeoutError, self).__init__(msg) - - -def delete_image(hostname): - # TODO: Do all this stuff properly with python novaclient - # Remove an image from the openstack tenancy - print "Deleting %s test disc image" % (hostname) - try: - cliapp.runcmd(['nova', 'image-delete', hostname]) - except cliapp.AppException as e: - # TODO: Stop assuming that image-delete failed because it was - # already removed - print "- Failed" - pass - -def delete_instance_and_image(hostname): - # TODO: Do all this stuff properly with python novaclient - # Stop and remove VM, and its image - print "Deleting %s test instance" % (hostname) - try: - cliapp.runcmd(['nova', 'delete', hostname]) - except cliapp.AppException as e: - # TODO: Stop assuming that delete failed because the instance - # wasn't running - print "- Failed" - pass - - # Sleep for a bit, or nova silently fails to delete the image - # TODO: Test whether the image has been deleted in a retry loop. - time.sleep(20) - - delete_image(hostname) - - -class DeployedSystemInstance(object): - - def __init__(self, deployment, ip_addr, hostname): - self.deployment = deployment - self.ip_address = ip_addr - self.hostname = hostname - - @property - def ssh_host(self): - # TODO: Stop assuming we ssh into test instances as root - return 'root@{host}'.format(host=self.ip_address) - - def runcmd(self, argv, chdir='.', **kwargs): - ssh_cmd = ['ssh', '-o', 'StrictHostKeyChecking=no', - '-o', 'UserKnownHostsFile=/dev/null', self.ssh_host] - cmd = ['sh', '-c', 'cd "$1" && shift && exec "$@"', '-', chdir] - cmd += argv - ssh_cmd.append(' '.join(map(pipes.quote, cmd))) - print ssh_cmd - return cliapp.runcmd(ssh_cmd, **kwargs) - - def _wait_for_dhcp(self, timeout): - '''Block until given hostname resolves successfully. - - Raises TimeoutError if the hostname has not appeared in 'timeout' - seconds. - - ''' - start_time = time.time() - while True: - try: - socket.gethostbyname(self.ip_address) - return - except socket.gaierror: - pass - if time.time() > start_time + timeout: - raise TimeoutError("Host %s did not appear after %i seconds" % - (self.ip_address, timeout)) - time.sleep(0.5) - - def _wait_for_ssh(self, timeout): - """Wait until the deployed VM is responding via SSH""" - print('Attempting to connect to test instance via SSH:') - start_time = time.time() - while True: - try: - self.runcmd(['true'], stdin=None, stdout=None, stderr=None) - print('SSH connection established.') - return - except cliapp.AppException: - # TODO: Stop assuming the ssh part of the command is what failed - if time.time() > start_time + timeout: - raise TimeoutError("%s sshd did not start after %i seconds" - % (self.ip_address, timeout)) - time.sleep(0.5) - - def _wait_for_cloud_init(self, timeout): - """Wait until cloud init has resized the disc""" - start_time = time.time() - while True: - try: - out = self.runcmd(['sh', '-c', - 'test -e "$1" && echo exists || echo does not exist', - '-', - '/root/cloud-init-finished']) - except: - import traceback - traceback.print_exc() - raise - if out.strip() == 'exists': - return - if time.time() > start_time + timeout: - raise TimeoutError("Disc size not increased after %i seconds" - % (timeout)) - time.sleep(3) - - def wait_until_online(self, timeout=180): - self._wait_for_dhcp(timeout) - self._wait_for_ssh(timeout) - #self._wait_for_cloud_init(timeout) - print "Test system %s ready to run tests." % (self.hostname) - - def delete(self): - delete_instance_and_image(self.hostname) - - -class Deployment(object): - - def __init__(self, net_id, image_file, flavour): - self.net_id = net_id - self.image_file = image_file - self.flavour = flavour - - def deploy(self): - hostname = str(uuid.uuid4()) - - # Deploy the image to openstack - # TODO: Ensure this is cleaned up when something raises an exception - print('Uploading image to openstack host') - args = ['glance', 'image-create', '--progress', - '--name', hostname, - '--disk-format', 'raw', - '--container-format', 'bare', - '--file', self.image_file] - output = cliapp.runcmd(args) - print(output) - - # Get a novaclient object - nc = client.Client(2, - os.environ['OS_USERNAME'], - os.environ['OS_PASSWORD'], - os.environ['OS_TENANT_NAME'], - os.environ['OS_AUTH_URL']) - - # Boot an instance from the image - # TODO: Ensure this is cleaned up when something raises an exception - # TODO: use python-novaclient - print('Booting an instance from the image') - args = ['nova', 'boot', - '--flavor', self.flavour, - '--image', hostname, - #'--user-data', '/usr/lib/mason/os-init-script', - '--nic', "net-id=%s" % (self.net_id), - hostname] - output = cliapp.runcmd(args) - - # Print nova boot output, with adminPass line removed - output_lines = output.split('\n') - for line in output_lines: - if line.find('adminPass') != -1: - password_line = line - output_lines.remove(password_line) - output = '\n'.join(output_lines) - print output - - # Sleep for a bit, or nova explodes when trying to assign IP address - time.sleep(20) - - # Assign a floating IP address - print('Requesting a floating IP address') - for _ in xrange(5): - ip_list = nc.floating_ips.list() - free_ip = None - for ip in ip_list: - if ip.instance_id == None: - free_ip = ip - break - else: - free_ip = nc.floating_ips.create( - nc.floating_ip_pools.list()[0].name) - if free_ip != None: - instance = nc.servers.find(name=hostname) - # TODO: switch back to cli tool, as python - # approach gave error. - instance.add_floating_ip(free_ip) - ip_addr = free_ip.ip - break - else: - delete_instance_and_image(hostname) - raise cliapp.AppException('Could not get a floating IP') - - # Print the IP address - print "IP address for instance %s: %s" % (hostname, ip_addr) - - return DeployedSystemInstance(self, ip_addr, hostname) - -class ReleaseApp(cliapp.Application): - - """Cliapp application which handles automatic builds and tests""" - - def add_settings(self): - """Add the command line options needed""" - group_main = 'Program Options' - self.settings.string(['net-id'], - 'Openstack network ID', - default=None, - group=group_main) - self.settings.string(['image-file'], - 'Path to system image to test', - default=None, - group=group_main) - self.settings.string(['flavour'], - 'Name of flavour to use for instance', - default=None, - group=group_main) - - def run_tests(self, instance, tests): - instance.wait_until_online() - - for test in tests: - with open(test, 'r') as stream: - data = yaml.load(stream) - - if data == None: - continue - if 'name' not in data: - print('Bad test: no name') - continue - if 'commands' not in data: - print('Bad test: no commands') - continue - - print('Running test: ' + data['name']) - for cmd in data['commands']: - print('$ ' + cmd) - instance.runcmd(['sh', '-c', cmd], stdout=self.output) - - def deploy_and_test_systems(self, tests): - """Run the deployments and tests""" - - deployment = Deployment(self.settings['net-id'], - self.settings['image-file'], - self.settings['flavour']) - - instance = deployment.deploy() - try: - self.run_tests(instance, tests) - finally: - instance.delete() - - def process_args(self, args): - """Process the command line args and kick off the builds/tests""" - for setting in ('net-id', 'image-file', 'flavour'): - self.settings.require(setting) - - if len(args) == 0: - print('Warning: No tests specified.') - - self.deploy_and_test_systems(args) - - -if __name__ == '__main__': - ReleaseApp().run() |