Add and require conf-file argument
* The user can now pass in a tempest configuration file to the refstack-client script. A tempest config file is now required. * The tempest script was changed from run_tests.sh to run_tempest.sh as run_tests.sh is meant for the tempest unit tests. * Made it so test results are printed out only with a verbosity count of two. * Deprecated/broken code was removed. * We now keep track of where the subunit data is saved. Change-Id: I8a98f45c2f50368512d711a675a07dea7e23ad04
This commit is contained in:
		@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Copyright (c) 2014 Piston Cloud Computing, Inc. All Rights Reserved.
 | 
					# Copyright (c) 2014 Piston Cloud Computing, Inc. All Rights Reserved.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
@@ -16,39 +17,26 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
Run tempest and upload results to Refstack.
 | 
					Run Tempest and upload results to Refstack.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This module deals with generating a working tempest conf file from
 | 
					This module runs the Tempest test suite on an OpenStack environment given a
 | 
				
			||||||
the environment variables (usually populated by sourcing openrc.sh)
 | 
					Tempest configuration file.
 | 
				
			||||||
 | 
					 | 
				
			||||||
This is currently just built for Havana tempest.
 | 
					 | 
				
			||||||
We're also just starting with nova-net and not neutron support.
 | 
					 | 
				
			||||||
TODO:
 | 
					 | 
				
			||||||
* generate config
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import argparse
 | 
					import argparse
 | 
				
			||||||
import ConfigParser
 | 
					 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import requests
 | 
					import requests
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Test:
 | 
					class RefstackClient:
 | 
				
			||||||
    log_format = "%(asctime)s %(name)s %(levelname)s %(message)s"
 | 
					    log_format = "%(asctime)s %(name)s %(levelname)s %(message)s"
 | 
				
			||||||
    base_config_file = os.path.join(
 | 
					 | 
				
			||||||
        os.path.abspath(os.path.dirname(__file__)),
 | 
					 | 
				
			||||||
        'tempest.conf')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    expected_env = {'OS_AUTH_URL': "uri",
 | 
					 | 
				
			||||||
                    'OS_TENANT_NAME': "admin_tenant_name",
 | 
					 | 
				
			||||||
                    'OS_USERNAME': "admin_username",
 | 
					 | 
				
			||||||
                    'OS_PASSWORD': "admin_password"}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, args):
 | 
					    def __init__(self, args):
 | 
				
			||||||
        '''Prepare a tempest test against a cloud.'''
 | 
					        '''Prepare a tempest test against a cloud.'''
 | 
				
			||||||
        self.logger = logging.getLogger("execute_test")
 | 
					        self.logger = logging.getLogger("refstack_client")
 | 
				
			||||||
        self.console_log_handle = logging.StreamHandler()
 | 
					        self.console_log_handle = logging.StreamHandler()
 | 
				
			||||||
        self.console_log_handle.setFormatter(
 | 
					        self.console_log_handle.setFormatter(
 | 
				
			||||||
            logging.Formatter(self.log_format))
 | 
					            logging.Formatter(self.log_format))
 | 
				
			||||||
