239 lines
4.9 KiB
Markdown
239 lines
4.9 KiB
Markdown
# EFKA 与 IOT 交互协议
|
||
|
||
本文档描述 `efka` 与 `iot` 之间的 TLS 长连接协议。当前协议由 Erlang term 直接序列化,发送端使用 `term_to_binary/1`,接收端使用 `binary_to_term(PacketBin, [safe])`。
|
||
|
||
## 传输层
|
||
|
||
- `efka` 作为 TLS client 连接 `iot`。
|
||
- `iot` 作为 TLS server 接收多个 `efka` 连接,一个连接对应一个 `ssl_channel` 进程。
|
||
- socket 使用 `{packet, 4}`,每个 Erlang term binary 作为一个完整包发送。
|
||
- `Ref` 使用 `make_ref()` 生成,只在当前连接的 inflight 表内匹配。
|
||
|
||
## 顶层帧
|
||
|
||
协议顶层 tuple 用来表达交互语义:
|
||
|
||
```erlang
|
||
{request, Ref, Body}
|
||
{response, Ref, Reply}
|
||
{command, Ref, {Domain, Payload}}
|
||
{command_response, Ref, {Domain, Reply}}
|
||
{message, Body}
|
||
```
|
||
|
||
语义说明:
|
||
|
||
| 帧 | 方向 | 语义 |
|
||
| --- | --- | --- |
|
||
| `{request, Ref, Body}` | efka -> iot | efka 发起请求,需要 iot 回复 |
|
||
| `{response, Ref, Reply}` | iot -> efka | iot 对 efka request 的回复 |
|
||
| `{command, Ref, {Domain, Payload}}` | iot -> efka | iot 下发命令,需要 efka 回复 |
|
||
| `{command_response, Ref, {Domain, Reply}}` | efka -> iot | efka 对 iot command 的回复 |
|
||
| `{message, Body}` | 双向 | 异步消息,不要求回复 |
|
||
|
||
`command` 和 `command_response` 的 `Domain` 表示业务域,目前支持:
|
||
|
||
- `container`
|
||
|
||
## 鉴权请求
|
||
|
||
初始连接由 `efka` 发起鉴权 request:
|
||
|
||
```erlang
|
||
{request, Ref, {auth_request, #{
|
||
uuid => UUID,
|
||
token => Token,
|
||
timestamp => Timestamp
|
||
}}}
|
||
```
|
||
|
||
`iot` 回复:
|
||
|
||
```erlang
|
||
{response, Ref, {auth_response, ok}}
|
||
{response, Ref, {auth_response, {error, {failed, Reason}}}}
|
||
```
|
||
|
||
处理语义:
|
||
|
||
- `ok`:`efka` 进入 `activated` 状态。
|
||
- `{error, {failed, Reason}}`:鉴权失败,连接关闭后重连。
|
||
|
||
## 授权控制
|
||
|
||
`/host/activate` 只修改 `iot` 本地和持久化的 host 授权状态,不再向 `efka` 下发 auth command。`efka` 可以继续保持连接并发送数据,是否处理这些数据由 `iot_host` 当前状态决定。
|
||
|
||
因此当前协议没有 `{command, Ref, {auth, ...}}` 和 `{command_response, Ref, {auth, ...}}`。授权关闭时,`iot_host` 保持 channel 在线,但不处理上报数据;授权重新打开后,已在线的 channel 可以继续使用。
|
||
|
||
## 容器管理命令
|
||
|
||
`iot` 对 `efka` 的容器管理使用 command 语义:
|
||
|
||
```erlang
|
||
{command, Ref, {container, CommandMap}}
|
||
```
|
||
|
||
`efka` 回复:
|
||
|
||
```erlang
|
||
{command_response, Ref, {container, Reply}}
|
||
```
|
||
|
||
`Reply` 取值:
|
||
|
||
```erlang
|
||
ok
|
||
{ok, Result}
|
||
{error, Reason}
|
||
```
|
||
|
||
### list
|
||
|
||
```erlang
|
||
#{action => list}
|
||
```
|
||
|
||
返回当前 `efka` 主机上的容器列表。
|
||
|
||
### deploy
|
||
|
||
```erlang
|
||
#{
|
||
action => deploy,
|
||
task_id => TaskId,
|
||
params => Params
|
||
}
|
||
```
|
||
|
||
触发容器部署。部署过程中的流式日志不通过该 command response 返回,而是通过 `message` 的 `task_event` 上报。
|
||
|
||
### start
|
||
|
||
```erlang
|
||
#{
|
||
action => start,
|
||
target => Target
|
||
}
|
||
```
|
||
|
||
### stop
|
||
|
||
```erlang
|
||
#{
|
||
action => stop,
|
||
target => Target,
|
||
timeout_seconds => TimeoutSeconds
|
||
}
|
||
```
|
||
|
||
### kill
|
||
|
||
```erlang
|
||
#{
|
||
action => kill,
|
||
target => Target,
|
||
signal => Signal
|
||
}
|
||
```
|
||
|
||
### remove
|
||
|
||
```erlang
|
||
#{
|
||
action => remove,
|
||
target => Target,
|
||
force => Force,
|
||
remove_volumes => RemoveVolumes
|
||
}
|
||
```
|
||
|
||
### config
|
||
|
||
```erlang
|
||
#{
|
||
action => config,
|
||
target => Target,
|
||
config => Config
|
||
}
|
||
```
|
||
|
||
更新容器配置文件。
|
||
|
||
### Target
|
||
|
||
容器目标使用 map 表示:
|
||
|
||
```erlang
|
||
#{
|
||
name => ContainerName,
|
||
id => ContainerId
|
||
}
|
||
```
|
||
|
||
`name` 和 `id` 至少一个非空;优先使用 `name`,`name` 为空时使用 `id`。
|
||
|
||
## 异步消息
|
||
|
||
`message` 不带 `Ref`,不要求对端回复。
|
||
|
||
### efka -> iot: data
|
||
|
||
```erlang
|
||
{message, {data, #{
|
||
route_key => RouteKey,
|
||
metric => Metric
|
||
}}}
|
||
```
|
||
|
||
用于 `efka` 上报业务指标数据。
|
||
|
||
### efka -> iot: task_event
|
||
|
||
```erlang
|
||
{message, {task_event, #{
|
||
task_id => TaskId,
|
||
type => Type,
|
||
stream => Stream
|
||
}}}
|
||
```
|
||
|
||
任务事件流关闭时:
|
||
|
||
```erlang
|
||
{message, {task_event, #{
|
||
task_id => TaskId,
|
||
type => <<"close">>,
|
||
stream => Reason
|
||
}}}
|
||
```
|
||
|
||
### iot -> efka: pub
|
||
|
||
```erlang
|
||
{message, {pub, #{
|
||
topic => Topic,
|
||
qos => Qos,
|
||
content => Content
|
||
}}}
|
||
```
|
||
|
||
用于 `iot` 向 `efka` 本地订阅系统发布 topic 消息。
|
||
|
||
## 状态与超时
|
||
|
||
- `efka` 鉴权超时时间:5 秒。
|
||
- `iot` command inflight 超时时间:60 秒。
|
||
- `iot` 管理多个 `efka` 时,每个连接有独立 `ssl_channel` 和独立 inflight 表。
|
||
- command 超时后,`iot` 删除 inflight 记录;之后如果迟到的 `command_response` 到达,会被视为未预期响应。
|
||
|
||
## 兼容性
|
||
|
||
当前协议不兼容旧 tuple:
|
||
|
||
- 旧容器管理:`{request, Ref, {container_request, ...}}`
|
||
- 旧容器回复:`{response, Ref, {container_response, ...}}`
|
||
- 旧授权控制:`{message, {auth_control, Command}}`
|
||
- 已移除的 auth command:`{command, Ref, {auth, activate | deactivate}}`
|
||
|
||
如果需要滚动升级,应先增加临时兼容分支或引入协议版本协商。
|