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
hello-world-actor
that we deployed in the previous section. We will
demonstrate that the downstream hello-world-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 hello-world-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 entrypoint"""
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, hello-world-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()
|
Using the client,
submit HPC job to
Tapis Application
|
tapis jobs submit
|
client.jobs.submit()
|
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
hello-world-actor
we deployed previously, and we can retrieve its ID
using the CLI:
$ tapis actors list
+---------------+--------------------+-------+-------------------------------+--------------------------+--------+--------+
| id | name | owner | image | lastUpdateTime | status | cronOn |
+---------------+--------------------+-------+-------------------------------+--------------------------+--------+--------+
| MqqbarbazBB8x | hello-world-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 our new Actor:
$ tapis actors list
+---------------+--------------------+-------+-----------------------------------+--------------------------+--------+--------+
| id | name | owner | image | lastUpdateTime | status | cronOn |
+---------------+--------------------+-------+-----------------------------------+--------------------------+--------+--------+
| MqqbarbazBB8x | hello-world-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 |
+---------------+--------------------+-------+-----------------------------------+--------------------------+--------+--------+
$ tapis actors show -v MDfoobar7AOwx
{
"id": "MDfoobar7AOwx",
"name": "upstream-messenger",
"description": "Sends message to another actor",
"owner": "eho",
"image": "enho/upstream-messenger:0.0.1",
"createTime": "2021-09-21T20:35:33.39Z0",
"lastUpdateTime": "2021-09-21T20:35:33.39Z0",
"defaultEnvironment": {
"DOWNSTREAM_ACTOR_ID": "MqqbarbazBB8x"
},
"gid": 859336,
"hints": [],
"link": "",
"mounts": [],
"privileged": false,
"queue": "default",
"stateless": true,
"status": "READY",
"statusMessage": " ",
"token": true,
"uid": 859336,
"useContainerUid": false,
"webhook": "",
"cronOn": false,
"cronSchedule": null,
"cronNextEx": null,
"_links": {
"executions": "https://api.tacc.utexas.edu/actors/v2/MDfoobar7AOwx/executions",
"owner": "https://api.tacc.utexas.edu/profiles/v2/eho",
"self": "https://api.tacc.utexas.edu/actors/v2/MDfoobar7AOwx"
}
}
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, hello-world-actor!' to MqqbarbazBB8x
Successfully triggered execution '5P7foobarrrA6' on actor 'MqqbarbazBB8x'
Check Execution of Downstream hello-world-actor
¶
The goal of this tutorial was to send a message to
upstream-messenger
and have it trigger an execution on
hello-world-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, hello-world-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.