Replaced sleeps with port polling.
Project: http://git-wip-us.apache.org/repos/asf/trafficserver-qa/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver-qa/commit/a502d378 Tree: http://git-wip-us.apache.org/repos/asf/trafficserver-qa/tree/a502d378 Diff: http://git-wip-us.apache.org/repos/asf/trafficserver-qa/diff/a502d378 Branch: refs/heads/master Commit: a502d378bd9eca7df0c9b1107d5d789bdbd5da81 Parents: 32fc37c Author: Joshua Blatt <[email protected]> Authored: Mon Dec 29 11:04:51 2014 -0800 Committer: Joshua Blatt <[email protected]> Committed: Mon Dec 29 11:04:51 2014 -0800 ---------------------------------------------------------------------- tests/hello_world_test.py | 10 ++-- tsqa/environment.py | 79 +++++++++++++++------------ tsqa/test_cases.py | 8 ++- tsqa/utils.py | 118 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 175 insertions(+), 40 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver-qa/blob/a502d378/tests/hello_world_test.py ---------------------------------------------------------------------- diff --git a/tests/hello_world_test.py b/tests/hello_world_test.py index 2d918ea..302e4b2 100644 --- a/tests/hello_world_test.py +++ b/tests/hello_world_test.py @@ -22,22 +22,24 @@ class TestEnvironmentCase(tsqa.test_cases.EnvironmentCase): # TODO: actually test this, this is currently terrible ;) def test_daemon(self): - self.environment.start() # start ATS - time.sleep(2) + self.log.info('Begin test_daemon') assert self.environment.cop.pid > 0 assert self.environment.cop.returncode is None - self.environment.stop() + self.log.info('End test_daemon') class TestDynamicHTTPEndpointCase(tsqa.test_cases.DynamicHTTPEndpointCase): def test_base(self): + self.log.info('Begin test_base') ret = requests.get(self.endpoint_url('/footest')) self.assertEqual(ret.status_code, 404) + self.log.info('End test_base') def test_endpoint_url(self): + self.log.info('Begin test_endpoint_url') assert self.endpoint_url() == 'http://127.0.0.1:{0}'.format(self.http_endpoint.address[1]) assert self.endpoint_url('/foo') == 'http://127.0.0.1:{0}/foo'.format(self.http_endpoint.address[1]) - + self.log.info('End test_endpoint_url') if __name__ == "__main__": unittest.main() http://git-wip-us.apache.org/repos/asf/trafficserver-qa/blob/a502d378/tsqa/environment.py ---------------------------------------------------------------------- diff --git a/tsqa/environment.py b/tsqa/environment.py index cd5b747..03c1396 100644 --- a/tsqa/environment.py +++ b/tsqa/environment.py @@ -3,8 +3,9 @@ import tempfile import os import copy import shutil -import json - +import tsqa.utils +import logging +import sys import tsqa.configs import tsqa.utils @@ -28,7 +29,7 @@ class EnvironmentFactory(object): # TODO: ensure this directory exists? (and is git?) self.source_dir = source_dir - + self.log = tsqa.utils.get_logger() self.env_cache_dir = env_cache_dir # base directory for environment caching if default_configure is not None: @@ -45,13 +46,20 @@ class EnvironmentFactory(object): ''' Autoreconf to make the configure script ''' + + kwargs = { + 'cwd': self.source_dir, + 'env': self.default_env, + 'stdout': subprocess.PIPE, + 'stderr': subprocess.PIPE + } + + if self.log.isEnabledFor(logging.DEBUG): + kwargs['stdout'] = sys.stdout.fileno() + kwargs['stderr'] = sys.stderr.fileno() + # run autoreconf in source tree - tsqa.utils.run_sync_command(['autoreconf', '-if'], - cwd=self.source_dir, - env=self.default_env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) + tsqa.utils.run_sync_command(['autoreconf', '-if'], **kwargs) @property def source_hash(self): @@ -114,39 +122,34 @@ class EnvironmentFactory(object): del env[blacklisted_key] key = self._get_key(configure, env) - # TODO: remove - print 'Key is:', key, 'args are:', configure, env + self.log.debug('Key is: %s, args are: %s %s' % (key, configure, env)) # if we don't have it built already, lets build it if key not in self.environment_stash: self.autoreconf() builddir = tempfile.mkdtemp() + kwargs = { + 'cwd': builddir, + 'env': env, + 'stdout': subprocess.PIPE, + 'stderr': subprocess.PIPE + } + + if self.log.isEnabledFor(logging.DEBUG): + kwargs['stdout'] = sys.stdout.fileno() + kwargs['stderr'] = sys.stderr.fileno() + # configure args = [os.path.join(self.source_dir, 'configure'), '--prefix=/'] + tsqa.utils.configure_list(configure) - tsqa.utils.run_sync_command(args, - cwd=builddir, - env=env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) + tsqa.utils.run_sync_command(args, **kwargs) # make - tsqa.utils.run_sync_command(['make', '-j'], - cwd=builddir, - env=env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) + tsqa.utils.run_sync_command(['make', '-j'], **kwargs) installdir = tempfile.mkdtemp(dir=self.env_cache_dir) # make install - tsqa.utils.run_sync_command(['make', 'install', 'DESTDIR={0}'.format(installdir)], - cwd=builddir, - env=env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) + tsqa.utils.run_sync_command(['make', 'install', 'DESTDIR={0}'.format(installdir)], **kwargs) shutil.rmtree(builddir) # delete builddir, not useful after install # stash the env @@ -184,6 +187,7 @@ class Layout: def __init__(self, prefix): self.prefix = prefix + self.log = tsqa.utils.get_logger() def __getattr__(self, name): # Raise an error for suffixes we don't know about @@ -237,8 +241,8 @@ class Environment: stdout=logfile, stderr=logfile, ) - import time - time.sleep(3) # TODO: wait or the process to listen? + tsqa.utils.poll_interfaces(self.hostports) + # TODO: better checking... self.cop.poll() if self.cop.returncode is not None: @@ -248,7 +252,9 @@ class Environment: """ Initialize a new Environment. """ + self.log = tsqa.utils.get_logger() self.cop = None + self.hostports = [] if layout: self.layout = layout else: @@ -292,6 +298,10 @@ class Environment: else: os.chmod(dirname, 0777) + http_server_port = tsqa.utils.bind_unused_port()[1] + manager_mgmt_port = tsqa.utils.bind_unused_port()[1] + self.hostports = [('127.0.0.1', http_server_port), ('127.0.0.1', manager_mgmt_port)] + # overwrite a few things that need to be changed to have a unique env records = tsqa.configs.RecordsConfig(os.path.join(self.layout.sysconfdir, 'records.config')) records['CONFIG'].update({ @@ -301,8 +311,8 @@ class Environment: 'proxy.config.bin_path': self.layout.bindir, 'proxy.config.log.logfile_dir': self.layout.logdir, 'proxy.config.local_state_dir': self.layout.runtimedir, - 'proxy.config.http.server_ports': str(tsqa.utils.bind_unused_port()[1]), # your own listen port - 'proxy.config.process_manager.mgmt_port': tsqa.utils.bind_unused_port()[1], # your own listen port + 'proxy.config.http.server_ports': str(http_server_port), # your own listen port + 'proxy.config.process_manager.mgmt_port': manager_mgmt_port, # your own listen port }) records.write() @@ -319,12 +329,15 @@ class Environment: self.layout = Layout(None) def start(self): + self.log.debug("Starting traffic cop") assert(os.path.isfile(os.path.join(self.layout.sysconfdir, 'records.config'))) self.__exec_cop() + self.log.debug("Started traffic cop: %s", self.cop) # TODO: more graceful stop? # TODO: raise exception when you call stop when its not started? def stop(self): + self.log.debug("Killing traffic cop: %s", self.cop) if self.cop is not None: self.cop.kill() self.cop.terminate() # TODO: remove?? or wait... http://git-wip-us.apache.org/repos/asf/trafficserver-qa/blob/a502d378/tsqa/test_cases.py ---------------------------------------------------------------------- diff --git a/tsqa/test_cases.py b/tsqa/test_cases.py index d7ba75a..cb1ef55 100644 --- a/tsqa/test_cases.py +++ b/tsqa/test_cases.py @@ -10,7 +10,6 @@ unittest = tsqa.utils.import_unittest() import os - # Example environment case class EnvironmentCase(unittest.TestCase): ''' @@ -25,6 +24,9 @@ class EnvironmentCase(unittest.TestCase): # call parent constructor super(EnvironmentCase, cls).setUpClass() + # get a logger + cls.log = tsqa.utils.get_logger() + # get an environment cls.environment = cls.getEnv() @@ -68,6 +70,7 @@ class EnvironmentCase(unittest.TestCase): if cls.environment.cop is not None and not cls.environment.running: raise Exception('ATS died during the test run') # stop ATS + cls.environment.stop() # call parent destructor @@ -89,6 +92,9 @@ class DynamicHTTPEndpointCase(unittest.TestCase): ''' @classmethod def setUpClass(cls, port=0): + # get a logger + cls.log = tsqa.utils.get_logger() + cls.http_endpoint = tsqa.endpoint.DynamicHTTPEndpoint(port=port) cls.http_endpoint.start() http://git-wip-us.apache.org/repos/asf/trafficserver-qa/blob/a502d378/tsqa/utils.py ---------------------------------------------------------------------- diff --git a/tsqa/utils.py b/tsqa/utils.py index f58a2bd..d522f66 100644 --- a/tsqa/utils.py +++ b/tsqa/utils.py @@ -4,6 +4,117 @@ import json import sys import subprocess import socket +import logging +import time + +tsqa_logger = None +tsqa_log_level = logging.INFO +tsqa_log_levels = { + 'CRITICAL': logging.CRITICAL, + 'ERROR': logging.ERROR, + 'WARN': logging.WARNING, + 'WARNING': logging.WARNING, + 'INFO': logging.INFO, + 'DEBUG': logging.DEBUG, + 'NOTSET': logging.NOTSET +} + +def set_log_level(log_level): + ''' + Set the global log level (override with env var TSQA_LOG_LEVEL). Must be called + before first get_logger() + ''' + + global tsqa_log_level + tsqa_log_level = log_level + +def get_log_level(): + ''' + Get the global log level (override with env var TSQA_LOG_LEVEL). + ''' + + if os.environ.has_key('TSQA_LOG_LEVEL'): + log_level = os.environ['TSQA_LOG_LEVEL'].upper() + + if tsqa_log_levels.has_key(log_level): + return tsqa_log_levels[log_level] + + return tsqa_log_level + +def set_logger(logger): + ''' + Set/replace the global logger + ''' + + global tsqa_logger + tsqa_logger = logger + +def get_logger(): + ''' + Get the global logger + ''' + + global tsqa_logger + + if tsqa_logger: + return tsqa_logger + + tsqa_logger = logging.getLogger() + tsqa_logger.setLevel(get_log_level()) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(levelname)s %(asctime)-15s - %(message)s")) + tsqa_logger.addHandler(handler) + + return tsqa_logger + +def poll_interfaces(hostports, **kwargs): + ''' Block until we can successfully connect to all ports or timeout + + :param hostports: + :param kwargs: optional timeout_sec + ''' + + connect_timeout_sec = 1 + poll_sleep_sec = 1 + + if kwargs.has_key('timeout_sec'): + timeout = time.time() + kwargs['timeout_sec'] + else: + timeout = time.time() + 5 + + hostports = hostports[:] # don't modify the caller's hostports + + while timeout > time.time(): + for hostport in hostports[:]: # don't modify our hostports copy during iteration + hostname = hostport[0] + port = hostport[1] + + if get_logger().isEnabledFor(logging.DEBUG): + get_logger().debug("Checking interface '%s:%d'", hostname, port) + + # This supports IPv6 + + try: + s = socket.create_connection((hostname, port), timeout=connect_timeout_sec) + s.close() + hostports.remove(hostport) + + if get_logger().isEnabledFor(logging.DEBUG): + get_logger().debug("Interface '%s:%d' is up", hostname, port) + except: + pass + + if not hostports: + break + + time.sleep(poll_sleep_sec) + + if hostports: + raise Exception("Timeout waiting for interfaces: {0}".format( + reduce(lambda x, y: str(x) + ',' + str(y), hostports))) + + if get_logger().isEnabledFor(logging.DEBUG): + get_logger().debug("All interfaces are up") # TODO: test def import_unittest(): @@ -37,9 +148,12 @@ def run_sync_command(*args, **kwargs): p = subprocess.Popen(*args, **kwargs) stdout, stderr = p.communicate() if p.returncode != 0: - raise Exception('Error running: {0}\n{1}'.format(args[0], stderr)) - return stdout, stderr + if stderr: + raise Exception('Error {0} running: {1}\n{2}'.format(p.returncode, args[0], stderr)) + else: + raise Exception('Error {0} running: {1}'.format(p.returncode, args[0])) + return stdout, stderr def merge_dicts(*args): '''
