def check_child_exit_code():
                """Check if the child process terminated cleanly
                and raise an error otherwise."""
                child_exitcode, unused_child_rusage = self._wait_for_process(
                    child_pid, args[0])
                child_exitcode = util.ProcessExitCode.from_raw(child_exitcode)
                logging.debug(
                    "Parent: child process of RunExecutor with PID %d"
                    " terminated with %s.",
                    child_pid,
                    child_exitcode,
                )

                if child_exitcode:
                    if child_exitcode.value:
                        if child_exitcode.value == CHILD_OSERROR:
                            # This was an OSError in the child,
                            # details were already logged
                            raise BenchExecException(
                                "execution in container failed, check log for details"
                            )
                        elif child_exitcode.value == CHILD_UNKNOWN_ERROR:
                            raise BenchExecException(
                                "unexpected error in container")
                        raise OSError(child_exitcode.value,
                                      os.strerror(child_exitcode.value))
                    raise OSError(
                        0,
                        "Child process of RunExecutor terminated with " +
                        str(child_exitcode),
                    )
Esempio n. 2
0
def expected_results_of_file(filename):
    """Create a dict of property->ExpectedResult from information encoded in a filename."""
    results = {}
    for (filename_part, (expected_result,
                         for_properties)) in _FILE_RESULTS.items():
        if filename_part in filename:
            expected_result_class = get_result_classification(expected_result)
            assert expected_result_class in {
                RESULT_CLASS_TRUE, RESULT_CLASS_FALSE
            }
            expected_result = expected_result_class == RESULT_CLASS_TRUE
            subproperty = None
            if len(for_properties) > 1:
                assert for_properties == _MEMSAFETY_SUBPROPERTIES and expected_result
                prop = _PROP_MEMSAFETY
            else:
                prop = next(iter(for_properties))
                if prop in _MEMSAFETY_SUBPROPERTIES and not expected_result:
                    subproperty = prop
                    prop = _PROP_MEMSAFETY
            if prop in results:
                raise BenchExecException(
                    "Duplicate property {} in filename {}".format(
                        prop, filename))
            results[prop] = ExpectedResult(expected_result, subproperty)
    return results
Esempio n. 3
0
def _init_container_and_load_tool(tool_module, *args, **kwargs):
    """Initialize container for the current process and load given tool-info module."""
    try:
        _init_container(*args, **kwargs)
    except EnvironmentError as e:
        raise BenchExecException("Failed to configure container: " + str(e))
    return _load_tool(tool_module)
Esempio n. 4
0
    def get_java(self):
        candidates = [
            "java",
            "/usr/bin/java",
            "/opt/oracle-jdk-bin-1.8.0.202/bin/java",
            "/usr/lib/jvm/java-8-openjdk-amd64/bin/java",
        ]
        for c in candidates:
            candidate = self.which(c)
            if not candidate:
                continue
            try:
                process = subprocess.Popen(
                    [candidate, "-version"],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.STDOUT,
                )
                (stdout, stderr) = process.communicate()
            except OSError:
                continue

            stdout = util.decode_to_string(stdout).strip()
            if not stdout:
                continue
            if "1.8" in stdout:
                return candidate
        raise BenchExecException(
            "Could not find a suitable Java version: Need Java 1.8")
Esempio n. 5
0
def load_task_definition_file(task_def_file):
    """Open and parse a task-definition file in YAML format."""
    try:
        with open(task_def_file) as f:
            task_def = yaml.safe_load(f)
    except OSError as e:
        raise BenchExecException("Cannot open task-definition file: " + str(e))
    except yaml.YAMLError as e:
        raise BenchExecException("Invalid task definition: " + str(e))

    if str(task_def.get("format_version")) not in ["0.1", "1.0"]:
        raise BenchExecException(
            "Task-definition file {} specifies invalid format_version '{}'."
            .format(task_def_file, task_def.get("format_version")))

    return task_def
Esempio n. 6
0
    def extract_runs_from_xml(self, sourcefilesTagList, global_required_files_pattern):
        '''
        This function builds a list of SourcefileSets (containing filename with options).
        The files and their options are taken from the list of sourcefilesTags.
        '''
        base_dir = self.benchmark.base_dir
        # runs are structured as sourcefile sets, one set represents one sourcefiles tag
        blocks = []

        for index, sourcefilesTag in enumerate(sourcefilesTagList):
            sourcefileSetName = sourcefilesTag.get("name")
            matchName = sourcefileSetName or str(index)
            if self.benchmark.config.selected_sourcefile_sets \
                and not any(util.wildcard_match(matchName, sourcefile_set) for sourcefile_set in self.benchmark.config.selected_sourcefile_sets):
                    continue

            required_files_pattern = global_required_files_pattern.union(
                set(tag.text for tag in sourcefilesTag.findall('requiredfiles')))

            # get lists of filenames
            task_def_files = self.get_task_def_files_from_xml(sourcefilesTag, base_dir)

            # get file-specific options for filenames
            fileOptions = util.get_list_from_xml(sourcefilesTag)
            propertyfile = util.text_or_none(util.get_single_child_from_xml(sourcefilesTag, PROPERTY_TAG))

            # some runs need more than one sourcefile,
            # the first sourcefile is a normal 'include'-file, we use its name as identifier
            # for logfile and result-category all other files are 'append'ed.
            appendFileTags = sourcefilesTag.findall("append")

            currentRuns = []
            for identifier in task_def_files:
                if identifier.endswith('.yml'):
                    if appendFileTags:
                        raise BenchExecException(
                            "Cannot combine <append> and task-definition files in the same <tasks> tag.")
                    run = self.create_run_from_task_definition(
                        identifier, fileOptions, propertyfile, required_files_pattern)
                else:
                    run = self.create_run_for_input_file(
                        identifier, fileOptions, propertyfile, required_files_pattern, appendFileTags)
                if run:
                    currentRuns.append(run)

            # add runs for cases without source files
            for run in sourcefilesTag.findall("withoutfile"):
                currentRuns.append(Run(run.text, [], fileOptions, self, propertyfile, required_files_pattern))

            blocks.append(SourcefileSet(sourcefileSetName, index, currentRuns))

        if self.benchmark.config.selected_sourcefile_sets:
            for selected in self.benchmark.config.selected_sourcefile_sets:
                if not any(util.wildcard_match(sourcefile_set.real_name, selected) for sourcefile_set in blocks):
                    logging.warning(
                        'The selected tasks "%s" are not present in the input file, '
                        'skipping them.',
                        selected)
        return blocks
