yandex-tank/Tank/ConsoleWorker.py

315 lines
12 KiB
Python
Raw Normal View History

2013-11-19 12:47:46 +00:00
""" Provides classes to run TankCore from console environment """
2012-10-03 16:49:13 +00:00
import datetime
2012-09-12 13:18:58 +00:00
import fnmatch
import logging
import os
import sys
2013-11-19 12:47:46 +00:00
import time
2012-09-12 13:18:58 +00:00
import traceback
import signal
from optparse import OptionParser
2013-11-19 12:47:46 +00:00
from Tank.Plugins.ConsoleOnline import RealConsoleMarkup
from tankcore import TankCore
class SingleLevelFilter(logging.Filter):
2013-11-19 12:47:46 +00:00
"""Exclude or approve one msg type at a time. """
def __init__(self, passlevel, reject):
2013-01-28 13:28:28 +00:00
logging.Filter.__init__(self)
self.passlevel = passlevel
self.reject = reject
def filter(self, record):
if self.reject:
2013-11-19 12:47:46 +00:00
return record.levelno != self.passlevel
else:
2013-11-19 12:47:46 +00:00
return record.levelno == self.passlevel
2013-01-28 13:28:28 +00:00
def signal_handler(sig, frame):
2013-11-19 12:47:46 +00:00
""" required for non-tty python runs to interrupt """
raise KeyboardInterrupt()
2013-11-19 12:47:46 +00:00
signal.signal(signal.SIGINT, signal_handler)
2012-12-05 09:54:27 +00:00
signal.signal(signal.SIGTERM, signal_handler)
2012-09-12 13:18:58 +00:00
2013-11-19 12:47:46 +00:00
2012-09-12 13:18:58 +00:00
class ConsoleTank:
2013-09-06 15:58:52 +00:00
""" Worker class that runs tank core accepting cmdline params """
2012-09-12 13:18:58 +00:00
IGNORE_LOCKS = "ignore_locks"
2012-09-12 13:18:58 +00:00
def __init__(self, options, ammofile):
self.core = TankCore()
self.options = options
self.ammofile = ammofile
2013-11-19 12:47:46 +00:00
self.baseconfigs_location = '/etc/yandex-tank'
2012-09-12 13:18:58 +00:00
self.log_filename = self.options.log
self.core.add_artifact_file(self.log_filename)
self.log = logging.getLogger(__name__)
self.signal_count = 0
self.scheduled_start = None
2012-09-12 13:18:58 +00:00
2012-09-12 13:18:58 +00:00
def set_baseconfigs_dir(self, directory):
2013-11-19 12:47:46 +00:00
""" Set directory where to read configs set """
2012-09-12 13:18:58 +00:00
self.baseconfigs_location = directory
2013-11-19 12:47:46 +00:00
2012-09-12 13:18:58 +00:00
def init_logging(self):
2013-11-19 12:47:46 +00:00
""" Set up logging, as it is very important for console tool """
2012-09-12 13:18:58 +00:00
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
# create file handler which logs even debug messages
2013-11-19 12:47:46 +00:00
if self.log_filename:
2012-09-19 07:49:06 +00:00
file_handler = logging.FileHandler(self.log_filename)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(name)s %(message)s"))
logger.addHandler(file_handler)
2013-11-19 12:47:46 +00:00
2012-09-12 13:18:58 +00:00
# create console handler with a higher log level
2012-09-19 07:49:06 +00:00
console_handler = logging.StreamHandler(sys.stdout)
2013-01-27 17:47:02 +00:00
stderr_hdl = logging.StreamHandler(sys.stderr)
2013-11-19 12:47:46 +00:00
2013-01-28 13:28:28 +00:00
fmt_verbose = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s %(message)s")
fmt_regular = logging.Formatter("%(asctime)s %(levelname)s: %(message)s", "%H:%M:%S")
2013-11-19 12:47:46 +00:00
2012-09-12 13:18:58 +00:00
if self.options.verbose:
2012-09-19 07:49:06 +00:00
console_handler.setLevel(logging.DEBUG)
2013-01-27 17:47:02 +00:00
console_handler.setFormatter(fmt_verbose)
stderr_hdl.setFormatter(fmt_verbose)
2012-09-12 13:18:58 +00:00
elif self.options.quiet:
2012-09-19 07:49:06 +00:00
console_handler.setLevel(logging.WARNING)
2013-01-27 17:47:02 +00:00
console_handler.setFormatter(fmt_regular)
stderr_hdl.setFormatter(fmt_regular)
2012-09-12 13:18:58 +00:00
else:
2012-09-19 07:49:06 +00:00
console_handler.setLevel(logging.INFO)
2013-01-27 17:47:02 +00:00
console_handler.setFormatter(fmt_regular)
stderr_hdl.setFormatter(fmt_regular)
f_err = SingleLevelFilter(logging.ERROR, True)
f_warn = SingleLevelFilter(logging.WARNING, True)
f_crit = SingleLevelFilter(logging.CRITICAL, True)
console_handler.addFilter(f_err)
console_handler.addFilter(f_warn)
console_handler.addFilter(f_crit)
2012-09-19 07:49:06 +00:00
logger.addHandler(console_handler)
2012-09-12 13:18:58 +00:00
f_info = SingleLevelFilter(logging.INFO, True)
f_debug = SingleLevelFilter(logging.DEBUG, True)
stderr_hdl.addFilter(f_info)
stderr_hdl.addFilter(f_debug)
logger.addHandler(stderr_hdl)
2012-09-12 13:18:58 +00:00
2012-09-23 11:53:38 +00:00
def __override_config_from_cmdline(self):
2013-11-19 12:47:46 +00:00
""" override config options from command line"""
if self.options.option:
self.core.apply_shorthand_options(self.options.option)
2012-09-12 13:18:58 +00:00
2013-03-22 13:55:13 +00:00
def get_default_configs(self):
2013-11-19 12:47:46 +00:00
""" returns default configs list, from /etc and home dir """
configs = []
2013-03-22 13:55:13 +00:00
try:
conf_files = os.listdir(self.baseconfigs_location)
conf_files.sort()
for filename in conf_files:
if fnmatch.fnmatch(filename, '*.ini'):
configs += [os.path.realpath(self.baseconfigs_location + os.sep + filename)]
except OSError:
self.log.warn(self.baseconfigs_location + ' is not acessible to get configs list')
configs += [os.path.expanduser('~/.yandex-tank')]
return configs
2013-11-19 12:47:46 +00:00
2012-09-12 13:18:58 +00:00
def configure(self):
2013-11-19 12:47:46 +00:00
""" Make all console-specific preparations before running Tank """
if self.options.ignore_lock:
self.log.warn("Lock files ignored. This is highly unrecommended practice!")
2013-11-19 12:47:46 +00:00
if self.options.lock_dir:
self.core.set_option(self.core.SECTION, "lock_dir", self.options.lock_dir)
2013-11-19 12:47:46 +00:00
while True:
try:
self.core.get_lock(self.options.ignore_lock)
break
2013-01-15 15:44:37 +00:00
except Exception, exc:
2012-09-17 13:48:02 +00:00
if self.options.lock_fail:
raise RuntimeError("Lock file present, cannot continue")
2013-01-15 15:44:37 +00:00
self.log.debug("Failed to get lock: %s", traceback.format_exc(exc))
2012-09-24 13:46:59 +00:00
self.log.info("Waiting 5s for retry...")
2012-09-12 13:18:58 +00:00
time.sleep(5)
2013-11-19 12:47:46 +00:00
try:
configs = []
2013-11-19 12:47:46 +00:00
if not self.options.no_rc:
2013-03-22 13:55:13 +00:00
configs = self.get_default_configs()
2013-11-19 12:47:46 +00:00
if not self.options.config:
2012-11-12 12:20:06 +00:00
if os.path.exists(os.path.realpath('load.ini')):
self.log.info("No config passed via cmdline, using ./load.ini")
configs += [os.path.realpath('load.ini')]
self.core.add_artifact_file(os.path.realpath('load.ini'), True)
elif os.path.exists(os.path.realpath('load.conf')):
# just for old 'lunapark' compatibility
self.log.warn("Using 'load.conf' is unrecommended, please use 'load.ini' instead")
conf_file = os.path.realpath('load.conf')
2012-11-12 12:20:06 +00:00
configs += [conf_file]
self.core.add_artifact_file(conf_file, True)
else:
for config_file in self.options.config:
2013-06-07 13:14:33 +00:00
configs.append(config_file)
2013-11-19 12:47:46 +00:00
self.core.load_configs(configs)
2013-11-19 12:47:46 +00:00
if self.ammofile:
self.log.debug("Ammofile: %s", self.ammofile)
2013-06-07 11:32:31 +00:00
self.core.set_option("phantom", 'ammofile', self.ammofile[0])
2013-11-19 12:47:46 +00:00
self.__override_config_from_cmdline()
2013-11-19 12:47:46 +00:00
self.core.load_plugins()
2013-11-19 12:47:46 +00:00
if self.options.scheduled_start:
try:
self.scheduled_start = datetime.datetime.strptime(self.options.scheduled_start, '%Y-%m-%d %H:%M:%S')
except ValueError:
2013-11-19 12:47:46 +00:00
self.scheduled_start = datetime.datetime.strptime(
datetime.datetime.now().strftime('%Y-%m-%d ') + self.options.scheduled_start,
'%Y-%m-%d %H:%M:%S')
if self.options.ignore_lock:
self.core.set_option(self.core.SECTION, self.IGNORE_LOCKS, "1")
2013-11-19 12:47:46 +00:00
except Exception, ex:
self.log.info("Exception: %s", traceback.format_exc(ex))
2013-01-29 10:31:51 +00:00
sys.stderr.write(RealConsoleMarkup.RED)
self.log.error("%s", ex)
2013-01-29 10:31:51 +00:00
sys.stderr.write(RealConsoleMarkup.RESET)
sys.stderr.write(RealConsoleMarkup.TOTAL_RESET)
self.core.release_lock()
raise ex
2012-09-12 13:18:58 +00:00
2012-09-23 11:53:38 +00:00
def __graceful_shutdown(self):
2013-11-19 12:47:46 +00:00
""" call shutdown routines """
2012-09-19 07:49:06 +00:00
retcode = 1
2012-09-12 13:18:58 +00:00
self.log.info("Trying to shutdown gracefully...")
2012-09-19 07:49:06 +00:00
retcode = self.core.plugins_end_test(retcode)
retcode = self.core.plugins_post_process(retcode)
self.log.info("Done graceful shutdown")
2012-09-19 07:49:06 +00:00
return retcode
2013-11-19 12:47:46 +00:00
2012-09-12 13:18:58 +00:00
def perform_test(self):
2013-11-19 12:47:46 +00:00
"""
2012-09-23 11:53:38 +00:00
Run the test sequence via Tank Core
2013-11-19 12:47:46 +00:00
"""
2012-09-12 13:18:58 +00:00
self.log.info("Performing test")
2012-09-19 07:49:06 +00:00
retcode = 1
2012-09-12 13:18:58 +00:00
try:
self.core.plugins_configure()
2012-09-12 13:18:58 +00:00
self.core.plugins_prepare_test()
if self.scheduled_start:
self.log.info("Waiting scheduled time: %s...", self.scheduled_start)
while datetime.datetime.now() < self.scheduled_start:
self.log.debug("Not yet: %s < %s", datetime.datetime.now(), self.scheduled_start)
time.sleep(1)
self.log.info("Time has come: %s", datetime.datetime.now())
2013-11-19 12:47:46 +00:00
if self.options.manual_start:
raw_input("Press Enter key to start test:")
2013-11-19 12:47:46 +00:00
2012-09-12 13:18:58 +00:00
self.core.plugins_start_test()
2012-09-19 07:49:06 +00:00
retcode = self.core.wait_for_finish()
retcode = self.core.plugins_end_test(retcode)
retcode = self.core.plugins_post_process(retcode)
2013-11-19 12:47:46 +00:00
2012-09-12 13:18:58 +00:00
except KeyboardInterrupt as ex:
2012-11-01 17:47:13 +00:00
sys.stdout.write(RealConsoleMarkup.YELLOW)
self.log.info("Do not press Ctrl+C again, the test will be broken otherwise")
sys.stdout.write(RealConsoleMarkup.RESET)
sys.stdout.write(RealConsoleMarkup.TOTAL_RESET)
2012-09-12 13:18:58 +00:00
self.signal_count += 1
self.log.debug("Caught KeyboardInterrupt: %s", traceback.format_exc(ex))
try:
2012-09-23 11:53:38 +00:00
retcode = self.__graceful_shutdown()
2012-09-12 13:18:58 +00:00
except KeyboardInterrupt as ex:
self.log.debug("Caught KeyboardInterrupt again: %s", traceback.format_exc(ex))
self.log.info("User insists on exiting, aborting graceful shutdown...")
2012-09-19 07:49:06 +00:00
retcode = 1
2013-11-19 12:47:46 +00:00
2012-09-12 13:18:58 +00:00
except Exception as ex:
self.log.info("Exception: %s", traceback.format_exc(ex))
2013-01-29 10:31:51 +00:00
sys.stderr.write(RealConsoleMarkup.RED)
2012-09-12 13:18:58 +00:00
self.log.error("%s", ex)
2013-01-29 10:31:51 +00:00
sys.stderr.write(RealConsoleMarkup.RESET)
sys.stderr.write(RealConsoleMarkup.TOTAL_RESET)
2012-09-23 11:53:38 +00:00
retcode = self.__graceful_shutdown()
self.core.release_lock()
2013-11-19 12:47:46 +00:00
2012-09-19 07:49:06 +00:00
self.log.info("Done performing test with code %s", retcode)
return retcode
2012-09-12 13:18:58 +00:00
class DevNullOpts:
2013-11-19 12:47:46 +00:00
def __init__(self):
pass
log = "/dev/null"
2013-11-19 12:47:46 +00:00
class CompletionHelperOptionParser(OptionParser):
def __init__(self):
OptionParser.__init__(self, add_help_option=False)
self.add_option('--bash-switches-list', action='store_true', dest="list_switches", help="Options list")
self.add_option('--bash-options-prev', action='store', dest="list_options_prev", help="Options list")
self.add_option('--bash-options-cur', action='store', dest="list_options_cur", help="Options list")
2013-11-19 12:47:46 +00:00
def error(self, msg):
pass
def exit(self, status=0, msg=None):
pass
def handle_request(self, parser):
options = self.parse_args()[0]
if options.list_switches:
opts = []
for option in parser.option_list:
if not "--bash" in option.get_opt_string():
opts.append(option.get_opt_string())
print ' '.join(opts)
exit(0)
2013-11-19 12:47:46 +00:00
if options.list_options_cur or options.list_options_prev:
cmdtank = ConsoleTank(DevNullOpts(), None)
cmdtank.core.load_configs(cmdtank.get_default_configs())
cmdtank.core.load_plugins()
2013-11-19 12:47:46 +00:00
opts = []
for option in cmdtank.core.get_available_options():
opts.append(cmdtank.core.SECTION + '.' + option + '=')
2013-11-19 12:47:46 +00:00
plugin_keys = cmdtank.core.config.get_options(cmdtank.core.SECTION, cmdtank.core.PLUGIN_PREFIX)
for (plugin_name, plugin_path) in plugin_keys:
opts.append(cmdtank.core.SECTION + '.' + cmdtank.core.PLUGIN_PREFIX + plugin_name + '=')
for plugin in cmdtank.core.plugins.values():
for option in plugin.get_available_options():
opts.append(plugin.SECTION + '.' + option + '=')
print ' '.join(sorted(opts))
exit(0)