Compare commits
136 Commits
dev_1.3_xu
...
feature/14
| Author | SHA1 | Date | |
|---|---|---|---|
| 850ff218ce | |||
| 9f4f4cf069 | |||
| b66b425512 | |||
| 363e69d11d | |||
| da6e2e515d | |||
| 4a64fa7900 | |||
| 1ce897dbff | |||
| 08281fa231 | |||
| 60796299b0 | |||
| 6de0bced1c | |||
| 8e37ef48cb | |||
| 8613cfae60 | |||
| f3934c3bf8 | |||
| 00613afdde | |||
| ea6ac27b8d | |||
| de4f40eb2c | |||
| 87607a3991 | |||
| 96d82957a2 | |||
| 63ac3ee254 | |||
| b0f76738e5 | |||
| c00d9390dc | |||
| a5b194ce35 | |||
| a501cfb709 | |||
| f8988b18cd | |||
| a67640db83 | |||
| d5270308d6 | |||
| 6c45cc94f3 | |||
| 371c819126 | |||
| a329fed3df | |||
| 860bad2113 | |||
| 0610a8291c | |||
| b83acb281b | |||
| 8d15188ea8 | |||
| 4ca08a12a4 | |||
| fe8d4fcb14 | |||
| f9f91f072a | |||
| 4b98a9cd37 | |||
| 6d9646de2c | |||
| 9f98ac6394 | |||
| a87a73559b | |||
| 79a77f6f6e | |||
| f1a73bd2d1 | |||
| 9e339e63e7 | |||
| af25316d68 | |||
| adb68fd469 | |||
| 8c4a677ffa | |||
| 574e40a4d4 | |||
| 4f41fbe04e | |||
| 6ab044480a | |||
| 4e37b96aff | |||
| 92d184681b | |||
| 4546d90c33 | |||
| 61d3e636ae | |||
| 184d3cb95b | |||
| bd266ef2a5 | |||
| d4d227ce60 | |||
| 261d652123 | |||
| b6344074ce | |||
| ad2a25aede | |||
| 68006467f7 | |||
| 56f0d10dc6 | |||
| 6ef2c98890 | |||
| 4e6bd687db | |||
| 4047214ddf | |||
| 74012c686d | |||
| d0224d53f6 | |||
| 8e6e2f1318 | |||
| 4045f9a3a5 | |||
| e597c7c124 | |||
| 9579f65501 | |||
| 902f3ba889 | |||
| 938ef53173 | |||
| ae00e676d7 | |||
| bf8c924326 | |||
| 00d7109503 | |||
| a29fe266a4 | |||
| 2154870a43 | |||
| 9f7981e117 | |||
| 57c23f19aa | |||
| 7e1c24fcc3 | |||
| cda7fdb135 | |||
| fcd0bac070 | |||
| 8c6fb44352 | |||
| 615f8709af | |||
| 137b3516d0 | |||
| f2fbb20a29 | |||
| 51e483d924 | |||
| 265b6f65d0 | |||
| c6b408c0f9 | |||
| 752e746b97 | |||
| 51663a9410 | |||
| f843eedda9 | |||
| b3b44d50c6 | |||
| ad61caf271 | |||
| 3b2a160c5c | |||
| 0295fd3262 | |||
| e2901df3ac | |||
| 26aec5ff35 | |||
| 87275b5c57 | |||
| eaa375c5ec | |||
| 944ea6ca00 | |||
| 542bd7be80 | |||
| f6a601e16c | |||
| 417c82bef6 | |||
| f485786470 | |||
| 224ae10052 | |||
| 38421b1ae7 | |||
| 585c41b83b | |||
| af1c2c7f88 | |||
| e45830d79f | |||
| 77fc2f1a41 | |||
| e083fe4daa | |||
| c9ef5aaf4c | |||
| 34fb9657f1 | |||
| 6edc420ce2 | |||
| cd0ba15286 | |||
| fe8b89d829 | |||
| d6d44669b4 | |||
| 557a69cbd2 | |||
| 73d8ebb5c1 | |||
| cf764c35bb | |||
| e3339533b8 | |||
| afc50364f8 | |||
| 6276d13814 | |||
| 0f3d65254e | |||
| 13516c4f73 | |||
| 68cbd3c500 | |||
| 1cc3351bef | |||
| 2a6a963ecd | |||
| 6e135b9d7a | |||
| afc31c02ab | |||
| 7be865d7f1 | |||
| a58e507f92 | |||
| cc9f3f21de | |||
| 56ae2ca4fc | |||
| bcd4829486 |
@@ -1,9 +1,17 @@
|
||||
.git
|
||||
target/
|
||||
.git/
|
||||
.gitignore
|
||||
.idea
|
||||
target
|
||||
rtr-db
|
||||
tests
|
||||
specs
|
||||
scripts
|
||||
README.md
|
||||
perf.*
|
||||
**/* copy.excalidraw
|
||||
ui/rpki-explorer/node_modules/
|
||||
ui/rpki-explorer/dist/
|
||||
ui/rpki-explorer/playwright-report/
|
||||
ui/rpki-explorer/test-results/
|
||||
ui/rpki-explorer/.vite/
|
||||
deploy/arm64-compose/.env
|
||||
target/
|
||||
*.profraw
|
||||
*.profdata
|
||||
*.tar
|
||||
*.tar.gz
|
||||
*.zip
|
||||
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,5 +1,9 @@
|
||||
target/
|
||||
rtr-db/
|
||||
.idea/
|
||||
logs/
|
||||
report/
|
||||
Cargo.lock
|
||||
perf.*
|
||||
specs/* copy.excalidraw
|
||||
ui/rpki-explorer/node_modules/
|
||||
ui/rpki-explorer/dist/
|
||||
ui/rpki-explorer/playwright-report/
|
||||
ui/rpki-explorer/test-results/
|
||||
ui/rpki-explorer/.vite/
|
||||
|
||||
3528
Cargo.lock
generated
3528
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
55
Cargo.toml
55
Cargo.toml
@@ -1,40 +1,37 @@
|
||||
[package]
|
||||
name = "rpki"
|
||||
version = "1.3.0"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "rpki_rtr"
|
||||
path = "src/main_rtr.rs"
|
||||
[features]
|
||||
default = ["full"]
|
||||
# Full build used by the main RP implementation (includes RocksDB-backed storage).
|
||||
full = ["dep:rocksdb"]
|
||||
profile = ["dep:pprof", "dep:flate2"]
|
||||
|
||||
[dependencies]
|
||||
der-parser = "10.0.0"
|
||||
asn1-rs = "0.7.1"
|
||||
der-parser = { version = "10.0.0", features = ["serialize"] }
|
||||
hex = "0.4.3"
|
||||
base64 = "0.22.1"
|
||||
sha2 = "0.10.8"
|
||||
thiserror = "2.0.18"
|
||||
time = "0.3.45"
|
||||
ring = "0.17.14"
|
||||
x509-parser = { version = "0.18.0", features = ["verify"] }
|
||||
url = "2.5.8"
|
||||
asn1-rs = "0.7.1"
|
||||
asn1-rs-derive = "0.6.0"
|
||||
asn1 = "0.23.0"
|
||||
arc-swap = "1.7.0"
|
||||
chrono = { version = "0.4.44", features = ["serde"] }
|
||||
chrono-tz = "0.10"
|
||||
bytes = "1.11.1"
|
||||
tokio = { version = "1.49.0", features = ["full"] }
|
||||
rand = "0.10.0"
|
||||
rocksdb = { version = "0.21.0", default-features = false }
|
||||
serde = { version = "1", features = ["derive", "rc"] }
|
||||
serde_json = "1"
|
||||
base64 = "0.22"
|
||||
anyhow = "1"
|
||||
tracing = "0.1.44"
|
||||
sha2 = "0.10"
|
||||
tempfile = "3"
|
||||
tokio-rustls = "0.26"
|
||||
rustls = "0.23"
|
||||
rustls-pemfile = "2"
|
||||
rustls-pki-types = "1.14.0"
|
||||
socket2 = "0.5"
|
||||
russh = { version = "0.60.0", default-features = false, features = ["ring", "rsa"] }
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
||||
serde = { version = "1.0.218", features = ["derive"] }
|
||||
serde_json = { version = "1.0.140", features = ["raw_value"] }
|
||||
toml = "0.8.20"
|
||||
rocksdb = { version = "0.22.0", optional = true, default-features = false, features = ["lz4"] }
|
||||
serde_cbor = "0.11.2"
|
||||
memmap2 = "0.9.10"
|
||||
roxmltree = "0.20.0"
|
||||
quick-xml = "0.37.2"
|
||||
uuid = { version = "1.7.0", features = ["v4"] }
|
||||
reqwest = { version = "0.12.12", default-features = false, features = ["blocking", "rustls-tls", "gzip", "brotli", "deflate"] }
|
||||
pprof = { version = "0.14.1", optional = true, features = ["flamegraph", "prost-codec"] }
|
||||
flate2 = { version = "1.0.35", optional = true }
|
||||
tempfile = "3.16.0"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
287
README.client.md
287
README.client.md
@@ -1,287 +0,0 @@
|
||||
# RTR Client 使用说明
|
||||
|
||||
本文说明仓库内三类 RTR Client 的用途、启动方式和验证命令:
|
||||
|
||||
- `deploy/client`: 本仓库自带的 `rtr_debug_client`,适合做 RTR 协议调试、连通性检查和单次查询。
|
||||
- `deploy/frr`: FRR 作为黑盒 RTR Client,适合验证真实路由软件是否能消费 server 下发的 VRP。
|
||||
- `deploy/bird`: BIRD 作为黑盒 RTR Client,适合验证 TCP 或 SSH transport 下的 RPKI/ROA/ASPA 数据导入。
|
||||
|
||||
以下命令默认从仓库根目录执行。
|
||||
|
||||
## 目录
|
||||
|
||||
- [前置条件](#前置条件)
|
||||
- [rtr_debug_client](#rtr_debug_client)
|
||||
- [Docker attach 交互命令](#docker-attach-交互命令)
|
||||
- [TCP](#tcp)
|
||||
- [TLS/mTLS](#tlsmtls)
|
||||
- [SSH](#ssh)
|
||||
- [多客户端压测](#多客户端压测)
|
||||
- [FRR Client](#frr-client)
|
||||
- [BIRD Client](#bird-client)
|
||||
- [TCP](#tcp-1)
|
||||
- [SSH](#ssh-1)
|
||||
- [如何判断 Client 正常](#如何判断-client-正常)
|
||||
|
||||
## 前置条件
|
||||
|
||||
先启动 RTR Server。TCP 模式是最常用的本地验证方式:
|
||||
|
||||
```bash
|
||||
./deploy/server/build-run.sh up --mode tcp
|
||||
```
|
||||
|
||||
如果要测试 TLS 或 SSH,需要先启动对应 server 模式,并准备匹配的证书或 SSH key:
|
||||
|
||||
```bash
|
||||
./deploy/server/build-run.sh up --mode tls
|
||||
./deploy/server/build-run.sh up --mode ssh
|
||||
```
|
||||
|
||||
TLS/SSH 所需的本地开发证书和密钥可以通过脚本生成:
|
||||
|
||||
```bash
|
||||
bash scripts/generate-certs.sh
|
||||
```
|
||||
|
||||
已有 `certs/` 时使用 `--force` 覆盖。脚本生成的 `certs/tls` 和 `certs/ssh` 文件名与本仓库 compose 默认配置一致。
|
||||
|
||||
Client 连接地址不要求固定在某个 Docker 网络里。`deploy/client` 的 TCP/TLS/SSH 模式分别通过 `RPKI_RTR_TCP_SERVER_ADDR`、`RPKI_RTR_TLS_SERVER_ADDR`、`RPKI_RTR_SSH_SERVER_ADDR` 指定 server 地址。`deploy/bird` 通过 `RPKI_BIRD_RPKI_HOST`/`RPKI_BIRD_RPKI_PORT` 指定 TCP server 地址,通过 `RPKI_BIRD_SSH_RPKI_HOST`/`RPKI_RTR_SSH_PORT` 指定 SSH server 地址。示例 compose 里的默认值只是为了和本仓库 server compose 直接联调。
|
||||
|
||||
`deploy/frr` 示例默认连接 `127.0.0.1:323`,可以通过 `RPKI_FRR_RPKI_HOST`/`RPKI_FRR_RPKI_PORT` 覆盖。
|
||||
|
||||
## rtr_debug_client
|
||||
|
||||
`rtr_debug_client` 是本仓库提供的调试客户端。它会发起 RTR Reset Query 或 Serial Query,并打印 server 返回的 PDU 摘要。Docker 配置默认执行:
|
||||
|
||||
```text
|
||||
<server_addr> <protocol_version> reset --summary-only
|
||||
```
|
||||
|
||||
Docker 镜像的 `entrypoint.sh` 会把 stdout/stderr 重定向到 `logs/client/*.stdout.log` 和 `logs/client/*.stderr.log`。因此 `docker attach` 后可以输入交互命令,但命令响应和收到的 PDU 仍然主要从日志文件查看:
|
||||
|
||||
```bash
|
||||
tail -f logs/client/*.stdout.log
|
||||
tail -f logs/client/*.stderr.log
|
||||
```
|
||||
|
||||
### Docker attach 交互命令
|
||||
|
||||
如果 debug client 已经通过 compose 启动,可以 attach 到容器后输入命令:
|
||||
|
||||
```bash
|
||||
docker attach --sig-proxy=false <rtr-debug-client-container>
|
||||
```
|
||||
|
||||
脱离 attach 会话时使用 `Ctrl-P` 后接 `Ctrl-Q`,避免 `Ctrl-C` 直接停止客户端进程。
|
||||
|
||||
可用交互命令如下:
|
||||
|
||||
| 命令 | 作用 |
|
||||
| --- | --- |
|
||||
| `help` | 显示可用命令列表。 |
|
||||
| `state` | 打印当前客户端状态,包括 `session_id`、`serial`、`refresh/retry/expire`、输出模式、自动轮询状态等。 |
|
||||
| `version` | 查看当前 RTR 协议版本。 |
|
||||
| `version <n>` | 修改后续查询使用的 RTR 协议版本,例如 `version 2`。 |
|
||||
| `reset` | 发送 Reset Query,通常用于让 server 重新下发完整数据集。 |
|
||||
| `serial` | 使用当前已记录的 `session_id` 和 `serial` 发送 Serial Query。需要客户端已经从 EndOfData 中拿到状态。 |
|
||||
| `serial <session_id> <serial>` | 显式指定参数发送 Serial Query,例如 `serial 31320 156`。 |
|
||||
| `timeout` | 查看当前读取 PDU 的超时时间。 |
|
||||
| `timeout <secs>` | 修改读取 PDU 的超时时间。超时只打印提示,连接保持打开。 |
|
||||
| `poll` | 查看当前自动轮询间隔、来源、refresh hint、错误状态和暂停状态。 |
|
||||
| `poll <secs>` | 修改自动轮询间隔。 |
|
||||
| `poll pause` | 暂停自动轮询。 |
|
||||
| `poll resume` | 恢复自动轮询。 |
|
||||
| `output` | 查看当前输出模式,以及 summary 模式下跳过的 payload PDU 数量。 |
|
||||
| `output verbose` | 打印所有收到的 PDU,包括 IPv4 Prefix、IPv6 Prefix、Router Key、ASPA 等 payload PDU。 |
|
||||
| `output summary` | 只打印摘要,跳过 payload PDU 详情。compose 默认使用该模式。 |
|
||||
| `quit` / `exit` | 退出客户端进程。 |
|
||||
|
||||
常用调试流程:
|
||||
|
||||
```text
|
||||
output verbose
|
||||
reset
|
||||
state
|
||||
serial
|
||||
```
|
||||
|
||||
### TCP
|
||||
|
||||
```bash
|
||||
./deploy/client/build-run.sh up --mode tcp
|
||||
./deploy/client/build-run.sh logs --mode tcp
|
||||
./deploy/client/build-run.sh down --mode tcp
|
||||
```
|
||||
|
||||
默认连接 `rpki-rtr:323`,协议版本默认是 `2`。可以通过 `deploy/client/.env` 或环境变量覆盖:
|
||||
|
||||
```bash
|
||||
RPKI_RTR_TCP_SERVER_ADDR=10.0.0.12:323 \
|
||||
RPKI_RTR_PROTOCOL_VERSION=2 \
|
||||
./deploy/client/build-run.sh up --mode tcp
|
||||
```
|
||||
|
||||
### TLS/mTLS
|
||||
|
||||
```bash
|
||||
./deploy/client/build-run.sh up --mode tls
|
||||
./deploy/client/build-run.sh logs --mode tls
|
||||
./deploy/client/build-run.sh down --mode tls
|
||||
```
|
||||
|
||||
TLS 模式默认读取 `certs/tls` 下的示例证书,并使用 `localhost` 作为 server name。常用变量:
|
||||
|
||||
| 变量 | 默认值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `RPKI_RTR_TLS_SERVER_ADDR` | `rpki-rtr:324` | TLS RTR server 地址 |
|
||||
| `RPKI_RTR_TLS_SERVER_NAME` | `localhost` | 必须匹配 server 证书 SAN dNSName |
|
||||
| `RPKI_RTR_TLS_CA_CERT_PATH` | `/app/certs/client-ca.crt` | 容器内 CA 证书路径 |
|
||||
| `RPKI_RTR_TLS_CLIENT_CERT_PATH` | `/app/certs/client-good.crt` | 容器内 client 证书路径 |
|
||||
| `RPKI_RTR_TLS_CLIENT_KEY_PATH` | `/app/certs/client-good.key` | 容器内 client 私钥路径 |
|
||||
| `RPKI_RTR_TLS_CERTS_HOST_DIR` | `../../certs/tls` | 宿主机 TLS 证书目录 |
|
||||
|
||||
### SSH
|
||||
|
||||
```bash
|
||||
./deploy/client/build-run.sh up --mode ssh
|
||||
./deploy/client/build-run.sh logs --mode ssh
|
||||
./deploy/client/build-run.sh down --mode ssh
|
||||
```
|
||||
|
||||
SSH 模式默认连接 `rpki-rtr-ssh:22`,使用 key 认证,并校验 server public key。常用变量:
|
||||
|
||||
| 变量 | 默认值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `RPKI_RTR_SSH_SERVER_ADDR` | `rpki-rtr:22` | SSH RTR server 地址 |
|
||||
| `RPKI_RTR_SSH_USERNAME` | `rpki-rtr` | SSH 用户名 |
|
||||
| `RPKI_RTR_CLIENT_KEYS_VOLUME` | `../../certs/ssh:/app/certs:ro` | 宿主机 SSH key 目录挂载 |
|
||||
| `RPKI_RTR_CLIENT_KEY_PATH` | `/app/certs/rtr-client.key` | 容器内 client 私钥 |
|
||||
| `RPKI_RTR_SSH_SERVER_PUBKEY_PATH` | `/app/certs/ssh_host_rsa_key.pub` | 容器内 server public key |
|
||||
|
||||
### 多客户端压测
|
||||
|
||||
`--clients multi` 会启动 5 个 TCP debug client,用于观察并发连接、client report 和 server 连接统计。
|
||||
|
||||
```bash
|
||||
./deploy/client/build-run.sh up --mode tcp --clients multi
|
||||
./deploy/client/build-run.sh logs --mode tcp --clients multi
|
||||
./deploy/client/build-run.sh down --mode tcp --clients multi
|
||||
```
|
||||
|
||||
## FRR Client
|
||||
|
||||
FRR 示例用于验证标准路由软件能否通过 RTR over TCP 从 server 获取前缀验证数据。
|
||||
|
||||
启动:
|
||||
|
||||
```bash
|
||||
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
默认连接 `127.0.0.1:323`。可以通过环境变量覆盖:
|
||||
|
||||
```bash
|
||||
RPKI_FRR_RPKI_HOST=10.0.0.12 \
|
||||
RPKI_FRR_RPKI_PORT=323 \
|
||||
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
验证连接和数据:
|
||||
|
||||
```bash
|
||||
docker exec -it frr-rpki-client vtysh -c "show rpki configuration"
|
||||
docker exec -it frr-rpki-client vtysh -c "show rpki cache-connection"
|
||||
docker exec -it frr-rpki-client vtysh -c "show rpki prefix-table"
|
||||
```
|
||||
|
||||
停止:
|
||||
|
||||
```bash
|
||||
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml down
|
||||
```
|
||||
|
||||
FRR 容器启动时会读取 `deploy/frr/frr.conf.template`,并生成 `/etc/frr/frr.conf`。模板中的 RTR cache 配置为:
|
||||
|
||||
```text
|
||||
rpki cache tcp ${RPKI_FRR_RPKI_HOST} ${RPKI_FRR_RPKI_PORT} preference ${RPKI_FRR_RPKI_PREFERENCE}
|
||||
```
|
||||
|
||||
如果需要调整 polling、timeout、BGP router-id 等其他 FRR 配置,可以修改 `deploy/frr/frr.conf.template`,或在 compose 中挂载自己的模板和 entrypoint。
|
||||
|
||||
## BIRD Client
|
||||
|
||||
BIRD 示例用于验证 BIRD 3.x 对 RTR v2、ROA 和 ASPA 的导入情况。容器启动后会周期性输出 RPKI 协议状态和表项摘要。
|
||||
|
||||
### TCP
|
||||
|
||||
启动:
|
||||
|
||||
```bash
|
||||
docker compose --env-file deploy/bird/.env -f deploy/bird/docker-compose.yml up -d --build
|
||||
```
|
||||
|
||||
查看日志:
|
||||
|
||||
```bash
|
||||
docker logs -f bird-rpki-client
|
||||
```
|
||||
|
||||
停止:
|
||||
|
||||
```bash
|
||||
docker compose --env-file deploy/bird/.env -f deploy/bird/docker-compose.yml down
|
||||
```
|
||||
|
||||
默认连接 `rpki-rtr:323`。可以通过环境变量覆盖:
|
||||
|
||||
```bash
|
||||
RPKI_BIRD_RPKI_HOST=10.0.0.12 \
|
||||
RPKI_BIRD_RPKI_PORT=323 \
|
||||
docker compose --env-file deploy/bird/.env -f deploy/bird/docker-compose.yml up -d --build
|
||||
```
|
||||
|
||||
### SSH
|
||||
|
||||
先启动 server SSH 模式:
|
||||
|
||||
```bash
|
||||
./deploy/server/build-run.sh up --mode ssh
|
||||
```
|
||||
|
||||
再启动 BIRD SSH client:
|
||||
|
||||
```bash
|
||||
docker compose --env-file deploy/bird/.env \
|
||||
-f deploy/bird/docker-compose.yml \
|
||||
-f deploy/bird/docker-compose.ssh.yml \
|
||||
up -d --build
|
||||
```
|
||||
|
||||
查看日志:
|
||||
|
||||
```bash
|
||||
docker logs -f bird-rpki-client
|
||||
```
|
||||
|
||||
SSH 模式默认读取 `certs/ssh` 目录下的 key,并使用 `bird.conf.ssh.template` 生成运行时配置。关键默认值:
|
||||
|
||||
| 变量 | 默认值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `RPKI_BIRD_SSH_RPKI_HOST` | `rpki-rtr` | SSH RTR server 主机名 |
|
||||
| `RPKI_RTR_SSH_PORT` | `22` | SSH RTR server 端口 |
|
||||
| `RPKI_BIRD_SSH_CERTS_HOST_DIR` | `../../certs/ssh` | 宿主机 SSH 证书和 key 目录,复用 `rtr-client.key` |
|
||||
|
||||
## 如何判断 Client 正常
|
||||
|
||||
- `rtr_debug_client`: 日志中能看到 Reset Query 成功、EndOfData 和 payload 统计。
|
||||
- FRR: `show rpki cache-connection` 显示已连接,`show rpki prefix-table` 有 VRP 条目。
|
||||
- BIRD: 日志中 RPKI protocol 状态为 `up`,ROA/ASPA 表项摘要不为空。
|
||||
|
||||
如果 client 无法连接,优先检查:
|
||||
|
||||
- server 是否按对应 transport 启动。
|
||||
- client 配置的 server 地址是否从当前容器或主机可达。
|
||||
- TLS 的 server name 是否匹配证书 SAN。
|
||||
- SSH 的用户名、client key 和 server public key 是否匹配。
|
||||
- `RPKI_RTR_PROTOCOL_VERSION` 或 BIRD 配置中的 `min version`/`max version` 是否和 server 能力一致。
|
||||
388
README.md
388
README.md
@@ -1,373 +1,63 @@
|
||||
# RPKI RTR Server
|
||||
|
||||
这是一个用 Rust 实现的 RPKI-to-Router (RTR) cache server。服务端从 CCR 文件中读取 RPKI 数据,生成 RTR cache snapshot 和 delta,并通过 RTR 协议提供给路由器或调试客户端。
|
||||
|
||||
当前代码重点覆盖:
|
||||
# 单元测试
|
||||
|
||||
- RTR server:TCP 默认启用,可选启用 TLS/mTLS 和进程内 SSH transport。
|
||||
- 数据输入:从 CCR 目录选择最新 `.ccr` 文件,解析 VRP 和 VAP/ASPA。
|
||||
- 本地策略:可选加载 SLURM 文件,对 CCR 解析出的 payload 做过滤和本地断言。
|
||||
- 持久化:使用 RocksDB 保存 RTR cache 状态。
|
||||
- 运行报告:输出 source、client、runtime 三类 JSON report。
|
||||
- 运行期管理:可选启用 HTTP Admin API,修改部分 runtime config、触发 source reload、管理 SLURM 文件。
|
||||
- 调试工具:`rtr_debug_client` 支持 TCP、TLS 和 SSH 连接 RTR server。
|
||||
```
|
||||
cargo test
|
||||
|
||||
## 目录
|
||||
|
||||
- [协议参考](#协议参考)
|
||||
- [项目结构](#项目结构)
|
||||
- [构建与测试](#构建与测试)
|
||||
- [数据输入](#数据输入)
|
||||
- [快速启动](#快速启动)
|
||||
- [前置数据生成](#前置数据生成)
|
||||
- [Docker 运行](#docker-运行)
|
||||
- [跨架构构建](#跨架构构建)
|
||||
- [传输模式](#传输模式)
|
||||
- [主要环境变量](#主要环境变量)
|
||||
- [运行报告](#运行报告)
|
||||
- [Admin API](#admin-api)
|
||||
- [调试客户端](#调试客户端)
|
||||
- [部署入口](#部署入口)
|
||||
- [开发说明](#开发说明)
|
||||
|
||||
## 协议参考
|
||||
|
||||
- RTR: [RFC 6810](https://www.rfc-editor.org/rfc/rfc6810.html), [RFC 8210](https://www.rfc-editor.org/rfc/rfc8210.html), [draft-ietf-sidrops-8210bis-26](https://www.ietf.org/archive/id/draft-ietf-sidrops-8210bis-26.html)
|
||||
- SLURM: [RFC 8416](https://www.rfc-editor.org/rfc/rfc8416.html), [draft-ietf-sidrops-aspa-slurm-04](https://www.ietf.org/archive/id/draft-ietf-sidrops-aspa-slurm-04.html)(已过期,Expired 2026-05-20)
|
||||
- CCR: [draft-ietf-sidrops-rpki-ccr](https://www.ietf.org/archive/id/draft-ietf-sidrops-rpki-ccr-02.html)
|
||||
|
||||
## 项目结构
|
||||
|
||||
| 路径 | 说明 |
|
||||
| --- | --- |
|
||||
| `src/main_rtr.rs` | RTR server 入口 |
|
||||
| `src/rtr/` | RTR 协议、cache、store、server transport、admin、report |
|
||||
| `src/source/` | CCR/SLURM source 加载流程 |
|
||||
| `src/slurm/` | SLURM 文件解析、合并和策略应用 |
|
||||
| `src/data_model/` | RPKI 相关数据结构 |
|
||||
| `src/bin/rtr_debug_client/` | RTR 调试客户端 |
|
||||
| `deploy/server/` | server Docker 镜像和 compose |
|
||||
| `deploy/client/` | debug client Docker 镜像和 compose |
|
||||
| `deploy/frr/`, `deploy/bird/` | FRR/BIRD 作为 RTR client 的测试部署 |
|
||||
| `tests/` | 单元和集成测试 |
|
||||
|
||||
## 构建与测试
|
||||
|
||||
```bash
|
||||
cargo build --bin rpki_rtr
|
||||
cargo build --bin rtr_debug_client
|
||||
cargo test
|
||||
# 查看输出
|
||||
cargo test -- --nocapture
|
||||
```
|
||||
|
||||
服务端 binary 名称是 `rpki_rtr`:
|
||||
# 覆盖率(cargo-llvm-cov)
|
||||
|
||||
```bash
|
||||
cargo run --bin rpki_rtr
|
||||
安装工具:
|
||||
|
||||
```
|
||||
rustup component add llvm-tools-preview
|
||||
cargo install cargo-llvm-cov --locked
|
||||
```
|
||||
|
||||
默认配置会监听 `0.0.0.0:323`,从 `./data` 查找 CCR 文件,并使用 `./rtr-db` 保存 RocksDB 数据。启动前需要确保 CCR 目录里存在可解析的 `.ccr` 文件。
|
||||
统计行覆盖率并要求 >=90%:
|
||||
|
||||
## 数据输入
|
||||
|
||||
服务端启动和刷新时会读取 `RPKI_RTR_CCR_DIR` 指向的目录:
|
||||
|
||||
1. 如果该目录下存在包含 `.ccr` 文件的子目录,先按子目录名排序选择最新子目录。
|
||||
2. 在选中的目录中按文件名排序选择最新 `.ccr` 文件。
|
||||
3. 从 CCR 中解析 VRP 和 VAP/ASPA。
|
||||
4. 如果配置了 `RPKI_RTR_SLURM_DIR`,加载其中启用的 `*.slurm` 文件并应用本地策略。
|
||||
|
||||
## 快速启动
|
||||
|
||||
### 前置数据生成
|
||||
|
||||
TLS/mTLS 或 SSH transport 需要本地证书和密钥。开发和 Docker 联调可用脚本生成 compose 默认引用的最小文件集:
|
||||
|
||||
```bash
|
||||
bash scripts/10_generate-certs.sh
|
||||
```
|
||||
./scripts/coverage.sh
|
||||
# 或
|
||||
cargo llvm-cov --fail-under-lines 90
|
||||
```
|
||||
|
||||
如需覆盖已有 `certs/` 内容:
|
||||
默认会复用现有插桩产物,不会先 clean。需要强制全量重编译时:
|
||||
|
||||
```bash
|
||||
bash scripts/10_generate-certs.sh --force
|
||||
```
|
||||
COVERAGE_FORCE_CLEAN=1 ./scripts/coverage.sh
|
||||
```
|
||||
|
||||
`10_generate-certs.sh` 常用参数:
|
||||
说明:
|
||||
- 默认行为适合本地重复确认覆盖率,避免每次都重编译整套插桩目标;
|
||||
- 默认还会设置 `RPKI_SKIP_HEAVY_SCRIPT_REPLAY_TESTS=1`,跳过会拉起 shell replay pipeline 的重型集成测试,避免 coverage 期间额外触发 `target/release` 构建;
|
||||
- 默认还会设置 `RPKI_SKIP_HEAVY_BLACKBOX_TESTS=1`,跳过更慢的 blackbox CLI / CIR record 脚本测试,进一步降低日常 coverage 成本;
|
||||
- 默认还会设置 `RPKI_SKIP_HEAVY_CRYPTO_TESTS=1`,跳过需要大量 OpenSSL 生成证书/CRL 的重型密码学测试,进一步压缩日常 coverage 时长;
|
||||
- 如需把这批脚本回放测试也纳入 coverage,可显式关闭该开关:
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --- | --- |
|
||||
| `--out-dir DIR` | 指定输出目录,默认生成到 `./certs`。 |
|
||||
| `--server-dns NAME` | 指定 TLS server 证书的 dNSName SAN,默认 `localhost`。 |
|
||||
| `--tls-only` | 只生成 `certs/tls` 下的 TLS 证书和私钥。 |
|
||||
| `--ssh-only` | 只生成 `certs/ssh` 下的 SSH host key、client key 和 `authorized_keys`。 |
|
||||
| `--force` | 覆盖已有文件。未指定时,如果目标文件已存在会直接报错退出。 |
|
||||
| `-h`, `--help` | 查看脚本帮助。 |
|
||||
|
||||
TLS server name 默认是 `localhost`。如果需要其它 DNS 名称,需要同时设置 `scripts/10_generate-certs.sh --server-dns <name>` 和 client 的 `RPKI_RTR_TLS_SERVER_NAME=<name>`。
|
||||
|
||||
### Docker 运行
|
||||
|
||||
推荐使用 `deploy/server/build-run.sh` 和 `deploy/client/build-run.sh` 分别管理 server 与 debug client:
|
||||
以下命令默认从仓库根目录执行。
|
||||
|
||||
```bash
|
||||
chmod +x deploy/server/build-run.sh deploy/client/build-run.sh
|
||||
./deploy/server/build-run.sh up --mode tcp
|
||||
./deploy/client/build-run.sh up --mode tcp
|
||||
./deploy/client/build-run.sh down
|
||||
./deploy/server/build-run.sh down
|
||||
```
|
||||
RPKI_SKIP_HEAVY_SCRIPT_REPLAY_TESTS=0 ./scripts/coverage.sh
|
||||
```
|
||||
|
||||
脚本常用参数:
|
||||
如需连同第二批 blackbox 测试一起跑:
|
||||
|
||||
| 参数/变量 | server | client | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `--mode base|tcp|tls|ssh`, `MODE` | yes | yes | 选择 compose 模式,默认 `tcp`。 |
|
||||
| `--clients single|multi`, `CLIENTS` | no | yes | 单 client 或 5 client,默认 `single`。 |
|
||||
| `--no-build`, `BUILD=0` | yes | yes | 只运行已有镜像,不执行 compose build。 |
|
||||
| `--server-image TAG`, `SERVER_IMAGE` | yes | no | server 镜像名;会传给 compose 的 `RPKI_RTR_SERVER_IMAGE`。 |
|
||||
| `--client-image TAG`, `CLIENT_IMAGE` | no | yes | client 镜像名;会传给 compose 的 `RPKI_RTR_CLIENT_IMAGE`。 |
|
||||
| `--platform VALUE`, `TARGET_PLATFORM` | yes | yes | `buildx` 目标平台,默认 `linux/arm64`。 |
|
||||
| `--push`, `BUILDX_PUSH=1` | yes | yes | `buildx` 构建后推送到 registry;不设置时加载到本地 Docker。 |
|
||||
| `--skip-base-pull`, `SKIP_BASE_IMAGE_PULL=1` | yes | yes | 跳过 buildx 前的基础镜像预拉取。 |
|
||||
|
||||
支持的 action 包括 `up`、`build`、`buildx`、`rebuild`、`down`/`stop`、`restart`、`logs`、`ps`。完整帮助见:
|
||||
|
||||
```bash
|
||||
./deploy/server/build-run.sh --help
|
||||
./deploy/client/build-run.sh --help
|
||||
```
|
||||
RPKI_SKIP_HEAVY_BLACKBOX_TESTS=0 ./scripts/coverage.sh
|
||||
```
|
||||
|
||||
### 跨架构构建
|
||||
示例:x86 开发机本地构建 ARM 镜像,打包后传到 ARM 部署机。
|
||||
如需连同重型 OpenSSL 证书路径测试一起跑:
|
||||
|
||||
```bash
|
||||
./deploy/server/build-run.sh buildx \
|
||||
--platform linux/arm64 \
|
||||
--server-image rpki-rtr:arm64
|
||||
|
||||
./deploy/client/build-run.sh buildx \
|
||||
--platform linux/arm64 \
|
||||
--client-image rpki-rtr-debug-client:arm64
|
||||
|
||||
mkdir -p dist/images
|
||||
docker save rpki-rtr:arm64 -o dist/images/rpki-rtr-arm64.tar
|
||||
docker save rpki-rtr-debug-client:arm64 -o dist/images/rpki-rtr-debug-client-arm64.tar
|
||||
|
||||
scp dist/images/*.tar user@arm-host:/tmp/
|
||||
```
|
||||
RPKI_SKIP_HEAVY_CRYPTO_TESTS=0 ./scripts/coverage.sh
|
||||
```
|
||||
|
||||
ARM 部署机加载镜像并运行:
|
||||
|
||||
```bash
|
||||
docker load -i /tmp/rpki-rtr-arm64.tar
|
||||
docker load -i /tmp/rpki-rtr-debug-client-arm64.tar
|
||||
|
||||
SERVER_IMAGE=rpki-rtr:arm64 \
|
||||
BUILD=0 ./deploy/server/build-run.sh up --mode tcp
|
||||
|
||||
CLIENT_IMAGE=rpki-rtr-debug-client:arm64 \
|
||||
BUILD=0 ./deploy/client/build-run.sh up --mode tcp
|
||||
```
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/server/docker-compose.yml up -d --build
|
||||
docker compose -f deploy/server/docker-compose.yml down
|
||||
```
|
||||
|
||||
TLS/mTLS 示例:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/server/docker-compose.tls.yml up -d --build
|
||||
docker compose -f deploy/server/docker-compose.tls.yml down
|
||||
```
|
||||
|
||||
## 传输模式
|
||||
|
||||
TCP 默认启用。TLS 和 SSH 是附加监听,不会替代 TCP。
|
||||
|
||||
| 模式 | 默认地址 | 启用方式 |
|
||||
| --- | --- | --- |
|
||||
| TCP | `0.0.0.0:323` | 默认启用 |
|
||||
| TLS/mTLS | `0.0.0.0:324` | `RPKI_RTR_ENABLE_TLS=true` |
|
||||
| SSH | `0.0.0.0:22` | `RPKI_RTR_ENABLE_SSH=true` |
|
||||
|
||||
TLS server 会加载服务端证书、私钥和 client CA。SSH server 使用 OpenSSH host key 和 authorized_keys 文件;`RPKI_RTR_SSH_AUTH_MODE` 支持 `key`、`password`、`both`。
|
||||
|
||||
## 主要环境变量
|
||||
|
||||
布尔值支持 `true/false`、`1/0`、`yes/no`、`on/off`。
|
||||
|
||||
| 变量 | 默认值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `RPKI_RTR_ENABLE_TLS` | `false` | 是否启用 TLS/mTLS 监听 |
|
||||
| `RPKI_RTR_ENABLE_SSH` | `false` | 是否启用 SSH 监听 |
|
||||
| `RPKI_RTR_TCP_ADDR` | `0.0.0.0:323` | TCP 监听地址 |
|
||||
| `RPKI_RTR_TLS_ADDR` | `0.0.0.0:324` | TLS 监听地址 |
|
||||
| `RPKI_RTR_SSH_ADDR` | `0.0.0.0:22` | SSH 监听地址 |
|
||||
| `RPKI_RTR_SSH_PORT` | 未设置 | 仅覆盖 `RPKI_RTR_SSH_ADDR` 中的端口 |
|
||||
| `RPKI_RTR_DB_PATH` | `./rtr-db` | RocksDB 路径 |
|
||||
| `RPKI_RTR_CCR_DIR` | `./data` | CCR 输入目录 |
|
||||
| `RPKI_RTR_SLURM_DIR` | 未设置 | SLURM 目录;空值表示禁用 |
|
||||
| `RPKI_RTR_REPORT_DIR` | `./report` | JSON report 输出目录 |
|
||||
| `RPKI_RTR_MAX_DELTA` | `100` | 每个 RTR 版本保留的最大 delta 数,必须大于 0 |
|
||||
| `RPKI_RTR_PRUNE_DELTA_BY_SNAPSHOT_SIZE` | `false` | 是否按 snapshot wire size 继续裁剪 delta window |
|
||||
| `RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS` | `300` | source 刷新间隔,单位秒 |
|
||||
| `RPKI_RTR_REFRESH_INTERVAL_SECS` | 未设置 | 旧变量名,仍兼容;建议使用 `RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS` |
|
||||
| `RPKI_RTR_TIMING_REFRESH_SECS` | `3600` | RTR EndOfData `refresh` 字段 |
|
||||
| `RPKI_RTR_TIMING_RETRY_SECS` | `600` | RTR EndOfData `retry` 字段 |
|
||||
| `RPKI_RTR_TIMING_EXPIRE_SECS` | `7200` | RTR EndOfData `expire` 字段,必须大于 refresh 和 retry |
|
||||
| `RPKI_RTR_MAX_CONNECTIONS` | `512` | 最大并发 RTR 连接数 |
|
||||
| `RPKI_RTR_MAX_CONCURRENT_HANDSHAKES` | `128` | 最大并发握手数,不能大于最大连接数 |
|
||||
| `RPKI_RTR_NOTIFY_QUEUE_SIZE` | `1024` | Serial Notify 广播队列大小 |
|
||||
| `RPKI_RTR_TCP_KEEPALIVE_SECS` | `60` | TCP keepalive;设为 `0` 表示禁用 |
|
||||
| `RPKI_RTR_WARN_INSECURE_TCP` | `true` | TCP 模式是否输出安全提示 |
|
||||
| `RPKI_RTR_REQUIRE_TLS_SERVER_DNS_NAME_SAN` | `false` | 是否要求 TLS 服务端证书包含 dNSName SAN |
|
||||
| `RPKI_RTR_ENFORCE_TLS_CLIENT_SAN_IP_MATCH` | `true` | 是否校验 TLS client 证书 SAN IP 与 peer IP 匹配 |
|
||||
| `RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS` | `300` | runtime report 周期 |
|
||||
| `RPKI_RTR_REPORT_HISTORY_LIMIT` | `10` | 每类 report 滚动保留数量 |
|
||||
| `RPKI_RTR_TIMEZONE` | `Asia/Shanghai` | 日志和 report 使用的时区 |
|
||||
| `RPKI_RTR_ADMIN_ADDR` | 未设置 | Admin API 监听地址;未设置时关闭 |
|
||||
| `RPKI_RTR_ADMIN_TOKEN` | 未设置 | Admin API Bearer token;非 loopback 监听时必须设置 |
|
||||
|
||||
TLS 相关路径:
|
||||
|
||||
| 变量 | 默认值 |
|
||||
| --- | --- |
|
||||
| `RPKI_RTR_TLS_CERT_PATH` | `./certs/tls/server-dns.crt` |
|
||||
| `RPKI_RTR_TLS_KEY_PATH` | `./certs/tls/server-dns.key` |
|
||||
| `RPKI_RTR_TLS_CLIENT_CA_PATH` | `./certs/tls/client-ca.crt` |
|
||||
|
||||
SSH 相关配置:
|
||||
|
||||
| 变量 | 默认值 |
|
||||
| --- | --- |
|
||||
| `RPKI_RTR_SSH_HOST_KEY_PATH` | `./certs/ssh/ssh_host_rsa_key` |
|
||||
| `RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH` | `./certs/ssh/rtr-authorized_keys` |
|
||||
| `RPKI_RTR_SSH_USERNAME` | `rpki-rtr` |
|
||||
| `RPKI_RTR_SSH_SUBSYSTEM_NAME` | `rpki-rtr` |
|
||||
| `RPKI_RTR_SSH_AUTH_MODE` | `key` |
|
||||
| `RPKI_RTR_SSH_PASSWORD` | 未设置 |
|
||||
|
||||
## 运行报告
|
||||
|
||||
服务会在 `RPKI_RTR_REPORT_DIR` 下写入 JSON report,并按 `RPKI_RTR_REPORT_HISTORY_LIMIT` 滚动保留:
|
||||
|
||||
- `rtr-source-*.json`:CCR/SLURM source、fingerprint、刷新状态、数据质量、cache 统计。
|
||||
- `rtr-clients-*.json`:client 连接数和连接方式统计。
|
||||
- `rtr-runtime-*.json`:进程状态、服务状态和当前生效的 runtime config。
|
||||
|
||||
## Admin API
|
||||
|
||||
Admin API 默认关闭。设置 `RPKI_RTR_ADMIN_ADDR` 后启用:
|
||||
|
||||
```bash
|
||||
export RPKI_RTR_ADMIN_ADDR=127.0.0.1:8323
|
||||
export RPKI_RTR_ADMIN_TOKEN=change-me
|
||||
```
|
||||
|
||||
如果监听非 loopback 地址,例如 `0.0.0.0:8323`,必须设置 `RPKI_RTR_ADMIN_TOKEN`。设置 token 后,请求需要携带:
|
||||
|
||||
```http
|
||||
Authorization: Bearer change-me
|
||||
```
|
||||
|
||||
当前实现的主要接口:
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/admin/rtr/health` | 健康检查和能力开关 |
|
||||
| `GET` | `/admin/rtr/config` | 查看 runtime config |
|
||||
| `POST` | `/admin/rtr/config` | 修改部分 runtime config |
|
||||
| `GET` | `/admin/rtr/logs/tail` | 读取 stdout/stderr 日志 tail |
|
||||
| `POST` | `/admin/rtr/slurm/reload` | 触发 source reload |
|
||||
| `GET` | `/admin/rtr/slurm/files` | 列出 SLURM 文件 |
|
||||
| `POST` | `/admin/rtr/slurm/files` | 新增或覆盖 SLURM 文件 |
|
||||
| `GET` | `/admin/rtr/slurm/files/{name}` | 读取 SLURM 文件 |
|
||||
| `PUT` | `/admin/rtr/slurm/files/{name}` | 新增或覆盖指定 SLURM 文件 |
|
||||
| `DELETE` | `/admin/rtr/slurm/files/{name}` | 删除 SLURM 文件 |
|
||||
| `POST` | `/admin/rtr/slurm/files/{name}/enable` | 启用 `.slurm.disabled` 文件 |
|
||||
| `POST` | `/admin/rtr/slurm/files/{name}/disable` | 禁用 `.slurm` 文件 |
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8323/admin/rtr/config \
|
||||
-H "Authorization: Bearer change-me"
|
||||
|
||||
curl -X POST http://127.0.0.1:8323/admin/rtr/config \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer change-me" \
|
||||
-d '{"max_delta": 6, "source_refresh_interval_seconds": 60}'
|
||||
```
|
||||
|
||||
## 调试客户端
|
||||
|
||||
`rtr_debug_client` 用于手动发起 Reset Query 或 Serial Query,并观察服务端返回的 PDU。
|
||||
|
||||
RTR Client 的 Docker 调试、FRR/BIRD 互通验证和常用排障步骤见 `README.client.md`。
|
||||
|
||||
TCP:
|
||||
|
||||
```bash
|
||||
cargo run --bin rtr_debug_client -- 127.0.0.1:323 1 reset
|
||||
cargo run --bin rtr_debug_client -- 127.0.0.1:323 1 serial 42 100
|
||||
```
|
||||
|
||||
TLS:
|
||||
|
||||
```bash
|
||||
cargo run --bin rtr_debug_client -- \
|
||||
127.0.0.1:324 1 reset \
|
||||
--tls \
|
||||
--ca-cert certs/tls/client-ca.crt \
|
||||
--server-name localhost \
|
||||
--client-cert certs/tls/client-good.crt \
|
||||
--client-key certs/tls/client-good.key
|
||||
```
|
||||
|
||||
SSH:
|
||||
|
||||
```bash
|
||||
cargo run --bin rtr_debug_client -- \
|
||||
127.0.0.1:22 1 reset \
|
||||
--ssh \
|
||||
--ssh-user rpki-rtr \
|
||||
--ssh-key certs/ssh/rtr-client.key \
|
||||
--ssh-server-key certs/ssh/ssh_host_rsa_key.pub
|
||||
```
|
||||
|
||||
SSH password auth 也已实现;SSH 模式下 `--ssh-key` 和 `--ssh-password` 二选一,同时必须通过 `--ssh-known-hosts` 或 `--ssh-server-key` 做 host key 校验。
|
||||
|
||||
更多参数见 `src/bin/rtr_debug_client/README.md`。
|
||||
|
||||
## 部署入口
|
||||
|
||||
| 用途 | Compose 文件 |
|
||||
| --- | --- |
|
||||
| Server | `deploy/server/docker-compose.yml` |
|
||||
| Server TCP 示例 | `deploy/server/docker-compose.tcp.yml` |
|
||||
| Server TLS 示例 | `deploy/server/docker-compose.tls.yml` |
|
||||
| Server SSH 示例 | `deploy/server/docker-compose.ssh.yml` |
|
||||
| Debug client | `deploy/client/docker-compose.yml` |
|
||||
| Debug client 多实例 | `deploy/client/docker-compose.clients.yml` |
|
||||
| FRR client | `deploy/frr/docker-compose.yml` |
|
||||
| BIRD client | `deploy/bird/docker-compose.yml` |
|
||||
|
||||
RTR Client 统一使用说明见 `README.client.md`。
|
||||
|
||||
通用操作:
|
||||
|
||||
```bash
|
||||
COMPOSE_FILE=deploy/server/docker-compose.yml
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --build
|
||||
docker compose -f "${COMPOSE_FILE}" ps
|
||||
docker compose -f "${COMPOSE_FILE}" down
|
||||
```
|
||||
|
||||
## 开发说明
|
||||
|
||||
- 代码中的 RTR cache 按协议版本维护状态;v0 只包含 Route Origin,v1 包含 Route Origin 和 Router Key,v2 包含 Route Origin、Router Key 和 ASPA。
|
||||
- 当前 source pipeline 从 CCR 生成 Route Origin 和 ASPA,再可选应用 SLURM。
|
||||
- `RtrStore::save_cache_state_versioned(...)` 是 cache 状态写入 RocksDB 的核心入口,相关边界测试在 `tests/test_store_boundary.rs`。
|
||||
- Admin API 的日志 tail 依赖部署入口写出的 stdout/stderr 日志文件;容器入口脚本会把日志写到 `/app/logs`。
|
||||
- replay 脚本现在也支持通过环境变量注入现成二进制,避免找不到二进制时自动 `cargo build --release`:
|
||||
- `RPKI_BIN`
|
||||
- `CIR_MATERIALIZE_BIN`
|
||||
- `CIR_EXTRACT_INPUTS_BIN`
|
||||
- `CCR_TO_COMPARE_VIEWS_BIN`
|
||||
- `COVERAGE_FORCE_CLEAN=1` 适合需要完全从零重建插桩目标时使用。
|
||||
|
||||
8
benchmark/ours_manifest_bench/Cargo.toml
Normal file
8
benchmark/ours_manifest_bench/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "ours-manifest-bench"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rpki = { path = "../..", default-features = false }
|
||||
|
||||
145
benchmark/ours_manifest_bench/src/main.rs
Normal file
145
benchmark/ours_manifest_bench/src/main.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
use rpki::data_model::manifest::ManifestObject;
|
||||
use std::hint::black_box;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Config {
|
||||
sample: Option<String>,
|
||||
manifest_path: Option<PathBuf>,
|
||||
iterations: u64,
|
||||
warmup_iterations: u64,
|
||||
repeats: u32,
|
||||
}
|
||||
|
||||
fn usage_and_exit() -> ! {
|
||||
eprintln!(
|
||||
"Usage:\n ours-manifest-bench (--sample <name> | --manifest <path>) [--iterations N] [--warmup-iterations N] [--repeats N]\n\nExamples:\n cargo run --release -- --sample small-01 --iterations 20000 --warmup-iterations 2000 --repeats 3\n cargo run --release -- --manifest ../../tests/benchmark/selected_der/small-01.mft"
|
||||
);
|
||||
std::process::exit(2);
|
||||
}
|
||||
|
||||
fn parse_args() -> Config {
|
||||
let mut sample: Option<String> = None;
|
||||
let mut manifest_path: Option<PathBuf> = None;
|
||||
let mut iterations: u64 = 20_000;
|
||||
let mut warmup_iterations: u64 = 2_000;
|
||||
let mut repeats: u32 = 3;
|
||||
|
||||
let mut args = std::env::args().skip(1);
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--sample" => sample = Some(args.next().unwrap_or_else(|| usage_and_exit())),
|
||||
"--manifest" => {
|
||||
manifest_path = Some(PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit())))
|
||||
}
|
||||
"--iterations" => {
|
||||
iterations = args
|
||||
.next()
|
||||
.unwrap_or_else(|| usage_and_exit())
|
||||
.parse()
|
||||
.unwrap_or_else(|_| usage_and_exit())
|
||||
}
|
||||
"--warmup-iterations" => {
|
||||
warmup_iterations = args
|
||||
.next()
|
||||
.unwrap_or_else(|| usage_and_exit())
|
||||
.parse()
|
||||
.unwrap_or_else(|_| usage_and_exit())
|
||||
}
|
||||
"--repeats" => {
|
||||
repeats = args
|
||||
.next()
|
||||
.unwrap_or_else(|| usage_and_exit())
|
||||
.parse()
|
||||
.unwrap_or_else(|_| usage_and_exit())
|
||||
}
|
||||
"-h" | "--help" => usage_and_exit(),
|
||||
_ => usage_and_exit(),
|
||||
}
|
||||
}
|
||||
|
||||
if sample.is_none() && manifest_path.is_none() {
|
||||
usage_and_exit();
|
||||
}
|
||||
if sample.is_some() && manifest_path.is_some() {
|
||||
usage_and_exit();
|
||||
}
|
||||
|
||||
Config {
|
||||
sample,
|
||||
manifest_path,
|
||||
iterations,
|
||||
warmup_iterations,
|
||||
repeats,
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_manifest_path(sample: &str) -> PathBuf {
|
||||
// Assumes current working directory is `rpki/benchmark/ours_manifest_bench`.
|
||||
PathBuf::from(format!("../../tests/benchmark/selected_der/{sample}.mft"))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cfg = parse_args();
|
||||
let manifest_path = cfg
|
||||
.manifest_path
|
||||
.clone()
|
||||
.unwrap_or_else(|| derive_manifest_path(cfg.sample.as_deref().unwrap()));
|
||||
|
||||
let bytes = std::fs::read(&manifest_path).unwrap_or_else(|e| {
|
||||
eprintln!("read manifest fixture failed: {e}; path={}", manifest_path.display());
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
let decoded_once = ManifestObject::decode_der(&bytes).unwrap_or_else(|e| {
|
||||
eprintln!("decode failed: {e}; path={}", manifest_path.display());
|
||||
std::process::exit(1);
|
||||
});
|
||||
let file_count = decoded_once.manifest.file_count();
|
||||
|
||||
let mut round_ns_per_op: Vec<f64> = Vec::with_capacity(cfg.repeats as usize);
|
||||
let mut round_ops_per_s: Vec<f64> = Vec::with_capacity(cfg.repeats as usize);
|
||||
|
||||
for _round in 0..cfg.repeats {
|
||||
for _ in 0..cfg.warmup_iterations {
|
||||
let obj = ManifestObject::decode_der(black_box(&bytes)).expect("warmup decode");
|
||||
black_box(obj);
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
for _ in 0..cfg.iterations {
|
||||
let obj = ManifestObject::decode_der(black_box(&bytes)).expect("timed decode");
|
||||
black_box(obj);
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
let ns_per_op = (elapsed.as_secs_f64() * 1e9) / (cfg.iterations as f64);
|
||||
let ops_per_s = (cfg.iterations as f64) / elapsed.as_secs_f64();
|
||||
round_ns_per_op.push(ns_per_op);
|
||||
round_ops_per_s.push(ops_per_s);
|
||||
}
|
||||
|
||||
let avg_ns_per_op = round_ns_per_op.iter().sum::<f64>() / (round_ns_per_op.len() as f64);
|
||||
let avg_ops_per_s = round_ops_per_s.iter().sum::<f64>() / (round_ops_per_s.len() as f64);
|
||||
|
||||
let sample_name = cfg.sample.clone().unwrap_or_else(|| {
|
||||
manifest_path
|
||||
.file_name()
|
||||
.map(|s| s.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| manifest_path.display().to_string())
|
||||
});
|
||||
let sample_name = sample_name
|
||||
.strip_suffix(".mft")
|
||||
.unwrap_or(&sample_name)
|
||||
.to_string();
|
||||
|
||||
println!("fixture: {}", manifest_path.display());
|
||||
println!();
|
||||
println!("| sample | avg ns/op | ops/s | file count |");
|
||||
println!("|---|---:|---:|---:|");
|
||||
println!(
|
||||
"| {} | {:.2} | {:.2} | {} |",
|
||||
sample_name, avg_ns_per_op, avg_ops_per_s, file_count
|
||||
);
|
||||
}
|
||||
8
benchmark/routinator_object_bench/Cargo.toml
Normal file
8
benchmark/routinator_object_bench/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "routinator-object-bench"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
rpki = { version = "=0.19.1", features = ["repository"] }
|
||||
552
benchmark/routinator_object_bench/src/main.rs
Normal file
552
benchmark/routinator_object_bench/src/main.rs
Normal file
@@ -0,0 +1,552 @@
|
||||
use rpki::repository::cert::Cert;
|
||||
use rpki::repository::crl::Crl;
|
||||
use rpki::repository::manifest::Manifest;
|
||||
use rpki::repository::roa::Roa;
|
||||
use rpki::repository::aspa::Aspa;
|
||||
use rpki::repository::resources::{AsResources, IpResources};
|
||||
|
||||
use std::hint::black_box;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum ObjType {
|
||||
Cer,
|
||||
Crl,
|
||||
Manifest,
|
||||
Roa,
|
||||
Aspa,
|
||||
}
|
||||
|
||||
impl ObjType {
|
||||
fn parse(s: &str) -> Result<Self, String> {
|
||||
match s {
|
||||
"cer" => Ok(Self::Cer),
|
||||
"crl" => Ok(Self::Crl),
|
||||
"manifest" => Ok(Self::Manifest),
|
||||
"roa" => Ok(Self::Roa),
|
||||
"aspa" => Ok(Self::Aspa),
|
||||
_ => Err("type must be one of: cer, crl, manifest, roa, aspa".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
ObjType::Cer => "cer",
|
||||
ObjType::Crl => "crl",
|
||||
ObjType::Manifest => "manifest",
|
||||
ObjType::Roa => "roa",
|
||||
ObjType::Aspa => "aspa",
|
||||
}
|
||||
}
|
||||
|
||||
fn ext(self) -> &'static str {
|
||||
match self {
|
||||
ObjType::Cer => "cer",
|
||||
ObjType::Crl => "crl",
|
||||
ObjType::Manifest => "mft",
|
||||
ObjType::Roa => "roa",
|
||||
ObjType::Aspa => "asa",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Sample {
|
||||
obj_type: ObjType,
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Config {
|
||||
dir: PathBuf,
|
||||
type_filter: Option<ObjType>,
|
||||
sample_filter: Option<String>,
|
||||
fixed_iters: Option<u64>,
|
||||
warmup_iters: u64,
|
||||
rounds: u64,
|
||||
min_round_ms: u64,
|
||||
max_adaptive_iters: u64,
|
||||
strict: bool,
|
||||
cert_inspect: bool,
|
||||
out_csv: Option<PathBuf>,
|
||||
out_md: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn usage_and_exit(err: Option<&str>) -> ! {
|
||||
if let Some(err) = err {
|
||||
eprintln!("error: {err}");
|
||||
eprintln!();
|
||||
}
|
||||
eprintln!(
|
||||
"Usage:\n\
|
||||
cargo run --release --manifest-path rpki/benchmark/routinator_object_bench/Cargo.toml -- [OPTIONS]\n\
|
||||
\n\
|
||||
Options:\n\
|
||||
--dir <PATH> Fixtures root dir (default: ../../tests/benchmark/selected_der_v2)\n\
|
||||
--type <cer|crl|manifest|roa|aspa> Filter by type\n\
|
||||
--sample <NAME> Filter by sample name (e.g. p50)\n\
|
||||
--iters <N> Fixed iterations per round (optional; otherwise adaptive)\n\
|
||||
--warmup-iters <N> Warmup iterations (default: 50)\n\
|
||||
--rounds <N> Rounds (default: 5)\n\
|
||||
--min-round-ms <MS> Adaptive: minimum round time (default: 200)\n\
|
||||
--max-iters <N> Adaptive: maximum iters (default: 1_000_000)\n\
|
||||
--strict <true|false> Strict DER where applicable (default: true)\n\
|
||||
--cert-inspect Also run Cert::inspect_ca/inspect_ee where applicable (default: false)\n\
|
||||
--out-csv <PATH> Write CSV output\n\
|
||||
--out-md <PATH> Write Markdown output\n\
|
||||
"
|
||||
);
|
||||
std::process::exit(2);
|
||||
}
|
||||
|
||||
fn parse_bool(s: &str, name: &str) -> bool {
|
||||
match s {
|
||||
"1" | "true" | "TRUE" | "yes" | "YES" => true,
|
||||
"0" | "false" | "FALSE" | "no" | "NO" => false,
|
||||
_ => usage_and_exit(Some(&format!("{name} must be true/false"))),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_u64(s: &str, name: &str) -> u64 {
|
||||
s.parse::<u64>()
|
||||
.unwrap_or_else(|_| usage_and_exit(Some(&format!("{name} must be an integer"))))
|
||||
}
|
||||
|
||||
fn default_samples_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../tests/benchmark/selected_der_v2")
|
||||
}
|
||||
|
||||
fn parse_args() -> Config {
|
||||
let mut dir: PathBuf = default_samples_dir();
|
||||
let mut type_filter: Option<ObjType> = None;
|
||||
let mut sample_filter: Option<String> = None;
|
||||
let mut fixed_iters: Option<u64> = None;
|
||||
let mut warmup_iters: u64 = 50;
|
||||
let mut rounds: u64 = 5;
|
||||
let mut min_round_ms: u64 = 200;
|
||||
let mut max_adaptive_iters: u64 = 1_000_000;
|
||||
let mut strict: bool = true;
|
||||
let mut cert_inspect: bool = false;
|
||||
let mut out_csv: Option<PathBuf> = None;
|
||||
let mut out_md: Option<PathBuf> = None;
|
||||
|
||||
let mut args = std::env::args().skip(1);
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--dir" => dir = PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit(None))),
|
||||
"--type" => {
|
||||
type_filter = Some(ObjType::parse(
|
||||
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||
)
|
||||
.unwrap_or_else(|e| usage_and_exit(Some(&e))))
|
||||
}
|
||||
"--sample" => {
|
||||
sample_filter = Some(args.next().unwrap_or_else(|| usage_and_exit(None)))
|
||||
}
|
||||
"--iters" => {
|
||||
fixed_iters = Some(parse_u64(
|
||||
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||
"--iters",
|
||||
))
|
||||
}
|
||||
"--warmup-iters" => {
|
||||
warmup_iters = parse_u64(
|
||||
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||
"--warmup-iters",
|
||||
)
|
||||
}
|
||||
"--rounds" => {
|
||||
rounds = parse_u64(&args.next().unwrap_or_else(|| usage_and_exit(None)), "--rounds")
|
||||
}
|
||||
"--min-round-ms" => {
|
||||
min_round_ms = parse_u64(
|
||||
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||
"--min-round-ms",
|
||||
)
|
||||
}
|
||||
"--max-iters" => {
|
||||
max_adaptive_iters = parse_u64(
|
||||
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||
"--max-iters",
|
||||
)
|
||||
}
|
||||
"--strict" => {
|
||||
strict = parse_bool(
|
||||
&args.next().unwrap_or_else(|| usage_and_exit(None)),
|
||||
"--strict",
|
||||
)
|
||||
}
|
||||
"--cert-inspect" => cert_inspect = true,
|
||||
"--out-csv" => out_csv = Some(PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit(None)))),
|
||||
"--out-md" => out_md = Some(PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit(None)))),
|
||||
"-h" | "--help" => usage_and_exit(None),
|
||||
_ => usage_and_exit(Some(&format!("unknown argument: {arg}"))),
|
||||
}
|
||||
}
|
||||
|
||||
if warmup_iters == 0 {
|
||||
usage_and_exit(Some("--warmup-iters must be > 0"));
|
||||
}
|
||||
if rounds == 0 {
|
||||
usage_and_exit(Some("--rounds must be > 0"));
|
||||
}
|
||||
if min_round_ms == 0 {
|
||||
usage_and_exit(Some("--min-round-ms must be > 0"));
|
||||
}
|
||||
if max_adaptive_iters == 0 {
|
||||
usage_and_exit(Some("--max-iters must be > 0"));
|
||||
}
|
||||
if let Some(n) = fixed_iters {
|
||||
if n == 0 {
|
||||
usage_and_exit(Some("--iters must be > 0"));
|
||||
}
|
||||
}
|
||||
|
||||
Config {
|
||||
dir,
|
||||
type_filter,
|
||||
sample_filter,
|
||||
fixed_iters,
|
||||
warmup_iters,
|
||||
rounds,
|
||||
min_round_ms,
|
||||
max_adaptive_iters,
|
||||
strict,
|
||||
cert_inspect,
|
||||
out_csv,
|
||||
out_md,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_samples(root: &Path) -> Vec<Sample> {
|
||||
let mut out = Vec::new();
|
||||
for obj_type in [
|
||||
ObjType::Cer,
|
||||
ObjType::Crl,
|
||||
ObjType::Manifest,
|
||||
ObjType::Roa,
|
||||
ObjType::Aspa,
|
||||
] {
|
||||
let dir = root.join(obj_type.as_str());
|
||||
let rd = match std::fs::read_dir(&dir) {
|
||||
Ok(rd) => rd,
|
||||
Err(_) => continue,
|
||||
};
|
||||
for ent in rd.flatten() {
|
||||
let path = ent.path();
|
||||
if path.extension().and_then(|s| s.to_str()) != Some(obj_type.ext()) {
|
||||
continue;
|
||||
}
|
||||
let name = path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
out.push(Sample { obj_type, name, path });
|
||||
}
|
||||
}
|
||||
out.sort_by(|a, b| a.obj_type.cmp(&b.obj_type).then_with(|| a.name.cmp(&b.name)));
|
||||
out
|
||||
}
|
||||
|
||||
fn choose_iters_adaptive<F: FnMut()>(mut op: F, min_round_ms: u64, max_iters: u64) -> u64 {
|
||||
let min_secs = (min_round_ms as f64) / 1e3;
|
||||
let mut iters: u64 = 1;
|
||||
loop {
|
||||
let start = Instant::now();
|
||||
for _ in 0..iters {
|
||||
op();
|
||||
}
|
||||
let elapsed = start.elapsed().as_secs_f64();
|
||||
if elapsed >= min_secs {
|
||||
return iters;
|
||||
}
|
||||
if iters >= max_iters {
|
||||
return iters;
|
||||
}
|
||||
iters = (iters.saturating_mul(2)).min(max_iters);
|
||||
}
|
||||
}
|
||||
|
||||
fn count_ip(res: &IpResources) -> u64 {
|
||||
if res.is_inherited() {
|
||||
return 1;
|
||||
}
|
||||
let Ok(blocks) = res.to_blocks() else {
|
||||
return 0;
|
||||
};
|
||||
blocks.iter().count() as u64
|
||||
}
|
||||
|
||||
fn count_as(res: &AsResources) -> u64 {
|
||||
if res.is_inherited() {
|
||||
return 1;
|
||||
}
|
||||
let Ok(blocks) = res.to_blocks() else {
|
||||
return 0;
|
||||
};
|
||||
blocks.iter().count() as u64
|
||||
}
|
||||
|
||||
fn complexity(obj_type: ObjType, bytes: &[u8], strict: bool, cert_inspect: bool) -> u64 {
|
||||
match obj_type {
|
||||
ObjType::Cer => {
|
||||
let cert = Cert::decode(bytes).expect("decode cert");
|
||||
if cert_inspect {
|
||||
if cert.is_ca() {
|
||||
cert.inspect_ca(strict).expect("inspect ca");
|
||||
} else {
|
||||
cert.inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
}
|
||||
count_ip(cert.v4_resources())
|
||||
.saturating_add(count_ip(cert.v6_resources()))
|
||||
.saturating_add(count_as(cert.as_resources()))
|
||||
}
|
||||
ObjType::Crl => {
|
||||
let crl = Crl::decode(bytes).expect("decode crl");
|
||||
crl.revoked_certs().iter().count() as u64
|
||||
}
|
||||
ObjType::Manifest => {
|
||||
let mft = Manifest::decode(bytes, strict).expect("decode manifest");
|
||||
if cert_inspect {
|
||||
mft.cert().inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
mft.content().len() as u64
|
||||
}
|
||||
ObjType::Roa => {
|
||||
let roa = Roa::decode(bytes, strict).expect("decode roa");
|
||||
if cert_inspect {
|
||||
roa.cert().inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
roa.content().iter().count() as u64
|
||||
}
|
||||
ObjType::Aspa => {
|
||||
let asa = Aspa::decode(bytes, strict).expect("decode aspa");
|
||||
if cert_inspect {
|
||||
asa.cert().inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
asa.content().provider_as_set().len() as u64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_profile(obj_type: ObjType, bytes: &[u8], strict: bool, cert_inspect: bool) {
|
||||
match obj_type {
|
||||
ObjType::Cer => {
|
||||
let cert = Cert::decode(black_box(bytes)).expect("decode cert");
|
||||
if cert_inspect {
|
||||
if cert.is_ca() {
|
||||
cert.inspect_ca(strict).expect("inspect ca");
|
||||
} else {
|
||||
cert.inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
}
|
||||
black_box(cert);
|
||||
}
|
||||
ObjType::Crl => {
|
||||
let crl = Crl::decode(black_box(bytes)).expect("decode crl");
|
||||
black_box(crl);
|
||||
}
|
||||
ObjType::Manifest => {
|
||||
let mft = Manifest::decode(black_box(bytes), strict).expect("decode manifest");
|
||||
if cert_inspect {
|
||||
mft.cert().inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
black_box(mft);
|
||||
}
|
||||
ObjType::Roa => {
|
||||
let roa = Roa::decode(black_box(bytes), strict).expect("decode roa");
|
||||
if cert_inspect {
|
||||
roa.cert().inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
black_box(roa);
|
||||
}
|
||||
ObjType::Aspa => {
|
||||
let asa = Aspa::decode(black_box(bytes), strict).expect("decode aspa");
|
||||
if cert_inspect {
|
||||
asa.cert().inspect_ee(strict).expect("inspect ee");
|
||||
}
|
||||
black_box(asa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ResultRow {
|
||||
obj_type: String,
|
||||
sample: String,
|
||||
size_bytes: usize,
|
||||
complexity: u64,
|
||||
avg_ns_per_op: f64,
|
||||
ops_per_sec: f64,
|
||||
}
|
||||
|
||||
fn render_markdown(title: &str, rows: &[ResultRow]) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str(&format!("# {title}\n\n"));
|
||||
out.push_str("| type | sample | size_bytes | complexity | avg ns/op | ops/s |\n");
|
||||
out.push_str("|---|---|---:|---:|---:|---:|\n");
|
||||
for r in rows {
|
||||
out.push_str(&format!(
|
||||
"| {} | {} | {} | {} | {:.2} | {:.2} |\n",
|
||||
r.obj_type, r.sample, r.size_bytes, r.complexity, r.avg_ns_per_op, r.ops_per_sec
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn render_csv(rows: &[ResultRow]) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("type,sample,size_bytes,complexity,avg_ns_per_op,ops_per_sec\n");
|
||||
for r in rows {
|
||||
let sample = r.sample.replace('"', "\"\"");
|
||||
out.push_str(&format!(
|
||||
"{},{},{},{},{:.6},{:.6}\n",
|
||||
r.obj_type,
|
||||
format!("\"{}\"", sample),
|
||||
r.size_bytes,
|
||||
r.complexity,
|
||||
r.avg_ns_per_op,
|
||||
r.ops_per_sec
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn create_parent_dirs(path: &Path) {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).unwrap_or_else(|e| {
|
||||
panic!("create_dir_all {}: {e}", parent.display());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn write_text_file(path: &Path, content: &str) {
|
||||
create_parent_dirs(path);
|
||||
std::fs::write(path, content).unwrap_or_else(|e| panic!("write {}: {e}", path.display()));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cfg = parse_args();
|
||||
let mut samples = read_samples(&cfg.dir);
|
||||
if samples.is_empty() {
|
||||
usage_and_exit(Some(&format!(
|
||||
"no samples found under: {}",
|
||||
cfg.dir.display()
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(t) = cfg.type_filter {
|
||||
samples.retain(|s| s.obj_type == t);
|
||||
if samples.is_empty() {
|
||||
usage_and_exit(Some(&format!("no sample matched --type {}", t.as_str())));
|
||||
}
|
||||
}
|
||||
if let Some(filter) = cfg.sample_filter.as_deref() {
|
||||
samples.retain(|s| s.name == filter);
|
||||
if samples.is_empty() {
|
||||
usage_and_exit(Some(&format!("no sample matched --sample {filter}")));
|
||||
}
|
||||
}
|
||||
|
||||
println!("# Routinator baseline (rpki crate) decode benchmark (selected_der_v2)");
|
||||
println!();
|
||||
println!("- dir: {}", cfg.dir.display());
|
||||
println!("- strict: {}", cfg.strict);
|
||||
println!("- cert_inspect: {}", cfg.cert_inspect);
|
||||
if let Some(t) = cfg.type_filter {
|
||||
println!("- type: {}", t.as_str());
|
||||
}
|
||||
if let Some(s) = cfg.sample_filter.as_deref() {
|
||||
println!("- sample: {}", s);
|
||||
}
|
||||
if let Some(n) = cfg.fixed_iters {
|
||||
println!("- iters: {} (fixed)", n);
|
||||
} else {
|
||||
println!(
|
||||
"- warmup: {} iters, rounds: {}, min_round: {}ms (adaptive iters, max {})",
|
||||
cfg.warmup_iters, cfg.rounds, cfg.min_round_ms, cfg.max_adaptive_iters
|
||||
);
|
||||
}
|
||||
if let Some(p) = cfg.out_csv.as_ref() {
|
||||
println!("- out_csv: {}", p.display());
|
||||
}
|
||||
if let Some(p) = cfg.out_md.as_ref() {
|
||||
println!("- out_md: {}", p.display());
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("| type | sample | size_bytes | complexity | avg ns/op | ops/s |");
|
||||
println!("|---|---|---:|---:|---:|---:|");
|
||||
|
||||
let mut rows: Vec<ResultRow> = Vec::with_capacity(samples.len());
|
||||
for sample in &samples {
|
||||
let bytes = std::fs::read(&sample.path)
|
||||
.unwrap_or_else(|e| panic!("read {}: {e}", sample.path.display()));
|
||||
let size_bytes = bytes.len();
|
||||
let complexity = complexity(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect);
|
||||
|
||||
for _ in 0..cfg.warmup_iters {
|
||||
decode_profile(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect);
|
||||
}
|
||||
|
||||
let mut per_round_ns_per_op = Vec::with_capacity(cfg.rounds as usize);
|
||||
for _round in 0..cfg.rounds {
|
||||
let iters = if let Some(n) = cfg.fixed_iters {
|
||||
n
|
||||
} else {
|
||||
choose_iters_adaptive(
|
||||
|| decode_profile(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect),
|
||||
cfg.min_round_ms,
|
||||
cfg.max_adaptive_iters,
|
||||
)
|
||||
};
|
||||
let start = Instant::now();
|
||||
for _ in 0..iters {
|
||||
decode_profile(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect);
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
let total_ns = elapsed.as_secs_f64() * 1e9;
|
||||
per_round_ns_per_op.push(total_ns / (iters as f64));
|
||||
}
|
||||
|
||||
let avg_ns = per_round_ns_per_op.iter().sum::<f64>() / (per_round_ns_per_op.len() as f64);
|
||||
let ops_per_sec = 1e9_f64 / avg_ns;
|
||||
|
||||
println!(
|
||||
"| {} | {} | {} | {} | {:.2} | {:.2} |",
|
||||
sample.obj_type.as_str(),
|
||||
sample.name,
|
||||
size_bytes,
|
||||
complexity,
|
||||
avg_ns,
|
||||
ops_per_sec
|
||||
);
|
||||
|
||||
rows.push(ResultRow {
|
||||
obj_type: sample.obj_type.as_str().to_string(),
|
||||
sample: sample.name.clone(),
|
||||
size_bytes,
|
||||
complexity,
|
||||
avg_ns_per_op: avg_ns,
|
||||
ops_per_sec,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(path) = cfg.out_md.as_ref() {
|
||||
let md = render_markdown(
|
||||
"Routinator baseline (rpki crate) decode+inspect (selected_der_v2)",
|
||||
&rows,
|
||||
);
|
||||
write_text_file(path, &md);
|
||||
eprintln!("Wrote {}", path.display());
|
||||
}
|
||||
if let Some(path) = cfg.out_csv.as_ref() {
|
||||
let csv = render_csv(&rows);
|
||||
write_text_file(path, &csv);
|
||||
eprintln!("Wrote {}", path.display());
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"slurmVersion": 2,
|
||||
"validationOutputFilters": {
|
||||
"prefixFilters": [
|
||||
{
|
||||
"prefix": "24.0.0.0/8",
|
||||
"comment": "Filter many VRPs in current CCR sample"
|
||||
}
|
||||
],
|
||||
"bgpsecFilters": [],
|
||||
"aspaFilters": [
|
||||
{
|
||||
"customerAsn": 80,
|
||||
"comment": "Filter one ASPA known to exist in current CCR sample"
|
||||
}
|
||||
]
|
||||
},
|
||||
"locallyAddedAssertions": {
|
||||
"prefixAssertions": [],
|
||||
"bgpsecAssertions": [],
|
||||
"aspaAssertions": []
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
# Build-time image knob.
|
||||
RPKI_BIRD_VERSION=3.2.1
|
||||
|
||||
# TCP mode target endpoint.
|
||||
RPKI_BIRD_RPKI_HOST=rpki-rtr
|
||||
RPKI_BIRD_RPKI_PORT=323
|
||||
|
||||
# SSH mode target endpoint.
|
||||
RPKI_BIRD_SSH_RPKI_HOST=rpki-rtr
|
||||
RPKI_RTR_SSH_PORT=22
|
||||
|
||||
# Config template paths in container.
|
||||
RPKI_BIRD_CONFIG_TEMPLATE_PATH=/config/bird.conf.template
|
||||
RPKI_BIRD_SSH_CONFIG_TEMPLATE_PATH=/config/bird.conf.ssh.template
|
||||
|
||||
# Observation and output knobs.
|
||||
RPKI_BIRD_OBSERVE_PROTO=rpki_tcp
|
||||
RPKI_BIRD_OBSERVE_MODE=interval
|
||||
RPKI_BIRD_OBSERVE_DEBOUNCE_SECS=1
|
||||
RPKI_BIRD_OBSERVE_INTERVAL=30
|
||||
RPKI_BIRD_OBSERVE_ASPA_TABLE=rtr_aspa
|
||||
RPKI_BIRD_OBSERVE_ROA4_TABLE=rtr_roa_v4
|
||||
RPKI_BIRD_OBSERVE_ROA6_TABLE=rtr_roa_v6
|
||||
RPKI_BIRD_OBSERVE_ASPA_COUNT=3
|
||||
RPKI_BIRD_OBSERVE_ROA4_COUNT=3
|
||||
RPKI_BIRD_OBSERVE_ROA6_COUNT=3
|
||||
RPKI_BIRD_SHOW_ASPA=1
|
||||
RPKI_BIRD_SHOW_ROA4=1
|
||||
RPKI_BIRD_SHOW_ROA6=1
|
||||
|
||||
# Host volume mounts.
|
||||
RPKI_BIRD_LOG_HOST_DIR=../../logs/bird
|
||||
RPKI_BIRD_SSH_CERTS_HOST_DIR=../../certs/ssh
|
||||
@@ -1,56 +0,0 @@
|
||||
FROM debian:bookworm-slim AS builder
|
||||
|
||||
ARG BIRD_VERSION=3.2.1
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
bison \
|
||||
flex \
|
||||
m4 \
|
||||
perl \
|
||||
ca-certificates \
|
||||
wget \
|
||||
xz-utils \
|
||||
libreadline-dev \
|
||||
libncurses-dev \
|
||||
libssh-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
RUN wget -O bird.tar.gz "https://bird.nic.cz/download/bird-${BIRD_VERSION}.tar.gz" \
|
||||
&& tar -xzf bird.tar.gz \
|
||||
&& mv "bird-${BIRD_VERSION}" bird
|
||||
|
||||
WORKDIR /build/bird
|
||||
|
||||
RUN ./configure \
|
||||
--prefix=/usr \
|
||||
--sysconfdir=/etc/bird \
|
||||
--localstatedir=/run \
|
||||
&& make -j"$(nproc)" \
|
||||
&& make install
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
gettext-base \
|
||||
tzdata \
|
||||
netcat-openbsd \
|
||||
libreadline8 \
|
||||
libncurses6 \
|
||||
libtinfo6 \
|
||||
libssh-4 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /usr/sbin/bird /usr/sbin/bird
|
||||
COPY --from=builder /usr/sbin/birdc /usr/sbin/birdc
|
||||
COPY --from=builder /etc/bird /etc/bird
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh \
|
||||
&& mkdir -p /run/bird
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -1,90 +0,0 @@
|
||||
# BIRD Minimal RTR Client Config
|
||||
|
||||
This folder provides a minimal BIRD setup for black-box interop testing
|
||||
against this repository's RTR server defaults.
|
||||
|
||||
Server defaults in this repo:
|
||||
- TCP: `0.0.0.0:323`
|
||||
- SSH: `0.0.0.0:22` (when enabled on server)
|
||||
|
||||
## Files
|
||||
|
||||
- `Dockerfile`: builds a minimal BIRD2 runtime image.
|
||||
- `bird.conf.example`: sample `/etc/bird/bird.conf`.
|
||||
- `bird.conf.ssh.example`: sample SSH transport `/etc/bird/bird.conf`.
|
||||
- `entrypoint.sh`: starts BIRD in foreground mode.
|
||||
- `docker-compose.yml`: one-click local TCP test client.
|
||||
- `docker-compose.ssh.yml`: compose override for SSH transport.
|
||||
|
||||
By default, the container uses event-driven observation and prints snapshots
|
||||
only when BIRD reports RPKI-related changes.
|
||||
|
||||
## Docker quick start
|
||||
|
||||
From repository root:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/bird/docker-compose.yml up --build
|
||||
```
|
||||
|
||||
Use another terminal to inspect:
|
||||
|
||||
```bash
|
||||
docker logs -f bird-rpki-client
|
||||
```
|
||||
|
||||
If protocol state is `up`, the RTR client path is working.
|
||||
|
||||
Detached mode:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/bird/docker-compose.yml up -d --build
|
||||
docker logs -f bird-rpki-client
|
||||
```
|
||||
|
||||
Stop:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/bird/docker-compose.yml down
|
||||
```
|
||||
|
||||
## SSH quick start
|
||||
|
||||
Start server in SSH mode first:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/server/docker-compose.ssh.yml up -d --build
|
||||
```
|
||||
|
||||
Start BIRD client with SSH override:
|
||||
|
||||
```bash
|
||||
docker compose \
|
||||
-f deploy/bird/docker-compose.yml \
|
||||
-f deploy/bird/docker-compose.ssh.yml \
|
||||
up --build
|
||||
```
|
||||
|
||||
In detached mode, observe with:
|
||||
|
||||
```bash
|
||||
docker logs -f bird-rpki-client
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- This setup renders `bird.conf.template` at startup and uses `RPKI_HOST` / `RPKI_PORT`
|
||||
for the RTR endpoint.
|
||||
- `network_mode: host` expects your RTR server to be reachable at
|
||||
`host.docker.internal:323` from the container.
|
||||
- Observation is controlled by env vars:
|
||||
`OBSERVE_MODE` (`event` by default, `interval` as fallback),
|
||||
`OBSERVE_DEBOUNCE_SECS` (default `1`),
|
||||
`OBSERVE_INTERVAL` (seconds, used when `OBSERVE_MODE=interval`),
|
||||
and `OBSERVE_PROTO`.
|
||||
- SSH mode mounts `../../certs/ssh` into `/config/ssh` and expects:
|
||||
`rtr-client.key` and `ssh_host_rsa_key.pub`.
|
||||
- Entrypoint auto-generates `/run/bird/known_hosts` from
|
||||
`/config/ssh/ssh_host_rsa_key.pub` for BIRD SSH host-key verification.
|
||||
- If your environment does not support Docker host networking, switch to a
|
||||
bridge network and replace `remote` addresses accordingly.
|
||||
@@ -1,80 +0,0 @@
|
||||
# BIRD 最小化 RTR 客户端配置
|
||||
|
||||
本目录提供最小化 BIRD 配置,用于与本仓库 RTR Server 做黑盒互通测试。
|
||||
|
||||
本仓库默认 RTR 监听地址:
|
||||
- TCP: `0.0.0.0:323`
|
||||
- SSH: `0.0.0.0:22`(仅在 server SSH 模式启用时)
|
||||
|
||||
## 文件说明
|
||||
|
||||
- `Dockerfile`: 构建最小 BIRD 运行镜像(包含 SSH transport 支持)。
|
||||
- `bird.conf.example`: `/etc/bird/bird.conf` 的 TCP 示例。
|
||||
- `bird.conf.ssh.example`: `/etc/bird/bird.conf` 的 SSH transport 示例。
|
||||
- `entrypoint.sh`: 前台启动 BIRD,并周期输出协议快照。
|
||||
- `docker-compose.yml`: TCP 一键启动。
|
||||
- `docker-compose.ssh.yml`: SSH 覆盖配置。
|
||||
|
||||
容器默认每 30 秒向日志输出一次 RPKI 协议状态快照。
|
||||
|
||||
## Docker 快速启动(TCP)
|
||||
|
||||
在仓库根目录执行:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/bird/docker-compose.yml up --build
|
||||
```
|
||||
|
||||
另开终端查看日志:
|
||||
|
||||
```bash
|
||||
docker logs -f bird-rpki-client
|
||||
```
|
||||
|
||||
如果协议状态显示 `up`,说明 RTR 客户端链路正常。
|
||||
|
||||
后台模式:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/bird/docker-compose.yml up -d --build
|
||||
docker logs -f bird-rpki-client
|
||||
```
|
||||
|
||||
停止:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/bird/docker-compose.yml down
|
||||
```
|
||||
|
||||
## SSH 快速启动
|
||||
|
||||
先启动 server 的 SSH 模式:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/server/docker-compose.ssh.yml up -d --build
|
||||
```
|
||||
|
||||
再启动 BIRD SSH 客户端:
|
||||
|
||||
```bash
|
||||
docker compose \
|
||||
-f deploy/bird/docker-compose.yml \
|
||||
-f deploy/bird/docker-compose.ssh.yml \
|
||||
up --build
|
||||
```
|
||||
|
||||
查看日志:
|
||||
|
||||
```bash
|
||||
docker logs -f bird-rpki-client
|
||||
```
|
||||
|
||||
## 说明
|
||||
|
||||
- 当前 compose 使用 `network_mode: host`,默认通过 `host.docker.internal` 访问 server。
|
||||
- 观测频率由环境变量控制:`OBSERVE_INTERVAL`(秒,默认 `30`)和 `OBSERVE_PROTO`。
|
||||
- SSH 模式会将 `../../certs/ssh` 挂载到容器 `/config/ssh`,并使用:
|
||||
`rtr-client.key`、`ssh_host_rsa_key.pub`。
|
||||
- 入口脚本会基于 `/config/ssh/ssh_host_rsa_key.pub` 自动生成
|
||||
`/run/bird/known_hosts`,用于 BIRD 的 SSH 主机密钥校验。
|
||||
- 如果你运行在 Docker Desktop(非 Linux 原生 host network 场景),建议改为自定义 bridge 网络并将 `remote` 地址改成可达的 server 容器名或宿主机地址。
|
||||
@@ -1,31 +0,0 @@
|
||||
log stderr all;
|
||||
debug protocols all;
|
||||
router id 192.0.2.2;
|
||||
|
||||
roa4 table rtr_roa_v4;
|
||||
roa6 table rtr_roa_v6;
|
||||
aspa table rtr_aspa;
|
||||
|
||||
protocol device {
|
||||
}
|
||||
|
||||
protocol rpki rpki_ssh {
|
||||
roa4 { table rtr_roa_v4; };
|
||||
roa6 { table rtr_roa_v6; };
|
||||
aspa { table rtr_aspa; };
|
||||
|
||||
remote "host.docker.internal" port 22;
|
||||
|
||||
min version 2;
|
||||
max version 2;
|
||||
|
||||
refresh 3600;
|
||||
retry 600;
|
||||
expire 7200;
|
||||
|
||||
transport ssh {
|
||||
user "rpki-rtr";
|
||||
bird private key "/config/ssh/rtr-client.key";
|
||||
remote public key "/run/bird/known_hosts";
|
||||
};
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
log stderr all;
|
||||
router id 192.0.2.2;
|
||||
|
||||
roa4 table rtr_roa_v4;
|
||||
roa6 table rtr_roa_v6;
|
||||
aspa table rtr_aspa;
|
||||
|
||||
protocol device {
|
||||
}
|
||||
|
||||
protocol rpki rpki_tcp {
|
||||
roa4 { table rtr_roa_v4; };
|
||||
roa6 { table rtr_roa_v6; };
|
||||
aspa { table rtr_aspa; };
|
||||
|
||||
remote "host.docker.internal" port 323;
|
||||
|
||||
min version 2;
|
||||
max version 2;
|
||||
|
||||
refresh 3600;
|
||||
retry 600;
|
||||
expire 7200;
|
||||
|
||||
transport tcp {
|
||||
authentication none;
|
||||
};
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
log stderr all;
|
||||
router id 192.0.2.2;
|
||||
|
||||
roa4 table rtr_roa_v4;
|
||||
roa6 table rtr_roa_v6;
|
||||
aspa table rtr_aspa;
|
||||
|
||||
protocol device {
|
||||
}
|
||||
|
||||
protocol rpki rpki_tcp {
|
||||
roa4 { table rtr_roa_v4; };
|
||||
roa6 { table rtr_roa_v6; };
|
||||
aspa { table rtr_aspa; };
|
||||
|
||||
remote "host.docker.internal" port 323;
|
||||
|
||||
min version 2;
|
||||
max version 2;
|
||||
|
||||
refresh 3600;
|
||||
retry 600;
|
||||
expire 7200;
|
||||
|
||||
transport tcp {
|
||||
authentication none;
|
||||
};
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
log stderr all;
|
||||
router id 192.0.2.2;
|
||||
|
||||
roa4 table rtr_roa_v4;
|
||||
roa6 table rtr_roa_v6;
|
||||
aspa table rtr_aspa;
|
||||
|
||||
protocol device {
|
||||
}
|
||||
|
||||
protocol rpki rpki_ssh {
|
||||
roa4 { table rtr_roa_v4; };
|
||||
roa6 { table rtr_roa_v6; };
|
||||
aspa { table rtr_aspa; };
|
||||
|
||||
remote "host.docker.internal" port 22;
|
||||
|
||||
min version 2;
|
||||
max version 2;
|
||||
|
||||
refresh 3600;
|
||||
retry 600;
|
||||
expire 7200;
|
||||
|
||||
transport ssh {
|
||||
user "rpki-rtr";
|
||||
bird private key "/config/ssh/rtr-client.key";
|
||||
remote public key "/run/bird/known_hosts";
|
||||
};
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
log stderr all;
|
||||
router id 192.0.2.2;
|
||||
|
||||
roa4 table rtr_roa_v4;
|
||||
roa6 table rtr_roa_v6;
|
||||
aspa table rtr_aspa;
|
||||
|
||||
protocol device {
|
||||
}
|
||||
|
||||
protocol rpki rpki_ssh {
|
||||
roa4 { table rtr_roa_v4; };
|
||||
roa6 { table rtr_roa_v6; };
|
||||
aspa { table rtr_aspa; };
|
||||
|
||||
remote "${RPKI_HOST}" port ${RPKI_PORT};
|
||||
|
||||
min version 2;
|
||||
max version 2;
|
||||
|
||||
refresh 3600;
|
||||
retry 600;
|
||||
expire 7200;
|
||||
|
||||
transport ssh {
|
||||
user "rpki-rtr";
|
||||
bird private key "/config/ssh/rtr-client.key";
|
||||
remote public key "/run/bird/known_hosts";
|
||||
};
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
log stderr all;
|
||||
router id 192.0.2.2;
|
||||
|
||||
roa4 table rtr_roa_v4;
|
||||
roa6 table rtr_roa_v6;
|
||||
aspa table rtr_aspa;
|
||||
|
||||
protocol device {
|
||||
}
|
||||
|
||||
protocol rpki rpki_tcp {
|
||||
roa4 { table rtr_roa_v4; };
|
||||
roa6 { table rtr_roa_v6; };
|
||||
aspa { table rtr_aspa; };
|
||||
|
||||
remote "${RPKI_HOST}" port ${RPKI_PORT};
|
||||
|
||||
min version 2;
|
||||
max version 2;
|
||||
|
||||
refresh 3600;
|
||||
retry 600;
|
||||
expire 7200;
|
||||
|
||||
transport tcp {
|
||||
authentication none;
|
||||
};
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
services:
|
||||
bird-rpki-client:
|
||||
environment:
|
||||
BIRD_CONFIG_TEMPLATE_PATH: "${RPKI_BIRD_SSH_CONFIG_TEMPLATE_PATH:-/config/bird.conf.ssh.template}"
|
||||
RPKI_HOST: "${RPKI_BIRD_SSH_RPKI_HOST:-rpki-rtr}"
|
||||
RPKI_PORT: "${RPKI_RTR_SSH_PORT:-22}"
|
||||
OBSERVE_PROTO: "rpki_ssh"
|
||||
volumes:
|
||||
- ./bird.conf.ssh.template:/config/bird.conf.ssh.template:ro
|
||||
- ${RPKI_BIRD_SSH_CERTS_HOST_DIR:-../../certs/ssh}:/config/ssh:ro
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
networks:
|
||||
rpki_net:
|
||||
name: rpki_net
|
||||
driver: bridge
|
||||
@@ -1,41 +0,0 @@
|
||||
services:
|
||||
bird-rpki-client:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
BIRD_VERSION: "${RPKI_BIRD_VERSION:-3.2.1}"
|
||||
container_name: bird-rpki-client
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
BIRD_CONFIG_TEMPLATE_PATH: "${RPKI_BIRD_CONFIG_TEMPLATE_PATH:-/config/bird.conf.template}"
|
||||
|
||||
RPKI_HOST: "${RPKI_BIRD_RPKI_HOST:-rpki-rtr}"
|
||||
RPKI_PORT: "${RPKI_BIRD_RPKI_PORT:-323}"
|
||||
|
||||
OBSERVE_PROTO: "${RPKI_BIRD_OBSERVE_PROTO:-rpki_tcp}"
|
||||
OBSERVE_MODE: "${RPKI_BIRD_OBSERVE_MODE:-interval}"
|
||||
OBSERVE_DEBOUNCE_SECS: "${RPKI_BIRD_OBSERVE_DEBOUNCE_SECS:-1}"
|
||||
OBSERVE_INTERVAL: "${RPKI_BIRD_OBSERVE_INTERVAL:-30}"
|
||||
|
||||
OBSERVE_ASPA_TABLE: "${RPKI_BIRD_OBSERVE_ASPA_TABLE:-rtr_aspa}"
|
||||
OBSERVE_ROA4_TABLE: "${RPKI_BIRD_OBSERVE_ROA4_TABLE:-rtr_roa_v4}"
|
||||
OBSERVE_ROA6_TABLE: "${RPKI_BIRD_OBSERVE_ROA6_TABLE:-rtr_roa_v6}"
|
||||
|
||||
OBSERVE_ASPA_COUNT: "${RPKI_BIRD_OBSERVE_ASPA_COUNT:-3}"
|
||||
OBSERVE_ROA4_COUNT: "${RPKI_BIRD_OBSERVE_ROA4_COUNT:-3}"
|
||||
OBSERVE_ROA6_COUNT: "${RPKI_BIRD_OBSERVE_ROA6_COUNT:-3}"
|
||||
|
||||
SHOW_ASPA: "${RPKI_BIRD_SHOW_ASPA:-1}"
|
||||
SHOW_ROA4: "${RPKI_BIRD_SHOW_ROA4:-1}"
|
||||
SHOW_ROA6: "${RPKI_BIRD_SHOW_ROA6:-1}"
|
||||
volumes:
|
||||
- ./bird.conf.template:/config/bird.conf.template:ro
|
||||
- ${RPKI_BIRD_LOG_HOST_DIR:-../../logs/bird}:/app/logs
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
networks:
|
||||
rpki_net:
|
||||
name: rpki_net
|
||||
driver: bridge
|
||||
@@ -1,218 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
mkdir -p /run/bird
|
||||
|
||||
SOCK_PATH="/run/bird/bird.ctl"
|
||||
PROTO="${OBSERVE_PROTO:-rpki_tcp}"
|
||||
INTERVAL="${OBSERVE_INTERVAL:-30}"
|
||||
MODE="${OBSERVE_MODE:-event}"
|
||||
DEBOUNCE_SECS="${OBSERVE_DEBOUNCE_SECS:-1}"
|
||||
RPKI_HOST="${RPKI_HOST:-host.docker.internal}"
|
||||
RPKI_PORT="${RPKI_PORT:-323}"
|
||||
|
||||
BIRD_CONFIG_PATH="${BIRD_CONFIG_PATH:-/config/bird.conf}"
|
||||
BIRD_CONFIG_TEMPLATE_PATH="${BIRD_CONFIG_TEMPLATE_PATH:-/config/bird.conf.template}"
|
||||
|
||||
ASPA_TABLE="${OBSERVE_ASPA_TABLE:-rtr_aspa}"
|
||||
ROA4_TABLE="${OBSERVE_ROA4_TABLE:-rtr_roa_v4}"
|
||||
ROA6_TABLE="${OBSERVE_ROA6_TABLE:-rtr_roa_v6}"
|
||||
|
||||
ASPA_COUNT="${OBSERVE_ASPA_COUNT:-3}"
|
||||
ROA4_COUNT="${OBSERVE_ROA4_COUNT:-3}"
|
||||
ROA6_COUNT="${OBSERVE_ROA6_COUNT:-3}"
|
||||
|
||||
SHOW_ASPA="${SHOW_ASPA:-1}"
|
||||
SHOW_ROA4="${SHOW_ROA4:-1}"
|
||||
SHOW_ROA6="${SHOW_ROA6:-1}"
|
||||
SSH_HOST_PUBKEY_PATH="${SSH_HOST_PUBKEY_PATH:-/config/ssh/ssh_host_rsa_key.pub}"
|
||||
SSH_KNOWN_HOSTS_PATH="${SSH_KNOWN_HOSTS_PATH:-/run/bird/known_hosts}"
|
||||
|
||||
LOG_DIR="${LOG_DIR:-/app/logs}"
|
||||
LOG_NAME="${LOG_NAME:-${HOSTNAME:-bird-rpki-client}}"
|
||||
STDOUT_LOG="${LOG_DIR}/${LOG_NAME}.stdout.log"
|
||||
STDERR_LOG="${LOG_DIR}/${LOG_NAME}.stderr.log"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
exec >>"$STDOUT_LOG" 2>>"$STDERR_LOG"
|
||||
|
||||
if [ -r "$BIRD_CONFIG_TEMPLATE_PATH" ]; then
|
||||
RENDERED_CONFIG_PATH="/run/bird/bird.generated.conf"
|
||||
envsubst '${RPKI_HOST} ${RPKI_PORT}' < "$BIRD_CONFIG_TEMPLATE_PATH" > "$RENDERED_CONFIG_PATH"
|
||||
BIRD_CONFIG_PATH="$RENDERED_CONFIG_PATH"
|
||||
echo "[entrypoint] rendered config : $BIRD_CONFIG_TEMPLATE_PATH -> $BIRD_CONFIG_PATH"
|
||||
fi
|
||||
|
||||
ensure_ssh_known_hosts() {
|
||||
if [ -s "$SSH_KNOWN_HOSTS_PATH" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ! -r "$SSH_HOST_PUBKEY_PATH" ]; then
|
||||
echo "[entrypoint] WARNING: SSH host key file not found: $SSH_HOST_PUBKEY_PATH"
|
||||
return
|
||||
fi
|
||||
|
||||
set -- $(awk 'NF >= 2 { print $1, $2; exit }' "$SSH_HOST_PUBKEY_PATH")
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "[entrypoint] WARNING: invalid SSH host key format in $SSH_HOST_PUBKEY_PATH"
|
||||
return
|
||||
fi
|
||||
|
||||
key_type="$1"
|
||||
key_data="$2"
|
||||
|
||||
if echo "$key_type" | grep -q '^ssh-'; then
|
||||
{
|
||||
echo "$RPKI_HOST $key_type $key_data"
|
||||
echo "[$RPKI_HOST]:$RPKI_PORT $key_type $key_data"
|
||||
} > "$SSH_KNOWN_HOSTS_PATH"
|
||||
else
|
||||
cp "$SSH_HOST_PUBKEY_PATH" "$SSH_KNOWN_HOSTS_PATH"
|
||||
fi
|
||||
|
||||
chmod 600 "$SSH_KNOWN_HOSTS_PATH" || true
|
||||
echo "[entrypoint] generated known_hosts: $SSH_KNOWN_HOSTS_PATH"
|
||||
}
|
||||
|
||||
print_first_n_objects() {
|
||||
table_name="$1"
|
||||
max_objects="$2"
|
||||
|
||||
birdc -s "$SOCK_PATH" show route table "$table_name" all 2>/dev/null | awk -v max="$max_objects" '
|
||||
BEGIN {
|
||||
count = 0
|
||||
}
|
||||
|
||||
# 直接跳过空行
|
||||
/^[[:space:]]*$/ {
|
||||
next
|
||||
}
|
||||
|
||||
# 保留 birdc 的表头
|
||||
/^BIRD / {
|
||||
print
|
||||
next
|
||||
}
|
||||
|
||||
/^Table / {
|
||||
print
|
||||
next
|
||||
}
|
||||
|
||||
# 非缩进且不是表头,视为一个新对象的开始
|
||||
/^[^[:space:]]/ {
|
||||
count++
|
||||
if (count > max) {
|
||||
exit
|
||||
}
|
||||
print
|
||||
next
|
||||
}
|
||||
|
||||
# 缩进内容,只有在已进入前 max 个对象时才打印
|
||||
{
|
||||
if (count > 0 && count <= max) {
|
||||
print
|
||||
}
|
||||
}
|
||||
' || true
|
||||
}
|
||||
|
||||
print_snapshot() {
|
||||
echo "==== $(date +"%Y-%m-%dT%H:%M:%S%:z") RPKI snapshot ($PROTO) ===="
|
||||
birdc -s "$SOCK_PATH" show protocols all "$PROTO" || true
|
||||
|
||||
if [ "$SHOW_ASPA" = "1" ]; then
|
||||
echo "---- ASPA table ($ASPA_TABLE, first ${ASPA_COUNT} objects) ----"
|
||||
print_first_n_objects "$ASPA_TABLE" "$ASPA_COUNT"
|
||||
fi
|
||||
|
||||
if [ "$SHOW_ROA4" = "1" ]; then
|
||||
echo "---- ROA4 table ($ROA4_TABLE, first ${ROA4_COUNT} objects) ----"
|
||||
print_first_n_objects "$ROA4_TABLE" "$ROA4_COUNT"
|
||||
fi
|
||||
|
||||
if [ "$SHOW_ROA6" = "1" ]; then
|
||||
echo "---- ROA6 table ($ROA6_TABLE, first ${ROA6_COUNT} objects) ----"
|
||||
print_first_n_objects "$ROA6_TABLE" "$ROA6_COUNT"
|
||||
fi
|
||||
}
|
||||
|
||||
is_rpki_related_event() {
|
||||
line="$1"
|
||||
echo "$line" | grep -Eiq "$PROTO|$ASPA_TABLE|$ROA4_TABLE|$ROA6_TABLE|rpki|rtr"
|
||||
}
|
||||
|
||||
echo "[entrypoint] starting bird"
|
||||
echo "[entrypoint] config : $BIRD_CONFIG_PATH"
|
||||
echo "[entrypoint] observe proto : $PROTO"
|
||||
echo "[entrypoint] observe mode : $MODE"
|
||||
echo "[entrypoint] observe interval : $INTERVAL (used when mode=interval)"
|
||||
echo "[entrypoint] debounce secs : $DEBOUNCE_SECS (used when mode=event)"
|
||||
echo "[entrypoint] target : $RPKI_HOST:$RPKI_PORT"
|
||||
echo "[entrypoint] show aspa : $SHOW_ASPA ($ASPA_TABLE, first $ASPA_COUNT objects)"
|
||||
echo "[entrypoint] show roa4 : $SHOW_ROA4 ($ROA4_TABLE, first $ROA4_COUNT objects)"
|
||||
echo "[entrypoint] show roa6 : $SHOW_ROA6 ($ROA6_TABLE, first $ROA6_COUNT objects)"
|
||||
|
||||
if [ "$PROTO" = "rpki_ssh" ] || grep -q 'transport[[:space:]]\+ssh' "$BIRD_CONFIG_PATH"; then
|
||||
ensure_ssh_known_hosts
|
||||
fi
|
||||
|
||||
if nc -zvw3 "$RPKI_HOST" "$RPKI_PORT"; then
|
||||
echo "[entrypoint] TCP connectivity to $RPKI_HOST:$RPKI_PORT OK"
|
||||
else
|
||||
echo "[entrypoint] WARNING: cannot connect to $RPKI_HOST:$RPKI_PORT before BIRD starts"
|
||||
fi
|
||||
|
||||
bird -f -c "$BIRD_CONFIG_PATH" -s "$SOCK_PATH" &
|
||||
BIRD_PID="$!"
|
||||
|
||||
sleep 1
|
||||
|
||||
case "$INTERVAL" in
|
||||
''|*[!0-9]*)
|
||||
INTERVAL=0
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$MODE" in
|
||||
event)
|
||||
case "$DEBOUNCE_SECS" in
|
||||
''|*[!0-9]*)
|
||||
DEBOUNCE_SECS=1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "[entrypoint] waiting for BIRD monitor events and only printing on RPKI-related changes"
|
||||
LAST_PRINT_TS=0
|
||||
|
||||
if birdc -s "$SOCK_PATH" monitor all 2>/dev/null | while IFS= read -r line; do
|
||||
[ -n "$line" ] || continue
|
||||
if ! is_rpki_related_event "$line"; then
|
||||
continue
|
||||
fi
|
||||
now_ts="$(date +%s)"
|
||||
if [ $((now_ts - LAST_PRINT_TS)) -lt "$DEBOUNCE_SECS" ]; then
|
||||
continue
|
||||
fi
|
||||
LAST_PRINT_TS="$now_ts"
|
||||
echo "[monitor] $line"
|
||||
print_snapshot
|
||||
done; then
|
||||
:
|
||||
else
|
||||
echo "[entrypoint] WARNING: birdc monitor failed, fallback to interval polling"
|
||||
MODE="interval"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$MODE" = "interval" ] && [ "$INTERVAL" -gt 0 ]; then
|
||||
while kill -0 "$BIRD_PID" 2>/dev/null; do
|
||||
print_snapshot
|
||||
sleep "$INTERVAL"
|
||||
done
|
||||
fi
|
||||
|
||||
wait "$BIRD_PID"
|
||||
@@ -1,37 +0,0 @@
|
||||
# Target RTR server addresses for client compose files
|
||||
# build-run.sh auto-detects amd64/arm64 unless IMAGE_ARCH/RPKI_RTR_IMAGE_ARCH is set before running it.
|
||||
# Direct docker compose uses RPKI_RTR_IMAGE_ARCH from this file; set it to arm64 on ARM hosts.
|
||||
RPKI_RTR_CLIENT_IMAGE_BASE=rpki-rtr-debug-client
|
||||
RPKI_RTR_CLIENT_IMAGE_TAG=v1.3
|
||||
RPKI_RTR_IMAGE_ARCH=arm64
|
||||
# RPKI_RTR_TARGET_PLATFORM=linux/amd64
|
||||
# RPKI_RTR_CLIENT_IMAGE=rpki-rtr-debug-client-amd64:v1.3
|
||||
|
||||
# TCP example: 10.0.0.12:323
|
||||
# TLS example: rpki.example.com:324
|
||||
# SSH example: 10.0.0.12:22
|
||||
RPKI_RTR_TCP_SERVER_ADDR=rpki-rtr:323
|
||||
RPKI_RTR_TLS_SERVER_ADDR=rpki-rtr:324
|
||||
RPKI_RTR_SSH_SERVER_ADDR=rpki-rtr:22
|
||||
|
||||
# RTR protocol version used as client command second argument (supported: 0,1,2)
|
||||
RPKI_RTR_PROTOCOL_VERSION=2
|
||||
|
||||
# TLS server name used by --server-name in TLS mode
|
||||
# Must match server certificate SAN dNSName.
|
||||
RPKI_RTR_TLS_SERVER_NAME=localhost
|
||||
RPKI_RTR_TLS_CA_CERT_PATH=/app/certs/client-ca.crt
|
||||
RPKI_RTR_TLS_CLIENT_CERT_PATH=/app/certs/client-good.crt
|
||||
RPKI_RTR_TLS_CLIENT_KEY_PATH=/app/certs/client-good.key
|
||||
RPKI_RTR_TLS_CERTS_HOST_DIR=../../certs/tls
|
||||
|
||||
# Shared client logs mount on host.
|
||||
RPKI_RTR_CLIENT_LOG_HOST_DIR=../../logs/client
|
||||
|
||||
# SSH mode examples:
|
||||
# RPKI_RTR_SSH_SERVER_ADDR=10.0.0.12:2222
|
||||
RPKI_RTR_CLIENT_KEYS_VOLUME=../../certs/ssh:/app/certs:ro
|
||||
RPKI_RTR_CLIENT_KEY_PATH=/app/certs/rtr-client.key
|
||||
RPKI_RTR_SSH_SERVER_PUBKEY_PATH=/app/certs/ssh_host_rsa_key.pub
|
||||
RPKI_RTR_SSH_USERNAME=rpki-rtr
|
||||
RPKI_RTR_SSH_PASSWORD=
|
||||
@@ -1,26 +0,0 @@
|
||||
FROM rust:1.89-bookworm AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends clang libclang-dev pkg-config \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY src ./src
|
||||
|
||||
RUN cargo build --release --bin rtr_debug_client
|
||||
|
||||
FROM debian:bookworm-slim AS runtime
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates tzdata \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
COPY --from=builder /build/target/release/rtr_debug_client /usr/local/bin/rtr_debug_client
|
||||
COPY --chmod=755 deploy/client/entrypoint.sh /usr/local/bin/rtr-debug-client-entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/rtr-debug-client-entrypoint.sh"]
|
||||
@@ -1,314 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
|
||||
CALLER_TARGET_PLATFORM="${TARGET_PLATFORM:-${RPKI_RTR_TARGET_PLATFORM:-}}"
|
||||
CALLER_IMAGE_ARCH="${IMAGE_ARCH:-${RPKI_RTR_IMAGE_ARCH:-}}"
|
||||
CALLER_CLIENT_IMAGE="${CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE:-}}"
|
||||
|
||||
load_env_defaults() {
|
||||
local line key value
|
||||
[[ -f "${SCRIPT_DIR}/.env" ]] || return
|
||||
|
||||
while IFS= read -r line || [[ -n "${line}" ]]; do
|
||||
line="${line%$'\r'}"
|
||||
[[ -z "${line}" || "${line}" =~ ^[[:space:]]*# ]] && continue
|
||||
[[ "${line}" == *"="* ]] || continue
|
||||
|
||||
key="${line%%=*}"
|
||||
value="${line#*=}"
|
||||
key="${key#"${key%%[![:space:]]*}"}"
|
||||
key="${key%"${key##*[![:space:]]}"}"
|
||||
[[ "${key}" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
|
||||
|
||||
if [[ -z "${!key+x}" ]]; then
|
||||
export "${key}=${value}"
|
||||
fi
|
||||
done < "${SCRIPT_DIR}/.env"
|
||||
}
|
||||
|
||||
detect_image_arch() {
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) printf '%s\n' amd64 ;;
|
||||
aarch64|arm64) printf '%s\n' arm64 ;;
|
||||
*) die "unsupported host architecture '$(uname -m)'; set RPKI_RTR_IMAGE_ARCH=amd64 or arm64" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
platform_for_image_arch() {
|
||||
case "$1" in
|
||||
amd64) printf '%s\n' linux/amd64 ;;
|
||||
arm64) printf '%s\n' linux/arm64 ;;
|
||||
*) die "unsupported image arch '$1', expected amd64 or arm64" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
image_arch_from_platform() {
|
||||
case "$1" in
|
||||
linux/amd64|amd64) printf '%s\n' amd64 ;;
|
||||
linux/arm64|arm64) printf '%s\n' arm64 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
load_env_defaults
|
||||
|
||||
MODE="${MODE:-tcp}"
|
||||
ACTION="up"
|
||||
DETACH="${DETACH:-1}"
|
||||
BUILD="${BUILD:-1}"
|
||||
CLIENTS="${CLIENTS:-single}"
|
||||
TARGET_PLATFORM="${CALLER_TARGET_PLATFORM}"
|
||||
IMAGE_ARCH="${CALLER_IMAGE_ARCH}"
|
||||
CLIENT_IMAGE="${CALLER_CLIENT_IMAGE}"
|
||||
BUILDX_PUSH="${BUILDX_PUSH:-0}"
|
||||
BUILDX_BUILDER="${BUILDX_BUILDER:-}"
|
||||
SKIP_BASE_IMAGE_PULL="${SKIP_BASE_IMAGE_PULL:-0}"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
deploy/client/build-run.sh [action] [options]
|
||||
|
||||
Actions:
|
||||
up Build and run debug client (default)
|
||||
build Build debug client image
|
||||
buildx Build debug client image with docker buildx
|
||||
rebuild Rebuild image without cache, then run client
|
||||
down|stop Stop client compose stack
|
||||
restart Stop, then build and run client
|
||||
logs Follow client logs
|
||||
ps Show client compose status
|
||||
|
||||
Options:
|
||||
--mode MODE base|tcp|tls|ssh (default: tcp)
|
||||
--clients MODE single|multi (default: single)
|
||||
--platform VALUE Target platform for buildx (default: host architecture)
|
||||
--image-arch VALUE
|
||||
Image architecture suffix for default tag: amd64|arm64
|
||||
--client-image TAG
|
||||
Client image tag for buildx and compose
|
||||
--push Push buildx image instead of loading it locally
|
||||
--builder NAME buildx builder name
|
||||
--skip-base-pull Do not pull Dockerfile base images before buildx
|
||||
--foreground Run single client in foreground
|
||||
--no-build Start without --build
|
||||
-h|--help Show this help
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "error: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
has_compose() {
|
||||
docker compose version >/dev/null 2>&1
|
||||
}
|
||||
|
||||
has_buildx() {
|
||||
docker buildx version >/dev/null 2>&1
|
||||
}
|
||||
|
||||
pull_base_images() {
|
||||
[[ "${SKIP_BASE_IMAGE_PULL}" == "1" ]] && return
|
||||
|
||||
local platform image
|
||||
local -a platforms images
|
||||
IFS=',' read -r -a platforms <<< "${TARGET_PLATFORM}"
|
||||
images=(rust:1.89-bookworm debian:bookworm-slim)
|
||||
|
||||
for platform in "${platforms[@]}"; do
|
||||
for image in "${images[@]}"; do
|
||||
docker pull --platform "${platform}" "${image}"
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
compose_files() {
|
||||
if [[ "${CLIENTS}" == "multi" ]]; then
|
||||
[[ "${MODE}" == "tcp" || "${MODE}" == "base" ]] || die "--clients multi is only defined for tcp/base mode"
|
||||
printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.clients.yml"
|
||||
return
|
||||
fi
|
||||
|
||||
case "${MODE}" in
|
||||
base) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.yml" ;;
|
||||
tcp) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tcp.yml" ;;
|
||||
tls) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tls.yml" ;;
|
||||
ssh) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.ssh.yml" ;;
|
||||
*) die "unsupported mode '${MODE}', expected base|tcp|tls|ssh" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
compose_args() {
|
||||
local -a args files
|
||||
args=()
|
||||
[[ -f "${SCRIPT_DIR}/.env" ]] && args+=(--env-file "${SCRIPT_DIR}/.env")
|
||||
mapfile -t files < <(compose_files)
|
||||
args+=("${files[@]}")
|
||||
printf '%s\n' "${args[@]}"
|
||||
}
|
||||
|
||||
compose_run() {
|
||||
local -a args
|
||||
mapfile -t args < <(compose_args)
|
||||
docker compose "${args[@]}" "$@"
|
||||
}
|
||||
|
||||
up_client() {
|
||||
local -a up_args
|
||||
up_args=(up)
|
||||
[[ "${CLIENTS}" == "multi" || "${DETACH}" == "1" ]] && up_args+=(-d)
|
||||
[[ "${BUILD}" == "1" && "${CLIENTS}" != "multi" ]] && up_args+=(--build)
|
||||
compose_run "${up_args[@]}"
|
||||
}
|
||||
|
||||
build_client() {
|
||||
if [[ "${CLIENTS}" == "multi" ]]; then
|
||||
docker build "$@" -f "${SCRIPT_DIR}/Dockerfile" -t "${CLIENT_IMAGE}" "${REPO_ROOT}"
|
||||
else
|
||||
compose_run build "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
buildx_client() {
|
||||
has_buildx || die "docker buildx is not available"
|
||||
if [[ "${TARGET_PLATFORM}" == *,* && "${BUILDX_PUSH}" != "1" ]]; then
|
||||
die "multi-platform buildx output requires --push"
|
||||
fi
|
||||
|
||||
local -a output_args builder_args
|
||||
[[ "${BUILDX_PUSH}" == "1" ]] && output_args=(--push) || output_args=(--load)
|
||||
[[ -n "${BUILDX_BUILDER}" ]] && builder_args=(--builder "${BUILDX_BUILDER}") || builder_args=()
|
||||
|
||||
pull_base_images
|
||||
|
||||
docker buildx build "${builder_args[@]}" \
|
||||
--platform "${TARGET_PLATFORM}" \
|
||||
-f "${SCRIPT_DIR}/Dockerfile" \
|
||||
-t "${CLIENT_IMAGE}" \
|
||||
"${output_args[@]}" \
|
||||
"${REPO_ROOT}"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
up|build|buildx|rebuild|down|stop|restart|logs|ps)
|
||||
ACTION="$1"
|
||||
shift
|
||||
;;
|
||||
--mode)
|
||||
[[ $# -ge 2 ]] || die "--mode requires a value"
|
||||
MODE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--clients)
|
||||
[[ $# -ge 2 ]] || die "--clients requires a value"
|
||||
CLIENTS="$2"
|
||||
shift 2
|
||||
;;
|
||||
--platform)
|
||||
[[ $# -ge 2 ]] || die "--platform requires a value"
|
||||
TARGET_PLATFORM="$2"
|
||||
shift 2
|
||||
;;
|
||||
--image-arch)
|
||||
[[ $# -ge 2 ]] || die "--image-arch requires a value"
|
||||
IMAGE_ARCH="$2"
|
||||
shift 2
|
||||
;;
|
||||
--client-image)
|
||||
[[ $# -ge 2 ]] || die "--client-image requires a value"
|
||||
CLIENT_IMAGE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--push)
|
||||
BUILDX_PUSH=1
|
||||
shift
|
||||
;;
|
||||
--builder)
|
||||
[[ $# -ge 2 ]] || die "--builder requires a value"
|
||||
BUILDX_BUILDER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--skip-base-pull)
|
||||
SKIP_BASE_IMAGE_PULL=1
|
||||
shift
|
||||
;;
|
||||
--foreground)
|
||||
DETACH=0
|
||||
shift
|
||||
;;
|
||||
--no-build)
|
||||
BUILD=0
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown argument '$1'"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "${TARGET_PLATFORM}" ]]; then
|
||||
[[ -n "${IMAGE_ARCH}" ]] || IMAGE_ARCH="$(detect_image_arch)"
|
||||
TARGET_PLATFORM="$(platform_for_image_arch "${IMAGE_ARCH}")"
|
||||
elif [[ -z "${IMAGE_ARCH}" ]]; then
|
||||
if [[ "${TARGET_PLATFORM}" != *,* ]] && IMAGE_ARCH="$(image_arch_from_platform "${TARGET_PLATFORM}")"; then
|
||||
:
|
||||
else
|
||||
IMAGE_ARCH="$(detect_image_arch)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "${CLIENT_IMAGE}" ]]; then
|
||||
if [[ "${TARGET_PLATFORM}" == *,* ]]; then
|
||||
CLIENT_IMAGE="${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}"
|
||||
else
|
||||
CLIENT_IMAGE="${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${IMAGE_ARCH}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}"
|
||||
fi
|
||||
fi
|
||||
|
||||
[[ "${CLIENTS}" == "single" || "${CLIENTS}" == "multi" ]] || die "unsupported clients '${CLIENTS}', expected single|multi"
|
||||
export RPKI_RTR_CLIENT_IMAGE="${CLIENT_IMAGE}"
|
||||
if [[ "${ACTION}" == "buildx" ]]; then
|
||||
has_buildx || die "docker buildx is not available"
|
||||
else
|
||||
has_compose || die "docker compose is not available"
|
||||
fi
|
||||
|
||||
cd "${REPO_ROOT}"
|
||||
|
||||
case "${ACTION}" in
|
||||
up)
|
||||
up_client
|
||||
;;
|
||||
build)
|
||||
build_client
|
||||
;;
|
||||
buildx)
|
||||
buildx_client
|
||||
;;
|
||||
rebuild)
|
||||
build_client --no-cache
|
||||
up_client
|
||||
;;
|
||||
down|stop)
|
||||
compose_run down
|
||||
;;
|
||||
restart)
|
||||
compose_run down
|
||||
up_client
|
||||
;;
|
||||
logs)
|
||||
compose_run logs -f
|
||||
;;
|
||||
ps)
|
||||
compose_run ps
|
||||
;;
|
||||
esac
|
||||
@@ -1,52 +0,0 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
rtr-client-1:
|
||||
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
|
||||
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
|
||||
volumes:
|
||||
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
|
||||
restart: no
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
rtr-client-2:
|
||||
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
|
||||
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
|
||||
volumes:
|
||||
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
|
||||
restart: no
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
rtr-client-3:
|
||||
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
|
||||
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
|
||||
volumes:
|
||||
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
|
||||
restart: no
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
rtr-client-4:
|
||||
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
|
||||
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
|
||||
volumes:
|
||||
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
|
||||
restart: no
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
rtr-client-5:
|
||||
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
|
||||
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
|
||||
volumes:
|
||||
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
|
||||
restart: no
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
networks:
|
||||
rpki_net:
|
||||
name: rpki_net
|
||||
driver: bridge
|
||||
@@ -1,36 +0,0 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
rtr-debug-client:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: deploy/client/Dockerfile
|
||||
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
|
||||
command:
|
||||
[
|
||||
"${RPKI_RTR_SSH_SERVER_ADDR:-rpki-rtr:22}",
|
||||
"${RPKI_RTR_PROTOCOL_VERSION:-2}",
|
||||
"reset",
|
||||
"--ssh",
|
||||
"--ssh-user",
|
||||
"${RPKI_RTR_SSH_USERNAME:-rpki-rtr}",
|
||||
"--ssh-password",
|
||||
"${RPKI_RTR_SSH_PASSWORD}",
|
||||
"--ssh-server-key",
|
||||
"${RPKI_RTR_SSH_SERVER_PUBKEY_PATH:-/app/certs/ssh_host_rsa_key.pub}",
|
||||
"--keep-after-error",
|
||||
"--summary-only"
|
||||
]
|
||||
volumes:
|
||||
- ${RPKI_RTR_CLIENT_KEYS_VOLUME:-../../certs/ssh:/app/certs:ro}
|
||||
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
|
||||
restart: no
|
||||
stdin_open: true
|
||||
tty: true
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
networks:
|
||||
rpki_net:
|
||||
name: rpki_net
|
||||
driver: bridge
|
||||
@@ -1,36 +0,0 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
rtr-debug-client:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: deploy/client/Dockerfile
|
||||
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
|
||||
command:
|
||||
[
|
||||
"${RPKI_RTR_SSH_SERVER_ADDR:-rpki-rtr:22}",
|
||||
"${RPKI_RTR_PROTOCOL_VERSION:-2}",
|
||||
"reset",
|
||||
"--ssh",
|
||||
"--ssh-user",
|
||||
"${RPKI_RTR_SSH_USERNAME:-rpki-rtr}",
|
||||
"--ssh-key",
|
||||
"${RPKI_RTR_CLIENT_KEY_PATH:-/app/certs/rtr-client.key}",
|
||||
"--ssh-server-key",
|
||||
"${RPKI_RTR_SSH_SERVER_PUBKEY_PATH:-/app/certs/ssh_host_rsa_key.pub}",
|
||||
"--keep-after-error",
|
||||
"--summary-only"
|
||||
]
|
||||
volumes:
|
||||
- ${RPKI_RTR_CLIENT_KEYS_VOLUME:-../../certs/ssh:/app/certs:ro}
|
||||
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
|
||||
restart: no
|
||||
stdin_open: true
|
||||
tty: true
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
networks:
|
||||
rpki_net:
|
||||
name: rpki_net
|
||||
driver: bridge
|
||||
@@ -1,19 +0,0 @@
|
||||
services:
|
||||
rtr-debug-client:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: deploy/client/Dockerfile
|
||||
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
|
||||
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
|
||||
volumes:
|
||||
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
|
||||
restart: no
|
||||
stdin_open: true
|
||||
tty: true
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
networks:
|
||||
rpki_net:
|
||||
name: rpki_net
|
||||
driver: bridge
|
||||
@@ -1,38 +0,0 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
rtr-debug-client:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: deploy/client/Dockerfile
|
||||
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
|
||||
command:
|
||||
[
|
||||
"${RPKI_RTR_TLS_SERVER_ADDR:-rpki-rtr:324}",
|
||||
"${RPKI_RTR_PROTOCOL_VERSION:-2}",
|
||||
"reset",
|
||||
"--tls",
|
||||
"--ca-cert",
|
||||
"${RPKI_RTR_TLS_CA_CERT_PATH:-/app/certs/client-ca.crt}",
|
||||
"--server-name",
|
||||
"${RPKI_RTR_TLS_SERVER_NAME:-localhost}",
|
||||
"--client-cert",
|
||||
"${RPKI_RTR_TLS_CLIENT_CERT_PATH:-/app/certs/client-good.crt}",
|
||||
"--client-key",
|
||||
"${RPKI_RTR_TLS_CLIENT_KEY_PATH:-/app/certs/client-good.key}",
|
||||
"--keep-after-error",
|
||||
"--summary-only"
|
||||
]
|
||||
volumes:
|
||||
- ${RPKI_RTR_TLS_CERTS_HOST_DIR:-../../certs/tls}:/app/certs:ro
|
||||
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
|
||||
restart: no
|
||||
stdin_open: true
|
||||
tty: true
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
networks:
|
||||
rpki_net:
|
||||
name: rpki_net
|
||||
driver: bridge
|
||||
@@ -1,19 +0,0 @@
|
||||
services:
|
||||
rtr-debug-client:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: deploy/client/Dockerfile
|
||||
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
|
||||
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
|
||||
volumes:
|
||||
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
|
||||
restart: no
|
||||
stdin_open: true
|
||||
tty: true
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
networks:
|
||||
rpki_net:
|
||||
name: rpki_net
|
||||
driver: bridge
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
mkdir -p /app/logs
|
||||
|
||||
log_name="${HOSTNAME:-rtr-debug-client}"
|
||||
stdout_log="/app/logs/${log_name}.stdout.log"
|
||||
stderr_log="/app/logs/${log_name}.stderr.log"
|
||||
|
||||
exec /usr/local/bin/rtr_debug_client "$@" >>"$stdout_log" 2>>"$stderr_log"
|
||||
123
deploy/docker-installer/.env.example
Normal file
123
deploy/docker-installer/.env.example
Normal file
@@ -0,0 +1,123 @@
|
||||
# ours RP Docker installer configuration
|
||||
# `build_docker_installer_package.sh` rewrites package-specific placeholders
|
||||
# when producing amd64/arm64 release packages.
|
||||
# 中文说明见 docs/README.zh-CN.md。English guide: docs/README.en.md
|
||||
|
||||
# Package metadata and architecture guardrails.
|
||||
PACKAGE_ARCH=__PACKAGE_ARCH__
|
||||
PACKAGE_PLATFORM=__PACKAGE_PLATFORM__
|
||||
ALLOW_CROSS_ARCH=0
|
||||
|
||||
# Compose project name.
|
||||
COMPOSE_PROJECT_NAME=ours-rp-__PACKAGE_ARCH__-installer
|
||||
|
||||
# Runtime image rebuilt from the current source commit while packaging.
|
||||
RPKI_IMAGE=__RUNTIME_IMAGE__
|
||||
RPKI_PLATFORM=__PACKAGE_PLATFORM__
|
||||
|
||||
# Metrics image rebuilt from the current source commit while packaging.
|
||||
METRICS_IMAGE=__METRICS_IMAGE__
|
||||
METRICS_PLATFORM=__PACKAGE_PLATFORM__
|
||||
|
||||
# Restart policy for the soak container. Production default keeps the daemon alive.
|
||||
# For finite acceptance tests such as MAX_RUNS=3, set SOAK_RESTART_POLICY=no to avoid an extra restarted run.
|
||||
SOAK_RESTART_POLICY=unless-stopped
|
||||
|
||||
# Host-side persistent data directory. All state/runs/logs/monitoring data are bind-mounted here.
|
||||
HOST_DATA_DIR=__HOST_DATA_DIR__
|
||||
|
||||
# RIR list. Options: afrinic,apnic,arin,lacnic,ripe
|
||||
RIRS=afrinic,apnic,arin,lacnic,ripe
|
||||
|
||||
# Negative MAX_RUNS means keep running forever. Default production interval is 10 minutes.
|
||||
MAX_RUNS=-1
|
||||
INTERVAL_SECS=600
|
||||
RETAIN_RUNS=100
|
||||
|
||||
# TAL/TA input mode:
|
||||
# file-with-ta: use packaged fixture TAL + TA only.
|
||||
# file-live-ta: use packaged fixture TAL; snapshot waits for live TA refresh, delta refreshes TA in background.
|
||||
# custom-file-with-ta: use one custom TAL + TA from the read-only custom fixture mount.
|
||||
# url: pass TAL URL to child process.
|
||||
TAL_INPUT_MODE=file-live-ta
|
||||
LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1
|
||||
LIVE_TA_REFRESH_CONNECT_TIMEOUT_SECS=15
|
||||
LIVE_TA_REFRESH_MAX_TIME_SECS=120
|
||||
|
||||
# Custom TAL patch mode. Keep TAL_INPUT_MODE=file-live-ta and the five-RIR RIRS
|
||||
# list above for the original package behavior. For a private fixture, set:
|
||||
# RIRS=custom
|
||||
# TAL_INPUT_MODE=custom-file-with-ta
|
||||
# The paths below are container paths; CUSTOM_FIXTURE_HOST_DIR is a host path
|
||||
# resolved relative to the compose directory when left at its default.
|
||||
CUSTOM_FIXTURE_HOST_DIR=../custom-fixtures
|
||||
CUSTOM_TAL_PATH=/opt/ours-rp/custom-fixtures/tal/custom.tal
|
||||
CUSTOM_TA_PATH=/opt/ours-rp/custom-fixtures/ta/custom-ta.cer
|
||||
CUSTOM_TAL_URI=https://host.docker.internal:18443/tal/custom.tal
|
||||
# Space-separated container paths; paths must not contain spaces. Set this in
|
||||
# custom mode when the RRDP server uses a private/self-signed CA.
|
||||
HTTP_ROOT_CERT_PATHS=
|
||||
|
||||
# Sync and runtime behavior.
|
||||
RSYNC_SCOPE=module-root
|
||||
DISABLE_COMPETING_RPS=0
|
||||
RUN_ROOT=/var/lib/ours-rp
|
||||
DB_DIR=/var/lib/ours-rp/state/db
|
||||
RSYNC_MIRROR_ROOT=/var/lib/ours-rp/state/rsync-mirror
|
||||
# 每轮完成并复制出正式 run 产物后清理 tmp/daemon-run_*,避免长跑占满磁盘。
|
||||
CLEAN_TMP_AFTER_RUN=1
|
||||
OUTPUT_COMPACT_REPORT=1
|
||||
ALLOW_RSYNC_MIRROR_REUSE=1
|
||||
FAILURE_SNAPSHOT_RESET=1
|
||||
# Periodic snapshot reset of active state DB.
|
||||
# 0: keep existing behavior.
|
||||
# 1: after one successful snapshot, allow at most N successful delta runs;
|
||||
# the next run is forced to snapshot and active state/db is rebuilt from empty.
|
||||
# Lifecycle run state is persisted independently at:
|
||||
# ${HOST_DATA_DIR}/state/run-lifecycle-state.json
|
||||
# It is not affected by run retention or state/db reset.
|
||||
PERIODIC_SNAPSHOT_RESET=0
|
||||
PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
||||
DB_STATS_EXACT_EVERY=0
|
||||
|
||||
# Resource certificate validation mode passed to the rpki child process.
|
||||
# Options: validation-update-03 | rfc6487
|
||||
RESOURCE_VALIDATION_MODE=validation-update-03
|
||||
|
||||
# Validation and performance options aligned with current optimized soak defaults.
|
||||
ENABLE_CHILD_CERTIFICATE_VALIDATION_CACHE=1
|
||||
RPKI_ANALYZE=1
|
||||
RPKI_EXTRA_ARGS="--enable-transport-request-prefetch --enable-publication-point-validation-cache --enable-roa-validation-cache --parallel-max-repo-sync-workers-global 4 --parallel-phase2-object-workers 4 --memory-trim-after-validation"
|
||||
|
||||
# Progress logs.
|
||||
RPKI_PROGRESS_LOG=1
|
||||
RPKI_PROGRESS_SLOW_SECS=20
|
||||
RPKI_PROGRESS_STAGE_FRESH_SLOW_MS=2000
|
||||
RPKI_PROGRESS_PP_CONTROL_SLOW_MS=200
|
||||
RPKI_PROGRESS_PP_CACHE_SLOW_MS=100
|
||||
RPKI_PROGRESS_CONTROL_LOOP_SLOW_MS=2000
|
||||
|
||||
# Metrics sidecar.
|
||||
METRICS_INSTANCE=__PACKAGE_ARCH__-installer
|
||||
METRICS_PORT=9556
|
||||
METRICS_POLL_SECS=10
|
||||
# Optional external RTR report directory produced by a separately deployed RTR service.
|
||||
# Default points to an installer-managed empty fallback directory. To enable real RTR
|
||||
# metrics, set this to the host-side report directory, for example:
|
||||
# RTR_REPORT_DIR=/root/rpki/report
|
||||
RTR_REPORT_DIR=__HOST_DATA_DIR__/empty-rtr-report
|
||||
RTR_REPORT_CONTAINER_DIR=/var/lib/ours-rp/rtr-report
|
||||
|
||||
# Prometheus / Grafana.
|
||||
# Monitor images are packaged as docker-save archives and loaded by install.sh.
|
||||
MONITOR_PLATFORM=__PACKAGE_PLATFORM__
|
||||
PROMETHEUS_IMAGE=prom/prometheus:v2.55.1
|
||||
GRAFANA_IMAGE=grafana/grafana:11.3.1
|
||||
PROMETHEUS_PORT=9090
|
||||
PROMETHEUS_RETENTION=7d
|
||||
GRAFANA_PORT=3000
|
||||
GRAFANA_ADMIN_USER=admin
|
||||
GRAFANA_ADMIN_PASSWORD=admin
|
||||
|
||||
# First snapshot waiting timeout used by start.sh.
|
||||
FIRST_RUN_WAIT_TIMEOUT_SECS=7200
|
||||
100
deploy/docker-installer/compose/docker-compose.yml
Normal file
100
deploy/docker-installer/compose/docker-compose.yml
Normal file
@@ -0,0 +1,100 @@
|
||||
services:
|
||||
ours-rp-soak:
|
||||
image: ${RPKI_IMAGE:?RPKI_IMAGE is required}
|
||||
platform: ${RPKI_PLATFORM:?RPKI_PLATFORM is required}
|
||||
container_name: ${COMPOSE_PROJECT_NAME:-ours-rp-package-installer}-soak
|
||||
env_file:
|
||||
- ../.env
|
||||
environment:
|
||||
PACKAGE_ROOT: /opt/ours-rp
|
||||
ENV_FILE: /opt/ours-rp/.env
|
||||
RUN_ROOT: /var/lib/ours-rp
|
||||
BIN_DIR: /opt/ours-rp/bin
|
||||
FIXTURE_DIR: /opt/ours-rp/fixtures
|
||||
CUSTOM_FIXTURE_DIR: /opt/ours-rp/custom-fixtures
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- ../.env:/opt/ours-rp/.env:ro
|
||||
# Overlay the patched runner while keeping the original runtime image
|
||||
# and its exact rpki binaries unchanged.
|
||||
- ../scripts/soak/run_soak.sh:/opt/ours-rp/run_soak.sh:ro
|
||||
- ${CUSTOM_FIXTURE_HOST_DIR:-../custom-fixtures}:/opt/ours-rp/custom-fixtures:ro
|
||||
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/state:/var/lib/ours-rp/state
|
||||
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/runs:/var/lib/ours-rp/runs
|
||||
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/logs:/var/lib/ours-rp/logs
|
||||
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/tmp:/var/lib/ours-rp/tmp
|
||||
restart: ${SOAK_RESTART_POLICY:-unless-stopped}
|
||||
profiles:
|
||||
- core
|
||||
|
||||
artifact-metrics:
|
||||
image: ${METRICS_IMAGE:?METRICS_IMAGE is required}
|
||||
platform: ${METRICS_PLATFORM:?METRICS_PLATFORM is required}
|
||||
container_name: ${COMPOSE_PROJECT_NAME:-ours-rp-package-installer}-artifact-metrics
|
||||
env_file:
|
||||
- ../.env
|
||||
environment:
|
||||
RPKI_METRICS_RTR_REPORT_DIR: ${RTR_REPORT_CONTAINER_DIR:-/var/lib/ours-rp/rtr-report}
|
||||
command:
|
||||
- /opt/ours-rp/bin/rpki_artifact_metrics
|
||||
- --run-root
|
||||
- /var/lib/ours-rp
|
||||
- --listen
|
||||
- 0.0.0.0:9556
|
||||
- --poll-secs
|
||||
- ${METRICS_POLL_SECS:-10}
|
||||
- --instance
|
||||
- ${METRICS_INSTANCE:-package-installer}
|
||||
ports:
|
||||
- "${METRICS_PORT:-9556}:9556"
|
||||
volumes:
|
||||
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/state:/var/lib/ours-rp/state:ro
|
||||
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/runs:/var/lib/ours-rp/runs:ro
|
||||
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/logs:/var/lib/ours-rp/logs:ro
|
||||
- ${RTR_REPORT_DIR:-/var/lib/ours-rp-package-installer/empty-rtr-report}:${RTR_REPORT_CONTAINER_DIR:-/var/lib/ours-rp/rtr-report}:ro
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- sidecar
|
||||
|
||||
prometheus:
|
||||
image: ${PROMETHEUS_IMAGE:-prom/prometheus:v2.55.1}
|
||||
platform: ${MONITOR_PLATFORM:?MONITOR_PLATFORM is required}
|
||||
container_name: ${COMPOSE_PROJECT_NAME:-ours-rp-package-installer}-prometheus
|
||||
command:
|
||||
- --config.file=/etc/prometheus/prometheus.yml
|
||||
- --storage.tsdb.path=/prometheus
|
||||
- --storage.tsdb.retention.time=${PROMETHEUS_RETENTION:-7d}
|
||||
- --web.enable-lifecycle
|
||||
depends_on:
|
||||
- artifact-metrics
|
||||
user: "0:0"
|
||||
ports:
|
||||
- "${PROMETHEUS_PORT:-9090}:9090"
|
||||
volumes:
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/prometheus:/prometheus
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- monitor
|
||||
|
||||
grafana:
|
||||
image: ${GRAFANA_IMAGE:-grafana/grafana:11.3.1}
|
||||
platform: ${MONITOR_PLATFORM:?MONITOR_PLATFORM is required}
|
||||
container_name: ${COMPOSE_PROJECT_NAME:-ours-rp-package-installer}-grafana
|
||||
depends_on:
|
||||
- prometheus
|
||||
user: "0:0"
|
||||
ports:
|
||||
- "${GRAFANA_PORT:-3000}:3000"
|
||||
environment:
|
||||
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
|
||||
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin}
|
||||
GF_USERS_ALLOW_SIGN_UP: "false"
|
||||
volumes:
|
||||
- ${HOST_DATA_DIR:-/var/lib/ours-rp-package-installer}/grafana:/var/lib/grafana
|
||||
- ./grafana/provisioning:/etc/grafana/provisioning:ro
|
||||
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- monitor
|
||||
@@ -0,0 +1,761 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 1,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_publication_points",
|
||||
"legendFormat": "publication points",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Publication Points",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_ok\"}",
|
||||
"legendFormat": "rrdp ok",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "RRDP OK Points",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 6,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 3,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_ok\"}) or vector(0)",
|
||||
"legendFormat": "fallback",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Rsync Fallback Points",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 6,
|
||||
"x": 18,
|
||||
"y": 0
|
||||
},
|
||||
"id": 4,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_repo_terminal_state_count{terminal_state=\"failed_no_cache\"}",
|
||||
"legendFormat": "failed no cache",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Failed No Cache Points",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 4
|
||||
},
|
||||
"id": 5,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_run_stage_duration_seconds{stage=\"repo_sync_total\"}",
|
||||
"legendFormat": "repo sync total",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_run_stage_duration_seconds{stage=\"rrdp_download_total\"}",
|
||||
"legendFormat": "rrdp download",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_run_stage_duration_seconds{stage=\"rsync_download_total\"}",
|
||||
"legendFormat": "rsync download",
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
"title": "Repo Sync Download Durations",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 12,
|
||||
"w": 12,
|
||||
"h": 8
|
||||
},
|
||||
"id": 6,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_repo_sync_phase_count",
|
||||
"legendFormat": "{{phase}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Repo Sync Phase Counts",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 12,
|
||||
"y": 12,
|
||||
"w": 12,
|
||||
"h": 8
|
||||
},
|
||||
"id": 7,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_ok\"}",
|
||||
"legendFormat": "rrdp failed, rsync ok",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_failed\"}",
|
||||
"legendFormat": "rrdp failed, rsync failed",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_repo_terminal_state_count{terminal_state=\"failed_no_cache\"}",
|
||||
"legendFormat": "failed no cache",
|
||||
"refId": "C"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_tree_instances{state=\"failed\"}",
|
||||
"legendFormat": "tree failed",
|
||||
"refId": "D"
|
||||
}
|
||||
],
|
||||
"title": "Repo Failure / Fallback Counts",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 20,
|
||||
"w": 12,
|
||||
"h": 8
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_repo_sync_phase_duration_seconds_total{phase=\"rrdp_failed_rsync_ok\"}",
|
||||
"legendFormat": "rsync fallback duration",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_repo_sync_phase_duration_seconds_total{phase=\"rrdp_failed_rsync_failed\"}",
|
||||
"legendFormat": "failed duration",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_repo_terminal_state_duration_seconds_total{terminal_state=\"failed_no_cache\"}",
|
||||
"legendFormat": "failed no cache duration",
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
"title": "Repo Failure / Fallback Durations",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 29,
|
||||
"w": 12,
|
||||
"h": 9
|
||||
},
|
||||
"id": 9,
|
||||
"options": {
|
||||
"showHeader": true,
|
||||
"cellHeight": "sm",
|
||||
"footer": {
|
||||
"show": false,
|
||||
"reducer": [
|
||||
"sum"
|
||||
],
|
||||
"countRows": false,
|
||||
"fields": ""
|
||||
}
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_rrdp_rsync_failed_repository_duration_seconds",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "RRDP + Rsync Failed Repositories",
|
||||
"type": "table",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {
|
||||
"job": true,
|
||||
"terminal_state": true,
|
||||
"rank": true,
|
||||
"transport": true,
|
||||
"__name__": true,
|
||||
"publication_points": true,
|
||||
"instance": true,
|
||||
"repo_id": true,
|
||||
"pp_id": true,
|
||||
"exported_instance": true,
|
||||
"rp": true,
|
||||
"source": true
|
||||
},
|
||||
"indexByName": {
|
||||
"Time": 0,
|
||||
"host": 1,
|
||||
"phase": 2,
|
||||
"uri": 3,
|
||||
"Value": 4
|
||||
},
|
||||
"renameByName": {
|
||||
"Value": "duration"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 12,
|
||||
"y": 29,
|
||||
"w": 12,
|
||||
"h": 9
|
||||
},
|
||||
"id": 11,
|
||||
"options": {
|
||||
"showHeader": true,
|
||||
"cellHeight": "sm",
|
||||
"footer": {
|
||||
"show": false,
|
||||
"reducer": [
|
||||
"sum"
|
||||
],
|
||||
"countRows": false,
|
||||
"fields": ""
|
||||
}
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "topk(20, ours_rp_top_repository_sync_duration_seconds)",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Top 20 Repositories by Sync Duration",
|
||||
"type": "table",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {
|
||||
"job": true,
|
||||
"terminal_state": true,
|
||||
"__name__": true,
|
||||
"publication_points": true,
|
||||
"instance": true,
|
||||
"repo_id": true,
|
||||
"phase": true,
|
||||
"pp_id": true,
|
||||
"exported_instance": true,
|
||||
"rp": true,
|
||||
"source": true
|
||||
},
|
||||
"indexByName": {
|
||||
"Time": 0,
|
||||
"host": 1,
|
||||
"rank": 2,
|
||||
"transport": 3,
|
||||
"uri": 4,
|
||||
"Value": 5
|
||||
},
|
||||
"renameByName": {
|
||||
"Value": "value"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 38,
|
||||
"w": 24,
|
||||
"h": 9
|
||||
},
|
||||
"id": 10,
|
||||
"options": {
|
||||
"showHeader": true,
|
||||
"cellHeight": "sm",
|
||||
"footer": {
|
||||
"show": false,
|
||||
"reducer": [
|
||||
"sum"
|
||||
],
|
||||
"countRows": false,
|
||||
"fields": ""
|
||||
}
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "topk(20, ours_rp_top_publication_point_object_count)",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Top Publication Points by Objects",
|
||||
"type": "table",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {
|
||||
"job": true,
|
||||
"__name__": true,
|
||||
"publication_points": true,
|
||||
"instance": true,
|
||||
"repo_id": true,
|
||||
"phase": true,
|
||||
"pp_id": true,
|
||||
"exported_instance": true,
|
||||
"rp": true,
|
||||
"source": true
|
||||
},
|
||||
"indexByName": {
|
||||
"Time": 0,
|
||||
"host": 1,
|
||||
"rank": 2,
|
||||
"terminal_state": 3,
|
||||
"transport": 4,
|
||||
"uri": 5,
|
||||
"Value": 6
|
||||
},
|
||||
"renameByName": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"description": "Per-repository sync success in the latest successful run; 1 means successful, 0 means failed or failed_no_cache.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "bool"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 47
|
||||
},
|
||||
"id": 12,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_repository_sync_success",
|
||||
"legendFormat": "{{host}} {{repo_id}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Repository Sync Success by Repo",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"description": "Per-repository total sync duration aggregated from publication point repo_sync_duration_ms.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 55
|
||||
},
|
||||
"id": 13,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_repository_sync_duration_seconds{stat=\"sum\"}",
|
||||
"legendFormat": "{{host}} {{repo_id}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Repository Sync Duration by Repo",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"description": "Per-repository downloaded bytes attributed from report.json downloads events.",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "bytes"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 63
|
||||
},
|
||||
"id": 14,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_repository_download_bytes",
|
||||
"legendFormat": "{{host}} {{repo_id}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Repository Download Bytes by Repo",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"refresh": "5s",
|
||||
"schemaVersion": 40,
|
||||
"tags": [
|
||||
"ours-rp",
|
||||
"rpki",
|
||||
"soak",
|
||||
"repo-sync"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-30m",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "browser",
|
||||
"title": "Ours RP Repo Sync",
|
||||
"uid": "ours-rp-repo-sync",
|
||||
"version": 3,
|
||||
"weekStart": ""
|
||||
}
|
||||
@@ -0,0 +1,666 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "RTR Metrics Enabled",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 4,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short",
|
||||
"decimals": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(ours_rp_rtr_metrics_enabled)",
|
||||
"legendFormat": "enabled",
|
||||
"refId": "A",
|
||||
"instant": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Refresh Success",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 5,
|
||||
"x": 4,
|
||||
"y": 0
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short",
|
||||
"decimals": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(ours_rp_rtr_source_refresh_status{status=\"success\"})",
|
||||
"legendFormat": "success",
|
||||
"refId": "A",
|
||||
"instant": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Consecutive Failures",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 5,
|
||||
"x": 9,
|
||||
"y": 0
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short",
|
||||
"decimals": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(ours_rp_rtr_source_refresh_consecutive_failures)",
|
||||
"legendFormat": "failures",
|
||||
"refId": "A",
|
||||
"instant": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Last Success Age",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 5,
|
||||
"x": 14,
|
||||
"y": 0
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "s",
|
||||
"decimals": 0,
|
||||
"min": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(ours_rp_rtr_source_last_success_age_seconds)",
|
||||
"legendFormat": "age",
|
||||
"refId": "A",
|
||||
"instant": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "RTR RSS",
|
||||
"type": "stat",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 5,
|
||||
"x": 19,
|
||||
"y": 0
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "bytes",
|
||||
"decimals": 0,
|
||||
"min": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(ours_rp_rtr_process_rss_bytes)",
|
||||
"legendFormat": "rss",
|
||||
"refId": "A",
|
||||
"instant": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"title": "Data Quality Totals",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 4
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short",
|
||||
"min": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_rtr_data_quality_items{stage=~\"ccr_input|before_slurm|after_slurm\",type=\"total\"}",
|
||||
"legendFormat": "{{stage}} total",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_rtr_data_quality_items{stage=\"after_slurm\",type=\"vrp\"}",
|
||||
"legendFormat": "after_slurm vrp",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_rtr_data_quality_items{stage=\"after_slurm\",type=\"aspa\"}",
|
||||
"legendFormat": "after_slurm aspa",
|
||||
"refId": "C"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"title": "SLURM Filters / Assertions",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 4
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short",
|
||||
"min": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_rtr_slurm_filters",
|
||||
"legendFormat": "filter {{type}}",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_rtr_slurm_assertions",
|
||||
"legendFormat": "assert {{type}}",
|
||||
"refId": "B"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"title": "Cache Ready / Delta Window",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 12
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short",
|
||||
"min": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_rtr_cache_ready",
|
||||
"legendFormat": "ready",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_rtr_cache_delta_window_length",
|
||||
"legendFormat": "v{{version}} length",
|
||||
"refId": "B"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"title": "Cache Snapshot Items",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 12
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short",
|
||||
"min": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_rtr_cache_snapshot_items",
|
||||
"legendFormat": "v{{version}} {{type}}",
|
||||
"refId": "A"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"title": "Latest Delta Items",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 12
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short",
|
||||
"min": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_rtr_cache_delta_items",
|
||||
"legendFormat": "v{{version}} {{direction}} {{type}}",
|
||||
"refId": "A"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"title": "Connections",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 20
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short",
|
||||
"min": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_rtr_active_connections",
|
||||
"legendFormat": "active",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_rtr_connections",
|
||||
"legendFormat": "{{transport}}",
|
||||
"refId": "B"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"title": "Connection Utilization / Max",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 20
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"min": 0
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byRegexp",
|
||||
"options": ".*utilization.*"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "percentunit"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byRegexp",
|
||||
"options": ".*max.*"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "short"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_rtr_connection_utilization",
|
||||
"legendFormat": "utilization",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_rtr_max_connections",
|
||||
"legendFormat": "max",
|
||||
"refId": "B"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"title": "Report Age",
|
||||
"type": "timeseries",
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 20
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "s",
|
||||
"min": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_rtr_source_report_age_seconds",
|
||||
"legendFormat": "source",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_rtr_runtime_report_age_seconds",
|
||||
"legendFormat": "runtime",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_rtr_clients_report_age_seconds",
|
||||
"legendFormat": "clients",
|
||||
"refId": "C"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"refresh": "10s",
|
||||
"schemaVersion": 40,
|
||||
"tags": [
|
||||
"rpki",
|
||||
"inter-rp",
|
||||
"routinator"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timezone": "browser",
|
||||
"title": "RTR Service Overview",
|
||||
"uid": "ours-rp-rtr-overview",
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,875 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"decimals": 0,
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 6,
|
||||
"h": 4
|
||||
},
|
||||
"id": 1,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(ours_rp_cir_trust_anchors and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||
"legendFormat": "RIRs",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Current Run RIRs",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 6,
|
||||
"y": 0,
|
||||
"w": 6,
|
||||
"h": 4
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(ours_rp_run_duration_seconds and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||
"legendFormat": "wall",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Latest Wall Time",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "bytes"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"w": 6,
|
||||
"h": 4
|
||||
},
|
||||
"id": 3,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(ours_rp_run_max_rss_bytes and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||
"legendFormat": "rss",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Latest Max RSS",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"decimals": 0,
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 18,
|
||||
"y": 0,
|
||||
"w": 6,
|
||||
"h": 4
|
||||
},
|
||||
"id": 4,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(ours_rp_publication_points and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||
"legendFormat": "publication points",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Publication Points",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"decimals": 0,
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 4,
|
||||
"w": 6,
|
||||
"h": 4
|
||||
},
|
||||
"id": 9,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(ours_rp_run_sequence)",
|
||||
"legendFormat": "seq",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Latest Run Sequence",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"decimals": 2,
|
||||
"unit": "percent",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "red",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "orange",
|
||||
"value": 90
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
"value": 98
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 6,
|
||||
"y": 4,
|
||||
"w": 6,
|
||||
"h": 4
|
||||
},
|
||||
"id": 10,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max((100 * sum by (exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"publication_point_cache\"}) / sum by (exported_instance) (ours_rp_publication_points)) and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||
"legendFormat": "PP cache hit ratio",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Latest PP Cache Hit Ratio",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "none",
|
||||
"decimals": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 12,
|
||||
"y": 4,
|
||||
"w": 6,
|
||||
"h": 4
|
||||
},
|
||||
"id": 11,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(ours_rp_vrps{kind=\"total\"} and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||
"legendFormat": "VRPs raw",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "VRPs",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "none",
|
||||
"decimals": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 18,
|
||||
"y": 4,
|
||||
"w": 6,
|
||||
"h": 4
|
||||
},
|
||||
"id": 12,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "11.3.1",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(ours_rp_vaps and on (exported_instance) topk(1, ours_rp_run_sequence))",
|
||||
"legendFormat": "VAPs",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "VAPs",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 8,
|
||||
"w": 12,
|
||||
"h": 8
|
||||
},
|
||||
"id": 5,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_run_duration_seconds",
|
||||
"legendFormat": "wall",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_stage_duration_seconds{stage=\"validation\"}",
|
||||
"legendFormat": "validation",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Run / Validation Duration",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"decimals": 0,
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 12,
|
||||
"y": 8,
|
||||
"w": 12,
|
||||
"h": 8
|
||||
},
|
||||
"id": 6,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_vrps{kind=\"total\"}",
|
||||
"legendFormat": "VRPs raw",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_vrps{kind=\"unique\"}",
|
||||
"legendFormat": "VRPs unique",
|
||||
"refId": "D"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_vaps",
|
||||
"legendFormat": "VAPs",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_cir_objects",
|
||||
"legendFormat": "CIR objects",
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
"title": "Output and Input Sizes",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"decimals": 0,
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 16,
|
||||
"w": 12,
|
||||
"h": 8
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_large_publication_points",
|
||||
"legendFormat": "> {{object_count_gt}} objects",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Large Publication Points by Object Count",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "s"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 12,
|
||||
"y": 16,
|
||||
"w": 12,
|
||||
"h": 8
|
||||
},
|
||||
"id": 13,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_run_stage_duration_seconds{stage=\"validation\"}",
|
||||
"legendFormat": "validation",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_run_stage_duration_seconds{stage=\"report_write\"}",
|
||||
"legendFormat": "report write",
|
||||
"refId": "E"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_run_stage_duration_seconds{stage=\"ccr_write\"}",
|
||||
"legendFormat": "ccr write",
|
||||
"refId": "F"
|
||||
},
|
||||
{
|
||||
"expr": "ours_rp_run_stage_duration_seconds{stage=\"cir_write\"}",
|
||||
"legendFormat": "cir write",
|
||||
"refId": "G"
|
||||
}
|
||||
],
|
||||
"title": "Output Stage Durations",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "bytes",
|
||||
"decimals": 2
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 24,
|
||||
"w": 12,
|
||||
"h": 8
|
||||
},
|
||||
"id": 14,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull",
|
||||
"max"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_run_max_rss_bytes",
|
||||
"legendFormat": "Max RSS",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Max RSS Over Time",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "percent",
|
||||
"decimals": 2,
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "red",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "orange",
|
||||
"value": 90
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
"value": 98
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 12,
|
||||
"y": 24,
|
||||
"w": 12,
|
||||
"h": 8
|
||||
},
|
||||
"id": 17,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull",
|
||||
"min",
|
||||
"max"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "100 * sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"publication_point_cache\"}) / sum by (job, instance, exported_instance) (ours_rp_publication_points)",
|
||||
"legendFormat": "PP cache hit ratio",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "PP Cache Hit Ratio",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "bytes",
|
||||
"decimals": 2
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 32,
|
||||
"w": 24,
|
||||
"h": 8
|
||||
},
|
||||
"id": 15,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull",
|
||||
"max"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_state_db_size_bytes",
|
||||
"legendFormat": "{{db}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "State DB Size Over Time",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "none",
|
||||
"decimals": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 40,
|
||||
"w": 24,
|
||||
"h": 8
|
||||
},
|
||||
"id": 16,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull",
|
||||
"max"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "ours_rp_state_db_files",
|
||||
"legendFormat": "{{db}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "State DB File Count Over Time",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "Prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "none",
|
||||
"decimals": 0,
|
||||
"min": 0
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 48,
|
||||
"w": 24,
|
||||
"h": 8
|
||||
},
|
||||
"id": 18,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"lastNotNull",
|
||||
"max"
|
||||
],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"fresh\"})",
|
||||
"legendFormat": "fresh pp",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"roa\"})",
|
||||
"legendFormat": "fresh roa",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"manifest\"})",
|
||||
"legendFormat": "fresh mft",
|
||||
"refId": "C"
|
||||
},
|
||||
{
|
||||
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"certificate\"})",
|
||||
"legendFormat": "fresh crt",
|
||||
"refId": "D"
|
||||
},
|
||||
{
|
||||
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"crl\"})",
|
||||
"legendFormat": "fresh crl",
|
||||
"refId": "E"
|
||||
}
|
||||
],
|
||||
"title": "Fresh PP / Object Counts by Run",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"refresh": "5s",
|
||||
"schemaVersion": 40,
|
||||
"tags": [
|
||||
"ours-rp",
|
||||
"rpki",
|
||||
"soak"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-30m",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "browser",
|
||||
"title": "Ours RP Soak Overview",
|
||||
"uid": "ours-rp-soak-overview",
|
||||
"version": 4,
|
||||
"weekStart": ""
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: ours-rp-docker-installer
|
||||
orgId: 1
|
||||
folder: Ours RP Docker Installer
|
||||
type: file
|
||||
disableDeletion: false
|
||||
updateIntervalSeconds: 10
|
||||
allowUiUpdates: true
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
uid: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://prometheus:9090
|
||||
isDefault: true
|
||||
editable: true
|
||||
13
deploy/docker-installer/compose/prometheus.yml
Normal file
13
deploy/docker-installer/compose/prometheus.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
global:
|
||||
scrape_interval: 5s
|
||||
evaluation_interval: 5s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: ours-rp-artifact-metrics
|
||||
metrics_path: /metrics
|
||||
static_configs:
|
||||
- targets:
|
||||
- artifact-metrics:9556
|
||||
labels:
|
||||
rp: ours-rp
|
||||
source: docker-installer-artifact-sidecar
|
||||
14
deploy/docker-installer/custom-fixtures/README.md
Normal file
14
deploy/docker-installer/custom-fixtures/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Custom TAL fixture mount
|
||||
|
||||
This directory is intentionally shipped as a mount point, not as a repository
|
||||
of private keys. Run the patch tool `tools/generate_custom_fixture.py` to create
|
||||
the test-only TAL, TA, RPKI objects, HTTPS CA/server certificates, RRDP files,
|
||||
and rsync module data below it.
|
||||
|
||||
The generated fixture contains `baseline-v1`, `sync-hash-mismatch` and
|
||||
`baseline-v2` for snapshot / hash / delta tests, plus
|
||||
`validation-expired`, `validation-max-length`,
|
||||
`validation-max-length-invalid`, `validation-roa-prefix-outside`,
|
||||
`validation-out-of-resource`, `validation-ca-over-resource`, and
|
||||
`validation-nonstandard` for validation cases. Generated keys are test-only
|
||||
and must not be used in a production RPKI hierarchy.
|
||||
30
deploy/docker-installer/custom-tal-patch/README.zh-CN.md
Normal file
30
deploy/docker-installer/custom-tal-patch/README.zh-CN.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# 自建 TAL/TA 补丁
|
||||
|
||||
本目录描述并生成一个叠加到客户 Arm64 Ours RP 组件包上的测试补丁。补丁基线是源码 commit `9f4f4cf069e9fa8f6f97ed0f1065adac38abe82f`,不会替换客户包中的 runtime image 或 `bin/rpki`、`bin/rpki_daemon` 二进制。
|
||||
|
||||
补丁做三件事:
|
||||
|
||||
1. 在 runner 中增加 `RIRS=custom`、`TAL_INPUT_MODE=custom-file-with-ta`、自定义 TAL/TA 路径和 `HTTP_ROOT_CERT_PATHS`;
|
||||
2. 通过 Compose 只读挂载 patched runner、`custom-fixtures/` 和 `host.docker.internal`,连接固定 HTTPS RRDP 与 rsync 服务;
|
||||
3. 提供固定证书链、RPKI 对象生成器和服务启动脚本,不依赖 Barry、Rapport 或公网 RIR。
|
||||
|
||||
先停止 Ours RP,再执行:
|
||||
|
||||
```bash
|
||||
./apply_custom_tal_patch.sh \
|
||||
--component-root /opt/ours-rp-rtr-stack/components/ours-rp \
|
||||
--enable-custom
|
||||
```
|
||||
|
||||
`--enable-custom` 会修改组件 `.env` 的 custom 模式入口;不加该选项只安装 overlay,不改变当前 `.env`。回滚:
|
||||
|
||||
```bash
|
||||
./rollback_custom_tal_patch.sh \
|
||||
--component-root /opt/ours-rp-rtr-stack/components/ours-rp
|
||||
```
|
||||
|
||||
补丁应用脚本会校验 `PACKAGE-MANIFEST.env` 的源码 commit 和 `arm64` 架构,并留下 `CUSTOM-TAL-PATCH-MANIFEST.env` 与 `.custom-tal-patch-state/` 备份。升级组件后需要重新检查 patch marker;不要把该 overlay 误当成客户原始安装包。
|
||||
|
||||
生成器默认把容器访问地址写成 `host.docker.internal`,固定服务端口为 HTTPS RRDP `18443`、rsync `1873`,rsync module 为 `custom`。可用 `--host localhost` 生成给宿主机直接运行 `bin/rpki` 的本地变体。
|
||||
|
||||
用例包括:`baseline-v1`、`sync-hash-mismatch`、`baseline-v2`、`validation-expired`、`validation-max-length`、`validation-max-length-invalid`、`validation-roa-prefix-outside`、`validation-out-of-resource`、`validation-ca-over-resource`、`validation-nonstandard`。其中 `validation-ca-over-resource` 在 `RESOURCE_VALIDATION_MODE=rfc6487` 下验证严格拒绝;`validation-update-03` 是原包默认的兼容处理模式。
|
||||
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PATCH_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
PAYLOAD_ROOT="$SCRIPT_DIR/payload"
|
||||
STATE_DIR_NAME=".custom-tal-patch-state"
|
||||
EXPECTED_SOURCE_COMMIT="9f4f4cf069e9fa8f6f97ed0f1065adac38abe82f"
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage:
|
||||
./apply_custom_tal_patch.sh --component-root <installed-ours-rp-root>
|
||||
./apply_custom_tal_patch.sh --component-root <root> --enable-custom
|
||||
|
||||
The default operation installs the overlay but does not change the existing
|
||||
.env. --enable-custom writes the custom TAL/TA/root-cert example values into
|
||||
the component .env so the next run uses the fixed local fixture.
|
||||
Stop the ours RP service before applying or rolling back the overlay.
|
||||
USAGE
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "error: $*" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
COMPONENT_ROOT=""
|
||||
ENABLE_CUSTOM=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--component-root)
|
||||
COMPONENT_ROOT="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--enable-custom)
|
||||
ENABLE_CUSTOM=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$COMPONENT_ROOT" ]] || die "--component-root is required"
|
||||
COMPONENT_ROOT="$(cd "$COMPONENT_ROOT" 2>/dev/null && pwd)" \
|
||||
|| die "component root does not exist: $COMPONENT_ROOT"
|
||||
[[ -f "$COMPONENT_ROOT/PACKAGE-MANIFEST.env" ]] \
|
||||
|| die "missing component PACKAGE-MANIFEST.env: $COMPONENT_ROOT"
|
||||
[[ -d "$PAYLOAD_ROOT" ]] || die "missing patch payload: $PAYLOAD_ROOT"
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "$COMPONENT_ROOT/PACKAGE-MANIFEST.env"
|
||||
[[ "${source_commit:-}" == "$EXPECTED_SOURCE_COMMIT" ]] \
|
||||
|| die "base source commit mismatch: ${source_commit:-missing} != $EXPECTED_SOURCE_COMMIT"
|
||||
[[ "${package_arch:-${PACKAGE_ARCH:-}}" == "arm64" ]] \
|
||||
|| die "this patch is for the customer Arm64 component, got ${package_arch:-${PACKAGE_ARCH:-missing}}"
|
||||
|
||||
STATE_ROOT="$COMPONENT_ROOT/$STATE_DIR_NAME"
|
||||
BACKUP_ROOT="$STATE_ROOT/original"
|
||||
marker="$COMPONENT_ROOT/CUSTOM-TAL-PATCH-MANIFEST.env"
|
||||
|
||||
set_env_value() {
|
||||
local env_path="$1"
|
||||
local key="$2"
|
||||
local value="$3"
|
||||
local tmp_path="${env_path}.custom-tal-patch.tmp"
|
||||
[[ -f "$env_path" ]] || die "missing component .env: $env_path"
|
||||
awk -v key="$key" -v value="$value" '
|
||||
BEGIN { found = 0 }
|
||||
$0 ~ "^" key "=" { print key "=" value; found = 1; next }
|
||||
{ print }
|
||||
END { if (!found) print key "=" value }
|
||||
' "$env_path" > "$tmp_path"
|
||||
mv "$tmp_path" "$env_path"
|
||||
}
|
||||
|
||||
copy_payload_file() {
|
||||
local source_path="$1"
|
||||
local target_path="$2"
|
||||
mkdir -p "$(dirname "$target_path")"
|
||||
cp "$source_path" "$target_path"
|
||||
chmod --reference="$source_path" "$target_path" 2>/dev/null || true
|
||||
}
|
||||
|
||||
append_custom_env_example() {
|
||||
local target_path="$1"
|
||||
if ! grep -Eq '^CUSTOM_TAL_URI=' "$target_path"; then
|
||||
{
|
||||
printf '\n# #142 custom TAL patch settings; original package values above are preserved.\n'
|
||||
cat "$SCRIPT_DIR/custom-tal.env.example"
|
||||
} >> "$target_path"
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ -e "$STATE_ROOT" ]]; then
|
||||
die "patch is already applied or an incomplete state exists: $STATE_ROOT"
|
||||
fi
|
||||
|
||||
mkdir -p "$BACKUP_ROOT"
|
||||
for relative_path in compose/docker-compose.yml .env.example; do
|
||||
[[ -f "$COMPONENT_ROOT/$relative_path" ]] \
|
||||
|| die "missing base file: $COMPONENT_ROOT/$relative_path"
|
||||
mkdir -p "$BACKUP_ROOT/$(dirname "$relative_path")"
|
||||
cp -a "$COMPONENT_ROOT/$relative_path" "$BACKUP_ROOT/$relative_path"
|
||||
done
|
||||
|
||||
runner_path="$COMPONENT_ROOT/scripts/soak/run_soak.sh"
|
||||
if [[ -e "$runner_path" ]]; then
|
||||
mkdir -p "$BACKUP_ROOT/scripts/soak"
|
||||
cp -a "$runner_path" "$BACKUP_ROOT/scripts/soak/run_soak.sh"
|
||||
printf 'runner_was_present=1\n' > "$STATE_ROOT/state.env"
|
||||
else
|
||||
printf 'runner_was_present=0\n' > "$STATE_ROOT/state.env"
|
||||
fi
|
||||
|
||||
if [[ -d "$COMPONENT_ROOT/custom-fixtures" ]]; then
|
||||
cp -a "$COMPONENT_ROOT/custom-fixtures" "$BACKUP_ROOT/custom-fixtures"
|
||||
printf 'custom_fixtures_were_present=1\n' >> "$STATE_ROOT/state.env"
|
||||
else
|
||||
printf 'custom_fixtures_were_present=0\n' >> "$STATE_ROOT/state.env"
|
||||
fi
|
||||
|
||||
if [[ -d "$COMPONENT_ROOT/custom-tal-patch" ]]; then
|
||||
cp -a "$COMPONENT_ROOT/custom-tal-patch" "$BACKUP_ROOT/custom-tal-patch"
|
||||
printf 'patch_tools_were_present=1\n' >> "$STATE_ROOT/state.env"
|
||||
else
|
||||
printf 'patch_tools_were_present=0\n' >> "$STATE_ROOT/state.env"
|
||||
fi
|
||||
|
||||
if [[ -f "$COMPONENT_ROOT/custom-tal.env.example" ]]; then
|
||||
cp -a "$COMPONENT_ROOT/custom-tal.env.example" "$BACKUP_ROOT/custom-tal.env.example"
|
||||
printf 'custom_env_example_was_present=1\n' >> "$STATE_ROOT/state.env"
|
||||
else
|
||||
printf 'custom_env_example_was_present=0\n' >> "$STATE_ROOT/state.env"
|
||||
fi
|
||||
|
||||
if [[ "$ENABLE_CUSTOM" == "1" ]]; then
|
||||
[[ -f "$COMPONENT_ROOT/.env" ]] || die "missing component .env: $COMPONENT_ROOT/.env"
|
||||
cp -a "$COMPONENT_ROOT/.env" "$BACKUP_ROOT/.env"
|
||||
fi
|
||||
|
||||
copy_payload_file "$PAYLOAD_ROOT/compose/docker-compose.yml" "$COMPONENT_ROOT/compose/docker-compose.yml"
|
||||
append_custom_env_example "$COMPONENT_ROOT/.env.example"
|
||||
copy_payload_file "$PAYLOAD_ROOT/scripts/soak/run_soak.sh" "$runner_path"
|
||||
mkdir -p "$COMPONENT_ROOT/custom-fixtures/tal" \
|
||||
"$COMPONENT_ROOT/custom-fixtures/ta" \
|
||||
"$COMPONENT_ROOT/custom-fixtures/certs" \
|
||||
"$COMPONENT_ROOT/custom-fixtures/services"
|
||||
if [[ -d "$PAYLOAD_ROOT/custom-fixtures" ]]; then
|
||||
cp -a "$PAYLOAD_ROOT/custom-fixtures/." "$COMPONENT_ROOT/custom-fixtures/"
|
||||
fi
|
||||
mkdir -p "$COMPONENT_ROOT/custom-tal-patch/tools"
|
||||
cp -a "$PAYLOAD_ROOT/tools/." "$COMPONENT_ROOT/custom-tal-patch/tools/"
|
||||
copy_payload_file "$SCRIPT_DIR/custom-tal.env.example" \
|
||||
"$COMPONENT_ROOT/custom-tal.env.example"
|
||||
|
||||
if [[ "$ENABLE_CUSTOM" == "1" ]]; then
|
||||
set_env_value "$COMPONENT_ROOT/.env" RIRS custom
|
||||
set_env_value "$COMPONENT_ROOT/.env" TAL_INPUT_MODE custom-file-with-ta
|
||||
set_env_value "$COMPONENT_ROOT/.env" CUSTOM_FIXTURE_HOST_DIR ../custom-fixtures
|
||||
set_env_value "$COMPONENT_ROOT/.env" CUSTOM_TAL_PATH /opt/ours-rp/custom-fixtures/tal/custom.tal
|
||||
set_env_value "$COMPONENT_ROOT/.env" CUSTOM_TA_PATH /opt/ours-rp/custom-fixtures/ta/custom-ta.cer
|
||||
set_env_value "$COMPONENT_ROOT/.env" CUSTOM_TAL_URI https://host.docker.internal:18443/tal/custom.tal
|
||||
set_env_value "$COMPONENT_ROOT/.env" HTTP_ROOT_CERT_PATHS /opt/ours-rp/custom-fixtures/certs/rrdp-ca.pem
|
||||
printf 'env_was_enabled=1\n' >> "$STATE_ROOT/state.env"
|
||||
else
|
||||
printf 'env_was_enabled=0\n' >> "$STATE_ROOT/state.env"
|
||||
fi
|
||||
|
||||
[[ -f "$SCRIPT_DIR/PATCH-MANIFEST.env" ]] \
|
||||
|| die "missing PATCH-MANIFEST.env"
|
||||
cp "$SCRIPT_DIR/PATCH-MANIFEST.env" "$marker"
|
||||
printf 'patch_root=%s\n' "$PATCH_ROOT" >> "$STATE_ROOT/state.env"
|
||||
printf 'applied_at_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$STATE_ROOT/state.env"
|
||||
|
||||
echo "custom TAL patch applied to $COMPONENT_ROOT"
|
||||
if [[ "$ENABLE_CUSTOM" == "1" ]]; then
|
||||
echo "custom mode enabled in $COMPONENT_ROOT/.env"
|
||||
else
|
||||
echo "custom mode remains disabled; review custom-tal.env.example before enabling it"
|
||||
fi
|
||||
@@ -0,0 +1,15 @@
|
||||
# Copy these values into the component .env after applying the patch.
|
||||
# The patch's --enable-custom option writes the same values automatically.
|
||||
RIRS=custom
|
||||
TAL_INPUT_MODE=custom-file-with-ta
|
||||
CUSTOM_FIXTURE_HOST_DIR=../custom-fixtures
|
||||
CUSTOM_TAL_PATH=/opt/ours-rp/custom-fixtures/tal/custom.tal
|
||||
CUSTOM_TA_PATH=/opt/ours-rp/custom-fixtures/ta/custom-ta.cer
|
||||
CUSTOM_TAL_URI=https://host.docker.internal:18443/tal/custom.tal
|
||||
HTTP_ROOT_CERT_PATHS=/opt/ours-rp/custom-fixtures/certs/rrdp-ca.pem
|
||||
|
||||
# Recommended for a finite acceptance run; keep the customer default for a
|
||||
# long-running test only after the fixture service is supervised.
|
||||
MAX_RUNS=1
|
||||
INTERVAL_SECS=0
|
||||
SOAK_RESTART_POLICY=no
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage:
|
||||
./rollback_custom_tal_patch.sh --component-root <installed-ours-rp-root>
|
||||
|
||||
Stop the ours RP service before rolling back the overlay.
|
||||
USAGE
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "error: $*" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
COMPONENT_ROOT=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--component-root)
|
||||
COMPONENT_ROOT="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$COMPONENT_ROOT" ]] || die "--component-root is required"
|
||||
COMPONENT_ROOT="$(cd "$COMPONENT_ROOT" 2>/dev/null && pwd)" \
|
||||
|| die "component root does not exist: $COMPONENT_ROOT"
|
||||
STATE_ROOT="$COMPONENT_ROOT/.custom-tal-patch-state"
|
||||
BACKUP_ROOT="$STATE_ROOT/original"
|
||||
[[ -d "$STATE_ROOT" ]] || die "patch state not found: $STATE_ROOT"
|
||||
[[ -f "$BACKUP_ROOT/compose/docker-compose.yml" ]] \
|
||||
|| die "patch backup is incomplete: $BACKUP_ROOT"
|
||||
|
||||
cp -a "$BACKUP_ROOT/compose/docker-compose.yml" "$COMPONENT_ROOT/compose/docker-compose.yml"
|
||||
cp -a "$BACKUP_ROOT/.env.example" "$COMPONENT_ROOT/.env.example"
|
||||
|
||||
if [[ -f "$BACKUP_ROOT/scripts/soak/run_soak.sh" ]]; then
|
||||
mkdir -p "$COMPONENT_ROOT/scripts/soak"
|
||||
cp -a "$BACKUP_ROOT/scripts/soak/run_soak.sh" "$COMPONENT_ROOT/scripts/soak/run_soak.sh"
|
||||
else
|
||||
rm -f "$COMPONENT_ROOT/scripts/soak/run_soak.sh"
|
||||
rmdir "$COMPONENT_ROOT/scripts/soak" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [[ -d "$BACKUP_ROOT/custom-fixtures" ]]; then
|
||||
rm -rf "$COMPONENT_ROOT/custom-fixtures"
|
||||
cp -a "$BACKUP_ROOT/custom-fixtures" "$COMPONENT_ROOT/custom-fixtures"
|
||||
else
|
||||
rm -rf "$COMPONENT_ROOT/custom-fixtures"
|
||||
fi
|
||||
|
||||
if [[ -d "$BACKUP_ROOT/custom-tal-patch" ]]; then
|
||||
rm -rf "$COMPONENT_ROOT/custom-tal-patch"
|
||||
cp -a "$BACKUP_ROOT/custom-tal-patch" "$COMPONENT_ROOT/custom-tal-patch"
|
||||
else
|
||||
rm -rf "$COMPONENT_ROOT/custom-tal-patch"
|
||||
fi
|
||||
|
||||
if [[ -f "$BACKUP_ROOT/custom-tal.env.example" ]]; then
|
||||
cp -a "$BACKUP_ROOT/custom-tal.env.example" "$COMPONENT_ROOT/custom-tal.env.example"
|
||||
else
|
||||
rm -f "$COMPONENT_ROOT/custom-tal.env.example"
|
||||
fi
|
||||
|
||||
if [[ -f "$BACKUP_ROOT/.env" ]]; then
|
||||
cp -a "$BACKUP_ROOT/.env" "$COMPONENT_ROOT/.env"
|
||||
fi
|
||||
rm -f "$COMPONENT_ROOT/CUSTOM-TAL-PATCH-MANIFEST.env"
|
||||
rm -rf "$STATE_ROOT"
|
||||
echo "custom TAL patch rolled back from $COMPONENT_ROOT"
|
||||
@@ -0,0 +1,636 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate a small self-contained RPKI repository for installer tests.
|
||||
|
||||
The generator uses Python cryptography and the OpenSSL CLI for CMS signing.
|
||||
It does not invoke Barry, Rapport, or a public RIR service.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import ipaddress
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, NoEncryption, PrivateFormat, PublicFormat
|
||||
from cryptography.x509 import AccessDescription, AuthorityInformationAccess, DistributionPoint, UniformResourceIdentifier
|
||||
from cryptography.x509.oid import AuthorityInformationAccessOID, NameOID, ObjectIdentifier
|
||||
|
||||
|
||||
RRDP_HOST = "host.docker.internal"
|
||||
RRDP_PORT = 18443
|
||||
RSYNC_PORT = 1873
|
||||
TAL_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/ta.cer"
|
||||
TAL_HTTPS_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/ta/custom-ta.cer"
|
||||
RRDP_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/rrdp/notification.xml"
|
||||
ROOT_REPO_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/root/"
|
||||
ROOT_MFT_URI = f"{ROOT_REPO_URI}root.mft"
|
||||
CHILD_REPO_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/child/"
|
||||
CHILD_MFT_URI = f"{CHILD_REPO_URI}child.mft"
|
||||
OID_SIA = ObjectIdentifier("1.3.6.1.5.5.7.1.11")
|
||||
OID_RESOURCES_IP = ObjectIdentifier("1.3.6.1.5.5.7.1.7")
|
||||
OID_RESOURCES_AS = ObjectIdentifier("1.3.6.1.5.5.7.1.8")
|
||||
OID_POLICY_IP_AS = ObjectIdentifier("1.3.6.1.5.5.7.14.2")
|
||||
OID_ROA_ECONTENT = "1.2.840.113549.1.9.16.1.24"
|
||||
OID_MANIFEST_ECONTENT = "1.2.840.113549.1.9.16.1.26"
|
||||
OID_SHA256 = "2.16.840.1.101.3.4.2.1"
|
||||
SIA_CA_REPOSITORY = "1.3.6.1.5.5.7.48.5"
|
||||
SIA_RPKI_MANIFEST = "1.3.6.1.5.5.7.48.10"
|
||||
SIA_SIGNED_OBJECT = "1.3.6.1.5.5.7.48.11"
|
||||
SIA_RPKI_NOTIFY = "1.3.6.1.5.5.7.48.13"
|
||||
UTC = timezone.utc
|
||||
VALID_FROM = datetime(2026, 1, 1, tzinfo=UTC)
|
||||
VALID_TO = datetime(2027, 12, 31, 23, 59, 59, tzinfo=UTC)
|
||||
CHILD_PREFIX = ipaddress.ip_network("203.0.113.0/24")
|
||||
SECOND_PREFIX = ipaddress.ip_network("203.0.113.128/25")
|
||||
OUTSIDE_PREFIX = ipaddress.ip_network("203.0.114.0/24")
|
||||
TEST_ASN = 64496
|
||||
SESSION_ID = "11111111-2222-4333-8444-555555555555"
|
||||
|
||||
|
||||
def tlv(tag: int, value: bytes) -> bytes:
|
||||
length = len(value)
|
||||
if length < 128:
|
||||
encoded_length = bytes([length])
|
||||
else:
|
||||
raw = length.to_bytes((length.bit_length() + 7) // 8, "big")
|
||||
encoded_length = bytes([0x80 | len(raw)]) + raw
|
||||
return bytes([tag]) + encoded_length + value
|
||||
|
||||
|
||||
def seq(*values: bytes) -> bytes:
|
||||
return tlv(0x30, b"".join(values))
|
||||
|
||||
|
||||
def integer(value: int) -> bytes:
|
||||
raw = value.to_bytes(max(1, (value.bit_length() + 7) // 8), "big")
|
||||
if raw[0] & 0x80:
|
||||
raw = b"\x00" + raw
|
||||
return tlv(0x02, raw)
|
||||
|
||||
|
||||
def oid(oid_value: str) -> bytes:
|
||||
parts = [int(part) for part in oid_value.split(".")]
|
||||
encoded = bytearray([40 * parts[0] + parts[1]])
|
||||
for part in parts[2:]:
|
||||
chunks = [part & 0x7F]
|
||||
part >>= 7
|
||||
while part:
|
||||
chunks.append(0x80 | (part & 0x7F))
|
||||
part >>= 7
|
||||
encoded.extend(reversed(chunks))
|
||||
return tlv(0x06, bytes(encoded))
|
||||
|
||||
|
||||
def octet(value: bytes) -> bytes:
|
||||
return tlv(0x04, value)
|
||||
|
||||
|
||||
def uri(value: str) -> bytes:
|
||||
return tlv(0x86, value.encode("ascii"))
|
||||
|
||||
|
||||
def bit_string(value: bytes, unused: int = 0) -> bytes:
|
||||
return tlv(0x03, bytes([unused]) + value)
|
||||
|
||||
|
||||
def generalized_time(value: datetime) -> bytes:
|
||||
return tlv(0x18, value.astimezone(UTC).strftime("%Y%m%d%H%M%SZ").encode("ascii"))
|
||||
|
||||
|
||||
def context_zero(value: bytes) -> bytes:
|
||||
return tlv(0xA0, value)
|
||||
|
||||
|
||||
def ip_choice(networks: list[ipaddress._BaseNetwork]) -> bytes:
|
||||
entries = []
|
||||
for network in networks:
|
||||
length = (network.prefixlen + 7) // 8
|
||||
raw = network.network_address.packed[:length]
|
||||
entries.append(bit_string(raw, length * 8 - network.prefixlen))
|
||||
return seq(*entries)
|
||||
|
||||
|
||||
def ip_resources(networks: list[ipaddress._BaseNetwork]) -> bytes:
|
||||
families: dict[int, list[ipaddress._BaseNetwork]] = {1: [], 2: []}
|
||||
for network in networks:
|
||||
families[1 if network.version == 4 else 2].append(network)
|
||||
return seq(
|
||||
*(
|
||||
seq(octet(afi.to_bytes(2, "big")), ip_choice(items))
|
||||
for afi, items in families.items()
|
||||
if items
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def ip_inherit() -> bytes:
|
||||
return seq(
|
||||
seq(octet(b"\x00\x01"), tlv(0x05, b"")),
|
||||
seq(octet(b"\x00\x02"), tlv(0x05, b"")),
|
||||
)
|
||||
|
||||
|
||||
def as_resources(asns: list[int] | None) -> bytes:
|
||||
if asns is None:
|
||||
return seq(context_zero(tlv(0x05, b"")))
|
||||
return seq(context_zero(seq(*(integer(asn) for asn in asns))))
|
||||
|
||||
|
||||
def sia(entries: list[tuple[str, str]]) -> bytes:
|
||||
return seq(*(seq(oid(method), uri(location)) for method, location in entries))
|
||||
|
||||
|
||||
def write(path: Path, data: bytes, mode: int | None = None) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(data)
|
||||
if mode is not None:
|
||||
path.chmod(mode)
|
||||
|
||||
|
||||
def pem_cert(cert: x509.Certificate) -> bytes:
|
||||
return cert.public_bytes(Encoding.PEM)
|
||||
|
||||
|
||||
def pem_key(key: rsa.RSAPrivateKey) -> bytes:
|
||||
return key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption())
|
||||
|
||||
|
||||
def write_key(path: Path, key: rsa.RSAPrivateKey) -> None:
|
||||
write(path, pem_key(key), 0o600)
|
||||
|
||||
|
||||
def sign_cert(
|
||||
subject: x509.Name,
|
||||
issuer: x509.Name,
|
||||
public_key,
|
||||
signing_key: rsa.RSAPrivateKey,
|
||||
serial: int,
|
||||
is_ca: bool,
|
||||
ski_key: rsa.RSAPrivateKey,
|
||||
aki_key: rsa.RSAPrivateKey | None = None,
|
||||
ip_ext: bytes | None = None,
|
||||
as_ext: bytes | None = None,
|
||||
ca_repository: str | None = None,
|
||||
manifest_uri: str | None = None,
|
||||
notify_uri: str | None = None,
|
||||
signed_object_uri: str | None = None,
|
||||
crl_uri: str | None = None,
|
||||
ca_issuer_uri: str | None = None,
|
||||
not_before: datetime = VALID_FROM,
|
||||
not_after: datetime = VALID_TO,
|
||||
) -> x509.Certificate:
|
||||
builder = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(subject)
|
||||
.issuer_name(issuer)
|
||||
.public_key(public_key)
|
||||
.serial_number(serial)
|
||||
.not_valid_before(not_before)
|
||||
.not_valid_after(not_after)
|
||||
)
|
||||
if is_ca:
|
||||
builder = builder.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
|
||||
if is_ca:
|
||||
usage = x509.KeyUsage(False, False, False, False, False, True, True, False, False)
|
||||
else:
|
||||
usage = x509.KeyUsage(True, False, False, False, False, False, False, False, False)
|
||||
builder = builder.add_extension(usage, critical=True)
|
||||
builder = builder.add_extension(x509.SubjectKeyIdentifier.from_public_key(ski_key.public_key()), critical=False)
|
||||
if aki_key is not None:
|
||||
builder = builder.add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(aki_key.public_key()), critical=False)
|
||||
builder = builder.add_extension(
|
||||
x509.CertificatePolicies([x509.PolicyInformation(OID_POLICY_IP_AS, None)]),
|
||||
critical=True,
|
||||
)
|
||||
if crl_uri is not None:
|
||||
builder = builder.add_extension(
|
||||
x509.CRLDistributionPoints(
|
||||
[DistributionPoint([UniformResourceIdentifier(crl_uri)], None, None, None)]
|
||||
),
|
||||
critical=False,
|
||||
)
|
||||
if ca_issuer_uri is not None:
|
||||
builder = builder.add_extension(
|
||||
AuthorityInformationAccess(
|
||||
[AccessDescription(AuthorityInformationAccessOID.CA_ISSUERS, UniformResourceIdentifier(ca_issuer_uri))]
|
||||
),
|
||||
critical=False,
|
||||
)
|
||||
sia_entries: list[tuple[str, str]] = []
|
||||
if ca_repository is not None:
|
||||
sia_entries.append((SIA_CA_REPOSITORY, ca_repository))
|
||||
if manifest_uri is not None:
|
||||
sia_entries.append((SIA_RPKI_MANIFEST, manifest_uri))
|
||||
if notify_uri is not None:
|
||||
sia_entries.append((SIA_RPKI_NOTIFY, notify_uri))
|
||||
if signed_object_uri is not None:
|
||||
sia_entries.append((SIA_SIGNED_OBJECT, signed_object_uri))
|
||||
if sia_entries:
|
||||
builder = builder.add_extension(x509.UnrecognizedExtension(OID_SIA, sia(sia_entries)), critical=False)
|
||||
if ip_ext is not None:
|
||||
builder = builder.add_extension(x509.UnrecognizedExtension(OID_RESOURCES_IP, ip_ext), critical=True)
|
||||
if as_ext is not None:
|
||||
builder = builder.add_extension(x509.UnrecognizedExtension(OID_RESOURCES_AS, as_ext), critical=True)
|
||||
return builder.sign(signing_key, hashes.SHA256())
|
||||
|
||||
|
||||
def make_crl(subject: x509.Name, key: rsa.RSAPrivateKey, number: int) -> x509.CertificateRevocationList:
|
||||
return (
|
||||
x509.CertificateRevocationListBuilder()
|
||||
.issuer_name(subject)
|
||||
.last_update(VALID_FROM + timedelta(days=30))
|
||||
.next_update(VALID_TO - timedelta(days=30))
|
||||
.add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(key.public_key()), critical=False)
|
||||
.add_extension(x509.CRLNumber(number), critical=False)
|
||||
.sign(key, hashes.SHA256())
|
||||
)
|
||||
|
||||
|
||||
def roa_content(prefix: ipaddress._BaseNetwork, max_length: int) -> bytes:
|
||||
length = (prefix.prefixlen + 7) // 8
|
||||
raw = prefix.network_address.packed[:length]
|
||||
address = seq(bit_string(raw, length * 8 - prefix.prefixlen), integer(max_length))
|
||||
family = seq(octet((1 if prefix.version == 4 else 2).to_bytes(2, "big")), seq(address))
|
||||
return seq(integer(TEST_ASN), seq(family))
|
||||
|
||||
|
||||
def manifest_content(number: int, files: list[tuple[str, bytes]]) -> bytes:
|
||||
entries = [
|
||||
seq(tlv(0x16, name.encode("ascii")), bit_string(hashlib.sha256(content).digest()))
|
||||
for name, content in files
|
||||
]
|
||||
return seq(
|
||||
integer(number),
|
||||
generalized_time(VALID_FROM + timedelta(days=30)),
|
||||
generalized_time(VALID_TO - timedelta(days=30)),
|
||||
oid(OID_SHA256),
|
||||
seq(*entries),
|
||||
)
|
||||
|
||||
|
||||
def write_cms(content: bytes, content_type: str, signer_cert: x509.Certificate, signer_key: rsa.RSAPrivateKey, output: Path, work: Path) -> None:
|
||||
content_path = work / f"{output.name}.content.der"
|
||||
signer_path = work / f"{output.name}.signer.pem"
|
||||
key_path = work / f"{output.name}.key.pem"
|
||||
write(content_path, content)
|
||||
write(signer_path, pem_cert(signer_cert))
|
||||
write_key(key_path, signer_key)
|
||||
subprocess.run(
|
||||
[
|
||||
"openssl", "cms", "-sign", "-binary", "-in", str(content_path),
|
||||
"-signer", str(signer_path), "-inkey", str(key_path),
|
||||
"-outform", "DER", "-nodetach", "-nosmimecap", "-keyid", "-md", "sha256",
|
||||
"-econtent_type", content_type, "-out", str(output),
|
||||
],
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
|
||||
|
||||
def make_roa(
|
||||
name: str,
|
||||
prefix,
|
||||
max_length: int,
|
||||
not_before: datetime,
|
||||
not_after: datetime,
|
||||
child_name,
|
||||
child_key,
|
||||
common: Path,
|
||||
work: Path,
|
||||
certificate_prefix=None,
|
||||
) -> None:
|
||||
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
cert = sign_cert(
|
||||
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, f"#142 EE {name}")]),
|
||||
child_name,
|
||||
key.public_key(),
|
||||
child_key,
|
||||
x509.random_serial_number(),
|
||||
False,
|
||||
key,
|
||||
child_key,
|
||||
ip_ext=ip_resources([certificate_prefix or prefix]),
|
||||
signed_object_uri=f"{CHILD_REPO_URI}{name}",
|
||||
crl_uri=f"{CHILD_REPO_URI}child.crl",
|
||||
ca_issuer_uri=f"{CHILD_REPO_URI}child.cer",
|
||||
not_before=not_before,
|
||||
not_after=not_after,
|
||||
)
|
||||
write_cms(roa_content(prefix, max_length), OID_ROA_ECONTENT, cert, key, common / name, work)
|
||||
|
||||
|
||||
def make_manifest(name: str, number: int, files: list[tuple[str, bytes]], child_name, child_key, common: Path, work: Path) -> bytes:
|
||||
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
cert = sign_cert(
|
||||
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, f"#142 Manifest EE {name}")]),
|
||||
child_name,
|
||||
key.public_key(),
|
||||
child_key,
|
||||
x509.random_serial_number(),
|
||||
False,
|
||||
key,
|
||||
child_key,
|
||||
ip_ext=ip_inherit(),
|
||||
as_ext=as_resources(None),
|
||||
signed_object_uri=CHILD_MFT_URI if name != "root" else ROOT_MFT_URI,
|
||||
crl_uri=f"{CHILD_REPO_URI}child.crl" if name != "root" else f"{ROOT_REPO_URI}root.crl",
|
||||
ca_issuer_uri=f"{CHILD_REPO_URI}child.cer" if name != "root" else f"{ROOT_REPO_URI}ta.cer",
|
||||
)
|
||||
output = common / f"{name}.mft"
|
||||
write_cms(manifest_content(number, files), OID_MANIFEST_ECONTENT, cert, key, output, work)
|
||||
return output.read_bytes()
|
||||
|
||||
|
||||
def publish(uri: str, path: Path) -> str:
|
||||
encoded = base64.b64encode(path.read_bytes()).decode("ascii")
|
||||
return f' <publish uri="{uri}">{encoded}</publish>\n'
|
||||
|
||||
|
||||
def snapshot(case_root: Path, names: list[str], serial: int, extra_root_names: list[str] | None = None) -> bytes:
|
||||
repository = case_root / "repository"
|
||||
objects = [
|
||||
(f"{ROOT_REPO_URI}root.crl", repository / "root/root.crl"),
|
||||
(f"{ROOT_REPO_URI}child.cer", repository / "root/child.cer"),
|
||||
*[
|
||||
(f"{ROOT_REPO_URI}{name}", repository / f"root/{name}")
|
||||
for name in extra_root_names or []
|
||||
],
|
||||
(f"{CHILD_REPO_URI}child.crl", repository / "child/child.crl"),
|
||||
*[(f"{CHILD_REPO_URI}{name}", repository / f"child/{name}") for name in names],
|
||||
(ROOT_MFT_URI, repository / "root/root.mft"),
|
||||
(CHILD_MFT_URI, repository / "child/child.mft"),
|
||||
]
|
||||
body = "".join(publish(uri, path) for uri, path in objects)
|
||||
return (
|
||||
f'<snapshot xmlns="http://www.ripe.net/rpki/rrdp" session_id="{SESSION_ID}" serial="{serial}">\n'
|
||||
f"{body}</snapshot>\n"
|
||||
).encode()
|
||||
|
||||
|
||||
def notification(serial: int, snap: bytes, delta: bytes | None) -> bytes:
|
||||
lines = [
|
||||
f'<notification xmlns="http://www.ripe.net/rpki/rrdp" version="1" session_id="{SESSION_ID}" serial="{serial}">',
|
||||
f' <snapshot uri="https://{RRDP_HOST}:{RRDP_PORT}/rrdp/snapshot.xml" hash="{hashlib.sha256(snap).hexdigest()}"/>',
|
||||
]
|
||||
if delta is not None:
|
||||
lines.append(
|
||||
f' <delta serial="{serial}" uri="https://{RRDP_HOST}:{RRDP_PORT}/rrdp/delta-{serial}.xml" hash="{hashlib.sha256(delta).hexdigest()}"/>'
|
||||
)
|
||||
return ("\n".join(lines) + "\n</notification>\n").encode()
|
||||
|
||||
|
||||
def corrupt_first_hash(xml: bytes) -> bytes:
|
||||
marker = b'hash="'
|
||||
start = xml.index(marker) + len(marker)
|
||||
end = xml.index(b'"', start)
|
||||
value = bytearray(xml[start:end])
|
||||
value[0] = ord("0") if value[0] != ord("0") else ord("1")
|
||||
return xml[:start] + bytes(value) + xml[end:]
|
||||
|
||||
|
||||
def delta(add_name: str, add_path: Path, new_mft: Path) -> bytes:
|
||||
body = publish(CHILD_MFT_URI, new_mft)
|
||||
body += publish(f"{CHILD_REPO_URI}{add_name}", add_path)
|
||||
return f'<delta xmlns="http://www.ripe.net/rpki/rrdp" session_id="{SESSION_ID}" serial="2">\n{body}</delta>\n'.encode()
|
||||
|
||||
|
||||
def build_case(
|
||||
output: Path,
|
||||
common: Path,
|
||||
tal: bytes,
|
||||
ta: bytes,
|
||||
name: str,
|
||||
number: int,
|
||||
names: list[str],
|
||||
child_mft: bytes,
|
||||
change: bytes | None = None,
|
||||
root_mft: bytes | None = None,
|
||||
extra_root_objects: list[tuple[str, bytes]] | None = None,
|
||||
notification_hash_mismatch: bool = False,
|
||||
) -> None:
|
||||
case_root = output / "cases" / name
|
||||
repo = case_root / "repository"
|
||||
(repo / "root").mkdir(parents=True, exist_ok=True)
|
||||
(repo / "child").mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(common / "ta.cer", repo / "ta.cer")
|
||||
for item in ("root.crl", "child.cer"):
|
||||
shutil.copy2(common / item, repo / "root" / item)
|
||||
(repo / "root/root.mft").write_bytes(root_mft if root_mft is not None else (common / "root.mft").read_bytes())
|
||||
for item, content in extra_root_objects or []:
|
||||
(repo / "root" / item).write_bytes(content)
|
||||
for item in (
|
||||
"child.crl", "valid.roa", "valid2.roa", "expired.roa", "max-length.roa",
|
||||
"max-length-invalid.roa", "roa-prefix-outside.roa", "out-of-resource.roa", "bad-format.roa",
|
||||
):
|
||||
if (common / item).exists():
|
||||
shutil.copy2(common / item, repo / "child" / item)
|
||||
(repo / "child/child.mft").write_bytes(child_mft)
|
||||
snap = snapshot(case_root, names, number, [item for item, _ in extra_root_objects or []])
|
||||
(case_root / "http/rrdp").mkdir(parents=True, exist_ok=True)
|
||||
(case_root / "http/tal").mkdir(parents=True, exist_ok=True)
|
||||
(case_root / "http/ta").mkdir(parents=True, exist_ok=True)
|
||||
(case_root / "http/rrdp/snapshot.xml").write_bytes(snap)
|
||||
notification_xml = notification(number, snap, change)
|
||||
if notification_hash_mismatch:
|
||||
notification_xml = corrupt_first_hash(notification_xml)
|
||||
(case_root / "http/rrdp/notification.xml").write_bytes(notification_xml)
|
||||
if change is not None:
|
||||
(case_root / "http/rrdp/delta-2.xml").write_bytes(change)
|
||||
(case_root / "http/tal/custom.tal").write_bytes(tal)
|
||||
(case_root / "http/ta/custom-ta.cer").write_bytes(ta)
|
||||
(case_root / "CASE-MANIFEST.txt").write_text(
|
||||
f"case={name}\nserial={number}\nobjects={','.join(names)}\nsnapshot_sha256={hashlib.sha256(snap).hexdigest()}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def make_https_certificates(output: Path) -> None:
|
||||
ca_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
ca_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "#142 fixed RRDP test CA")])
|
||||
ca_cert = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(ca_name).issuer_name(ca_name).public_key(ca_key.public_key())
|
||||
.serial_number(x509.random_serial_number()).not_valid_before(VALID_FROM).not_valid_after(VALID_TO)
|
||||
.add_extension(x509.BasicConstraints(ca=True, path_length=1), critical=True)
|
||||
.add_extension(x509.KeyUsage(False, False, False, False, False, True, True, False, False), critical=True)
|
||||
.sign(ca_key, hashes.SHA256())
|
||||
)
|
||||
server_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
server_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, RRDP_HOST)])
|
||||
try:
|
||||
server_host = x509.IPAddress(ipaddress.ip_address(RRDP_HOST))
|
||||
except ValueError:
|
||||
server_host = x509.DNSName(RRDP_HOST)
|
||||
san_entries = [server_host]
|
||||
if RRDP_HOST != "localhost":
|
||||
san_entries.append(x509.DNSName("localhost"))
|
||||
server_cert = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(server_name).issuer_name(ca_cert.subject).public_key(server_key.public_key())
|
||||
.serial_number(x509.random_serial_number()).not_valid_before(VALID_FROM).not_valid_after(VALID_TO)
|
||||
.add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
|
||||
.add_extension(x509.KeyUsage(True, False, False, False, False, False, False, False, False), critical=True)
|
||||
.add_extension(x509.SubjectAlternativeName(san_entries), critical=False)
|
||||
.sign(ca_key, hashes.SHA256())
|
||||
)
|
||||
write(output / "certs/rrdp-ca.pem", pem_cert(ca_cert))
|
||||
write_key(output / "certs/rrdp-ca.key", ca_key)
|
||||
write(output / "certs/rrdp-server.pem", pem_cert(server_cert))
|
||||
write_key(output / "certs/rrdp-server.key", server_key)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
global RRDP_HOST, TAL_URI, TAL_HTTPS_URI, RRDP_URI
|
||||
global ROOT_REPO_URI, ROOT_MFT_URI, CHILD_REPO_URI, CHILD_MFT_URI
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument(
|
||||
"--host",
|
||||
default=RRDP_HOST,
|
||||
help="host embedded in TAL, certificate SIA and RRDP data (default: host.docker.internal)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
RRDP_HOST = args.host
|
||||
TAL_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/ta.cer"
|
||||
TAL_HTTPS_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/ta/custom-ta.cer"
|
||||
RRDP_URI = f"https://{RRDP_HOST}:{RRDP_PORT}/rrdp/notification.xml"
|
||||
ROOT_REPO_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/root/"
|
||||
ROOT_MFT_URI = f"{ROOT_REPO_URI}root.mft"
|
||||
CHILD_REPO_URI = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/child/"
|
||||
CHILD_MFT_URI = f"{CHILD_REPO_URI}child.mft"
|
||||
output = args.output.resolve()
|
||||
if output.exists():
|
||||
shutil.rmtree(output)
|
||||
for directory in ("tal", "ta", "certs", "services", "keys"):
|
||||
(output / directory).mkdir(parents=True)
|
||||
|
||||
root_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
child_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
root_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "#142 Custom RPKI Root")])
|
||||
child_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "#142 Custom RPKI Child")])
|
||||
root_cert = sign_cert(
|
||||
root_name, root_name, root_key.public_key(), root_key, 0x14200001, True, root_key,
|
||||
ip_ext=ip_resources([CHILD_PREFIX]),
|
||||
as_ext=as_resources([TEST_ASN]), ca_repository=ROOT_REPO_URI, manifest_uri=ROOT_MFT_URI,
|
||||
notify_uri=RRDP_URI,
|
||||
)
|
||||
child_cert = sign_cert(
|
||||
child_name, root_name, child_key.public_key(), root_key, 0x14200002, True, child_key, root_key,
|
||||
ip_ext=ip_resources([CHILD_PREFIX]), as_ext=as_resources([TEST_ASN]),
|
||||
ca_repository=CHILD_REPO_URI, manifest_uri=CHILD_MFT_URI, notify_uri=RRDP_URI,
|
||||
crl_uri=f"{ROOT_REPO_URI}root.crl", ca_issuer_uri=f"{ROOT_REPO_URI}child.cer",
|
||||
)
|
||||
bad_child_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "#142 Custom RPKI Overclaiming Child")])
|
||||
bad_child_repo_uri = f"rsync://{RRDP_HOST}:{RSYNC_PORT}/custom/bad-child/"
|
||||
bad_child_cert = sign_cert(
|
||||
bad_child_name, root_name, child_key.public_key(), root_key, 0x14200003, True, child_key, root_key,
|
||||
ip_ext=ip_resources([OUTSIDE_PREFIX]), as_ext=as_resources([TEST_ASN]),
|
||||
ca_repository=bad_child_repo_uri, manifest_uri=f"{bad_child_repo_uri}bad-child.mft", notify_uri=RRDP_URI,
|
||||
crl_uri=f"{ROOT_REPO_URI}root.crl", ca_issuer_uri=f"{ROOT_REPO_URI}child.cer",
|
||||
)
|
||||
root_crl = make_crl(root_name, root_key, 1)
|
||||
child_crl = make_crl(child_name, child_key, 1)
|
||||
tal = (
|
||||
f"{TAL_URI}\n{TAL_HTTPS_URI}\n\n"
|
||||
+ base64.b64encode(root_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)).decode()
|
||||
+ "\n"
|
||||
).encode()
|
||||
write(output / "tal/custom.tal", tal)
|
||||
write(output / "ta/custom-ta.cer", root_cert.public_bytes(Encoding.DER))
|
||||
with tempfile.TemporaryDirectory(prefix="custom-rpki-cms-") as temp:
|
||||
work = Path(temp)
|
||||
common = output / ".common"
|
||||
common.mkdir()
|
||||
write(common / "ta.cer", root_cert.public_bytes(Encoding.DER))
|
||||
write(common / "child.cer", child_cert.public_bytes(Encoding.DER))
|
||||
write(common / "bad-child.cer", bad_child_cert.public_bytes(Encoding.DER))
|
||||
write(common / "root.crl", root_crl.public_bytes(Encoding.DER))
|
||||
write(common / "child.crl", child_crl.public_bytes(Encoding.DER))
|
||||
for name, prefix, max_length, before, after, certificate_prefix in (
|
||||
("valid.roa", CHILD_PREFIX, 24, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None),
|
||||
("valid2.roa", SECOND_PREFIX, 25, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None),
|
||||
("expired.roa", CHILD_PREFIX, 24, VALID_FROM - timedelta(days=30), VALID_FROM - timedelta(days=1), None),
|
||||
("max-length.roa", CHILD_PREFIX, 25, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None),
|
||||
("max-length-invalid.roa", CHILD_PREFIX, 33, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None),
|
||||
("roa-prefix-outside.roa", OUTSIDE_PREFIX, 24, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), CHILD_PREFIX),
|
||||
("out-of-resource.roa", OUTSIDE_PREFIX, 24, VALID_FROM + timedelta(days=31), VALID_TO - timedelta(days=31), None),
|
||||
):
|
||||
make_roa(
|
||||
name, prefix, max_length, before, after, child_name, child_key, common, work,
|
||||
certificate_prefix=certificate_prefix,
|
||||
)
|
||||
bad = bytearray((common / "valid.roa").read_bytes())
|
||||
bad[-1] ^= 1
|
||||
write(common / "bad-format.roa", bytes(bad))
|
||||
|
||||
root_mft = make_manifest(
|
||||
"root", 1, [
|
||||
("root.crl", (common / "root.crl").read_bytes()),
|
||||
("child.cer", (common / "child.cer").read_bytes()),
|
||||
], root_name, root_key, common, work,
|
||||
)
|
||||
write(common / "root.mft", root_mft)
|
||||
bad_root_mft = make_manifest(
|
||||
"root", 1, [
|
||||
("root.crl", (common / "root.crl").read_bytes()),
|
||||
("child.cer", (common / "child.cer").read_bytes()),
|
||||
("bad-child.cer", (common / "bad-child.cer").read_bytes()),
|
||||
], root_name, root_key, common, work,
|
||||
)
|
||||
write(common / "root.mft", root_mft)
|
||||
case_specs = (
|
||||
("baseline-v1", 1, ["child.crl", "valid.roa"]),
|
||||
("sync-hash-mismatch", 1, ["child.crl", "valid.roa"]),
|
||||
("baseline-v2", 2, ["child.crl", "valid.roa", "valid2.roa"]),
|
||||
("validation-expired", 1, ["child.crl", "expired.roa"]),
|
||||
("validation-max-length", 1, ["child.crl", "max-length.roa"]),
|
||||
("validation-max-length-invalid", 1, ["child.crl", "max-length-invalid.roa"]),
|
||||
("validation-roa-prefix-outside", 1, ["child.crl", "roa-prefix-outside.roa"]),
|
||||
("validation-out-of-resource", 1, ["child.crl", "out-of-resource.roa"]),
|
||||
("validation-ca-over-resource", 1, ["child.crl", "valid.roa"]),
|
||||
("validation-nonstandard", 1, ["child.crl", "bad-format.roa"]),
|
||||
)
|
||||
child_mfts = {}
|
||||
for name, number, names in case_specs:
|
||||
child_mfts[name] = make_manifest(
|
||||
name, number, [(item, (common / item).read_bytes()) for item in names],
|
||||
child_name, child_key, common, work,
|
||||
)
|
||||
for name, number, names in case_specs:
|
||||
change = None
|
||||
if name == "baseline-v2":
|
||||
change = delta("valid2.roa", common / "valid2.roa", common / "baseline-v2.mft")
|
||||
build_case(
|
||||
output, common, tal, root_cert.public_bytes(Encoding.DER), name, number, names,
|
||||
child_mfts[name], change,
|
||||
root_mft=bad_root_mft if name == "validation-ca-over-resource" else None,
|
||||
extra_root_objects=[("bad-child.cer", (common / "bad-child.cer").read_bytes())]
|
||||
if name == "validation-ca-over-resource" else None,
|
||||
notification_hash_mismatch=name == "sync-hash-mismatch",
|
||||
)
|
||||
write_key(output / "keys/root.key", root_key)
|
||||
write_key(output / "keys/child.key", child_key)
|
||||
make_https_certificates(output)
|
||||
shutil.rmtree(output / ".common")
|
||||
(output / "FIXTURE-MANIFEST.txt").write_text(
|
||||
"fixture_schema_version=1\nfixture_kind=custom-tal-fixed-local-rpki\n"
|
||||
f"tal_uri={TAL_URI}\nrrdp_uri={RRDP_URI}\nrsync_module=custom\n"
|
||||
f"rrdp_port={RRDP_PORT}\nrsync_port={RSYNC_PORT}\n"
|
||||
"cases=baseline-v1,sync-hash-mismatch,baseline-v2,validation-expired,validation-max-length,validation-max-length-invalid,"
|
||||
"validation-roa-prefix-outside,validation-out-of-resource,validation-ca-over-resource,validation-nonstandard\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
print(f"generated custom fixture: {output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Serve a fixture directory over HTTPS with request logging."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import http.server
|
||||
import ssl
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class RequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def log_message(self, format: str, *args) -> None:
|
||||
print("%s %s" % (self.log_date_time_string(), format % args), flush=True)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--directory", type=Path, required=True)
|
||||
parser.add_argument("--certfile", type=Path, required=True)
|
||||
parser.add_argument("--keyfile", type=Path, required=True)
|
||||
parser.add_argument("--host", default="0.0.0.0")
|
||||
parser.add_argument("--port", type=int, default=18443)
|
||||
args = parser.parse_args()
|
||||
handler = lambda *handler_args, directory=str(args.directory): RequestHandler(
|
||||
*handler_args, directory=directory
|
||||
)
|
||||
server = http.server.ThreadingHTTPServer((args.host, args.port), handler)
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
context.load_cert_chain(certfile=args.certfile, keyfile=args.keyfile)
|
||||
server.socket = context.wrap_socket(server.socket, server_side=True)
|
||||
print(f"https fixture server listening on {args.host}:{args.port} root={args.directory}", flush=True)
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$BASH_SOURCE")" && pwd)"
|
||||
FIXTURE_ROOT=""
|
||||
CASE_NAME="baseline-v1"
|
||||
RRDP_PORT="$(printenv RRDP_PORT 2>/dev/null || true)"
|
||||
RSYNC_PORT="$(printenv RSYNC_PORT 2>/dev/null || true)"
|
||||
PID_ROOT=""
|
||||
|
||||
[[ -n "$RRDP_PORT" ]] || RRDP_PORT=18443
|
||||
[[ -n "$RSYNC_PORT" ]] || RSYNC_PORT=1873
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage:
|
||||
./start_fixed_services.sh --fixture-root <generated-fixtures> [--case NAME]
|
||||
|
||||
Cases: baseline-v1, sync-hash-mismatch, baseline-v2, validation-expired,
|
||||
validation-max-length, validation-max-length-invalid,
|
||||
validation-roa-prefix-outside, validation-out-of-resource,
|
||||
validation-ca-over-resource, validation-nonstandard
|
||||
Environment: RRDP_PORT (default 18443), RSYNC_PORT (default 1873)
|
||||
USAGE
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "error: $*" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--fixture-root) FIXTURE_ROOT="$2"; shift 2 ;;
|
||||
--case) CASE_NAME="$2"; shift 2 ;;
|
||||
--pid-root) PID_ROOT="$2"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) die "unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$FIXTURE_ROOT" ]] || die "--fixture-root is required"
|
||||
FIXTURE_ROOT="$(cd "$FIXTURE_ROOT" 2>/dev/null && pwd)" || die "fixture root not found"
|
||||
CASE_ROOT="$FIXTURE_ROOT/cases/$CASE_NAME"
|
||||
[[ -d "$CASE_ROOT/repository" && -d "$CASE_ROOT/http" ]] || die "fixture case not found: $CASE_NAME"
|
||||
if [[ -z "$PID_ROOT" ]]; then
|
||||
PID_ROOT="$FIXTURE_ROOT/services/$CASE_NAME"
|
||||
fi
|
||||
mkdir -p "$PID_ROOT"
|
||||
[[ ! -f "$PID_ROOT/pids.env" ]] || die "services already appear to be running: $PID_ROOT/pids.env"
|
||||
rm -f "$PID_ROOT/rsyncd.pid" "$PID_ROOT/rsyncd.lock"
|
||||
|
||||
RSYNCD_CONF="$PID_ROOT/rsyncd.conf"
|
||||
RSYNC_UID="$(id -un)"
|
||||
RSYNC_GID="$(id -gn)"
|
||||
cat > "$RSYNCD_CONF" <<EOF
|
||||
uid = $RSYNC_UID
|
||||
gid = $RSYNC_GID
|
||||
use chroot = no
|
||||
read only = yes
|
||||
port = $RSYNC_PORT
|
||||
pid file = $PID_ROOT/rsyncd.pid
|
||||
lock file = $PID_ROOT/rsyncd.lock
|
||||
log file = $PID_ROOT/rsyncd.log
|
||||
|
||||
[custom]
|
||||
path = $CASE_ROOT/repository
|
||||
comment = #142 fixed custom RPKI repository
|
||||
read only = yes
|
||||
EOF
|
||||
|
||||
rsync --daemon --no-detach --config="$RSYNCD_CONF" >"$PID_ROOT/rsyncd.stdout.log" 2>&1 &
|
||||
RSYNC_PID=$!
|
||||
python3 "$SCRIPT_DIR/serve_https.py" \
|
||||
--directory "$CASE_ROOT/http" \
|
||||
--certfile "$FIXTURE_ROOT/certs/rrdp-server.pem" \
|
||||
--keyfile "$FIXTURE_ROOT/certs/rrdp-server.key" \
|
||||
--port "$RRDP_PORT" \
|
||||
>"$PID_ROOT/https.log" 2>&1 &
|
||||
HTTPS_PID=$!
|
||||
|
||||
printf 'case=%s\nrrdp_pid=%s\nrsync_pid=%s\nrrdp_port=%s\nrsync_port=%s\n' \
|
||||
"$CASE_NAME" "$HTTPS_PID" "$RSYNC_PID" "$RRDP_PORT" "$RSYNC_PORT" > "$PID_ROOT/pids.env"
|
||||
sleep 1
|
||||
kill -0 "$HTTPS_PID" 2>/dev/null || die "HTTPS fixture server exited; see $PID_ROOT/https.log"
|
||||
kill -0 "$RSYNC_PID" 2>/dev/null || die "rsync fixture server exited; see $PID_ROOT/rsyncd.stdout.log"
|
||||
echo "fixed RRDP/rsync services started case=$CASE_NAME rrdp=$RRDP_PORT rsync=$RSYNC_PORT pid_root=$PID_ROOT"
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PID_ROOT="$1"
|
||||
[[ -n "$PID_ROOT" ]] || { echo "Usage: $0 <pid-root>" >&2; exit 2; }
|
||||
[[ -f "$PID_ROOT/pids.env" ]] || { echo "services are not running: $PID_ROOT" >&2; exit 2; }
|
||||
# shellcheck disable=SC1090
|
||||
source "$PID_ROOT/pids.env"
|
||||
service_pids=("${rrdp_pid:-}" "${rsync_pid:-}")
|
||||
for pid in "${service_pids[@]}"; do
|
||||
if [[ "$pid" =~ ^[0-9]+$ ]] && kill -0 "$pid" 2>/dev/null; then
|
||||
kill "$pid" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
for pid in "${service_pids[@]}"; do
|
||||
[[ "$pid" =~ ^[0-9]+$ ]] || continue
|
||||
for _ in $(seq 1 50); do
|
||||
kill -0 "$pid" 2>/dev/null || break
|
||||
sleep 0.1
|
||||
done
|
||||
done
|
||||
rm -f "$PID_ROOT/pids.env"
|
||||
echo "fixed RRDP/rsync services stopped: $PID_ROOT"
|
||||
194
deploy/docker-installer/docs/README.en.md
Normal file
194
deploy/docker-installer/docs/README.en.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# ours RP Docker Installer Guide (`__PACKAGE_ARCH__` / `__PACKAGE_PLATFORM__`)
|
||||
|
||||
## Goal
|
||||
|
||||
This package deploys ours RP on Linux servers whose host architecture matches the packaged installer metadata, using Docker Compose and continuous all-five RIR validation.
|
||||
|
||||
The package includes four architecture-matched images: ours RP runtime, artifact metrics, Prometheus, and Grafana. Deployment does not need to pull application images on the target host. Runtime state, run artifacts, logs, Prometheus data and Grafana data are persisted through host bind mounts.
|
||||
|
||||
Every installer build rebuilds the ours RP runtime and artifact metrics images. The deliverable name is `ours-rp-installer-{arch}-{git8}-{YYYYMMDDTHHMMSSZ}.tar.gz`, and both ours RP image tags use the current source commit. `PACKAGE-MANIFEST.env` records the full commit, UTC build time, dirty state, and image archive hashes; `./scripts/status.sh --brief` displays that provenance after installation.
|
||||
|
||||
By default the host architecture must match `PACKAGE_ARCH` from `PACKAGE-MANIFEST.env`. If you intentionally want to run this package on a different host architecture through QEMU/binfmt, set:
|
||||
|
||||
```bash
|
||||
ALLOW_CROSS_ARCH=1
|
||||
```
|
||||
|
||||
## Compose Template Source
|
||||
|
||||
`compose/` is the single production Docker Compose template source. The Docker installer build script copies this directory directly. Do not maintain a second copy of the Compose, Prometheus, or Grafana dashboard files elsewhere.
|
||||
|
||||
The legacy direct Compose deployment entry point has been removed. For either amd64 or arm64, build the matching installer package with `scripts/docker/build_docker_installer_package.sh`, then use `scripts/install.sh`, `scripts/start.sh`, and `scripts/status.sh` as described here.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
tar -xzf ours-rp-installer-__PACKAGE_ARCH__-*.tar.gz
|
||||
cd ours-rp-installer-__PACKAGE_ARCH__-*
|
||||
|
||||
./scripts/install.sh
|
||||
cp .env.example .env # install.sh creates .env automatically if missing
|
||||
vim .env
|
||||
./scripts/start.sh
|
||||
./scripts/status.sh
|
||||
```
|
||||
|
||||
Defaults:
|
||||
|
||||
- `PACKAGE_ARCH=__PACKAGE_ARCH__`
|
||||
- `PACKAGE_PLATFORM=__PACKAGE_PLATFORM__`
|
||||
- `RIRS=afrinic,apnic,arin,lacnic,ripe`
|
||||
- `MAX_RUNS=-1`
|
||||
- `INTERVAL_SECS=600`
|
||||
- `TAL_INPUT_MODE=file-live-ta`
|
||||
- `RESOURCE_VALIDATION_MODE=validation-update-03`
|
||||
- `LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1`
|
||||
- `PERIODIC_SNAPSHOT_RESET=0`
|
||||
- `PERIODIC_SNAPSHOT_MAX_DELTAS=100`
|
||||
- `HOST_DATA_DIR=__HOST_DATA_DIR__`
|
||||
- `SOAK_RESTART_POLICY=unless-stopped`
|
||||
- `RPKI_IMAGE=__RUNTIME_IMAGE__`
|
||||
- `METRICS_IMAGE=__METRICS_IMAGE__`
|
||||
- `METRICS_PLATFORM=__PACKAGE_PLATFORM__`
|
||||
- `MONITOR_PLATFORM=__PACKAGE_PLATFORM__`
|
||||
- `ALLOW_CROSS_ARCH=0`
|
||||
- `RTR_REPORT_DIR=__HOST_DATA_DIR__/empty-rtr-report`, which mounts an empty fallback directory by default; after deploying a separate RTR service, register its same-host report directory through `scripts/register_rtr_monitor.sh`
|
||||
|
||||
## First Start Semantics
|
||||
|
||||
If there is no successful run under `HOST_DATA_DIR/runs`, `start.sh` starts the core `ours-rp-soak` service first and waits for the first snapshot to succeed before starting metrics, Prometheus and Grafana.
|
||||
|
||||
Container/image mapping:
|
||||
|
||||
- `ours-rp-soak` uses `RPKI_IMAGE`
|
||||
- `artifact-metrics` uses `METRICS_IMAGE`
|
||||
- `prometheus` / `grafana` use their monitor images
|
||||
|
||||
The first snapshot refreshes live TA certificates before starting the RP process.
|
||||
|
||||
## Resource Validation Mode
|
||||
|
||||
New knob:
|
||||
|
||||
```bash
|
||||
RESOURCE_VALIDATION_MODE=validation-update-03
|
||||
```
|
||||
|
||||
Supported values:
|
||||
|
||||
- `validation-update-03`: default behavior, using the current validation-update draft semantics;
|
||||
- `rfc6487`: switch back to the original RFC 6487 resource containment behavior.
|
||||
|
||||
The installer / soak runner passes `--resource-validation-mode` to every `rpki` child start.
|
||||
When an upgraded deployment reuses an older `.env` that does not contain this key, the installer appends the new default `validation-update-03` automatically.
|
||||
|
||||
## Architecture Guardrails
|
||||
|
||||
The key scripts read:
|
||||
|
||||
- `PACKAGE_ARCH` / `PACKAGE_PLATFORM` from `.env`
|
||||
- `PACKAGE-MANIFEST.env`
|
||||
- the current host `uname -m`
|
||||
|
||||
Default behavior:
|
||||
|
||||
1. matching host and package architectures: run natively;
|
||||
2. mismatched host and package architectures: fail with an explicit error;
|
||||
3. only when `ALLOW_CROSS_ARCH=1` is set do the scripts attempt to enable matching `binfmt/qemu`.
|
||||
|
||||
That means an `arm64` package on an `x86_64` host does not silently switch to emulation by default.
|
||||
|
||||
## Ports
|
||||
|
||||
Default ports:
|
||||
|
||||
- metrics: `http://<host>:9556/metrics`
|
||||
- Prometheus: `http://<host>:9090`
|
||||
- Grafana: `http://<host>:3000`
|
||||
|
||||
Grafana credentials come from `.env`:
|
||||
|
||||
```bash
|
||||
GRAFANA_ADMIN_USER=admin
|
||||
GRAFANA_ADMIN_PASSWORD=admin
|
||||
```
|
||||
|
||||
Change the password and restrict public access for production deployments.
|
||||
|
||||
## Optional RTR report metrics input
|
||||
|
||||
If a separately deployed RTR service on the same host continuously writes `rtr-source-*`, `rtr-runtime-*`, and `rtr-clients-*` JSON reports, run:
|
||||
|
||||
```bash
|
||||
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report
|
||||
```
|
||||
|
||||
The command accepts only an absolute path on the same host, validates the candidate Compose mount, and atomically updates `.env`. It recreates only `artifact-metrics`, Prometheus, and Grafana; it does not restart or recreate `ours-rp-soak`. The `artifact-metrics` container mounts the directory read-only and reads it through `RPKI_METRICS_RTR_REPORT_DIR`, producing `ours_rp_rtr_*` metrics. By default, `RTR_REPORT_DIR` points at an empty fallback directory under the installer data root, so the metrics container also starts cleanly without an RTR service.
|
||||
|
||||
## Data Directory
|
||||
|
||||
Default host directory:
|
||||
|
||||
```text
|
||||
__HOST_DATA_DIR__/
|
||||
state/
|
||||
runs/
|
||||
logs/
|
||||
tmp/
|
||||
prometheus/
|
||||
grafana/
|
||||
```
|
||||
|
||||
Each `runs/run_XXXX/` directory contains `report.json`, `result.ccr`, `input.cir`, `vrps.csv`, `vaps.csv`, `stage-timing.json`, logs and metadata.
|
||||
|
||||
## Periodic Snapshot Reset
|
||||
|
||||
New knobs:
|
||||
|
||||
```bash
|
||||
PERIODIC_SNAPSHOT_RESET=0
|
||||
PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
||||
```
|
||||
|
||||
Semantics:
|
||||
|
||||
- disabled by default, keeping previous behavior unchanged;
|
||||
- when enabled, one successful snapshot is followed by at most `N` successful delta runs;
|
||||
- after the threshold is reached, the next run is forced to snapshot;
|
||||
- before that forced snapshot, only the active `state/db` is reset, while `runs/`, `logs/`, `state/rsync-mirror`, `.env`, and Prometheus/Grafana data are preserved;
|
||||
- the counter is persisted independently in `HOST_DATA_DIR/state/run-lifecycle-state.json`, so it does not depend on retained `runs/` history;
|
||||
- if that lifecycle file is corrupt, it is backed up as `run-lifecycle-state.json.corrupt.<timestamp>.<pid>` before best-effort bootstrap from retained runs;
|
||||
- after a successful forced snapshot, the old DB staging is deleted so disk usage does not keep growing elsewhere.
|
||||
|
||||
Check the latest `run-meta.json` for:
|
||||
|
||||
- `sync_mode`
|
||||
- `snapshot_reason`
|
||||
- `periodic_snapshot_delta_count`
|
||||
- `periodic_snapshot_forced`
|
||||
- `reset_db_cleanup_status`
|
||||
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
./scripts/status.sh
|
||||
./scripts/logs.sh ours-rp-soak --tail 200
|
||||
./scripts/restart.sh
|
||||
./scripts/stop.sh
|
||||
./scripts/cleanup.sh --keep-runs 100 --execute
|
||||
./scripts/uninstall.sh
|
||||
```
|
||||
|
||||
For finite acceptance tests, for example `MAX_RUNS=3`, also set:
|
||||
|
||||
```bash
|
||||
SOAK_RESTART_POLICY=no
|
||||
```
|
||||
|
||||
Otherwise Compose `unless-stopped` will restart the container after it exits successfully.
|
||||
|
||||
`uninstall.sh` keeps data by default. Use the following only when you really want to delete `HOST_DATA_DIR`:
|
||||
|
||||
```bash
|
||||
./scripts/uninstall.sh --purge-data
|
||||
```
|
||||
196
deploy/docker-installer/docs/README.zh-CN.md
Normal file
196
deploy/docker-installer/docs/README.zh-CN.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# ours RP Docker 安装包使用说明(`__PACKAGE_ARCH__` / `__PACKAGE_PLATFORM__`)
|
||||
|
||||
## 目标
|
||||
|
||||
本安装包用于在目标架构与包元数据匹配的 Linux 服务器上,通过 Docker Compose 部署 ours RP,并持续运行 all5 RIR 同步验证任务。
|
||||
|
||||
安装包内置与包架构一致的四类镜像:ours RP runtime、artifact metrics、Prometheus、Grafana。部署时不需要现场拉取应用镜像。运行产物、状态数据库、日志、Prometheus 和 Grafana 数据均通过宿主机目录挂载保存。
|
||||
|
||||
每次构建安装包都会重新构建 ours RP runtime 与 artifact metrics 镜像。交付包命名为 `ours-rp-installer-{arch}-{git8}-{YYYYMMDDTHHMMSSZ}.tar.gz`,两类 ours RP 镜像分别使用当前源码 commit 的 tag。完整 commit、UTC 构建时间、dirty 状态和镜像归档 hash 保存在 `PACKAGE-MANIFEST.env`,可通过 `./scripts/status.sh --brief` 查看。
|
||||
|
||||
默认情况下,宿主架构必须与 `PACKAGE-MANIFEST.env` 中的 `PACKAGE_ARCH` 匹配;若确需在异构主机上通过 QEMU/binfmt 运行,必须显式设置:
|
||||
|
||||
```bash
|
||||
ALLOW_CROSS_ARCH=1
|
||||
```
|
||||
|
||||
## Compose 模板来源
|
||||
|
||||
`compose/` 是唯一的生产 Docker Compose 模板来源,Docker 安装包构建脚本会直接复制该目录。不要在其他目录维护第二份 compose、Prometheus 或 Grafana dashboard 文件。
|
||||
|
||||
旧的直接 Compose 部署入口已经移除。需要部署 amd64 或 arm64 时,统一先用 `scripts/docker/build_docker_installer_package.sh` 构建对应架构安装包,再按本说明执行 `scripts/install.sh`、`scripts/start.sh` 和 `scripts/status.sh`。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
tar -xzf ours-rp-installer-__PACKAGE_ARCH__-*.tar.gz
|
||||
cd ours-rp-installer-__PACKAGE_ARCH__-*
|
||||
|
||||
./scripts/install.sh
|
||||
cp .env.example .env # 如 install.sh 已自动创建,可直接编辑现有 .env
|
||||
vim .env
|
||||
./scripts/start.sh
|
||||
./scripts/status.sh
|
||||
```
|
||||
|
||||
默认配置:
|
||||
|
||||
- `PACKAGE_ARCH=__PACKAGE_ARCH__`
|
||||
- `PACKAGE_PLATFORM=__PACKAGE_PLATFORM__`
|
||||
- `RIRS=afrinic,apnic,arin,lacnic,ripe`
|
||||
- `MAX_RUNS=-1`
|
||||
- `INTERVAL_SECS=600`
|
||||
- `TAL_INPUT_MODE=file-live-ta`
|
||||
- `RESOURCE_VALIDATION_MODE=validation-update-03`
|
||||
- `LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1`
|
||||
- `PERIODIC_SNAPSHOT_RESET=0`
|
||||
- `PERIODIC_SNAPSHOT_MAX_DELTAS=100`
|
||||
- `HOST_DATA_DIR=__HOST_DATA_DIR__`
|
||||
- `SOAK_RESTART_POLICY=unless-stopped`
|
||||
- `RPKI_IMAGE=__RUNTIME_IMAGE__`
|
||||
- `METRICS_IMAGE=__METRICS_IMAGE__`
|
||||
- `METRICS_PLATFORM=__PACKAGE_PLATFORM__`
|
||||
- `MONITOR_PLATFORM=__PACKAGE_PLATFORM__`
|
||||
- `ALLOW_CROSS_ARCH=0`
|
||||
- `RTR_REPORT_DIR=__HOST_DATA_DIR__/empty-rtr-report`,默认挂载空目录;独立 RTR 服务部署完成后,使用 `scripts/register_rtr_monitor.sh` 注册同机 report 目录
|
||||
|
||||
## 首次启动语义
|
||||
|
||||
如果 `HOST_DATA_DIR/runs` 下没有成功 run,`start.sh` 会先启动核心 `ours-rp-soak`,等待第一轮 snapshot 成功后再启动 metrics、Prometheus 和 Grafana。
|
||||
|
||||
容器镜像分工:
|
||||
|
||||
- `ours-rp-soak` 使用 `RPKI_IMAGE`
|
||||
- `artifact-metrics` 使用 `METRICS_IMAGE`
|
||||
- `prometheus` / `grafana` 使用各自 monitor image
|
||||
|
||||
第一轮 snapshot 会先拉取 live TA,避免 clean state 使用旧 fixture TA。
|
||||
|
||||
## 资源验证模式
|
||||
|
||||
新增配置:
|
||||
|
||||
```bash
|
||||
RESOURCE_VALIDATION_MODE=validation-update-03
|
||||
```
|
||||
|
||||
可选值:
|
||||
|
||||
- `validation-update-03`:默认,按当前 validation update draft 语义运行;
|
||||
- `rfc6487`:切换为 RFC 6487 原始资源包含判定语义。
|
||||
|
||||
installer / soak runner 每次启动 `rpki` 子进程时都会显式传入 `--resource-validation-mode`。
|
||||
如果升级时复用旧 `.env` 且缺少该变量,installer 会自动补入默认值 `validation-update-03`,不会破坏旧 `.env` 复用流程。
|
||||
|
||||
## 架构检查
|
||||
|
||||
关键脚本会读取:
|
||||
|
||||
- `.env` 中的 `PACKAGE_ARCH` / `PACKAGE_PLATFORM`
|
||||
- `PACKAGE-MANIFEST.env`
|
||||
- 当前宿主 `uname -m`
|
||||
|
||||
默认行为:
|
||||
|
||||
1. 宿主与包架构匹配:直接运行;
|
||||
2. 宿主与包架构不匹配:明确报错并停止;
|
||||
3. 仅当 `ALLOW_CROSS_ARCH=1` 时,脚本才会尝试启用对应架构的 `binfmt/qemu`。
|
||||
|
||||
因此在 `x86_64` 主机上运行 `arm64` 包,不会默认静默进入模拟执行。
|
||||
|
||||
## 访问端口
|
||||
|
||||
默认端口:
|
||||
|
||||
- metrics: `http://<host>:9556/metrics`
|
||||
- Prometheus: `http://<host>:9090`
|
||||
- Grafana: `http://<host>:3000`
|
||||
|
||||
Grafana 默认账号密码来自 `.env`:
|
||||
|
||||
```bash
|
||||
GRAFANA_ADMIN_USER=admin
|
||||
GRAFANA_ADMIN_PASSWORD=admin
|
||||
```
|
||||
|
||||
生产部署时应修改密码并限制外部访问。
|
||||
|
||||
## 可选 RTR report 监控接入
|
||||
|
||||
如果同一台机器上已经有独立部署的 RTR 服务,并且它持续输出 `rtr-source-*`、`rtr-runtime-*`、`rtr-clients-*` JSON report,执行:
|
||||
|
||||
```bash
|
||||
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report
|
||||
```
|
||||
|
||||
该命令只接受本机绝对路径,会先校验候选 Compose 挂载,再原子更新 `.env`。随后只重建 `artifact-metrics`、Prometheus 和 Grafana,不重启或重建 `ours-rp-soak`。`artifact-metrics` 以只读方式挂载该目录,并通过 `RPKI_METRICS_RTR_REPORT_DIR` 读取 report,输出 `ours_rp_rtr_*` 指标。默认 `RTR_REPORT_DIR` 指向安装包数据目录下的空 fallback 目录,因此未接入 RTR 服务时 metrics 容器也能稳定启动。
|
||||
|
||||
## 数据目录
|
||||
|
||||
默认宿主机目录:
|
||||
|
||||
```text
|
||||
__HOST_DATA_DIR__/
|
||||
state/
|
||||
runs/
|
||||
logs/
|
||||
tmp/
|
||||
prometheus/
|
||||
grafana/
|
||||
```
|
||||
|
||||
`runs/run_XXXX/` 中包含每轮 `report.json`、`result.ccr`、`input.cir`、`vrps.csv`、`vaps.csv`、`stage-timing.json`、日志和元数据。
|
||||
|
||||
## 定期 snapshot reset
|
||||
|
||||
新增配置:
|
||||
|
||||
```bash
|
||||
PERIODIC_SNAPSHOT_RESET=0
|
||||
PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
||||
```
|
||||
|
||||
语义:
|
||||
|
||||
- 默认关闭,行为与旧版本一致;
|
||||
- 开启后,一次成功 snapshot 后最多连续执行 `N` 个成功 delta;
|
||||
- 达到阈值后,下一轮强制跑 snapshot;
|
||||
- 强制 snapshot 前只重置 active `state/db`,保留 `runs/`、`logs/`、`state/rsync-mirror`、`.env`、Prometheus/Grafana 数据;
|
||||
- 周期计数保存在独立 lifecycle 文件 `HOST_DATA_DIR/state/run-lifecycle-state.json`,不依赖 `runs/` 保留窗口;
|
||||
- lifecycle 文件损坏时会先备份为 `run-lifecycle-state.json.corrupt.<timestamp>.<pid>`,再从当前保留 run 尽力 bootstrap;
|
||||
- 强制 snapshot 成功后旧 DB staging 会被删除,避免磁盘只是换目录继续增长。
|
||||
|
||||
可通过最新 `run-meta.json` 中的以下字段确认:
|
||||
|
||||
- `sync_mode`
|
||||
- `snapshot_reason`
|
||||
- `periodic_snapshot_delta_count`
|
||||
- `periodic_snapshot_forced`
|
||||
- `reset_db_cleanup_status`
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
./scripts/status.sh
|
||||
./scripts/logs.sh ours-rp-soak --tail 200
|
||||
./scripts/restart.sh
|
||||
./scripts/stop.sh
|
||||
./scripts/cleanup.sh --keep-runs 100 --execute
|
||||
./scripts/uninstall.sh
|
||||
```
|
||||
|
||||
如果做有限轮次验收,例如 `MAX_RUNS=3`,建议同时设置:
|
||||
|
||||
```bash
|
||||
SOAK_RESTART_POLICY=no
|
||||
```
|
||||
|
||||
否则 Compose 的 `unless-stopped` 策略会在容器正常退出后再次拉起下一轮。
|
||||
|
||||
`uninstall.sh` 默认不删除数据。只有显式执行:
|
||||
|
||||
```bash
|
||||
./scripts/uninstall.sh --purge-data
|
||||
```
|
||||
|
||||
才会删除 `HOST_DATA_DIR`。
|
||||
137
deploy/docker-installer/docs/operations.en.md
Normal file
137
deploy/docker-installer/docs/operations.en.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# Operations Guide (`__PACKAGE_ARCH__`)
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
./scripts/install.sh
|
||||
```
|
||||
|
||||
The installer is idempotent:
|
||||
|
||||
- existing `.env` is kept;
|
||||
- existing Docker/Compose installation is reused;
|
||||
- repeated loading of packaged runtime, metrics, Prometheus and Grafana images for the package architecture is safe;
|
||||
- existing data directory is reused;
|
||||
- matching host/package architecture is required by default.
|
||||
|
||||
## Start
|
||||
|
||||
```bash
|
||||
./scripts/start.sh
|
||||
```
|
||||
|
||||
Start without waiting for the first snapshot:
|
||||
|
||||
```bash
|
||||
./scripts/start.sh --no-wait-first-run
|
||||
```
|
||||
|
||||
If you intentionally want to run this package on a different host architecture through QEMU/binfmt, first set:
|
||||
|
||||
```bash
|
||||
ALLOW_CROSS_ARCH=1
|
||||
```
|
||||
|
||||
Then rerun `./scripts/install.sh` or `./scripts/self-check.sh`, and only then will the scripts attempt to enable the matching binfmt handler.
|
||||
|
||||
## Stop and Restart
|
||||
|
||||
```bash
|
||||
./scripts/stop.sh
|
||||
./scripts/restart.sh
|
||||
```
|
||||
|
||||
## Status Checks
|
||||
|
||||
```bash
|
||||
./scripts/status.sh
|
||||
./scripts/self-check.sh
|
||||
```
|
||||
|
||||
Important checks:
|
||||
|
||||
- Docker/Compose availability;
|
||||
- runtime, metrics, Prometheus and Grafana images exist;
|
||||
- `HOST_DATA_DIR` is writable;
|
||||
- Compose config is valid;
|
||||
- latest run status;
|
||||
- metrics, Prometheus and Grafana endpoints;
|
||||
- whether `host_arch`, `package_arch`, and `arch_mode` match expectations.
|
||||
|
||||
`status.sh` also prints:
|
||||
|
||||
- `package_arch`
|
||||
- `package_platform`
|
||||
- `runtime_image`
|
||||
- `metrics_image`
|
||||
- `allow_cross_arch`
|
||||
- `periodic_snapshot_reset`
|
||||
- `periodic_snapshot_max_deltas`
|
||||
- `rtr_report_dir`
|
||||
- `rtr_report_container_dir`
|
||||
|
||||
To verify RTR report ingestion:
|
||||
|
||||
```bash
|
||||
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report --dry-run
|
||||
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report
|
||||
./scripts/status.sh
|
||||
curl -s "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" | grep '^ours_rp_rtr_' | head
|
||||
```
|
||||
|
||||
Registration accepts only an absolute directory on the same host. The command retains a timestamped `.env` backup and updates only the metrics, Prometheus, and Grafana sidecars; the `ours-rp-soak` container ID must remain unchanged.
|
||||
|
||||
## Upgrade
|
||||
|
||||
Extract the new package into a new directory and explicitly reuse the existing `.env` through the upgrade script:
|
||||
|
||||
```bash
|
||||
./scripts/upgrade.sh --reuse-env-from /path/to/old-installer/.env
|
||||
```
|
||||
|
||||
If the new package directory already has a `.env`, the upgrade script keeps it.
|
||||
|
||||
If an older `.env` does not contain `METRICS_IMAGE` or `METRICS_PLATFORM`, the install/upgrade flow backfills package-matched defaults automatically.
|
||||
|
||||
Upgrade does not delete:
|
||||
|
||||
- `runs/`
|
||||
- `logs/`
|
||||
- `state/rsync-mirror`
|
||||
- runtime configuration referenced by `.env`
|
||||
- Prometheus / Grafana data
|
||||
|
||||
To validate periodic forced snapshot behavior, temporarily set:
|
||||
|
||||
```bash
|
||||
PERIODIC_SNAPSHOT_RESET=1
|
||||
PERIODIC_SNAPSHOT_MAX_DELTAS=2
|
||||
```
|
||||
|
||||
Then confirm the latest `run-meta.json` contains:
|
||||
|
||||
```bash
|
||||
snapshot_reason=periodic_snapshot_delta_limit
|
||||
```
|
||||
|
||||
And inspect the independent lifecycle state:
|
||||
|
||||
```bash
|
||||
jq '{last_run,last_success_snapshot,successful_deltas_since_snapshot,state_health}' \
|
||||
"${HOST_DATA_DIR}/state/run-lifecycle-state.json"
|
||||
```
|
||||
|
||||
After validation, restore:
|
||||
|
||||
```bash
|
||||
PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
|
||||
```bash
|
||||
./scripts/cleanup.sh --keep-runs 100
|
||||
./scripts/cleanup.sh --keep-runs 100 --execute
|
||||
```
|
||||
|
||||
Cleanup is dry-run by default. Add `--execute` after reviewing the output.
|
||||
137
deploy/docker-installer/docs/operations.zh-CN.md
Normal file
137
deploy/docker-installer/docs/operations.zh-CN.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# 运维手册(`__PACKAGE_ARCH__`)
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
./scripts/install.sh
|
||||
```
|
||||
|
||||
安装脚本是幂等的:
|
||||
|
||||
- 已有 `.env` 不覆盖;
|
||||
- 已安装 Docker/Compose 则跳过;
|
||||
- 包内 runtime、metrics、Prometheus、Grafana 对应架构镜像重复加载是安全的;
|
||||
- 数据目录已存在则复用;
|
||||
- 默认要求宿主与包架构匹配。
|
||||
|
||||
## 启动
|
||||
|
||||
```bash
|
||||
./scripts/start.sh
|
||||
```
|
||||
|
||||
如需后台启动后不等待首轮 snapshot:
|
||||
|
||||
```bash
|
||||
./scripts/start.sh --no-wait-first-run
|
||||
```
|
||||
|
||||
如需显式允许异构主机通过 QEMU/binfmt 跑包,先在 `.env` 中设置:
|
||||
|
||||
```bash
|
||||
ALLOW_CROSS_ARCH=1
|
||||
```
|
||||
|
||||
然后重新执行 `./scripts/install.sh` 或 `./scripts/self-check.sh`,脚本才会尝试启用对应 binfmt。
|
||||
|
||||
## 停止和重启
|
||||
|
||||
```bash
|
||||
./scripts/stop.sh
|
||||
./scripts/restart.sh
|
||||
```
|
||||
|
||||
## 状态检查
|
||||
|
||||
```bash
|
||||
./scripts/status.sh
|
||||
./scripts/self-check.sh
|
||||
```
|
||||
|
||||
重点检查项:
|
||||
|
||||
- Docker/Compose 可用;
|
||||
- runtime、metrics、Prometheus、Grafana 镜像存在;
|
||||
- `HOST_DATA_DIR` 可写;
|
||||
- Compose 配置合法;
|
||||
- 最新 run 状态;
|
||||
- metrics、Prometheus、Grafana endpoint;
|
||||
- `host_arch`、`package_arch`、`arch_mode` 是否符合预期。
|
||||
|
||||
`status.sh` 还会显示:
|
||||
|
||||
- `package_arch`
|
||||
- `package_platform`
|
||||
- `runtime_image`
|
||||
- `metrics_image`
|
||||
- `allow_cross_arch`
|
||||
- `periodic_snapshot_reset`
|
||||
- `periodic_snapshot_max_deltas`
|
||||
- `rtr_report_dir`
|
||||
- `rtr_report_container_dir`
|
||||
|
||||
如需确认 RTR report 已接入:
|
||||
|
||||
```bash
|
||||
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report --dry-run
|
||||
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report
|
||||
./scripts/status.sh
|
||||
curl -s "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" | grep '^ours_rp_rtr_' | head
|
||||
```
|
||||
|
||||
注册只支持同机的绝对目录。注册命令会保留一份带时间戳的 `.env` 备份,并且只更新 metrics、Prometheus、Grafana 三个旁路容器;`ours-rp-soak` 的容器 ID 应保持不变。
|
||||
|
||||
## 升级
|
||||
|
||||
把新安装包解压到新目录后,推荐通过升级脚本显式复用旧 `.env`:
|
||||
|
||||
```bash
|
||||
./scripts/upgrade.sh --reuse-env-from /path/to/old-installer/.env
|
||||
```
|
||||
|
||||
如果新目录已经存在 `.env`,升级脚本会保留它,不覆盖。
|
||||
|
||||
如果旧 `.env` 缺失 `METRICS_IMAGE` 或 `METRICS_PLATFORM`,升级/安装链路会按当前包架构自动补齐默认值。
|
||||
|
||||
升级不会删除以下数据:
|
||||
|
||||
- `runs/`
|
||||
- `logs/`
|
||||
- `state/rsync-mirror`
|
||||
- `.env` 对应的运行配置
|
||||
- Prometheus / Grafana 数据
|
||||
|
||||
验证定期 forced snapshot 时,可临时设置:
|
||||
|
||||
```bash
|
||||
PERIODIC_SNAPSHOT_RESET=1
|
||||
PERIODIC_SNAPSHOT_MAX_DELTAS=2
|
||||
```
|
||||
|
||||
然后检查最新 `run-meta.json` 应出现:
|
||||
|
||||
```bash
|
||||
snapshot_reason=periodic_snapshot_delta_limit
|
||||
```
|
||||
|
||||
并检查独立 lifecycle 状态:
|
||||
|
||||
```bash
|
||||
jq '{last_run,last_success_snapshot,successful_deltas_since_snapshot,state_health}' \
|
||||
"${HOST_DATA_DIR}/state/run-lifecycle-state.json"
|
||||
```
|
||||
|
||||
验证完成后恢复:
|
||||
|
||||
```bash
|
||||
PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
||||
```
|
||||
|
||||
## 清理
|
||||
|
||||
```bash
|
||||
./scripts/cleanup.sh --keep-runs 100
|
||||
./scripts/cleanup.sh --keep-runs 100 --execute
|
||||
```
|
||||
|
||||
默认 dry-run,确认后加 `--execute`。
|
||||
142
deploy/docker-installer/docs/troubleshooting.en.md
Normal file
142
deploy/docker-installer/docs/troubleshooting.en.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Troubleshooting (`__PACKAGE_ARCH__` / `__PACKAGE_PLATFORM__`)
|
||||
|
||||
## Docker or Compose Is Unavailable
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
docker version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
If missing, run:
|
||||
|
||||
```bash
|
||||
./scripts/install.sh
|
||||
```
|
||||
|
||||
## Package Architecture Mismatch
|
||||
|
||||
If `status.sh`, `install.sh`, or `self-check.sh` reports:
|
||||
|
||||
```text
|
||||
package architecture mismatch
|
||||
```
|
||||
|
||||
the host `uname -m` does not match the packaged `PACKAGE_ARCH=__PACKAGE_ARCH__`. The default behavior is to fail clearly rather than silently switching to emulation.
|
||||
|
||||
Only if you intentionally accept QEMU/binfmt cross-architecture execution should you set:
|
||||
|
||||
```bash
|
||||
ALLOW_CROSS_ARCH=1
|
||||
```
|
||||
|
||||
Then rerun install or self-check.
|
||||
|
||||
## `__PACKAGE_ARCH__` Image Cannot Run
|
||||
|
||||
Running `__PACKAGE_PLATFORM__` images on a different host architecture requires binfmt/qemu. The installer only attempts this automatically when `ALLOW_CROSS_ARCH=1`; you can also enable it manually:
|
||||
|
||||
```bash
|
||||
docker run --rm --privileged tonistiigi/binfmt --install __PACKAGE_ARCH__
|
||||
docker run --rm --platform __PACKAGE_PLATFORM__ debian:bookworm-slim uname -m
|
||||
```
|
||||
|
||||
For `arm64` packages the expected output is `aarch64`; for `amd64` packages the expected output is `x86_64`.
|
||||
|
||||
To confirm the metrics image is also loaded correctly:
|
||||
|
||||
```bash
|
||||
docker image inspect "$(grep '^METRICS_IMAGE=' .env | cut -d= -f2-)"
|
||||
docker run --rm --platform __PACKAGE_PLATFORM__ \
|
||||
"$(grep '^METRICS_IMAGE=' .env | cut -d= -f2-)" \
|
||||
/opt/ours-rp/bin/rpki_artifact_metrics --help
|
||||
```
|
||||
|
||||
## RTR report metrics are empty
|
||||
|
||||
First check registration and the report directory:
|
||||
|
||||
```bash
|
||||
./scripts/status.sh
|
||||
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report --dry-run
|
||||
```
|
||||
|
||||
Check the metrics container mount:
|
||||
|
||||
```bash
|
||||
docker inspect "$(grep '^COMPOSE_PROJECT_NAME=' .env | cut -d= -f2-)-artifact-metrics" \
|
||||
| jq '.[0].Mounts[] | select(.Destination=="/var/lib/ours-rp/rtr-report")'
|
||||
```
|
||||
|
||||
## First Snapshot Times Out
|
||||
|
||||
All-five snapshot can be slow, especially under QEMU. Increase timeout:
|
||||
|
||||
```bash
|
||||
./scripts/start.sh --timeout-secs 14400
|
||||
```
|
||||
|
||||
## Output Counts Are Too Low
|
||||
|
||||
Check:
|
||||
|
||||
```bash
|
||||
grep LIVE_TA_REFRESH_BEFORE_SNAPSHOT .env
|
||||
ls -l __HOST_DATA_DIR__/state/live-ta
|
||||
tail -100 __HOST_DATA_DIR__/logs/live-ta-refresh-*.log
|
||||
```
|
||||
|
||||
In `file-live-ta` mode, snapshot should wait until live TA refresh succeeds.
|
||||
|
||||
## Grafana Login Fails
|
||||
|
||||
Check `.env`:
|
||||
|
||||
```bash
|
||||
GRAFANA_ADMIN_USER=admin
|
||||
GRAFANA_ADMIN_PASSWORD=admin
|
||||
```
|
||||
|
||||
If Grafana has already started, changing `.env` may not reset the existing Grafana database. Stop services and back up/clean `${HOST_DATA_DIR}/grafana` if needed.
|
||||
|
||||
## A Finite Acceptance Test Starts an Extra Run
|
||||
|
||||
If `.env` sets a finite `MAX_RUNS=3` while `SOAK_RESTART_POLICY=unless-stopped`, Docker Compose restarts the soak container after it exits successfully.
|
||||
|
||||
For finite tests, set:
|
||||
|
||||
```bash
|
||||
SOAK_RESTART_POLICY=no
|
||||
```
|
||||
|
||||
## How to Confirm a Periodic Forced Snapshot
|
||||
|
||||
Check the latest run metadata:
|
||||
|
||||
```bash
|
||||
latest="$(find ${HOST_DATA_DIR}/runs -maxdepth 1 -type d -name 'run_*' | sort | tail -1)"
|
||||
jq '{run_id,sync_mode,snapshot_reason,periodic_snapshot_delta_count,periodic_snapshot_forced,reset_db_cleanup_status}' "$latest/run-meta.json"
|
||||
```
|
||||
|
||||
For a threshold-triggered reset you should see:
|
||||
|
||||
- `sync_mode: "snapshot"`
|
||||
- `snapshot_reason: "periodic_snapshot_delta_limit"`
|
||||
- `periodic_snapshot_forced: true`
|
||||
|
||||
To confirm delta counting still advances after retained runs are pruned, also inspect:
|
||||
|
||||
```bash
|
||||
jq '{last_success_snapshot,successful_deltas_since_snapshot,recent_runs_count:(.recent_runs|length)}' \
|
||||
"${HOST_DATA_DIR}/state/run-lifecycle-state.json"
|
||||
```
|
||||
|
||||
## Lifecycle State File Is Corrupt
|
||||
|
||||
The script backs up the corrupt file before best-effort bootstrap from retained runs:
|
||||
|
||||
```bash
|
||||
ls -1 "${HOST_DATA_DIR}/state"/run-lifecycle-state.json.corrupt.*
|
||||
jq . "${HOST_DATA_DIR}/state/run-lifecycle-state.json"
|
||||
```
|
||||
142
deploy/docker-installer/docs/troubleshooting.zh-CN.md
Normal file
142
deploy/docker-installer/docs/troubleshooting.zh-CN.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# 故障排查(`__PACKAGE_ARCH__` / `__PACKAGE_PLATFORM__`)
|
||||
|
||||
## Docker 或 Compose 不可用
|
||||
|
||||
执行:
|
||||
|
||||
```bash
|
||||
docker version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
如果缺失,重新执行:
|
||||
|
||||
```bash
|
||||
./scripts/install.sh
|
||||
```
|
||||
|
||||
## 架构不匹配报错
|
||||
|
||||
当 `status.sh` / `install.sh` / `self-check.sh` 报出:
|
||||
|
||||
```text
|
||||
package architecture mismatch
|
||||
```
|
||||
|
||||
说明宿主 `uname -m` 与包的 `PACKAGE_ARCH=__PACKAGE_ARCH__` 不一致。默认行为就是失败而不是静默模拟运行。
|
||||
|
||||
只有在你明确接受 QEMU/binfmt 跨架构运行时,才设置:
|
||||
|
||||
```bash
|
||||
ALLOW_CROSS_ARCH=1
|
||||
```
|
||||
|
||||
然后重新执行安装或自检。
|
||||
|
||||
## `__PACKAGE_ARCH__` 镜像无法运行
|
||||
|
||||
在异构主机上运行 `__PACKAGE_PLATFORM__` 镜像需要 binfmt/qemu。安装脚本只有在 `ALLOW_CROSS_ARCH=1` 时才会自动尝试启用;也可以手动执行:
|
||||
|
||||
```bash
|
||||
docker run --rm --privileged tonistiigi/binfmt --install __PACKAGE_ARCH__
|
||||
docker run --rm --platform __PACKAGE_PLATFORM__ debian:bookworm-slim uname -m
|
||||
```
|
||||
|
||||
对于 `arm64` 包,预期输出 `aarch64`;对于 `amd64` 包,预期输出 `x86_64`。
|
||||
|
||||
如需进一步确认 metrics 镜像也已正确加载:
|
||||
|
||||
```bash
|
||||
docker image inspect "$(grep '^METRICS_IMAGE=' .env | cut -d= -f2-)"
|
||||
docker run --rm --platform __PACKAGE_PLATFORM__ \
|
||||
"$(grep '^METRICS_IMAGE=' .env | cut -d= -f2-)" \
|
||||
/opt/ours-rp/bin/rpki_artifact_metrics --help
|
||||
```
|
||||
|
||||
## RTR report 指标为空
|
||||
|
||||
先检查注册状态和 report 目录:
|
||||
|
||||
```bash
|
||||
./scripts/status.sh
|
||||
./scripts/register_rtr_monitor.sh --report-dir /root/rpki/report --dry-run
|
||||
```
|
||||
|
||||
检查 metrics 容器是否挂载该目录:
|
||||
|
||||
```bash
|
||||
docker inspect "$(grep '^COMPOSE_PROJECT_NAME=' .env | cut -d= -f2-)-artifact-metrics" \
|
||||
| jq '.[0].Mounts[] | select(.Destination=="/var/lib/ours-rp/rtr-report")'
|
||||
```
|
||||
|
||||
## 首轮 snapshot 超时
|
||||
|
||||
all5 snapshot 可能很慢,尤其在 QEMU 环境。可以提高超时:
|
||||
|
||||
```bash
|
||||
./scripts/start.sh --timeout-secs 14400
|
||||
```
|
||||
|
||||
## 产物数量异常偏低
|
||||
|
||||
检查:
|
||||
|
||||
```bash
|
||||
grep LIVE_TA_REFRESH_BEFORE_SNAPSHOT .env
|
||||
ls -l __HOST_DATA_DIR__/state/live-ta
|
||||
tail -100 __HOST_DATA_DIR__/logs/live-ta-refresh-*.log
|
||||
```
|
||||
|
||||
`file-live-ta` 模式下,snapshot 应等待 live TA 成功刷新。
|
||||
|
||||
## Grafana 无法登录
|
||||
|
||||
确认 `.env` 中:
|
||||
|
||||
```bash
|
||||
GRAFANA_ADMIN_USER=admin
|
||||
GRAFANA_ADMIN_PASSWORD=admin
|
||||
```
|
||||
|
||||
如果曾经启动过 Grafana,修改 `.env` 不一定重置已有 Grafana 数据库账号。可以停止服务后按需备份并清理 `${HOST_DATA_DIR}/grafana`。
|
||||
|
||||
## 有限轮次验收后又多跑了一轮
|
||||
|
||||
如果 `.env` 中设置了 `MAX_RUNS=3` 这类有限轮次,同时 `SOAK_RESTART_POLICY=unless-stopped`,Docker Compose 会在 soak 容器正常退出后重新启动容器。
|
||||
|
||||
有限验收建议设置:
|
||||
|
||||
```bash
|
||||
SOAK_RESTART_POLICY=no
|
||||
```
|
||||
|
||||
## 如何确认触发了定期 forced snapshot
|
||||
|
||||
检查最新 run metadata:
|
||||
|
||||
```bash
|
||||
latest="$(find ${HOST_DATA_DIR}/runs -maxdepth 1 -type d -name 'run_*' | sort | tail -1)"
|
||||
jq '{run_id,sync_mode,snapshot_reason,periodic_snapshot_delta_count,periodic_snapshot_forced,reset_db_cleanup_status}' "$latest/run-meta.json"
|
||||
```
|
||||
|
||||
阈值触发时应看到:
|
||||
|
||||
- `sync_mode: "snapshot"`
|
||||
- `snapshot_reason: "periodic_snapshot_delta_limit"`
|
||||
- `periodic_snapshot_forced: true`
|
||||
|
||||
如需确认 retain 裁剪后仍在累计 delta,可继续检查:
|
||||
|
||||
```bash
|
||||
jq '{last_success_snapshot,successful_deltas_since_snapshot,recent_runs_count:(.recent_runs|length)}' \
|
||||
"${HOST_DATA_DIR}/state/run-lifecycle-state.json"
|
||||
```
|
||||
|
||||
## Lifecycle 状态文件损坏
|
||||
|
||||
脚本会先备份损坏文件,再从当前保留的 `runs/` 尽力 bootstrap:
|
||||
|
||||
```bash
|
||||
ls -1 "${HOST_DATA_DIR}/state"/run-lifecycle-state.json.corrupt.*
|
||||
jq . "${HOST_DATA_DIR}/state/run-lifecycle-state.json"
|
||||
```
|
||||
56
deploy/docker-installer/scripts/cleanup.sh
Executable file
56
deploy/docker-installer/scripts/cleanup.sh
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
DRY_RUN=1
|
||||
KEEP_RUNS=""
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: ./scripts/cleanup.sh [--execute] [--keep-runs N]
|
||||
|
||||
By default this is a dry-run. It removes old run_* directories beyond KEEP_RUNS
|
||||
and clears tmp contents.
|
||||
USAGE
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--execute)
|
||||
DRY_RUN=0
|
||||
shift
|
||||
;;
|
||||
--keep-runs)
|
||||
KEEP_RUNS="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
load_env
|
||||
keep="${KEEP_RUNS:-${RETAIN_RUNS:-100}}"
|
||||
mapfile -t runs < <(find "$HOST_DATA_DIR/runs" -maxdepth 1 -type d -name 'run_*' 2>/dev/null | sort)
|
||||
delete_count=$(( ${#runs[@]} - keep ))
|
||||
if (( delete_count > 0 )); then
|
||||
for ((i=0; i<delete_count; i++)); do
|
||||
if [[ "$DRY_RUN" == "1" ]]; then
|
||||
echo "DRY-RUN rm -rf ${runs[$i]}"
|
||||
else
|
||||
rm -rf "${runs[$i]}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [[ "$DRY_RUN" == "1" ]]; then
|
||||
echo "DRY-RUN rm -rf $HOST_DATA_DIR/tmp/*"
|
||||
else
|
||||
find "$HOST_DATA_DIR/tmp" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
||||
fi
|
||||
df -h "$HOST_DATA_DIR" 2>/dev/null || true
|
||||
597
deploy/docker-installer/scripts/common.sh
Normal file
597
deploy/docker-installer/scripts/common.sh
Normal file
@@ -0,0 +1,597 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
INSTALLER_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
ENV_FILE="${ENV_FILE:-$INSTALLER_ROOT/.env}"
|
||||
ENV_EXAMPLE="$INSTALLER_ROOT/.env.example"
|
||||
COMPOSE_FILE="$INSTALLER_ROOT/compose/docker-compose.yml"
|
||||
MANIFEST_FILE="${MANIFEST_FILE:-$INSTALLER_ROOT/PACKAGE-MANIFEST.env}"
|
||||
|
||||
log() {
|
||||
printf '[ours-rp-installer] %s\n' "$*"
|
||||
}
|
||||
|
||||
warn() {
|
||||
printf '[ours-rp-installer][WARN] %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
die() {
|
||||
printf '[ours-rp-installer][ERROR] %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || die "missing command: $1"
|
||||
}
|
||||
|
||||
normalize_arch() {
|
||||
case "$1" in
|
||||
amd64|x86_64|linux/amd64)
|
||||
printf 'amd64\n'
|
||||
;;
|
||||
arm64|aarch64|linux/arm64)
|
||||
printf 'arm64\n'
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
platform_for_arch() {
|
||||
printf 'linux/%s\n' "$1"
|
||||
}
|
||||
|
||||
env_file_has_key() {
|
||||
local env_path="$1"
|
||||
local key="$2"
|
||||
[[ -f "$env_path" ]] && grep -Eq "^${key}=" "$env_path"
|
||||
}
|
||||
|
||||
persist_env_default() {
|
||||
local env_path="$1"
|
||||
local key="$2"
|
||||
local value="$3"
|
||||
local tmp_env
|
||||
if env_file_has_key "$env_path" "$key"; then
|
||||
return 0
|
||||
fi
|
||||
tmp_env="$(mktemp)"
|
||||
cat "$env_path" > "$tmp_env"
|
||||
printf '%s=%s\n' "$key" "$value" >> "$tmp_env"
|
||||
mv "$tmp_env" "$env_path"
|
||||
}
|
||||
|
||||
env_flag_enabled() {
|
||||
case "${1:-0}" in
|
||||
1|true|TRUE|yes|YES|on|ON)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
detect_host_arch() {
|
||||
local raw_arch
|
||||
raw_arch="$(uname -m)"
|
||||
normalize_arch "$raw_arch" || die "unsupported host architecture: $raw_arch"
|
||||
}
|
||||
|
||||
load_manifest() {
|
||||
MANIFEST_PACKAGE_ARCH_RAW=""
|
||||
MANIFEST_PACKAGE_PLATFORM_RAW=""
|
||||
if [[ -f "$MANIFEST_FILE" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$MANIFEST_FILE"
|
||||
set +a
|
||||
MANIFEST_PACKAGE_ARCH_RAW="${PACKAGE_ARCH:-${package_arch:-}}"
|
||||
MANIFEST_PACKAGE_PLATFORM_RAW="${PACKAGE_PLATFORM:-${package_platform:-}}"
|
||||
fi
|
||||
}
|
||||
|
||||
effective_package_arch() {
|
||||
if [[ -n "${PACKAGE_ARCH:-}" ]]; then
|
||||
normalize_arch "$PACKAGE_ARCH" || die "unsupported PACKAGE_ARCH=${PACKAGE_ARCH}"
|
||||
return 0
|
||||
fi
|
||||
if [[ -n "${PACKAGE_PLATFORM:-}" ]]; then
|
||||
normalize_arch "$PACKAGE_PLATFORM" || die "unsupported PACKAGE_PLATFORM=${PACKAGE_PLATFORM}"
|
||||
return 0
|
||||
fi
|
||||
if [[ -n "${RPKI_PLATFORM:-}" ]]; then
|
||||
normalize_arch "$RPKI_PLATFORM" || die "unsupported RPKI_PLATFORM=${RPKI_PLATFORM}"
|
||||
return 0
|
||||
fi
|
||||
if [[ -n "${METRICS_PLATFORM:-}" ]]; then
|
||||
normalize_arch "$METRICS_PLATFORM" || die "unsupported METRICS_PLATFORM=${METRICS_PLATFORM}"
|
||||
return 0
|
||||
fi
|
||||
if [[ -n "${MONITOR_PLATFORM:-}" ]]; then
|
||||
normalize_arch "$MONITOR_PLATFORM" || die "unsupported MONITOR_PLATFORM=${MONITOR_PLATFORM}"
|
||||
return 0
|
||||
fi
|
||||
die "unable to determine package architecture from manifest or env"
|
||||
}
|
||||
|
||||
arch_mode() {
|
||||
local host_arch package_arch
|
||||
host_arch="$(detect_host_arch)"
|
||||
package_arch="$(effective_package_arch)"
|
||||
if [[ "$host_arch" == "$package_arch" ]]; then
|
||||
printf 'native\n'
|
||||
return 0
|
||||
fi
|
||||
if env_flag_enabled "${ALLOW_CROSS_ARCH:-0}"; then
|
||||
printf 'cross-arch-enabled\n'
|
||||
return 0
|
||||
fi
|
||||
printf 'mismatch-blocked\n'
|
||||
}
|
||||
|
||||
assert_arch_compatibility() {
|
||||
local host_arch package_arch package_platform mode
|
||||
host_arch="$(detect_host_arch)"
|
||||
package_arch="$(effective_package_arch)"
|
||||
package_platform="${PACKAGE_PLATFORM:-${RPKI_PLATFORM:-$(platform_for_arch "$package_arch")}}"
|
||||
mode="$(arch_mode)"
|
||||
case "$mode" in
|
||||
native)
|
||||
return 0
|
||||
;;
|
||||
cross-arch-enabled)
|
||||
log "cross-arch execution explicitly enabled: host_arch=$host_arch package_arch=$package_arch package_platform=$package_platform"
|
||||
return 0
|
||||
;;
|
||||
mismatch-blocked)
|
||||
die "package architecture mismatch: host_arch=$host_arch package_arch=$package_arch package_platform=$package_platform. Set ALLOW_CROSS_ARCH=1 in $ENV_FILE to allow explicit QEMU/binfmt emulation."
|
||||
;;
|
||||
*)
|
||||
die "unknown arch compatibility mode: $mode"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
load_env() {
|
||||
load_manifest
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
[[ -f "$ENV_EXAMPLE" ]] || die "missing $ENV_EXAMPLE"
|
||||
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
||||
log "created .env from .env.example"
|
||||
fi
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
|
||||
if [[ -n "${MANIFEST_PACKAGE_ARCH_RAW:-}" ]]; then
|
||||
manifest_package_arch="$(normalize_arch "$MANIFEST_PACKAGE_ARCH_RAW")" || die "unsupported manifest package arch: $MANIFEST_PACKAGE_ARCH_RAW"
|
||||
if [[ -n "${PACKAGE_ARCH:-}" ]]; then
|
||||
env_package_arch="$(normalize_arch "$PACKAGE_ARCH")" || die "unsupported env package arch: $PACKAGE_ARCH"
|
||||
[[ "$env_package_arch" == "$manifest_package_arch" ]] || die "env PACKAGE_ARCH=$env_package_arch mismatches manifest PACKAGE_ARCH=$manifest_package_arch"
|
||||
fi
|
||||
PACKAGE_ARCH="$manifest_package_arch"
|
||||
else
|
||||
if [[ -z "${PACKAGE_ARCH:-}" ]]; then
|
||||
PACKAGE_ARCH="$(effective_package_arch)"
|
||||
else
|
||||
PACKAGE_ARCH="$(normalize_arch "$PACKAGE_ARCH")"
|
||||
fi
|
||||
fi
|
||||
if [[ -n "${MANIFEST_PACKAGE_PLATFORM_RAW:-}" ]]; then
|
||||
manifest_package_platform="$MANIFEST_PACKAGE_PLATFORM_RAW"
|
||||
if [[ -n "${PACKAGE_PLATFORM:-}" && "$PACKAGE_PLATFORM" != "$manifest_package_platform" ]]; then
|
||||
die "env PACKAGE_PLATFORM=$PACKAGE_PLATFORM mismatches manifest PACKAGE_PLATFORM=$manifest_package_platform"
|
||||
fi
|
||||
PACKAGE_PLATFORM="$manifest_package_platform"
|
||||
else
|
||||
PACKAGE_PLATFORM="${PACKAGE_PLATFORM:-$(platform_for_arch "$PACKAGE_ARCH")}"
|
||||
fi
|
||||
|
||||
HOST_DATA_DIR="${HOST_DATA_DIR:-/var/lib/ours-rp-${PACKAGE_ARCH}-installer}"
|
||||
COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-ours-rp-${PACKAGE_ARCH}-installer}"
|
||||
RPKI_IMAGE="${RPKI_IMAGE:-ours-rp-runtime-${PACKAGE_ARCH}:${source_commit_short:-unknown}}"
|
||||
RPKI_PLATFORM="${RPKI_PLATFORM:-$PACKAGE_PLATFORM}"
|
||||
METRICS_IMAGE="${METRICS_IMAGE:-ours-rp-metrics-${PACKAGE_ARCH}:${source_commit_short:-unknown}}"
|
||||
METRICS_PLATFORM="${METRICS_PLATFORM:-$PACKAGE_PLATFORM}"
|
||||
RTR_REPORT_DIR="${RTR_REPORT_DIR:-$HOST_DATA_DIR/empty-rtr-report}"
|
||||
RTR_REPORT_CONTAINER_DIR="${RTR_REPORT_CONTAINER_DIR:-/var/lib/ours-rp/rtr-report}"
|
||||
MONITOR_PLATFORM="${MONITOR_PLATFORM:-$PACKAGE_PLATFORM}"
|
||||
PROMETHEUS_IMAGE="${PROMETHEUS_IMAGE:-prom/prometheus:v2.55.1}"
|
||||
GRAFANA_IMAGE="${GRAFANA_IMAGE:-grafana/grafana:11.3.1}"
|
||||
METRICS_INSTANCE="${METRICS_INSTANCE:-${PACKAGE_ARCH}-installer}"
|
||||
ALLOW_CROSS_ARCH="${ALLOW_CROSS_ARCH:-0}"
|
||||
RESOURCE_VALIDATION_MODE="${RESOURCE_VALIDATION_MODE:-validation-update-03}"
|
||||
FIRST_RUN_WAIT_TIMEOUT_SECS="${FIRST_RUN_WAIT_TIMEOUT_SECS:-7200}"
|
||||
|
||||
persist_env_default "$ENV_FILE" "METRICS_IMAGE" "$METRICS_IMAGE"
|
||||
persist_env_default "$ENV_FILE" "METRICS_PLATFORM" "$METRICS_PLATFORM"
|
||||
persist_env_default "$ENV_FILE" "RTR_REPORT_DIR" "$RTR_REPORT_DIR"
|
||||
persist_env_default "$ENV_FILE" "RTR_REPORT_CONTAINER_DIR" "$RTR_REPORT_CONTAINER_DIR"
|
||||
persist_env_default "$ENV_FILE" "RESOURCE_VALIDATION_MODE" "$RESOURCE_VALIDATION_MODE"
|
||||
}
|
||||
|
||||
compose_cmd() {
|
||||
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" -p "$COMPOSE_PROJECT_NAME" "$@"
|
||||
}
|
||||
|
||||
validate_rtr_report_dir() {
|
||||
load_env
|
||||
[[ -n "${RTR_REPORT_DIR:-}" ]] || return 0
|
||||
[[ "$RTR_REPORT_DIR" = /* ]] || die "RTR_REPORT_DIR must be an absolute host path: $RTR_REPORT_DIR"
|
||||
[[ -d "$RTR_REPORT_DIR" ]] || die "RTR_REPORT_DIR does not exist or is not a directory: $RTR_REPORT_DIR"
|
||||
}
|
||||
|
||||
validate_rtr_registration_dir() {
|
||||
local report_dir="$1"
|
||||
|
||||
[[ -n "$report_dir" ]] || die "--report-dir is required"
|
||||
[[ "$report_dir" = /* ]] || die "--report-dir must be an absolute host path: $report_dir"
|
||||
[[ "$report_dir" =~ ^/[A-Za-z0-9._/@:+,-]+$ ]] || die "--report-dir contains unsupported characters: $report_dir"
|
||||
[[ -d "$report_dir" ]] || die "--report-dir does not exist: $report_dir"
|
||||
[[ -r "$report_dir" ]] || die "--report-dir is not readable: $report_dir"
|
||||
}
|
||||
|
||||
rtr_report_file_count() {
|
||||
local report_dir="$1"
|
||||
{
|
||||
find "$report_dir" -maxdepth 1 -type f \( \
|
||||
-name 'rtr-source-*.json' -o \
|
||||
-name 'rtr-runtime-*.json' -o \
|
||||
-name 'rtr-clients-*.json' \
|
||||
\) -print 2>/dev/null || true
|
||||
} | wc -l | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
latest_rtr_report_file() {
|
||||
local report_dir="$1"
|
||||
{
|
||||
find "$report_dir" -maxdepth 1 -type f \( \
|
||||
-name 'rtr-source-*.json' -o \
|
||||
-name 'rtr-runtime-*.json' -o \
|
||||
-name 'rtr-clients-*.json' \
|
||||
\) -printf '%T@ %p\n' 2>/dev/null || true
|
||||
} | sort -nr | awk 'NR == 1 { sub(/^[^ ]+ /, ""); print }'
|
||||
}
|
||||
|
||||
replace_env_key_file() {
|
||||
local env_path="$1"
|
||||
local key="$2"
|
||||
local value="$3"
|
||||
local temp_path
|
||||
|
||||
[[ "$key" =~ ^[A-Z][A-Z0-9_]*$ ]] || die "invalid environment key: $key"
|
||||
[[ "$value" != *$'\n'* && "$value" != *$'\r'* ]] || die "environment value must be single-line"
|
||||
|
||||
temp_path="$(mktemp "${env_path}.tmp.XXXXXX")"
|
||||
awk -v key="$key" -v value="$value" '
|
||||
BEGIN { replaced = 0 }
|
||||
$0 ~ "^" key "=" {
|
||||
print key "=" value
|
||||
replaced = 1
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
END {
|
||||
if (!replaced) {
|
||||
print key "=" value
|
||||
}
|
||||
}
|
||||
' "$env_path" > "$temp_path"
|
||||
chmod --reference="$env_path" "$temp_path" 2>/dev/null || true
|
||||
mv "$temp_path" "$env_path"
|
||||
}
|
||||
|
||||
wait_for_endpoint() {
|
||||
local url="$1"
|
||||
local timeout_secs="$2"
|
||||
local elapsed=0
|
||||
|
||||
while (( elapsed < timeout_secs )); do
|
||||
if endpoint_ok "$url"; then
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
elapsed=$((elapsed + 2))
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
create_data_dirs() {
|
||||
load_env
|
||||
mkdir -p \
|
||||
"$HOST_DATA_DIR/state" \
|
||||
"$HOST_DATA_DIR/runs" \
|
||||
"$HOST_DATA_DIR/logs" \
|
||||
"$HOST_DATA_DIR/tmp" \
|
||||
"$HOST_DATA_DIR/empty-rtr-report" \
|
||||
"$HOST_DATA_DIR/prometheus" \
|
||||
"$HOST_DATA_DIR/grafana"
|
||||
validate_rtr_report_dir
|
||||
chmod 755 "$HOST_DATA_DIR" "$HOST_DATA_DIR/state" "$HOST_DATA_DIR/runs" "$HOST_DATA_DIR/logs" "$HOST_DATA_DIR/tmp" || true
|
||||
chmod 777 "$HOST_DATA_DIR/prometheus" "$HOST_DATA_DIR/grafana" || true
|
||||
}
|
||||
|
||||
latest_run_dir() {
|
||||
load_env
|
||||
find "$HOST_DATA_DIR/runs" -maxdepth 1 -mindepth 1 -type d -name 'run_*' 2>/dev/null | sort | tail -1
|
||||
}
|
||||
|
||||
latest_success_run_dir() {
|
||||
load_env
|
||||
find "$HOST_DATA_DIR/runs" -maxdepth 2 -type f -path '*/run-summary.json' 2>/dev/null \
|
||||
| while read -r summary; do
|
||||
if jq -e '.status == "success"' "$summary" >/dev/null 2>&1; then
|
||||
dirname "$summary"
|
||||
fi
|
||||
done | sort | tail -1
|
||||
}
|
||||
|
||||
has_success_run() {
|
||||
[[ -n "$(latest_success_run_dir)" ]]
|
||||
}
|
||||
|
||||
print_run_summary() {
|
||||
local run_dir="$1"
|
||||
local summary="$run_dir/run-summary.json"
|
||||
local meta="$run_dir/run-meta.json"
|
||||
local timing="$run_dir/stage-timing.json"
|
||||
local process_time="$run_dir/process-time.txt"
|
||||
local vrps_file="$run_dir/vrps.csv"
|
||||
local vaps_file="$run_dir/vaps.csv"
|
||||
local status="unknown"
|
||||
local sync_mode="unknown"
|
||||
local wall_ms="null"
|
||||
local validation_ms="null"
|
||||
local repo_sync_ms="null"
|
||||
local max_rss_kb="null"
|
||||
local publication_points="null"
|
||||
local vrps="null"
|
||||
local vaps="null"
|
||||
local warnings="null"
|
||||
[[ -f "$summary" ]] || {
|
||||
warn "missing run-summary.json in $run_dir"
|
||||
return 1
|
||||
}
|
||||
status="$(jq -r '.status // "unknown"' "$summary" 2>/dev/null || echo unknown)"
|
||||
wall_ms="$(jq -r '.wallMs // .wall_ms // "null"' "$summary" 2>/dev/null || echo null)"
|
||||
warnings="$(jq -r '.warningCount // .warnings // "null"' "$summary" 2>/dev/null || echo null)"
|
||||
if [[ -f "$meta" ]]; then
|
||||
sync_mode="$(jq -r '.sync_mode // .syncMode // "unknown"' "$meta" 2>/dev/null || echo unknown)"
|
||||
status="$(jq -r --arg fallback "$status" '.status // $fallback' "$meta" 2>/dev/null || echo "$status")"
|
||||
fi
|
||||
if [[ -f "$timing" ]]; then
|
||||
validation_ms="$(jq -r '.validation_ms // "null"' "$timing" 2>/dev/null || echo null)"
|
||||
repo_sync_ms="$(jq -r '.repo_sync_ms_total // "null"' "$timing" 2>/dev/null || echo null)"
|
||||
publication_points="$(jq -r '.publication_points // "null"' "$timing" 2>/dev/null || echo null)"
|
||||
fi
|
||||
if [[ -f "$process_time" ]]; then
|
||||
max_rss_kb="$(awk -F': ' '/Maximum resident set size/ {print $2; found=1} END {if (!found) print "null"}' "$process_time")"
|
||||
fi
|
||||
if [[ -f "$vrps_file" ]]; then
|
||||
vrps="$(( $(wc -l < "$vrps_file") > 0 ? $(wc -l < "$vrps_file") - 1 : 0 ))"
|
||||
fi
|
||||
if [[ -f "$vaps_file" ]]; then
|
||||
vaps="$(( $(wc -l < "$vaps_file") > 0 ? $(wc -l < "$vaps_file") - 1 : 0 ))"
|
||||
fi
|
||||
jq -n \
|
||||
--arg run "$(basename "$run_dir")" \
|
||||
--arg status "$status" \
|
||||
--arg syncMode "$sync_mode" \
|
||||
--argjson wallMs "$wall_ms" \
|
||||
--argjson validationMs "$validation_ms" \
|
||||
--argjson repoSyncMs "$repo_sync_ms" \
|
||||
--argjson maxRssKb "$max_rss_kb" \
|
||||
--argjson vrps "$vrps" \
|
||||
--argjson vaps "$vaps" \
|
||||
--argjson publicationPoints "$publication_points" \
|
||||
--argjson warnings "$warnings" \
|
||||
'{run:$run,status:$status,syncMode:$syncMode,wallMs:$wallMs,validationMs:$validationMs,repoSyncMs:$repoSyncMs,maxRssKb:$maxRssKb,vrps:$vrps,vaps:$vaps,publicationPoints:$publicationPoints,warnings:$warnings}'
|
||||
}
|
||||
|
||||
wait_for_new_success_run() {
|
||||
local before_latest="$1"
|
||||
local timeout_secs="$2"
|
||||
local start_epoch now run_dir summary meta status meta_status
|
||||
start_epoch="$(date +%s)"
|
||||
while true; do
|
||||
run_dir="$(latest_run_dir || true)"
|
||||
if [[ -n "$run_dir" && "$run_dir" != "$before_latest" ]]; then
|
||||
summary="$run_dir/run-summary.json"
|
||||
meta="$run_dir/run-meta.json"
|
||||
if [[ -f "$summary" ]]; then
|
||||
status="$(jq -r '.status // "unknown"' "$summary" 2>/dev/null || echo unknown)"
|
||||
if [[ "$status" == "success" ]]; then
|
||||
meta_status="unknown"
|
||||
if [[ -f "$meta" ]]; then
|
||||
meta_status="$(jq -r '.status // "unknown"' "$meta" 2>/dev/null || echo unknown)"
|
||||
fi
|
||||
if [[ "$meta_status" == "success" ]]; then
|
||||
print_run_summary "$run_dir" || true
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
if [[ "$status" == "failed" || "$status" == "error" ]]; then
|
||||
print_run_summary "$run_dir" || true
|
||||
die "run failed: $run_dir"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
now="$(date +%s)"
|
||||
if (( now - start_epoch > timeout_secs )); then
|
||||
die "timed out waiting for first successful run after ${timeout_secs}s"
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
}
|
||||
|
||||
docker_compose_available() {
|
||||
docker compose version >/dev/null 2>&1
|
||||
}
|
||||
|
||||
install_docker_if_missing() {
|
||||
if command -v docker >/dev/null 2>&1 && docker_compose_available && command -v jq >/dev/null 2>&1 && command -v rsync >/dev/null 2>&1 && command -v curl >/dev/null 2>&1; then
|
||||
log "docker and docker compose are already installed"
|
||||
return 0
|
||||
fi
|
||||
if [[ "${SKIP_DEP_INSTALL:-0}" == "1" ]]; then
|
||||
die "docker/docker compose missing and SKIP_DEP_INSTALL=1"
|
||||
fi
|
||||
if ! command -v apt-get >/dev/null 2>&1; then
|
||||
die "docker/docker compose missing; automatic install currently supports apt-get only"
|
||||
fi
|
||||
log "installing missing runtime packages via apt"
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl jq rsync gzip tar docker.io
|
||||
if ! docker_compose_available; then
|
||||
if apt-cache show docker-compose-v2 >/dev/null 2>&1; then
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-v2
|
||||
elif apt-cache show docker-compose-plugin >/dev/null 2>&1; then
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-plugin
|
||||
elif apt-cache show docker-compose >/dev/null 2>&1; then
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose
|
||||
fi
|
||||
fi
|
||||
systemctl enable --now docker >/dev/null 2>&1 || true
|
||||
docker_compose_available || die "docker compose is still unavailable after install"
|
||||
}
|
||||
|
||||
load_installer_images() {
|
||||
load_env
|
||||
require_cmd docker
|
||||
shopt -s nullglob
|
||||
local image load_output desired_tag
|
||||
local found=0
|
||||
for image in "$INSTALLER_ROOT"/images/*.tar "$INSTALLER_ROOT"/images/*.tar.gz; do
|
||||
found=1
|
||||
log "loading docker image: $image"
|
||||
if [[ "$image" == *.gz ]]; then
|
||||
load_output="$(gzip -dc "$image" | docker load)"
|
||||
else
|
||||
load_output="$(docker load -i "$image")"
|
||||
fi
|
||||
printf '%s\n' "$load_output"
|
||||
desired_tag=""
|
||||
case "$(basename "$image")" in
|
||||
"${image_tar:-}")
|
||||
desired_tag="${image_tag:-$RPKI_IMAGE}"
|
||||
;;
|
||||
"${metrics_image_tar:-}")
|
||||
desired_tag="${metrics_image:-$METRICS_IMAGE}"
|
||||
;;
|
||||
"${prometheus_image_tar:-}")
|
||||
desired_tag="${prometheus_image:-$PROMETHEUS_IMAGE}"
|
||||
;;
|
||||
"${grafana_image_tar:-}")
|
||||
desired_tag="${grafana_image:-$GRAFANA_IMAGE}"
|
||||
;;
|
||||
esac
|
||||
if [[ -n "$desired_tag" ]]; then
|
||||
ensure_loaded_image_tag "$desired_tag" "$load_output" "$image"
|
||||
fi
|
||||
done
|
||||
shopt -u nullglob
|
||||
(( found == 1 )) || warn "no image tar found under $INSTALLER_ROOT/images"
|
||||
}
|
||||
|
||||
ensure_loaded_image_tag() {
|
||||
local desired_tag="$1"
|
||||
local load_output="$2"
|
||||
local image_path="$3"
|
||||
local loaded_ref=""
|
||||
local loaded_id=""
|
||||
local desired_id=""
|
||||
loaded_ref="$(printf '%s\n' "$load_output" | awk -F'Loaded image: ' '/Loaded image: / {print $2; exit}')"
|
||||
if [[ -z "$loaded_ref" ]]; then
|
||||
loaded_ref="$(printf '%s\n' "$load_output" | awk -F'Loaded image ID: ' '/Loaded image ID: / {print $2; exit}')"
|
||||
fi
|
||||
[[ -n "$loaded_ref" ]] || die "unable to determine loaded image reference for $image_path"
|
||||
loaded_id="$(docker image inspect --format '{{.Id}}' "$loaded_ref" 2>/dev/null || true)"
|
||||
desired_id="$(docker image inspect --format '{{.Id}}' "$desired_tag" 2>/dev/null || true)"
|
||||
if [[ -n "$loaded_id" && -n "$desired_id" && "$loaded_id" == "$desired_id" ]]; then
|
||||
return 0
|
||||
fi
|
||||
log "tagging loaded image for package use: source=$loaded_ref target=$desired_tag"
|
||||
docker tag "$loaded_ref" "$desired_tag"
|
||||
}
|
||||
|
||||
ensure_binfmt_if_needed() {
|
||||
require_cmd docker
|
||||
load_env
|
||||
local host_arch package_arch
|
||||
host_arch="$(detect_host_arch)"
|
||||
package_arch="$(effective_package_arch)"
|
||||
if [[ "$host_arch" == "$package_arch" ]]; then
|
||||
return 0
|
||||
fi
|
||||
assert_arch_compatibility
|
||||
log "host arch is $host_arch; enabling binfmt/qemu for package arch $package_arch"
|
||||
docker run --rm --privileged tonistiigi/binfmt --install "$package_arch"
|
||||
}
|
||||
|
||||
verify_runtime_image() {
|
||||
load_env
|
||||
require_cmd docker
|
||||
log "verifying runtime image $RPKI_IMAGE on $RPKI_PLATFORM"
|
||||
verify_image_platform "$RPKI_IMAGE" "$RPKI_PLATFORM" "runtime"
|
||||
verify_image_usage_output "$RPKI_IMAGE" "$RPKI_PLATFORM" "runtime" /opt/ours-rp/bin/rpki --help
|
||||
}
|
||||
|
||||
verify_metrics_image() {
|
||||
load_env
|
||||
require_cmd docker
|
||||
log "verifying metrics image $METRICS_IMAGE on $METRICS_PLATFORM"
|
||||
verify_image_platform "$METRICS_IMAGE" "$METRICS_PLATFORM" "metrics"
|
||||
verify_image_usage_output "$METRICS_IMAGE" "$METRICS_PLATFORM" "metrics" /opt/ours-rp/bin/rpki_artifact_metrics --help
|
||||
}
|
||||
|
||||
verify_image_platform() {
|
||||
local image="$1"
|
||||
local expected_platform="$2"
|
||||
local role="$3"
|
||||
local actual_platform
|
||||
docker image inspect "$image" >/dev/null
|
||||
actual_platform="$(docker image inspect --format '{{.Os}}/{{.Architecture}}' "$image" 2>/dev/null || echo unknown)"
|
||||
[[ "$actual_platform" == "$expected_platform" ]] || die "$role image platform mismatch: image=$image expected=$expected_platform actual=$actual_platform"
|
||||
}
|
||||
|
||||
verify_image_usage_output() {
|
||||
local image="$1"
|
||||
local expected_platform="$2"
|
||||
local role="$3"
|
||||
shift 3
|
||||
local help_path
|
||||
local status
|
||||
help_path="$(mktemp "${TMPDIR:-/tmp}/ours-rp-image-help.XXXXXX")"
|
||||
set +e
|
||||
docker run --rm --platform "$expected_platform" "$image" "$@" >"$help_path" 2>&1
|
||||
status=$?
|
||||
set -e
|
||||
if [[ "$status" != "0" && "$status" != "1" ]]; then
|
||||
cat "$help_path" >&2 || true
|
||||
rm -f "$help_path"
|
||||
die "$role image command failed: image=$image platform=$expected_platform exit=$status"
|
||||
fi
|
||||
grep -q '^Usage' "$help_path" || {
|
||||
cat "$help_path" >&2 || true
|
||||
rm -f "$help_path"
|
||||
die "$role image did not emit usage output: image=$image platform=$expected_platform exit=$status"
|
||||
}
|
||||
head -5 "$help_path" || true
|
||||
rm -f "$help_path"
|
||||
}
|
||||
|
||||
verify_monitor_images() {
|
||||
load_env
|
||||
require_cmd docker
|
||||
verify_image_platform "$PROMETHEUS_IMAGE" "$MONITOR_PLATFORM" "prometheus"
|
||||
verify_image_platform "$GRAFANA_IMAGE" "$MONITOR_PLATFORM" "grafana"
|
||||
}
|
||||
|
||||
endpoint_ok() {
|
||||
local url="$1"
|
||||
curl -fsS --max-time 5 "$url" >/dev/null 2>&1
|
||||
}
|
||||
49
deploy/docker-installer/scripts/install.sh
Executable file
49
deploy/docker-installer/scripts/install.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: ./scripts/install.sh [--skip-dep-install]
|
||||
|
||||
Install or update the ours RP Docker installer package idempotently.
|
||||
USAGE
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--skip-dep-install)
|
||||
export SKIP_DEP_INSTALL=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
load_env
|
||||
assert_arch_compatibility
|
||||
install_docker_if_missing
|
||||
require_cmd curl
|
||||
require_cmd jq
|
||||
require_cmd rsync
|
||||
require_cmd gzip
|
||||
require_cmd tar
|
||||
create_data_dirs
|
||||
load_installer_images
|
||||
ensure_binfmt_if_needed
|
||||
verify_runtime_image
|
||||
verify_metrics_image
|
||||
verify_monitor_images
|
||||
compose_config_path="$(mktemp "${TMPDIR:-/tmp}/ours-rp-compose-config.XXXXXX.yml")"
|
||||
compose_cmd --profile core --profile sidecar --profile monitor config >"$compose_config_path"
|
||||
"$SCRIPT_DIR/self-check.sh" --quick
|
||||
rm -f "$compose_config_path"
|
||||
log "install complete"
|
||||
7
deploy/docker-installer/scripts/logs.sh
Executable file
7
deploy/docker-installer/scripts/logs.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
load_env
|
||||
compose_cmd --profile core --profile sidecar --profile monitor logs "$@"
|
||||
149
deploy/docker-installer/scripts/register_rtr_monitor.sh
Executable file
149
deploy/docker-installer/scripts/register_rtr_monitor.sh
Executable file
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
REPORT_DIR=""
|
||||
DRY_RUN=0
|
||||
CANDIDATE_ENV=""
|
||||
CONFIG_FILE=""
|
||||
ORIGINAL_ENV_FILE=""
|
||||
ENV_BACKUP=""
|
||||
REGISTRATION_APPLIED=0
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ./scripts/register_rtr_monitor.sh --report-dir <absolute-host-path> [--dry-run]
|
||||
|
||||
Registers one local RTR report directory for the artifact metrics sidecar.
|
||||
The directory must be on the same host as this installer and may contain
|
||||
rtr-source-*.json, rtr-runtime-*.json, and rtr-clients-*.json report files.
|
||||
|
||||
This command recreates only artifact-metrics, Prometheus, and Grafana. It does
|
||||
not restart or recreate the ours-rp-soak container.
|
||||
EOF
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
[[ -n "$CANDIDATE_ENV" && -f "$CANDIDATE_ENV" ]] && rm -f "$CANDIDATE_ENV"
|
||||
[[ -n "$CONFIG_FILE" && -f "$CONFIG_FILE" ]] && rm -f "$CONFIG_FILE"
|
||||
return 0
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
restore_failed_registration() {
|
||||
if (( REGISTRATION_APPLIED == 0 )) || [[ -z "$ENV_BACKUP" || ! -f "$ENV_BACKUP" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "registration failed after .env replacement; restoring previous metrics configuration"
|
||||
cp -p "$ENV_BACKUP" "$ORIGINAL_ENV_FILE"
|
||||
ENV_FILE="$ORIGINAL_ENV_FILE"
|
||||
load_env || warn "unable to reload restored .env"
|
||||
compose_cmd --profile sidecar up -d --force-recreate artifact-metrics \
|
||||
|| warn "unable to recreate artifact-metrics with restored configuration"
|
||||
}
|
||||
|
||||
fail_after_apply() {
|
||||
restore_failed_registration
|
||||
die "$*"
|
||||
}
|
||||
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--report-dir)
|
||||
[[ $# -ge 2 ]] || die "--report-dir requires a value"
|
||||
REPORT_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown argument: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
require_cmd docker
|
||||
require_cmd curl
|
||||
require_cmd grep
|
||||
require_cmd jq
|
||||
validate_rtr_registration_dir "$REPORT_DIR"
|
||||
load_env
|
||||
assert_arch_compatibility
|
||||
|
||||
ORIGINAL_ENV_FILE="$ENV_FILE"
|
||||
REPORT_FILE_COUNT="$(rtr_report_file_count "$REPORT_DIR")"
|
||||
LATEST_REPORT_FILE="$(latest_rtr_report_file "$REPORT_DIR")"
|
||||
if (( REPORT_FILE_COUNT == 0 )); then
|
||||
warn "no recognized RTR report JSON files are currently present in $REPORT_DIR"
|
||||
fi
|
||||
|
||||
CANDIDATE_ENV="$(mktemp "$INSTALLER_ROOT/.env.rtr-register.XXXXXX")"
|
||||
cp -p "$ORIGINAL_ENV_FILE" "$CANDIDATE_ENV"
|
||||
replace_env_key_file "$CANDIDATE_ENV" "RTR_REPORT_DIR" "$REPORT_DIR"
|
||||
|
||||
ENV_FILE="$CANDIDATE_ENV"
|
||||
load_env
|
||||
CONFIG_FILE="$(mktemp "$INSTALLER_ROOT/.rtr-compose-config.XXXXXX")"
|
||||
compose_cmd --profile sidecar --profile monitor config --format json > "$CONFIG_FILE"
|
||||
jq -e --arg source "$REPORT_DIR" --arg target "$RTR_REPORT_CONTAINER_DIR" '
|
||||
.services["artifact-metrics"].volumes
|
||||
| any(.[]; .type == "bind" and .source == $source and .target == $target and .read_only == true)
|
||||
' "$CONFIG_FILE" >/dev/null || die "candidate compose configuration is missing expected read-only RTR bind mount"
|
||||
|
||||
if (( DRY_RUN == 1 )); then
|
||||
printf 'dry_run=ok\n'
|
||||
printf 'report_dir=%s\n' "$REPORT_DIR"
|
||||
printf 'recognized_report_files=%s\n' "$REPORT_FILE_COUNT"
|
||||
printf 'latest_report=%s\n' "${LATEST_REPORT_FILE:--}"
|
||||
printf 'expected_mount_source=%s\n' "$REPORT_DIR"
|
||||
printf 'expected_mount_target=%s\n' "$RTR_REPORT_CONTAINER_DIR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ENV_FILE="$ORIGINAL_ENV_FILE"
|
||||
load_env
|
||||
soak_container_before="$(compose_cmd ps -q ours-rp-soak || true)"
|
||||
ENV_BACKUP="${ORIGINAL_ENV_FILE}.rtr-register-$(date -u +%Y%m%dT%H%M%SZ).bak"
|
||||
cp -p "$ORIGINAL_ENV_FILE" "$ENV_BACKUP"
|
||||
mv "$CANDIDATE_ENV" "$ORIGINAL_ENV_FILE"
|
||||
CANDIDATE_ENV=""
|
||||
REGISTRATION_APPLIED=1
|
||||
|
||||
load_env
|
||||
compose_cmd --profile sidecar up -d --force-recreate artifact-metrics \
|
||||
|| fail_after_apply "unable to recreate artifact-metrics"
|
||||
compose_cmd --profile sidecar --profile monitor up -d prometheus grafana \
|
||||
|| fail_after_apply "unable to start Prometheus and Grafana"
|
||||
|
||||
soak_container_after="$(compose_cmd ps -q ours-rp-soak || true)"
|
||||
if [[ -n "$soak_container_before" && "$soak_container_before" != "$soak_container_after" ]]; then
|
||||
fail_after_apply "ours-rp-soak container changed during RTR registration; investigate before continuing"
|
||||
fi
|
||||
|
||||
wait_for_endpoint "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" 60 \
|
||||
|| fail_after_apply "artifact-metrics did not become ready"
|
||||
wait_for_endpoint "http://127.0.0.1:${PROMETHEUS_PORT:-9090}/-/ready" 60 \
|
||||
|| fail_after_apply "Prometheus did not become ready"
|
||||
wait_for_endpoint "http://127.0.0.1:${GRAFANA_PORT:-3000}/api/health" 60 \
|
||||
|| fail_after_apply "Grafana did not become ready"
|
||||
|
||||
if (( REPORT_FILE_COUNT > 0 )); then
|
||||
curl -fsS "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" | grep -q '^ours_rp_rtr_' \
|
||||
|| fail_after_apply "artifact-metrics exposes no RTR metrics after registration"
|
||||
fi
|
||||
|
||||
printf 'registration=ok\n'
|
||||
printf 'report_dir=%s\n' "$REPORT_DIR"
|
||||
printf 'recognized_report_files=%s\n' "$REPORT_FILE_COUNT"
|
||||
printf 'latest_report=%s\n' "${LATEST_REPORT_FILE:--}"
|
||||
printf 'env_backup=%s\n' "$ENV_BACKUP"
|
||||
printf 'ours_rp_soak_container_unchanged=%s\n' "${soak_container_before:-not-running}"
|
||||
5
deploy/docker-installer/scripts/restart.sh
Executable file
5
deploy/docker-installer/scripts/restart.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
"$SCRIPT_DIR/stop.sh" || true
|
||||
"$SCRIPT_DIR/start.sh" "$@"
|
||||
41
deploy/docker-installer/scripts/self-check.sh
Executable file
41
deploy/docker-installer/scripts/self-check.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
QUICK=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--quick)
|
||||
QUICK=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "Usage: ./scripts/self-check.sh [--quick]"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
load_env
|
||||
assert_arch_compatibility
|
||||
require_cmd docker
|
||||
require_cmd jq
|
||||
docker compose version >/dev/null
|
||||
[[ -f "$COMPOSE_FILE" ]] || die "missing compose file"
|
||||
[[ -f "$ENV_FILE" ]] || die "missing .env"
|
||||
create_data_dirs
|
||||
[[ -w "$HOST_DATA_DIR" ]] || die "data dir is not writable: $HOST_DATA_DIR"
|
||||
compose_cmd --profile core --profile sidecar --profile monitor config >/dev/null
|
||||
verify_image_platform "$RPKI_IMAGE" "$RPKI_PLATFORM" "runtime"
|
||||
verify_image_platform "$METRICS_IMAGE" "$METRICS_PLATFORM" "metrics"
|
||||
verify_monitor_images
|
||||
if [[ "$QUICK" == "0" ]]; then
|
||||
verify_runtime_image
|
||||
verify_metrics_image
|
||||
fi
|
||||
log "self-check ok"
|
||||
59
deploy/docker-installer/scripts/start.sh
Executable file
59
deploy/docker-installer/scripts/start.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
WAIT_FIRST_RUN=1
|
||||
TIMEOUT_SECS=""
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: ./scripts/start.sh [--no-wait-first-run] [--timeout-secs N]
|
||||
|
||||
Start ours RP. If no successful run exists, wait for the first snapshot to succeed
|
||||
before starting metrics, Prometheus and Grafana.
|
||||
USAGE
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--no-wait-first-run)
|
||||
WAIT_FIRST_RUN=0
|
||||
shift
|
||||
;;
|
||||
--timeout-secs)
|
||||
TIMEOUT_SECS="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
load_env
|
||||
assert_arch_compatibility
|
||||
create_data_dirs
|
||||
timeout_secs="${TIMEOUT_SECS:-$FIRST_RUN_WAIT_TIMEOUT_SECS}"
|
||||
before_latest="$(latest_run_dir || true)"
|
||||
had_success=0
|
||||
if has_success_run; then
|
||||
had_success=1
|
||||
fi
|
||||
|
||||
log "starting core soak service"
|
||||
compose_cmd --profile core up -d ours-rp-soak
|
||||
|
||||
if [[ "$had_success" == "0" && "$WAIT_FIRST_RUN" == "1" ]]; then
|
||||
log "no previous successful run found; waiting for first run timeout=${timeout_secs}s"
|
||||
wait_for_new_success_run "$before_latest" "$timeout_secs"
|
||||
fi
|
||||
|
||||
log "starting metrics and monitor services"
|
||||
compose_cmd --profile sidecar --profile monitor up -d artifact-metrics prometheus grafana
|
||||
"$SCRIPT_DIR/status.sh" --brief || true
|
||||
87
deploy/docker-installer/scripts/status.sh
Executable file
87
deploy/docker-installer/scripts/status.sh
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
BRIEF=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--brief)
|
||||
BRIEF=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "Usage: ./scripts/status.sh [--brief]"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
load_env
|
||||
host_arch="$(detect_host_arch)"
|
||||
mode="$(arch_mode)"
|
||||
echo "installer_root=$INSTALLER_ROOT"
|
||||
echo "manifest_file=$MANIFEST_FILE"
|
||||
echo "package_name=${package_name:-}"
|
||||
echo "source_commit=${source_commit:-${git_commit:-}}"
|
||||
echo "source_commit_short=${source_commit_short:-}"
|
||||
echo "source_dirty=${source_dirty:-}"
|
||||
echo "build_timestamp_utc=${build_timestamp_utc:-${created_at_utc:-}}"
|
||||
echo "host_data_dir=$HOST_DATA_DIR"
|
||||
echo "host_arch=$host_arch"
|
||||
echo "package_arch=$PACKAGE_ARCH"
|
||||
echo "package_platform=$PACKAGE_PLATFORM"
|
||||
echo "allow_cross_arch=$ALLOW_CROSS_ARCH"
|
||||
echo "arch_mode=$mode"
|
||||
echo "runtime_image=$RPKI_IMAGE"
|
||||
echo "runtime_platform=$RPKI_PLATFORM"
|
||||
echo "runtime_image_revision=${runtime_image_revision:-}"
|
||||
echo "runtime_image_dirty=${runtime_image_dirty:-}"
|
||||
echo "metrics_image=$METRICS_IMAGE"
|
||||
echo "metrics_platform=$METRICS_PLATFORM"
|
||||
echo "metrics_image_revision=${metrics_image_revision:-}"
|
||||
echo "metrics_image_dirty=${metrics_image_dirty:-}"
|
||||
echo "rtr_report_dir=$RTR_REPORT_DIR"
|
||||
echo "rtr_report_container_dir=$RTR_REPORT_CONTAINER_DIR"
|
||||
echo "monitor_platform=$MONITOR_PLATFORM"
|
||||
echo "rirs=${RIRS:-}"
|
||||
echo "max_runs=${MAX_RUNS:-}"
|
||||
echo "interval_secs=${INTERVAL_SECS:-}"
|
||||
echo "periodic_snapshot_reset=${PERIODIC_SNAPSHOT_RESET:-0}"
|
||||
echo "periodic_snapshot_max_deltas=${PERIODIC_SNAPSHOT_MAX_DELTAS:-100}"
|
||||
echo
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
docker version --format 'docker={{.Server.Version}}' 2>/dev/null || echo "docker=unavailable"
|
||||
docker compose version 2>/dev/null || true
|
||||
compose_cmd --profile core --profile sidecar --profile monitor ps || true
|
||||
else
|
||||
echo "docker=missing"
|
||||
fi
|
||||
echo
|
||||
df -h "$HOST_DATA_DIR" 2>/dev/null || true
|
||||
echo
|
||||
latest="$(latest_run_dir || true)"
|
||||
if [[ -n "$latest" ]]; then
|
||||
echo "latest_run=$latest"
|
||||
print_run_summary "$latest" || true
|
||||
else
|
||||
echo "latest_run=none"
|
||||
fi
|
||||
if [[ "$BRIEF" == "0" ]]; then
|
||||
echo
|
||||
endpoint_ok "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" && echo "metrics=ok" || echo "metrics=unavailable"
|
||||
endpoint_ok "http://127.0.0.1:${PROMETHEUS_PORT:-9090}/-/ready" && echo "prometheus=ok" || echo "prometheus=unavailable"
|
||||
endpoint_ok "http://127.0.0.1:${GRAFANA_PORT:-3000}/api/health" && echo "grafana=ok" || echo "grafana=unavailable"
|
||||
echo
|
||||
if [[ "$RTR_REPORT_DIR" == "$HOST_DATA_DIR/empty-rtr-report" ]]; then
|
||||
echo "rtr_registration=fallback-empty-directory"
|
||||
else
|
||||
echo "rtr_registration=registered"
|
||||
fi
|
||||
echo "rtr_report_files=$(rtr_report_file_count "$RTR_REPORT_DIR")"
|
||||
echo "rtr_latest_report=$(latest_rtr_report_file "$RTR_REPORT_DIR" || true)"
|
||||
fi
|
||||
7
deploy/docker-installer/scripts/stop.sh
Executable file
7
deploy/docker-installer/scripts/stop.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
load_env
|
||||
compose_cmd --profile core --profile sidecar --profile monitor stop "$@"
|
||||
32
deploy/docker-installer/scripts/uninstall.sh
Executable file
32
deploy/docker-installer/scripts/uninstall.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
PURGE_DATA=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--purge-data)
|
||||
PURGE_DATA=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "Usage: ./scripts/uninstall.sh [--purge-data]"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
load_env
|
||||
compose_cmd --profile core --profile sidecar --profile monitor down --remove-orphans || true
|
||||
if [[ "$PURGE_DATA" == "1" ]]; then
|
||||
[[ "$HOST_DATA_DIR" == "/" || -z "$HOST_DATA_DIR" ]] && die "refuse to purge unsafe HOST_DATA_DIR=$HOST_DATA_DIR"
|
||||
rm -rf "$HOST_DATA_DIR"
|
||||
log "purged data dir $HOST_DATA_DIR"
|
||||
else
|
||||
log "containers removed; data kept at $HOST_DATA_DIR"
|
||||
fi
|
||||
110
deploy/docker-installer/scripts/upgrade.sh
Executable file
110
deploy/docker-installer/scripts/upgrade.sh
Executable file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
REUSE_ENV_FROM=""
|
||||
UPDATE_PACKAGE_IMAGE=1
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: ./scripts/upgrade.sh [--reuse-env-from /path/to/.env] [--keep-reused-image]
|
||||
|
||||
By default, --reuse-env-from creates the new .env from this package's
|
||||
.env.example first, then overlays existing user settings from the old .env.
|
||||
Image tags are intentionally kept from the new package so the upgraded service
|
||||
actually runs the new packaged runtime and monitor images. Use
|
||||
--keep-reused-image only when you intentionally want to keep previous image tags.
|
||||
USAGE
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--reuse-env-from)
|
||||
REUSE_ENV_FROM="$2"
|
||||
shift 2
|
||||
;;
|
||||
--keep-reused-image)
|
||||
UPDATE_PACKAGE_IMAGE=0
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown option: $1"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
env_get_key() {
|
||||
local env_path="$1"
|
||||
local key="$2"
|
||||
awk -F= -v key="$key" '$1 == key {sub(/^[^=]*=/, ""); print; exit}' "$env_path"
|
||||
}
|
||||
|
||||
env_set_key() {
|
||||
local env_path="$1"
|
||||
local key="$2"
|
||||
local value="$3"
|
||||
local tmp_env
|
||||
if grep -q "^${key}=" "$env_path"; then
|
||||
tmp_env="$(mktemp)"
|
||||
awk -v key="$key" -v value="$value" '
|
||||
BEGIN { done=0 }
|
||||
$0 ~ "^" key "=" { print key "=" value; done=1; next }
|
||||
{ print }
|
||||
END { if (!done) print key "=" value }
|
||||
' "$env_path" > "$tmp_env"
|
||||
mv "$tmp_env" "$env_path"
|
||||
else
|
||||
printf '%s=%s\n' "$key" "$value" >> "$env_path"
|
||||
fi
|
||||
}
|
||||
|
||||
overlay_reused_env() {
|
||||
local source_env="$1"
|
||||
local target_env="$2"
|
||||
local key
|
||||
local value
|
||||
while IFS='=' read -r key _; do
|
||||
[[ -n "$key" ]] || continue
|
||||
[[ "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
|
||||
case "$key" in
|
||||
PACKAGE_ARCH|PACKAGE_PLATFORM|RPKI_PLATFORM|METRICS_PLATFORM|MONITOR_PLATFORM)
|
||||
continue
|
||||
;;
|
||||
RPKI_IMAGE|METRICS_IMAGE|PROMETHEUS_IMAGE|GRAFANA_IMAGE)
|
||||
[[ "$UPDATE_PACKAGE_IMAGE" == "0" ]] || continue
|
||||
;;
|
||||
esac
|
||||
value="$(env_get_key "$source_env" "$key")"
|
||||
env_set_key "$target_env" "$key" "$value"
|
||||
done < <(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$source_env" || true)
|
||||
}
|
||||
|
||||
if [[ -n "$REUSE_ENV_FROM" ]]; then
|
||||
[[ -f "$REUSE_ENV_FROM" ]] || die "missing reuse env file: $REUSE_ENV_FROM"
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
[[ -f "$ENV_EXAMPLE" ]] || die "missing $ENV_EXAMPLE"
|
||||
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
||||
overlay_reused_env "$REUSE_ENV_FROM" "$ENV_FILE"
|
||||
log "created new package env from .env.example and overlaid user settings from $REUSE_ENV_FROM"
|
||||
else
|
||||
log "keeping existing env at $ENV_FILE; reuse source ignored: $REUSE_ENV_FROM"
|
||||
fi
|
||||
fi
|
||||
|
||||
load_env
|
||||
assert_arch_compatibility
|
||||
create_data_dirs
|
||||
install_docker_if_missing
|
||||
load_installer_images
|
||||
ensure_binfmt_if_needed
|
||||
verify_runtime_image
|
||||
verify_metrics_image
|
||||
verify_monitor_images
|
||||
compose_cmd --profile core --profile sidecar --profile monitor up -d --force-recreate
|
||||
"$SCRIPT_DIR/status.sh" --brief || true
|
||||
@@ -1,4 +0,0 @@
|
||||
# RTR server endpoint used by deploy/frr/frr.conf.template.
|
||||
RPKI_FRR_RPKI_HOST=127.0.0.1
|
||||
RPKI_FRR_RPKI_PORT=323
|
||||
RPKI_FRR_RPKI_PREFERENCE=1
|
||||
@@ -1,78 +0,0 @@
|
||||
# FRR Minimal RTR Client Config
|
||||
|
||||
中文文档: [README.zh.md](./README.zh.md)
|
||||
|
||||
This folder provides a minimal FRR setup for black-box interop testing
|
||||
against this repository's RTR server defaults.
|
||||
|
||||
Server defaults in this repo:
|
||||
- TCP: `0.0.0.0:323`
|
||||
- TLS: `0.0.0.0:324`
|
||||
|
||||
Reference:
|
||||
- `src/main.rs`
|
||||
|
||||
## Files
|
||||
|
||||
- `daemons.example`: sample `/etc/frr/daemons`
|
||||
- `frr.conf.example`: static sample `/etc/frr/frr.conf`
|
||||
- `frr.conf.template`: Docker template used to generate `/etc/frr/frr.conf`
|
||||
- `entrypoint.sh`: renders `frr.conf.template` from environment variables before starting FRR
|
||||
- `.env`: default Docker environment values
|
||||
|
||||
## How to apply on an FRR host
|
||||
|
||||
1. Copy `daemons.example` to `/etc/frr/daemons`.
|
||||
2. Copy `frr.conf.example` to `/etc/frr/frr.conf`.
|
||||
3. Restart FRR:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart frr
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
vtysh -c "show rpki configuration"
|
||||
vtysh -c "show rpki cache-server"
|
||||
vtysh -c "show rpki cache-connection"
|
||||
vtysh -c "show rpki prefix-table"
|
||||
```
|
||||
|
||||
If `show rpki cache-connection` is connected and `show rpki prefix-table`
|
||||
contains VRPs, the RTR client path is working.
|
||||
|
||||
## Docker quick start
|
||||
|
||||
From repository root:
|
||||
|
||||
```bash
|
||||
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml up -d
|
||||
docker exec -it frr-rpki-client vtysh -c "show rpki cache-connection"
|
||||
docker exec -it frr-rpki-client vtysh -c "show rpki prefix-table"
|
||||
```
|
||||
|
||||
The Docker example defaults to `127.0.0.1:323`. Override the RTR server
|
||||
endpoint with environment variables:
|
||||
|
||||
```bash
|
||||
RPKI_FRR_RPKI_HOST=10.0.0.12 \
|
||||
RPKI_FRR_RPKI_PORT=323 \
|
||||
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
Stop:
|
||||
|
||||
```bash
|
||||
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml down
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- This setup targets RTR over TCP (`rpki cache tcp`).
|
||||
- Docker mode renders `frr.conf.template` into `/etc/frr/frr.conf` at startup.
|
||||
- Keep protocol-level conformance checks in Rust tests and
|
||||
`src/bin/rtr_debug_client`.
|
||||
- `network_mode: host` is kept for the minimal FRR container example, but the
|
||||
RTR server address is controlled by `RPKI_FRR_RPKI_HOST` and
|
||||
`RPKI_FRR_RPKI_PORT`.
|
||||
@@ -1,83 +0,0 @@
|
||||
# FRR 最小化 RTR 客户端配置
|
||||
|
||||
本目录提供一个 FRR 最小配置,用于和本仓库 RTR Server 做黑盒互通测试。
|
||||
|
||||
本仓库默认 RTR 监听地址:
|
||||
- TCP:`0.0.0.0:323`
|
||||
- TLS:`0.0.0.0:324`
|
||||
|
||||
参考实现:
|
||||
- `src/main.rs`
|
||||
|
||||
## 文件说明
|
||||
|
||||
- `daemons.example`:示例 `/etc/frr/daemons`
|
||||
- `frr.conf.example`:静态示例 `/etc/frr/frr.conf`
|
||||
- `frr.conf.template`:Docker 启动时生成 `/etc/frr/frr.conf` 的模板
|
||||
- `entrypoint.sh`:启动 FRR 前根据环境变量渲染 `frr.conf.template`
|
||||
- `.env`:Docker 示例使用的默认环境变量
|
||||
|
||||
## 在 FRR 主机上应用
|
||||
|
||||
1. 复制 `daemons.example` 到 `/etc/frr/daemons`
|
||||
2. 复制 `frr.conf.example` 到 `/etc/frr/frr.conf`
|
||||
3. 重启 FRR:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart frr
|
||||
```
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
vtysh -c "show rpki configuration"
|
||||
vtysh -c "show rpki cache-server"
|
||||
vtysh -c "show rpki cache-connection"
|
||||
vtysh -c "show rpki prefix-table"
|
||||
```
|
||||
|
||||
当 `show rpki cache-connection` 显示已连接,且 `show rpki prefix-table` 出现 VRP 时,说明 RTR 客户端链路工作正常。
|
||||
|
||||
## Docker 快速启动
|
||||
|
||||
在仓库根目录执行:
|
||||
|
||||
```bash
|
||||
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml up -d
|
||||
docker exec -it frr-rpki-client vtysh -c "show rpki cache-connection"
|
||||
docker exec -it frr-rpki-client vtysh -c "show rpki prefix-table"
|
||||
```
|
||||
|
||||
Docker 示例默认连接 `127.0.0.1:323`。如果 RTR Server 在其他地址,可以通过环境变量覆盖:
|
||||
|
||||
```bash
|
||||
RPKI_FRR_RPKI_HOST=10.0.0.12 \
|
||||
RPKI_FRR_RPKI_PORT=323 \
|
||||
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
停止:
|
||||
|
||||
```bash
|
||||
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml down
|
||||
```
|
||||
|
||||
## 可测试范围
|
||||
|
||||
- FRR 作为 RTR Client 与本仓库 Server 的 TCP 建链能力
|
||||
- FRR 侧基础会话状态可见性(`cache-server` / `cache-connection`)
|
||||
- VRP 下发与导入是否成功(`prefix-table` 是否有条目)
|
||||
- 基于服务端数据变化触发的前缀表更新(可通过替换 `data` 后观察)
|
||||
- 黑盒互通回归:用于确认“路由器客户端视角”功能可用
|
||||
|
||||
## 不覆盖范围
|
||||
|
||||
- 不替代协议级单元测试/集成测试(PDU 细节、异常路径、边界条件)
|
||||
- 不替代 `src/bin/rtr_debug_client` 的逐报文调试能力
|
||||
- 默认示例以 TCP 为主,TLS/mTLS 需按你的证书与 FRR 配置单独扩展
|
||||
|
||||
## 说明
|
||||
|
||||
- 当前示例主要针对 RTR over TCP(`rpki cache tcp`)
|
||||
- Docker 模式会在启动时把 `frr.conf.template` 渲染为 `/etc/frr/frr.conf`
|
||||
- `network_mode: host` 仍用于最小化 FRR 容器示例,但 RTR Server 地址由 `RPKI_FRR_RPKI_HOST` 和 `RPKI_FRR_RPKI_PORT` 控制
|
||||
@@ -1,7 +0,0 @@
|
||||
# Minimal FRR daemons config for RPKI testing
|
||||
|
||||
zebra=yes
|
||||
bgpd=yes
|
||||
|
||||
# Enable bgpd RPKI module
|
||||
bgpd_options=" -A 127.0.0.1 -M rpki"
|
||||
@@ -1,16 +0,0 @@
|
||||
services:
|
||||
frr-rpki-client:
|
||||
image: quay.io/frrouting/frr:10.2.2
|
||||
container_name: frr-rpki-client
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
privileged: true
|
||||
environment:
|
||||
RPKI_FRR_RPKI_HOST: "${RPKI_FRR_RPKI_HOST:-127.0.0.1}"
|
||||
RPKI_FRR_RPKI_PORT: "${RPKI_FRR_RPKI_PORT:-323}"
|
||||
RPKI_FRR_RPKI_PREFERENCE: "${RPKI_FRR_RPKI_PREFERENCE:-1}"
|
||||
entrypoint: ["/bin/sh", "/config/entrypoint.sh"]
|
||||
volumes:
|
||||
- ./daemons.example:/etc/frr/daemons:ro
|
||||
- ./frr.conf.template:/config/frr.conf.template:ro
|
||||
- ./entrypoint.sh:/config/entrypoint.sh:ro
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
: "${RPKI_FRR_RPKI_HOST:=127.0.0.1}"
|
||||
: "${RPKI_FRR_RPKI_PORT:=323}"
|
||||
: "${RPKI_FRR_RPKI_PREFERENCE:=1}"
|
||||
: "${RPKI_FRR_CONFIG_TEMPLATE_PATH:=/config/frr.conf.template}"
|
||||
: "${RPKI_FRR_CONFIG_PATH:=/etc/frr/frr.conf}"
|
||||
|
||||
escape_sed() {
|
||||
printf '%s' "$1" | sed 's/[&|\\]/\\&/g'
|
||||
}
|
||||
|
||||
host="$(escape_sed "${RPKI_FRR_RPKI_HOST}")"
|
||||
port="$(escape_sed "${RPKI_FRR_RPKI_PORT}")"
|
||||
preference="$(escape_sed "${RPKI_FRR_RPKI_PREFERENCE}")"
|
||||
|
||||
sed \
|
||||
-e "s|\${RPKI_FRR_RPKI_HOST}|${host}|g" \
|
||||
-e "s|\${RPKI_FRR_RPKI_PORT}|${port}|g" \
|
||||
-e "s|\${RPKI_FRR_RPKI_PREFERENCE}|${preference}|g" \
|
||||
"${RPKI_FRR_CONFIG_TEMPLATE_PATH}" > "${RPKI_FRR_CONFIG_PATH}"
|
||||
|
||||
exec /usr/lib/frr/docker-start
|
||||
@@ -1,22 +0,0 @@
|
||||
frr version 10.2
|
||||
frr defaults traditional
|
||||
hostname rpki-lab
|
||||
service integrated-vtysh-config
|
||||
!
|
||||
debug rpki
|
||||
!
|
||||
rpki
|
||||
rpki polling_period 10
|
||||
rpki timeout 10
|
||||
rpki retry_interval 10
|
||||
rpki expire_interval 7200
|
||||
rpki cache tcp 127.0.0.1 323 preference 1
|
||||
exit
|
||||
!
|
||||
router bgp 65001
|
||||
bgp router-id 192.0.2.1
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
exit-address-family
|
||||
!
|
||||
line vty
|
||||
@@ -1,22 +0,0 @@
|
||||
frr version 10.2
|
||||
frr defaults traditional
|
||||
hostname rpki-lab
|
||||
service integrated-vtysh-config
|
||||
!
|
||||
debug rpki
|
||||
!
|
||||
rpki
|
||||
rpki polling_period 10
|
||||
rpki timeout 10
|
||||
rpki retry_interval 10
|
||||
rpki expire_interval 7200
|
||||
rpki cache tcp ${RPKI_FRR_RPKI_HOST} ${RPKI_FRR_RPKI_PORT} preference ${RPKI_FRR_RPKI_PREFERENCE}
|
||||
exit
|
||||
!
|
||||
router bgp 65001
|
||||
bgp router-id 192.0.2.1
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
exit-address-family
|
||||
!
|
||||
line vty
|
||||
@@ -1,54 +0,0 @@
|
||||
# Core runtime knobs.
|
||||
# build-run.sh auto-detects amd64/arm64 unless IMAGE_ARCH/RPKI_RTR_IMAGE_ARCH is set before running it.
|
||||
# Direct docker compose uses RPKI_RTR_IMAGE_ARCH from this file; set it to arm64 on ARM hosts.
|
||||
RPKI_RTR_SERVER_IMAGE_BASE=rpki-rtr
|
||||
RPKI_RTR_SERVER_IMAGE_TAG=v1.3
|
||||
RPKI_RTR_IMAGE_ARCH=arm64
|
||||
# RPKI_RTR_TARGET_PLATFORM=linux/amd64
|
||||
# RPKI_RTR_SERVER_IMAGE=rpki-rtr-amd64:v1.3
|
||||
|
||||
# Data source directories on host.
|
||||
RPKI_RTR_CCR_HOST_DIR=../../data
|
||||
RPKI_RTR_SLURM_HOST_DIR=../../data
|
||||
|
||||
# In-container data source directories.
|
||||
RPKI_RTR_CCR_DIR=/app/data
|
||||
RPKI_RTR_SLURM_DIR=/app/slurm
|
||||
RPKI_RTR_REPORT_HOST_DIR=../../report
|
||||
|
||||
# In-container runtime paths.
|
||||
RPKI_RTR_DB_PATH=/app/rtr-db
|
||||
RPKI_RTR_REPORT_DIR=/app/report
|
||||
|
||||
# Persistent directories on host.
|
||||
RPKI_RTR_DB_HOST_DIR=../../rtr-db
|
||||
RPKI_RTR_LOG_HOST_DIR=../../logs/server
|
||||
RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS=300
|
||||
RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS=300
|
||||
RPKI_RTR_REPORT_HISTORY_LIMIT=10
|
||||
RPKI_RTR_TIMEZONE=Asia/Shanghai
|
||||
RPKI_RTR_MAX_DELTA=10
|
||||
RPKI_RTR_MAX_CONNECTIONS=100000
|
||||
RPKI_RTR_MAX_CONCURRENT_HANDSHAKES=128
|
||||
RPKI_RTR_ADMIN_ADDR=0.0.0.0:8323
|
||||
RPKI_RTR_ADMIN_TOKEN=qwert
|
||||
RUST_LOG=info
|
||||
|
||||
# TLS mode knobs.
|
||||
RPKI_RTR_ENFORCE_TLS_CLIENT_SAN_IP_MATCH=false
|
||||
RPKI_RTR_TLS_CERT_PATH=/app/certs/server-dns.crt
|
||||
RPKI_RTR_TLS_KEY_PATH=/app/certs/server-dns.key
|
||||
RPKI_RTR_TLS_CLIENT_CA_PATH=/app/certs/client-ca.crt
|
||||
|
||||
RPKI_RTR_TLS_CERTS_HOST_DIR=../../certs/tls
|
||||
|
||||
# SSH mode knobs.
|
||||
RPKI_RTR_SSH_HOST_PORT=2222
|
||||
RPKI_RTR_SSH_CONTAINER_PORT=22
|
||||
RPKI_RTR_SSH_AUTH_MODE=key
|
||||
RPKI_RTR_SSH_USERNAME=rpki-rtr
|
||||
RPKI_RTR_SSH_SUBSYSTEM_NAME=rpki-rtr
|
||||
RPKI_RTR_SSH_HOST_KEY_PATH=/app/certs/ssh_host_rsa_key
|
||||
RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH=/app/certs/rtr-authorized_keys
|
||||
|
||||
RPKI_RTR_SSH_CERTS_HOST_DIR=../../certs/ssh
|
||||
@@ -1,99 +0,0 @@
|
||||
# Deployment (Docker Compose)
|
||||
|
||||
This project runs `src/main.rs` as a long-running server that:
|
||||
|
||||
1. loads latest `.ccr` from a configured directory,
|
||||
2. applies optional SLURM filtering,
|
||||
3. starts RTR server.
|
||||
|
||||
The container runs `rpki` directly as PID 1.
|
||||
|
||||
## Files
|
||||
|
||||
- `deploy/server/Dockerfile`
|
||||
- `deploy/server/docker-compose.yml`
|
||||
|
||||
## Runtime Paths in Container
|
||||
|
||||
- CCR directory: `/app/data`
|
||||
- RocksDB directory: `/app/rtr-db`
|
||||
- SLURM directory: `/app/slurm`
|
||||
- Report directory: `/app/report`
|
||||
- TLS cert directory (optional): `/app/certs`
|
||||
|
||||
## Path Configuration via `.env`
|
||||
|
||||
- `RPKI_RTR_CCR_HOST_DIR`: host CCR directory mounted into container
|
||||
- `RPKI_RTR_SLURM_HOST_DIR`: host SLURM directory mounted into container
|
||||
- `RPKI_RTR_CCR_DIR`: in-container CCR directory path
|
||||
- `RPKI_RTR_SLURM_DIR`: in-container SLURM directory path
|
||||
- `RPKI_RTR_DB_HOST_DIR`: host RocksDB directory
|
||||
- `RPKI_RTR_LOG_HOST_DIR`: host log directory
|
||||
- `RPKI_RTR_REPORT_HOST_DIR`: host directory receiving split RTR JSON reports
|
||||
- `RPKI_RTR_DB_PATH`: in-container RocksDB directory
|
||||
- `RPKI_RTR_REPORT_DIR`: in-container report directory
|
||||
|
||||
## Runtime Configuration via `.env`
|
||||
|
||||
- Core: `RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS`, `RPKI_RTR_MAX_DELTA`, `RPKI_RTR_MAX_CONCURRENT_HANDSHAKES`, `RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS`, `RPKI_RTR_REPORT_HISTORY_LIMIT`, `RPKI_RTR_TIMEZONE`, `RPKI_RTR_ADMIN_ADDR`, `RPKI_RTR_ADMIN_TOKEN`, `RUST_LOG`
|
||||
- TCP mode: `RPKI_RTR_MAX_CONNECTIONS`
|
||||
- TLS mode: `RPKI_RTR_ENFORCE_TLS_CLIENT_SAN_IP_MATCH`, `RPKI_RTR_TLS_CERT_PATH`, `RPKI_RTR_TLS_KEY_PATH`, `RPKI_RTR_TLS_CLIENT_CA_PATH`, `RPKI_RTR_TLS_CERTS_HOST_DIR`
|
||||
- SSH mode: `RPKI_RTR_SSH_HOST_PORT`, `RPKI_RTR_SSH_CONTAINER_PORT`, `RPKI_RTR_SSH_AUTH_MODE`, `RPKI_RTR_SSH_USERNAME`, `RPKI_RTR_SSH_SUBSYSTEM_NAME`, `RPKI_RTR_SSH_HOST_KEY_PATH`, `RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH`, `RPKI_RTR_SSH_CERTS_HOST_DIR`
|
||||
|
||||
## Start
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/server/docker-compose.yml up -d --build
|
||||
```
|
||||
|
||||
## Stop
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/server/docker-compose.yml down
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/server/docker-compose.yml logs -f rpki-rtr
|
||||
```
|
||||
|
||||
The admin API can also stream the redirected log file:
|
||||
|
||||
```bash
|
||||
curl -N "http://127.0.0.1:8323/admin/rtr/logs/tail?stream=stdout&lines=200" \
|
||||
-H "Authorization: Bearer $RPKI_RTR_ADMIN_TOKEN"
|
||||
```
|
||||
|
||||
It reads `/app/logs/${HOSTNAME}.stdout.log` or `.stderr.log` by default. Set
|
||||
`RPKI_RTR_LOG_DIR` and `RPKI_RTR_LOG_NAME` to override that lookup.
|
||||
|
||||
## Runtime Report
|
||||
|
||||
The server writes split JSON reports. Each report file uses a local-time
|
||||
timestamp suffix and each category keeps `RPKI_RTR_REPORT_HISTORY_LIMIT` files,
|
||||
defaulting to 10.
|
||||
|
||||
- `rtr-source-*.json`: CCR and SLURM source metadata, latest refresh status,
|
||||
data quality counts, cache snapshot counts, and delta counts. Written on
|
||||
startup and source refresh events.
|
||||
- `rtr-clients-*.json`: active client connection counts and counts by transport
|
||||
(`tcp`, `tls`, `ssh`). Written on startup, whenever the active connection
|
||||
count changes.
|
||||
- `rtr-runtime-*.json`: service start time, uptime, process RSS, and
|
||||
non-sensitive runtime configuration. Written on startup and every
|
||||
`RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS`, defaulting to 300 seconds.
|
||||
|
||||
Timestamps in logs and report JSON files use `RPKI_RTR_TIMEZONE`, which
|
||||
defaults to `Asia/Shanghai`. Use IANA timezone names such as `Asia/Shanghai`,
|
||||
`Europe/London`, `America/New_York`, or `UTC`; `Shanghai` is accepted as a
|
||||
convenience alias for `Asia/Shanghai`.
|
||||
|
||||
## Runtime Admin Config
|
||||
|
||||
The admin endpoint is disabled by default. Set `RPKI_RTR_ADMIN_ADDR` to enable
|
||||
`POST /admin/rtr/config`. If the address is not loopback, `RPKI_RTR_ADMIN_TOKEN`
|
||||
must also be set and requests must include `Authorization: Bearer <token>`.
|
||||
|
||||
The endpoint accepts partial JSON updates. See `docs/rtr-admin-api.md` for the
|
||||
complete request/response schema, examples, and runtime apply semantics.
|
||||
@@ -1,85 +0,0 @@
|
||||
FROM rust:1.89-bookworm AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
ENV CARGO_BUILD_JOBS=2
|
||||
|
||||
RUN set -eux; \
|
||||
cat > /etc/apt/sources.list.d/debian.sources <<'EOF'
|
||||
Types: deb
|
||||
URIs: http://mirrors.tuna.tsinghua.edu.cn/debian
|
||||
Suites: bookworm bookworm-updates
|
||||
Components: main
|
||||
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
||||
|
||||
Types: deb
|
||||
URIs: http://mirrors.tuna.tsinghua.edu.cn/debian-security
|
||||
Suites: bookworm-security
|
||||
Components: main
|
||||
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
||||
EOF
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --fix-missing --no-install-recommends \
|
||||
-o Acquire::Retries=10 \
|
||||
-o Acquire::http::Timeout=60 \
|
||||
build-essential \
|
||||
cmake \
|
||||
pkg-config \
|
||||
clang \
|
||||
libclang-dev \
|
||||
libssl-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY src ./src
|
||||
|
||||
RUN cargo build --release --bin rpki_rtr
|
||||
|
||||
FROM debian:bookworm-slim AS runtime
|
||||
|
||||
RUN set -eux; \
|
||||
cat > /etc/apt/sources.list.d/debian.sources <<'EOF'
|
||||
Types: deb
|
||||
URIs: http://mirrors.tuna.tsinghua.edu.cn/debian
|
||||
Suites: bookworm bookworm-updates
|
||||
Components: main
|
||||
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
||||
|
||||
Types: deb
|
||||
URIs: http://mirrors.tuna.tsinghua.edu.cn/debian-security
|
||||
Suites: bookworm-security
|
||||
Components: main
|
||||
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
||||
EOF
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --fix-missing --no-install-recommends \
|
||||
-o Acquire::Retries=10 \
|
||||
-o Acquire::http::Timeout=60 \
|
||||
ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /build/target/release/rpki_rtr /usr/local/bin/rpki_rtr
|
||||
COPY --chmod=755 deploy/server/entrypoint.sh /usr/local/bin/rpki-rtr-entrypoint.sh
|
||||
|
||||
RUN mkdir -p /app/data /app/rtr-db /app/certs /app/slurm /app/logs /app/report
|
||||
|
||||
ENV RPKI_RTR_ENABLE_TLS=false \
|
||||
RPKI_RTR_TCP_ADDR=0.0.0.0:323 \
|
||||
RPKI_RTR_TLS_ADDR=0.0.0.0:324 \
|
||||
RPKI_RTR_DB_PATH=/app/rtr-db \
|
||||
RPKI_RTR_CCR_DIR=/app/data \
|
||||
RPKI_RTR_SLURM_DIR=/app/slurm \
|
||||
RPKI_RTR_REPORT_DIR=/app/report \
|
||||
RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS=300 \
|
||||
RPKI_RTR_REPORT_HISTORY_LIMIT=10 \
|
||||
RPKI_RTR_REFRESH_INTERVAL_SECS=300 \
|
||||
RPKI_RTR_ADMIN_ADDR="" \
|
||||
RPKI_RTR_ADMIN_TOKEN=""
|
||||
|
||||
EXPOSE 323 324
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/rpki-rtr-entrypoint.sh"]
|
||||
@@ -1,292 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
|
||||
CALLER_TARGET_PLATFORM="${TARGET_PLATFORM:-${RPKI_RTR_TARGET_PLATFORM:-}}"
|
||||
CALLER_IMAGE_ARCH="${IMAGE_ARCH:-${RPKI_RTR_IMAGE_ARCH:-}}"
|
||||
CALLER_SERVER_IMAGE="${SERVER_IMAGE:-${RPKI_RTR_SERVER_IMAGE:-}}"
|
||||
|
||||
load_env_defaults() {
|
||||
local line key value
|
||||
[[ -f "${SCRIPT_DIR}/.env" ]] || return
|
||||
|
||||
while IFS= read -r line || [[ -n "${line}" ]]; do
|
||||
line="${line%$'\r'}"
|
||||
[[ -z "${line}" || "${line}" =~ ^[[:space:]]*# ]] && continue
|
||||
[[ "${line}" == *"="* ]] || continue
|
||||
|
||||
key="${line%%=*}"
|
||||
value="${line#*=}"
|
||||
key="${key#"${key%%[![:space:]]*}"}"
|
||||
key="${key%"${key##*[![:space:]]}"}"
|
||||
[[ "${key}" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
|
||||
|
||||
if [[ -z "${!key+x}" ]]; then
|
||||
export "${key}=${value}"
|
||||
fi
|
||||
done < "${SCRIPT_DIR}/.env"
|
||||
}
|
||||
|
||||
detect_image_arch() {
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) printf '%s\n' amd64 ;;
|
||||
aarch64|arm64) printf '%s\n' arm64 ;;
|
||||
*) die "unsupported host architecture '$(uname -m)'; set RPKI_RTR_IMAGE_ARCH=amd64 or arm64" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
platform_for_image_arch() {
|
||||
case "$1" in
|
||||
amd64) printf '%s\n' linux/amd64 ;;
|
||||
arm64) printf '%s\n' linux/arm64 ;;
|
||||
*) die "unsupported image arch '$1', expected amd64 or arm64" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
image_arch_from_platform() {
|
||||
case "$1" in
|
||||
linux/amd64|amd64) printf '%s\n' amd64 ;;
|
||||
linux/arm64|arm64) printf '%s\n' arm64 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
load_env_defaults
|
||||
|
||||
MODE="${MODE:-tcp}"
|
||||
ACTION="up"
|
||||
DETACH="${DETACH:-1}"
|
||||
BUILD="${BUILD:-1}"
|
||||
TARGET_PLATFORM="${CALLER_TARGET_PLATFORM}"
|
||||
IMAGE_ARCH="${CALLER_IMAGE_ARCH}"
|
||||
SERVER_IMAGE="${CALLER_SERVER_IMAGE}"
|
||||
BUILDX_PUSH="${BUILDX_PUSH:-0}"
|
||||
BUILDX_BUILDER="${BUILDX_BUILDER:-}"
|
||||
SKIP_BASE_IMAGE_PULL="${SKIP_BASE_IMAGE_PULL:-0}"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
deploy/server/build-run.sh [action] [options]
|
||||
|
||||
Actions:
|
||||
up Build and start server (default)
|
||||
build Build server image with docker compose
|
||||
buildx Build server image with docker buildx
|
||||
rebuild Rebuild image without cache, then start server
|
||||
down|stop Stop server compose stack
|
||||
restart Stop, then build and start server
|
||||
logs Follow server logs
|
||||
ps Show server compose status
|
||||
|
||||
Options:
|
||||
--mode MODE base|tcp|tls|ssh (default: tcp)
|
||||
--platform VALUE Target platform for buildx (default: host architecture)
|
||||
--image-arch VALUE
|
||||
Image architecture suffix for default tag: amd64|arm64
|
||||
--server-image TAG
|
||||
Server image tag for buildx and compose
|
||||
--push Push buildx image instead of loading it locally
|
||||
--builder NAME buildx builder name
|
||||
--skip-base-pull Do not pull Dockerfile base images before buildx
|
||||
--foreground Run server in foreground
|
||||
--no-build Start without --build
|
||||
-h|--help Show this help
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "error: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
has_compose() {
|
||||
docker compose version >/dev/null 2>&1
|
||||
}
|
||||
|
||||
has_buildx() {
|
||||
docker buildx version >/dev/null 2>&1
|
||||
}
|
||||
|
||||
pull_base_images() {
|
||||
[[ "${SKIP_BASE_IMAGE_PULL}" == "1" ]] && return
|
||||
|
||||
local platform image
|
||||
local -a platforms images
|
||||
IFS=',' read -r -a platforms <<< "${TARGET_PLATFORM}"
|
||||
images=(rust:1.89-bookworm debian:bookworm-slim)
|
||||
|
||||
for platform in "${platforms[@]}"; do
|
||||
for image in "${images[@]}"; do
|
||||
docker pull --platform "${platform}" "${image}"
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
compose_files() {
|
||||
case "${MODE}" in
|
||||
base) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.yml" ;;
|
||||
tcp) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tcp.yml" ;;
|
||||
tls) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tls.yml" ;;
|
||||
ssh) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.ssh.yml" ;;
|
||||
*) die "unsupported mode '${MODE}', expected base|tcp|tls|ssh" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
compose_args() {
|
||||
local -a args files
|
||||
args=()
|
||||
[[ -f "${SCRIPT_DIR}/.env" ]] && args+=(--env-file "${SCRIPT_DIR}/.env")
|
||||
mapfile -t files < <(compose_files)
|
||||
args+=("${files[@]}")
|
||||
printf '%s\n' "${args[@]}"
|
||||
}
|
||||
|
||||
compose_run() {
|
||||
local -a args
|
||||
mapfile -t args < <(compose_args)
|
||||
docker compose "${args[@]}" "$@"
|
||||
}
|
||||
|
||||
up_server() {
|
||||
local -a up_args
|
||||
up_args=(up)
|
||||
[[ "${DETACH}" == "1" ]] && up_args+=(-d)
|
||||
[[ "${BUILD}" == "1" ]] && up_args+=(--build)
|
||||
compose_run "${up_args[@]}"
|
||||
}
|
||||
|
||||
buildx_server() {
|
||||
has_buildx || die "docker buildx is not available"
|
||||
if [[ "${TARGET_PLATFORM}" == *,* && "${BUILDX_PUSH}" != "1" ]]; then
|
||||
die "multi-platform buildx output requires --push"
|
||||
fi
|
||||
|
||||
local -a output_args builder_args
|
||||
[[ "${BUILDX_PUSH}" == "1" ]] && output_args=(--push) || output_args=(--load)
|
||||
[[ -n "${BUILDX_BUILDER}" ]] && builder_args=(--builder "${BUILDX_BUILDER}") || builder_args=()
|
||||
|
||||
pull_base_images
|
||||
|
||||
docker buildx build "${builder_args[@]}" \
|
||||
--platform "${TARGET_PLATFORM}" \
|
||||
-f "${SCRIPT_DIR}/Dockerfile" \
|
||||
-t "${SERVER_IMAGE}" \
|
||||
"${output_args[@]}" \
|
||||
"${REPO_ROOT}"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
up|build|buildx|rebuild|down|stop|restart|logs|ps)
|
||||
ACTION="$1"
|
||||
shift
|
||||
;;
|
||||
--mode)
|
||||
[[ $# -ge 2 ]] || die "--mode requires a value"
|
||||
MODE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--platform)
|
||||
[[ $# -ge 2 ]] || die "--platform requires a value"
|
||||
TARGET_PLATFORM="$2"
|
||||
shift 2
|
||||
;;
|
||||
--image-arch)
|
||||
[[ $# -ge 2 ]] || die "--image-arch requires a value"
|
||||
IMAGE_ARCH="$2"
|
||||
shift 2
|
||||
;;
|
||||
--server-image)
|
||||
[[ $# -ge 2 ]] || die "--server-image requires a value"
|
||||
SERVER_IMAGE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--push)
|
||||
BUILDX_PUSH=1
|
||||
shift
|
||||
;;
|
||||
--builder)
|
||||
[[ $# -ge 2 ]] || die "--builder requires a value"
|
||||
BUILDX_BUILDER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--skip-base-pull)
|
||||
SKIP_BASE_IMAGE_PULL=1
|
||||
shift
|
||||
;;
|
||||
--foreground)
|
||||
DETACH=0
|
||||
shift
|
||||
;;
|
||||
--no-build)
|
||||
BUILD=0
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown argument '$1'"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "${TARGET_PLATFORM}" ]]; then
|
||||
[[ -n "${IMAGE_ARCH}" ]] || IMAGE_ARCH="$(detect_image_arch)"
|
||||
TARGET_PLATFORM="$(platform_for_image_arch "${IMAGE_ARCH}")"
|
||||
elif [[ -z "${IMAGE_ARCH}" ]]; then
|
||||
if [[ "${TARGET_PLATFORM}" != *,* ]] && IMAGE_ARCH="$(image_arch_from_platform "${TARGET_PLATFORM}")"; then
|
||||
:
|
||||
else
|
||||
IMAGE_ARCH="$(detect_image_arch)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "${SERVER_IMAGE}" ]]; then
|
||||
if [[ "${TARGET_PLATFORM}" == *,* ]]; then
|
||||
SERVER_IMAGE="${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}"
|
||||
else
|
||||
SERVER_IMAGE="${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}-${IMAGE_ARCH}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}"
|
||||
fi
|
||||
fi
|
||||
|
||||
export RPKI_RTR_SERVER_IMAGE="${SERVER_IMAGE}"
|
||||
if [[ "${ACTION}" == "buildx" ]]; then
|
||||
has_buildx || die "docker buildx is not available"
|
||||
else
|
||||
has_compose || die "docker compose is not available"
|
||||
fi
|
||||
|
||||
cd "${REPO_ROOT}"
|
||||
|
||||
case "${ACTION}" in
|
||||
up)
|
||||
up_server
|
||||
;;
|
||||
build)
|
||||
compose_run build
|
||||
;;
|
||||
buildx)
|
||||
buildx_server
|
||||
;;
|
||||
rebuild)
|
||||
compose_run build --no-cache
|
||||
up_server
|
||||
;;
|
||||
down|stop)
|
||||
compose_run down
|
||||
;;
|
||||
restart)
|
||||
compose_run down
|
||||
up_server
|
||||
;;
|
||||
logs)
|
||||
compose_run logs -f
|
||||
;;
|
||||
ps)
|
||||
compose_run ps
|
||||
;;
|
||||
esac
|
||||
@@ -1,54 +0,0 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
rpki-rtr:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: deploy/server/Dockerfile
|
||||
image: ${RPKI_RTR_SERVER_IMAGE:-${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}}
|
||||
container_name: rpki-rtr-ssh
|
||||
restart: no
|
||||
ports:
|
||||
- "323:323"
|
||||
- "${RPKI_RTR_SSH_HOST_PORT:-2222}:${RPKI_RTR_SSH_CONTAINER_PORT:-22}"
|
||||
- "8323:8323"
|
||||
environment:
|
||||
RPKI_RTR_ENABLE_TLS: "false"
|
||||
RPKI_RTR_ENABLE_SSH: "true"
|
||||
RPKI_RTR_TCP_ADDR: "0.0.0.0:323"
|
||||
RPKI_RTR_SSH_ADDR: "0.0.0.0:${RPKI_RTR_SSH_CONTAINER_PORT:-22}"
|
||||
RPKI_RTR_SSH_HOST_KEY_PATH: "${RPKI_RTR_SSH_HOST_KEY_PATH:-/app/certs/ssh_host_rsa_key}"
|
||||
RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH: "${RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH:-/app/certs/rtr-authorized_keys}"
|
||||
RPKI_RTR_SSH_USERNAME: "${RPKI_RTR_SSH_USERNAME:-rpki-rtr}"
|
||||
RPKI_RTR_SSH_SUBSYSTEM_NAME: "${RPKI_RTR_SSH_SUBSYSTEM_NAME:-rpki-rtr}"
|
||||
# SSH auth mode: key | password | both
|
||||
RPKI_RTR_SSH_AUTH_MODE: "${RPKI_RTR_SSH_AUTH_MODE:-key}"
|
||||
# Optional: enable password authentication in addition to publickey
|
||||
# RPKI_RTR_SSH_PASSWORD: "test-password"
|
||||
RPKI_RTR_DB_PATH: "${RPKI_RTR_DB_PATH:-/app/rtr-db}"
|
||||
RPKI_RTR_CCR_DIR: "${RPKI_RTR_CCR_DIR:-/app/data}"
|
||||
RPKI_RTR_SLURM_DIR: "${RPKI_RTR_SLURM_DIR:-/app/slurm}"
|
||||
RPKI_RTR_REPORT_DIR: "${RPKI_RTR_REPORT_DIR:-/app/report}"
|
||||
RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS: "${RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS:-300}"
|
||||
RPKI_RTR_REPORT_HISTORY_LIMIT: "${RPKI_RTR_REPORT_HISTORY_LIMIT:-10}"
|
||||
RPKI_RTR_TIMEZONE: "${RPKI_RTR_TIMEZONE:-Asia/Shanghai}"
|
||||
RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:-300}"
|
||||
RPKI_RTR_MAX_DELTA: "${RPKI_RTR_MAX_DELTA:-10}"
|
||||
RPKI_RTR_MAX_CONCURRENT_HANDSHAKES: "${RPKI_RTR_MAX_CONCURRENT_HANDSHAKES:-128}"
|
||||
RPKI_RTR_ADMIN_ADDR: "${RPKI_RTR_ADMIN_ADDR:-}"
|
||||
RPKI_RTR_ADMIN_TOKEN: "${RPKI_RTR_ADMIN_TOKEN:-}"
|
||||
RUST_LOG: "${RUST_LOG:-info}"
|
||||
volumes:
|
||||
- ${RPKI_RTR_CCR_HOST_DIR:-../../data}:${RPKI_RTR_CCR_DIR:-/app/data}:ro
|
||||
- ${RPKI_RTR_DB_HOST_DIR:-../../rtr-db}:${RPKI_RTR_DB_PATH:-/app/rtr-db}
|
||||
- ${RPKI_RTR_SLURM_HOST_DIR:-../../data}:${RPKI_RTR_SLURM_DIR:-/app/slurm}
|
||||
- ${RPKI_RTR_SSH_CERTS_HOST_DIR:-../../certs/ssh}:/app/certs:ro
|
||||
- ${RPKI_RTR_LOG_HOST_DIR:-../../logs/server}:/app/logs
|
||||
- ${RPKI_RTR_REPORT_HOST_DIR:-../../report}:${RPKI_RTR_REPORT_DIR:-/app/report}
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
networks:
|
||||
rpki_net:
|
||||
name: rpki_net
|
||||
driver: bridge
|
||||
@@ -1,44 +0,0 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
rpki-rtr:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: deploy/server/Dockerfile
|
||||
image: ${RPKI_RTR_SERVER_IMAGE:-${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}}
|
||||
container_name: rpki-rtr-tcp
|
||||
restart: no
|
||||
ports:
|
||||
- "323:323"
|
||||
- "8323:8323"
|
||||
environment:
|
||||
RPKI_RTR_ENABLE_TLS: "false"
|
||||
RPKI_RTR_ENABLE_SSH: "false"
|
||||
RPKI_RTR_TCP_ADDR: "0.0.0.0:323"
|
||||
RPKI_RTR_DB_PATH: "${RPKI_RTR_DB_PATH:-/app/rtr-db}"
|
||||
RPKI_RTR_CCR_DIR: "${RPKI_RTR_CCR_DIR:-/app/data}"
|
||||
RPKI_RTR_SLURM_DIR: "${RPKI_RTR_SLURM_DIR:-/app/slurm}"
|
||||
RPKI_RTR_REPORT_DIR: "${RPKI_RTR_REPORT_DIR:-/app/report}"
|
||||
RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS: "${RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS:-300}"
|
||||
RPKI_RTR_REPORT_HISTORY_LIMIT: "${RPKI_RTR_REPORT_HISTORY_LIMIT:-10}"
|
||||
RPKI_RTR_TIMEZONE: "${RPKI_RTR_TIMEZONE:-Asia/Shanghai}"
|
||||
RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:-60}"
|
||||
RPKI_RTR_MAX_DELTA: "${RPKI_RTR_MAX_DELTA:-10}"
|
||||
RPKI_RTR_MAX_CONNECTIONS: "${RPKI_RTR_MAX_CONNECTIONS:-100000}"
|
||||
RPKI_RTR_MAX_CONCURRENT_HANDSHAKES: "${RPKI_RTR_MAX_CONCURRENT_HANDSHAKES:-128}"
|
||||
RPKI_RTR_ADMIN_ADDR: "${RPKI_RTR_ADMIN_ADDR:-}"
|
||||
RPKI_RTR_ADMIN_TOKEN: "${RPKI_RTR_ADMIN_TOKEN:-}"
|
||||
RUST_LOG: "${RUST_LOG:-info}"
|
||||
volumes:
|
||||
- ${RPKI_RTR_CCR_HOST_DIR:-../../data}:${RPKI_RTR_CCR_DIR:-/app/data}:ro
|
||||
- ${RPKI_RTR_DB_HOST_DIR:-../../rtr-db}:${RPKI_RTR_DB_PATH:-/app/rtr-db}
|
||||
- ${RPKI_RTR_SLURM_HOST_DIR:-../../data}:${RPKI_RTR_SLURM_DIR:-/app/slurm}
|
||||
- ${RPKI_RTR_LOG_HOST_DIR:-../../logs/server}:/app/logs
|
||||
- ${RPKI_RTR_REPORT_HOST_DIR:-../../report}:${RPKI_RTR_REPORT_DIR:-/app/report}
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
networks:
|
||||
rpki_net:
|
||||
name: rpki_net
|
||||
driver: bridge
|
||||
@@ -1,48 +0,0 @@
|
||||
services:
|
||||
rpki-rtr:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: deploy/server/Dockerfile
|
||||
image: ${RPKI_RTR_SERVER_IMAGE:-${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}}
|
||||
container_name: rpki-rtr-tls
|
||||
restart: no
|
||||
ports:
|
||||
# - "323:323"
|
||||
- "324:324"
|
||||
- "8323:8323"
|
||||
environment:
|
||||
RPKI_RTR_ENABLE_TLS: "true"
|
||||
RPKI_RTR_ENABLE_SSH: "false"
|
||||
# RPKI_RTR_TCP_ADDR: "0.0.0.0:323"
|
||||
RPKI_RTR_TLS_ADDR: "0.0.0.0:324"
|
||||
RPKI_RTR_TLS_CERT_PATH: "${RPKI_RTR_TLS_CERT_PATH:-/app/certs/server-dns.crt}"
|
||||
RPKI_RTR_TLS_KEY_PATH: "${RPKI_RTR_TLS_KEY_PATH:-/app/certs/server-dns.key}"
|
||||
RPKI_RTR_TLS_CLIENT_CA_PATH: "${RPKI_RTR_TLS_CLIENT_CA_PATH:-/app/certs/client-ca.crt}"
|
||||
RPKI_RTR_ENFORCE_TLS_CLIENT_SAN_IP_MATCH: "${RPKI_RTR_ENFORCE_TLS_CLIENT_SAN_IP_MATCH:-false}"
|
||||
RPKI_RTR_DB_PATH: "${RPKI_RTR_DB_PATH:-/app/rtr-db}"
|
||||
RPKI_RTR_CCR_DIR: "${RPKI_RTR_CCR_DIR:-/app/data}"
|
||||
RPKI_RTR_SLURM_DIR: "${RPKI_RTR_SLURM_DIR:-/app/slurm}"
|
||||
RPKI_RTR_REPORT_DIR: "${RPKI_RTR_REPORT_DIR:-/app/report}"
|
||||
RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS: "${RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS:-300}"
|
||||
RPKI_RTR_REPORT_HISTORY_LIMIT: "${RPKI_RTR_REPORT_HISTORY_LIMIT:-10}"
|
||||
RPKI_RTR_TIMEZONE: "${RPKI_RTR_TIMEZONE:-Asia/Shanghai}"
|
||||
RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:-300}"
|
||||
RPKI_RTR_MAX_DELTA: "${RPKI_RTR_MAX_DELTA:-10}"
|
||||
RPKI_RTR_MAX_CONCURRENT_HANDSHAKES: "${RPKI_RTR_MAX_CONCURRENT_HANDSHAKES:-128}"
|
||||
RPKI_RTR_ADMIN_ADDR: "${RPKI_RTR_ADMIN_ADDR:-}"
|
||||
RPKI_RTR_ADMIN_TOKEN: "${RPKI_RTR_ADMIN_TOKEN:-}"
|
||||
RUST_LOG: "${RUST_LOG:-info}"
|
||||
volumes:
|
||||
- ${RPKI_RTR_CCR_HOST_DIR:-../../data}:${RPKI_RTR_CCR_DIR:-/app/data}:ro
|
||||
- ${RPKI_RTR_DB_HOST_DIR:-../../rtr-db}:${RPKI_RTR_DB_PATH:-/app/rtr-db}
|
||||
- ${RPKI_RTR_SLURM_HOST_DIR:-../../data}:${RPKI_RTR_SLURM_DIR:-/app/slurm}
|
||||
- ${RPKI_RTR_TLS_CERTS_HOST_DIR:-../../certs/tls}:/app/certs:ro
|
||||
- ${RPKI_RTR_LOG_HOST_DIR:-../../logs/server}:/app/logs
|
||||
- ${RPKI_RTR_REPORT_HOST_DIR:-../../report}:${RPKI_RTR_REPORT_DIR:-/app/report}
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
networks:
|
||||
rpki_net:
|
||||
name: rpki_net
|
||||
driver: bridge
|
||||
@@ -1,58 +0,0 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
rpki-rtr:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: deploy/server/Dockerfile
|
||||
image: ${RPKI_RTR_SERVER_IMAGE:-${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}}
|
||||
container_name: rpki-rtr
|
||||
restart: no
|
||||
ports:
|
||||
- "323:323"
|
||||
- "324:324"
|
||||
- "8323:8323"
|
||||
# SSH mode example:
|
||||
# - "22:22"
|
||||
environment:
|
||||
RPKI_RTR_ENABLE_TLS: "false"
|
||||
RPKI_RTR_TCP_ADDR: "0.0.0.0:323"
|
||||
RPKI_RTR_TLS_ADDR: "0.0.0.0:324"
|
||||
RPKI_RTR_DB_PATH: "${RPKI_RTR_DB_PATH:-/app/rtr-db}"
|
||||
RPKI_RTR_CCR_DIR: "${RPKI_RTR_CCR_DIR:-/app/data}"
|
||||
RPKI_RTR_SLURM_DIR: "${RPKI_RTR_SLURM_DIR:-/app/slurm}"
|
||||
RPKI_RTR_REPORT_DIR: "${RPKI_RTR_REPORT_DIR:-/app/report}"
|
||||
RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS: "${RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS:-300}"
|
||||
RPKI_RTR_REPORT_HISTORY_LIMIT: "${RPKI_RTR_REPORT_HISTORY_LIMIT:-10}"
|
||||
RPKI_RTR_TIMEZONE: "${RPKI_RTR_TIMEZONE:-Asia/Shanghai}"
|
||||
RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:-300}"
|
||||
RPKI_RTR_MAX_DELTA: "${RPKI_RTR_MAX_DELTA:-10}"
|
||||
RPKI_RTR_MAX_CONCURRENT_HANDSHAKES: "${RPKI_RTR_MAX_CONCURRENT_HANDSHAKES:-128}"
|
||||
RPKI_RTR_ADMIN_ADDR: "${RPKI_RTR_ADMIN_ADDR:-}"
|
||||
RPKI_RTR_ADMIN_TOKEN: "${RPKI_RTR_ADMIN_TOKEN:-}"
|
||||
RUST_LOG: "${RUST_LOG:-info}"
|
||||
# SSH mode example:
|
||||
# RPKI_RTR_ENABLE_SSH: "true"
|
||||
# RPKI_RTR_SSH_ADDR: "0.0.0.0:22"
|
||||
# RPKI_RTR_SSH_PORT: "22"
|
||||
# RPKI_RTR_SSH_HOST_KEY_PATH: "/app/certs/ssh_host_rsa_key"
|
||||
# RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH: "/app/certs/rtr-authorized_keys"
|
||||
# RPKI_RTR_SSH_USERNAME: "rpki-rtr"
|
||||
# RPKI_RTR_SSH_SUBSYSTEM_NAME: "rpki-rtr"
|
||||
# Optional: enable password auth in addition to publickey
|
||||
# RPKI_RTR_SSH_PASSWORD: "test-password"
|
||||
volumes:
|
||||
- ${RPKI_RTR_CCR_HOST_DIR:-../../data}:${RPKI_RTR_CCR_DIR:-/app/data}:ro
|
||||
- ${RPKI_RTR_DB_HOST_DIR:-../../rtr-db}:${RPKI_RTR_DB_PATH:-/app/rtr-db}
|
||||
- ${RPKI_RTR_SLURM_HOST_DIR:-../../data}:${RPKI_RTR_SLURM_DIR:-/app/slurm}
|
||||
- ${RPKI_RTR_LOG_HOST_DIR:-../../logs/server}:/app/logs
|
||||
- ${RPKI_RTR_REPORT_HOST_DIR:-../../report}:${RPKI_RTR_REPORT_DIR:-/app/report}
|
||||
# TLS mode example:
|
||||
# - ../../certs/ssh:/app/certs:ro
|
||||
networks:
|
||||
- rpki_net
|
||||
|
||||
networks:
|
||||
rpki_net:
|
||||
name: rpki_net
|
||||
driver: bridge
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
mkdir -p /app/logs
|
||||
|
||||
log_name="${HOSTNAME:-rpki-rtr}"
|
||||
stdout_log="/app/logs/${log_name}.stdout.log"
|
||||
stderr_log="/app/logs/${log_name}.stderr.log"
|
||||
|
||||
exec /usr/local/bin/rpki_rtr "$@" >>"$stdout_log" 2>>"$stderr_log"
|
||||
117
docker/ours-rp-metrics.Dockerfile
Normal file
117
docker/ours-rp-metrics.Dockerfile
Normal file
@@ -0,0 +1,117 @@
|
||||
ARG BUILDER_IMAGE=rust:1-bookworm
|
||||
ARG RUNTIME_IMAGE=debian:bookworm-slim
|
||||
|
||||
FROM --platform=$BUILDPLATFORM ${BUILDER_IMAGE} AS builder
|
||||
|
||||
ARG BUILDARCH
|
||||
ARG TARGETARCH
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
clang \
|
||||
cmake \
|
||||
git \
|
||||
libclang-dev \
|
||||
make \
|
||||
perl \
|
||||
pkg-config \
|
||||
python3 \
|
||||
&& if [ "$TARGETARCH" != "$BUILDARCH" ]; then \
|
||||
case "$TARGETARCH" in \
|
||||
arm64) \
|
||||
apt-get install -y --no-install-recommends \
|
||||
g++-aarch64-linux-gnu \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libc6-dev-arm64-cross \
|
||||
;; \
|
||||
amd64) \
|
||||
apt-get install -y --no-install-recommends \
|
||||
g++-x86-64-linux-gnu \
|
||||
gcc-x86-64-linux-gnu \
|
||||
libc6-dev-amd64-cross \
|
||||
;; \
|
||||
*) \
|
||||
echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 \
|
||||
;; \
|
||||
esac; \
|
||||
fi \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& case "$TARGETARCH" in \
|
||||
amd64|arm64) ;; \
|
||||
*) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 ;; \
|
||||
esac
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/usr/local/cargo/git \
|
||||
--mount=type=cache,target=/src/target \
|
||||
set -eux; \
|
||||
case "$TARGETARCH" in \
|
||||
amd64) target_triple=x86_64-unknown-linux-gnu ;; \
|
||||
arm64) target_triple=aarch64-unknown-linux-gnu ;; \
|
||||
*) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 ;; \
|
||||
esac; \
|
||||
rustup target add "$target_triple"; \
|
||||
if [ "$TARGETARCH" != "$BUILDARCH" ]; then \
|
||||
case "$TARGETARCH" in \
|
||||
arm64) \
|
||||
export \
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
|
||||
CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \
|
||||
CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ \
|
||||
AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar \
|
||||
PKG_CONFIG_ALLOW_CROSS=1 \
|
||||
;; \
|
||||
amd64) \
|
||||
export \
|
||||
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc \
|
||||
CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc \
|
||||
CXX_x86_64_unknown_linux_gnu=x86_64-linux-gnu-g++ \
|
||||
AR_x86_64_unknown_linux_gnu=x86_64-linux-gnu-ar \
|
||||
PKG_CONFIG_ALLOW_CROSS=1 \
|
||||
;; \
|
||||
*) \
|
||||
echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 \
|
||||
;; \
|
||||
esac; \
|
||||
fi; \
|
||||
cargo build --release --target "$target_triple" --bin rpki_artifact_metrics; \
|
||||
mkdir -p /build-out/bin; \
|
||||
cp "target/$target_triple/release/rpki_artifact_metrics" /build-out/bin/
|
||||
|
||||
FROM --platform=$TARGETPLATFORM ${RUNTIME_IMAGE} AS runtime
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /opt/ours-rp
|
||||
|
||||
COPY --from=builder /build-out/bin/ /opt/ours-rp/bin/
|
||||
|
||||
ARG SOURCE_COMMIT=unknown
|
||||
ARG SOURCE_DIRTY=unknown
|
||||
ARG BUILD_TIMESTAMP_UTC=unknown
|
||||
|
||||
LABEL org.opencontainers.image.title="ours-rp-metrics" \
|
||||
org.opencontainers.image.description="Ours RP artifact metrics image for multi-arch Docker Compose deployment" \
|
||||
org.opencontainers.image.revision="${SOURCE_COMMIT}" \
|
||||
org.opencontainers.image.created="${BUILD_TIMESTAMP_UTC}" \
|
||||
org.opencontainers.image.source-dirty="${SOURCE_DIRTY}"
|
||||
|
||||
RUN chmod +x /opt/ours-rp/bin/* \
|
||||
&& mkdir -p /var/lib/ours-rp/state /var/lib/ours-rp/runs /var/lib/ours-rp/logs
|
||||
|
||||
ENV RUN_ROOT=/var/lib/ours-rp \
|
||||
BIN_DIR=/opt/ours-rp/bin \
|
||||
RUST_BACKTRACE=1
|
||||
|
||||
VOLUME ["/var/lib/ours-rp"]
|
||||
145
docker/ours-rp-runtime.Dockerfile
Normal file
145
docker/ours-rp-runtime.Dockerfile
Normal file
@@ -0,0 +1,145 @@
|
||||
ARG BUILDER_IMAGE=rust:1-bookworm
|
||||
ARG RUNTIME_IMAGE=debian:bookworm-slim
|
||||
|
||||
FROM --platform=$BUILDPLATFORM ${BUILDER_IMAGE} AS builder
|
||||
|
||||
ARG BUILDARCH
|
||||
ARG TARGETARCH
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
clang \
|
||||
cmake \
|
||||
git \
|
||||
libclang-dev \
|
||||
make \
|
||||
perl \
|
||||
pkg-config \
|
||||
python3 \
|
||||
&& if [ "$TARGETARCH" != "$BUILDARCH" ]; then \
|
||||
case "$TARGETARCH" in \
|
||||
arm64) \
|
||||
apt-get install -y --no-install-recommends \
|
||||
g++-aarch64-linux-gnu \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libc6-dev-arm64-cross \
|
||||
;; \
|
||||
amd64) \
|
||||
apt-get install -y --no-install-recommends \
|
||||
g++-x86-64-linux-gnu \
|
||||
gcc-x86-64-linux-gnu \
|
||||
libc6-dev-amd64-cross \
|
||||
;; \
|
||||
*) \
|
||||
echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 \
|
||||
;; \
|
||||
esac; \
|
||||
fi \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& case "$TARGETARCH" in \
|
||||
amd64|arm64) ;; \
|
||||
*) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 ;; \
|
||||
esac
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=/usr/local/cargo/git \
|
||||
--mount=type=cache,target=/src/target \
|
||||
set -eux; \
|
||||
case "$TARGETARCH" in \
|
||||
amd64) target_triple=x86_64-unknown-linux-gnu ;; \
|
||||
arm64) target_triple=aarch64-unknown-linux-gnu ;; \
|
||||
*) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 ;; \
|
||||
esac; \
|
||||
rustup target add "$target_triple"; \
|
||||
if [ "$TARGETARCH" != "$BUILDARCH" ]; then \
|
||||
case "$TARGETARCH" in \
|
||||
arm64) \
|
||||
export \
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
|
||||
CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \
|
||||
CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ \
|
||||
AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar \
|
||||
PKG_CONFIG_ALLOW_CROSS=1 \
|
||||
;; \
|
||||
amd64) \
|
||||
export \
|
||||
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc \
|
||||
CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc \
|
||||
CXX_x86_64_unknown_linux_gnu=x86_64-linux-gnu-g++ \
|
||||
AR_x86_64_unknown_linux_gnu=x86_64-linux-gnu-ar \
|
||||
PKG_CONFIG_ALLOW_CROSS=1 \
|
||||
;; \
|
||||
*) \
|
||||
echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 2 \
|
||||
;; \
|
||||
esac; \
|
||||
fi; \
|
||||
cargo build --release --target "$target_triple" \
|
||||
--bin rpki \
|
||||
--bin rpki_daemon \
|
||||
--bin db_stats; \
|
||||
mkdir -p /build-out/bin; \
|
||||
cp \
|
||||
"target/$target_triple/release/rpki" \
|
||||
"target/$target_triple/release/rpki_daemon" \
|
||||
"target/$target_triple/release/db_stats" \
|
||||
/build-out/bin/
|
||||
|
||||
FROM --platform=$TARGETPLATFORM ${RUNTIME_IMAGE} AS runtime
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
coreutils \
|
||||
curl \
|
||||
findutils \
|
||||
iputils-ping \
|
||||
jq \
|
||||
procps \
|
||||
python3 \
|
||||
rsync \
|
||||
time \
|
||||
tzdata \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /opt/ours-rp
|
||||
|
||||
COPY --from=builder /build-out/bin/ /opt/ours-rp/bin/
|
||||
COPY scripts/soak/run_soak.sh /opt/ours-rp/run_soak.sh
|
||||
COPY scripts/soak/portable-soak.env.example /opt/ours-rp/portable-soak.env.example
|
||||
COPY tests/fixtures/tal/ /opt/ours-rp/fixtures/tal/
|
||||
COPY tests/fixtures/ta/ /opt/ours-rp/fixtures/ta/
|
||||
COPY fixtures/live_20260619/tal/ /opt/ours-rp/fixtures/live_20260619/tal/
|
||||
COPY fixtures/live_20260619/ta/ /opt/ours-rp/fixtures/live_20260619/ta/
|
||||
|
||||
ARG SOURCE_COMMIT=unknown
|
||||
ARG SOURCE_DIRTY=unknown
|
||||
ARG BUILD_TIMESTAMP_UTC=unknown
|
||||
|
||||
LABEL org.opencontainers.image.title="ours-rp-runtime" \
|
||||
org.opencontainers.image.description="Ours RP runtime image for multi-arch Docker Compose deployment" \
|
||||
org.opencontainers.image.revision="${SOURCE_COMMIT}" \
|
||||
org.opencontainers.image.created="${BUILD_TIMESTAMP_UTC}" \
|
||||
org.opencontainers.image.source-dirty="${SOURCE_DIRTY}"
|
||||
|
||||
RUN chmod +x /opt/ours-rp/run_soak.sh /opt/ours-rp/bin/* \
|
||||
&& mkdir -p /var/lib/ours-rp/state /var/lib/ours-rp/runs /var/lib/ours-rp/logs /var/lib/ours-rp/tmp
|
||||
|
||||
ENV PACKAGE_ROOT=/opt/ours-rp \
|
||||
ENV_FILE=/opt/ours-rp/.env \
|
||||
RUN_ROOT=/var/lib/ours-rp \
|
||||
BIN_DIR=/opt/ours-rp/bin \
|
||||
FIXTURE_DIR=/opt/ours-rp/fixtures \
|
||||
RUST_BACKTRACE=1
|
||||
|
||||
VOLUME ["/var/lib/ours-rp"]
|
||||
|
||||
CMD ["/opt/ours-rp/run_soak.sh"]
|
||||
@@ -1,310 +0,0 @@
|
||||
# RTR Admin API
|
||||
|
||||
本文档描述 RTR server 当前提供的 HTTP 管理接口。
|
||||
|
||||
## 启用方式
|
||||
|
||||
Admin API 默认关闭。设置 `RPKI_RTR_ADMIN_ADDR` 后启用:
|
||||
|
||||
```env
|
||||
RPKI_RTR_ADMIN_ADDR=127.0.0.1:8323
|
||||
RPKI_RTR_ADMIN_TOKEN=change-me
|
||||
```
|
||||
|
||||
如果在 Docker 中需要从宿主机访问,容器内建议监听:
|
||||
|
||||
```env
|
||||
RPKI_RTR_ADMIN_ADDR=0.0.0.0:8323
|
||||
RPKI_RTR_ADMIN_TOKEN=change-me
|
||||
```
|
||||
|
||||
并在 compose 中映射端口:
|
||||
|
||||
```yaml
|
||||
ports:
|
||||
- "8323:8323"
|
||||
```
|
||||
|
||||
安全规则:
|
||||
|
||||
- `RPKI_RTR_ADMIN_ADDR` 为空时,不启动 Admin API。
|
||||
- 非 loopback 地址,例如 `0.0.0.0:8323`,必须配置 `RPKI_RTR_ADMIN_TOKEN`,否则 admin server 会拒绝启动。
|
||||
- 设置 token 后,请求必须带 `Authorization: Bearer <token>`。
|
||||
|
||||
## 通用 Headers
|
||||
|
||||
```http
|
||||
Authorization: Bearer change-me
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
GET 请求不需要 `Content-Type`。
|
||||
|
||||
## GET /admin/rtr/health
|
||||
|
||||
用于检查 Admin API 是否可达,以及当前启用了哪些管理能力。
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8323/admin/rtr/health \
|
||||
-H "Authorization: Bearer change-me"
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"config_api": true,
|
||||
"source_reload_api": true,
|
||||
"slurm_api": true,
|
||||
"logs_api": true
|
||||
}
|
||||
```
|
||||
|
||||
## GET /admin/rtr/logs/tail
|
||||
|
||||
实时读取 RTR 服务日志,效果类似 `tail -f`。默认读取 stdout 日志,先返回最近 200 行,然后持续输出新增内容。
|
||||
|
||||
```bash
|
||||
curl -N "http://127.0.0.1:8323/admin/rtr/logs/tail?stream=stdout&lines=200&follow=true" \
|
||||
-H "Authorization: Bearer change-me"
|
||||
```
|
||||
|
||||
Query 参数:
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `stream` | `stdout` | 可选 `stdout` 或 `stderr`。 |
|
||||
| `lines` | `200` | 首次返回的历史行数,范围会限制在 `1..=5000`。 |
|
||||
| `follow` | `true` | `true` 时保持连接并持续输出新增日志;`false` 时只返回当前 tail 内容。 |
|
||||
|
||||
日志文件路径按容器 entrypoint 的规则解析:
|
||||
|
||||
- 目录:`RPKI_RTR_LOG_DIR`,默认 `/app/logs`。
|
||||
- 文件名前缀:`RPKI_RTR_LOG_NAME`,未设置时使用 `HOSTNAME`,再未设置时为 `rpki-rtr`。
|
||||
- stdout 文件:`<name>.stdout.log`。
|
||||
- stderr 文件:`<name>.stderr.log`。
|
||||
|
||||
## GET /admin/rtr/config
|
||||
|
||||
查询当前运行中的 runtime 配置。
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8323/admin/rtr/config \
|
||||
-H "Authorization: Bearer change-me"
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"config": {
|
||||
"max_delta": 10,
|
||||
"prune_delta_by_snapshot_size": true,
|
||||
"source_refresh_interval_seconds": 300,
|
||||
"runtime_report_interval_seconds": 300,
|
||||
"report_history_limit": 10,
|
||||
"timezone": "Asia/Shanghai",
|
||||
"timing": {
|
||||
"refresh": 3600,
|
||||
"retry": 600,
|
||||
"expire": 7200
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## POST /admin/rtr/config
|
||||
|
||||
运行中动态修改 RTR server 的部分 runtime 配置。请求体是 JSON object,支持部分更新,只需要传要修改的字段。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `max_delta` | integer | 每个 RTR 版本最多保留的 delta 条数,必须 `>= 1`。 |
|
||||
| `prune_delta_by_snapshot_size` | boolean | 是否按 snapshot wire size 裁剪 delta window。 |
|
||||
| `source_refresh_interval_seconds` | integer | CCR/SLURM source refresh 间隔,单位秒,必须 `>= 1`。 |
|
||||
| `runtime_report_interval_seconds` | integer | `rtr-runtime-*.json` 周期写入间隔,单位秒,必须 `>= 1`。 |
|
||||
| `report_history_limit` | integer | 每类 report 文件滚动保留数量,必须 `>= 1`。 |
|
||||
| `timezone` | string | IANA 时区名,例如 `Asia/Shanghai`、`UTC`、`Europe/London`。 |
|
||||
| `timing.refresh` | integer | RTR EndOfData `refresh`,单位秒。 |
|
||||
| `timing.retry` | integer | RTR EndOfData `retry`,单位秒。 |
|
||||
| `timing.expire` | integer | RTR EndOfData `expire`,必须大于 `refresh` 和 `retry`。 |
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8323/admin/rtr/config \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer change-me" \
|
||||
-d '{"max_delta": 6}'
|
||||
```
|
||||
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"config": {
|
||||
"max_delta": 6,
|
||||
"prune_delta_by_snapshot_size": true,
|
||||
"source_refresh_interval_seconds": 300,
|
||||
"runtime_report_interval_seconds": 300,
|
||||
"report_history_limit": 10,
|
||||
"timezone": "Asia/Shanghai",
|
||||
"timing": {
|
||||
"refresh": 3600,
|
||||
"retry": 600,
|
||||
"expire": 7200
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## GET /admin/rtr/slurm/files
|
||||
|
||||
列出 `RPKI_RTR_SLURM_DIR` 下的 SLURM 文件。只返回合法的 `*.slurm` 和 `*.slurm.disabled` 文件,不返回 `.backup/`。
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8323/admin/rtr/slurm/files \
|
||||
-H "Authorization: Bearer change-me"
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"files": [
|
||||
{
|
||||
"name": "local-policy.slurm",
|
||||
"path": "/app/slurm/local-policy.slurm",
|
||||
"enabled": true,
|
||||
"size_bytes": 168,
|
||||
"modified_unix_seconds": 1782100000
|
||||
},
|
||||
{
|
||||
"name": "old-policy.slurm.disabled",
|
||||
"path": "/app/slurm/old-policy.slurm.disabled",
|
||||
"enabled": false,
|
||||
"size_bytes": 168,
|
||||
"modified_unix_seconds": 1782100100
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## GET /admin/rtr/slurm/files/{name}
|
||||
|
||||
读取单个 SLURM 文件,前端编辑页面可以直接使用这个接口加载内容。
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8323/admin/rtr/slurm/files/local-policy.slurm \
|
||||
-H "Authorization: Bearer change-me"
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"file": {
|
||||
"name": "local-policy.slurm",
|
||||
"path": "/app/slurm/local-policy.slurm",
|
||||
"enabled": true,
|
||||
"size_bytes": 168,
|
||||
"modified_unix_seconds": 1782100000,
|
||||
"content": "{ \"slurmVersion\": 1, \"validationOutputFilters\": { \"prefixFilters\": [], \"bgpsecFilters\": [] }, \"locallyAddedAssertions\": { \"prefixAssertions\": [], \"bgpsecAssertions\": [] } }"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## POST /admin/rtr/slurm/files
|
||||
|
||||
新增或覆盖一个 SLURM 文件。文件名放在请求体中。
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "local-policy.slurm",
|
||||
"content": "{ \"slurmVersion\": 1, \"validationOutputFilters\": { \"prefixFilters\": [], \"bgpsecFilters\": [] }, \"locallyAddedAssertions\": { \"prefixAssertions\": [], \"bgpsecAssertions\": [] } }",
|
||||
"reload": true
|
||||
}
|
||||
```
|
||||
|
||||
## PUT /admin/rtr/slurm/files/{name}
|
||||
|
||||
新增或覆盖指定 SLURM 文件。
|
||||
|
||||
```bash
|
||||
curl -X PUT http://127.0.0.1:8323/admin/rtr/slurm/files/local-policy.slurm \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer change-me" \
|
||||
-d '{
|
||||
"content": "{ \"slurmVersion\": 1, \"validationOutputFilters\": { \"prefixFilters\": [], \"bgpsecFilters\": [] }, \"locallyAddedAssertions\": { \"prefixAssertions\": [], \"bgpsecAssertions\": [] } }",
|
||||
"reload": true
|
||||
}'
|
||||
```
|
||||
|
||||
## DELETE /admin/rtr/slurm/files/{name}
|
||||
|
||||
删除指定 SLURM 文件。删除前会先备份。
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://127.0.0.1:8323/admin/rtr/slurm/files/local-policy.slurm?reload=true" \
|
||||
-H "Authorization: Bearer change-me"
|
||||
```
|
||||
|
||||
## POST /admin/rtr/slurm/files/{name}/disable
|
||||
|
||||
将启用文件改名为失效文件:
|
||||
|
||||
```text
|
||||
local-policy.slurm -> local-policy.slurm.disabled
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -X POST "http://127.0.0.1:8323/admin/rtr/slurm/files/local-policy.slurm/disable?reload=true" \
|
||||
-H "Authorization: Bearer change-me"
|
||||
```
|
||||
|
||||
## POST /admin/rtr/slurm/files/{name}/enable
|
||||
|
||||
将失效文件改名为启用文件:
|
||||
|
||||
```text
|
||||
local-policy.slurm.disabled -> local-policy.slurm
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -X POST "http://127.0.0.1:8323/admin/rtr/slurm/files/local-policy.slurm.disabled/enable?reload=true" \
|
||||
-H "Authorization: Bearer change-me"
|
||||
```
|
||||
|
||||
## POST /admin/rtr/slurm/reload
|
||||
|
||||
不修改文件,只立即触发一次 CCR/SLURM source reload。
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8323/admin/rtr/slurm/reload \
|
||||
-H "Authorization: Bearer change-me"
|
||||
```
|
||||
|
||||
## SLURM 文件规则
|
||||
|
||||
- `*.slurm`:启用,会被 source refresh 加载。
|
||||
- `*.slurm.disabled`:失效,不会被加载。
|
||||
- `.backup/`:接口自动生成的备份目录。
|
||||
- 文件名只能是 basename,不能包含 `/`、`\` 或 `..`。
|
||||
- 文件名只能包含 ASCII 字母、数字、`.`、`_`、`-`。
|
||||
- 写入内容必须是合法 JSON,并且能被解析为合法 SLURM 文件。
|
||||
- 单个请求体默认限制为 5 MiB。
|
||||
- 覆盖、删除前会写入 `.backup/` 备份。
|
||||
|
||||
## 错误响应
|
||||
|
||||
| HTTP 状态码 | 含义 |
|
||||
| --- | --- |
|
||||
| `400 Bad Request` | JSON 非法、配置值非法、SLURM 文件名非法、目标文件不存在、reload 失败等。 |
|
||||
| `401 Unauthorized` | 配置了 token,但请求缺少或使用了错误的 `Authorization`。 |
|
||||
| `404 Not Found` | 路径或方法不支持。 |
|
||||
| `413 Payload Too Large` | 请求体超过限制。 |
|
||||
80
fixtures/live_20260619/manifest.json
Normal file
80
fixtures/live_20260619/manifest.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"created_at_utc": "2026-06-19T08:08:03Z",
|
||||
"items": [
|
||||
{
|
||||
"rir": "afrinic",
|
||||
"ta_bytes": 1216,
|
||||
"ta_download": "200 1216 1.351147",
|
||||
"ta_elapsed_s": 1.227,
|
||||
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/afrinic-ta.cer",
|
||||
"ta_sha256": "43a26fd28bafb9398e5b2ab19e036b450bd04f4973a7f5ad151cebdee0edac36",
|
||||
"ta_uri": "https://rpki.afrinic.net/repository/AfriNIC.cer",
|
||||
"tal_bytes": 496,
|
||||
"tal_download": "200 496 1.361326",
|
||||
"tal_elapsed_s": 1.238,
|
||||
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/afrinic.tal",
|
||||
"tal_sha256": "2838ef30ea27ce5705abf5f5adb131d8c35b1f50858338a2f3c84bb207c2fa35",
|
||||
"tal_url": "https://rpki.afrinic.net/tal/afrinic.tal"
|
||||
},
|
||||
{
|
||||
"rir": "apnic",
|
||||
"ta_bytes": 1222,
|
||||
"ta_download": "200 1222 1.012321",
|
||||
"ta_elapsed_s": 0.921,
|
||||
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/apnic-ta.cer",
|
||||
"ta_sha256": "2014230ad49b2777ac2bde0948ddfa4b8f207114c549e26d755de88c3593e3af",
|
||||
"ta_uri": "https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer",
|
||||
"tal_bytes": 532,
|
||||
"tal_download": "200 466 1.007155",
|
||||
"tal_elapsed_s": 0.917,
|
||||
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/apnic.tal",
|
||||
"tal_sha256": "472e551f7c551c2e999e582b7c9437d3bee4900fe53afff62aeb28d4940ade94",
|
||||
"tal_url": "https://tal.apnic.net/apnic.tal"
|
||||
},
|
||||
{
|
||||
"rir": "arin",
|
||||
"ta_bytes": 1143,
|
||||
"ta_download": "200 1143 0.816714",
|
||||
"ta_elapsed_s": 0.743,
|
||||
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/arin-ta.cer",
|
||||
"ta_sha256": "5b3c2f6f04abd19261084487a43c156f778b0b8926d63801d48c7a93ed349492",
|
||||
"ta_uri": "https://rrdp.arin.net/arin-rpki-ta.cer",
|
||||
"tal_bytes": 1258,
|
||||
"tal_download": "200 1258 0.848581",
|
||||
"tal_elapsed_s": 0.774,
|
||||
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/arin.tal",
|
||||
"tal_sha256": "1f8bdb03bcc30a3b8e11fd9a87102fba250c22137a3c8baa9c81b139cb412639",
|
||||
"tal_url": "https://www.arin.net/resources/manage/rpki/arin.tal"
|
||||
},
|
||||
{
|
||||
"rir": "lacnic",
|
||||
"ta_bytes": 1166,
|
||||
"ta_download": "200 1166 1.025273",
|
||||
"ta_elapsed_s": 0.932,
|
||||
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/lacnic-ta.cer",
|
||||
"ta_sha256": "f44bc51008fd6998de7597b72a79b07bf3ebcb0f14daa7c7c022c0e9d66e0ad0",
|
||||
"ta_uri": "https://rrdp.lacnic.net/ta/rta-lacnic-rpki.cer",
|
||||
"tal_bytes": 502,
|
||||
"tal_download": "200 502 1.729122",
|
||||
"tal_elapsed_s": 1.565,
|
||||
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/lacnic.tal",
|
||||
"tal_sha256": "d44bb9394ab009c8b53e5efebf2a1c9450bab61a27efe00de5a3e4587a3a2f6a",
|
||||
"tal_url": "https://www.lacnic.net/innovaportal/file/4983/1/lacnic.tal"
|
||||
},
|
||||
{
|
||||
"rir": "ripe",
|
||||
"ta_bytes": 1036,
|
||||
"ta_download": "200 1036 1.060992",
|
||||
"ta_elapsed_s": 4.536,
|
||||
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/ripe-ncc-ta.cer",
|
||||
"ta_sha256": "3e3f7e4efc8d0cea03d9cc1fde6e168b45c26d7b3272e14abd8da4871886e539",
|
||||
"ta_uri": "https://rpki.ripe.net/ta/ripe-ncc-ta.cer",
|
||||
"tal_bytes": 482,
|
||||
"tal_download": "200 482 1.092082",
|
||||
"tal_elapsed_s": 0.992,
|
||||
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/ripe-ncc.tal",
|
||||
"tal_sha256": "59ca27ef93f23682749fcefe7c6d70fbc723343549ff9e4d3996acaff79817fb",
|
||||
"tal_url": "https://tal.rpki.ripe.net/ripe-ncc.tal"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
fixtures/live_20260619/ta/afrinic-ta.cer
Normal file
BIN
fixtures/live_20260619/ta/afrinic-ta.cer
Normal file
Binary file not shown.
BIN
fixtures/live_20260619/ta/apnic-ta.cer
Normal file
BIN
fixtures/live_20260619/ta/apnic-ta.cer
Normal file
Binary file not shown.
BIN
fixtures/live_20260619/ta/arin-ta.cer
Normal file
BIN
fixtures/live_20260619/ta/arin-ta.cer
Normal file
Binary file not shown.
BIN
fixtures/live_20260619/ta/lacnic-ta.cer
Normal file
BIN
fixtures/live_20260619/ta/lacnic-ta.cer
Normal file
Binary file not shown.
BIN
fixtures/live_20260619/ta/ripe-ncc-ta.cer
Normal file
BIN
fixtures/live_20260619/ta/ripe-ncc-ta.cer
Normal file
Binary file not shown.
10
fixtures/live_20260619/tal/afrinic.tal
Normal file
10
fixtures/live_20260619/tal/afrinic.tal
Normal file
@@ -0,0 +1,10 @@
|
||||
rsync://rpki.afrinic.net/repository/AfriNIC.cer
|
||||
https://rpki.afrinic.net/repository/AfriNIC.cer
|
||||
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxsAqAhWIO+ON2Ef9oRDM
|
||||
pKxv+AfmSLIdLWJtjrvUyDxJPBjgR+kVrOHUeTaujygFUp49tuN5H2C1rUuQavTH
|
||||
vve6xNF5fU3OkTcqEzMOZy+ctkbde2SRMVdvbO22+TH9gNhKDc9l7Vu01qU4LeJH
|
||||
k3X0f5uu5346YrGAOSv6AaYBXVgXxa0s9ZvgqFpim50pReQe/WI3QwFKNgpPzfQL
|
||||
6Y7fDPYdYaVOXPXSKtx7P4s4KLA/ZWmRL/bobw/i2fFviAGhDrjqqqum+/9w1hEl
|
||||
L/vqihVnV18saKTnLvkItA/Bf5i11Yhw2K7qv573YWxyuqCknO/iYLTR1DToBZcZ
|
||||
UQIDAQAB
|
||||
10
fixtures/live_20260619/tal/apnic-rfc7730-https.tal
Normal file
10
fixtures/live_20260619/tal/apnic-rfc7730-https.tal
Normal file
@@ -0,0 +1,10 @@
|
||||
https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer
|
||||
rsync://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer
|
||||
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx9RWSL61YAAYumEiU8z8
|
||||
qH2ETVIL01ilxZlzIL9JYSORMN5Cmtf8V2JblIealSqgOTGjvSjEsiV73s67zYQI
|
||||
7C/iSOb96uf3/s86NqbxDiFQGN8qG7RNcdgVuUlAidl8WxvLNI8VhqbAB5uSg/Mr
|
||||
LeSOvXRja041VptAxIhcGzDMvlAJRwkrYK/Mo8P4E2rSQgwqCgae0ebY1CsJ3Cjf
|
||||
i67C1nw7oXqJJovvXJ4apGmEv8az23OLC6Ki54Ul/E6xk227BFttqFV3YMtKx42H
|
||||
cCcDVZZy01n7JjzvO8ccaXmHIgR7utnqhBRNNq5Xc5ZhbkrUsNtiJmrZzVlgU6Ou
|
||||
0wIDAQAB
|
||||
10
fixtures/live_20260619/tal/apnic.tal
Normal file
10
fixtures/live_20260619/tal/apnic.tal
Normal file
@@ -0,0 +1,10 @@
|
||||
https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer
|
||||
rsync://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer
|
||||
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx9RWSL61YAAYumEiU8z8
|
||||
qH2ETVIL01ilxZlzIL9JYSORMN5Cmtf8V2JblIealSqgOTGjvSjEsiV73s67zYQI
|
||||
7C/iSOb96uf3/s86NqbxDiFQGN8qG7RNcdgVuUlAidl8WxvLNI8VhqbAB5uSg/Mr
|
||||
LeSOvXRja041VptAxIhcGzDMvlAJRwkrYK/Mo8P4E2rSQgwqCgae0ebY1CsJ3Cjf
|
||||
i67C1nw7oXqJJovvXJ4apGmEv8az23OLC6Ki54Ul/E6xk227BFttqFV3YMtKx42H
|
||||
cCcDVZZy01n7JjzvO8ccaXmHIgR7utnqhBRNNq5Xc5ZhbkrUsNtiJmrZzVlgU6Ou
|
||||
0wIDAQAB
|
||||
19
fixtures/live_20260619/tal/arin.tal
Normal file
19
fixtures/live_20260619/tal/arin.tal
Normal file
@@ -0,0 +1,19 @@
|
||||
# THIS TRUST ANCHOR LOCATOR IS PROVIDED BY THE AMERICAN REGISTRY FOR
|
||||
# INTERNET NUMBERS (ARIN) "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL ARIN BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS PUBLIC KEY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
rsync://rpki.arin.net/repository/arin-rpki-ta.cer
|
||||
https://rrdp.arin.net/arin-rpki-ta.cer
|
||||
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3lZPjbHvMRV5sDDqfLc/685th5FnreHMJjg8
|
||||
pEZUbG8Y8TQxSBsDebbsDpl3Ov3Cj1WtdrJ3CIfQODCPrrJdOBSrMATeUbPC+JlNf2SRP3UB+VJFgtTj
|
||||
0RN8cEYIuhBW5t6AxQbHhdNQH+A1F/OJdw0q9da2U29Lx85nfFxvnC1EpK9CbLJS4m37+RlpNbT1cba+
|
||||
b+loXpx0Qcb1C4UpJCGDy7uNf5w6/+l7RpATAHqqsX4qCtwwDYlbHzp2xk9owF3mkCxzl0HwncO+sEHH
|
||||
eaL3OjtwdIGrRGeHi2Mpt+mvWHhtQqVG+51MHTyg+nIjWFKKGx1Q9+KDx4wJStwveQIDAQAB
|
||||
4
fixtures/live_20260619/tal/lacnic.tal
Normal file
4
fixtures/live_20260619/tal/lacnic.tal
Normal file
@@ -0,0 +1,4 @@
|
||||
https://rrdp.lacnic.net/ta/rta-lacnic-rpki.cer
|
||||
rsync://repository.lacnic.net/rpki/lacnic/rta-lacnic-rpki.cer
|
||||
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqZEzhYK0+PtDOPfub/KRc3MeWx3neXx4/wbnJWGbNAtbYqXg3uU5J4HFzPgk/VIppgSKAhlO0H60DRP48by9gr5/yDHu2KXhOmnMg46sYsUIpfgtBS9+VtrqWziJfb+pkGtuOWeTnj6zBmBNZKK+5AlMCW1WPhrylIcB+XSZx8tk9GS/3SMQ+YfMVwwAyYjsex14Uzto4GjONALE5oh1M3+glRQduD6vzSwOD+WahMbc9vCOTED+2McLHRKgNaQf0YJ9a1jG9oJIvDkKXEqdfqDRktwyoD74cV57bW3tBAexB7GglITbInyQAsmdngtfg2LUMrcROHHP86QPZINjDQIDAQAB
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user