Esempio n. 7
0
    def create(cls, propertyfile, allow_unknown):
        """
        Create a Property instance by attempting to parse the given property file.
        @param propertyfile: A file name of a property file
        @param allow_unknown: Whether to accept unknown properties
        """
        with open(propertyfile) as f:
            content = f.read().strip()

        # parse content for known properties
        is_svcomp = False
        known_properties = []
        only_known_svcomp_property = True

        if content == "OBSERVER AUTOMATON" or content == "SATISFIABLE":
            known_properties = [_PROPERTY_NAMES[content]]

        elif content.startswith("CHECK"):
            is_svcomp = True
            for line in filter(None, content.splitlines()):
                if content.startswith("CHECK"):
                    # SV-COMP property, either a well-known one or a new one
                    props_in_line = [
                        prop for (substring, prop) in _PROPERTY_NAMES.items()
                        if substring in line
                    ]
                    if len(props_in_line) == 1:
                        known_properties.append(props_in_line[0])
                    else:
                        only_known_svcomp_property = False
                else:
                    # not actually an SV-COMP property file
                    is_svcomp = False
                    known_properties = []
                    break

        # check if some known property content was found
        subproperties = None
        if only_known_svcomp_property and len(known_properties) == 1:
            is_well_known = True
            name = known_properties[0]

        elif (only_known_svcomp_property
              and set(known_properties) == _MEMSAFETY_SUBPROPERTIES):
            is_well_known = True
            name = _PROP_MEMSAFETY
            subproperties = list(known_properties)

        else:
            if not allow_unknown:
                raise BenchExecException(
                    'File "{0}" does not contain a known property.'.format(
                        propertyfile))
            is_well_known = False
            name = os.path.splitext(os.path.basename(propertyfile))[0]

        return cls(propertyfile, is_well_known, is_svcomp, name, subproperties)
def getAWSInput(benchmark):
    (
        requirements,
        number_of_runs,
        limits_and_num_runs,
        run_definitions,
        source_files,
    ) = getBenchmarkData(benchmark)
    (working_dir, toolpaths) = getToolData(benchmark)

    abs_working_dir = os.path.abspath(working_dir)
    abs_tool_paths = list(map(os.path.abspath, toolpaths))
    abs_source_files = list(map(os.path.abspath, source_files))
    abs_base_dir = benchexec.util.common_base_dir(abs_source_files +
                                                  abs_tool_paths)

    if abs_base_dir == "":
        raise BenchExecException("No common base dir found.")

    toolpaths = {
        "absBaseDir": abs_base_dir,
        "workingDir": working_dir,
        "absWorkingDir": abs_working_dir,
        "toolpaths": toolpaths,
        "absToolpaths": abs_tool_paths,
        "sourceFiles": source_files,
        "absSourceFiles": abs_source_files,
    }

    aws_input = {
        "requirements": requirements,
        "workingDir": os.path.relpath(abs_working_dir, abs_base_dir),
    }
    if benchmark.result_files_patterns:
        if len(benchmark.result_files_patterns) > 1:
            raise BenchExecException(
                "Multiple result-file patterns not supported in cloud mode.")
        aws_input.update(
            {"resultFilePatterns": benchmark.result_files_patterns[0]})

    aws_input.update({"limitsAndNumRuns": limits_and_num_runs})
    aws_input.update({"runDefinitions": run_definitions})

    return (toolpaths, aws_input)
Esempio n. 9
0
def getToolData(benchmark):

    working_dir = benchmark.working_directory()
    if not os.path.isdir(working_dir):
        raise BenchExecException(
            f"Missing working directory '{working_dir}', cannot run tool.")
    logging.debug("Working dir: %s", working_dir)

    toolpaths = benchmark.required_files()
    valid_toolpaths = set()
    for file in toolpaths:
        if not os.path.exists(file):
            raise BenchExecException(
                f"Missing file '{os.path.normpath(file)}', "
                f"not running benchmark without it.")
        for glob in benchexec.util.expand_filename_pattern(file, working_dir):
            valid_toolpaths.add(glob)

    return (working_dir, valid_toolpaths)
Esempio n. 10
0
def _call_tool_func(name, args, kwargs):
    """Call a method on the tool instance.
    @param name: The method name to call.
    @param args: List of arguments to be passed as positional arguments.
    @param kwargs: Dict of arguments to be passed as keyword arguments.
    """
    global tool
    try:
        return getattr(tool, name)(*args, **kwargs)
    except SystemExit as e:
        # SystemExit would terminate the worker process instead of being propagated.
        raise BenchExecException(str(e.code))
Esempio n. 11
0
def get_propertytag(parent):
    tag = util.get_single_child_from_xml(parent, "propertyfile")
    if tag is None:
        return None
    expected_verdict = tag.get("expectedverdict")
    if (expected_verdict is not None and expected_verdict
            not in _EXPECTED_RESULT_FILTER_VALUES.values()
            and not re.match("false(.*)", expected_verdict)):
        raise BenchExecException(
            "Invalid value '{}' for expectedverdict of <propertyfile> in tag <{}>: "
            "Only 'true', 'false', 'false(<subproperty>)' and 'unknown' "
            "are allowed!".format(expected_verdict, parent.tag))
    return tag
Esempio n. 12
0
 def expand_patterns_from_tag(tag):
     result = []
     patterns = task_def.get(tag, [])
     if isinstance(patterns, str) or not isinstance(patterns, collections.Iterable):
         # accept single string in addition to list of strings
         patterns = [patterns]
     for pattern in patterns:
         expanded = util.expand_filename_pattern(
             str(pattern), os.path.dirname(task_def_file))
         if not expanded:
             raise BenchExecException(
                 "Pattern '{}' in task-definition file {} did not match any paths."
                 .format(pattern, task_def_file))
         expanded.sort()
         result.extend(expanded)
     return result
def _createArchiveFile(archive_path, abs_base_dir, abs_paths):
    with zipfile.ZipFile(archive_path, "w", zipfile.ZIP_DEFLATED) as zipf:
        for file in abs_paths:
            if not os.path.exists(file):
                zipf.close()
                if os.path.isfile(archive_path):
                    os.remove(archive_path)

                raise BenchExecException(
                    "Missing file '{0}', cannot run benchmark without it.".
                    format(os.path.normpath(file)))

            if os.path.isdir(file):
                _zipdir(file, zipf, abs_base_dir)
            else:
                zipf.write(file, os.path.relpath(file, abs_base_dir))
Esempio n. 14
0
def _get_cgroup_version():
    version = None
    try:
        with open("/proc/mounts") as mountsFile:
            for mount in mountsFile:
                mount = mount.split(" ")
                if mount[2] == "cgroup":
                    version = CGROUPS_V1

                # only set v2 if it's the only active mount
                # we don't support crippled hybrid mode
                elif mount[2] == "cgroup2" and version != CGROUPS_V1:
                    version = CGROUPS_V2

            if version is None:
                raise BenchExecException("Could not detect Cgroup Version")
    except OSError:
        logging.exception("Cannot read /proc/mounts")

    return version
Esempio n. 15
0
    def _ultimate_version(self, executable):
        data_dir = os.path.join(os.path.dirname(executable), "data")
        launcher_jar = self._get_current_launcher_jar(executable)

        cmds = [
            # 2
            [
                self.get_java(),
                "-Xss4m",
                "-jar",
                launcher_jar,
                "-data",
                "@noDefault",
                "-ultimatedata",
                data_dir,
                "--version",
            ],
            # 1
            [
                self.get_java(),
                "-Xss4m",
                "-jar",
                launcher_jar,
                "-data",
                data_dir,
                "--version",
            ],
        ]

        self.api = len(cmds)
        for cmd in cmds:
            version = self._query_ultimate_version(cmd, self.api)
            if version != "":
                return version
            self.api = self.api - 1
        raise BenchExecException("Could not determine Ultimate version")
