Exemplo n.º 1
0
    def create_metadata(self, metadata):
        result = {}
        post_url = self.url
        if not post_url.endswith('/'):
            post_url += '/'

        log.debug("Posting metadata to %s: %s" % (post_url, metadata))
        status_code, result = pscheduler.url_post(
            post_url,
            data=pscheduler.json_dump(metadata),
            headers=self.headers,
            throw=False,
            json=True,
            verify_keys=self.verify_ssl,
            bind=self.bind,
            allow_redirects=False,
            timeout=HTTP_TIMEOUT)
        
        log.debug("Esmond returned HTTP {0}: {1}".format(status_code, result))
        
        if status_code == 301:
            #redirects
            return False, "Server is attempting to redirect archive URL %s. If redirecting to https, please change archive URL to https instead of relying on redirection." % post_url
        elif status_code not in [200, 201]:
            try:
                result_json = pscheduler.json_load(result)
            except:
                result_json = {"detail": "Invalid JSON returned"}
            return False, "%d: %s" % (
                status_code,
                result_json.get("detail", pscheduler.json_dump(result_json)))

        return True, result
Exemplo n.º 2
0
def handle_run_error(msg, diags=None, do_log=True):
    if do_log:
        log.error(msg)
    results = {
        'schema': LATENCY_SCHEMA_VERSION,
        'succeeded': False,
        'error': msg,
        'diags': diags
    }
    print pscheduler.json_dump(results)
    print pscheduler.api_result_delimiter()
    sys.stdout.flush()
Exemplo n.º 3
0
def handle_run_error(msg, diags=None, do_log=True):
    if do_log:
        log.error(msg)
    results = { 
        'schema': LATENCY_SCHEMA_VERSION, 
        'succeeded': False,
        'error': msg,
        'diags': diags
    }
    print pscheduler.json_dump(results)
    print pscheduler.api_result_delimiter()
    sys.stdout.flush()
Exemplo n.º 4
0
    def create_data(self, metadata_key, data_points):
        result = {}
        put_url = self.url
        if not put_url.endswith('/'):
            put_url += '/'
        put_url += ("%s/" % metadata_key)
        data = {'data': data_points}

        log.debug("Putting data to %s: %s" % (put_url, data))
        status_code, result = pscheduler.url_put(put_url,
                                                 data=data,
                                                 headers=self.headers,
                                                 json=True,
                                                 throw=False,
                                                 verify_keys=self.verify_ssl,
                                                 bind=self.bind,
                                                 allow_redirects=False,
                                                 timeout=HTTP_TIMEOUT)

        if status_code == 409:
            #duplicate data
            log.debug("Attempted to add duplicate data point. Skipping")
        elif status_code == 301:
            #redirects
            return False, "Server is attempting to redirect archive URL %s. If redirecting to https, please change archive URL to https instead of relying on redirection." % put_url
        elif status_code not in [200, 201]:
            try:
                result_json = pscheduler.json_load(result)
            except:
                result_json = {"detail": "Invalid JSON returned"}
            return False, "%d: %s" % (
                status_code,
                result_json.get("detail", pscheduler.json_dump(result_json)))

        return True, ""
Exemplo n.º 5
0
    def evaluate(
            self,
            run  # The proposed run
    ):

        # Dissent if the test isn't our type
        if run["type"] != self.test:
            return {
                "passed": False,
                "reasons": ["Test is not '%s'" % self.test]
            }

        pass_input = {"spec": run["spec"], "limit": self.limit}

        returncode, stdout, stderr = pscheduler.run_program(
            [
                "pscheduler", "internal", "invoke", "test", self.test,
                "limit-passes"
            ],
            stdin=pscheduler.json_dump(pass_input),
            # TODO:  Is this reasonable?
            timeout=5)

        if returncode != 0:
            raise RuntimeError("Failed to validate limit: %s" % stderr)

        check_result = pscheduler.json_load(stdout, max_schema=1)
        passed = check_result["passes"]

        result = {"passed": passed, "limit": self.limit}
        if not passed:
            result["reasons"] = check_result["errors"]

        return result
Exemplo n.º 6
0
def tests_name_spec(name):

    try:
        cursor = dbcursor_query("SELECT EXISTS (SELECT * FROM test WHERE NAME = %s)",
                                [ name ])
    except Exception as ex:
        return error(str(ex))

    exists = cursor.fetchone()[0]
    if not exists:
        return not_found()

    try:
        args = arg_json('args')
    except ValueError:
        return error("Invalid JSON passed to 'args'")
    
    status, stdout, stderr = pscheduler.run_program(
        [ 'pscheduler', 'internal', 'invoke', 'test', name, 'cli-to-spec' ],
        stdin = pscheduler.json_dump(args),
        short = True,
        )

    if status != 0:
        return bad_request(stderr)

    # The extra parse here makes 'pretty' work.
    returned_json = pscheduler.json_load(stdout)
    return ok_json(returned_json)
Exemplo n.º 7
0
def tests_name_spec(name):

    cursor = dbcursor_query("SELECT EXISTS (SELECT * FROM test"
                            "  WHERE available AND name = %s)",
                            [ name ])

    exists = cursor.fetchone()[0]
    cursor.close()
    if not exists:
        return not_found()

    try:
        args = arg_json('args')
    except ValueError as ex:
        return bad_request("JSON passed to 'args': %s " % (str(ex)))

    status, stdout, stderr = pscheduler.run_program(
        [ 'pscheduler', 'internal', 'invoke', 'test', name, 'cli-to-spec' ],
        stdin = pscheduler.json_dump(args),
        timeout=5
        )

    if status != 0:
        return bad_request(stderr)

    # The extra parse here makes 'pretty' work.
    returned_json = pscheduler.json_load(stdout)
    return ok_json(returned_json, sanitize=False)
Exemplo n.º 8
0
    def __init__(
            self,
            data  # Data suitable for this class
    ):

        valid, message = data_is_valid(data)
        if not valid:
            raise ValueError("Invalid data: %s" % message)

        self.test = data['test']
        self.limit = data['limit']

        returncode, stdout, stderr = pscheduler.run_program(
            [
                "pscheduler", "internal", "invoke", "test", self.test,
                "limit-is-valid"
            ],
            stdin=pscheduler.json_dump(self.limit),
            # TODO:  Is this reasonable?
            timeout=5)

        if returncode != 0:
            raise RuntimeError("Failed to validate limit: %s" % stderr)

        result = pscheduler.json_load(stdout, max_schema=1)
        if not result['valid']:
            raise ValueError("Invalid limit: %s" % result['message'])
Exemplo n.º 9
0
    def evaluate(self, run):  # The proposed run

        # Dissent if the test isn't our type
        if run["type"] != self.test:
            return {"passed": False, "reasons": ["Test is not '%s'" % self.test]}

        pass_input = {"spec": run["spec"], "limit": self.limit}

        returncode, stdout, stderr = pscheduler.run_program(
            ["pscheduler", "internal", "invoke", "test", self.test, "limit-passes"],
            stdin=pscheduler.json_dump(pass_input),
            # TODO:  Is this reasonable?
            timeout=5,
        )

        if returncode != 0:
            raise RuntimeError("Failed to validate limit: %s" % stderr)

        check_result = pscheduler.json_load(stdout)
        passed = check_result["passes"]

        result = {"passed": passed}
        if not passed:
            result["reasons"] = check_result["errors"]

        return result
Exemplo n.º 10
0
 def create_data(self, metadata_key, data_points):
     result = {}
     put_url = self.url
     if not put_url.endswith('/'):
         put_url += '/'
     put_url += ("%s/" % metadata_key)
     data = { 'data': data_points }
     log.debug("Putting data to %s: %s" % (put_url, data))
     r = requests.put(put_url, data=pscheduler.json_dump(data), headers=self.headers, verify=self.verify_ssl)
     if r.status_code== 409:
         #duplicate data
         log.debug("Attempted to add duplicate data point. Skipping")
     elif r.status_code != 200 and r.status_code != 201:
         try:
             return False, "%d: %s" % (r.status_code, pscheduler.json_load(r.text)['detail'])
         except:
             return False, "%d: %s" % (r.status_code, r.text)
     
     return True, ""
Exemplo n.º 11
0
 def create_metadata(self, metadata):
     result = {}
     post_url = self.url
     if not post_url.endswith('/'):
         post_url += '/'
     log.debug("Posting metadata to %s: %s" % (post_url, metadata))
     r = requests.post(post_url, data=pscheduler.json_dump(metadata), headers=self.headers, verify=self.verify_ssl)
     if r.status_code != 200 and r.status_code != 201:
         try:
             return False, "%d: %s" % (r.status_code, pscheduler.json_load(r.text)['detail'])
         except:
             return False, "%d: %s" % (r.status_code, r.text)
     try:
         rjson = pscheduler.json_load(r.text)
         log.debug("Metadata POST result: %s" % rjson)
     except:
         return False, "Invalid JSON returned from server: %s" % r.text 
     
     return True, rjson
Exemplo n.º 12
0
def tasks_uuid_cli(uuid):

    if not uuid_is_valid(uuid):
        return not_found()

    # Get a task, adding server-derived details if a 'detail'
    # argument is present.

    try:
        cursor = dbcursor_query(
            """SELECT
                   task.json #>> '{test, spec}',
                   test.name
               FROM
                   task
                   JOIN test on test.id = task.test
               WHERE task.uuid = %s""", [uuid])
    except Exception as ex:
        return error(str(ex))

    if cursor.rowcount == 0:
        return not_found()

    row = cursor.fetchone()
    if row is None:
        return not_found()
    json, test = row

    try:
        returncode, stdout, stderr = pscheduler.run_program(
            ["pscheduler", "internal", "invoke", "test", test, "spec-to-cli"],
            stdin=json)
        if returncode != 0:
            return error("Unable to convert test spec: " + stderr)
    except Exception as ex:
        return error("Unable to convert test spec: " + str(ex))

    returned = pscheduler.json_load(stdout)
    returned.insert(0, test)

    return ok(pscheduler.json_dump(returned))
Exemplo n.º 13
0
    def __call__(self, task, classifiers, validate_callback=None):
        """
        Rewrite the task given the classifiers.  Returns a tuple
        containing the rewritten task and an array of diagnostic
        messages.

        Returns a tuple containing a boolean indicating whether or not
        the task was changed, the revised task and an array of strings
        containing diagnostic information.

        The caller is expected to catch and deal with any
        JQRuntimeError that is thrown.
        """

        task_in = copy.deepcopy(task)

        # Rewriter-private data
        task_in[self.PRIVATE_KEY] = {
            "classifiers": classifiers,
            "changed": False,
            "diags": []
        }

        result = self.transform(task_in)[0]

        #        print "RES", pscheduler.json_dump(result, pretty=True)

        if ("test" not in result) \
           or ("type" not in result["test"]) \
           or (not isinstance(result["test"]["type"], basestring)) \
           or ("spec" not in result["test"]) \
           or (result["test"]["type"] != task["test"]["type"]):
            raise ValueError("Invalid rewriter result:\n%s" \
                             % pscheduler.json_dump(result))

        changed = result[self.PRIVATE_KEY]["changed"]
        diags = result[self.PRIVATE_KEY]["diags"]
        del result[self.PRIVATE_KEY]

        return changed, result, diags
