Beispiel #1
0
def route_create_ensemble_workflow(ensemble):
    dao = Ensembles(g.session)
    e = dao.get_ensemble(g.user.username, ensemble)

    name = request.form.get("name", None)
    if name is None:
        raise EMError("Specify ensemble workflow 'name'")

    priority = request.form.get("priority", 0)

    basedir = request.form.get("basedir")
    if basedir is None:
        raise EMError(
            "Specify 'basedir' where plan command should be executed")

    plan_command = request.form.get("plan_command")
    if plan_command is None:
        raise EMError(
            "Specify 'plan_command' that should be executed to plan workflow")

    dao.create_ensemble_workflow(e.id, name, basedir, priority, plan_command)

    g.session.commit()

    return api.json_created(
        url_for("route_get_ensemble_workflow",
                ensemble=ensemble,
                workflow=name))
Beispiel #2
0
def route_analyze_ensemble_workflow(ensemble, workflow):
    dao = Ensembles(g.session)
    e = dao.get_ensemble(g.user.username, ensemble)
    w = dao.get_ensemble_workflow(e.id, workflow)
    report = "".join(analyze(w))
    resp = make_response(report, 200)
    resp.headers["Content-Type"] = "text/plain"
    return resp
Beispiel #3
0
def route_create_ensemble():
    name = request.form.get("name", None)
    if name is None:
        raise EMError("Specify ensemble name")

    max_running = request.form.get("max_running", 1)
    max_planning = request.form.get("max_planning", 1)

    dao = Ensembles(g.session)
    dao.create_ensemble(g.user.username, name, max_running, max_planning)
    g.session.commit()

    return api.json_created(
        url_for("route_get_ensemble", name=name, _external=True))
Beispiel #4
0
def route_update_ensemble_workflow(ensemble, workflow):
    dao = Ensembles(g.session)

    e = dao.get_ensemble(g.user.username, ensemble)
    w = dao.get_ensemble_workflow(e.id, workflow)

    priority = request.form.get("priority", None)
    if priority is not None:
        w.set_priority(priority)

    state = request.form.get("state", None)
    if state is not None:
        w.change_state(state)

    w.set_updated()

    g.session.commit()

    return api.json_response(w.get_detail_object())
Beispiel #5
0
def route_delete_trigger(ensemble, trigger):
    # verify that ensemble exists for user
    e_dao = Ensembles(g.session)

    # raises EMError code 404 if does not exist
    ensemble_id = e_dao.get_ensemble(g.user.username, ensemble).id

    # update trigger state to be STOPPED so that the TriggerManager can
    # handle it appropriately
    t_dao = Triggers(g.session)

    # make sure get_trigger raises 404 if nothing found
    trigger_id = t_dao.get_trigger(ensemble_id, trigger).id
    t_dao.update_state(ensemble_id, trigger_id)

    # TODO: what to return here
    # return HTTP code that represents that it was successful and that nothing
    # is to returned
    # status code 204, nothing else to return
    return "hello world from delete_trigger!"
Beispiel #6
0
 def loop_forever(self):
     while True:
         u = user.get_user_by_uid(os.getuid())
         session = connection.connect(
             u.get_master_db_url(), connect_args={"check_same_thread": False}
         )
         try:
             dao = Ensembles(session)
             self.loop_once(dao)
         finally:
             session.close()
         time.sleep(self.interval)