Esempio n. 16
0
    def _start_execution_in_container(self, args, stdin, stdout, stderr, env,
                                      root_dir, cwd, temp_dir, cgroups,
                                      output_dir, result_files_patterns,
                                      parent_setup_fn, child_setup_fn,
                                      parent_cleanup_fn):
        """Execute the given command and measure its resource usage similarly to super()._start_execution(),
        but inside a container implemented using Linux namespaces.
        The command has no network access (only loopback),
        a fresh directory as /tmp and no write access outside of this,
        and it does not see other processes except itself.
        """
        assert self._use_namespaces

        env.update(self._env_override)

        args = self._build_cmdline(args, env=env)

        # We have three processes involved:
        # parent: the current Python process in which RunExecutor is executing
        # child: child process in new namespace (PID 1 in inner namespace),
        #        configures inner namespace, serves as dummy init,
        #        collects result of grandchild and passes it to parent
        # grandchild: child of child process (PID 2 in inner namespace), exec()s tool

        # We need the following communication steps between these proceses:
        # 1a) grandchild tells parent its PID (in outer namespace).
        # 1b) grandchild tells parent that it is ready and measurement should begin.
        # 2) parent tells grandchild that measurement has begun and tool should
        #    be exec()ed.
        # 3) child tells parent about return value and resource consumption of grandchild.
        # 1a and 1b are done together by sending the PID through a pipe.
        # 2 is done by sending a null byte through a pipe.
        # 3 is done by sending a pickled object through the same pipe as #2.
        # We cannot use the same pipe for both directions, because otherwise a sender might
        # read the bytes it has sent itself.

        # Error codes from child to parent
        CHILD_OSERROR = 128
        CHILD_UNKNOWN_ERROR = 129

        from_parent, to_grandchild = os.pipe(
        )  # "downstream" pipe parent->grandchild
        from_grandchild, to_parent = os.pipe(
        )  # "upstream" pipe grandchild/child->parent

        # If the current directory is within one of the bind mounts we create,
        # we need to cd into this directory again, otherwise we would not see the bind mount,
        # but the directory behind it. Thus we always set cwd to force a change of directory.
        if root_dir is None:
            cwd = os.path.abspath(cwd or os.curdir)
        else:
            root_dir = os.path.abspath(root_dir)
            cwd = os.path.abspath(cwd)

        def grandchild():
            """Setup everything inside the process that finally exec()s the tool."""
            try:
                # We know that this process has PID 2 in the inner namespace,
                # but we actually need to know its PID in the outer namespace
                # such that parent can put us into the correct cgroups.
                # According to http://man7.org/linux/man-pages/man7/pid_namespaces.7.html,
                # there are two ways to achieve this: sending a message with the PID
                # via a socket (but Python < 3.3 lacks a convenient API for sendmsg),
                # and reading /proc/self in the outer procfs instance (that's what we do).
                my_outer_pid = container.get_my_pid_from_procfs()

                container.mount_proc()
                container.drop_capabilities()
                container.reset_signal_handling()
                child_setup_fn()  # Do some other setup the caller wants.

                # Signal readiness to parent by sending our PID and wait until parent is also ready
                os.write(to_parent, str(my_outer_pid).encode())
                received = os.read(from_parent, 1)
                assert received == b'\0', received
            finally:
                # close remaining ends of pipe
                os.close(from_parent)
                os.close(to_parent)
            # here Python will exec() the tool for us

        def child():
            """Setup everything inside the container, start the tool, and wait for result."""
            try:
                logging.debug(
                    "Child: child process of RunExecutor with PID %d started",
                    container.get_my_pid_from_procfs())

                # Put all received signals on hold until we handle them later.
                container.block_all_signals()

                # We want to avoid leaking file descriptors to the executed child.
                # It is also nice if the child has only the minimal necessary file descriptors,
                # to avoid keeping other pipes and files open, e.g., those that the parent
                # uses to communicate with other containers (if containers are started in parallel).
                # Thus we do not use the close_fds feature of subprocess.Popen,
                # but do the same here manually.
                # We keep the relevant ends of our pipes, and stdin/out/err of child and grandchild.
                necessary_fds = {
                    sys.stdin, sys.stdout, sys.stderr, to_parent, from_parent,
                    stdin, stdout, stderr
                } - {None}
                container.close_open_fds(keep_files=necessary_fds)

                try:
                    if not self._allow_network:
                        container.activate_network_interface("lo")

                    if root_dir is not None:
                        self._setup_root_filesystem(root_dir)
                    else:
                        self._setup_container_filesystem(temp_dir)
                except EnvironmentError as e:
                    logging.critical("Failed to configure container: %s", e)
                    return CHILD_OSERROR

                try:
                    os.chdir(cwd)
                except EnvironmentError as e:
                    logging.critical(
                        "Cannot change into working directory inside container: %s",
                        e)
                    return CHILD_OSERROR

                try:
                    grandchild_proc = subprocess.Popen(args,
                                                       stdin=stdin,
                                                       stdout=stdout,
                                                       stderr=stderr,
                                                       env=env,
                                                       close_fds=False,
                                                       preexec_fn=grandchild)
                except (EnvironmentError, RuntimeError) as e:
                    logging.critical("Cannot start process: %s", e)
                    return CHILD_OSERROR

                container.drop_capabilities()

                # Close other fds that were still necessary above.
                container.close_open_fds(
                    keep_files={sys.stdout, sys.stderr, to_parent})

                # Set up signal handlers to forward signals to grandchild
                # (because we are PID 1, there is a special signal handling otherwise).
                # cf. dumb-init project: https://github.com/Yelp/dumb-init
                # Also wait for grandchild and return its result.
                if _HAS_SIGWAIT:
                    grandchild_result = container.wait_for_child_and_forward_all_signals(
                        grandchild_proc.pid, args[0])
                else:
                    container.forward_all_signals_async(
                        grandchild_proc.pid, args[0])
                    grandchild_result = self._wait_for_process(
                        grandchild_proc.pid, args[0])

                logging.debug(
                    "Child: process %s terminated with exit code %d.", args[0],
                    grandchild_result[0])
                os.write(to_parent, pickle.dumps(grandchild_result))
                os.close(to_parent)

                return 0
            except EnvironmentError as e:
                logging.exception("Error in child process of RunExecutor")
                return CHILD_OSERROR
            except:
                # Need to catch everything because this method always needs to return a int
                # (we are inside a C callback that requires returning int).
                logging.exception("Error in child process of RunExecutor")
                return CHILD_UNKNOWN_ERROR

        try:  # parent
            try:
                child_pid = container.execute_in_namespace(
                    child, use_network_ns=not self._allow_network)
            except OSError as e:
                raise BenchExecException(
                    "Creating namespace for container mode failed: " +
                    os.strerror(e.errno))
            logging.debug(
                "Parent: child process of RunExecutor with PID %d started.",
                child_pid)

            def check_child_exit_code():
                """Check if the child process terminated cleanly and raise an error otherwise."""
                child_exitcode, unused_child_rusage = self._wait_for_process(
                    child_pid, args[0])
                child_exitcode = util.ProcessExitCode.from_raw(child_exitcode)
                logging.debug(
                    "Parent: child process of RunExecutor with PID %d terminated with %s.",
                    child_pid, child_exitcode)

                if child_exitcode:
                    if child_exitcode.value:
                        if child_exitcode.value == CHILD_OSERROR:
                            # This was an OSError in the child, details were already logged
                            raise BenchExecException(
                                "execution in container failed, check log for details"
                            )
                        elif child_exitcode.value == CHILD_UNKNOWN_ERROR:
                            raise BenchExecException(
                                "unexpected error in container")
                        raise OSError(child_exitcode.value,
                                      os.strerror(child_exitcode.value))
                    raise OSError(
                        0, "Child process of RunExecutor terminated with " +
                        str(child_exitcode))

            # Close unnecessary ends of pipes such that read() does not block forever
            # if all other processes have terminated.
            os.close(from_parent)
            os.close(to_parent)

            container.setup_user_mapping(child_pid,
                                         uid=self._uid,
                                         gid=self._gid)

            try:
                grandchild_pid = int(os.read(
                    from_grandchild, 10))  # 10 bytes is enough for 32bit int
            except ValueError:
                # probably empty read, i.e., pipe closed, i.e., child or grandchild failed
                check_child_exit_code()
                assert False, "Child process of RunExecutor terminated cleanly but did not send expected data."

            logging.debug(
                "Parent: executing %s in grand child with PID %d via child with PID %d.",
                args[0], grandchild_pid, child_pid)

            # start measurements
            cgroups.add_task(grandchild_pid)
            parent_setup = parent_setup_fn()

            # Signal grandchild that setup is finished
            os.write(to_grandchild, b'\0')

            # Copy file descriptor, otherwise we could not close from_grandchild in finally block
            # and would leak a file descriptor in case of exception.
            from_grandchild_copy = os.dup(from_grandchild)
        finally:
            os.close(from_grandchild)
            os.close(to_grandchild)

        def wait_for_grandchild():
            # 1024 bytes ought to be enough for everyone^Wour pickled result
            try:
                received = os.read(from_grandchild_copy, 1024)
            except OSError as e:
                if self.PROCESS_KILLED and e.errno == errno.EINTR:
                    # Read was interrupted because of Ctrl+C, we just try again
                    received = os.read(from_grandchild_copy, 1024)
                else:
                    raise e

            parent_cleanup = parent_cleanup_fn(parent_setup)

            os.close(from_grandchild_copy)
            check_child_exit_code()

            if result_files_patterns:
                self._transfer_output_files(temp_dir, cwd, output_dir,
                                            result_files_patterns)

            exitcode, ru_child = pickle.loads(received)
            return exitcode, ru_child, parent_cleanup

        return grandchild_pid, wait_for_grandchild
