Authoring a compute module in your local development environment

Beta

The Compute Modules feature is in beta and may not be available on all enrollments.

Fundamentally, compute modules are Docker containers that adhere to a basic API and can therefore be written anywhere a Docker image can be built. This document provides a high-level overview of how the various compute module APIs interact.

For a concrete example of a compute module, see Creating a Basic Python Compute Module.

Implementing the Compute Module API

If you need to work with an unsupported language, or you want maximum flexibility, you will need to create an HTTPS client that connects to the Compute Module API. This client will fetch jobs from a server, process them, and then send the results back to the server.

Connection Info

The worker needs to know the connection details to communicate with the server. For convenience, these details are stored in a file, and the path to this file is specified in an environment variable (or "env var") named CONNECTION_TO_RUNTIME. The environment variable will be present at startup, but the file may not be written when your container starts. The file contains a JSON object with the following properties:

  • host: The server hostname.
    • This is always localhost.
  • port: The server port.
    • The port is always 8945.
  • getJobPath: The API endpoint to fetch a job.
    • /interactive-module/api/internal-query/job
  • postResultPath: The API endpoint to post the job result.
    • /interactive-module/api/internal-query/results
  • moduleAuthToken: The authentication token for the module.
    • An environment variable called MODULE_AUTH_TOKEN points to a file that stores this, and will be present at startup.
  • trustStorePath: The path to the SSL certificate file.
    • An environment variable called CONNECTIONS_TO_OTHER_PODS_CA_PATH points to a file that stores this, and will be present at startup.

Example content for the connection information file:

{
    "host": "localhost",
    "port": 8945,
    "getJobPath": "/api/v1/getJob",
    "postResultPath": "/api/v1/postResult",
    "moduleAuthToken": "your_auth_token",
    "trustStorePath": "/path/to/ssl/certificate"
}

Fetching Jobs

To fetch a job from the server, the worker sends an HTTPS GET request to the getJobPath endpoint. The request must include the Module-Auth-Token header with the value of moduleAuthToken.

If the server has a job available, it will respond with a JSON object containing the job details. The worker should parse the response and extract the jobId from the computeModuleJobV1 property. The get job endpoint is long-polling, so the server will block for up to 5 seconds if no job was received, and will immediately return if a job is present. You should call this endpoint in a while loop with no delay. If no job is present, the server will respond with a HTTP status 204, if a job is present, it will respond with a status 200.

Example of a job response:

{
   "type": "computeModuleJobV1",
   "computeModuleJobV1": {
        "jobId": "12345",
        "queryType": "double",
        "query": 2
   }
}

The jobId must be used when posting a result back to the server. The queryType can be used to route jobs. The queryType will be the same as the Function name if you’re calling from Functions. The query field contains an event, and can be used to as arguments to a function.

Sending Job Results

Once the worker has processed the job, it must send the results back to the server. To do this, the worker sends an HTTPS POST request to the postResultPath endpoint, appending the jobId to the path. The request must include the following headers:

  • Module-Auth-Token: The value of moduleAuthToken.
  • Content-Type: Set to application/octet-stream.

The compiled path will look like ${connectionInfo.host}:${connectionInfo.port}${connectionInfo.postResultPath}/${jobId}.

The request body should contain the job result as an encoded byte stream, an example of this in Python is as follows: json.dumps(result).encode('utf-8')

Error Handling and Retries

The worker should handle errors and retries when fetching the connection info, fetching jobs, and sending job results. In the provided code sample, the worker retries reading the connection info file if it fails, up to a maximum number of attempts (MAX_READ_ATTEMPTS). The worker also handles errors during GET and POST requests and continues to fetch and process jobs even if an error occurs.

Implementation Steps

  1. Read the connection info from the file specified in the CONNECTION_TO_RUNTIME environment variable.
  2. Set up an HTTPS agent with the SSL certificate from the trustStorePath.
  3. Create a loop to continuously fetch and process jobs:
    1. Send a GET request to the getJobPath endpoint to fetch a job.
    2. If a job is available, process it according to the specific requirements of the job.
    3. Send a POST request to the postResultPath endpoint with the jobId to send the job results.
    4. Handle errors and retries during GET and POST requests.
  4. Start the loop to fetch and process jobs.

See Creating a Basic Python Compute Module for steps on building the docker image

Creating a Docker image

Create a Dockerfile using the base images from Python3 and update the environment variable DEPLOYED_APP_PATH to app.py as seen below. Once you’ve created the file, run the command docker build . --platform=linux/amd64 in your project folder to create your Docker image.

FROM --platform=linux/amd64 python:latest

...

COPY app.py $(DEPLOYED_APP_PATH}/

Make sure the image platform aligns with the Resource Queue you’re deploying into. Some Foundry instances can support deploying linux/arm64 images, but by default only linux/amd64 is supported. If you are building your image on Apple silicon (M series non-Intel chips), double-check your image's architecture.

Now that you have built a Docker image, you can continue with deploying your compute module.