Exemplo n.º 14
0
    def __init__(self, data):  # Data suitable for this class

        valid, message = test_data_is_valid(data)
        if not valid:
            raise ValueError("Invalid data: %s" % message)

        self.test = data["test"]
        self.limit = data["limit"]

        returncode, stdout, stderr = pscheduler.run_program(
            ["pscheduler", "internal", "invoke", "test", self.test, "limit-is-valid"],
            stdin=pscheduler.json_dump(self.limit),
            # TODO:  Is this reasonable?
            timeout=5,
        )

        if returncode != 0:
            raise RuntimeError("Failed to validate limit: %s" % stderr)

        result = pscheduler.json_load(stdout)
        if not result["valid"]:
            raise ValueError("Invalid limit: %s" % result["message"])
Exemplo n.º 15
0
def tasks_uuid_cli(uuid):

    # Get a task, adding server-derived details if a 'detail'
    # argument is present.

    try:
        cursor = dbcursor_query(
            """SELECT
                   task.json #>> '{test, spec}',
                   test.name
               FROM
                   task
                   JOIN test on test.id = task.test
               WHERE task.uuid = %s""", [uuid])
    except Exception as ex:
        return error(str(ex))

    if cursor.rowcount == 0:
        return not_found()

    row = cursor.fetchone()
    if row is None:
        return not_found()
    json, test = row

    try:
        returncode, stdout, stderr = pscheduler.run_program(
            [ "pscheduler", "internal", "invoke", "test",
              test, "spec-to-cli" ], stdin = json )
        if returncode != 0:
            return error("Unable to convert test spec: " + stderr)
    except Exception as ex:
        return error("Unable to convert test spec: " + str(ex))

    returned = pscheduler.json_load(stdout)
    returned.insert(0, test)

    return ok(pscheduler.json_dump(returned))
Exemplo n.º 16
0
def json_dump(dump):
    return pscheduler.json_dump(dump, pretty=arg_boolean("pretty"))
Exemplo n.º 17
0
def tasks_uuid(uuid):

    if not uuid_is_valid(uuid):
        return not_found()

    if request.method == 'GET':

        try:
            tasks = __tasks_get_filtered(request.base_url,
                                         where_clause="task.uuid = %s",
                                         args=[uuid],
                                         expanded=True,
                                         detail=arg_boolean("detail"),
                                         single=True)
        except Exception as ex:
            return error(str(ex))

        if not tasks:
            return not_found()

        return ok_json(tasks[0])

    elif request.method == 'POST':

        log.debug("Posting to %s", uuid)
        log.debug("Data is %s", request.data)

        # TODO: This is only for participant 1+
        # TODO: This should probably a PUT and not a POST.

        try:
            json_in = pscheduler.json_load(request.data, max_schema=1)
        except ValueError:
            return bad_request("Invalid JSON")
        log.debug("JSON is %s", json_in)

        try:
            participant = arg_cardinal('participant')
        except ValueError as ex:
            return bad_request("Invalid participant: " + str(ex))
        log.debug("Participant %d", participant)

        # Evaluate the task against the limits and reject the request
        # if it doesn't pass.

        log.debug("Checking limits on task")

        processor, whynot = limitprocessor()
        if processor is None:
            message = "Limit processor is not initialized: %s" % whynot
            log.debug(message)
            return no_can_do(message)

        hints = request_hints()
        hints_data = pscheduler.json_dump(hints)

        passed, limits_passed, diags = processor.process(
            json_in["test"], hints)

        if not passed:
            return forbidden("Task forbidden by limits:\n" + diags)
        log.debug("Limits passed")

        # TODO: Pluck UUID from URI
        uuid = url_last_in_path(request.url)

        log.debug("Posting task %s", uuid)

        try:
            try:
                participants = pscheduler.json_load(
                    request.data, max_schema=1)["participants"]
            except:
                return bad_request("No participants provided")
            cursor = dbcursor_query(
                "SELECT * FROM api_task_post(%s, %s, %s, %s, %s, %s, TRUE)", [
                    request.data, participants, hints_data,
                    pscheduler.json_dump(limits_passed), participant, uuid
                ])
        except Exception as ex:
            return error(str(ex))
        if cursor.rowcount == 0:
            return error("Task post failed; poster returned nothing.")
        # TODO: Assert that rowcount is 1
        log.debug("All done: %s", base_url())
        return ok(base_url())

    elif request.method == 'DELETE':

        parsed = list(urlparse.urlsplit(request.url))
        parsed[1] = "%s"
        template = urlparse.urlunsplit(parsed)

        try:
            requester = task_requester(uuid)
            if requester is None:
                return not_found()

            if not access_write_ok(requester):
                return forbidden()

            cursor = dbcursor_query("SELECT api_task_disable(%s, %s)",
                                    [uuid, template])
            cursor.close()

        except Exception as ex:
            return error(str(ex))

        return ok()

    else:

        return not_allowed()
Exemplo n.º 18
0
def tasks():

    if request.method == 'GET':

        where_clause = "TRUE"
        args = []

        try:
            json_query = arg_json("json")
        except ValueError as ex:
            return bad_request(str(ex))

        if json_query is not None:
            where_clause += " AND task.json @> %s"
            args.append(request.args.get("json"))

        where_clause += " ORDER BY added"

        try:
            tasks = __tasks_get_filtered(request.base_url,
                                         where_clause=where_clause,
                                         args=args,
                                         expanded=is_expanded(),
                                         detail=arg_boolean("detail"),
                                         single=False)
        except Exception as ex:
            return error(str(ex))

        return ok_json(tasks)

    elif request.method == 'POST':

        try:
            task = pscheduler.json_load(request.data, max_schema=1)
        except ValueError as ex:
            return bad_request("Invalid task specification: %s" % (str(ex)))

        # Validate the JSON against a TaskSpecification
        # TODO: Figure out how to do this without the intermediate object

        valid, message = pscheduler.json_validate({"": task}, {
            "type": "object",
            "properties": {
                "": {
                    "$ref": "#/pScheduler/TaskSpecification"
                }
            },
            "required": [""]
        })

        if not valid:
            return bad_request("Invalid task specification: %s" % (message))

        # See if the test spec is valid

        try:
            returncode, stdout, stderr = pscheduler.run_program(
                [
                    "pscheduler", "internal", "invoke", "test",
                    task['test']['type'], "spec-is-valid"
                ],
                stdin=pscheduler.json_dump(task['test']['spec']))

            if returncode != 0:
                return error("Unable to validate test spec: %s" % (stderr))
            validate_json = pscheduler.json_load(stdout, max_schema=1)
            if not validate_json["valid"]:
                return bad_request(
                    "Invalid test specification: %s" %
                    (validate_json.get("error", "Unspecified error")))
        except Exception as ex:
            return error("Unable to validate test spec: " + str(ex))

        log.debug("Validated test: %s", pscheduler.json_dump(task['test']))

        # Reject tasks that have archive specs that use transforms.
        # See ticket #330.

        try:
            for archive in task['archives']:
                if "transform" in archive:
                    return bad_request(
                        "Use of transforms in archives is not yet supported.")
        except KeyError:
            pass  # Not there

        # Find the participants

        try:

            # HACK: BWCTLBC
            if "lead-bind" in task:
                lead_bind_env = {
                    "PSCHEDULER_LEAD_BIND_HACK": task["lead-bind"]
                }
            else:
                lead_bind_env = None

            returncode, stdout, stderr = pscheduler.run_program(
                [
                    "pscheduler", "internal", "invoke", "test",
                    task['test']['type'], "participants"
                ],
                stdin=pscheduler.json_dump(task['test']['spec']),
                timeout=5,
                env_add=lead_bind_env)

            if returncode != 0:
                return error("Unable to determine participants: " + stderr)

            participants = [
                host if host is not None else server_netloc() for host in
                pscheduler.json_load(stdout, max_schema=1)["participants"]
            ]
        except Exception as ex:
            return error("Exception while determining participants: " +
                         str(ex))
        nparticipants = len(participants)

        # TODO: The participants must be unique.  This should be
        # verified by fetching the host name from each one.

        #
        # TOOL SELECTION
        #

        lead_bind = task.get("lead-bind", None)

        # TODO: Need to provide for tool being specified by the task
        # package.

        tools = []

        tool_params = {"test": pscheduler.json_dump(task["test"])}
        # HACK: BWCTLBC
        if lead_bind is not None:
            log.debug("Using lead bind of %s" % str(lead_bind))
            tool_params["lead-bind"] = lead_bind

        for participant_no in range(0, len(participants)):

            participant = participants[participant_no]

            try:

                # Make sure the other participants are running pScheduler

                participant_api = pscheduler.api_url_hostport(participant)

                log.debug("Pinging %s" % (participant))
                status, result = pscheduler.url_get(participant_api,
                                                    throw=False,
                                                    timeout=10,
                                                    bind=lead_bind)

                if status == 400:
                    raise TaskPostingException(result)
                elif status in [ 202, 204, 205, 206, 207, 208, 226,
                                 300, 301, 302, 303, 304, 205, 306, 307, 308 ] \
                    or ( (status >= 400) and (status <=499) ):
                    raise TaskPostingException(
                        "Host is not running pScheduler")
                elif status != 200:
                    raise TaskPostingException("returned status %d: %s" %
                                               (status, result))

                # TODO: This will fail with a very large test spec.
                status, result = pscheduler.url_get("%s/tools" %
                                                    (participant_api),
                                                    params=tool_params,
                                                    throw=False,
                                                    bind=lead_bind)
                if status != 200:
                    raise TaskPostingException("%d: %s" % (status, result))
                tools.append(result)
            except TaskPostingException as ex:
                return error("Error getting tools from %s: %s" \
                                     % (participant, str(ex)))
            log.debug("Participant %s offers tools %s", participant, result)

        if len(tools) != nparticipants:
            return error("Didn't get a full set of tool responses")

        if "tools" in task:
            tool = pick_tool(tools, pick_from=task['tools'])
        else:
            tool = pick_tool(tools)

        if tool is None:
            # TODO: This could stand some additional diagnostics.
            return no_can_do(
                "Couldn't find a tool in common among the participants.")

        task['tool'] = tool

        #
        # TASK CREATION
        #

        tasks_posted = []

        # Evaluate the task against the limits and reject the request
        # if it doesn't pass.

        log.debug("Checking limits on %s", task["test"])

        (processor, whynot) = limitprocessor()
        if processor is None:
            log.debug("Limit processor is not initialized. %s", whynot)
            return no_can_do("Limit processor is not initialized: %s" % whynot)

        hints = request_hints()
        hints_data = pscheduler.json_dump(hints)

        log.debug("Processor = %s" % processor)
        passed, limits_passed, diags = processor.process(task["test"], hints)

        if not passed:
            return forbidden("Task forbidden by limits:\n" + diags)

        # Post the lead with the local database, which also assigns
        # its UUID.  Make it disabled so the scheduler doesn't try to
        # do anything with it until the task has been submitted to all
        # of the other participants.

        try:
            cursor = dbcursor_query(
                "SELECT * FROM api_task_post(%s, %s, %s, %s, 0, NULL, FALSE)",
                [
                    pscheduler.json_dump(task), participants, hints_data,
                    pscheduler.json_dump(limits_passed)
                ],
                onerow=True)
        except Exception as ex:
            return error(str(ex.diag.message_primary))

        if cursor.rowcount == 0:
            return error("Task post failed; poster returned nothing.")

        task_uuid = cursor.fetchone()[0]

        log.debug("Tasked lead, UUID %s", task_uuid)

        # Other participants get the UUID and participant list forced upon them.

        task["participants"] = participants
        task_data = pscheduler.json_dump(task)

        for participant in range(1, nparticipants):

            part_name = participants[participant]
            log.debug("Tasking participant %s", part_name)
            try:

                # Post the task

                log.debug("Tasking %d@%s: %s", participant, part_name,
                          task_data)
                post_url = pscheduler.api_url_hostport(part_name,
                                                       'tasks/' + task_uuid)
                log.debug("Posting task to %s", post_url)
                status, result = pscheduler.url_post(
                    post_url,
                    params={'participant': participant},
                    data=task_data,
                    bind=lead_bind,
                    json=False,
                    throw=False)
                log.debug("Remote returned %d: %s", status, result)
                if status != 200:
                    raise TaskPostingException(
                        "Unable to post task to %s: %s" % (part_name, result))
                tasks_posted.append(result)

                # Fetch the task's details and add the list of limits
                # passed to our own.

                status, result = pscheduler.url_get(post_url,
                                                    params={"detail": True},
                                                    bind=lead_bind,
                                                    throw=False)
                if status != 200:
                    raise TaskPostingException(
                        "Unable to fetch posted task from %s: %s" %
                        (part_name, result))
                log.debug("Fetched %s", result)
                try:
                    details = result["detail"]["spec-limits-passed"]
                    log.debug("Details from %s: %s", post_url, details)
                    limits_passed.extend(details)
                except KeyError:
                    pass

            except TaskPostingException as ex:

                # Disable the task locally and let it get rid of the
                # other participants.

                posted_to = "%s/%s" % (request.url, task_uuid)
                parsed = list(urlparse.urlsplit(posted_to))
                parsed[1] = "%s"
                template = urlparse.urlunsplit(parsed)

                try:
                    dbcursor_query("SELECT api_task_disable(%s, %s)",
                                   [task_uuid, template])
                except Exception:
                    log.exception()

                return error("Error while tasking %s: %s" % (part_name, ex))

        # Update the list of limits passed in the local database
        # TODO: How do the other participants know about this?
        log.debug("Limits passed: %s", limits_passed)
        try:
            cursor = dbcursor_query(
                "UPDATE task SET limits_passed = %s::JSON WHERE uuid = %s",
                [pscheduler.json_dump(limits_passed), task_uuid])
        except Exception as ex:
            return error(str(ex.diag.message_primary))

        # Enable the task so the scheduler will schedule it.
        try:
            dbcursor_query("SELECT api_task_enable(%s)", [task_uuid])
        except Exception as ex:
            log.exception()
            return error("Failed to enable task %s.  See system logs." %
                         task_uuid)
        log.debug("Task enabled for scheduling.")

        return ok_json("%s/%s" % (request.base_url, task_uuid))

    else:

        return not_allowed()