Esempio n. 17
0
    def create_run_from_task_definition(
            self, task_def_file, options, propertyfile, required_files_pattern):
        """Create a Run from a task definition in yaml format"""
        task_def = load_task_definition_file(task_def_file)

        def expand_patterns_from_tag(tag):
            result = []
            patterns = task_def.get(tag, [])
            if isinstance(patterns, str) or not isinstance(patterns, collections.Iterable):
                # accept single string in addition to list of strings
                patterns = [patterns]
            for pattern in patterns:
                expanded = util.expand_filename_pattern(
                    str(pattern), os.path.dirname(task_def_file))
                if not expanded:
                    raise BenchExecException(
                        "Pattern '{}' in task-definition file {} did not match any paths."
                        .format(pattern, task_def_file))
                expanded.sort()
                result.extend(expanded)
            return result

        input_files = expand_patterns_from_tag("input_files")
        if not input_files:
            raise BenchExecException(
                "Task-definition file {} does not define any input files.".format(task_def_file))
        required_files = expand_patterns_from_tag("required_files")

        run = Run(
            task_def_file,
            input_files,
            options,
            self,
            propertyfile,
            required_files_pattern,
            required_files)

        # run.propertyfile of Run is fully determined only after Run is created,
        # thus we handle it and the expected results here.
        if not run.propertyfile:
            return run

        # TODO: support "property_name" attribute in yaml
        prop = result.Property.create(run.propertyfile, allow_unknown=True)
        run.properties = [prop]

        for prop_dict in task_def.get("properties", []):
            if not isinstance(prop_dict, dict) or "property_file" not in prop_dict:
                raise BenchExecException(
                    "Missing property file for property in task-definition file {}."
                    .format(task_def_file))
            expanded = util.expand_filename_pattern(
                prop_dict["property_file"], os.path.dirname(task_def_file))
            if len(expanded) != 1:
                raise BenchExecException(
                    "Property pattern '{}' in task-definition file {} does not refer to exactly one file."
                    .format(prop_dict["property_file"], task_def_file))

            # TODO We could reduce I/O by checking absolute paths and using os.path.samestat
            # with cached stat calls.
            if prop.filename == expanded[0] or os.path.samefile(prop.filename, expanded[0]):
                expected_result = prop_dict.get("expected_verdict")
                if expected_result is not None and not isinstance(expected_result, bool):
                    raise BenchExecException(
                        "Invalid expected result '{}' for property {} in task-definition file {}."
                        .format(expected_result, prop_dict["property_file"], task_def_file))
                run.expected_results[prop.filename] = \
                    result.ExpectedResult(expected_result, prop_dict.get("subproperty"))

        if not run.expected_results:
            logging.debug(
                "Ignoring run '%s' because it does not have the property from %s.",
                run.identifier, run.propertyfile)
            return None
        elif len(run.expected_results) > 1:
            raise BenchExecException(
                "Property '{}' specified multiple times in task-definition file {}."
                .format(prop.filename, task_def_file))
        else:
            return run
