yandex-tank/Tank/ConsoleWorker.py

323 lines
12 KiB
Python
Raw Normal View History

2012-09-23 11:53:38 +00:00
'''
Provides class to run TankCore from console environment
'''
2012-09-12 13:18:58 +00:00
from Tank.Core import TankCore
2012-09-13 10:45:15 +00:00
from Tank import Utils
2012-09-12 13:18:58 +00:00
import ConfigParser
import fnmatch
import logging
import os
import sys
import tempfile
import time
import traceback
2012-09-14 10:34:39 +00:00
from Tank.Plugins.ConsoleOnline import RealConsoleMarkup
2012-10-01 11:23:59 +00:00
import datetime
2012-09-12 13:18:58 +00:00
# TODO: 2 add system resources busy check
2012-09-12 13:18:58 +00:00
class ConsoleTank:
"""
Worker class that runs tank core accepting cmdline params
"""
MIGRATE_SECTION = 'migrate_old'
PID_OPTION = 'pid'
LOCK_DIR = '/var/lock'
old_options_mapping = {
'instances': ('phantom', ''),
'tank_type': ('phantom', ''),
'gatling_ip': ('phantom', ''),
'ssl': ('phantom', ''),
'address': ('phantom', ''),
'port': ('phantom', ''),
'writelog': ('phantom', ''),
'phantom_http_line': ('phantom', ''),
'phantom_http_field_num': ('phantom', ''),
'phantom_http_field': ('phantom', ''),
'phantom_http_entity': ('phantom', ''),
'load': ('phantom', 'rps_schedule'),
'instances_schedule': ('phantom', ''),
'ammofile': ('phantom', ''),
'loop': ('phantom', ''),
'autocases': ('phantom', ''),
'chosen_cases': ('phantom', ''),
'uri': ('phantom', 'uris'),
'header': ('phantom', 'headers'),
'time_periods': ('aggregator', ''),
'detailed_field': ('aggregator', ''),
'task': ('meta', ''),
'job_name': ('meta', ''),
'job_dsc': ('meta', ''),
'component': ('meta', ''),
'regress': ('meta', ''),
'ver': ('meta', ''),
2012-09-24 13:46:59 +00:00
'inform': ('meta', 'notify'),
2012-09-12 13:18:58 +00:00
'autostop': ('autostop', ''),
'monitoring_config': ('monitoring', 'config'),
'manual_start': ('tank', ''),
'http_base': ('meta', 'api_address')
}
def __init__(self, options, ammofile):
# @type tank Tank.Core.TankCore
self.core = TankCore()
self.options = options
self.ammofile = ammofile
2012-09-26 14:18:17 +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
def set_baseconfigs_dir(self, directory):
2012-09-23 11:53:38 +00:00
'''
Set directory where to read configs set
'''
2012-09-12 13:18:58 +00:00
self.baseconfigs_location = directory
def init_logging(self):
2012-09-23 11:53:38 +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
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)
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)
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)
console_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(name)s %(message)s"))
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)
console_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s: %(message)s", "%H:%M:%S"))
2012-09-12 13:18:58 +00:00
else:
2012-09-19 07:49:06 +00:00
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s: %(message)s", "%H:%M:%S"))
logger.addHandler(console_handler)
2012-09-12 13:18:58 +00:00
2012-09-23 11:53:38 +00:00
def __convert_old_multiline_options(self, old_lines):
opts = {}
option = None
2012-09-23 11:53:38 +00:00
res = ''
for line in old_lines:
try:
if line.strip() and line.strip()[0] == '#':
res += line
2012-09-23 11:53:38 +00:00
continue
option = line[:line.index('=')]
value = line[line.index('=') + 1:]
2012-09-23 11:53:38 +00:00
if option not in opts.keys():
opts[option] = []
opts[option].append(value.strip())
except Exception:
2012-09-23 12:29:20 +00:00
if option:
opts[option].append(line.strip())
else:
res += line.strip() + "\n"
2012-09-23 11:53:38 +00:00
for option, values in opts.iteritems():
res += option + '=' + "\n\t".join(values) + "\n"
return res
2012-09-23 12:29:20 +00:00
2012-09-23 11:53:38 +00:00
def __adapt_old_config(self, config):
2012-09-12 13:18:58 +00:00
test_parser = ConfigParser.ConfigParser()
try:
test_parser.read(config)
self.log.debug("Config passed ini format test: %s", config)
return config
2012-09-20 16:18:04 +00:00
except Exception:
2012-09-12 13:18:58 +00:00
self.log.warning("Config failed INI format test, consider upgrading it: %s", config)
2012-09-23 11:53:38 +00:00
file_handle, corrected_file = tempfile.mkstemp(".ini", "corrected_")
2012-09-12 13:18:58 +00:00
self.log.debug("Creating corrected INI config for it: %s", corrected_file)
2012-09-19 07:49:06 +00:00
os.write(file_handle, "[" + self.MIGRATE_SECTION + "]\n")
2012-09-23 11:53:38 +00:00
os.write(file_handle, self.__convert_old_multiline_options(open(config, 'r').readlines()))
2012-09-12 13:18:58 +00:00
return corrected_file
2012-09-23 11:53:38 +00:00
def __add_adapted_config(self, configs, conf_file):
conf_file = self.__adapt_old_config(conf_file)
2012-09-12 13:18:58 +00:00
configs += [conf_file]
self.core.add_artifact_file(conf_file, True)
2012-09-23 11:53:38 +00:00
def __override_config_from_cmdline(self):
2012-09-12 13:18:58 +00:00
# override config options from command line
if self.options.option:
for option_str in self.options.option:
2012-09-19 07:17:56 +00:00
try:
section = option_str[:option_str.index('.')]
option = option_str[option_str.index('.') + 1:option_str.index('=')]
except ValueError:
section = self.MIGRATE_SECTION
option = option_str[:option_str.index('=')]
2012-09-12 13:18:58 +00:00
value = option_str[option_str.index('=') + 1:]
self.log.debug("Override option: %s => [%s] %s=%s", option_str, section, option, value)
self.core.set_option(section, option, value)
2012-09-23 11:53:38 +00:00
def __there_is_locks(self):
2012-09-19 07:49:06 +00:00
retcode = False
2012-09-12 13:18:58 +00:00
for filename in os.listdir(self.LOCK_DIR):
if fnmatch.fnmatch(filename, 'lunapark_*.lock'):
full_name = self.LOCK_DIR + os.sep + filename
self.log.warn("Lock file present: %s", full_name)
try:
info = ConfigParser.ConfigParser()
info.read(full_name)
pid = info.get(TankCore.SECTION, self.PID_OPTION)
2012-09-13 10:45:15 +00:00
if not Utils.pid_exists(int(pid)):
2012-09-12 13:18:58 +00:00
self.log.debug("Lock PID %s not exists, ignoring and trying to remove", pid)
try:
os.remove(full_name)
2012-09-20 16:18:04 +00:00
except Exception, exc:
self.log.debug("Failed to delete lock %s: %s", full_name, exc)
2012-09-12 13:18:58 +00:00
else:
2012-09-19 07:49:06 +00:00
retcode = True
2012-09-20 16:18:04 +00:00
except Exception, exc:
self.log.warn("Failed to load info from lock %s: %s", full_name, exc)
2012-09-19 07:49:06 +00:00
retcode = True
return retcode
2012-09-12 13:18:58 +00:00
2012-09-23 11:53:38 +00:00
def __translate_old_options(self):
2012-09-12 13:18:58 +00:00
for old_option, value in self.core.config.get_options(self.MIGRATE_SECTION):
if old_option in self.old_options_mapping.keys():
new_sect = self.old_options_mapping[old_option][0]
new_opt = self.old_options_mapping[old_option][1] if self.old_options_mapping[old_option][1] else old_option
self.log.debug("Translating old option %s=%s into new: %s.%s", old_option, value, new_sect, new_opt)
self.core.set_option(new_sect, new_opt, value)
else:
self.log.warn("Unknown old option, please add it to translation mapping: %s=%s", old_option, value)
if self.core.config.config.has_section(self.MIGRATE_SECTION):
self.core.config.config.remove_section(self.MIGRATE_SECTION)
def configure(self):
2012-09-23 11:53:38 +00:00
'''
Make all console-specific preparations before running Tank
'''
2012-09-12 13:18:58 +00:00
if not self.options.ignore_lock:
2012-09-23 11:53:38 +00:00
while self.__there_is_locks():
2012-09-17 13:48:02 +00:00
if self.options.lock_fail:
raise RuntimeError("Lock file present, cannot continue")
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)
else:
self.log.warn("Lock files ignored. This is highly unrecommended practice!")
self.core.config.set_out_file(tempfile.mkstemp('.lock', 'lunapark_', self.LOCK_DIR)[1])
configs = []
2012-09-25 13:59:58 +00:00
if not self.options.no_rc:
try:
for filename in os.listdir(self.baseconfigs_location):
2012-09-26 14:18:17 +00:00
if fnmatch.fnmatch(filename, '*.ini'):
2012-09-25 13:59:58 +00:00
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')]
2012-09-12 13:18:58 +00:00
if not self.options.config:
2012-09-26 14:18:17 +00:00
# just for old 'lunapark' compatibility
2012-09-12 13:18:58 +00:00
self.log.debug("No config passed via cmdline, using ./load.conf")
2012-09-23 11:53:38 +00:00
conf_file = self.__adapt_old_config(os.path.realpath('load.conf'))
2012-09-12 13:18:58 +00:00
configs += [conf_file]
self.core.add_artifact_file(conf_file, True)
else:
for config_file in self.options.config:
2012-09-23 11:53:38 +00:00
self.__add_adapted_config(configs, config_file)
2012-09-12 13:18:58 +00:00
self.core.load_configs(configs)
self.core.set_option(TankCore.SECTION, self.PID_OPTION, os.getpid())
if self.ammofile:
self.log.debug("Ammofile: %s", self.ammofile)
self.core.set_option(self.MIGRATE_SECTION, 'ammofile', self.ammofile[0])
2012-09-23 11:53:38 +00:00
self.__translate_old_options()
self.__override_config_from_cmdline()
2012-09-12 13:18:58 +00:00
self.core.load_plugins()
2012-10-01 11:23:59 +00:00
if self.options.manual_start:
self.core.manual_start = self.options.manual_start
if self.options.scheduled_start:
try:
self.core.scheduled_start = datetime.datetime.strptime(self.options.scheduled_start, '%Y-%m-%d %H:%M:%S')
except ValueError:
self.core.scheduled_start = datetime.datetime.strptime(datetime.datetime.now().strftime('%Y-%m-%d ') + self.options.scheduled_start, '%Y-%m-%d %H:%M:%S')
2012-09-12 13:18:58 +00:00
2012-09-23 11:53:38 +00:00
def __graceful_shutdown(self):
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)
2012-09-12 13:18:58 +00:00
self.log.info("Done graceful shutdown.")
2012-09-19 07:49:06 +00:00
return retcode
2012-09-12 13:18:58 +00:00
def perform_test(self):
2012-09-23 11:53:38 +00:00
'''
Run the test sequence via Tank Core
'''
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()
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)
2012-09-12 13:18:58 +00:00
except KeyboardInterrupt as ex:
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
2012-09-12 13:18:58 +00:00
except Exception as ex:
self.log.info("Exception: %s", traceback.format_exc(ex))
2012-09-14 10:34:39 +00:00
sys.stdout.write(RealConsoleMarkup.RED)
2012-09-12 13:18:58 +00:00
self.log.error("%s", ex)
2012-09-14 10:34:39 +00:00
sys.stdout.write(RealConsoleMarkup.RESET)
2012-09-23 11:53:38 +00:00
retcode = self.__graceful_shutdown()
2012-09-12 13:18:58 +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