Skip to content

Commit

Permalink
Agent: Support systemd Socket Activation
Browse files Browse the repository at this point in the history
With socket activation, the ready notification isn't useful, because the
socket is ready before the Agent is started. Disable the ready
notification in that case.
  • Loading branch information
holesch committed Aug 16, 2024
1 parent 46ca9b6 commit 0422820
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 14 deletions.
30 changes: 23 additions & 7 deletions doc/how-to-guides/set-up-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,24 @@ Log out and log back in again for the changes to take effect.

## Configuring the Service

Create a `systemd` service file (replace `<my-hub-address>` with the address or
Configure `systemd` to create and listen on a Unix domain socket:
```{code-block} systemd
:caption: /etc/systemd/system/not-my-board-agent.socket
[Unit]
Description=Board Farm Agent Socket
[Socket]
ListenStream=/run/not-my-board-agent.sock
SocketGroup=not-my-board
SocketMode=0660
[Install]
WantedBy=sockets.target
```

Then create a `systemd` service, that is started, the fist time a `not-my-board`
command connects to the *Agent* (replace `<my-hub-address>` with the address or
domain name of the *Hub*):
```{code-block} systemd
:caption: /etc/systemd/system/not-my-board-agent.service
Expand All @@ -26,18 +43,17 @@ domain name of the *Hub*):
Description=Board Farm Agent
[Service]
ExecStart=/usr/local/bin/not-my-board agent https://<my-hub-address>
[Install]
WantedBy=multi-user.target
ExecStart=/usr/local/bin/not-my-board agent --fd 0 https://<my-hub-address>
StandardInput=socket
StandardOutput=journal
```

If authentication is configured in the *Hub*, log in:
```console
$ sudo not-my-board login https://<my-hub-address>
```

Enable and start the service:
Enable and start the socket:
```console
$ sudo systemctl enable --now not-my-board-agent
$ sudo systemctl enable --now not-my-board-agent.socket
```
7 changes: 6 additions & 1 deletion doc/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Here's a description of all the commands and options `not-my-board` supports.
**`export`** \[**`-h`**|**`--help`**\] \[**`--cacert`** *cacert*\] \[**`--token-cmd`** *token_cmd*\] *hub_url* *export_description*
: Make connected boards and equipment available in the board farm.

**`agent`** \[**`-h`**|**`--help`**\] \[**`--cacert`** *cacert*\] \[**`--token-cmd`** *token_cmd*\] *hub_url*
**`agent`** \[**`-h`**|**`--help`**\] \[**`--cacert`** *cacert*\] \[**`--token-cmd`** *token_cmd*\] \[**`--fd`** *fd*\] *hub_url*
: Start an *Agent*.

**`login`** \[**`-h`**|**`--help`**\] \[**`-v`**|**`--verbose`**\] \[**`--cacert`** *cacert*\] *hub_url*
Expand Down Expand Up @@ -66,6 +66,11 @@ This option is an alternative to the `login` command. It can be used in
non-interactive environments.
```

```{option} --fd fd
Use file descriptor *fd*, instead of creating the listening socket. Should be a
Unix domain socket with the address `/run/not-my-board-agent.sock`.
```

```{option} hub_url
HTTP or HTTPS URL of the *Hub*.
```
Expand Down
17 changes: 13 additions & 4 deletions not_my_board/_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import logging
import pathlib
import shutil
import socket
import traceback
import urllib.parse
import weakref
Expand All @@ -24,9 +25,10 @@


class AgentIO:
def __init__(self, hub_url, http_client):
def __init__(self, hub_url, http_client, unix_server_fd=None):
self._hub_url = hub_url
self._http = http_client
self._unix_server_fd = unix_server_fd

@contextlib.asynccontextmanager
async def hub_rpc(self):
Expand All @@ -36,10 +38,15 @@ async def hub_rpc(self):

@contextlib.asynccontextmanager
async def unix_server(self, api_obj):
socket_path = pathlib.Path("/run") / "not-my-board-agent.sock"
if self._unix_server_fd is not None:
s = socket.socket(fileno=self._unix_server_fd)
else:
socket_path = pathlib.Path("/run") / "not-my-board-agent.sock"
if socket_path.is_socket():
socket_path.unlink(missing_ok=True)

connection_handler = functools.partial(self._handle_unix_client, api_obj)
async with util.UnixServer(connection_handler, socket_path) as unix_server:
s = socket.socket(family=socket.AF_UNIX)
s.bind(socket_path.as_posix())
socket_path.chmod(0o660)
try:
shutil.chown(socket_path, group="not-my-board")
Expand All @@ -48,6 +55,8 @@ async def unix_server(self, api_obj):
'Failed to change group on agent socket "%s": %s', socket_path, e
)

connection_handler = functools.partial(self._handle_unix_client, api_obj)
async with util.UnixServer(connection_handler, sock=s) as unix_server:
yield unix_server

@staticmethod
Expand Down
8 changes: 6 additions & 2 deletions not_my_board/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ def add_cacert_arg(subparser):
subparser.set_defaults(verbose=True)
add_cacert_arg(subparser)
subparser.add_argument("--token-cmd", help="generate ID tokens with shell command")
subparser.add_argument(
"--fd", type=int, help="listen on socket from this file descriptor"
)
subparser.add_argument("hub_url", help="http(s) URL of the hub")

subparser = add_subcommand("reserve", help="reserve a place")
Expand Down Expand Up @@ -174,11 +177,12 @@ async def _export_command(args):

async def _agent_command(args):
http_client = http.Client(args.cacert)
io = agent.AgentIO(args.hub_url, http_client)
io = agent.AgentIO(args.hub_url, http_client, args.fd)
token_src = _token_src(args, http_client)

async with agent.Agent(args.hub_url, io, token_src) as agent_:
print("ready", flush=True)
if args.fd is None:
print("ready", flush=True)
await agent_.serve_forever()


Expand Down

0 comments on commit 0422820

Please sign in to comment.