Esempio n. 18
0
    def init(self, config, benchmark):
        """
        This functions will set up the docker network to execute the test.
        As a result, it needs root permission for the setup part.
        """

        tool_locator = tooladapter.create_tool_locator(config)
        benchmark.executable = benchmark.tool.executable(tool_locator)
        benchmark.tool_version = benchmark.tool.version(benchmark.executable)

        # Read test inputs paths
        (
            self.switch_source_path,
            self.ptf_folder_path,
            self.network_config_path,
        ) = self.read_folder_paths(benchmark)

        if not os.path.isdir(self.switch_source_path):
            logging.critical(
                "Switch folder path not found: %s, {self.switch_source_path}"
            )
            raise BenchExecException(
                "Switch folder path not found. Look over setup definition"
            )
        if not os.path.isdir(self.ptf_folder_path):
            logging.critical(
                "Ptf test folder path not found: %s, {self.ptf_folder_path}"
            )
            raise (
                BenchExecException(
                    f"Ptf test folder path not found: {self.ptf_folder_path}"
                )
            )

        if not self.switch_source_path or not self.ptf_folder_path:
            raise BenchExecException(
                "Switch or Ptf folder path not defined."
                f"Switch path: {self.switch_source_path} Folder path: {self.ptf_folder_path}"
            )

        # Extract network config info
        if not self.network_config_path:
            logging.error("No network config file was defined")
            raise BenchExecException("No network config file was defined")

        with open(self.network_config_path) as json_file:
            self.network_config = json.load(json_file)

        setup_is_valid = self.network_file_isValid()

        if not setup_is_valid:
            raise BenchExecException("Network config file is not valid")

        # Container setup
        self.client = docker.from_env()
        self.switch_target_path = "/app"
        self.nrOfNodes = len(self.network_config["nodes"])

        try:
            # Create the ptf tester container
            mount_ptf_tester = docker.types.Mount(
                "/app", self.ptf_folder_path, type="bind"
            )
            try:
                self.ptf_tester = self.client.containers.create(
                    PTF_IMAGE_NAME,
                    detach=True,
                    name="ptfTester",
                    mounts=[mount_ptf_tester],
                    tty=True,
                )
            except docker.errors.APIError:
                self.ptf_tester = self.client.containers.get("ptfTester")

            # Create node containers
            self.nodes = []

            for node_name in self.network_config["nodes"]:
                try:
                    self.nodes.append(
                        self.client.containers.create(
                            NODE_IMAGE_NAME, detach=True, name=node_name
                        )
                    )
                except docker.errors.APIError:
                    logging.error("Failed to setup node container.")

            self.switches = []

            # Each switch needs their own mount copy
            for switch_info in self.network_config["switches"]:
                mount_path = self.create_switch_mount_copy(switch_info)
                mount_switch = docker.types.Mount(
                    self.switch_target_path, mount_path, type="bind"
                )

                try:
                    self.switches.append(
                        self.client.containers.create(
                            SWITCH_IMAGE_NAME,
                            detach=True,
                            name=switch_info,
                            mounts=[mount_switch],
                        )
                    )
                except docker.errors.APIError:
                    self.switches.append(self.client.containers.get(switch_info))

            logging.info("Setting up network")
            self.setup_network()
            self.connect_nodes_to_switch()

        except docker.errors.APIError as e:
            self.close()
            raise BenchExecException(str(e))
    def _start_execution_in_container(
        self,
        args,
        stdin,
        stdout,
        stderr,
        env,
        root_dir,
        cwd,
        temp_dir,
        memlimit,
        memory_nodes,
        cgroups,
        output_dir,
        result_files_patterns,
        parent_setup_fn,
        child_setup_fn,
        parent_cleanup_fn,
    ):
        """Execute the given command and measure its resource usage similarly to
        super()._start_execution(), but inside a container implemented using Linux
        namespaces.  The command has no network access (only loopback),
        a fresh directory as /tmp and no write access outside of this,
        and it does not see other processes except itself.
        """
        assert self._use_namespaces

        if root_dir is None:
            env.update(self._env_override)

        # We have three processes involved:
        # parent: the current Python process in which RunExecutor is executing
        # child: child process in new namespace (PID 1 in inner namespace),
        #        configures inner namespace, serves as dummy init,
        #        collects result of grandchild and passes it to parent
        # grandchild: child of child process (PID 2 in inner namespace), exec()s tool

        # We need the following communication steps between these proceses:
        # 1a) grandchild tells parent its PID (in outer namespace).
        # 1b) grandchild tells parent that it is ready and measurement should begin.
        # 2) parent tells grandchild that measurement has begun and tool should
        #    be exec()ed.
        # 3) child tells parent about return value and resource consumption of
        #    grandchild.
        # 1a and 1b are done together by sending the PID through a pipe.
        # 2 is done by sending a null byte through a pipe.
        # 3 is done by sending a pickled object through the same pipe as #2.
        # We cannot use the same pipe for both directions, because otherwise a sender
        # might read the bytes it has sent itself.

        # Error codes from child to parent
        CHILD_OSERROR = 128  # noqa: N806 local constant
        CHILD_UNKNOWN_ERROR = 129  # noqa: N806 local constant

        # "downstream" pipe parent->grandchild
        from_parent, to_grandchild = os.pipe()
        # "upstream" pipe grandchild/child->parent
        from_grandchild, to_parent = os.pipe()

        # The protocol for these pipes is that first the parent sends the marker for
        # user mappings, then the grand child sends its outer PID back,
        # and finally the parent sends its completion marker.
        # After the run, the child sends the result of the grand child and then waits
        # for the post_run marker, before it terminates.
        MARKER_USER_MAPPING_COMPLETED = b"A"  # noqa: N806 local constant
        MARKER_PARENT_COMPLETED = b"B"  # noqa: N806 local constant
        MARKER_PARENT_POST_RUN_COMPLETED = b"C"  # noqa: N806 local constant

        # If the current directory is within one of the bind mounts we create,
        # we need to cd into this directory again, otherwise we would not see the
        # bind mount, but the directory behind it.
        # Thus we always set cwd to force a change of directory.
        if root_dir is None:
            cwd = os.path.abspath(cwd or os.curdir)
        else:
            root_dir = os.path.abspath(root_dir)
            cwd = os.path.abspath(cwd)

        def grandchild():
            """Setup everything inside the process that finally exec()s the tool."""
            try:
                # We know that this process has PID 2 in the inner namespace,
                # but we actually need to know its PID in the outer namespace
                # such that parent can put us into the correct cgroups.  According to
                # http://man7.org/linux/man-pages/man7/pid_namespaces.7.html,
                # there are two ways to achieve this: sending a message with the PID
                # via a socket (but Python 2 lacks a convenient API for sendmsg),
                # and reading /proc/self in the outer procfs instance
                # (that's what we do).
                my_outer_pid = container.get_my_pid_from_procfs()

                container.mount_proc(self._container_system_config)
                container.drop_capabilities()
                container.reset_signal_handling()
                child_setup_fn()  # Do some other setup the caller wants.

                # Signal readiness to parent by sending our PID
                # and wait until parent is also ready
                os.write(to_parent, str(my_outer_pid).encode())
                received = os.read(from_parent, 1)
                assert received == MARKER_PARENT_COMPLETED, received
            finally:
                # close remaining ends of pipe
                os.close(from_parent)
                os.close(to_parent)
            # here Python will exec() the tool for us

        def child():
            """Setup everything inside the container,
            start the tool, and wait for result."""
            try:
                logging.debug(
                    "Child: child process of RunExecutor with PID %d started",
                    container.get_my_pid_from_procfs(),
                )

                # Put all received signals on hold until we handle them later.
                container.block_all_signals()

                # We want to avoid leaking file descriptors to the executed child.
                # It is also nice if the child has only the minimal necessary file
                # descriptors, to avoid keeping other pipes and files open, e.g.,
                # those that the parent uses to communicate with other containers
                # (if containers are started in parallel).
                # Thus we do not use the close_fds feature of subprocess.Popen,
                # but do the same here manually. We keep the relevant ends of our pipes,
                # and stdin/out/err of child and grandchild.
                necessary_fds = {
                    sys.stdin,
                    sys.stdout,
                    sys.stderr,
                    to_parent,
                    from_parent,
                    stdin,
                    stdout,
                    stderr,
                } - {None}
                container.close_open_fds(keep_files=necessary_fds)

                try:
                    if self._container_system_config:
                        # A standard hostname increases reproducibility.
                        socket.sethostname(container.CONTAINER_HOSTNAME)

                    if not self._allow_network:
                        container.activate_network_interface("lo")

                    # Wait until user mapping is finished,
                    # this is necessary for filesystem writes
                    received = os.read(from_parent,
                                       len(MARKER_USER_MAPPING_COMPLETED))
                    assert received == MARKER_USER_MAPPING_COMPLETED, received

                    if root_dir is not None:
                        self._setup_root_filesystem(root_dir)
                    else:
                        self._setup_container_filesystem(
                            temp_dir,
                            output_dir if result_files_patterns else None,
                            memlimit,
                            memory_nodes,
                        )

                    # Marking this process as "non-dumpable" (no core dumps) also
                    # forbids several other ways how other processes can access and
                    # influence it:
                    # ptrace is forbidden and much of /proc/<child>/ is inaccessible.
                    # We set this to prevent the benchmarked tool from messing with this
                    # process or using it to escape from the container. More info:
                    # http://man7.org/linux/man-pages/man5/proc.5.html
                    # It needs to be done after MARKER_USER_MAPPING_COMPLETED.
                    libc.prctl(libc.PR_SET_DUMPABLE, libc.SUID_DUMP_DISABLE, 0,
                               0, 0)
                except OSError as e:
                    logging.critical("Failed to configure container: %s", e)
                    return CHILD_OSERROR

                try:
                    os.chdir(cwd)
                except OSError as e:
                    logging.critical(
                        "Cannot change into working directory inside container: %s",
                        e)
                    return CHILD_OSERROR

                container.setup_seccomp_filter()

                try:
                    grandchild_proc = subprocess.Popen(
                        args,
                        stdin=stdin,
                        stdout=stdout,
                        stderr=stderr,
                        env=env,
                        close_fds=False,
                        preexec_fn=grandchild,
                    )
                except (OSError, RuntimeError) as e:
                    logging.critical("Cannot start process: %s", e)
                    return CHILD_OSERROR

                # keep capability for unmount if necessary later
                necessary_capabilities = ([libc.CAP_SYS_ADMIN]
                                          if result_files_patterns else [])
                container.drop_capabilities(keep=necessary_capabilities)

                # Close other fds that were still necessary above.
                container.close_open_fds(keep_files={
                    sys.stdout, sys.stderr, to_parent, from_parent
                })

                # Set up signal handlers to forward signals to grandchild
                # (because we are PID 1, there is a special signal handling otherwise).
                # cf. dumb-init project: https://github.com/Yelp/dumb-init
                # Also wait for grandchild and return its result.
                grandchild_result = container.wait_for_child_and_forward_signals(
                    grandchild_proc.pid, args[0])

                logging.debug(
                    "Child: process %s terminated with exit code %d.",
                    args[0],
                    grandchild_result[0],
                )

                if result_files_patterns:
                    # Remove the bind mount that _setup_container_filesystem added
                    # such that the parent can access the result files.
                    libc.umount(temp_dir.encode())

                # Re-allow access to /proc/<child>/...,
                # this is used by the parent for accessing output files
                libc.prctl(libc.PR_SET_DUMPABLE, libc.SUID_DUMP_USER, 0, 0, 0)

                os.write(to_parent, pickle.dumps(grandchild_result))
                os.close(to_parent)

                # Now the parent copies the output files, we need to wait until this is
                # finished. If the child terminates, the container file system and its
                # tmpfs go away.
                assert os.read(from_parent,
                               1) == MARKER_PARENT_POST_RUN_COMPLETED
                os.close(from_parent)

                return 0
            except OSError:
                logging.exception("Error in child process of RunExecutor")
                return CHILD_OSERROR
            except BaseException:
                # Need to catch everything because this method always needs to return an
                # int (we are inside a C callback that requires returning int).
                logging.exception("Error in child process of RunExecutor")
                return CHILD_UNKNOWN_ERROR

        try:  # parent
            try:
                child_pid = container.execute_in_namespace(
                    child, use_network_ns=not self._allow_network)
            except OSError as e:
                if (e.errno == errno.EPERM and util.try_read_file(
                        "/proc/sys/kernel/unprivileged_userns_clone") == "0"):
                    raise BenchExecException(
                        "Unprivileged user namespaces forbidden on this system, please "
                        "enable them with 'sysctl -w kernel.unprivileged_userns_clone=1' "
                        "or disable container mode")
                elif (e.errno in {errno.ENOSPC, errno.EINVAL} and
                      util.try_read_file("/proc/sys/user/max_user_namespaces")
                      == "0"):
                    # Ubuntu has ENOSPC, Centos seems to produce EINVAL in this case
                    raise BenchExecException(
                        "Unprivileged user namespaces forbidden on this system, please "
                        "enable by using 'sysctl -w user.max_user_namespaces=10000' "
                        "(or another value) or disable container mode")
                else:
                    raise BenchExecException(
                        "Creating namespace for container mode failed: " +
                        os.strerror(e.errno))
            logging.debug(
                "Parent: child process of RunExecutor with PID %d started.",
                child_pid)

            def check_child_exit_code():
                """Check if the child process terminated cleanly
                and raise an error otherwise."""
                child_exitcode, unused_child_rusage = self._wait_for_process(
                    child_pid, args[0])
                child_exitcode = util.ProcessExitCode.from_raw(child_exitcode)
                logging.debug(
                    "Parent: child process of RunExecutor with PID %d"
                    " terminated with %s.",
                    child_pid,
                    child_exitcode,
                )

                if child_exitcode:
                    if child_exitcode.value:
                        if child_exitcode.value == CHILD_OSERROR:
                            # This was an OSError in the child,
                            # details were already logged
                            raise BenchExecException(
                                "execution in container failed, check log for details"
                            )
                        elif child_exitcode.value == CHILD_UNKNOWN_ERROR:
                            raise BenchExecException(
                                "unexpected error in container")
                        raise OSError(child_exitcode.value,
                                      os.strerror(child_exitcode.value))
                    raise OSError(
                        0,
                        "Child process of RunExecutor terminated with " +
                        str(child_exitcode),
                    )

            # Close unnecessary ends of pipes such that read() does not block forever
            # if all other processes have terminated.
            os.close(from_parent)
            os.close(to_parent)

            container.setup_user_mapping(child_pid,
                                         uid=self._uid,
                                         gid=self._gid)
            # signal child to continue
            os.write(to_grandchild, MARKER_USER_MAPPING_COMPLETED)

            try:
                # read at most 10 bytes because this is enough for 32bit int
                grandchild_pid = int(os.read(from_grandchild, 10))
            except ValueError:
                # probably empty read, i.e., pipe closed,
                # i.e., child or grandchild failed
                check_child_exit_code()
                assert False, (
                    "Child process of RunExecutor terminated cleanly"
                    " but did not send expected data.")

            logging.debug(
                "Parent: executing %s in grand child with PID %d"
                " via child with PID %d.",
                args[0],
                grandchild_pid,
                child_pid,
            )

            # start measurements
            cgroups.add_task(grandchild_pid)
            parent_setup = parent_setup_fn()

            # Signal grandchild that setup is finished
            os.write(to_grandchild, MARKER_PARENT_COMPLETED)

            # Copy file descriptor, otherwise we could not close from_grandchild in
            # finally block and would leak a file descriptor in case of exception.
            from_grandchild_copy = os.dup(from_grandchild)
            to_grandchild_copy = os.dup(to_grandchild)
        finally:
            os.close(from_grandchild)
            os.close(to_grandchild)

        def wait_for_grandchild():
            # 1024 bytes ought to be enough for everyone^Wour pickled result
            try:
                received = os.read(from_grandchild_copy, 1024)
            except OSError as e:
                if self.PROCESS_KILLED and e.errno == errno.EINTR:
                    # Read was interrupted because of Ctrl+C, we just try again
                    received = os.read(from_grandchild_copy, 1024)
                else:
                    raise e

            if not received:
                # Typically this means the child exited prematurely because an error
                # occurred, and check_child_exitcode() will handle this.
                # We close the pipe first, otherwise child could hang infinitely.
                os.close(from_grandchild_copy)
                os.close(to_grandchild_copy)
                check_child_exit_code()
                assert False, "Child process terminated cleanly without sending result"

            exitcode, ru_child = pickle.loads(received)

            base_path = "/proc/{}/root".format(child_pid)
            parent_cleanup = parent_cleanup_fn(
                parent_setup, util.ProcessExitCode.from_raw(exitcode),
                base_path)

            if result_files_patterns:
                # As long as the child process exists
                # we can access the container file system here
                self._transfer_output_files(base_path + temp_dir, cwd,
                                            output_dir, result_files_patterns)

            os.close(from_grandchild_copy)
            os.write(to_grandchild_copy, MARKER_PARENT_POST_RUN_COMPLETED)
            os.close(to_grandchild_copy)  # signal child that it can terminate
            check_child_exit_code()

            return exitcode, ru_child, parent_cleanup

        return grandchild_pid, wait_for_grandchild
