25.10 RabbitMQ
This section provides additional details about how PEcAn uses RabbitMQ to manage communication between its Docker containers.
In PEcAn, we use the Python pika
client to retrieve messages from RabbitMQ. The PEcAn.remote library has convenience functions that wrap the API to post and read messages. The executor and models use the python version of the RabbitMQ scripts to retrieve messages, and launch the appropriate code.
25.10.1 Producer – PEcAn.remote::rabbitmq_post_message
The PEcAn.remote::rabbitmq_post_message
function allows you to post messages to RabbitMQ from R. In the RabbitMQ documentation, it is known as a “producer”. It takes the body of the message to be posted and will return any output generated when the message is posted to the approriate queue.
The function has three required arguments and two optional ones:
uri
– This is the URI used to connect to RabbitMQ, it will have the formamqp://username:password\@server:5672/vhost
. Most containers will have this injected using the environment variableRABBITMQ_URI
.queue
– The queue to post the message on, this is eitherpecan
for a workflow to be exected, the name of the model and version to be executed, for exampleSIPNET_r136
.message
– The actual message to be send, this is of type list and will be converted to a json string representation.prefix
– The code will talk to the rest api, this is the prefix of the rest api. In the case of the default docker-compse file, this will be/rabbitmq
and will be injected asRABBITMQ_PREFIX
.port
– The code will talk to the rest api, this is the port of the rest api. In the case of the default docker-compse file, this will be15672
and will be injected asRABBITMQ_PORT
.
The PEcAn.remote::start_rabbitmq
function is a wrapper for this function that provides an easy way to post a folder
message to RabbitMQ.
25.10.2 Consumer – receiver.py
The receiver.py
script runs like a daemon, constantly listening for messages.
In the RabbitMQ documentation, it is known as a “consumer”.
In PEcAn, you can tell that it is ready to receive messages if the corresponding logs (e.g. docker-compose logs executor
) show the following message:
[*] Waiting for messages. To exit press CTRL+C.
Our reciever
is configured by three environment variables:
RABBITMQ_URI
– This defines the URI where RabbitMQ is running. See corresponding argument in the producerRABBITMQ_QUEUE
– This is the name of the queue on which the consumer will listen for messages, just as in the producer.APPLICATION
– This specifies the name (including the path) of the default executable to run when receiving a message. At the moment, it should be an executable that runs in the directory specified by the message’sfolder
variable. In the case of PEcAn models, this is usually./job.sh
, such that thefolder
corresponds to therun
directory associated with a particularrunID
(i.e. where thejob.sh
is located). For the PEcAn workflow itself, this is set toR CMD BATCH workflow.R
, such that thefolder
is the root directory of the workflow (in theexecutor
Docker container, something like/data/workflows/PEcAn_<workflowID>
). This default executable is overridden if the message contains acustom_application
key. If included, the string specified by thecustom_application
key will be run as a command exactly as is on the container, from the directory specified byfolder
. For instance, in the example below, the container will print “Hello there!” instead of running its default application.{"custom_application": "echo 'Hello there!'", "folder": "/path/to/my/dir"}
NOTE that in RabbitMQ messages, the
folder
key is always required.
25.10.3 RabbitMQ and the PEcAn web interface
RabbitMQ is configured by the following variables in config.php
:
$rabbitmq_host
– The RabbitMQ server hostname (default:rabbitmq
, because that is the name of therabbitmq
service indocker-compose.yml
)$rabbitmq_port
– The port on which RabbitMQ listens for messages (default: 5672)$rabbitmq_vhost
– The path of the RabbitMQ Virtual Host (default:/
).$rabbitmq_queue
– The name of the RabbitMQ queue associated with the PEcAn workflow (default:pecan
)$rabbitmq_username
– The RabbitMQ username (default:guest
)$rabbitmq_password
– The RabbitMQ password (default:guest
)
In addition, for running models via RabbitMQ, you will also need to add an entry like the following to the config.php
$hostlist
:
$hostlist=array($fqdn => array("rabbitmq" => "amqp://guest:guest@rabbitmq/%2F"), ...)
This will set the hostname to the name of the current machine (defined by the $fqdn
variable earlier in the config.php
file) to an array with one entry, whose key is rabbitmq
and whose value is the RabbitMQ URI (amqp://...
).
These values are converted into the appropriate entries in the pecan.xml
in web/04-runpecan.php
.
25.10.4 RabbitMQ in the PEcAn XML
RabbitMQ is a special case of remote execution, so it is configured by the host
node.
An example RabbitMQ configuration is as follows:
host>
<rabbitmq>
<uri>amqp://guest:guest@rabbitmq/%2F</uri>
<queue>sipnet_136</queue>
<rabbitmq>
</host> </
Here, uri
and queue
have the same general meanings as described in “producer”.
Note that queue
here refers to the target model.
In PEcAn, RabbitMQ model queues are named as MODELTYPE_REVISION
,
so the example above refers to the SIPNET model version 136.
Another example is ED2_git
, referring to the latest git
version of the ED2 model.
25.10.5 RabbitMQ configuration in Dockerfiles
As described in the “consumer” section, our standard RabbitMQ receiver script is configured using three environment variables: RABBITMQ_URI
, RABBITMQ_QUEUE
, and APPLICATION
.
Therefore, configuring a container to work with PEcAn’s RabbitMQ instance requires setting these three variables in the Dockerfile using an ENV
statement.
For example, this excerpt from docker/base/Dockerfile.executor
(for the pecan/executor
image responsible for the PEcAn workflow) sets these variables as follows:
ENV RABBITMQ_URI="amqp://guest:guest@rabbitmq/%2F" \
"pecan" \
RABBITMQ_QUEUE="R CMD BATCH workflow.R" APPLICATION=
Similarly, this excerpt from docker/models/Dockerfile.sipnet
(which builds the SIPNET model image) is a typical example for a model image.
Note the use of ARG
here to specify a default version model version of 136 while allowing this to be configurable (via --build-arg MODEL_VERSION=X
) at build time:
ARG MODEL_VERSION=136
ENV APPLICATION="./job.sh" \
"SIPNET" \
MODEL_TYPE="${MODEL_VERSION}"
MODEL_VERSION=
ENV RABBITMQ_QUEUE="${MODEL_TYPE}_${MODEL_VERSION}"
WARNING: Dockerfile environment variables set via ENV
are assigned all at once; they do not evaluate successively, left to right.
Consider the following block:
# Don't do this!
ENV MODEL_TYPE="SIPNET" \
\
MODEL_VERSION=136 # <- Doesn't know about MODEL_TYPE or MODEL_VERSION! RABBITMQ_QUEUE=${MODEL_TYPE}_${MODEL_VERSION}
In this block, the expansion for setting RABBITMQ_QUEUE
is not aware of the current values of MODEL_TYPE
or MODEL_VERSION
, and will therefore be set incorrectly to just _
(unless they have been set previously, in which case it will be aware only of their earlier values).
As such, variables depending on other variables must be set in a separate, subsequent ENV
statement than the variables they depend on.
25.10.6 Case study: PEcAn web interface
The following describes in general terms what happens during a typical run of the PEcAn web interface with RabbitMQ.
The user initializes all containers with
docker-compose up
. All the services that interact with RabbitMQ (executor
and all models) runreceiver.py
in the foreground, waiting for messages to tell them what to do.The user browses to http://pecan.localhost/pecan/ and steps through the web interface. All the pages up to the
04-runpecan.php
run on theweb
container, and are primarily for setting up thepecan.xml
file.Once the user starts the PEcAn workflow at
04-runpecan.php
, the underlying PHP code connects to RabbitMQ (based on the URI provided inconfig.php
) and posts the following message to thepecan
queue:
{"folder": "/workflows/PEcAn_WORKFLOWID", "workflowid": "WORKFLOWID"}
- The
executor
service, which is listening on thepecan
queue, hears this message and executes itsAPPLICATION
(R CMD BATCH workflow.R
) in the working directory specified in the message’sfolder
. Theexecutor
service then performs the pre-execution steps (e.g. trait meta-analysis, conversions) itself. Then, to actually execute the model,executor
posts the following message to the target model’s queue:
{"folder": "/workflows/PEcAn_WORKFLOWID/run/RUNID"}
The target model service, which is listening on its dedicated queue, hears this message and runs its
APPLICATION
, which isjob.sh
, in the directory indicated by the message. Upon exiting (normally), the model service writes its status into a file calledrabbitmq.out
in the same directory.The
executor
container continuously looks for therabbitmq.out
file as an indication of the model run’s status. Once it sees this file, it reads the status and proceeds with the post-execution parts of the workflow. (NOTE that this isn’t perfect. If the model running process exits abnormally, therabbitmq.out
file may not be created, which can cause theexecutor
container to hang. If this happens, the solution is to restart theexecutor
container withdocker-compose restart executor
).