feat: complete phase2 baseline cdr quality rbac
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
# 业务前缀重构第 8 步迁移与发布前验收 Runbook
|
||||
|
||||
## 范围
|
||||
|
||||
本文用于业务前缀、客户网关匹配和落地号码改写重构的发布前准备。当前仍以本地 KVM/Tailscale 环境为准,不进入阿里云生产迁移。
|
||||
|
||||
本步骤覆盖:
|
||||
|
||||
- B 执行新增 Prisma migration 前后的检查。
|
||||
- 旧客户网关单 IP 和旧 `customer_gateway_policies` 到二期模型的安全迁移。
|
||||
- Redis 配置发布检查。
|
||||
- A OpenSIPS/Lua 替换前语法检查和回滚点。
|
||||
- A/B/T 端到端验收清单。
|
||||
|
||||
## 影响、风险与回滚点
|
||||
|
||||
影响范围:
|
||||
|
||||
- MySQL:新增二期表/列后,`customer_gateways.line_group_id`、`customer_gateway_ips`、`customer_gateway_caller_prefixes` 会被迁移脚本写入。
|
||||
- Redis:迁移脚本成功应用后会写 `customer_gateway_config` outbox,config-publisher 会发布新版本配置。
|
||||
- OpenSIPS:后续发布 A 侧 `opensips.cfg` 与 `lisglosips_hotpath.lua` 后,INVITE 鉴权、路由、主被叫改写进入新热路径。
|
||||
- CDR:第 7 步已让 Worker 可写入原始被叫、业务前缀、落地主被叫字段。
|
||||
|
||||
主要风险:
|
||||
|
||||
- 多条旧策略无法自动合并到“一个客户网关一个线路组”,必须人工拆分或确认保留规则。
|
||||
- 旧 `calleeMode=EQUALS/PREFIX` 不能安全推断为业务前缀,必须先在“业务前缀管理”中建立前缀并重新绑定客户网关。
|
||||
- 旧 `callerMode=EQUALS` 在新模型下没有严格等值语义,不能自动迁移为前缀,否则可能扩大匹配范围。
|
||||
- A 侧 OpenSIPS 配置替换会影响现有通话,必须维护窗口或确认可接受。
|
||||
|
||||
回滚点:
|
||||
|
||||
- B 数据库:执行 migration 前必须按 S30 Runbook 备份 MySQL;迁移脚本 apply 前需保存 dry-run 报告。
|
||||
- B release:保留当前 `/opt/lisglosips/current` 指向,可通过 S30 rollback 脚本切回旧 release。
|
||||
- Redis:记录 `cfg:active_version` 与 `cfg:previous_version`,必要时切回上一版本。
|
||||
- A OpenSIPS:替换前备份 `/etc/opensips/opensips.cfg` 与 `/etc/opensips/lisglosips_hotpath.lua`;语法检查失败不得重启;重启后失败则恢复备份并再次 `opensips -C`。
|
||||
|
||||
## 迁移脚本
|
||||
|
||||
本仓库新增:
|
||||
|
||||
```bash
|
||||
scripts/phase2-gateway-migration.mjs
|
||||
```
|
||||
|
||||
默认 dry-run,不写库:
|
||||
|
||||
```bash
|
||||
node scripts/phase2-gateway-migration.mjs --json
|
||||
```
|
||||
|
||||
显式 apply 才写库:
|
||||
|
||||
```bash
|
||||
node scripts/phase2-gateway-migration.mjs --apply --json
|
||||
```
|
||||
|
||||
脚本只自动迁移满足以下条件的客户网关:
|
||||
|
||||
- 每个客户网关恰好一条启用旧策略。
|
||||
- 旧策略 `calleeMode=ANY`。
|
||||
- 旧策略 `callerMode=ANY`,或 `callerMode=PREFIX` 且 `callerValue` 非空。
|
||||
|
||||
脚本会拒绝自动迁移以下情况:
|
||||
|
||||
- 多条启用旧策略。
|
||||
- 没有启用旧策略。
|
||||
- 旧被叫等值或前缀匹配。
|
||||
- 旧主叫等值匹配。
|
||||
- 新旧线路组字段冲突。
|
||||
|
||||
apply 成功后,脚本会写入一个 `customer_gateway_config` outbox,用于触发配置发布。
|
||||
|
||||
## B 侧发布前步骤
|
||||
|
||||
1. 记录状态:
|
||||
|
||||
```bash
|
||||
readlink -f /opt/lisglosips/current
|
||||
mysql -NBe "SELECT migration_name, finished_at FROM _prisma_migrations ORDER BY finished_at DESC LIMIT 5;"
|
||||
redis-cli --no-auth-warning GET cfg:active_version
|
||||
```
|
||||
|
||||
2. 执行 S30 备份:
|
||||
|
||||
```bash
|
||||
sudo /opt/lisglosips/current/infra/server-b/s30/lisglosips-release-preflight.sh
|
||||
sudo /usr/local/sbin/lisglosips-mysql-backup
|
||||
sudo /usr/local/sbin/lisglosips-redis-backup
|
||||
```
|
||||
|
||||
3. 部署新 release 到独立目录,不在线修改 current。
|
||||
|
||||
4. 执行数据库 migration:
|
||||
|
||||
```bash
|
||||
export DATABASE_URL='mysql://<MIGRATE_USER>@127.0.0.1:3306/lisglosips'
|
||||
pnpm exec prisma migrate deploy
|
||||
pnpm exec prisma migrate status
|
||||
```
|
||||
|
||||
5. 迁移脚本 dry-run:
|
||||
|
||||
```bash
|
||||
export DATABASE_URL='mysql://<MIGRATE_USER>@127.0.0.1:3306/lisglosips'
|
||||
node scripts/phase2-gateway-migration.mjs --json > /tmp/phase2-gateway-migration.dry-run.json
|
||||
```
|
||||
|
||||
6. 若 `blockers` 非空,先人工处理,不执行 apply。
|
||||
|
||||
7. 确认 dry-run 后执行 apply:
|
||||
|
||||
```bash
|
||||
node scripts/phase2-gateway-migration.mjs --apply --json > /tmp/phase2-gateway-migration.apply.json
|
||||
```
|
||||
|
||||
8. 等待或触发 config-publisher,检查新配置发布:
|
||||
|
||||
```bash
|
||||
systemctl status lisglosips@config-publisher --no-pager
|
||||
redis-cli --no-auth-warning GET cfg:active_version
|
||||
```
|
||||
|
||||
## A 侧 OpenSIPS 发布前步骤
|
||||
|
||||
1. 备份:
|
||||
|
||||
```bash
|
||||
sudo install -d -m 0750 /var/backups/lisglosips-phase2
|
||||
sudo cp -a /etc/opensips/opensips.cfg /var/backups/lisglosips-phase2/opensips.cfg.$(date +%Y%m%d%H%M%S)
|
||||
sudo cp -a /etc/opensips/lisglosips_hotpath.lua /var/backups/lisglosips-phase2/lisglosips_hotpath.lua.$(date +%Y%m%d%H%M%S)
|
||||
```
|
||||
|
||||
2. 替换候选文件后,加载 Lua 并确认 SHA 与 `opensips.cfg` 中 `EVALSHA` 一致。
|
||||
|
||||
3. 必须先执行语法检查:
|
||||
|
||||
```bash
|
||||
sudo opensips -C -f /etc/opensips/opensips.cfg
|
||||
```
|
||||
|
||||
4. 语法检查通过后才可重启:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart opensips
|
||||
sudo systemctl is-active opensips
|
||||
```
|
||||
|
||||
## A/B/T 验收清单
|
||||
|
||||
至少验证以下呼叫:
|
||||
|
||||
- 命中业务前缀:例如客户送 `67118822190000`,话单 `callee=18822190000`,`raw_callee=67118822190000`,`business_prefix=671`。
|
||||
- 任意被叫规则:客户网关 `calleeMatchMode=ANY` 时,旧无业务前缀呼叫仍可路由。
|
||||
- 主叫前缀命中:配置 `callerPrefixes` 后,命中前缀允许呼叫。
|
||||
- 主叫前缀不命中:返回 `CALLER_PREFIX_NOT_MATCHED`。
|
||||
- 业务前缀不命中:返回 `BUSINESS_PREFIX_NOT_MATCHED`。
|
||||
- 落地被叫前缀:落地侧实际 Request-URI 被叫包含 `landingCalleePrefix`。
|
||||
- 落地指定主叫:落地侧 From 主叫按权重池选择,话单 `landing_caller` 有值。
|
||||
- 屏蔽地区跳过:命中落地网关屏蔽地区时尝试同线路组下一网关。
|
||||
- 当前通话页:呼叫过程中可看到当前通话,强制挂断可用。
|
||||
- 话单详情:可看到原始被叫、业务前缀、落地主叫、落地被叫、地级市、运营商。
|
||||
|
||||
## 回滚
|
||||
|
||||
B 应用回滚:
|
||||
|
||||
```bash
|
||||
sudo infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/<previous-release>
|
||||
```
|
||||
|
||||
Redis 配置回滚:
|
||||
|
||||
```bash
|
||||
# 使用当前系统已验证的 Redis 运维方式切回 cfg:previous_version
|
||||
```
|
||||
|
||||
A OpenSIPS 回滚:
|
||||
|
||||
```bash
|
||||
sudo cp -a /var/backups/lisglosips-phase2/<opensips.cfg.backup> /etc/opensips/opensips.cfg
|
||||
sudo cp -a /var/backups/lisglosips-phase2/<lisglosips_hotpath.lua.backup> /etc/opensips/lisglosips_hotpath.lua
|
||||
sudo opensips -C -f /etc/opensips/opensips.cfg
|
||||
sudo systemctl restart opensips
|
||||
```
|
||||
|
||||
数据库回滚原则:
|
||||
|
||||
- 本次 migration 为向前兼容新增表/列,不做破坏性 down migration。
|
||||
- 若 apply 后发现业务规则错误,优先通过客户网关页面/API 修正 `lineGroupId`、主叫规则和业务前缀绑定,再重新发布 Redis 配置。
|
||||
- 若必须恢复数据,使用执行前 MySQL 备份在隔离环境验证后再恢复。
|
||||
@@ -0,0 +1,107 @@
|
||||
# S42 二期业务前缀基线冻结报告
|
||||
|
||||
## 1. 目标
|
||||
|
||||
冻结 LisgloSIPS V2 本地 KVM/Tailscale 环境中二期业务前缀、客户网关匹配、号码库、落地号码改写和真实话单接入后的当前可回滚基线。
|
||||
|
||||
本冻结不进入阿里云生产迁移,不替代未来生产环境的 VPC、安全组、正式 DNS/TLS、独立数据盘、备份恢复和灰度验收。
|
||||
|
||||
## 2. 冻结结论
|
||||
|
||||
当前 `s42-phase2-business-prefix-20260624140000` 具备冻结条件,可作为“二期业务前缀小样本联调基线”。
|
||||
|
||||
本冻结不包含真实 80 万手机号段导入,不包含生产式号码库压测,不包含浏览器登录态逐页按钮全量人工复测。
|
||||
|
||||
## 3. 冻结清单
|
||||
|
||||
| 项 | 当前值 |
|
||||
| --- | --- |
|
||||
| B current release | `/opt/lisglosips/releases/s42-phase2-business-prefix-20260624140000` |
|
||||
| Redis active version | `1782286812696` |
|
||||
| Redis previous version | `1782286145681` |
|
||||
| A OpenSIPS cfg SHA-256 | `ae1544e9a39d2db114e6981babac042c2ab6d399d59ba597a52b85d597aa5aa5` |
|
||||
| A Lua SHA-1 | `0b7ee595cdcad2f38a670dd8ff3553387820039e` |
|
||||
| Prisma migrations | 4 条,B 当前库 `Database schema is up to date` |
|
||||
| 最新 MySQL 备份 | `/data/backups/mysql/20260624T055810Z`,SHA256 校验通过 |
|
||||
| 最新 Redis 备份 | `/data/backups/redis/20260624T055810Z`,SHA256 校验通过 |
|
||||
| T 测试脚本 | `/opt/lisglosips-s28/lisglosips-s28-sip.py` |
|
||||
|
||||
## 4. 验证结果
|
||||
|
||||
本地代码验证:
|
||||
|
||||
- `corepack pnpm@10.33.0 typecheck` 通过。
|
||||
- `corepack pnpm@10.33.0 lint` 通过。
|
||||
- `corepack pnpm@10.33.0 build` 通过。
|
||||
- `corepack pnpm@10.33.0 exec vitest run --hookTimeout=60000 --maxWorkers=1` 通过:25 个测试文件,87 条测试通过。
|
||||
|
||||
B 侧验证:
|
||||
|
||||
- `lisglosips@api`、`lisglosips@cdr-worker`、`lisglosips@recording-worker`、`lisglosips@config-publisher`、`nginx`、`mysql`、`redis-server`、`heplify-server`、`lisglosips-prometheus`、`grafana-server` 均为 `active`。
|
||||
- `https://127.0.0.1/api/v2/health/ready` 返回 `ok`。
|
||||
- `infra/server-b/s30/lisglosips-release-preflight.sh` 已补齐执行权限,直接执行通过。
|
||||
- Redis `stream:cdr_deadletter` 长度为 26,抽样均为 2026-06-23 至 2026-06-24 早期修复前历史死信,包括 `config_version` INT 越界、旧占位外键失败、旧 `NO_POLICY` 事件;冻结验收实呼未新增 deadletter。
|
||||
|
||||
A 侧验证:
|
||||
|
||||
- `opensips`、`lisglosips-redis-auth-proxy`、`rtpengine`、`rtpengine-recording-daemon` 均为 `active`。
|
||||
- `opensips -C -f /etc/opensips/opensips.cfg` 通过。
|
||||
- 历史 4 条 dialog 经 `dlg_end_dlg` 无法清理,返回 OpenSIPS MI `Operation failed`;已在语法检查通过后重启 `opensips` 清空。
|
||||
- 冻结验收实呼后出现 1 条测试 dialog 残留;已再次语法检查并重启 `opensips`,最终 `dlg_list` 为空。
|
||||
|
||||
T 侧验证:
|
||||
|
||||
- `opensips` 为 `active`。
|
||||
- `100.93.185.30:5060` 和 `100.93.185.30:50620` 均在 UDP 监听。
|
||||
|
||||
端到端小样本呼叫:
|
||||
|
||||
- Call-ID:`s28-1782288889943-ckv83lce@lisglosips-t`
|
||||
- SIP:`100 Giving it a try`、`200 OK`、BYE `200 OK`
|
||||
- B raw CDR:`raw_d5732df32b8d496686512870fb087232`
|
||||
- 话单字段:`caller=s28-ip-1001`、`callee=13800138000`、`raw_callee=13800138000`、`business_prefix=NULL`、`landing_caller=s28-ip-1001`、`landing_callee=13800138000`、`sip_code=200`、`rating_status=RATED`
|
||||
- 计费:客户费用 `0.012000`,供应商成本 `0.012000`
|
||||
- CDR Worker:`processed=1`、`deadlettered=0`
|
||||
|
||||
## 5. 已处理冻结前问题
|
||||
|
||||
1. 历史 dialog 残留:已通过重启 A OpenSIPS 清空,最终 `dlg_list` 为空。
|
||||
2. CDR deadletter:未删除历史死信,已确认样本属于早期修复前历史事件;冻结验收呼叫未新增 deadletter。
|
||||
3. B preflight 执行权限:已将 `/opt/lisglosips/current/infra/server-b/s30/lisglosips-release-preflight.sh` 权限修正为 `0755`,直接执行通过。
|
||||
4. 全量 Vitest:初次使用默认 hook timeout 时 `app.e2e.spec.ts` 超时;使用 `--hookTimeout=60000` 后全量测试通过。
|
||||
5. 冻结范围:明确不包含真实 80 万手机号段导入和浏览器登录态逐页按钮全量人工复测。
|
||||
|
||||
## 6. 回滚点
|
||||
|
||||
B 应用回滚:
|
||||
|
||||
```bash
|
||||
sudo /opt/lisglosips/current/infra/server-b/s30/lisglosips-release-rollback.sh /opt/lisglosips/releases/s33-active-calls-20260623094000
|
||||
```
|
||||
|
||||
Redis 配置回滚:
|
||||
|
||||
- 当前版本:`1782286812696`
|
||||
- 上一版本:`1782286145681`
|
||||
- 回滚时切回 `cfg:previous_version`,不删除 Stream 或 deadletter。
|
||||
|
||||
A OpenSIPS 回滚:
|
||||
|
||||
- 当前配置语法已通过,当前 Lua SHA 已记录。
|
||||
- 若需回滚,使用最近一次 `/var/backups/lisglosips-phase2/` 下的 `opensips.cfg` 和 `lisglosips_hotpath.lua` 备份,先执行 `opensips -C -f /etc/opensips/opensips.cfg`,通过后重启 `opensips`。
|
||||
|
||||
数据库回滚:
|
||||
|
||||
- 二期 migration 为向前兼容新增表/列,不建议执行破坏性 down migration。
|
||||
- 若必须恢复数据,使用 `/data/backups/mysql/20260624T055810Z`,先恢复到隔离库验证后再进入维护窗口处理。
|
||||
|
||||
## 7. 遗留风险
|
||||
|
||||
- 真实 80 万手机号段尚未导入;号码库相关性能、Redis 快照体量和导入失败明细仍需单独验收。
|
||||
- 浏览器登录态逐页按钮未做完整人工复测;本次只完成 API、构建、单元/集成测试和端到端小样本呼叫验证。
|
||||
- OpenSIPS dialog 在测试呼叫结束后仍可能短期残留,当前通过重启清空。后续应单独分析 dialog 生命周期和 `dlg_end_dlg Operation failed` 的根因。
|
||||
- 当前仍为本地 KVM/Tailscale 环境,开发 CA、Tailscale IP、本地根盘布局不能原样用于阿里云生产。
|
||||
|
||||
## 8. 结论
|
||||
|
||||
`s42-phase2-business-prefix-20260624140000` 可冻结为二期业务前缀小样本联调基线。后续需求或 Bug 修复应创建新的 release,不再覆盖该冻结点;阿里云迁移前仍需重新执行 S30 Runbook、备份恢复演练、正式网络/TLS 验收和灰度观察。
|
||||
Reference in New Issue
Block a user