Exemplo n.º 19
0
    if len(raw_offset):
        offset = raw_offset[:3] + ":" + raw_offset[-2:]
    else:
        offset = ""

    result = {
        "time": time_here.strftime("%Y-%m-%dT%H:%M:%S.%f") + offset,
        "synchronized": system_synchronized
    }

    if system_synchronized:

        # Assume NTP for the time being

        try:
            ntp = ntplib.NTPClient().request("127.0.0.1")
            result["offset"] = ntp.offset
            result["source"] = "ntp"
            result["reference"] = "%s from %s" % (ntplib.stratum_to_text(
                ntp.stratum), ntplib.ref_id_to_text(ntp.ref_id))
        except Exception as ex:
            result["synchronized"] = False
            result["error"] = str(ex)

    return result


if __name__ == "__main__":
    import pscheduler
    print pscheduler.json_dump(clock_state(), pretty=True)
Exemplo n.º 20
0
def response_json_dump(dump):
    return pscheduler.json_dump(dump,
                                pretty=arg_boolean('pretty')
                                )
Exemplo n.º 21
0
def tasks_uuid(uuid):

    if not uuid_is_valid(uuid):
        return not_found()

    if request.method == 'GET':

        tasks = __tasks_get_filtered(request.base_url,
                                     where_clause="task.uuid = %s",
                                     args=[uuid],
                                     expanded=True,
                                     detail=arg_boolean("detail"),
                                     single=True)

        if not tasks:
            return not_found()

        return ok_json(tasks[0])

    elif request.method == 'POST':

        data = request.data.decode("ascii")

        log.debug("Posting to %s", uuid)
        log.debug("Data is %s", data)

        # TODO: This is only for participant 1+
        # TODO: This should probably a PUT and not a POST.

        try:
            json_in = pscheduler.json_load(data, max_schema=3)
        except ValueError as ex:
            return bad_request("Invalid JSON: %s" % str(ex))
        log.debug("JSON is %s", json_in)

        try:
            participant = arg_cardinal('participant')
        except ValueError as ex:
            return bad_request("Invalid participant: " + str(ex))
        log.debug("Participant %d", participant)

        # Evaluate the task against the limits and reject the request
        # if it doesn't pass.

        log.debug("Checking limits on task")

        processor, whynot = limitprocessor()
        if processor is None:
            message = "Limit processor is not initialized: %s" % whynot
            log.debug(message)
            return no_can_do(message)

        hints, error_response = request_hints()
        if hints is None:
            log.debug("Can't come up with valid hints for subordinate limits.")
            return error_response

        hints_data = pscheduler.json_dump(hints)

        # Only the lead rewrites tasks; everyone else just applies
        # limits.
        passed, limits_passed, diags, _new_task, priority \
            = processor.process(json_in, hints, rewrite=False)

        if not passed:
            return forbidden("Task forbidden by limits:\n" + diags)
        log.debug("Limits passed")

        # TODO: Pluck UUID from URI
        uuid = url_last_in_path(request.url)

        log.debug("Posting task %s", uuid)

        try:

            try:
                participants = pscheduler.json_load(
                    data, max_schema=3)["participants"]
            except Exception as ex:
                return bad_request("Task error: %s" % str(ex))
            cursor = dbcursor_query(
                "SELECT * FROM api_task_post(%s, %s, %s, %s, %s, %s, %s, TRUE, %s)",
                [
                    data, participants, hints_data,
                    pscheduler.json_dump(limits_passed), participant,
                    json_in.get("priority", None), uuid, "\n".join(diags)
                ])

        except Exception as ex:
            return bad_request("Task error: %s" % str(ex))

        if cursor.rowcount == 0:
            return error("Task post failed; poster returned nothing.")
        # TODO: Assert that rowcount is 1
        log.debug("All done: %s", base_url())
        return ok(base_url())

    elif request.method == 'DELETE':

        requester, key = task_requester_key(uuid)
        if requester is None:
            return not_found()

        if not access_write_task(requester, key):
            return forbidden()

        parsed = list(urllib.parse.urlsplit(request.url))
        parsed[1] = "%s"
        template = urllib.parse.urlunsplit(parsed)

        log.debug("Disabling")

        cursor = dbcursor_query("SELECT api_task_disable(%s, %s)",
                                [uuid, template])
        cursor.close()

        return ok()

    else:

        return not_allowed()
Exemplo n.º 22
0
def tasks():

    if request.method == 'GET':

        expanded = is_expanded()

        query = """
            SELECT json, uuid
            FROM task
            """
        args = []

        try:
            json_query = arg_json("json")
        except ValueError as ex:
            return bad_request(str(ex))

        if json_query is not None:
            query += "WHERE json @> %s"
            args.append(request.args.get("json"))

        query += " ORDER BY added"

        try:
            cursor = dbcursor_query(query, args)
        except Exception as ex:
            return error(str(ex))

        result = []
        for row in cursor:
            url = base_url(row[1])
            if not expanded:
                result.append(url)
                continue
            row[0]['href'] = url
            result.append(row[0])
        return json_response(result)

    elif request.method == 'POST':

        try:
            task = pscheduler.json_load(request.data)
        except ValueError:
            return bad_request("Invalid JSON:" + request.data)

        # TODO: Validate the JSON against a TaskSpecification


        # See if the task spec is valid

        try:
            returncode, stdout, stderr = pscheduler.run_program(
                [ "pscheduler", "internal", "invoke", "test",
                  task['test']['type'], "spec-is-valid" ],
                stdin = pscheduler.json_dump(task['test']['spec'])
                )

            if returncode != 0:
                return error("Invalid test specification: " + stderr)
        except Exception as ex:
            return error("Unable to validate test spec: " + str(ex))

        log.debug("Validated test: %s", pscheduler.json_dump(task['test']))


        # Find the participants

        try:
            returncode, stdout, stderr = pscheduler.run_program(
                [ "pscheduler", "internal", "invoke", "test",
                  task['test']['type'], "participants" ],
                stdin = pscheduler.json_dump(task['test']['spec'])
                )

            if returncode != 0:
                return error("Unable to determine participants: " + stderr)

            participants = [ host if host is not None
                             else pscheduler.api_this_host()
                             for host in pscheduler.json_load(stdout)["participants"] ]
        except Exception as ex:
            return error("Unable to determine participants: " + str(ex))
        nparticipants = len(participants)

        # TODO: The participants must be unique.  This should be
        # verified by fetching the host name from each one.

        #
        # TOOL SELECTION
        #

        # TODO: Need to provide for tool being specified by the task
        # package.

        tools = []

        for participant in participants:

            try:
                # TODO: This will fail with a very large test spec.
                status, result = pscheduler.url_get(
                    pscheduler.api_url(participant, "tools"),
                    params={ 'test': pscheduler.json_dump(task['test']) }
                    )
                if status != 200:
                    raise Exception("%d: %s" % (status, result))
                tools.append(result)
            except Exception as ex:
                return error("Error getting tools from %s: %s" \
                                     % (participant, str(ex)))
            log.debug("Participant %s offers tools %s", participant, tools)

        if len(tools) != nparticipants:
            return error("Didn't get a full set of tool responses")

        if "tools" in task:
            tool = pick_tool(tools, pick_from=task['tools'])
        else:
            tool = pick_tool(tools)

        if tool is None:
            # TODO: This could stand some additional diagnostics.
            return no_can_do("Couldn't find a tool in common among the participants.")

        task['tool'] = tool

        #
        # TASK CREATION
        #

        task_data = pscheduler.json_dump(task)
        log.debug("Task data: %s", task_data)

        tasks_posted = []

        # Evaluate the task against the limits and reject the request
        # if it doesn't pass.

        log.debug("Checking limits on %s", task["test"])

        (processor, whynot) = limitprocessor()
        if processor is None:
            log.debug("Limit processor is not initialized. %s", whynot)
            return no_can_do("Limit processor is not initialized: %s" % whynot)

        # TODO: This is cooked up in two places.  Make a function of it.
        hints = {
            "ip": request.remote_addr
            }
        hints_data = pscheduler.json_dump(hints)

        log.debug("Processor = %s" % processor)
        passed, diags = processor.process(task["test"], hints)

        if not passed:
            return forbidden("Task forbidden by limits:\n" + diags)

        # Post the lead with the local database, which also assigns
        # its UUID.  Make it disabled so the scheduler doesn't try to
        # do anything with it until the task has been submitted to all
        # of the other participants.

        try:
            cursor = dbcursor_query("SELECT * FROM api_task_post(%s, %s, 0, NULL, FALSE)",
                                    [task_data, hints_data], onerow=True)
        except Exception as ex:
            return error(str(ex.diag.message_primary))

        if cursor.rowcount == 0:
            return error("Task post failed; poster returned nothing.")

        task_uuid = cursor.fetchone()[0]

        log.debug("Tasked lead, UUID %s", task_uuid)

        # Other participants get the UUID forced upon them.

        for participant in range(1,nparticipants):
            part_name = participants[participant]
            try:
                log.debug("Tasking %d@%s: %s", participant, part_name, task_data)
                post_url = pscheduler.api_url(part_name,
                                              'tasks/' + task_uuid)
                log.debug("Posting task to %s", post_url)
                status, result = pscheduler.url_post(
                    post_url,
                    params={ 'participant': participant },
                    data=task_data,
                    json=False,
                    throw=False)
                log.debug("Remote returned %d: %s", status, result)
                if status != 200:
                    raise Exception("Unable to post task to %s: %s"
                                    % (part_name, result))
                tasks_posted.append(result)

            except Exception as ex:

                log.exception()

                for url in tasks_posted:
                    # TODO: Handle failure?
                    status, result = requests.delete(url)

                    try:
                        dbcursor_query("SELECT api_task_delete(%s)",
                                       [task_uuid])
                    except Exception as ex:
                        log.exception()

                return error("Error while tasking %d@%s: %s" % (participant, part_name, ex))


        # Enable the task so the scheduler will schedule it.
        try:
            dbcursor_query("SELECT api_task_enable(%s)", [task_uuid])
        except Exception as ex:
            log.exception()
            return error("Failed to enable task %s.  See system logs." % task_uuid)
        log.debug("Task enabled for scheduling.")

        return ok_json("%s/%s" % (request.base_url, task_uuid))

    else:

        return not_allowed()