def getBenchmarkData(benchmark):
    r = benchmark.requirements
    # These values are currently not used internally, but the goal is
    # to eventually integrate them in a later stage.
    if r.cpu_model is None:
        r.cpu_model = "AmazonAWS"
    if r.cpu_cores is None or r.memory is None:
        raise BenchExecException(
            "The entry for either the amount of used cpu cores or memory "
            "is missing from the benchmark definition")
    requirements = {
        "cpu_cores": r.cpu_cores,
        "cpu_model": r.cpu_model,
        "memory_in_mb": bytes_to_mb(r.memory),
    }

    # get limits and number of runs
    time_limit = benchmark.rlimits.cputime_hard
    mem_limit = bytes_to_mb(benchmark.rlimits.memory)
    if time_limit is None or mem_limit is None:
        raise BenchExecException(
            "An entry for either the time- or memory-limit is missing "
            "in the benchmark definition")

    core_limit = benchmark.rlimits.cpu_cores
    number_of_runs = sum(
        len(run_set.runs) for run_set in benchmark.run_sets
        if run_set.should_be_executed())
    limits_and_num_runs = {
        "number_of_runs": number_of_runs,
        "time_limit_in_sec": time_limit,
        "mem_limit_in_mb": mem_limit,
    }
    if core_limit is not None:
        limits_and_num_runs.update({"core_limit": core_limit})

    # get runs with args and source_files
    source_files = set()
    run_definitions = []
    for run_set in benchmark.run_sets:
        if not run_set.should_be_executed():
            continue
        if STOPPED_BY_INTERRUPT:
            break

        # get runs
        for run in run_set.runs:
            run_definition = {}

            # wrap list-elements in quotations-marks if they contain whitespace
            cmdline = [
                "'{}'".format(x) if " " in x else x for x in run.cmdline()
            ]
            cmdline = " ".join(cmdline)
            log_file = os.path.relpath(run.log_file, benchmark.log_folder)

            run_definition.update({
                "cmdline": cmdline,
                "log_file": log_file,
                "sourcefile": run.sourcefiles,
                "required_files": run.required_files,
            })

            run_definitions.append(run_definition)
            source_files.update(run.sourcefiles)
            source_files.update(run.required_files)

    if not run_definitions:
        raise BenchExecException("Benchmark has nothing to run.")

    return (
        requirements,
        number_of_runs,
        limits_and_num_runs,
        run_definitions,
        source_files,
    )
