Work with Actors

In Tapis, actors are container-based functions-as-a-service that follow the actor model of concurrent computation. An actor responds to messages it receives by changing its state, performing an action, sending out response messages, or all of the above.

The function an actor performs is exposed as the default command in a container. It is typically quick and requires little processing power - i.e. an app may be configured to run FastQC, and an actor may trigger a job using that app.

The guide below is a brief introduction to interacting with actors on the Tapis platform. For a full reference guide to actors, see the Abaco Documentation.

Create a New Actor

The function of an actor is exposed as the default command in a Docker container. Here, we will create an actor from an existing Docker container image called jturcino/abaco-trial:latest available on ‘Docker Hub <https://hub.docker.com/r/jturcino/abaco-trial/>’_ The default command for this container simply prints some information about the current environment to STDOUT, which will be captured in the actor logs.

Create the actor as:

$ tapis actors create --repo jturcino/abaco-trial:latest \
                      -n example-actor \
                      -d "Test actor that prints environment info" \
                      -e foo=bar \
                      -e bar=baz
+----------------+-----------------------------+
| Field          | Value                       |
+----------------+-----------------------------+
| id             | boEg3mEvrKO5w               |
| name           | example-actor               |
| owner          | taccuser                    |
| image          | jturcino/abaco-trial:latest |
| lastUpdateTime | 2020-05-15 02:39:37.230860  |
| status         | SUBMITTED                   |
+----------------+-----------------------------+

The --repo flag points to the Docker Hub repo on which this actor is based, the -n flag and -d flag attach a human-readable name and description to the actor, and the -e flags demonstrate how to set (optional) environment variables for the actor.

The resulting actor is assigned an id: boEg3mEvrKO5w. The actor id can be queried by:

$ tapis actors show -v boEg3mEvrKO5w
{
  "id": "boEg3mEvrKO5w",
  "name": "example-actor",
  "description": "Test actor that prints environment info",
  "owner": "taccuser",
  "image": "jturcino/abaco-trial:latest",
  "createTime": "2020-05-15 02:39:37.230860",
  "lastUpdateTime": "2020-05-15 02:39:37.230860",
  "defaultEnvironment": {
    "bar": "baz",
    "foo": "bar"
  },
  "gid": 851953,
  "hints": [],
  "link": "",
  "mounts": [],
  "privileged": false,
  "queue": "default",
  "stateless": true,
  "status": "READY",
  "statusMessage": " ",
  "token": true,
  "uid": 851953,
  "useContainerUid": false,
  "webhook": "",
  "_links": {
    "executions": "https://api.tacc.utexas.edu/actors/v2/boEg3mEvrKO5w/executions",
    "owner": "https://api.tacc.utexas.edu/profiles/v2/taccuser",
    "self": "https://api.tacc.utexas.edu/actors/v2/boEg3mEvrKO5w"
  }
}

Above, you can see the plain text name, description, and default environment variables that were passed on the command line. In addition, you can see the “status” of the actor is “READY”, meaning it is ready to receive and act on messages. Finally, you can list all actors visible to you with:

$ tapis actors list
+---------------+---------------+----------+-----------------------------+----------------------------+--------+
| id            | name          | owner    | image                       | lastUpdateTime             | status |
+---------------+---------------+----------+-----------------------------+----------------------------+--------+
| boEg3mEvrKO5w | example-actor | taccuser | jturcino/abaco-trial:latest | 2020-05-15 02:39:37.230860 | READY  |
+---------------+---------------+----------+-----------------------------+----------------------------+--------+

Probe the Underlying Container

An actor now exists and is waiting for a message to respond to. But, how will the actor respond when sent a message? We can probe the underlying container to figure out what this specific actor will do. First pull the container locally:

$ docker pull jturcino/abaco-trial:latest
latest: Pulling from jturcino/abaco-trial
...
Digest: sha256:976a83992e1f36b6a1afa0ba71c59ab3d5ff17e66a2f6b1ff1c8a370003087b4
Status: Downloaded newer image for jturcino/abaco-trial:latest
docker.io/jturcino/abaco-trial:latest

Then find the default command for the container:

$ docker inspect jturcino/abaco-trial:latest | jq ".[].ContainerConfig.Cmd"
[
  "/bin/sh",
  "-c",
  "#(nop) ",
  "CMD [\"python\" \"/script.py\"]"
]

It runs script.py at the root level. Print out the contents of script.py to inspect:

$ docker run --rm jturcino/abaco-trial:latest cat /script.py
 1 #!/usr/bin/env python
 2
 3 import os
 4 import sys
 5 import json
 6 from agavepy.actors import get_context
 7
 8 if __name__ == '__main__':
 9