Exemplo n.º 23
0
    def __init__(self, test, nparticipants, a, z, debug=False):
        """
        Construct a task runner
        """

        self.debug = debug

        self.results = {
            "hosts": {
                "a": a,
                "z": z
            },
            "nparticipants": nparticipants,
            "diags": []
        }
        self.diags = self.results["diags"]

        # Make sure we have sufficient pSchedulers to cover the participants
        if (nparticipants == 2) and ("pscheduler" not in z):
            # TODO: Assert that Z has a host?
            self.__diag("No pScheduler for or on %s." % (z["host"]))
            return

        # Fill in the test's blanks and construct a task spec

        test = copy.deepcopy(test)
        test = pscheduler.json_substitute(test, "__A__", a["pscheduler"])

        z_end = z["host"] if nparticipants == 1 else z.get(
            "pscheduler", z["host"])
        test = pscheduler.json_substitute(test, "__Z__", z_end)

        task = {
            "schema": 1,
            "test": test,
            # This is required; empty is fine.
            "schedule": {
                # TODO: Don't hard-wire this.
                "slip": "PT10M"
            }
        }

        # Post the task

        task_post = pscheduler.api_url(host=a["pscheduler"], path="/tasks")

        status, task_href = pscheduler.url_post(
            task_post, data=pscheduler.json_dump(task), throw=False)
        if status != 200:
            self.__diag("Unable to post task: %s" % (task_href))
            return

        self.__debug("Posted task %s" % (task_href))

        self.task_href = task_href

        # Get the task from the server with full details

        status, task_data = pscheduler.url_get(task_href,
                                               params={"detail": True},
                                               throw=False)
        if status != 200:
            self.__diag("Unable to get detailed task data: %s" % (task_data))
            return

        # Wait for the first run to be scheduled

        first_run_url = task_data["detail"]["first-run-href"]

        status, run_data = pscheduler.url_get(first_run_url, throw=False)

        if status == 404:
            self.__diag("The server never scheduled a run for the task.")
            return
        if status != 200:
            self.__diag("Error %d: %s" % (status, run_data))
            return

        for key in ["start-time", "end-time", "result-href"]:
            if key not in run_data:
                self.__diag("Server did not return %s with run data" % (key))
                return

        self.results["href"] = run_data["href"]
        self.run_data = run_data
        self.__debug(
            "Run times: %s to %s" \
            % (run_data["start-time"], run_data["end-time"]))

        self.worker = threading.Thread(target=lambda: self.run())
        self.worker.setDaemon(True)
        self.worker.start()
Exemplo n.º 24
0
def tasks():

    if request.method == 'GET':

        where_clause = "TRUE"
        args = []

        try:
            json_query = arg_json("json")
        except ValueError as ex:
            return bad_request(str(ex))

        if json_query is not None:
            where_clause += " AND task.json_detail @> %s"
            args.append(request.args.get("json"))

        where_clause += " ORDER BY added"

        tasks = __tasks_get_filtered(request.base_url,
                                     where_clause=where_clause,
                                     args=args,
                                     expanded=is_expanded(),
                                     detail=arg_boolean("detail"),
                                     single=False)

        return ok_json(tasks)

    elif request.method == 'POST':

        data = request.data.decode("ascii")

        try:
            task = pscheduler.json_load(data, max_schema=3)
        except ValueError as ex:
            return bad_request("Invalid task specification: %s" % (str(ex)))

        # Validate the JSON against a TaskSpecification
        # TODO: Figure out how to do this without the intermediate object

        valid, message = pscheduler.json_validate({"": task}, {
            "type": "object",
            "properties": {
                "": {
                    "$ref": "#/pScheduler/TaskSpecification"
                }
            },
            "required": [""]
        })

        if not valid:
            return bad_request("Invalid task specification: %s" % (message))

        # See if the test spec is valid

        try:
            returncode, stdout, stderr = pscheduler.plugin_invoke(
                "test",
                task['test']['type'],
                "spec-is-valid",
                stdin=pscheduler.json_dump(task['test']['spec']))

            if returncode != 0:
                return error("Unable to validate test spec: %s" % (stderr))
            validate_json = pscheduler.json_load(stdout, max_schema=1)
            if not validate_json["valid"]:
                return bad_request(
                    "Invalid test specification: %s" %
                    (validate_json.get("error", "Unspecified error")))
        except Exception as ex:
            return error("Unable to validate test spec: " + str(ex))

        log.debug("Validated test: %s", pscheduler.json_dump(task['test']))

        # Validate the schedule

        try:
            cron = crontab.CronTab(task["schedule"]["repeat-cron"])
        except (AttributeError, ValueError):
            return error("Cron repeat specification is invalid.")
        except KeyError:
            pass

        # Validate the archives

        for archive in task.get("archives", []):

            # Data

            try:
                returncode, stdout, stderr = pscheduler.plugin_invoke(
                    "archiver",
                    archive["archiver"],
                    "data-is-valid",
                    stdin=pscheduler.json_dump(archive["data"]),
                )
                if returncode != 0:
                    return error("Unable to validate archive spec: %s" %
                                 (stderr))
            except Exception as ex:
                return error("Unable to validate test spec: " + str(ex))

            try:
                returned_json = pscheduler.json_load(stdout)
                if not returned_json["valid"]:
                    return bad_request("Invalid archiver data: %s" %
                                       (returned_json["error"]))
            except Exception as ex:
                return error("Internal probelm validating archiver data: %s" %
                             (str(ex)))

            # Transform, if there was one.

            if "transform" in archive:
                transform = archive["transform"]
                try:
                    _ = pscheduler.JQFilter(filter_spec=transform["script"],
                                            args=transform.get("args", {}))

                except ValueError as ex:
                    return error("Invalid transform: %s" % (str(ex)))

        # Validate the lead binding if there was one.

        lead_bind = task.get("lead-bind", None)
        if lead_bind is not None \
           and (pscheduler.address_interface(lead_bind) is None):
            return bad_request("Lead bind '%s' is not  on this host" %
                               (lead_bind))

        # Evaluate the task against the limits and reject the request
        # if it doesn't pass.  We do this early so anything else in
        # the process gets any rewrites.

        log.debug("Checking limits on %s", task)

        (processor, whynot) = limitprocessor()
        if processor is None:
            log.debug("Limit processor is not initialized. %s", whynot)
            return no_can_do("Limit processor is not initialized: %s" % whynot)

        hints, error_response = request_hints()
        if hints is None:
            log.debug("Can't come up with valid hints for lead task limits.")
            return error_response

        hints_data = pscheduler.json_dump(hints)

        log.debug("Processor = %s" % processor)
        passed, limits_passed, diags, new_task, _priority \
            = processor.process(task, hints)

        if not passed:
            return forbidden("Task forbidden by limits:\n" + diags)

        if new_task is not None:
            try:
                task = new_task
                returncode, stdout, stderr = pscheduler.plugin_invoke(
                    "test",
                    task['test']['type'],
                    "spec-is-valid",
                    stdin=pscheduler.json_dump(task["test"]["spec"]))

                if returncode != 0:
                    return error(
                        "Failed to validate rewritten test specification: %s" %
                        (stderr))
                validate_json = pscheduler.json_load(stdout, max_schema=1)
                if not validate_json["valid"]:
                    return bad_request(
                        "Rewritten test specification is invalid: %s" %
                        (validate_json.get("error", "Unspecified error")))
            except Exception as ex:
                return error(
                    "Unable to validate rewritten test specification: " +
                    str(ex))

        # Find the participants

        try:

            returncode, stdout, stderr = pscheduler.plugin_invoke(
                "test",
                task['test']['type'],
                "participants",
                stdin=pscheduler.json_dump(task['test']['spec']),
                timeout=5)

            if returncode != 0:
                return error("Unable to determine participants: " + stderr)

            participants = [
                host if host is not None else server_netloc() for host in
                pscheduler.json_load(stdout, max_schema=1)["participants"]
            ]
        except Exception as ex:
            return error("Exception while determining participants: " +
                         str(ex))
        nparticipants = len(participants)

        # TODO: The participants must be unique.  This should be
        # verified by fetching the host name from each one.

        #
        # TOOL SELECTION
        #

        # TODO: Need to provide for tool being specified by the task
        # package.

        tools = []

        tool_params = {"test": pscheduler.json_dump(task["test"])}

        tool_offers = {}

        for participant_no in range(0, len(participants)):

            participant = participants[participant_no]

            try:

                # Make sure the other participants are running pScheduler

                participant_api = pscheduler.api_url_hostport(participant)

                log.debug("Pinging %s" % (participant))
                status, result = pscheduler.url_get(participant_api,
                                                    throw=False,
                                                    timeout=10,
                                                    bind=lead_bind)

                if status == 400:
                    raise TaskPostingException(result)
                elif status in [ 202, 204, 205, 206, 207, 208, 226,
                                 300, 301, 302, 303, 304, 205, 306, 307, 308 ] \
                    or ( (status >= 400) and (status <=499) ):
                    raise TaskPostingException(
                        "Host is not running pScheduler")
                elif status != 200:
                    raise TaskPostingException("returned status %d: %s" %
                                               (status, result))

                # TODO: This will fail with a very large test spec.
                status, result = pscheduler.url_get("%s/tools" %
                                                    (participant_api),
                                                    params=tool_params,
                                                    throw=False,
                                                    bind=lead_bind)
                if status != 200:
                    raise TaskPostingException("%d: %s" % (status, result))
                tools.append(result)
            except TaskPostingException as ex:
                return error("Error getting tools from %s: %s" \
                                     % (participant, str(ex)))
            log.debug("Participant %s offers tools %s", participant, result)
            tool_offers[participant] = result

        if len(tools) != nparticipants:
            return error("Didn't get a full set of tool responses")

        if "tools" in task:
            tool = pick_tool(tools, pick_from=task['tools'])
        else:
            tool = pick_tool(tools)

        # Complain if no usable tool was found

        if tool is None:

            offers = []
            for participant in participants:
                participant_offers = tool_offers.get(participant,
                                                     [{
                                                         "name": "nothing"
                                                     }])
                if participant_offers is not None:
                    offer_set = [offer["name"] for offer in participant_offers]
                else:
                    offer_set = ["nothing"]
                offers.append("%s offered %s" %
                              (participant, ", ".join(offer_set)))

            return no_can_do("No tool in common among the participants:  %s." %
                             (";  ".join(offers)))

        task['tool'] = tool

        #
        # TASK CREATION
        #

        tasks_posted = []

        # Post the lead with the local database, which also assigns
        # its UUID.  Make it disabled so the scheduler doesn't try to
        # do anything with it until the task has been submitted to all
        # of the other participants.

        cursor = dbcursor_query(
            "SELECT * FROM api_task_post(%s, %s, %s, %s, 0, %s, NULL, FALSE, %s)",
            [
                pscheduler.json_dump(task), participants, hints_data,
                pscheduler.json_dump(limits_passed),
                task.get("priority", None), diags
            ],
            onerow=True)

        if cursor.rowcount == 0:
            return error("Task post failed; poster returned nothing.")

        task_uuid = cursor.fetchone()[0]

        log.debug("Tasked lead, UUID %s", task_uuid)

        # Other participants get the UUID and participant list forced upon them.

        task["participants"] = participants

        task_params = {"key": task["_key"]} if "_key" in task else {}

        for participant in range(1, nparticipants):

            part_name = participants[participant]
            log.debug("Tasking participant %s", part_name)
            try:

                # Post the task

                log.debug("Tasking %d@%s: %s", participant, part_name, task)
                post_url = pscheduler.api_url_hostport(part_name,
                                                       'tasks/' + task_uuid)

                task_params["participant"] = participant

                log.debug("Posting task to %s", post_url)
                status, result = pscheduler.url_post(post_url,
                                                     params=task_params,
                                                     data=task,
                                                     bind=lead_bind,
                                                     json=False,
                                                     throw=False)
                log.debug("Remote returned %d: %s", status, result)
                if status != 200:
                    raise TaskPostingException(
                        "Unable to post task to %s: %s" % (part_name, result))
                tasks_posted.append(result)

                # Fetch the task's details and add the list of limits
                # passed to our own.

                status, result = pscheduler.url_get(post_url,
                                                    params={"detail": True},
                                                    bind=lead_bind,
                                                    throw=False)
                if status != 200:
                    raise TaskPostingException(
                        "Unable to fetch posted task from %s: %s" %
                        (part_name, result))
                log.debug("Fetched %s", result)
                try:
                    details = result["detail"]["spec-limits-passed"]
                    log.debug("Details from %s: %s", post_url, details)
                    limits_passed.extend(details)
                except KeyError:
                    pass

            except TaskPostingException as ex:

                # Disable the task locally and let it get rid of the
                # other participants.

                posted_to = "%s/%s" % (request.url, task_uuid)
                parsed = list(urllib.parse.urlsplit(posted_to))
                parsed[1] = "%s"
                template = urllib.parse.urlunsplit(parsed)

                try:
                    dbcursor_query("SELECT api_task_disable(%s, %s)",
                                   [task_uuid, template])
                except Exception:
                    log.exception()

                return error("Error while tasking %s: %s" % (part_name, ex))

        # Update the list of limits passed in the local database
        # TODO: How do the other participants know about this?
        log.debug("Limits passed: %s", limits_passed)
        cursor = dbcursor_query(
            "UPDATE task SET limits_passed = %s::JSON WHERE uuid = %s",
            [pscheduler.json_dump(limits_passed), task_uuid])

        # Enable the task so the scheduler will schedule it.
        try:
            dbcursor_query("SELECT api_task_enable(%s)", [task_uuid])
        except Exception:
            log.exception()
            return error("Failed to enable task %s.  See system logs." %
                         task_uuid)
        log.debug("Task enabled for scheduling.")

        task_url = "%s/%s" % (request.base_url, task_uuid)

        # Non-expanded gets just the URL
        if not arg_boolean("expanded"):
            return ok_json(task_url)

        # Expanded gets a redirect to GET+expanded

        params = []
        for arg in ["detail", "pretty"]:
            if arg_boolean(arg):
                params.append(arg)

        if params:
            task_url += "?%s" % ("&".join(params))

        return see_other(task_url)

    else:

        return not_allowed()
