Send a Message Between Actors¶
Introduction¶
While standalone Actors are useful, one can also network multiple Actors to generate more complex workflows. Actors have the ability to send messages to other Actors, allowing developers to chain together workflow steps, each contained in a separate Actor.
In this section of the tutorial, we will deploy a simple
upstream-messenger Actor that sends a message to the
example-actor that we deployed in the previous section. We will
demonstrate that the downstream example-actor runs after it receives
the message from upstream-messenger.
Create a New Actor Named upstream-messenger¶
First, let’s write the code that our new Actor will run on execution. Instead of manually writing these files, we can simply adapt one of the provided Actor templates. To view all available Actor templates, issue:
$ tapis actors init -L
+--------------------+--------------------+------------------------------------------------------------+----------+
| id | name | description | level |
+--------------------+--------------------+------------------------------------------------------------+----------+
| default | Default | Basic code and configuration skeleton | beginner |
| echo | Echo | Echo message | beginner |
| hello_world | Hello World | Say Hello, World! | beginner |
| sd2e_base | sd2e_base | Default reactor context for docker://sd2e/reactors:python3 | beginner |
| tacc_reactors_base | tacc_reactors_base | Default actor context for docker://sd2e/reactors:python3 | beginner |
+--------------------+--------------------+------------------------------------------------------------+----------+
Each template is a project directory for a different type of Actor. For
this Actor, let’s use the default template:
$ tapis actors init --template default --actor-name upstream-messenger
+-------+---------------------------------------------------------------+
| stage | message |
+-------+---------------------------------------------------------------+
| setup | Project path: ./upstream_messenger |
| setup | CookieCutter variable name=upstream-messenger |
| setup | CookieCutter variable project_slug=upstream_messenger |
| setup | CookieCutter variable docker_namespace=taccuser |
| setup | CookieCutter variable docker_registry=https://index.docker.io |
| clone | Project path: ./upstream_messenger |
+-------+---------------------------------------------------------------+
$ cd upstream_messenger
$ find -L .
.
./requirements.txt
./Dockerfile
./project.ini
./message.jsonschema
./default.py
./.gitignore
./secrets.jsonsample
./config.yml
We see that the tapis actors init command has initialized an Actor
project directory for us, and it already contains many files that we
could have written by hand such as the Dockerfile or the Python
source code in default.py.
The only file that was not provided by the template is secrets.json.
Let’s make an empty one now:
echo '{}' > secrets.json
Edit Actor Source¶
The Actor we just created doesn’t do much; it just says “hello world,”
like the example-actor we deployed previously. Let’s change its
behavior so it does something more interesting, like message another
Actor. We will use the Python API command
sendMessage
to implement this. Using your favorite text editor, edit the
default.py script so it looks like:
import os
from agavepy.actors import get_context, get_client
def main():
"""Main entry to grab message context from user input"""
context = get_context()
m = context['raw_message']
print("Actor received message: {}".format(m))
# Get an active Tapis client
client = get_client()
# Pull in the downstream Actor ID from the environment
downstream_actor_id = context['DOWNSTREAM_ACTOR_ID']
# alternatively:
# downstream_actor_id = os.environ['DOWNSTREAM_ACTOR_ID']
# Using our Tapis client, send a message to the downstream Actor
message = 'greetings, example-actor!'
print("Sending message '{}' to {}".format(message, downstream_actor_id))
response = client.actors.sendMessage(actorId=downstream_actor_id, body={"message": message})
print("Successfully triggered execution '{}' on actor '{}'".format(response['executionId'], downstream_actor_id))
if __name__ == '__main__':
main()
All we’ve done is add a block of code that calls the Tapis/Agave API so that it sends a message to another Actor. Notice that we are mimicking the CLI workflow from before:
Action |
CLI |
Python API |
|---|---|---|
Get an authenticated Tapis client |
tapis auth init |
client = get_client() |
Using the client, send message to an Actor |
tapis actors submit |
client.actors.sendMessage() |
In fact, the CLI is making the same calls to the Python API under the hood!
Notice that we haven’t actually defined which Actor ID we want to
send the message to. Per best practice, we’ve chosen not to “hard code”
the Actor ID into default.py, but rather read it from the Actor
environment, which we access via context['DOWNSTREAM_ACTOR_ID'] or
alternatively os.environ['DOWNSTREAM_ACTOR_ID']. To set the
DOWNSTREAM_ACTOR_ID, we need only define it in the Actor environment
when we deploy in the next step. The downstream Actor is the
example-actor we deployed previously, and we can retrieve its ID
using the CLI:
$ tapis actors list
+---------------+--------------------+-------+-------------------------------+--------------------------+--------+--------+
| id | name | owner | image | lastUpdateTime | status | cronOn |
+---------------+--------------------+-------+-------------------------------+--------------------------+--------+--------+
| MqqbarbazBB8x | example-actor | eho | tacc/hello-world:latest | 2021-08-24T19:13:44.036Z | READY | False |
+---------------+--------------------+-------+-------------------------------+--------------------------+--------+--------+
We will need this Actor ID (MqqbarbazBB8x in my case, yours will be
different) when we deploy in the next section.
Deploy Actor¶
Our new upstream-messenger Actor is now ready to deploy. Just like
before, we want to:
Build the Docker image
Push the Docker image
Register the Docker image as a new Actor
Remember to replace the DOWNSTREAM_ACTOR_ID with the appropriate
Actor ID from above, and the placeholder taccuser with your
DockerHub username.
$ docker build -t taccuser/upstream-messenger:0.0.1 .
$ docker push taccuser/upstream-messenger:0.0.1
$ tapis actors create --repo taccuser/upstream-messenger:0.0.1 \
-n upstream-messenger \
-d "Sends message to another actor" \
-e DOWNSTREAM_ACTOR_ID=MqqbarbazBB8x
+----------------+-----------------------------------+
| Field | Value |
+----------------+-----------------------------------+
| id | MDfoobar7AOwx |
| name | upstream-messenger |
| owner | taccuser |
| image | taccuser/upstream-messenger:0.0.1 |
| lastUpdateTime | 2021-08-26T20:33:20.320620 |
| status | SUBMITTED |
| cronOn | False |
+----------------+-----------------------------------+
If deployment was successful, we should now see two available Actors:
$ tapis actors list
+---------------+--------------------+-------+-----------------------------------+--------------------------+--------+--------+
| id | name | owner | image | lastUpdateTime | status | cronOn |
+---------------+--------------------+-------+-----------------------------------+--------------------------+--------+--------+
| MqqbarbazBB8x | example-actor | eho | tacc/hello-world:latest | 2021-08-24T19:13:44.036Z | READY | False |
| MDfoobar7AOwx | upstream-messenger | eho | taccuser/upstream-messenger:0.0.1 | 2021-08-24T20:23:07.619Z | READY | False |
+---------------+--------------------+-------+-----------------------------------+--------------------------+--------+--------+
Send Message to upstream-messenger Using CLI¶
Once the upsteam_messenger Actor is READY, we can trigger a new
execution by sending it a message:
$ tapis actors submit -m 'hello, upstream-messenger!' MDfoobar7AOwx
+-------------+----------------------------+
| Field | Value |
+-------------+----------------------------+
| executionId | MDanexec7AOwx |
| msg | hello, upstream-messenger! |
+-------------+----------------------------+
As usual, we check the status of the execution, and show the logs when it finishes:
$ tapis actors execs show MDfoobar7AOwx MDanexec7AOwx
+-----------+-----------------------------+
| Field | Value |
+-----------+-----------------------------+
| actorId | MDfoobar7AOwx |
| apiServer | https://api.tacc.utexas.edu |
| id | MDanexec7AOwx |
| status | COMPLETE |
| workerId | wZvworker1KmQ |
+-----------+-----------------------------+
$ tapis actors execs logs MDfoobar7AOwx MDanexec7AOwx
Actor received message: hello, upstream-messenger!
Sending message 'greetings, example-actor!' to MqqbarbazBB8x
Successfully triggered execution '5P7foobarrrA6' on actor 'MqqbarbazBB8x'
Check Execution of Downstream example-actor¶
The goal of this tutorial was to send a message to
upstream-messenger and have it trigger an execution on
example-actor. Let’s check the status of the execution and inspect
the logs:
$ tapis actors execs show MqqbarbazBB8x 5P7foobarrrA6
+-----------+-----------------------------+
| Field | Value |
+-----------+-----------------------------+
| actorId | MqqbarbazBB8x |
| apiServer | https://api.tacc.utexas.edu |
| id | 5P7foobarrrA6 |
| status | COMPLETE |
| workerId | DJPworkerzKlN |
+-----------+-----------------------------+
$ tapis actors execs logs MqqbarbazBB8x 5P7foobarrrA6
Logs for execution 5P7foobarrrA6
Actor received message: hello, example-actor!
Conclusion¶
Congratulations! We have successfully deployed a workflow that sends a message between two Actors. Of course, real-world multi-Actor workflows will send much more useful information than “hello, world.” In practice, messages contain file paths, names of analyses to run, and other metadata. It is also possible for one Actor to send messages to multiple other Actors, allowing for a single action such as a file upload to trigger many downstream processes, such as file management, running analyses, logging, and more.