class TestInfoFromReport(Resource): def __init__(self): self.report_service = ReportService() def get(self, platform, suite, test): result = [] reports = list( self.report_service.find_all_reports({'platform': platform, 'suits.name': suite, 'suits.tests.name': test})) test_info = self.report_service.get_test_info(suite, test) for report in reports: target_suite = [test_suite for test_suite in report['suits'] if test_suite['name'] == suite][0] target_test = [selected_test for selected_test in target_suite['tests'] if selected_test['name'] == test][0] new_item = { 'date': report['start_time'], 'status': target_test['result'], 'report_id': str(report['_id']) } if 'message' in target_test: new_item['message'] = target_test['message'] new_item['stack_trace'] = target_test['stack_trace'] result.append(new_item) result = sorted(result, reverse=True, key=itemgetter('date')) for item in result: item['date'] = item['date'].strftime("%Y-%m-%d %H:%M:%S").__str__() return result, 200
class FailedTests(Resource): def __init__(self): self.report_service = ReportService() def get(self): data = self.report_service.get_failed_tests_comparison() return json.loads(json.dumps(data, default=json_util.default)), 200
class ReportMonthlyResource (Resource): report_service = ReportService() @swag_from('../../spec/reports/monthly.yml') def get(self): try: req_data = self.report_service.monthly(request.args) if (len(req_data)) != 0: res_data = [[i[0] for i in req_data[0].items()]] + [list(i) for i in req_data] sheet = p.Sheet(res_data) output = make_response(sheet.csv) output.headers["Content-Disposition"] = "attachment; filename=export.csv" output.headers["Content-type"] = "text/csv" return output else: res_json = {'status': 1, 'message': 'No Data found in the specified range'} return jsonify(res_json) except Exception as e: print(e) if e.args: res_data = e.args[0] else: res_data = e res_json = {'status': 0, 'error': res_data} return jsonify(res_json)
def run(self): if self.state == TaskStatus.OK: parser = configparser.ConfigParser() parser.read(self.config_file) self.swift_dir = parser['SYSTEM']['SWIFT_Directory'] self.stma_software_dir = parser['SYSTEM']['STMA_Software_Directory'] self.dynust_dir = parser['SYSTEM']['DynusT_Software_Directory'] self.dynastudio_executable = parser['SYSTEM']['DynuStudio_Executable'] self.dynust_executable_name = parser['SYSTEM']['DynusT_Executable_Name'] self.common_dir = os.path.join(self.swift_dir, 'CommonData') self.threads = int(parser['SYSTEM']['Number_Threads']) self.scen_dir = os.path.join(self.swift_dir, 'Scenarios', self.scen) self.logger = ReportService(os.path.join(self.scen_dir, self.scen + '_STM_A.log')).get_logger() if not self.check_dir(self.scen_dir): self.logger.error('Scenario {:s} Not Found at'.format(self.scen_dir)) self.state = State.ERROR return self.state self.base_dir = os.path.join(self.swift_dir, 'Scenarios', self.base) if not os.path.exists(self.base_dir): self.logger.error('Baseline {:s} Not Found at'.format(self.base_dir)) self.state = State.ERROR return self.state if self.mode.upper() == 'FULL': self.mode = "FULL" else: self.mode = 'QUICK' self.logger.info('Scenario {:s} Execution Starts'.format(self.scen)) self.logger.info('') self.logger.info('TASK {:s}_{:s}_{:s}: START'.format(self.family, self.step_id, self.__class__.__name__)) self.logger.info('') self.logger.info('Scenario Name = {:s}'.format(self.scen)) self.logger.info('Baseline Name = {:s}'.format(self.base)) self.logger.info('Execution Mode = {:s}'.format(self.mode)) self.logger.info('Use Local Network = {:}'.format(self.local_network)) self.logger.info('Configuration File = {:s}'.format(self.config_file)) self.logger.info('Number of Threads = {:d}'.format(self.threads))
class ReportForDate(Resource): def __init__(self): self.report_service = ReportService() def get(self, platform, date): result = self.report_service.get_report_for_date(platform, date) if len(result) > 0: return json.loads(json.dumps(result[0], default=json_util.default)), 200 else: return {'message': 'Report was not found'}, 404
class Report(Resource): def __init__(self): self.report_service = ReportService() @jwt_required def get(self, report_id: str): try: result = self.report_service.find_report({'_id': ObjectId(report_id)}, {'suits': 0}) except InvalidId: return {'message': 'Invalid report id is provided.'}, 400 if result: return json.loads(json.dumps(result, default=json_util.default)), 200 return {'message': "Requested report was not found!"}, 404 @jwt_required def delete(self, report_id): count = self.report_service.delete(report_id) if count == 0: return {'message': 'report was not deleted'} return {'message': 'report was deleted'}, 200
class HistoricData(Resource): def __init__(self): self.report_service = ReportService() def get(self): try: result = self.report_service.get_historic_data() except InvalidId: return {'message': 'Invalid platform is provided.'}, 400 if result: return json.loads(json.dumps(result, default=json_util.default)), 200 return {'Message': "Requested platform was not found!"}, 404
def update_system_keys(self): """ Update the system keys :return: """ if self.state == State.ERROR: return self.state if 'TITLE' not in self.keys: self.keys['TITLE'] = Key('TITLE', input_value='') if self.keys['TITLE'].input_value: self.title = self.keys['TITLE'].input_value else: self.title = self.name self.keys['TITLE'].value = self.title if 'PROJECT_DIRECTORY' not in self.keys or self.keys[ 'PROJECT_DIRECTORY'].input_value is None: self.keys['PROJECT_DIRECTORY'] = Key('PROJECT_DIRECTORY') self.project_dir = self.keys['PROJECT_DIRECTORY'].value if 'REPORT_FILE' not in self.keys or not self.keys[ 'REPORT_FILE'].input_value: self.keys['REPORT_FILE'] = Key( 'REPORT_FILE', input_value=os.path.basename(self.control_file)[:-4] + '.prn') else: if self.keys['REPORT_FILE'].input_value.find('.prn') < 0: self.keys['REPORT_FILE'].value = self.keys[ 'REPORT_FILE'].input_value + ".prn" self.report_file = os.path.join(self.project_dir, self.keys['REPORT_FILE'].value) if 'RANDOM_SEED' not in self.keys or not self.keys[ 'RANDOM_SEED'].input_value: self.keys['RANDOM_SEED'] = Key('RANDOM_SEED') self.logger = ReportService(self.report_file).get_logger()
class Reports(Resource): def __init__(self): self.report_service = ReportService() def get(self, target: str): try: result = list( self.report_service.find_all_reports({'target': target}, {'suits': 0})) except InvalidId: return {'message': 'Invalid report id is provided.'}, 400 if result: return json.loads(json.dumps(result, default=json_util.default)), 200 return {'Message': "Requested report was not found!"}, 404
class HistoricDataPerPlatform(Resource): def __init__(self): self.report_service = ReportService() def get(self, platform): try: parse = reqparse.RequestParser() parse.add_argument('period', type=int) args = parse.parse_args() result = self.report_service.get_historic_data( platform, args['period']) except InvalidId: return {'message': 'Invalid platform is provided.'}, 400 if result: return json.loads(json.dumps(result, default=json_util.default)), 200 return {'Message': 'Selected time rage does not contain any data'}, 404
class ControlService(object): common_keys = ('TITLE', 'REPORT_FILE', 'PROJECT_DIRECTORY', 'RANDOM_SEED') required_keys = () optional_keys = () def __init__(self, name=None, input_control_file=None): self.name = name self.title = None self.exec_dir = os.getcwd() self.control_file = input_control_file self.control_files = [] self.keys = {} self.tokens = {} self.unused_keys = [] self.highest_group = 0 self.state = State.OK self.project_dir = None self.report_file = None self.logger = None self.seed = 42 @staticmethod def is_comment(line): return line.startswith("##") | line.startswith("//") @staticmethod def strip_comment(line): """ strip the comment :param line: a string with comments at the end. Comments symbols not at the beginning :return: """ loc_pound_sign = line.find("##") loc_slash_sign = line.find("//") if loc_pound_sign >= 0: return line[:loc_pound_sign] elif loc_slash_sign >= 0: return line[:loc_slash_sign] else: return line @staticmethod def split_key_value(line): """ Replace all tabs with 4 spaces :param line: :return: """ key_value_pair = re.split(r"[\s]{2,}", line.replace('\t', ' ')) if len(key_value_pair) > 1: return key_value_pair[0], key_value_pair[1] else: return key_value_pair[0], None def replace_tokens(self, s, token='%'): s = s.strip() substrs = [] token_count, prev = 0, 0 for i, ch in enumerate(s): if ch == token: token_count += 1 if token_count == 1: if i > 0: substrs.append(s[prev:i]) prev = i if token_count == 2: substrs.append(s[prev:i + 1]) token_count = 0 prev = i + 1 if i == len(s) - 1: substrs.append(s[prev:i + 1]) output = [s for s in substrs if s] if token == '%': for i, substr in enumerate(substrs): if fnmatch(substr, '%*%'): output[i] = os.environ.get(substr[1:-1], '').strip() if token == '@': for i, substr in enumerate(substrs): if fnmatch(substr, '@*@'): output[i] = self.tokens.get(substr, '') return ''.join(output) def read_control(self, control_file): control_file = control_file.strip() if self.state == State.ERROR: return self.state elif not os.path.exists(control_file): self.state = State.ERROR sys.stderr.write("Control file %s is not found\n" % control_file) return self.state else: with open(control_file, mode='r') as control: for line in control: line = line.strip() if line and not self.is_comment(line): key, value = self.split_key_value( self.strip_comment(line)) root, _ = self.get_root_key(key) if root not in KEYS_DATABASE: self.unused_keys.append(key) else: if value: key = key.upper() if key.startswith('CONTROL_KEY_FILE'): # for case: CONTROL_KEY_FILE @SOME_FILE_NAME@ if fnmatch(value, "*@*@*"): value = self.replace_tokens(value, token='@') elif fnmatch(value, "*%*%*"): value = self.replace_tokens(value, token='%') value = os.path.join( self.exec_dir, value ) # CONTROL_KEY_FILE relative to exec_dir self.read_control(value) elif fnmatch(key, "*@*@*"): self.tokens[ key] = value # Collect program tokens @*@ else: if fnmatch(value, "*%*%*"): value = self.replace_tokens(value, token='%') self.keys[key] = Key(key=key, input_value=value) if self.keys[ key].group > self.highest_group: self.highest_group = self.keys[ key].group else: self.keys[key] = Key(key=key) def update_system_keys(self): """ Update the system keys :return: """ if self.state == State.ERROR: return self.state if 'TITLE' not in self.keys: self.keys['TITLE'] = Key('TITLE', input_value='') if self.keys['TITLE'].input_value: self.title = self.keys['TITLE'].input_value else: self.title = self.name self.keys['TITLE'].value = self.title if 'PROJECT_DIRECTORY' not in self.keys or self.keys[ 'PROJECT_DIRECTORY'].input_value is None: self.keys['PROJECT_DIRECTORY'] = Key('PROJECT_DIRECTORY') self.project_dir = self.keys['PROJECT_DIRECTORY'].value if 'REPORT_FILE' not in self.keys or not self.keys[ 'REPORT_FILE'].input_value: self.keys['REPORT_FILE'] = Key( 'REPORT_FILE', input_value=os.path.basename(self.control_file)[:-4] + '.prn') else: if self.keys['REPORT_FILE'].input_value.find('.prn') < 0: self.keys['REPORT_FILE'].value = self.keys[ 'REPORT_FILE'].input_value + ".prn" self.report_file = os.path.join(self.project_dir, self.keys['REPORT_FILE'].value) if 'RANDOM_SEED' not in self.keys or not self.keys[ 'RANDOM_SEED'].input_value: self.keys['RANDOM_SEED'] = Key('RANDOM_SEED') self.logger = ReportService(self.report_file).get_logger() @staticmethod def parse_integer_list_key(value): """ Parse a range key :param value: a string like 1,2,4..5 :return: a list with all individual values """ res = [] if ',' in value: value_split = value.strip().split(',') for v in value_split: v_split = v.split('..') if len(v_split) == 1: res.append(int(v_split[0])) else: lb, hb = int(v_split[0]), int(v_split[1]) for k in range(lb, hb + 1): res.append(k) else: v_split = value.split('..') lb, hb = int(v_split[0]), int(v_split[1]) for k in range(lb, hb + 1): res.append(k) return res @staticmethod def parse_float_list_key(value): """ :param value: :return: """ res = [] if ',' in value: value_split = value.strip().split(',') res = [float(v) for v in value_split] else: res.append(float(value)) return res @staticmethod def parse_boolean_key(value): if str(value).upper().find('FALSE') >= 0 or str(value).upper() == '0': return False return True def parse_time_range(self, time_range): """ parse the time range :param time_range: parse comma-separated ranges like "0..6, 15..19" :type time_range: str :return: "0..6, 15..19" is parsed into [(0, 6), (15, 19)] """ if not isinstance(time_range, str): self.logger.error("Time range must be a string") return [(-1, -1)] parts = time_range.split(",") ranges = [] for part in parts: if part.find("..") < 0: self.logger.error( "Time range must have both start and end times. Input is %s" % part) return [(-1, -1)] else: start_time, end_time = part.split("..") ranges.append((float(start_time), float(end_time))) return ranges @staticmethod def get_root_key(key_name): """ get the root key name :param key_name: :type key_name: str :return: """ parts = key_name.split("_") if parts[-1].isdigit(): return key_name[:len(key_name) - len(parts[-1]) - 1], int( parts[-1]) else: return key_name, 0 @staticmethod def is_output_file(key_name): if key_name.find('NEW') >= 0 and key_name.find('_FILE') >= 0: return True return False def update_key_value(self, key): if self.state == State.ERROR: return self.state if key.input_value is not None: while key.input_value is not None and fnmatch( key.input_value, "@*@"): key.input_value = self.tokens.get(key.input_value) while key.input_value is not None and fnmatch( key.input_value, "%*%"): key.input_value = self.tokens.get(key.input_value[1:-1]) if key.value_type == KeyValueTypes.TIME_RANGE: val = self.parse_time_range(key.input_value) if val[0][0] >= 0: key.value = val else: self.state = State.ERROR elif key.value_type == KeyValueTypes.STRING: converter = str key.value = converter(key.input_value) elif key.value_type == KeyValueTypes.FLOAT: converter = float key.value = converter(key.input_value) elif key.value_type == KeyValueTypes.INTEGER: converter = int key.value = converter(key.input_value) elif key.value_type == KeyValueTypes.FILE: if key.order > Offset.NETWORK_KEYS_OFFSET and self.project_dir and key.input_value: key.value = os.path.join(self.project_dir, key.input_value) elif key.value_type == KeyValueTypes.INTEGER_LIST: key.value = self.parse_integer_list_key(key.input_value) elif key.value_type == KeyValueTypes.FLOAT_LIST: key.value = self.parse_float_list_key(key.input_value) elif key.value_type == KeyValueTypes.BOOLEAN: key.value = self.parse_boolean_key(key.input_value) def check_keys(self): """ Populate all keys and check required key; :return: The key dictionary has all the acceptable keys in fully suffixed notation; If the value is None, the key is not set by the user """ if self.state == State.ERROR: return self.state acceptable_keys = self.common_keys + self.required_keys + self.optional_keys single_keys = [ k for k in acceptable_keys if KEYS_DATABASE[k].group == KeyGroupTypes.SINGLE ] group_suffixes = [ "_" + str(g) for g in range(1, self.highest_group + 1) ] group_keys = [ k for k in acceptable_keys if KEYS_DATABASE[k].group == KeyGroupTypes.GROUP ] full_key_list = single_keys + [ k + s for k, s in itertools.product(group_keys, group_suffixes) ] for k in full_key_list: if k not in self.keys: root_key_name, key_group = self.get_root_key(k) key_value = KEYS_DATABASE[root_key_name].default if key_group > 1: # take the value of the first defined group if not specified; otherwise default value prev_group = key_group - 1 while root_key_name + "_" + str( prev_group) not in self.keys: prev_group -= 1 key_value = self.keys[root_key_name + "_" + str(prev_group)].input_value new_key = Key(key=k, input_value=key_value) self.keys.update({k: new_key}) # Update the key value to internal data structures for k in self.keys.values(): self.update_key_value(k) # Check required keys for each group. if a value is None, raise an error check_key = None # found = False for req_k in self.required_keys: found = False is_group_key = KEYS_DATABASE[req_k].group == KeyGroupTypes.GROUP if is_group_key: for g in group_suffixes: check_key = req_k + g for name, k in self.keys.items(): if check_key == name and k.input_value is not None and len( k.input_value) > 0: found = True break else: check_key = req_k for name, k in self.keys.items(): if check_key == name and k.value is not None: found = True break if not found: self.logger.error("Required key %s not found" % check_key) self.state = State.ERROR def check_files(self): if self.state == State.ERROR: return self.state for k in self.keys.values(): if k.key == 'PROJECT_DIRECTORY': if not os.path.exists(k.value): self.state = State.ERROR self.logger.error("Project Directory %s does not exist" % k.value) if k.value_type == KeyValueTypes.FILE: if self.is_output_file(k.key): if k.value and not os.path.exists(os.path.dirname( k.value)): self.state = State.ERROR self.logger.error("Path %s for %s does not exist" % (os.path.dirname(k.value), k.key)) elif k.key != 'REPORT_FILE': if k.value and not os.path.exists(k.value): self.state = State.ERROR self.logger.error("File %s for %s does not exist" % (k.value, k.key)) def print_keys(self): if self.state == State.ERROR: return self.state keys = [(k, v.value, v.order) for k, v in self.keys.items()] keys = sorted(keys, key=lambda k: k[2]) for k, v, _ in keys: self.logger.info("%s = %s" % (k, v)) for key in self.unused_keys: self.logger.warning("Unused key {:s}".format(key)) self.logger.info("") self.logger.info("") def execute(self): self.read_control(self.control_file) self.update_system_keys() self.check_keys() self.print_keys() self.check_files() return self.state
def __init__(self): self.report_service = ReportService()
class ReportUploader(Resource): def __init__(self): self.report_service = ReportService() def post(self): parse = reqparse.RequestParser() parse.add_argument('file', type=werkzeug.datastructures.FileStorage, location='files', required=True, help='Parameter file is required.') args = parse.parse_args() try: file_content = args['file'].stream.read().decode('utf-8') report_hash = hashlib.md5(file_content.encode('utf-8')).hexdigest() existing_report = self.report_service.find_report_by_hash( report_hash) if existing_report: return { "message": 'This report was already submitted on {0}'.format( existing_report['added_on']) }, 400 json_report = json.loads(file_content) platform = json_report['meta']['platform'] if platform == 'win' and ('win7' in json_report['meta']['config']): platform = 'win7' new_report = { 'hash': report_hash, 'client_ip': request.remote_addr, 'added_on': datetime.datetime.now(), 'start_time': datetime.datetime.utcfromtimestamp( json_report['meta']['start_time']), 'end_time': datetime.datetime.utcfromtimestamp( json_report['meta']['end_time']), 'platform': platform, 'target': json_report['meta']['params']['target'], 'errors': json_report['meta']['errors'], 'failed': json_report['meta']['failed'], 'passed': json_report['meta']['passed'], 'skipped': json_report['meta']['skipped'], 'total': json_report['meta']['total'], 'branch': json_report['meta']['iris_branch'], 'suits': [] } for suite in json_report['tests']['all_tests']: new_suite = {'name': suite['name'], 'tests': []} for test in suite['children']: new_test = { 'name': test['name'], 'description': test['description'], 'result': test['result'], 'time': test['time'] } if 'assert' in test: if 'message' in test['assert']: new_test['message'] = test['assert']['message'] if 'call_stack' in test['assert']: new_test['stack_trace'] = test['assert'][ 'call_stack'] new_suite['tests'].append(new_test) new_report['suits'].append(new_suite) report_id = self.report_service.create_report(new_report) return { 'message': 'The report is delivered.', 'report_id': report_id }, 201 except UnicodeDecodeError: return {'message': 'Unsupported report format detected.'}, 400 except JSONDecodeError: return {'message': 'Invalid JSON report format detected.'}, 400 except KeyError: return {'message': 'Invalid Iris report format detected.'}, 400
def main(base, scen, mode, local, config_file): logger = ReportService('default_log') start_time = time.time() wf = workflow(base, scen, mode, local, config_file) steps = list(wf.keys()) for name, step in wf.items(): step.execute() if name == 'ConfigureExecution': logger = step.logger logger.info('----------------STMA WorkFlow------------------') for i, s in enumerate(steps): logger.info("Step {:d} - {:30s}".format(i + 1, s)) end_time = time.time() execution_time = max((end_time - start_time) / 60.0, 0.0) model_status = [s.state for s in wf.values()] model_status = {s: st for s, st in zip(steps, model_status)} logger.info('----------------STMA Execution Summary------------------') stma_status = TaskStatus.OK stepid = 0 for step, status in model_status.items(): stepid += 1 logger.info('Step {:d} {:50s} Status = {:10s}'.format( stepid, step, str(status))) if status != TaskStatus.OK: stma_status = TaskStatus.FAIL if stma_status == TaskStatus.OK: logger.info('STMA Completed in {:.0f} minutes'.format(execution_time)) else: logger.error('STMA Failed in {:.0f} minutes'.format(execution_time)) return stma_status
class ConfigureExecution(Task): family = 'Setup' def __init__(self, base, scen, mode, config_file, local_network='FALSE', step_id='00'): super().__init__(step_id=step_id) self.base = base self.scen = scen self.mode = mode.upper() self.local_network = local_network if local_network.upper() == 'TRUE': self.local_network = True else: self.local_network = False self.config_file = config_file self.swift_dir = None self.stma_software_dir = None self.dynust_dir = None self.dynastudio_executable = None self.dynust_executable_name = None self.common_dir = None self.base_dir = None self.scen_dir = None self.config = None self.threads = 1 self.logger = None def check_dir(self, d, root_dir=None): if not os.path.exists(d): os.mkdir(d) if not os.path.exists(d): if root_dir is not None: self.logger.error('{:60s} Failed to Create'.format(os.path.relpath(d, root_dir))) else: self.logger.error('{:60s} Failed to Create'.format(d)) return False else: if root_dir is not None: self.logger.info('{:60s} Created'.format(os.path.relpath(d, root_dir))) else: self.logger.info('{:60s} Created'.format(d)) return True else: if root_dir is not None: self.logger.info('{:60s} Checked'.format(os.path.relpath(d, root_dir))) else: self.logger.info('{:60s} Checked'.format(d)) return True def require(self): current_dir = os.getcwd() if self.config_file.startswith('.'): self.config_file = os.path.join(current_dir, self.config_file) if not os.path.exists(self.config_file): self.state = State.ERROR sys.stderr('STM-A Configuration File {:s} Not Found - STM-A Failed'.format(self.config_file)) return self.state def run(self): if self.state == TaskStatus.OK: parser = configparser.ConfigParser() parser.read(self.config_file) self.swift_dir = parser['SYSTEM']['SWIFT_Directory'] self.stma_software_dir = parser['SYSTEM']['STMA_Software_Directory'] self.dynust_dir = parser['SYSTEM']['DynusT_Software_Directory'] self.dynastudio_executable = parser['SYSTEM']['DynuStudio_Executable'] self.dynust_executable_name = parser['SYSTEM']['DynusT_Executable_Name'] self.common_dir = os.path.join(self.swift_dir, 'CommonData') self.threads = int(parser['SYSTEM']['Number_Threads']) self.scen_dir = os.path.join(self.swift_dir, 'Scenarios', self.scen) self.logger = ReportService(os.path.join(self.scen_dir, self.scen + '_STM_A.log')).get_logger() if not self.check_dir(self.scen_dir): self.logger.error('Scenario {:s} Not Found at'.format(self.scen_dir)) self.state = State.ERROR return self.state self.base_dir = os.path.join(self.swift_dir, 'Scenarios', self.base) if not os.path.exists(self.base_dir): self.logger.error('Baseline {:s} Not Found at'.format(self.base_dir)) self.state = State.ERROR return self.state if self.mode.upper() == 'FULL': self.mode = "FULL" else: self.mode = 'QUICK' self.logger.info('Scenario {:s} Execution Starts'.format(self.scen)) self.logger.info('') self.logger.info('TASK {:s}_{:s}_{:s}: START'.format(self.family, self.step_id, self.__class__.__name__)) self.logger.info('') self.logger.info('Scenario Name = {:s}'.format(self.scen)) self.logger.info('Baseline Name = {:s}'.format(self.base)) self.logger.info('Execution Mode = {:s}'.format(self.mode)) self.logger.info('Use Local Network = {:}'.format(self.local_network)) self.logger.info('Configuration File = {:s}'.format(self.config_file)) self.logger.info('Number of Threads = {:d}'.format(self.threads)) def complete(self): message = 'TASK {:s}_{:s}_{:s}: STATUS = {:15s}'.format( self.family, self.step_id, self.__class__.__name__, str(self.state)) self.logger.info('') self.logger.info('') self.logger.info(message) self.logger.info('') self.logger.info('') def execute(self): self.require() self.run() self.complete() return self.state