@@ -59,86 +47,83 @@ class Test:
 | 
				
			|||||||
        elif args.verbose == 1:
 | 
					        elif args.verbose == 1:
 | 
				
			||||||
            self.logger.setLevel(logging.INFO)
 | 
					            self.logger.setLevel(logging.INFO)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.logger.setLevel(logging.CRITICAL)
 | 
					            self.logger.setLevel(logging.ERROR)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # assign local vars to match args
 | 
					        # assign local vars to match args
 | 
				
			||||||
        self.url = args.url
 | 
					        self.url = args.url
 | 
				
			||||||
        self.test_id = args.test_id
 | 
					 | 
				
			||||||
        self.tempest_dir = args.tempest_dir
 | 
					        self.tempest_dir = args.tempest_dir
 | 
				
			||||||
        self.output_conf = os.path.join(self.tempest_dir, 'tempest.config')
 | 
					        self.tempest_script = os.path.join(self.tempest_dir, 'run_tempest.sh')
 | 
				
			||||||
        self.tempest_script = os.path.join(self.tempest_dir, 'run_tests.sh')
 | 
					        self.test_cases = args.test_cases
 | 
				
			||||||
 | 
					        self.verbose = args.verbose
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # set up object config parser
 | 
					        # Check that the config file exists.
 | 
				
			||||||
        self.config_parser = ConfigParser.SafeConfigParser()
 | 
					        if not os.path.isfile(args.conf_file):
 | 
				
			||||||
        self.config_parser.read(self.base_config_file)
 | 
					            self.logger.error("File not valid: %s" % args.conf_file)
 | 
				
			||||||
 | 
					            exit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        #import config
 | 
					        self.conf_file = args.conf_file
 | 
				
			||||||
        self.import_config_from_env()
 | 
					        self.results_file = self._get_subunit_output_file()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # write the config file
 | 
					    def post_results(self):
 | 
				
			||||||
        self.config_parser.write(open(self.output_conf, 'w+'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        #prep cloud based on new config
 | 
					 | 
				
			||||||
        self.prepare_cloud()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def import_config_from_env(self):
 | 
					 | 
				
			||||||
        """create config from environment variables if set otherwise
 | 
					 | 
				
			||||||
        promt user for values"""
 | 
					 | 
				
			||||||
        for env_var, key in self.expected_env.items():
 | 
					 | 
				
			||||||
            if not os.environ.get(env_var):
 | 
					 | 
				
			||||||
                value = raw_input("Enter value for %s: " % env_var)
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                value = os.environ.get(env_var)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            self.config_parser.set('identity', key, value)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def prepare_cloud(self):
 | 
					 | 
				
			||||||
        """triggers tempest auto generate code to add users and setup image"""
 | 
					 | 
				
			||||||
        #TODO davidlenwell: figure out how to import and run code in
 | 
					 | 
				
			||||||
        #                   scripts/prep_cloud.py
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def post_test_result(self):
 | 
					 | 
				
			||||||
        '''Post the combined results back to the server.'''
 | 
					        '''Post the combined results back to the server.'''
 | 
				
			||||||
        self.logger.info('Send back the result')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with open(self.result, 'rb') as result_file:
 | 
					        # TODO(cdiep): This method needs to generate results in a format
 | 
				
			||||||
            # clean out any passwords or sensitive data.
 | 
					        # defined in https://review.openstack.org/#/c/105901/, and post those
 | 
				
			||||||
            result_file = self._scrub_sensitive_data(result_file)
 | 
					        # results using the v1 API.
 | 
				
			||||||
            # send the file home.
 | 
					 | 
				
			||||||
            payload = {'file': result_file, 'test_id': self.test_id}
 | 
					 | 
				
			||||||
            self._post_to_api('post-result', payload)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _scrub_sensitive_data(self, results_file):
 | 
					        self.logger.info('Sending the Tempest test results to the Refstack '
 | 
				
			||||||
        '''stub function for cleaning the results before sending them back.'''
 | 
					                         'API server.')
 | 
				
			||||||
        return results_file
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _post_status(self, message):
 | 
					    def _get_subunit_output_file(self):
 | 
				
			||||||
        '''this function posts status back to the api server
 | 
					        '''This method reads from the next-stream file in the .testrepository
 | 
				
			||||||
        if it has a test_id to report it with.'''
 | 
					           directory of the given Tempest path. The integer here is the name
 | 
				
			||||||
        if self.test_id:
 | 
					           of the file where subunit output will be saved to.'''
 | 
				
			||||||
            payload = {'test_id': self.test_id, 'status': message}
 | 
					 | 
				
			||||||
            self._post_to_api('update-test-status', payload)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _post_to_api(self, method, payload):
 | 
					 | 
				
			||||||
        '''This is a utility function for posting to the api. it is used by
 | 
					 | 
				
			||||||
        both _post_status and post_test_result'''
 | 
					 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            url = '%s/v1/%s' % (self.url, method)
 | 
					            subunit_file = open(os.path.join(
 | 
				
			||||||
            requests.post(url, payload)
 | 
					                                self.tempest_dir, '.testrepository',
 | 
				
			||||||
            self.logger.info('posted successfully')
 | 
					                                'next-stream'), 'r').read()
 | 
				
			||||||
        except Exception as e:
 | 
					        except (IOError, OSError):
 | 
				
			||||||
            #TODO davidlenwell: be more explicit with exceptions.
 | 
					            self.logger.debug('The .testrepository/next-stream file was not '
 | 
				
			||||||
            self.logger.critical('failed to post %s - %s ' % (url,e))
 | 
					                              'found. Assuming subunit results will be stored '
 | 
				
			||||||
            raise
 | 
					                              'in file 0.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Testr saves the first test stream to .testrepository/0 when
 | 
				
			||||||
 | 
					            # there is a newly generated .testrepository directory.
 | 
				
			||||||
 | 
					            subunit_file = "0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return os.path.join(self.tempest_dir, '.testrepository', subunit_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run(self):
 | 
					    def run(self):
 | 
				
			||||||
        '''Execute tempest test against the cloud.'''
 | 
					        '''Execute tempest test against the cloud.'''
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            cmd = (self.tempest_script, '-C', self.output_conf)
 | 
					            self.logger.info("Starting Tempest test...")
 | 
				
			||||||
            subprocess.check_output(cmd, shell=True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.post_test_result()
 | 
					            # Run the tempest script, specifying the output_conf file and the
 | 
				
			||||||
 | 
					            # flag telling it to not use a virtual environment.
 | 
				
			||||||
 | 
					            cmd = (self.tempest_script, '-C', self.conf_file, '-N')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add the tempest test cases to test as arguments.
 | 
				
			||||||
 | 
					            if self.test_cases:
 | 
				
			||||||
 | 
					                cmd += ('--', self.test_cases)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # If there were two verbose flags, show tempest results.
 | 
				
			||||||
 | 
					            if self.verbose > 1:
 | 
				
			||||||
 | 
					                stderr = None
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                # Suppress tempest results output. Note that testr prints
 | 
				
			||||||
 | 
					                # results to stderr.
 | 
				
			||||||
 | 
					                stderr = open(os.devnull, 'w')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Execute the tempest test script in a subprocess.
 | 
				
			||||||
 | 
					            process = subprocess.Popen(cmd, stderr=stderr)
 | 
				
			||||||
 | 
					            process.communicate()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # TODO(cdiep): Enable post_test_result once the code is ready.
 | 
				
			||||||
 | 
					            # self.post_results()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.logger.info('Tempest test complete.')
 | 
				
			||||||
 | 
					            self.logger.debug('Subunit results located in: %s' %
 | 
				
			||||||
 | 
					                              self.results_file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        except subprocess.CalledProcessError as e:
 | 
					        except subprocess.CalledProcessError as e:
 | 
				
			||||||
            self.logger.error('%s failed to complete' % (e))
 | 
					            self.logger.error('%s failed to complete' % (e))
 | 
				
			||||||
@@ -150,39 +135,42 @@ if __name__ == '__main__':
 | 
				
			|||||||
                                     formatter_class=argparse.
 | 
					                                     formatter_class=argparse.
 | 
				
			||||||
                                     ArgumentDefaultsHelpFormatter)
 | 
					                                     ArgumentDefaultsHelpFormatter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser.add_argument('-s', '--silent',
 | 
					    parser.add_argument('-v', '--verbose',
 | 
				
			||||||
                        action='store_true',
 | 
					                        action='count',
 | 
				
			||||||
                        help='rigged for silent running')
 | 
					                        help='Show verbose output. Note that -vv will show '
 | 
				
			||||||
 | 
					                             'Tempest test result output.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser.add_argument("-v", "--verbose",
 | 
					    parser.add_argument('--url',
 | 
				
			||||||
                        action="count",
 | 
					 | 
				
			||||||
                        help="show verbose output")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    parser.add_argument("--url",
 | 
					 | 
				
			||||||
                        action='store',
 | 
					                        action='store',
 | 
				
			||||||
                        required=False,
 | 
					                        required=False,
 | 
				
			||||||
                        default='https://api.refstack.org',
 | 
					                        default='https://api.refstack.org',
 | 
				
			||||||
                        type=str,
 | 
					                        type=str,
 | 
				
			||||||
                        help="refstack API url \
 | 
					                        help='Refstack API URL to post results to (e.g. --url '
 | 
				
			||||||
                               retrieve configurations. i.e.:\
 | 
					                             'https://127.0.0.1:8000).')
 | 
				
			||||||
                               --url https://127.0.0.1:8000")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    parser.add_argument("--test-id",
 | 
					    parser.add_argument('--tempest-dir',
 | 
				
			||||||
                        action='store',
 | 
					 | 
				
			||||||
                        required=False,
 | 
					 | 
				
			||||||
                        dest='test_id',
 | 
					 | 
				
			||||||
                        type=int,
 | 
					 | 
				
			||||||
                        help="refstack test ID i.e.:\
 | 
					 | 
				
			||||||
                                       --test-id 1234 ")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    parser.add_argument("--tempest-dir",
 | 
					 | 
				
			||||||
                        action='store',
 | 
					                        action='store',
 | 
				
			||||||
                        required=False,
 | 
					                        required=False,
 | 
				
			||||||
                        dest='tempest_dir',
 | 
					                        dest='tempest_dir',
 | 
				
			||||||
                        default='test_runner/src/tempest',
 | 
					                        default='test_runner/src/tempest',
 | 
				
			||||||
                        help="tempest directory path")
 | 
					                        help='Path of the tempest project directory.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parser.add_argument('-c', '--conf-file',
 | 
				
			||||||
 | 
					                        action='store',
 | 
				
			||||||
 | 
					                        required=True,
 | 
				
			||||||
 | 
					                        dest='conf_file',
 | 
				
			||||||
 | 
					                        type=str,
 | 
				
			||||||
 | 
					                        help='Path of the tempest configuration file to use.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parser.add_argument('-t', '--test-cases',
 | 
				
			||||||
 | 
					                        action='store',
 | 
				
			||||||
 | 
					                        required=False,
 | 
				
			||||||
 | 
					                        dest='test_cases',
 | 
				
			||||||
 | 
					                        type=str,
 | 
				
			||||||
 | 
					                        help='Specify a subset of test cases to run '
 | 
				
			||||||
 | 
					                             '(e.g. --test-cases tempest.api.compute).')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    args = parser.parse_args()
 | 
					    args = parser.parse_args()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test = Test(args)
 | 
					    test = RefstackClient(args)
 | 
				
			||||||
    test.run
 | 
					    test.run()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user