From 9fbacc171ba124970d630fce33dc41ff8b1bfd2c Mon Sep 17 00:00:00 2001 From: Nadja Reitzenstein Date: Mon, 17 Jan 2022 19:54:53 +0100 Subject: [PATCH] Adds an example python process actor --- examples/actor.py | 179 ++++++++++++++++++++++++++++++++++++++++++++ examples/bffh.dhall | 26 ++++++- 2 files changed, 203 insertions(+), 2 deletions(-) create mode 100755 examples/actor.py diff --git a/examples/actor.py b/examples/actor.py new file mode 100755 index 0000000..489260c --- /dev/null +++ b/examples/actor.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 + +import sys +import argparse + +def on_free(args, actor_name): + """ + Function called when the state of the connected machine changes to Free + again + """ + if args.verbose > 2: + print("on_free called!") + + if actor_name == "DoorControl1": + # Do whatever you want to do in case `DoorControl1` is returned back to free. + # Keep in mind that process actors should return quickly to not miss + # updates, so if you need to do things that take a while fork a new + # process e.g. with the `subprocess` Module + print("I'm locking door 1!") + pass + elif actor_name == "DoorControl2": + print("I'm locking door 2!") + pass # Close a different door + else: + if not args.quiet: + print("process called with unknown id %s for state `Free`" % actor_name) + # It's a good idea to exit with an error code in case something + # unexpected happens. + # The process module logs everything printed to stdout by actors into + # the server log, but marks them as `Error` in case the actor process + # exits with a code != 0, making debugging somewhat easier. + exit(-1) + +def on_use(args, actor_name, user_id): + """ + Function called when an user takes control of the connected machine + + user_id contains the UID of the user now using the machine + """ + if args.verbose > 2: + print("on_use called!") + if actor_name == "DoorControl1": + print("I'm opening door 1 for 10 seconds!") + pass # Open door one + elif actor_name == "DoorControl2": + print("I'm opening door 2 for 10 seconds!") + pass # Open a different door + else: + if not args.quiet: + print("process called with unknown id %s for state `InUse`" % actor_name) + # It's a good idea to exit with an error code in case something + # unexpected happens. + # The process module logs everything printed to stdout by actors into + # the server log, but marks them as `Error` in case the actor process + # exits with a code != 0, making debugging somewhat easier. + exit(-1) + +def on_tocheck(args, actor_name, user_id): + """ + Function called when an user returns control and the connected machine is + configured to go to state `ToCheck` instead of `Free` in that case. + + user_id contains the UID of the manager expected to check the machine. + The user that used the machine beforehand has to be taken from the last + user field using the API (via e.g. the mobile app) + """ + if args.verbose > 2: + print("on_tocheck called!") + if not args.quiet: + print("process called with unexpected combo id %s and state 'ToCheck'" % actor_name) + exit(-1) + +def on_blocked(args, actor_name, user_id): + """ + Function called when an manager marks the connected machine as `Blocked` + + user_id contains the UID of the manager that blocked the machine + """ + if args.verbose > 2: + print("on_blocked called!") + if not args.quiet: + print("process called with unexpected combo id %s and state 'Blocked'" % actor_name) + exit(-1) + +def on_disabled(args, actor_name): + """ + Function called when the connected machine is marked `Disabled` + """ + if not args.quiet: + print("process called with unexpected combo id %s and state 'Disabled'" % actor_name) + exit(-1) + +def on_reserve(args, actor_name, user_id): + """ + Function called when the connected machine has been reserved by somebody. + + user_id contains the UID of the reserving user. + """ + if not args.quiet: + print("process called with unexpected combo id %s and state 'Reserved'" % actor_name) + exit(-1) + + +def main(args): + """ + Python example actor + + This is an example how to use the `process` actor type to run a Python script. + """ + + if args.verbose is not None: + if args.verbose == 1: + print("verbose output enabled") + elif args.verbose == 2: + print("loud output enabled!") + elif args.verbose == 3: + print("LOUD output enabled!!!") + elif args.verbose > 4: + print("Okay stop you're being ridiculous.") + sys.exit(-2) + else: + args.verbose = 0 + + # You could also check the actor name here and call different functions + # depending on that variable instead of passing it to the state change + # methods. + + new_state = args.state + if new_state == "free": + on_free(args, args.name) + elif new_state == "inuse": + on_use(args, args.name, args.userid) + elif new_state == "tocheck": + on_tocheck(args, args.name, args.userid) + elif new_state == "blocked": + on_blocked(args, args.name, args.userid) + elif new_state == "disabled": + on_disabled(args, args.name) + elif new_state == "reserved": + on_reserve(args, args.name, args.userid) + else: + print("Process actor called with unknown state %s" % new_state) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + # Parameters are passed to the Process actor as follows: + # 1. the contents of params.args, split by whitespace as separate args + # 2. the configured id of the actor (e.g. "DoorControl1") + # 3. the new state as one of [free|inuse|tocheck|blocked|disabled|reserved] + + parser.add_argument("-q", "--quiet", help="be less verbose", action="store_true") + parser.add_argument("-v", "--verbose", help="be more verbose", action="count") + + parser.add_argument("name", + help="name of this actor as configured in bffh.dhall" + ) + + # We parse the new state using subparsers so that we only require a userid + # in case it's a state that sets one. + subparsers = parser.add_subparsers(required=True, dest="state") + + parser_free = subparsers.add_parser("free") + + parser_inuse = subparsers.add_parser("inuse") + parser_inuse.add_argument("userid", help="The user that is now using the machine") + + parser_tocheck = subparsers.add_parser("tocheck") + parser_tocheck.add_argument("userid", help="The user that should go check the machine") + + parser_blocked = subparsers.add_parser("blocked") + parser_blocked.add_argument("userid", help="The user that marked the machine as blocked") + + parser_disabled = subparsers.add_parser("disabled") + + parser_reserved = subparsers.add_parser("reserved") + parser_reserved.add_argument("userid", help="The user that reserved the machine") + + args = parser.parse_args() + main(args) diff --git a/examples/bffh.dhall b/examples/bffh.dhall index a7a5f88..dcf8d5c 100644 --- a/examples/bffh.dhall +++ b/examples/bffh.dhall @@ -180,6 +180,28 @@ args = "your ad could be here" } }, + + DoorControl1 = { + -- This actor calls the actor.py script in examples/ + -- It gets passed it's own name, so you can have several actors + -- from the same script. + -- If you need to pass more arguments to the command you can use the `args` key in + -- `params` as is done with the actor `Bash` + module = "Process", + params = { cmd = "./examples/actor.py", } + }, + DoorControl2 = { + module = "Process", + params = { cmd = "./examples/actor.py", } + }, + DoorControl3 = { + -- This is an example for how it looks like if an actor is misconfigured. + -- the actor.py doesn't know anything about DoorControl3 and, if this actor is enabled, + -- will return with an error showing up in the server logs. + module = "Process", + params = { cmd = "./examples/actor.py", } + }, + Bash2 = { module = "Process", params = { cmd = "./examples/actor.sh" , args = "this is a different one" }}, FailBash = { module = "Process", params = { cmd = "./examples/fail-actor.sh" }} }, @@ -188,7 +210,7 @@ -- Actors need to be connected to machines to be useful. A machine can be connected to multiple actors, but one -- actor can only be connected to one machine. actor_connections = [ - { machine = "Testmachine", actor = "Shelly1234" }, + { machine = "Testmachine", actor = "DoorControl1" }, { machine = "Another", actor = "Bash" }, { machine = "Yetmore", actor = "Bash2" }, { machine = "Yetmore", actor = "FailBash"} @@ -207,4 +229,4 @@ -- The below is once again how you have to define *no* initiators. --init_connections = [] : List { machine : Text, initiator : Text } init_connections = [{ machine = "Testmachine", initiator = "Initiator" }] -} \ No newline at end of file +}