Agents
Agents are daemons that listens to Redis queues and execute tasks inside the system.
Agent types
The core cluster and node agent, and every module instance run their
own agent process. The process is started by the agent.service Systemd
user unit and the agent@.service Systemd system template unit provided
by the core.
Systemd units for agents are:
-
agent@cluster.servicerunning as root. Its actions are defined in/var/lib/nethserver/cluster/actions -
agent@node.servicerunning as root. Its actions are defined in/var/lib/nethserver/node/actions -
agent.servicerunning as non-privileged Unix user for each rootless module instance. See the “Additional modules” section below for more details
See also the Agent documentation.
Tasks processing
Agents wait for tasks on a Redis list. Only the cluster agent and the
api-server are allowed to push a new task for an arbitrary module agent. The
task contain two main attributes: the action name (action), and the
action input (data).
The following Redis command run the list-actions action on the cluster agent:
LPUSH cluster/tasks '{"id":"1a4a3965-d8d2-4c22-99d5-e17e6a5db36b","action":"list-actions","data":{}}'
Read the action output:
GET cluster/task/1a4a3965-d8d2-4c22-99d5-e17e6a5db36b/output
The action output is a string in JSON format.
[
"list-actions",
"destroy-module",
"add-module",
"add-user",
"remove-module",
"remove-repository",
"update-routes",
"alter-user",
"grant-actions",
"remove-user",
"add-repository",
"get-module-info",
"join-cluster",
"list-installed-modules",
"list-updates",
"revoke-actions",
"create-module",
"get-status",
"add-node",
"alter-repository",
"create-cluster",
"list-modules",
"list-repositories"
]
Read the collected stderr data from the action steps:
GET cluster/task/1a4a3965-d8d2-4c22-99d5-e17e6a5db36b/error
Read the originally submitted task payload:
GET cluster/task/1a4a3965-d8d2-4c22-99d5-e17e6a5db36b/context
Read the action exit code:
GET cluster/task/1a4a3965-d8d2-4c22-99d5-e17e6a5db36b/exit_code
The above keys are transient. After a few hours they are evicted. This command return the remaining key TTL (Time To Live), in seconds.
TTL cluster/task/1a4a3965-d8d2-4c22-99d5-e17e6a5db36b/error
While the action is running, some messages are sent through the progress channel. It is possible to get them by subscribing the channel.
SUBSCRIBE progress/cluster/task/1a4a3965-d8d2-4c22-99d5-e17e6a5db36b
Tasks submission
The Python package agent.tasks implements the task processing, handling
communication errors and re-connection/retrying logic at every stage, like:
- Task submission
- Task progress tracking
- Task response parsing
- Task cancellation when the TERM signal is received
Example:
import agent.tasks
print(agent.tasks.run(agent_id='cluster', action='list-actions', endpoint='redis://cluster-leader'))
Note the endpoint= argument: it uses the redis:// protocol, that
requires cluster privileges to work.
As said above, only the cluster agent and api-server are allowed to
LPUSH new tasks in Redis. Module agents must submit tasks for other agents
as HTTP requests, through the api-server.
Basically this can be done in two steps:
- obtain a JWT token with the module Redis credentials
- send the task request
The Python module agent.tasks implements the two steps internally.
It has a few assumptions, that are always satisfied by an action step environment:
- It obtain the module credentials from the environment from
REDIS_USERandREDIS_PASSWORD - It stores the JWT token in the current working directory (the file is
./apitoken.cache)
This is an example call, note that the agent_id is obtained from the
environment.
import agent.tasks, os
print(agent.tasks.run(agent_id=os.environ['AGENT_ID'], action='list-actions'))
The above call fails, because agents are not generally authorized to run
list-actions. However, just for this example, it is possible to override
the Redis credentials received from the environment.
import agent.tasks, os
os.environ['REDIS_USER'] = 'admin'
os.environ['REDIS_PASSWORD'] = 'Nethesis,1234'
print(agent.tasks.run(agent_id=os.environ['AGENT_ID'], action='list-actions'))
The next section explains how to authorize agents to run actions through roles assignment.
Roles and authorizations
In normal conditions module actions must be assigned to roles, and roles
must be granted to agents and users.
In Redis, a SET defines the actions assigned to a role. If default
assignments are not enough the set can be modified by the module during
the create-module action. A set element represent an exact action name
or an action name pattern, expressed in glob-pattern
syntax (e.g. get-*).
The following Bash command in the create-module action allows agents
with the actionsreader role to run list-actions on AGENT_ID (e.g.
module/mymod1).
redis-exec SADD "${AGENT_ID}/roles/actionsreader" "list-actions"
Same as above, but to allow also any other action with name prefix list-:
redis-exec SADD "${AGENT_ID}/roles/actionsreader" "list-*"
Now it is possible to grant the role actionsreader on module/mymod1 to
another agent (e.g. module/authmod2). This is the corresponding Redis command:
HSET roles/module/authmod2 module/mymod1 actionsreader
Roles are always granted by the cluster agent.
Users with the owner role can use the following actions to manage users
and their roles:
add-userremove-usergrant-actionsrevoke-actions
Those actions are implemented with the cluster.grants Python module.
A module can require additional roles to other modules when it is
instantiated. Set the label org.nethserver.authorizations of the module
image to a space separated list of values. Each value describes what is
the required role and what are the modules. The value syntax is {agent
selector}:{role}. For instance
org.nethserver.authorizations = mymod@node:actionsreader funmod@cluster:rolex coolmod@any:roley
If the installed module is authmod2, the above label is interpreted in
the following way:
-
grant
authmod2roleactionsreaderon the default instance of modulemymodrunning on the same node (@nodesuffix). The first instance ofmymodinstalled on the node ofauthmod2is usually the default one. -
grant
authmod2rolerolexon the default instance offunmodat cluster level (@clustersuffix). That means the first instance offunmodinstalled in the cluster on any node. -
grant
authmod2roleroleyon any existing instance of modulecoolmod.
Other possible agent selector values:
cluster- selects the cluster agentnode- selects the node agent where the module is running
Depending on the running node ID the value resolves to an agent ID. The above rules
are implemented in Python by the agent.resolve_agent_id() function
import agent, os
print(agent.resolve_agent_id('traefik@node', node_id=os.getenv('NODE_ID', 1)))
The node_id argument is optional.