10     context = get_context()
11     print 'FULL CONTEXT:'
12     print json.dumps(context, indent=2)
13
14     print '\nMESSAGE:'
15     message = context.message_dict
16     print json.dumps(message, indent=2)
17
18     print '\nFULL ENVIRONMENT:'
19     print json.dumps(dict(os.environ), indent=2)
20
21     print '\nROOT FILES:'
22     print ' '.join(os.listdir('/'))

This container, when run, will first get the message that was passed to it (from the get_context() function, line 10). Then it will print various parts of the message and the environment.

Submit a Message to the Actor

Next, let’s craft a simple message to send to the reactor. Messages can be plain text or in JSON format. When using the python actor libraries as in the example above, JSON-formatted messages are made available as python dictionaries.

# Write a message
$ export MESSAGE='{"key1":"value1", "key2":"value2"}'
$ echo $MESSAGE
{"key1":"value1", "key2":"value2"}

$ Submit the message to the actor
$ tapis actors submit -m "$MESSAGE" boEg3mEvrKO5w
+-------------+------------------------------------+
| Field       | Value                              |
+-------------+------------------------------------+
| executionId | ayB45Oe8GJvAA                      |
| msg         | {"key1":"value1", "key2":"value2"} |
+-------------+------------------------------------+

The id of the actor (boEg3mEvrKO5w) was used on the command line to specify which actor should receive the message. In response, an “execution id” (ayB45Oe8GJvAA) is returned. An execution is a specific instance of an actor. List all the executions for a given actor as:

The above execution has already completed. Show detailed information for the execution with:

$ tapis actors execs show -v boEg3mEvrKO5w ayB45Oe8GJvAA
{
  "actorId": "boEg3mEvrKO5w",
  "apiServer": "https://api.tacc.utexas.edu",
  "cpu": 0,
  "exitCode": 0,
  "finalState": {
    "Dead": false,
    "Error": "",
    "ExitCode": 0,
    "FinishedAt": "2020-05-15T02:52:27.885125138Z",
    "OOMKilled": false,
    "Paused": false,
    "Pid": 0,
    "Restarting": false,
    "Running": false,
    "StartedAt": "2020-05-15T02:52:27.778284083Z",
    "Status": "exited"
  },
  "id": "ayB45Oe8GJvAA",
  "io": 0,
  "messageReceivedTime": "2020-05-15 02:52:20.737730",
  "runtime": 1,
  "startTime": "2020-05-15 02:52:27.137874",
  "status": "COMPLETE",
  "workerId": "AMgVYDQ8lvlP6",
  "_links": {
    "logs": "https://api.tacc.utexas.edu/actors/v2/boEg3mEvrKO5w/executions/ayB45Oe8GJvAA/logs",
    "owner": "https://api.tacc.utexas.edu/profiles/v2/taccuser",
    "self": "https://api.tacc.utexas.edu/actors/v2/boEg3mEvrKO5w/executions/ayB45Oe8GJvAA"
  }
}

Check the Logs for an Execution

An execution’s logs will contain whatever was printed to STDOUT / STDERR by the actor. In our demo actor, we just expect the actor to print various parts of the environment.

$ tapis actors execs logs boEg3mEvrKO5w ayB45Oe8GJvAA
Logs for execution ayB45Oe8GJvAA
 FULL CONTEXT:
{
  "username": "taccuser",
  "_abaco_jwt_header_name": "X-Jwt-Assertion-Tacc-Prod",
  "_abaco_worker_id": "AMgVYDQ8lvlP6",
  "raw_message": "{\"key1\":\"value1\", \"key2\":\"value2\"}",
  "actor_dbid": "TACC-PROD_boEg3mEvrKO5w",
  "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  "_abaco_access_token": "7a2f635733c430b29819c267590f042",
  "_abaco_container_repo": "jturcino/abaco-trial:latest",
  "content_type": null,
  "HOME": "/",
  "MSG": "{\"key1\":\"value1\", \"key2\":\"value2\"}",
  "bar": "baz",
  "_abaco_api_server": "https://api.tacc.utexas.edu",
  "_abaco_actor_name": "example-actor",
  "_abaco_Content_Type": "str",
  "execution_id": "ayB45Oe8GJvAA",
  "_abaco_synchronous": "False",
  "_abaco_actor_state": "{}",
  "message_dict": {
    "key2": "value2",
    "key1": "value1"
  },
  "_abaco_actor_dbid": "TACC-PROD_boEg3mEvrKO5w",
  "HOSTNAME": "9e70a9f51927",
  "_abaco_actor_id": "boEg3mEvrKO5w",
  "_abaco_execution_id": "ayB45Oe8GJvAA",
  "environment": "_abaco_synchronous",
  "state": "{}",
  "_abaco_username": "taccuser",
  "actor_id": "boEg3mEvrKO5w",
  "foo": "bar"
}

MESSAGE:
{
  "key2": "value2",
  "key1": "value1"
}