Esempio n. 21
0
def _init_container(
    temp_dir,
    network_access,
    dir_modes,
    container_system_config,
    container_tmpfs,  # ignored, tmpfs is always used
):
    """
    Create a fork of this process in a container. This method only returns in the fork,
    so calling it seems like moving the current process into a container.
    """
    # Prepare for private home directory, some tools write there
    if container_system_config:
        dir_modes.setdefault(container.CONTAINER_HOME, container.DIR_HIDDEN)
        os.environ["HOME"] = container.CONTAINER_HOME

    # Preparations
    temp_dir = temp_dir.encode()
    dir_modes = collections.OrderedDict(
        sorted(
            ((path.encode(), kind) for (path, kind) in dir_modes.items()),
            key=lambda tupl: len(tupl[0]),
        )
    )
    uid = container.CONTAINER_UID if container_system_config else os.getuid()
    gid = container.CONTAINER_GID if container_system_config else os.getgid()

    # Create container.
    # Contrary to ContainerExecutor, which uses clone to start a new process in new
    # namespaces, we use unshare, which puts the current process (the multiprocessing
    # worker process) into new namespaces.
    # The exception is the PID namespace, which will only apply to children processes.
    flags = (
        libc.CLONE_NEWNS
        | libc.CLONE_NEWUTS
        | libc.CLONE_NEWIPC
        | libc.CLONE_NEWUSER
        | libc.CLONE_NEWPID
    )
    if not network_access:
        flags |= libc.CLONE_NEWNET
    try:
        libc.unshare(flags)
    except OSError as e:
        if (
            e.errno == errno.EPERM
            and util.try_read_file("/proc/sys/kernel/unprivileged_userns_clone") == "0"
        ):
            raise BenchExecException(
                "Unprivileged user namespaces forbidden on this system, please "
                "enable them with 'sysctl kernel.unprivileged_userns_clone=1' "
                "or disable container mode"
            )
        else:
            raise BenchExecException(
                "Creating namespace for container mode failed: " + os.strerror(e.errno)
            )

    # Container config
    container.setup_user_mapping(os.getpid(), uid, gid)
    _setup_container_filesystem(temp_dir, dir_modes, container_system_config)
    if container_system_config:
        libc.sethostname(container.CONTAINER_HOSTNAME)
    if not network_access:
        container.activate_network_interface("lo")

    # Because this process is not actually in the new PID namespace, we fork.
    # The child will be in the new PID namespace and will assume the role of the acting
    # multiprocessing worker (which it can do because it inherits the file descriptors
    # that multiprocessing uses for communication).
    # The original multiprocessing worker (the parent of the fork) must do nothing in
    # order to not confuse multiprocessing.
    pid = os.fork()
    if pid:
        container.drop_capabilities()
        # block parent such that it does nothing
        os.waitpid(pid, 0)
        os._exit(0)

    # Finalize container setup in child
    container.mount_proc(container_system_config)  # only possible in child
    container.drop_capabilities()
    libc.prctl(libc.PR_SET_DUMPABLE, libc.SUID_DUMP_DISABLE, 0, 0, 0)
    container.setup_seccomp_filter()
Esempio n. 22
0
from p4.counter import Counter

from benchexec import tooladapter
from benchexec import util
from benchexec import BenchExecException

# File handling
from shutil import copyfile, rmtree
import json
from distutils.dir_util import copy_tree

try:
    import docker
except ModuleNotFoundError:
    raise BenchExecException(
        "Python-docker package not found. Try reinstalling python docker module"
    )

try:
    from pyroute2 import IPRoute
    from pyroute2 import NetNS
except ModuleNotFoundError:
    raise BenchExecException(
        "pyroute2 python package not found. Try reinstalling pyroute2"
    )


STOPPED_BY_INTERRUPT = False

