From 325076bef6ddbc7e3e5ca1d279cb691a2e4135a1 Mon Sep 17 00:00:00 2001 From: Nolan Brubaker Date: Mon, 22 May 2017 11:35:11 -0400 Subject: [PATCH] Allow graceful exiting with SIGINT and SIGTERM The test_loop function was only gracefully interrupted with a keyboard interrupt before. This is fine for interactive testing, but in an automated scenario the script may be running in the background and need to receive a signal from some other process, such as Ansible. This patch adds a handler for SIGINT and SIGTERM that will allow the current iteration of the loop to finish, then exit the program cleanly by putting the test_loop function into a class. The test_loop function is not refactored other than to add the `stop_now` flag, though the internal variables may be amenable to later refactoring to use the class. Change-Id: I0667d2c7093e2821afdfcea987cec6b58515885f --- bowling_ball/rolling_tests.py | 59 +++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/bowling_ball/rolling_tests.py b/bowling_ball/rolling_tests.py index 9dab5bf2..2855b713 100755 --- a/bowling_ball/rolling_tests.py +++ b/bowling_ball/rolling_tests.py @@ -25,6 +25,7 @@ from keystoneauth1.exceptions.http import InternalServerError from keystoneclient.v3 import client as key_client import logging import os +import signal import sys import time from glanceclient import Client @@ -145,7 +146,7 @@ class KeystoneTest(ServiceTest): # the test_loop function try: keystone.projects.list() - msg = "Project lest retrieved" + msg = "Project list retrieved" except InternalServerError: msg = "Failed to get project list" return msg @@ -196,23 +197,38 @@ class GlanceTest(ServiceTest): return glance_endpoint.url -def test_loop(test): - """Main loop to execute tests +class TestRunner(object): + """Run a test in a loop, with the option to gracefully exit""" + stop_now = False - Executes and times interactions with OpenStack services to gather timing - data. - :param: test - on object that performs some action - against an OpenStack service API. - """ - disconnected = None - # Has to be a tuple for python syntax reasons. - # This is currently the set needed for glance; should probably - # provide some way of letting a test say which exceptions should - # be caught for a service. - exc_list = (ConnectFailure, InternalServerError, BadGateway, - glance_exc.CommunicationError, - glance_exc.HTTPInternalServerError) - try: + def __init__(self): + signal.signal(signal.SIGINT, self.prep_exit) + signal.signal(signal.SIGTERM, self.prep_exit) + + def prep_exit(self, signum, frame): + self.stop_now = True + logger.info("Received signal, stopping") + + def test_loop(self, test): + """Main loop to execute tests + + Executes and times interactions with OpenStack services to gather + timing data. + + Execution can be ended by sending SIGINT or SIGTERM and the running + test will finish. + + :param: test - on object that performs some action + against an OpenStack service API. + """ + disconnected = None + # Has to be a tuple for python syntax reasons. + # This is currently the set needed for glance; should probably + # provide some way of letting a test say which exceptions should + # be caught for a service. + exc_list = (ConnectFailure, InternalServerError, BadGateway, + glance_exc.CommunicationError, + glance_exc.HTTPInternalServerError) while True: try: # Pause for a bit so we're not generating more data than we @@ -249,8 +265,9 @@ def test_loop(test): except (exc_list): if not disconnected: disconnected = datetime.datetime.now() - except KeyboardInterrupt: - sys.exit() + + if self.stop_now: + sys.exit() available_tests = { @@ -294,4 +311,6 @@ if __name__ == "__main__": target_test = target_test_class() target_test.configure_logger(logger) - test_loop(target_test) + runner = TestRunner() + + runner.test_loop(target_test)