Exemplo n.º 25
0
def tasks_uuid_runs_run_result(task, run):

    if task is None:
        return bad_request("Missing or invalid task")

    if run is None:
        return bad_request("Missing or invalid run")

    wait = arg_boolean('wait')

    format = request.args.get('format')

    if format is None:
        format = 'application/json'

    if format not in [ 'application/json', 'text/html', 'text/plain' ]:
        return bad_request("Unsupported format " + format)

    # If asked for 'first', dig up the first run and use its UUID.
    # This is more for debug convenience than anything else.
    if run == 'first':
        try:
            run = __runs_first_run(task)
        except Exception as ex:
            log.exception()
            return error(str(ex))
        if run is None:
            return not_found()



    #
    # Camp on the run for a result
    #

    # 40 tries at 0.25s intervals == 10 sec.
    tries = 40 if wait else 1

    while tries:

        try:
            cursor = dbcursor_query("""
                SELECT
                    test.name,
                    run.result_merged,
                    task.json #> '{test, spec}'
                FROM
                    run
                    JOIN task ON task.id = run.task
                    JOIN test ON test.id = task.test
                WHERE
                    task.uuid = %s
                    AND run.uuid = %s
                """, [task, run])
        except Exception as ex:
            log.exception()
            return error(str(ex))

        if cursor.rowcount == 0:
            return not_found()

        # TODO: Make sure we got back one row with two columns.
        row = cursor.fetchone()

        if not wait and row[1] is None:
            time.sleep(0.25)
            tries -= 1
        else:
            break

    if tries == 0:
        return not_found()


    test_type, merged_result, test_spec = row


    # JSON requires no formatting.
    if format == 'application/json':
        return ok_json(merged_result)

    if not merged_result['succeeded']:
        if format == 'text/plain':
            return ok("Test failed.", mimetype=format)
        elif format == 'text/html':
            return ok("<p>Test failed.</p>", mimetype=format)
        return error("Unsupported format " + format)

    formatter_input = {
        "spec": test_spec,
        "result": merged_result
        }

    returncode, stdout, stderr = pscheduler.run_program(
        [ "pscheduler", "internal", "invoke", "test", test_type,
          "result-format", format ],
        stdin = pscheduler.json_dump(formatter_input)
        )

    if returncode != 0:
        return error("Failed to format result: " + stderr)

    return ok(stdout.rstrip(), mimetype=format)
Exemplo n.º 26
0
    def __init__(self, calls, argv=[], stdin=None):
        """The 'calls' argument is an array of dictionaries.  Each dictionary
        contains an entry called "call," a string that names the program
        to be run, and another called "input" which is an arbitrary blob
        of data (usually a dictionary) that can be converted into JSON and
        passed through to the program's standard input.

        The format for the program's input is that for a pScheduler
        context plugin's "change" method.  It consists of two items:
        "data", which is an arbitrary blob of (JSON) data for the
        program to use and "exec," a string indicating the path to the
        program that should be exec'd when the program completes
        successfully.

        For example:

        {
            "program": "/run/this/program",
            "input": {
                "data": { "foo": "bar", "baz": 31415 },
                "exec": "/run/that/program"
            }
        }

        The "argv" and "stdin" are program parameters and standard
        input with the same semantics as the same arguments to
        pscheduler.run_program().
        """

        if not isinstance(calls, list):
            raise ValueError("Calls must be a list.")

        self.stages = []

        if not calls:
            return

        # Create the temporary files that will hold the scripts.

        try:

            # Create a list of temporary files ahead of time so the
            # stage n script can refer to the one for stage n+1.  The
            # extra added on is the "final" stage where the program to
            # be run is actually run.

            for _ in range(0, len(calls) + 1):

                (fileno, path) = tempfile.mkstemp(prefix="ContextedRunner-")
                os.close(fileno)
                os.chmod(path, stat.S_IRWXU)
                self.stages.append(path)

            # Write the scripts

            for stage in range(0, len(calls)):

                stage_script = "#!/bin/sh -e\n" \
                               "exec %s <<'EOF'\n" \
                               "%s\n" \
                               "EOF\n" % (
                                   " ".join([quote(arg) for arg in calls[stage]["program"]]),
                                   pscheduler.json_dump({
                                       "data": calls[stage]["input"],
                                       "exec": self.stages[stage+1]
                                   }))

                with open(self.stages[stage], "w") as output:
                    output.write(stage_script)

            # Write the "final" stage

            with open(self.stages[-1], "w") as output:
                output.write("#!/bin/sh -e\n"
                             "exec %s" %
                             (" ".join([quote(arg) for arg in argv])))

                if stdin is not None:
                    output.write(" <<'EOF'\n"
                                 "%s%s"
                                 "EOF\n" %
                                 (stdin, "" if stdin[-1] == "\n" else "\n"))
                else:
                    # PORT: This is Unix-specific.
                    output.write(" < /dev/null\n")

        except Exception as ex:

            for remove in self.stages:
                try:
                    os.unlink(remove)
                except IOError:
                    pass  # This is best effort only.

            raise ex
Exemplo n.º 27
0
def response_json_dump(dump, sanitize=True):
    if sanitize:
        sanitized = pscheduler.json_decomment(dump, prefix="_", null=True)
        return pscheduler.json_dump(sanitized, pretty=arg_boolean('pretty'))
    else:
        return pscheduler.json_dump(dump, pretty=arg_boolean('pretty'))