Beispiel #7
0
def route_create_trigger(ensemble, trigger):
    # verify that ensemble exists for user
    e_dao = Ensembles(g.session)

    # raises EMError code 404 if does not exist
    ensemble_id = e_dao.get_ensemble(g.user.username, ensemble).id

    # create trigger entry in db
    t_dao = Triggers(g.session)

    trigger_type = request.form.get("type")
    kwargs = {
        "ensemble_id": ensemble_id,
        "trigger": trigger,
        "trigger_type": trigger_type,
        "workflow_script": request.form.get("workflow_script"),
        "workflow_args": json.loads(request.form.get("workflow_args")),
    }

    if trigger_type == TriggerType.CRON.value:
        # add cron trigger specific parameters
        kwargs["interval"] = request.form.get("interval")
        kwargs["timeout"] = request.form.get("timeout")
    elif trigger_type == TriggerType.FILE_PATTERN.value:
        # add file pattern specific parameters
        kwargs["interval"] = request.form.get("interval")
        kwargs["timeout"] = request.form.get("timeout")
        kwargs["file_patterns"] = json.loads(request.form.get("file_patterns"))
    else:
        raise NotImplementedError(
            "encountered unsupported trigger type: {}".format(trigger_type))

    t_dao.insert_trigger(**kwargs)

    # TODO: what to return here
    # return ID that was created, in this case trigger name is sufficient
    # probably code 201
    # use Flask response object and a json object representing an id of the entity
    return "hello world from create_trigger!"
Beispiel #8
0
    def run(self):
        """Trigger manager main loop."""

        self.log.info("trigger manager starting")

        while True:
            # TODO: user will always be the same.., keep this in the loop or move it out
            u = user.get_user_by_uid(os.getuid())
            session = connection.connect(
                u.get_master_db_url(),
                connect_args={"check_same_thread": False})

            try:
                self.trigger_dao = Triggers(session)
                self.ensemble_dao = Ensembles(session)
                triggers = self.trigger_dao.list_triggers()
                self.log.info("processing {} triggers".format(len(triggers)))
                for t in triggers:
                    t_name = TriggerManager.get_tname(t)
                    if t.state == "READY":
                        self.start_trigger(t)
                    elif t.state == "RUNNING" and t_name not in self.running:
                        # restart
                        self.log.debug(
                            "{} not in memory, restarting it".format(t_name))
                        self.start_trigger(t)
                    elif t.state == "RUNNING" and not self.running[
                            t_name].is_alive():
                        # exited
                        self.log.debug(
                            "{} exited, removing references to it".format(
                                t_name))
                        self.stop_trigger(t)
                    elif t.state == "STOPPED":
                        self.stop_trigger(t)
            finally:
                session.close()

            time.sleep(self.polling_rate)
Beispiel #9
0
def route_update_ensemble(name):
    dao = Ensembles(g.session)
    e = dao.get_ensemble(g.user.username, name)

    max_running = request.form.get("max_running", None)
    if max_running is not None:
        e.set_max_running(max_running)

    max_planning = request.form.get("max_planning", None)
    if max_planning is not None:
        e.set_max_planning(max_planning)

    state = request.form.get("state", None)
    if state is not None:
        if state != e.state:
            # TODO Do the necessary state transition
            e.set_state(state)

    e.set_updated()

    g.session.commit()

    return api.json_response(e.get_object())
Beispiel #10
0
def route_get_ensemble(name):
    dao = Ensembles(g.session)
    e = dao.get_ensemble(g.user.username, name)
    result = e.get_object()
    return api.json_response(result)
Beispiel #11
0
def route_list_ensembles():
    dao = Ensembles(g.session)
    ensembles = dao.list_ensembles(g.user.username)
    result = [e.get_object() for e in ensembles]
    return api.json_response(result)
Beispiel #12
0
def route_get_ensemble_workflow(ensemble, workflow):
    dao = Ensembles(g.session)
    e = dao.get_ensemble(g.user.username, ensemble)
    w = dao.get_ensemble_workflow(e.id, workflow)
    result = w.get_detail_object()
    return api.json_response(result)
Beispiel #13
0
def route_list_ensemble_workflows(name):
    dao = Ensembles(g.session)
    e = dao.get_ensemble(g.user.username, name)
    result = [w.get_object() for w in dao.list_ensemble_workflows(e.id)]
    return api.json_response(result)
