Give connectors a store that survives restarts, and a way to clear it
`ConnectorNode.store` keeps short strings per connector package in the
engine's database (`connector_state`, migration f8c3e5a91d27), so the nodes of
one package share them and `fluksio serve` needs nothing new: get, set and
delete, at most 64 kB a value and 256 keys a package. A superuser lists the
keys (never the values) and clears them through `/connectors/{node_type}/state`,
or under Stored state in a connector node's panel. Documented in the connector
contract.
This commit is contained in:
@@ -201,6 +201,45 @@ a remote panel wants a frame every second or two; higher rates than that are
|
||||
for the local network, and full-rate video still belongs on the camera's own
|
||||
stream.
|
||||
|
||||
## Keeping state across restarts
|
||||
|
||||
A connection, a session or a device handle lives in the connector's own
|
||||
attributes and goes with the process. The few things that must not, such as
|
||||
the certificate a device presented on first contact or a token it paired with,
|
||||
go in `self.store`:
|
||||
|
||||
```python
|
||||
pem = await asyncio.to_thread(self.store.get, f"cert:{self.config.host}")
|
||||
await asyncio.to_thread(self.store.set, f"cert:{self.config.host}", pem)
|
||||
await asyncio.to_thread(self.store.delete, f"cert:{self.config.host}")
|
||||
```
|
||||
|
||||
- Keys and values are strings; `get` returns `None` for a key that is not
|
||||
there, and `delete` returns whether there was one.
|
||||
- The store belongs to the connector's **top-level package**, not to a node:
|
||||
every node of the package sees the same keys, so two nodes on one bridge
|
||||
share what was stored for it. Put the device in the key.
|
||||
- It lives in the engine's database, so it survives a restart and needs
|
||||
nothing `fluksio serve` does not have. It is available before `start`.
|
||||
- Limits: a key is 1 to 200 characters without `/` or whitespace, a value at
|
||||
most 64 kB, and a package at most 256 keys. Past any of them, `set` raises
|
||||
`ValueError`. Bytes worth keeping belong in `save_artifact`.
|
||||
- Every call is a database round trip, so on the event loop it goes through
|
||||
`asyncio.to_thread`.
|
||||
|
||||
A superuser can see the keys (never the values) and clear them, all of them or
|
||||
one at a time, under *Stored state* in a connector node's settings, or through
|
||||
the API, which names the connector by any node type it installs:
|
||||
|
||||
```sh
|
||||
curl -s $FLUKSIO/connectors/hue/state -H "Authorization: Bearer $TOKEN"
|
||||
curl -X DELETE $FLUKSIO/connectors/hue/state/cert:10.0.0.2 -H "Authorization: Bearer $TOKEN"
|
||||
curl -X DELETE $FLUKSIO/connectors/hue/state -H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
So a connector has to cope with a key disappearing: clearing is how somebody
|
||||
tells it to start over.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
```python
|
||||
@@ -239,7 +278,8 @@ load is reported separately and never runs.
|
||||
|
||||
- **Hold state that belongs in the flow.** Logic nodes are stateless and run in
|
||||
parallel; a connector may hold a connection, a session, or a device handle,
|
||||
but a value another node needs belongs in a message.
|
||||
and keep what has to outlive a restart in `self.store`, but a value another
|
||||
node needs belongs in a message.
|
||||
- **Block the event loop.** `poll`, `start` and `stop` are async and run on the
|
||||
loop. Use `asyncio.to_thread` for blocking I/O.
|
||||
- **Read credentials from the environment.** They come through parameters, so
|
||||
|
||||
Reference in New Issue
Block a user