Exemplo n.º 28
0
def tasks_uuid_runs_run(task, run):

    if not uuid_is_valid(task):
        return not_found()

    if ((request.method in ['PUT', 'DELETE'] and not uuid_is_valid(run))
            or (run not in ['first', 'next'] and not uuid_is_valid(run))):
        return not_found()

    if request.method == 'GET':

        # Wait for there to be a local result
        wait_local = arg_boolean('wait-local')

        # Wait for there to be a merged result
        wait_merged = arg_boolean('wait-merged')

        if wait_local and wait_merged:
            return bad_request("Cannot wait on local and merged results")

        # Figure out how long to wait in seconds.  Zero means don't
        # wait.

        wait_time = arg_integer('wait')
        if wait_time is None:
            wait_time = 30
        if wait_time < 0:
            return bad_request("Wait time must be >= 0")

        # If asked for 'first', dig up the first run and use its UUID.

        if run in ['next', 'first']:
            future = run == 'next'
            wait_interval = 0.5
            tries = int(wait_time / wait_interval) if wait_time > 0 \
                    else 1
            while tries > 0:
                run = __runs_first_run(task, future)
                if run is not None:
                    break
                if wait_time > 0:
                    time.sleep(1.0)
                tries -= 1

            if run is None:
                return not_found()

        # Obey the wait time with tries at 0.5s intervals
        tries = wait_time * 2 if (wait_local or wait_merged) else 1
        result = {}

        while tries:

            try:
                cursor = dbcursor_query(
                    """
                    SELECT
                        run_json(run.id),
                        run_state.finished
                    FROM
                        task
                        JOIN run ON task.id = run.task
                        JOIN run_state ON run_state.id = run.state
                    WHERE 
                        task.uuid = %s
                        AND run.uuid = %s
                    """, [task, run])
            except Exception as ex:
                log.exception()
                return error(str(ex))

            if cursor.rowcount == 0:
                cursor.close()
                return not_found()

            result, finished = cursor.fetchone()
            cursor.close()

            if not (wait_local or wait_merged):
                break
            else:
                if (wait_local and result['result'] is None) \
                   or (wait_merged \
                       and ( (result['result-full'] is None) or (not finished) ) ):
                    log.debug("Waiting (%d left) for merged: %s %s", tries,
                              result['result-full'], finished)
                    time.sleep(0.5)
                    tries -= 1
                else:
                    log.debug("Got the requested result.")
                    break

        # Even if we timed out waiting, return the last result we saw
        # and let the client sort it out.

        # This strips any query parameters and replaces the last item
        # with the run, which might be needed if the 'first' option
        # was used.

        href_path_parts = urllib.parse.urlparse(request.url).path.split('/')
        href_path_parts[-1] = run
        href_path = '/'.join(href_path_parts)
        href = urllib.parse.urljoin(request.url, href_path)

        result['href'] = href
        result['task-href'] = root_url('tasks/' + task)
        result['result-href'] = href + '/result'

        # For a NULL first participant, fill in the netloc.

        try:
            if result['participants'][0] is None:
                result['participants'][0] = server_netloc()
        except KeyError:
            pass  # Not there?  Don't care.

        return json_response(result)

    elif request.method == 'PUT':

        data = request.data.decode("ascii")

        log.debug("Run PUT %s", request.url)

        requester, key = task_requester_key(task)

        if requester is None:
            return not_found()

        if not access_write_task(requester, key):
            return forbidden()

        # Get the JSON from the body
        try:
            run_data = pscheduler.json_load(data, max_schema=1)
        except ValueError:
            log.exception()
            log.debug("Run data was %s", data)
            return bad_request("Invalid or missing run data")

        # If the run doesn't exist, take the whole thing as if it were
        # a POST.

        cursor = dbcursor_query(
            "SELECT EXISTS (SELECT * FROM run WHERE uuid = %s)", [run],
            onerow=True)

        fetched = cursor.fetchone()[0]
        cursor.close()
        if not fetched:

            log.debug("Record does not exist; full PUT.")

            try:
                start_time = \
                    pscheduler.iso8601_as_datetime(run_data['start-time'])
            except KeyError:
                return bad_request("Missing start time")
            except ValueError:
                return bad_request("Invalid start time")

            try:

                passed, diags, response, priority \
                    = __evaluate_limits(task, start_time)
                if response is not None:
                    return response

                if passed:
                    diag_message = None
                else:
                    diag_message = "Run forbidden by limits:\n%s" % (diags)

                cursor = dbcursor_query(
                    "SELECT * FROM api_run_post(%s, %s, %s, %s, %s, %s)",
                    [task, start_time, run, diag_message, priority, diags],
                    onerow=True)
                succeeded, uuid, conflicts, error_message = cursor.fetchone()
                cursor.close()
                if conflicts:
                    return conflict(error_message)
                if not succeeded:
                    return error(error_message)
                log.debug("Full put of %s, got back %s", run, uuid)
            except Exception as ex:
                log.exception()
                return error(str(ex))

            return ok()

        # For anything else, only one thing can be udated at a time,
        # and even that is a select subset.

        log.debug("Record exists; partial PUT.")

        if 'part-data-full' in run_data:

            log.debug("Updating part-data-full from %s", run_data)

            try:
                part_data_full = \
                    pscheduler.json_dump(run_data['part-data-full'])
            except KeyError:
                return bad_request("Missing part-data-full")
            except ValueError:
                return bad_request("Invalid part-data-full")

            log.debug("Full data is: %s", part_data_full)

            cursor = dbcursor_query(
                """
                          UPDATE
                              run
                          SET
                              part_data_full = %s
                          WHERE
                              uuid = %s
                              AND EXISTS (SELECT * FROM task WHERE UUID = %s)
                          """, [part_data_full, run, task])

            rowcount = cursor.rowcount
            cursor.close()
            if rowcount != 1:
                return not_found()

            log.debug("Full data updated")

            return ok()

        elif 'result-full' in run_data:

            log.debug("Updating result-full from %s", run_data)

            try:
                result_full = \
                    pscheduler.json_dump(run_data['result-full'])
            except KeyError:
                return bad_request("Missing result-full")
            except ValueError:
                return bad_request("Invalid result-full")

            try:
                succeeded = bool(run_data['succeeded'])
            except KeyError:
                return bad_request("Missing success value")
            except ValueError:
                return bad_request("Invalid success value")

            log.debug("Updating result-full: JSON %s", result_full)
            log.debug("Updating result-full: Run  %s", run)
            log.debug("Updating result-full: Task %s", task)
            cursor = dbcursor_query(
                """
                          UPDATE
                              run
                          SET
                              result_full = %s,
                              state = CASE %s
                                  WHEN TRUE THEN run_state_finished()
                                  ELSE run_state_failed()
                                  END
                          WHERE
                              uuid = %s
                              AND EXISTS (SELECT * FROM task WHERE UUID = %s)
                          """, [result_full, succeeded, run, task])

            rowcount = cursor.rowcount
            cursor.close()
            if rowcount != 1:
                return not_found()

            return ok()

    elif request.method == 'DELETE':

        # TODO: If this is the lead, the run's counterparts on the
        # other participating nodes need to be removed as well.

        requester, key = task_requester_key(task)
        if requester is None:
            return not_found()

        if not access_write_task(requester, key):
            return forbidden()

        cursor = dbcursor_query(
            """
        DELETE FROM run
        WHERE
            task in (SELECT id FROM task WHERE uuid = %s)
            AND uuid = %s 
        """, [task, run])

        rowcount = cursor.rowcount
        cursor.close()

        return ok() if rowcount == 1 else not_found()

    else:

        return not_allowed()
Exemplo n.º 29
0
    raw_offset = time_here.strftime("%z")
    if len(raw_offset):
        offset = raw_offset[:3] + ":" + raw_offset[-2:]
    else:
        offset = ""

    result = {"time": time_here.strftime("%Y-%m-%dT%H:%M:%S.%f") + offset, "synchronized": system_synchronized}

    if system_synchronized:

        # Assume NTP for the time being

        try:
            ntp = ntplib.NTPClient().request("127.0.0.1")
            result["offset"] = ntp.offset
            result["source"] = "ntp"
            result["reference"] = "%s from %s" % (
                ntplib.stratum_to_text(ntp.stratum),
                ntplib.ref_id_to_text(ntp.ref_id),
            )
        except Exception as ex:
            result["error"] = str(ex)

    return result


if __name__ == "__main__":
    import pscheduler

    print pscheduler.json_dump(clock_state(), pretty=True)
Exemplo n.º 30
0
def tasks_uuid_runs_run_result(task, run):

    if not uuid_is_valid(task) or not uuid_is_valid(run):
        return not_found()

    wait = arg_boolean('wait')

    format = request.args.get('format')

    if format is None:
        format = 'application/json'

    if format not in ['application/json', 'text/html', 'text/plain']:
        return bad_request("Unsupported format " + format)

    # If asked for 'first', dig up the first run and use its UUID.
    # This is more for debug convenience than anything else.
    if run == 'first':
        run = __runs_first_run(task)
        if run is None:
            return not_found()

    #
    # Camp on the run for a result
    #

    # 40 tries at 0.25s intervals == 10 sec.
    tries = 40 if wait else 1

    while tries:

        cursor = dbcursor_query(
            """
            SELECT
                test.name,
                run.result_merged,
                task.json #> '{test, spec}'
            FROM
                run
                JOIN task ON task.id = run.task
                JOIN test ON test.id = task.test
            WHERE
                task.uuid = %s
                AND run.uuid = %s
            """, [task, run])

        if cursor.rowcount == 0:
            cursor.close()
            return not_found()

        # TODO: Make sure we got back one row with two columns.
        row = cursor.fetchone()
        cursor.close()

        if not wait and row[1] is None:
            time.sleep(0.25)
            tries -= 1
        else:
            break

    if tries == 0:
        return not_found()

    test_type, merged_result, test_spec = row

    # JSON requires no formatting.
    if format == 'application/json':
        return ok_json(merged_result)

    if not merged_result['succeeded']:
        if format == 'text/plain':
            return ok("Run failed.", mimetype=format)
        elif format == 'text/html':
            return ok("<p>Run failed.</p>", mimetype=format)
        return bad_request("Unsupported format " + format)

    formatter_input = {"spec": test_spec, "result": merged_result}

    returncode, stdout, stderr = pscheduler.plugin_invoke(
        "test",
        test_type,
        "result-format",
        argv=[format],
        stdin=pscheduler.json_dump(formatter_input))

    if returncode != 0:
        return error("Failed to format result: " + stderr)

    return ok(stdout.rstrip(), mimetype=format)
