Exemple #1
0
    def post(self, user, pipeline_identifier):
        (boutiques_descriptor_path, descriptor_type
         ), error = get_original_descriptor_path_and_type(pipeline_identifier)

        if error:
            return marshal(error), 400
        body = request.get_json(force=True)
        invocation = body['invocation']
        queries = body['queries']
        inv_tmp = tempfile.NamedTemporaryFile(mode="r+", delete=True)
        try:
            json.dump(invocation, inv_tmp)
            inv_tmp.flush()
            executor = LocalExecutor(boutiques_descriptor_path, inv_tmp.name, {
                "forcePathType": True,
                "destroyTempScripts": True,
                "changeUser": True
            })
            query_results = []
            for query in queries:
                query_results += [evaluateEngine(executor, query)]
            inv_tmp.close()
            return query_results[0] if len(query_results) == 1 else query_results, 200
        except Exception as error:
            inv_tmp.close()
            raise error
Exemple #2
0
def evaluate(*params):
    parser = ArgumentParser("Evaluates parameter values for a descriptor"
                            " and invocation")
    parser.add_argument("descriptor", action="store",
                        help="The Boutiques descriptor as a JSON file, JSON "
                             "string or Zenodo ID (prefixed by 'zenodo.').")
    parser.add_argument("invocation", action="store",
                        help="Input JSON complying to invocation.")
    parser.add_argument("query", action="store", nargs="*",
                        help="The query to be performed. Simply request keys "
                        "from the descriptor (i.e. output-files), and chain "
                        "together queries (i.e. id=myfile or optional=false) "
                        "slashes between them and commas connecting them. "
                        "(i.e. output-files/optional=false,id=myfile). "
                        "Perform multiple queries by separating them with a "
                        "space.")
    result = parser.parse_args(params)

    # Generate object that will parse the invocation and descriptor
    from boutiques.localExec import LocalExecutor
    executor = LocalExecutor(result.descriptor, result.invocation,
                             {"forcePathType": True,
                              "destroyTempScripts": True,
                              "changeUser": True,
                              "skipDataCollect": True})

    from boutiques.evaluate import evaluateEngine
    query_results = []
    for query in result.query:
        query_results += [evaluateEngine(executor, query)]
    return query_results[0] if len(query_results) == 1 else query_results
Exemple #3
0
def evaluate(*params):
    parser = parser_evaluate()
    result = parser.parse_args(params)

    # Generate object that will parse the invocation and descriptor
    from boutiques.localExec import LocalExecutor
    executor = LocalExecutor(result.descriptor, result.invocation,
                             {"forcePathType": True,
                              "destroyTempScripts": True,
                              "changeUser": True,
                              "skipDataCollect": True})

    from boutiques.evaluate import evaluateEngine
    query_results = []
    for query in result.query:
        query_results += [evaluateEngine(executor, query)]
    return query_results[0] if len(query_results) == 1 else query_results
