chore: 初始开源发布

iotlcm-worker-svr:异步执行中枢(RabbitMQ 消费者 + 全量同步编排)。

- Go module:iotlcm-worker-svr → git.5iot.com/iotlcm-public/iotlcm-worker-svr(214 文件)
- 凭据与内部标识脱敏:MySQL/Redis 凭据、私网与生产 IP、内网域名、
  库名 xiujie_admin → iotlcm、哨兵名占位
- 159 处真实手机号 → 合法 11 位编造号(保留号段与后 4 位,维持互异,
  不破坏「校验 MSISDN 格式」等测试断言)
- 移除内部资产:CI 触发器、Jenkins 触发测试文档、预演门禁与镜像
  digest 记录
- Dockerfile 改用公共基础镜像(golang:1.25-alpine → alpine:3.23),
  移除写死的 git 凭据
- README 重写(架构/全量同步模型/配置/已知失败),补 Apache-2.0
  LICENSE,LF 行尾 + .gitattributes

已知:3 个测试包在源仓同一提交上同样失败(client/upstream、
service/allocate、service/inbound),属既有缺陷,本仓未引入亦未修复。
This commit is contained in:
MiFaZhan
2026-10-22 16:37:56 +08:00
commit 2bec257289
361 changed files with 71524 additions and 0 deletions
@@ -0,0 +1,179 @@
package monthlyusagehistory
import (
"context"
"testing"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/queue/zset"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/testutil"
)
func newZombieTestProducer(t *testing.T) (*Producer, *zset.Store) {
t.Helper()
store, err := zset.New(testutil.Redis(t), "flow:")
if err != nil {
t.Fatal(err)
}
producer, err := New(&pageScanner{cards: nil}, store, Config{PageSize: 100, RoundRetryEnabled: true})
if err != nil {
t.Fatal(err)
}
ctx := context.Background()
if acquired, err := store.AcquireLockOrTakeover(ctx, producer.config.RunLockKey, producer.owner, "pending-check", producer.config.RunLockTTL); err != nil || !acquired {
t.Fatalf("acquire producer lock=%t err=%v", acquired, err)
}
t.Cleanup(func() { _, _ = store.ReleaseLock(ctx, producer.config.RunLockKey, producer.owner) })
return producer, store
}
// TestHasPendingTasksReclaimsZombieRun guards the "zombie run" regression.
//
// A producer that dies before MarkProducerDone (for example the container is
// replaced mid-run) leaves its run in status=producing with producerDone=0.
// completeRunIfIdle and advanceRunRound both require producerDone=1, so such a
// run can never be closed, never leaves the active index, and never gets the
// retention TTL. Treating it as backpressure rejected every later request
// forever; merely warning about it leaked keys and an index entry indefinitely.
func TestHasPendingTasksReclaimsZombieRun(t *testing.T) {
producer, store := newZombieTestProducer(t)
ctx := context.Background()
// A genuinely in-flight run (producerDone set, consumer working) must keep
// providing backpressure.
if err := store.CreateRun(ctx, zset.RunMeta{RunID: "running-run", Status: "running", ProducerDone: true, CurrentRound: 1}); err != nil {
t.Fatal(err)
}
pending, err := producer.hasPendingTasks(ctx, "new-run")
if err != nil {
t.Fatal(err)
}
if !pending {
t.Fatal("a running run must still provide backpressure")
}
if _, err := store.RedisClient().Do(ctx, "SREM", store.ActiveRunsKey(), "running-run"); err != nil {
t.Fatal(err)
}
// A zombie run must not block a new request, and must be promoted so the
// normal lifecycle can drain and close it.
if err := store.CreateRun(ctx, zset.RunMeta{RunID: "zombie-run", Status: "producing", ProducerDone: false, CurrentRound: 1}); err != nil {
t.Fatal(err)
}
pending, err = producer.hasPendingTasks(ctx, "new-run-2")
if err != nil {
t.Fatal(err)
}
if pending {
t.Fatal("a producing run with producerDone=0 must not block new requests (zombie run)")
}
meta, err := store.GetRunMeta(ctx, "zombie-run")
if err != nil {
t.Fatal(err)
}
if !meta.ProducerDone {
t.Fatal("the zombie run must be promoted (producerDone=1) so it can be closed")
}
if meta.Status != "running" {
t.Fatalf("promoted run status = %q, want running", meta.Status)
}
// Promotion makes the run closable, which is what applies the retention TTL
// and releases the active index entry.
if completed, err := store.CompleteRunIfIdle(ctx, "zombie-run"); err != nil || !completed {
t.Fatalf("promoted run must be closable: completed=%v err=%v", completed, err)
}
}
// TestHasPendingTasksNeverReclaimsReplayRun guards the boundary: a replay target
// is also created as "producing", but it is driven by the replay engine under
// its own lock, so its producerDone is not the producer's to set.
func TestHasPendingTasksNeverReclaimsReplayRun(t *testing.T) {
producer, store := newZombieTestProducer(t)
ctx := context.Background()
if err := store.CreateRun(ctx, zset.RunMeta{
RunID: "replay-run", Status: "producing", ProducerDone: false, CurrentRound: 1,
SyncType: "replay", SourceRunID: "source-run", ReplayCycle: 1,
}); err != nil {
t.Fatal(err)
}
if pending, err := producer.hasPendingTasks(ctx, "new-run"); err != nil || !pending {
t.Fatalf("replay must retain backpressure: pending=%t err=%v", pending, err)
}
meta, err := store.GetRunMeta(ctx, "replay-run")
if err != nil {
t.Fatal(err)
}
if meta.ProducerDone {
t.Fatal("a replay run must not be promoted by the full-sync producer")
}
if meta.Status != "producing" {
t.Fatalf("replay run status = %q, want producing", meta.Status)
}
}
// TestHasPendingTasksDropsStaleIndexEntry covers a second unrecoverable path:
// the active index can list a run whose hash is already gone. Returning that as
// an error made the request fail permanently (the consumer redelivers it
// forever), which is the same "only fixable by hand-cleaning Redis" failure.
func TestHasPendingTasksDropsStaleIndexEntry(t *testing.T) {
producer, store := newZombieTestProducer(t)
ctx := context.Background()
// Index a run that has no metadata hash.
if _, err := store.RedisClient().Do(ctx, "SADD", store.ActiveRunsKey(), "ghost-run"); err != nil {
t.Fatal(err)
}
pending, err := producer.hasPendingTasks(ctx, "new-run")
if err != nil {
t.Fatalf("a stale index entry must not fail the request: %v", err)
}
if pending {
t.Fatal("a stale index entry must not provide backpressure")
}
// The stale entry is removed so it does not have to be handled again.
members, err := store.ActiveRuns(ctx)
if err != nil {
t.Fatal(err)
}
for _, member := range members {
if member == "ghost-run" {
t.Fatal("the stale index entry must be removed")
}
}
}
// TestProduceCreatesRunAfterZombie covers the end-to-end path through Produce:
// a zombie run must not stop a new run from being created.
func TestProduceCreatesRunAfterZombie(t *testing.T) {
store, err := zset.New(testutil.Redis(t), "flow:")
if err != nil {
t.Fatal(err)
}
ctx := context.Background()
producer, err := New(&pageScanner{cards: []Card{
{ID: 1, ChanKey: "channel-a", TempKey: "sim_yd_v5", Operator: 1},
}}, store, Config{PageSize: 100, RoundRetryEnabled: true})
if err != nil {
t.Fatal(err)
}
// A zombie run from a crashed producer.
if err := store.CreateRun(ctx, zset.RunMeta{RunID: "zombie-run", Status: "producing", ProducerDone: false, CurrentRound: 1}); err != nil {
t.Fatal(err)
}
// A new request must still produce its own run.
if err := producer.Produce(ctx, scopedMessage("fresh-run", "channel-a")); err != nil {
t.Fatalf("a zombie run must not stop a new run: %v", err)
}
meta, err := store.GetRunMeta(ctx, "fresh-run")
if err != nil {
t.Fatal(err)
}
// The producer enqueues into the active ZSet; ActiveCount is the count it
// maintains. EnqueuedCount is an accounting field the producer does not set.
if !meta.ProducerDone || meta.ActiveCount != 1 {
t.Fatalf("fresh run metadata = %+v, want producerDone with 1 active task", meta)
}
}