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:
Paul Van Eck 2014-07-19 18:16:14 -07:00
parent 4cdbf530fd
commit cfd6754c6e

View File

@ -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()