Exemple #4
0
    def execute(self, mount_strings):
        '''
        The execute method runs the generated command line
        (from either generateRandomParams or readInput)
        If docker is specified, it will attempt to use it, instead
        of local execution.
        After execution, it checks for output file existence.
        '''
        command, exit_code, con = self.cmdLine[0], None, self.con or {}
        # Check for Container image
        conType, conImage = con.get('type'), con.get('image'),
        conIndex = con.get("index")
        conIsPresent = (conImage is not None)
        # Export environment variables, if they are specified in the descriptor
        envVars = {}
        if 'environment-variables' in list(self.desc_dict.keys()):
            variables = [(p['name'], p['value']) for p in
                         self.desc_dict['environment-variables']]
            for (envVarName, envVarValue) in variables:
                os.environ[envVarName] = envVarValue
                envVars[envVarName] = envVarValue
        # Container script constant name
        # Note that docker/singularity cannot do a local volume
        # mount of files starting with a '.', hence this one does not
        millitime = int(time.time()*1000)
        dsname = ('temp-' +
                  str(random.SystemRandom().randint(0, int(millitime))) +
                  "-" + str(millitime) + '.localExec.boshjob.sh')
        dsname = op.realpath(dsname)
        # If container is present, alter the command template accordingly
        container_location = ""
        container_command = ""
        if conIsPresent:
            if conType == 'docker':
                # Pull the docker image
                if self._localExecute("docker pull " + str(conImage))[1]:
                    container_location = "Local copy"
            elif conType == 'singularity':
                if not conIndex:
                    conIndex = "shub://"
                elif not conIndex.endswith("://"):
                    conIndex = conIndex + "://"
                conName = conImage.replace("/", "-").replace(":", "-") + ".simg"

                if conName not in os.listdir('./'):
                    pull_loc = "\"{0}\" {1}{2}".format(conName,
                                                       conIndex,
                                                       conImage)
                    container_location = ("Pulled from {1} ({0} not found "
                                          "in current"
                                          "working director").format(conName,
                                                                     pull_loc)
                    # Pull the singularity image
                    if self._localExecute("singularity pull --name " +
                                          pull_loc)[1]:
                        raise ExecutorError("Could not pull Singularity image")
                else:
                    container_location = "Local ({0})".format(conName)
                conName = op.abspath(conName)
            else:
                raise ExecutorError('Unrecognized container'
                                    ' type: \"%s\"' % conType)
            # Generate command script
            uname, uid = pwd.getpwuid(os.getuid())[0], str(os.getuid())
            # Adds the user to the container before executing
            # the templated command line
            userchange = '' if not self.changeUser else ("useradd --uid " +
                                                         uid + ' ' + uname +
                                                         "\n")
            # If --changeUser was desired, run with su so that
            # any output files are owned by the user instead of root
            # Get the supported shell by the docker or singularity
            if self.changeUser:
                command = 'su ' + uname + ' -c ' + "\"{0}\"".format(command)
            cmdString = "#!"+self.shell+" -l\n" + userchange + str(command)
            with open(dsname, "w") as scrFile:
                scrFile.write(cmdString)
            # Ensure the script is executable
            self._localExecute("chmod 755 " + dsname)
            # Prepare extra environment variables
            envString = ""
            if envVars:
                for (key, val) in list(envVars.items()):
                    envString += "SINGULARITYENV_{0}='{1}' ".format(key, val)
            # Change launch (working) directory if desired
            launchDir = self.launchDir
            if launchDir is None:
                launchDir = op.realpath('./')
            launchDir = op.realpath(launchDir)
            # Run it in docker
            mount_strings = [] if not mount_strings else mount_strings
            mount_strings = [op.realpath(m.split(":")[0])+":"+m.split(":")[1]
                             for m in mount_strings]
            mount_strings.append(op.realpath('./') + ':' + launchDir)
            if conType == 'docker':
                envString = " "
                if envVars:
                    for (key, val) in list(envVars.items()):
                        envString += " -e {0}='{1}' ".format(key, val)
                # export mounts to docker string
                docker_mounts = " -v ".join(m for m in mount_strings)
                container_command = ('docker run --entrypoint=' + self.shell +
                                     ' --rm' + envString +
                                     ' -v ' + docker_mounts +
                                     ' -w ' + launchDir + ' ' +
                                     str(conImage) + ' ' + dsname)
            elif conType == 'singularity':
                envString = ""
                if envVars:
                    for (key, val) in list(envVars.items()):
                        envString += "SINGULARITYENV_{0}='{1}' ".format(key,
                                                                        val)
                # TODO: Singularity 2.4.6 default configuration binds: /proc,
                # /sys, /dev, ${HOME}, /tmp, /var/tmp, /etc/localtime, and
                # /etc/hosts. This means that any path down-stream shouldn't
                # be bound on the command-line, as this will currently raise
                # an exception. See:
                #   https://github.com/singularityware/singularity/issues/1469
                #
                # Previous bind string:
                #   singularity_mounts = " -B ".join(m for m in mount_strings)

                def_mounts = ["/proc", "/sys", "/dev", "/tmp", "/var/tmp",
                              "/etc/localtime", "/etc/hosts",
                              op.realpath(op.expanduser('~')),
                              op.expanduser('~')]

                # Ensures the set of paths provided has no overlap
                compaths = list()
                for idxm, m in enumerate(mount_strings):
                    for n in mount_strings[idxm:]:
                        if n != m:
                            tmp = op.dirname(op.commonprefix([n, m]))
                            if tmp != '/':
                                compaths += [tmp]
                    if not any(m.startswith(c) for c in compaths):
                        compaths += [m]
                mount_strings = set(compaths)

                # Only adds mount points for those not already included
                singularity_mounts = ""
                for m in mount_strings:
                    if not any(d in m for d in def_mounts):
                        singularity_mounts += "-B {0} ".format(m)

                container_command = (envString + 'singularity exec '
                                     '--cleanenv ' +
                                     singularity_mounts +
                                     ' -W ' + launchDir + ' ' +
                                     str(conName) + ' ' + dsname)
            else:
                raise ExecutorError('Unrecognized container type: '
                                    '\"%s\"' % conType)
            (stdout, stderr), exit_code = self._localExecute(container_command)
        # Otherwise, just run command locally
        else:
            (stdout, stderr), exit_code = self._localExecute(command)
        time.sleep(0.5)  # Give the OS a (half) second to finish writing

        # Destroy temporary docker script, if desired.
        # By default, keep the script so the dev can look at it.
        if conIsPresent and self.destroyTempScripts:
            if os.path.isfile(dsname):
                os.remove(dsname)

        # Check for output files
        missing_files = []
        output_files = []
        all_files = evaluateEngine(self, "output-files")
        required_files = evaluateEngine(self, "output-files/optional=False")
        optional_files = evaluateEngine(self, "output-files/optional=True")
        for f in all_files.keys():
            file_name = all_files[f]
            fd = FileDescription(f, file_name, False)
            if op.exists(file_name):
                output_files.append(fd)
            else:  # file does not exist
                if f in required_files.keys():
                    missing_files.append(fd)

        # Set error messages
        desc_err = ''
        if 'error-codes' in list(self.desc_dict.keys()):
            for err_elem in self.desc_dict['error-codes']:
                if err_elem['code'] == exit_code:
                        desc_err = err_elem['description']
                        break

        return ExecutorOutput(stdout,
                              stderr,
                              exit_code,
                              desc_err,
                              output_files,
                              missing_files,
                              command,
                              container_command, container_location)