Beispiel #14
0
class TriggerManager(threading.Thread):
    """
    Manages workflow triggers. Work is done based on the contents of the
    "trigger" table, and the state of each trigger in that table.
    """
    def __init__(self, ):
        threading.Thread.__init__(self, daemon=True)

        self.log = logging.getLogger("trigger.manager")

        # interval in seconds at which the trigger manager will query the
        # database to see if there is work to do (start, stop, restart) triggers
        self.polling_rate = 15

        # references to currently running trigger threads
        # key: (<ensenble_id>, <trigger_name>), value: handle to trigger thread
        self.running = dict()

        self.trigger_dao = None
        self.ensemble_dao = None

    def run(self):
        """Trigger manager main loop."""

        self.log.info("trigger manager starting")

        while True:
            # TODO: user will always be the same.., keep this in the loop or move it out
            u = user.get_user_by_uid(os.getuid())
            session = connection.connect(
                u.get_master_db_url(),
                connect_args={"check_same_thread": False})

            try:
                self.trigger_dao = Triggers(session)
                self.ensemble_dao = Ensembles(session)
                triggers = self.trigger_dao.list_triggers()
                self.log.info("processing {} triggers".format(len(triggers)))
                for t in triggers:
                    t_name = TriggerManager.get_tname(t)
                    if t.state == "READY":
                        self.start_trigger(t)
                    elif t.state == "RUNNING" and t_name not in self.running:
                        # restart
                        self.log.debug(
                            "{} not in memory, restarting it".format(t_name))
                        self.start_trigger(t)
                    elif t.state == "RUNNING" and not self.running[
                            t_name].is_alive():
                        # exited
                        self.log.debug(
                            "{} exited, removing references to it".format(
                                t_name))
                        self.stop_trigger(t)
                    elif t.state == "STOPPED":
                        self.stop_trigger(t)
            finally:
                session.close()

            time.sleep(self.polling_rate)

    def start_trigger(self, trigger: Trigger):
        """Given a trigger, start the appropriate trigger thread.

        :param trigger: the trigger to be started
        :type trigger: Trigger
        """

        trigger_name = TriggerManager.get_tname(trigger)
        self.log.debug("starting {}".format(trigger_name))

        workflow = json.loads(trigger.workflow)
        required_args = {
            "ensemble_id": trigger.ensemble_id,
            "ensemble":
            self.ensemble_dao.get_ensemble_name(trigger.ensemble_id),
            "trigger": trigger.name,
            "workflow_script": workflow["script"],
            "workflow_args": workflow["args"] if workflow["args"] else [],
        }
        trigger_specific_kwargs = json.loads(trigger.args)

        # create trigger thread
        if trigger._type == TriggerType.CRON.value:
            t = CronTrigger(**required_args, **trigger_specific_kwargs)

        elif trigger._type == TriggerType.FILE_PATTERN.value:
            t = FilePatternTrigger(**required_args, **trigger_specific_kwargs)

        else:
            raise NotImplementedError("unsupported trigger type: {}".format(
                trigger.type))

        # keep ref to trigger thread
        self.running[trigger_name] = t
        t.start()

        # update state
        self.log.debug(
            "changing {name} state: {old_state} -> {new_state}".format(
                name=trigger_name,
                old_state=trigger.state,
                new_state="RUNNING",
            ))
        self.trigger_dao.update_state(ensemble_id=trigger.ensemble_id,
                                      trigger_id=trigger._id,
                                      new_state="RUNNING")

    def stop_trigger(self, trigger: Trigger):
        """Stop a trigger thread.

        :param trigger: the trigger to be stopped
        :type trigger: Trigger
        """
        # using reference to trigger, tell trigger to shutdown
        target_trigger = TriggerManager.get_tname(trigger)
        self.log.debug("stopping {}".format(target_trigger))
        self.running[target_trigger].shutdown()
        del self.running[target_trigger]

        # remove entry from database
        self.trigger_dao.delete_trigger(trigger.ensemble_id, trigger.name)

    @staticmethod
    def get_tname(trigger: Trigger) -> tuple:
        """Given a trigger object, get its name as a pair (<ensemble_id>, <trigger_name>)

        :param trigger: the trigger
        :type trigger: Trigger
        :return: pair (<ensemble_id>, <trigger_name>)
        :rtype: tuple
        """
        return (trigger.ensemble_id, trigger.name)