Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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