e6472b0ec6
为什么改:发布执行链是"与目标同机"假设写死的(本机 docker.sock、本机 drain.json、 本机 Redis/PG),把目标换成云测试不能只改展示 URL;直接搬整套 worker 又会抢走 本机封存/QA/周报队列并按本机配置写错目标。所以先把"谁消费哪段队列"变成显式角色, 再补齐跨主机搬运与目标侧门禁。 角色(LIBRARY_WORKER_ROLE=full/release-executor/capture)不只影响 handler 列表, 还决定是否拥有 DDL(release-executor 只读校验迁移版本,落后即拒绝启动)、 收拾哪些 library_jobs.kind、是否跑 release 接管扫描、是否起 scheduler/QA/周报/探测。 capture 保留本机原有全部业务;release-executor 只挂 sync/restore/recover/baseline。 快照跨主机:新增内容寻址的 bundle(版本清单先落、blob 按 sha256 命名、清单最后 Link 作提交点),pack/unpack 幂等且拒绝覆盖与同名不同内容;执行器使用前仍逐 blob 重算 sha256,因此传输不可变有两道证据。 目标侧门禁:目标知识库 graph_enabled=true 时拒绝发布(发布只写 PG/ES/OSS,不写 Neo4j;未知也拒绝),只读检测、不改目标配置、不做图谱迁移。 云端入口:新增 drain 感知的 ingress 闸门(建连超时与读超时分离,半关闭单向传播, SIGTERM 排空后写 drain.json 并 exit 0/2)、compose 叠加片段(app 不再发布端口、 frontend 与 adapter 的入口收敛到闸门、指向真实网络 weknora-test-v071-network)、 执行器启动脚本与治理 PG 反向隧道脚本(默认 unix socket,不占 TCP)。 验证:deploy/cloud-test/test_gate.py 13 项全 PASS(含 12 秒静默流、半关闭、2MiB 完整、 排空 exit 0/active_at_close=1、排空超时 exit 2、上游不可达不挂住);go test 相关包全绿; scripts/ci_checks.py 16 项全部 success(EXIT=0,1241 用例通过/0 失败/191 已声明可选集成跳过); 云测试只读预检 17 项 / 6 FAIL,FAIL 项即当前部署缺口(无闸门、app 仍发布端口、 adapter 与 frontend 仍直连 app、云端归档根未建),未部署、未改云端任何状态。
88 lines
3.5 KiB
Go
88 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"knowledge-governance/internal/appconfig"
|
|
"knowledge-governance/internal/jobs"
|
|
postgresstore "knowledge-governance/internal/platform/postgres"
|
|
"knowledge-governance/internal/versionhistory"
|
|
)
|
|
|
|
// 发布执行器必须只挂四类 release 作业:多挂一类就会抢走本机队列里别的执行者的作业。
|
|
func TestRegisterLibraryReleaseHandlersCoversOnlyReleaseJobs(t *testing.T) {
|
|
handlers := map[string]jobs.Handler{}
|
|
registerLibraryReleaseHandlers(handlers, nil, appconfig.Config{}, nil)
|
|
want := []string{librarySyncJobType, libraryRestoreJobType, libraryRecoverJobType, libraryBaselineJobType}
|
|
if len(handlers) != len(want) {
|
|
t.Fatalf("registered %d handlers, want %d: %#v", len(handlers), len(want), handlers)
|
|
}
|
|
for _, jobType := range want {
|
|
if handlers[jobType] == nil {
|
|
t.Fatalf("handler %q is not registered", jobType)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 准入未通过时 handler 必须"明确失败且不做任何动作":不能靠 nil store 或进程内状态蒙过去。
|
|
func TestRegisterLibraryReleaseHandlersFailsClosedWhenNotAdmitted(t *testing.T) {
|
|
handlers := map[string]jobs.Handler{}
|
|
registerLibraryReleaseHandlers(handlers, nil, appconfig.Config{}, errNotAdmitted{})
|
|
for _, jobType := range []string{librarySyncJobType, libraryRestoreJobType, libraryRecoverJobType, libraryBaselineJobType} {
|
|
err := handlers[jobType](context.Background(), jobs.Job{})
|
|
if err == nil || !strings.Contains(err.Error(), "准入未通过") {
|
|
t.Fatalf("handler %q error = %v, want admission refusal", jobType, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
type errNotAdmitted struct{}
|
|
|
|
func (errNotAdmitted) Error() string { return "fixture: target instance unknown" }
|
|
|
|
// capture 角色不能注册任何 source 之外的队列;release-executor 反之。
|
|
func TestWorkerHandlersFollowWorkerRole(t *testing.T) {
|
|
release := appconfig.Config{
|
|
LibraryWorkerRole: appconfig.LibraryWorkerRoleReleaseExecutor,
|
|
LibraryAgentSocket: "/run/library-agent/agent.sock",
|
|
TestWeKnoraEnabled: true,
|
|
TestWeKnoraAPIKeyFile: "/nonexistent/api-key",
|
|
PPScanEnabled: true,
|
|
VersionHistoryGiteaBaseURL: "https://gitea.invalid/Weknora/knowledge-governance",
|
|
VersionHistoryAllowedOrigins: []string{"https://gitea.invalid"},
|
|
}
|
|
handlers, err := workerHandlers(postgresstore.NewStore(nil), release)
|
|
if err != nil {
|
|
t.Fatalf("release-executor workerHandlers() error = %v", err)
|
|
}
|
|
for _, forbidden := range []string{libraryCaptureJobType, "test.upload", "test.poll", "test.validate", "test.remove", "source.scan.pp", "source.catalog.refresh", "artifact.fetch", versionhistory.JobType} {
|
|
if handlers[forbidden] != nil {
|
|
t.Fatalf("release-executor registered %q", forbidden)
|
|
}
|
|
}
|
|
if handlers["demo"] == nil {
|
|
t.Fatal("demo handler must stay registered")
|
|
}
|
|
|
|
capture := appconfig.Config{
|
|
LibraryWorkerRole: appconfig.LibraryWorkerRoleCapture,
|
|
LibraryAgentSocket: "/run/library-agent/agent.sock",
|
|
TestWeKnoraEnabled: true,
|
|
TestWeKnoraAPIKeyFile: "/nonexistent/api-key",
|
|
}
|
|
if _, err := workerHandlers(nil, capture); err == nil || !strings.Contains(err.Error(), "test WeKnora API key") {
|
|
t.Fatalf("capture role must reach the test WeKnora wiring, got %v", err)
|
|
}
|
|
capture.TestWeKnoraEnabled = false
|
|
capture.PPScanEnabled = false
|
|
handlers, err = workerHandlers(postgresstore.NewStore(nil), capture)
|
|
if err != nil {
|
|
t.Fatalf("capture workerHandlers() error = %v", err)
|
|
}
|
|
if handlers[libraryCaptureJobType] == nil {
|
|
t.Fatal("capture role must register library.capture")
|
|
}
|
|
}
|