FULL ENVIRONMENT:
{
  "_abaco_synchronous": "False",
  "_abaco_actor_state": "{}",
  "bar": "baz",
  "_abaco_actor_id": "boEg3mEvrKO5w",
  "_abaco_actor_dbid": "TACC-PROD_boEg3mEvrKO5w",
  "HOSTNAME": "9e70a9f51927",
  "_abaco_execution_id": "ayB45Oe8GJvAA",
  "_abaco_Content_Type": "str",
  "_abaco_container_repo": "jturcino/abaco-trial:latest",
  "environment": "_abaco_synchronous",
  "_abaco_jwt_header_name": "X-Jwt-Assertion-Tacc-Prod",
  "_abaco_username": "taccuser",
  "_abaco_worker_id": "AMgVYDQ8lvlP6",
  "_abaco_access_token": "7a2f635733c430b29819c267590f042",
  "MSG": "{\"key1\":\"value1\", \"key2\":\"value2\"}",
  "HOME": "/",
  "_abaco_api_server": "https://api.tacc.utexas.edu",
  "_abaco_actor_name": "example-actor",
  "foo": "bar",
  "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
}

ROOT FILES:
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var _abaco_results.sock .dockerenv requirements.txt agavepy script.py

Sure enough, the information in the execution logs match what we expected script.py to print. The message dictionary was pulled in by the `get_context() function. It was not done in this script, but in a normal scenario, the actor would then act on the contents of that message to, e.g., kick off a job, perform some data management, send messages to other actors, or more.

Update an Actor

Updating an actor would be useful to modify its environment, or to deploy a new tagged version of the Docker container containing, perhaps, an updated actor python script. Update an existing actor as:

$ tapis actors update --repo jturcino/abaco-trial:latest \
                      -e new_foo=new_bar \
                      boEg3mEvrKO5w
+----------------+-----------------------------+
| Field          | Value                       |
+----------------+-----------------------------+
| id             | boEg3mEvrKO5w               |
| name           | example-actor               |
| owner          | taccuser                    |
| image          | jturcino/abaco-trial:latest |
| lastUpdateTime | 2020-05-15 03:03:03.724195  |
| status         | READY                       |
+----------------+-----------------------------+

In this example, a new environment variable was provided and the previously-passed environment variables were omitted. The Docker repo stayed the same, but must still be passed on the command line

Run Synchronously

The previous message submission (with tapis actors submit) was an asynchronous run, meaning the command prompt detached from the process after it was submit to the actor. In that case, it was up to us to check the execution to see if it had completed and manually print the logs.

There is also a mode to run actors synchronously using tapis actors run, meaning the command line stays attached to the process awaiting a response after sending a message to the actor. For example:

$ tapis actors run -m "$MESSAGE" boEg3mEvrKO5w
FULL CONTEXT:
{
  "username": "taccuser",
  "HOSTNAME": "33d4dd334ef9",
  "_abaco_worker_id": "X5xGkZ0lol0D3",
  "raw_message": "{\"key1\":\"value1\", \"key2\":\"value2\"}",
  "actor_dbid": "TACC-PROD_boEg3mEvrKO5w",
  "new_foo": "new_bar",
  "_abaco_container_repo": "jturcino/abaco-trial:latest",
  "content_type": null,
  "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  "MSG": "{\"key1\":\"value1\", \"key2\":\"value2\"}",
  "HOME": "/",
  "_abaco_actor_state": "{}",
  "_abaco_actor_name": "example-actor",
  "_abaco_Content_Type": "str",
  "execution_id": "jP3RExQW108wM",
  "_abaco_synchronous": "True",
  "_abaco_access_token": "de6d11bdbb5a16bdd85beec692b1b283",
  "message_dict": {
    "key2": "value2",
    "key1": "value1"
  },
  "_abaco_api_server": "https://api.tacc.utexas.edu",
  "_abaco_actor_dbid": "TACC-PROD_boEg3mEvrKO5w",
  "_abaco_jwt_header_name": "X-Jwt-Assertion-Tacc-Prod",
  "_abaco_actor_id": "boEg3mEvrKO5w",
  "_abaco_execution_id": "jP3RExQW108wM",
  "state": "{}",
  "_abaco_username": "taccuser",
  "actor_id": "boEg3mEvrKO5w"
}
...

The output above is truncated because it is mostly the same response as our first execution of the actor. This time, however, we did not need to query the logs for this execution for them to print to screen - that was done automatically. In addition, the new environment variable settings can be seen in the context (see highlighted line).

Delete an Actor

Similar to other resources in Tapis, actors can be deleted with the following:

$ tapis actors delete boEg3mEvrKO5w
+----------+-------------------+
| Field    | Value             |
+----------+-------------------+
| deleted  | ['boEg3mEvrKO5w'] |
| messages | []                |
+----------+-------------------+

This will delete the actor and any associated executions.