Exemplo n.º 31
0
def tasks_uuid_runs_run(task, run):

    if not uuid_is_valid(task):
        return not_found()

    if ((request.method in ['PUT', 'DELETE'] and not uuid_is_valid(run))
            or (run not in ['first', 'next'] and not uuid_is_valid(run))):
        return not_found()

    if request.method == 'GET':

        # Wait for there to be a local result
        wait_local = arg_boolean('wait-local')

        # Wait for there to be a merged result
        wait_merged = arg_boolean('wait-merged')

        if wait_local and wait_merged:
            return bad_request("Cannot wait on local and merged results")

        # Figure out how long to wait in seconds.  Zero means don't
        # wait.

        wait_time = arg_integer('wait')
        if wait_time is None:
            wait_time = 30
        if wait_time < 0:
            return bad_request("Wait time must be >= 0")

        # If asked for 'first', dig up the first run and use its UUID.

        if run in ['next', 'first']:
            future = run == 'next'
            wait_interval = 0.5
            tries = int(wait_time / wait_interval) if wait_time > 0 \
                    else 1
            while tries > 0:
                try:
                    run = __runs_first_run(task, future)
                except Exception as ex:
                    log.exception()
                    return error(str(ex))
                if run is not None:
                    break
                if wait_time > 0:
                    time.sleep(1.0)
                tries -= 1

            if run is None:
                return not_found()

        # 60 tries at 0.5s intervals == 30 sec.
        tries = 60 if (wait_local or wait_merged) else 1

        while tries:

            try:
                cursor = dbcursor_query(
                    """
                    SELECT
                        lower(run.times),
                        upper(run.times),
                        upper(run.times) - lower(run.times),
                        task.participant,
                        task.nparticipants,
                        task.participants,
                        run.part_data,
                        run.part_data_full,
                        run.result,
                        run.result_full,
                        run.result_merged,
                        run_state.enum,
                        run_state.display,
                        run.errors,
                        run.clock_survey,
                        run.id,
                        archiving_json(run.id),
                        run.added
                    FROM
                        run
                        JOIN task ON task.id = run.task
                        JOIN run_state ON run_state.id = run.state
                    WHERE
                        task.uuid = %s
                        AND run.uuid = %s""", [task, run])
            except Exception as ex:
                log.exception()
                return error(str(ex))

            if cursor.rowcount == 0:
                cursor.close()
                return not_found()

            row = cursor.fetchone()
            cursor.close()

            if not (wait_local or wait_merged):
                break
            else:
                if (wait_local and row[7] is None) \
                        or (wait_merged and row[9] is None):
                    time.sleep(0.5)
                    tries -= 1
                else:
                    break

        # Return a result Whether or not we timed out and let the
        # client sort it out.

        result = {}

        # This strips any query parameters and replaces the last item
        # with the run, which might be needed if the 'first' option
        # was used.

        href_path_parts = urlparse.urlparse(request.url).path.split('/')
        href_path_parts[-1] = run
        href_path = '/'.join(href_path_parts)
        href = urlparse.urljoin(request.url, href_path)

        result['href'] = href
        result['start-time'] = pscheduler.datetime_as_iso8601(row[0])
        result['end-time'] = pscheduler.datetime_as_iso8601(row[1])
        result['duration'] = pscheduler.timedelta_as_iso8601(row[2])
        participant_num = row[3]
        result['participant'] = participant_num
        result['participants'] = [
            server_netloc()
            if participant is None and participant_num == 0 else participant
            for participant in row[5]
        ]
        result['participant-data'] = row[6]
        result['participant-data-full'] = row[7]
        result['result'] = row[8]
        result['result-full'] = row[9]
        result['result-merged'] = row[10]
        result['state'] = row[11]
        result['state-display'] = row[12]
        result['errors'] = row[13]
        if row[14] is not None:
            result['clock-survey'] = row[14]
        if row[16] is not None:
            result['archivings'] = row[16]
        if row[17] is not None:
            result['added'] = pscheduler.datetime_as_iso8601(row[17])
        result['task-href'] = root_url('tasks/' + task)
        result['result-href'] = href + '/result'

        return json_response(result)

    elif request.method == 'PUT':

        log.debug("Run PUT %s", request.url)

        # Get the JSON from the body
        try:
            run_data = pscheduler.json_load(request.data, max_schema=1)
        except ValueError:
            log.exception()
            log.debug("Run data was %s", request.data)
            return bad_request("Invalid or missing run data")

        # If the run doesn't exist, take the whole thing as if it were
        # a POST.

        try:
            cursor = dbcursor_query(
                "SELECT EXISTS (SELECT * FROM run WHERE uuid = %s)", [run],
                onerow=True)
        except Exception as ex:
            log.exception()
            return error(str(ex))

        fetched = cursor.fetchone()[0]
        cursor.close()
        if not fetched:

            log.debug("Record does not exist; full PUT.")

            try:
                start_time = \
                    pscheduler.iso8601_as_datetime(run_data['start-time'])
            except KeyError:
                return bad_request("Missing start time")
            except ValueError:
                return bad_request("Invalid start time")

            try:

                passed, diags, response = __evaluate_limits(task, start_time)
                if response is not None:
                    return response

                cursor = dbcursor_query(
                    "SELECT * FROM api_run_post(%s, %s, %s)",
                    [task, start_time, run],
                    onerow=True)
                succeeded, uuid, conflicts, error_message = cursor.fetchone()
                cursor.close()
                if conflicts:
                    return conflict(error_message)
                if not succeeded:
                    return error(error_message)
                log.debug("Full put of %s, got back %s", run, uuid)
            except Exception as ex:
                log.exception()
                return error(str(ex))

            return ok()

        # For anything else, only one thing can be udated at a time,
        # and even that is a select subset.

        log.debug("Record exists; partial PUT.")

        if 'part-data-full' in run_data:

            log.debug("Updating part-data-full from %s", run_data)

            try:
                part_data_full = \
                    pscheduler.json_dump(run_data['part-data-full'])
            except KeyError:
                return bad_request("Missing part-data-full")
            except ValueError:
                return bad_request("Invalid part-data-full")

            log.debug("Full data is: %s", part_data_full)

            try:
                cursor = dbcursor_query(
                    """
                              UPDATE
                                  run
                              SET
                                  part_data_full = %s
                              WHERE
                                  uuid = %s
                                  AND EXISTS (SELECT * FROM task WHERE UUID = %s)
                              """, [part_data_full, run, task])
            except Exception as ex:
                log.exception()
                return error(str(ex))

            rowcount = cursor.rowcount
            cursor.close()
            if rowcount != 1:
                return not_found()

            log.debug("Full data updated")

            return ok()

        elif 'result-full' in run_data:

            log.debug("Updating result-full from %s", run_data)

            try:
                result_full = \
                    pscheduler.json_dump(run_data['result-full'])
            except KeyError:
                return bad_request("Missing result-full")
            except ValueError:
                return bad_request("Invalid result-full")

            try:
                succeeded = bool(run_data['succeeded'])
            except KeyError:
                return bad_request("Missing success value")
            except ValueError:
                return bad_request("Invalid success value")

            log.debug("Updating result-full: JSON %s", result_full)
            log.debug("Updating result-full: Run  %s", run)
            log.debug("Updating result-full: Task %s", task)
            try:
                cursor = dbcursor_query(
                    """
                              UPDATE
                                  run
                              SET
                                  result_full = %s,
                                  state = CASE %s
                                      WHEN TRUE THEN run_state_finished()
                                      ELSE run_state_failed()
                                      END
                              WHERE
                                  uuid = %s
                                  AND EXISTS (SELECT * FROM task WHERE UUID = %s)
                              """, [result_full, succeeded, run, task])
            except Exception as ex:
                log.exception()
                return error(str(ex))

            rowcount = cursor.rowcount
            cursor.close()
            if rowcount != 1:
                return not_found()

            return ok()

    elif request.method == 'DELETE':

        # TODO: If this is the lead, the run's counterparts on the
        # other participating nodes need to be removed as well.

        try:
            requester = task_requester(task)
            if requester is None:
                return not_found()

            if not access_write_ok(requester):
                return forbidden()

        except Exception as ex:
            return error(str(ex))

        try:
            cursor = dbcursor_query(
                """
            DELETE FROM run
            WHERE
                task in (SELECT id FROM task WHERE uuid = %s)
                AND uuid = %s 
            """, [task, run])
        except Exception as ex:
            log.exception()
            return error(str(ex))

        rowcount = cursor.rowcount
        cursor.close()

        return ok() if rowcount == 1 else not_found()

    else:

        return not_allowed()
Exemplo n.º 32
0
def tasks_uuid(uuid):
    if request.method == 'GET':

        # Get a task, adding server-derived details if a 'detail'
        # argument is present.

        try:
            cursor = dbcursor_query("""
                SELECT
                    task.json,
                    task.added,
                    task.start,
                    task.slip,
                    task.duration,
                    task.post,
                    task.runs,
                    task.participants,
                    scheduling_class.anytime,
                    scheduling_class.exclusive,
                    scheduling_class.multi_result,
                    task.participant,
                    task.enabled,
                    task.cli
                FROM
                    task
                    JOIN test ON test.id = task.test
                    JOIN scheduling_class
                        ON scheduling_class.id = test.scheduling_class
                WHERE uuid = %s
            """, [uuid])
        except Exception as ex:
            return error(str(ex))

        if cursor.rowcount == 0:
            return not_found()

        row = cursor.fetchone()
        if row is None:
            return not_found()
        json = row[0]

        # Redact anything in the test spec or archivers that's marked
        # private as well as _key at the top level if there is one.

        if "_key" in json:
            json["_key"] = None 

        json["test"]["spec"] = pscheduler.json_decomment(
            json["test"]["spec"], prefix="_", null=True)

        try:
            for archive in range(0,len(json["archives"])):
                json["archives"][archive]["data"] = pscheduler.json_decomment(
                    json["archives"][archive]["data"], prefix="_", null=True)
        except KeyError:
            pass  # Don't care if not there.

        # Add details if we were asked for them.

        if arg_boolean('detail'):

            part_list = row[7];
            # The database is not supposed to allow this, but spit out
            # a sane default as a last resort in case it happens.
            if part_list is None:
                part_list = [None]
            if row[10] == 0 and part_list[0] is None:
                part_list[0] = pscheduler.api_this_host()

            json['detail'] = {
                'added': None if row[1] is None \
                    else pscheduler.datetime_as_iso8601(row[1]),
                'start': None if row[2] is None \
                    else pscheduler.datetime_as_iso8601(row[2]),
                'slip': None if row[3] is None \
                    else pscheduler.timedelta_as_iso8601(row[3]),
                'duration': None if row[4] is None \
                    else pscheduler.timedelta_as_iso8601(row[4]),
                'post': None if row[5] is None \
                    else pscheduler.timedelta_as_iso8601(row[5]),
                'runs': None if row[6] is None \
                    else int(row[6]),
                'participants': part_list,
                'anytime':  row[8],
                'exclusive':  row[9],
                'multi-result':  row[10],
                'enabled':  row[12],
                'cli':  row[13]
                }

        return ok_json(json)

    elif request.method == 'POST':

        log.debug("Posting to %s", uuid)
        log.debug("Data is %s", request.data)

        # TODO: This is only for participant 1+
        # TODO: This should probably a PUT and not a POST.

        try:
            json_in = pscheduler.json_load(request.data)
        except ValueError:
            return bad_request("Invalid JSON")
        log.debug("JSON is %s", json_in)

        try:
            participant = arg_cardinal('participant')
        except ValueError as ex:
            return bad_request("Invalid participant: " + str(ex))
        log.debug("Participant %d", participant)

        # Evaluate the task against the limits and reject the request
        # if it doesn't pass.

        log.debug("Checking limits on task")

        processor, whynot = limitprocessor()
        if processor is None:
            message = "Limit processor is not initialized: %s" % whynot
            log.debug(message)
            return no_can_do(message)

        # TODO: This is cooked up in two places.  Make a function of it.
        hints = {
            "ip": request.remote_addr
            }
        hints_data = pscheduler.json_dump(hints)

        passed, diags = processor.process(json_in["test"], hints)

        if not passed:
            return forbidden("Task forbidden by limits:\n" + diags)
        log.debug("Limits passed")

        # TODO: Pluck UUID from URI
        uuid = url_last_in_path(request.url)

        log.debug("Posting task %s", uuid)

        try:
            cursor = dbcursor_query(
                "SELECT * FROM api_task_post(%s, %s, %s, %s)",
                [request.data, hints_data, participant, uuid])
        except Exception as ex:
            return error(str(ex))
        if cursor.rowcount == 0:
            return error("Task post failed; poster returned nothing.")
        # TODO: Assert that rowcount is 1
        log.debug("All done: %s", base_url())
        return ok(base_url())

    elif request.method == 'DELETE':

        parsed = list(urlparse.urlsplit(request.url))
        parsed[1] = "%s"
        template = urlparse.urlunsplit(parsed)

        try:
            cursor = dbcursor_query(
                "SELECT api_task_disable(%s, %s)", [uuid, template])
        except Exception as ex:
            return error(str(ex))

        return ok()

    else:

        return not_allowed()