# Static Parameters
MGNT_NETWORK_SUBNET = "172.19"  # Subnet 192.19.x.x/16
Esempio n. 23
0
    def __init__(self, benchmark_file, config, start_time):
        """
        The constructor of Benchmark reads the source files, options, columns and the tool
        from the XML in the benchmark_file..
        """
        logging.debug("I'm loading the benchmark %s.", benchmark_file)

        self.config = config
        self.benchmark_file = benchmark_file
        self.base_dir = os.path.dirname(self.benchmark_file)

        # get benchmark-name
        self.name = os.path.basename(
            benchmark_file)[:-4]  # remove ending ".xml"
        if config.name:
            self.name += "." + config.name

        self.description = None
        if config.description_file is not None:
            try:
                self.description = util.read_file(config.description_file)
            except (OSError, UnicodeDecodeError) as e:
                raise BenchExecException(
                    "File '{}' given for description could not be read: {}".
                    format(config.description_file, e))

        self.start_time = start_time
        self.instance = start_time.strftime(util.TIMESTAMP_FILENAME_FORMAT)

        self.output_base_name = config.output_path + self.name + "." + self.instance
        self.log_folder = self.output_base_name + ".logfiles" + os.path.sep
        self.log_zip = self.output_base_name + ".logfiles.zip"
        self.result_files_folder = self.output_base_name + ".files"

        # parse XML
        try:
            rootTag = ElementTree.ElementTree().parse(benchmark_file)
        except ElementTree.ParseError as e:
            sys.exit("Benchmark file {} is invalid: {}".format(
                benchmark_file, e))
        if "benchmark" != rootTag.tag:
            sys.exit("Benchmark file {} is invalid: "
                     "It's root element is not named 'benchmark'.".format(
                         benchmark_file))

        # get tool
        tool_name = rootTag.get("tool")
        if not tool_name:
            sys.exit(
                "A tool needs to be specified in the benchmark definition file."
            )
        (self.tool_module, self.tool) = load_tool_info(tool_name, config)
        self.tool_name = self.tool.name()
        # will be set from the outside if necessary (may not be the case in SaaS environments)
        self.tool_version = None
        self.executable = None
        self.display_name = rootTag.get("displayName")

        def parse_memory_limit(value):
            # In a future BenchExec version, we could treat unit-less limits as bytes
            try:
                value = int(value)
            except ValueError:
                return util.parse_memory_value(value)
            else:
                raise ValueError(
                    "Memory limit must have a unit suffix, e.g., '{} MB'".
                    format(value))

        def handle_limit_value(name, key, cmdline_value, parse_fn):
            value = rootTag.get(key, None)
            # override limit from XML with values from command line
            if cmdline_value is not None:
                if cmdline_value.strip() == "-1":  # infinity
                    value = None
                else:
                    value = cmdline_value

            if value is not None:
                try:
                    self.rlimits[key] = parse_fn(value)
                except ValueError as e:
                    sys.exit("Invalid value for {} limit: {}".format(
                        name.lower(), e))
                if self.rlimits[key] <= 0:
                    sys.exit(
                        '{} limit "{}" is invalid, it needs to be a positive number '
                        "(or -1 on the command line for disabling it).".format(
                            name, value))

        self.rlimits = {}
        handle_limit_value("Time", TIMELIMIT, config.timelimit,
                           util.parse_timespan_value)
        handle_limit_value("Hard time", HARDTIMELIMIT, config.timelimit,
                           util.parse_timespan_value)
        handle_limit_value("Wall time", WALLTIMELIMIT, config.walltimelimit,
                           util.parse_timespan_value)
        handle_limit_value("Memory", MEMLIMIT, config.memorylimit,
                           parse_memory_limit)
        handle_limit_value("Core", CORELIMIT, config.corelimit, int)

        if HARDTIMELIMIT in self.rlimits:
            hardtimelimit = self.rlimits.pop(HARDTIMELIMIT)
            if TIMELIMIT in self.rlimits:
                if hardtimelimit < self.rlimits[TIMELIMIT]:
                    logging.warning(
                        "Hard timelimit %d is smaller than timelimit %d, ignoring the former.",
                        hardtimelimit,
                        self.rlimits[TIMELIMIT],
                    )
                elif hardtimelimit > self.rlimits[TIMELIMIT]:
                    self.rlimits[SOFTTIMELIMIT] = self.rlimits[TIMELIMIT]
                    self.rlimits[TIMELIMIT] = hardtimelimit
            else:
                self.rlimits[TIMELIMIT] = hardtimelimit

        self.num_of_threads = int(rootTag.get("threads", 1))
        if config.num_of_threads is not None:
            self.num_of_threads = config.num_of_threads
        if self.num_of_threads < 1:
            logging.error("At least ONE thread must be given!")
            sys.exit()

        # get global options and property file
        self.options = util.get_list_from_xml(rootTag)
        self.propertytag = get_propertytag(rootTag)

        # get columns
        self.columns = Benchmark.load_columns(rootTag.find("columns"))

        # get global source files, they are used in all run sets
        if rootTag.findall("sourcefiles"):
            sys.exit(
                "Benchmark file {} has unsupported old format. "
                "Rename <sourcefiles> tags to <tasks>.".format(benchmark_file))
        globalSourcefilesTags = rootTag.findall("tasks")

        # get required files
        self._required_files = set()
        for required_files_tag in rootTag.findall("requiredfiles"):
            required_files = util.expand_filename_pattern(
                required_files_tag.text, self.base_dir)
            if not required_files:
                logging.warning(
                    "Pattern %s in requiredfiles tag did not match any file.",
                    required_files_tag.text,
                )
            self._required_files = self._required_files.union(required_files)

        # get requirements
        self.requirements = Requirements(rootTag.findall("require"),
                                         self.rlimits, config)

        result_files_tags = rootTag.findall("resultfiles")
        if result_files_tags:
            self.result_files_patterns = [
                os.path.normpath(p.text) for p in result_files_tags if p.text
            ]
            for pattern in self.result_files_patterns:
                if pattern.startswith(".."):
                    sys.exit(
                        "Invalid relative result-files pattern '{}'.".format(
                            pattern))
        else:
            # default is "everything below current directory"
            self.result_files_patterns = ["."]

        # get benchmarks
        self.run_sets = []
        for (i,
             rundefinitionTag) in enumerate(rootTag.findall("rundefinition")):
            self.run_sets.append(
                RunSet(rundefinitionTag, self, i + 1, globalSourcefilesTags))

        if not self.run_sets:
            logging.warning(
                "Benchmark file %s specifies no runs to execute "
                "(no <rundefinition> tags found).",
                benchmark_file,
            )

        if not any(runSet.should_be_executed() for runSet in self.run_sets):
            logging.warning(
                "No <rundefinition> tag selected, nothing will be executed.")
            if config.selected_run_definitions:
                logging.warning(
                    "The selection %s does not match any run definitions of %s.",
                    config.selected_run_definitions,
                    [runSet.real_name for runSet in self.run_sets],
                )
        elif config.selected_run_definitions:
            for selected in config.selected_run_definitions:
                if not any(
                        util.wildcard_match(run_set.real_name, selected)
                        for run_set in self.run_sets):
                    logging.warning(
                        'The selected run definition "%s" is not present in the input file, '
                        "skipping it.",
                        selected,
                    )