Exemplo n.º 33
0
        self.__debug("Worker started")
        try:
            self.__run()
        except Exception as ex:
            self.__diag(str(ex))

    def result(self):
        """Wait for the result and return it."""

        try:
            if self.worker.is_alive():
                self.worker.join()
        except AttributeError:
            pass  # Don't care if it's not there.

        return self.results


if __name__ == "__main__":

    test = {"type": "simplestream", "spec": {"schema": 1, "dest": "__Z__"}}

    a = {"pscheduler": "dev7", "host": "dev7"}

    z = {"pscheduler": "dev6", "host": "dev6"}

    nparticipants = 2

    r = TaskRunner(test, nparticipants, a, z, debug=True)
    print pscheduler.json_dump(r.result(), pretty=True)
Exemplo n.º 34
0
def tasks_uuid_runs_run(task, run):

    if task is None:
        return bad_request("Missing or invalid task")

    if run is None:
        return bad_request("Missing or invalid run")

    if request.method == 'GET':

        # Wait for there to be a local result
        wait_local = arg_boolean('wait-local')

        # Wait for there to be a merged result
        wait_merged = arg_boolean('wait-merged')

        if wait_local and wait_merged:
            return error("Cannot wait on local and merged results")

        # If asked for 'first', dig up the first run and use its UUID.

        if run == 'first':
            # 60 tries at 0.5s intervals == 30 sec.
            tries = 60
            while tries > 0:
                try:
                    run = __runs_first_run(task)
                except Exception as ex:
                    log.exception()
                    return error(str(ex))
                if run is not None:
                    break
                time.sleep(1.0)
                tries -= 1

            if run is None:
                return not_found()


        # 60 tries at 0.5s intervals == 30 sec.
        tries = 60 if (wait_local or wait_merged) else 1

        while tries:

            try:
                cursor = dbcursor_query(
                    """
                    SELECT
                        lower(run.times),
                        upper(run.times),
                        upper(run.times) - lower(run.times),
                        task.participant,
                        task.nparticipants,
                        task.participants,
                        run.part_data,
                        run.part_data_full,
                        run.result,
                        run.result_full,
                        run.result_merged,
                        run_state.enum,
                        run_state.display,
                        run.errors,
                        run.clock_survey
                    FROM
                        run
                        JOIN task ON task.id = run.task
                        JOIN run_state ON run_state.id = run.state
                    WHERE
                        task.uuid = %s
                        AND run.uuid = %s""", [task, run])
            except Exception as ex:
                log.exception()
                return error(str(ex))

            if cursor.rowcount == 0:
                return not_found()

            row = cursor.fetchone()

            if not (wait_local or wait_merged):
                break
            else:
                if (wait_local and row[7] is None) \
                        or (wait_merged and row[9] is None):
                    time.sleep(0.5)
                    tries -= 1
                else:
                    break

        # Return a result Whether or not we timed out and let the
        # client sort it out.

        result = {}

        # This strips any query parameters and replaces the last item
        # with the run, which might be needed if the 'first' option
        # was used.

        href_path_parts = urlparse.urlparse(request.url).path.split('/')
        href_path_parts[-1] = run
        href_path = '/'.join(href_path_parts)
        href = urlparse.urljoin( request.url, href_path )

        result['href'] = href
        result['start-time'] = pscheduler.datetime_as_iso8601(row[0])
        result['end-time'] = pscheduler.datetime_as_iso8601(row[1])
        result['duration'] = pscheduler.timedelta_as_iso8601(row[2])
        participant_num = row[3]
        result['participant'] = participant_num
        result['participants'] = [
            pscheduler.api_this_host()
            if participant is None and participant_num == 0
            else participant
            for participant in row[5]
            ]
        result['participant-data'] = row[6]
        result['participant-data-full'] = row[7]
        result['result'] = row[8]
        result['result-full'] = row[9]
        result['result-merged'] = row[10]
        result['state'] = row[11]
        result['state-display'] = row[12]
        result['errors'] = row[13]
        if row[14] is not None:
            result['clock-survey'] = row[14]
        result['task-href'] = root_url('tasks/' + task)
        result['result-href'] = href + '/result'

        return json_response(result)

    elif request.method == 'PUT':

        log.debug("Run PUT %s", request.url)

        # Get the JSON from the body
        try:
            run_data = pscheduler.json_load(request.data)
        except ValueError:
            log.exception()
            log.debug("Run data was %s", request.data)
            return error("Invalid or missing run data")

        # If the run doesn't exist, take the whole thing as if it were
        # a POST.

        try:
            cursor = dbcursor_query(
                "SELECT EXISTS (SELECT * FROM run WHERE uuid = %s)",
                [run], onerow=True)
        except Exception as ex:
            log.exception()
            return error(str(ex))

        if not cursor.fetchone()[0]:

            log.debug("Record does not exist; full PUT.")

            try:
                start_time = \
                    pscheduler.iso8601_as_datetime(run_data['start-time'])
            except KeyError:
                return bad_request("Missing start time")
            except ValueError:
                return bad_request("Invalid start time")

            passed, diags = __evaluate_limits(task, start_time)

            try:
                cursor = dbcursor_query("SELECT api_run_post(%s, %s, %s)",
                               [task, start_time, run], onerow=True)
                log.debug("Full put of %s, got back %s", run, cursor.fetchone()[0])
            except Exception as ex:
                log.exception()
                return error(str(ex))

            return ok()

        # For anything else, only one thing can be udated at a time,
        # and even that is a select subset.

        log.debug("Record exists; partial PUT.")

        if 'part-data-full' in run_data:

            log.debug("Updating part-data-full from %s", run_data)

            try:
                part_data_full = \
                    pscheduler.json_dump(run_data['part-data-full'])
            except KeyError:
                return bad_request("Missing part-data-full")
            except ValueError:
                return bad_request("Invalid part-data-full")

            log.debug("Full data is: %s", part_data_full)

            try:
                cursor = dbcursor_query("""
                              UPDATE
                                  run
                              SET
                                  part_data_full = %s
                              WHERE
                                  uuid = %s
                                  AND EXISTS (SELECT * FROM task WHERE UUID = %s)
                              """,
                           [ part_data_full, run, task])
            except Exception as ex:
                log.exception()
                return error(str(ex))
            if cursor.rowcount != 1:
                return not_found()

            log.debug("Full data updated")

            return ok()

        elif 'result-full' in run_data:

            log.debug("Updating result-full from %s", run_data)

            try:
                result_full = \
                    pscheduler.json_dump(run_data['result-full'])
            except KeyError:
                return bad_request("Missing result-full")
            except ValueError:
                return bad_request("Invalid result-full")

            try:
                succeeded = bool(run_data['succeeded'])
            except KeyError:
                return bad_request("Missing success value")
            except ValueError:
                return bad_request("Invalid success value")

            log.debug("Updating result-full: JSON %s", result_full)
            log.debug("Updating result-full: Run  %s", run)
            log.debug("Updating result-full: Task %s", task)
            try:
                cursor = dbcursor_query("""
                              UPDATE
                                  run
                              SET
                                  result_full = %s,
                                  state = CASE %s
                                      WHEN TRUE THEN run_state_finished()
                                      ELSE run_state_failed()
                                      END
                              WHERE
                                  uuid = %s
                                  AND EXISTS (SELECT * FROM task WHERE UUID = %s)
                              """,
                               [ result_full, succeeded, run, task ])
            except Exception as ex:
                log.exception()
                return error(str(ex))

            if cursor.rowcount != 1:
                return not_found()

            return ok()



    elif request.method == 'DELETE':

        # TODO: If this is the lead, the run's counterparts on the
        # other participating nodes need to be removed as well.

        try:
            cursor = dbcursor_query("""
            DELETE FROM run
            WHERE
                task in (SELECT id FROM task WHERE uuid = %s)
                AND uuid = %s 
            """, [task, run])
        except Exception as ex:
            log.exception()
            return error(str(ex))

        return ok() if cursor.rowcount == 1 else not_found()

    else:

        return not_allowed()
Exemplo n.º 35
0
            },
            "parting-comment": {
                "description":
                "Parting comment must contain a vowel if not empty",
                "match": {
                    "style": "regex",
                    "match": "(^$|[aeiou])",
                    "case-insensitive": True
                }
            }
        }
    })

    print pscheduler.json_dump(limit.evaluate({
        "task": {
            "test": {
                "type": "idle",
                "spec": {
                    "schema": 1,
                    "#duration": "PT45M",
                    "duration": "PT45S",
                    "starting-comment": "Perry the PLATYPUS",
                    "#starting-comment": "Ferb",
                    "#parting-comment": "Vwl!",
                    "parting-comment": "Vowel!"
                }
            }
        }
    }),
                               pretty=True)
Exemplo n.º 36
0
 def __call__(self, json):
     """Emit serialized JSON to the file"""
     self.emit_text(pscheduler.json_dump(json, pretty=False))
Exemplo n.º 37
0
                "type": "http",
                "spec": {
                    "url": "https://www.not-a-real-domain.foo/",
                    "parse": "perfSONAR",
                    "keep-content": 100,
                    "always-succeed": False
                }
            }
    }, {
            "test": {
                "type": "http",
                "spec": {
                    "url": "https://www.not-a-real-domain.foo/",
                    "parse": "perfSONAR",
                    "keep-content": 100,
                    "always-succeed": True
                }
            }
    }, {
            "test": {
                "type": "http",
                "spec": {
                    "url": "https://www.perfsonar.net",
                    "parse": "perfSONAR",
                    "keep-content": 100,
                    "always-succeed": True
                }
            }
    }]:
        print(pscheduler.json_dump(run(data), pretty=True))
Exemplo n.º 38
0
def json_dump(dump):
    return pscheduler.json_dump(dump,
                                pretty=arg_boolean('pretty')
                                )
Exemplo n.º 39
0
        "schedule": {
            "max-runs": 3,
            "repeat": "PT10S",
            "slip": "PT5M"
        },
        "test": {
            "spec": {
                "dest": "www.notonthe.net",
                "schema": 1
            },
            "type": "trace"
        },
        "tools": ["traceroute", "paris-traceroute"]
    }

    try:
        (changed, new_task, diags) = rewriter(task, ["c1", "c2", "c3"])

        if changed:
            if len(diags):
                print "Diagnostics:"
                print "\n".join(map(lambda s: " - " + s, diags))
            else:
                print "No diagnostics."
            print
            print pscheduler.json_dump(new_task, pretty=True)
        else:
            print "No changes."
    except Exception as ex:
        print "Failed:", ex