feat: complete daemon operations and recovery tooling

This commit is contained in:
2026-08-22 15:34:09 +08:00
parent a0a4a7556a
commit 1abe8df6ee
19 changed files with 1421 additions and 171 deletions
+72 -5
View File
@@ -22,7 +22,7 @@ import (
func TestServerAcceptsBackendUpdateThroughUnixSocket(t *testing.T) {
socketPath := shortSocketPath(t)
updater := &fakeUpdater{record: transaction.Transaction{ID: "transaction-01", State: transaction.StateCommitted}}
server, err := New(socketPath, updater, slog.New(slog.NewTextHandler(io.Discard, nil)))
server, err := New(socketPath, updater, &fakeDiagnoser{}, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err != nil {
t.Fatalf("create daemon server: %v", err)
}
@@ -65,7 +65,7 @@ func TestServerAcceptsBackendUpdateThroughUnixSocket(t *testing.T) {
func TestServerAcceptsDirectNativeJARThroughUnixSocket(t *testing.T) {
socketPath := shortSocketPath(t)
updater := &fakeUpdater{record: transaction.Transaction{ID: "transaction-direct-01", State: transaction.StateCommitted}}
server, err := New(socketPath, updater, slog.New(slog.NewTextHandler(io.Discard, nil)))
server, err := New(socketPath, updater, &fakeDiagnoser{}, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err != nil {
t.Fatalf("create daemon server: %v", err)
}
@@ -93,7 +93,7 @@ func TestServerAcceptsDirectNativeJARThroughUnixSocket(t *testing.T) {
func TestServerAcceptsContainerImageThroughUnixSocket(t *testing.T) {
socketPath := shortSocketPath(t)
updater := &fakeUpdater{record: transaction.Transaction{ID: "transaction-container-01", State: transaction.StateCommitted}}
server, err := New(socketPath, updater, slog.New(slog.NewTextHandler(io.Discard, nil)))
server, err := New(socketPath, updater, &fakeDiagnoser{}, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err != nil {
t.Fatalf("create daemon server: %v", err)
}
@@ -121,7 +121,7 @@ func TestServerReturnsTransactionFailure(t *testing.T) {
record: transaction.Transaction{ID: "transaction-02", State: transaction.StateRolledBack},
err: errors.New("health check failed"),
}
server, err := New(socketPath, updater, slog.New(slog.NewTextHandler(io.Discard, nil)))
server, err := New(socketPath, updater, &fakeDiagnoser{}, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err != nil {
t.Fatalf("create daemon server: %v", err)
}
@@ -143,7 +143,7 @@ func TestServerReturnsTransactionFailure(t *testing.T) {
func TestServerAcceptsBackendRestartThroughUnixSocket(t *testing.T) {
socketPath := shortSocketPath(t)
updater := &fakeUpdater{record: transaction.Transaction{ID: "transaction-restart-01", State: transaction.StateCommitted}}
server, err := New(socketPath, updater, slog.New(slog.NewTextHandler(io.Discard, nil)))
server, err := New(socketPath, updater, &fakeDiagnoser{}, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err != nil {
t.Fatalf("create daemon server: %v", err)
}
@@ -168,6 +168,55 @@ func TestServerAcceptsBackendRestartThroughUnixSocket(t *testing.T) {
}
}
// TestServerReturnsStatusThroughUnixSocket 验证服务端能通过 Unix Socket 处理 status 请求,
// 并把诊断结果返回给客户端。
func TestServerReturnsStatusThroughUnixSocket(t *testing.T) {
socketPath := shortSocketPath(t)
updater := &fakeUpdater{}
diagnosis := daemonapi.Diagnosis{Service: "backend", Type: "container", Healthy: true, Items: []daemonapi.DiagnosisItem{{Level: daemonapi.DiagnosisLevelOK, Code: "ok", Message: "ok"}}}
diagnoser := &fakeDiagnoser{diagnosis: diagnosis}
server, err := New(socketPath, updater, diagnoser, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err != nil {
t.Fatalf("create daemon server: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
serveResult := make(chan error, 1)
go func() { serveResult <- server.Serve(ctx) }()
waitForSocket(t, socketPath, serveResult)
result, err := daemonclient.Status(context.Background(), socketPath, "backend")
if err != nil {
t.Fatalf("submit status: %v", err)
}
if result.Service != "backend" || !result.Healthy {
t.Fatalf("unexpected status result: %+v", result)
}
}
// TestServerReconcileForwardsApply 验证 reconcile --apply 会把 apply 标志正确传递给诊断器。
func TestServerReconcileForwardsApply(t *testing.T) {
socketPath := shortSocketPath(t)
updater := &fakeUpdater{}
diagnoser := &fakeDiagnoser{diagnosis: daemonapi.Diagnosis{Service: "backend", Type: "container", Healthy: true}}
server, err := New(socketPath, updater, diagnoser, slog.New(slog.NewTextHandler(io.Discard, nil)))
if err != nil {
t.Fatalf("create daemon server: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
serveResult := make(chan error, 1)
go func() { serveResult <- server.Serve(ctx) }()
waitForSocket(t, socketPath, serveResult)
if _, err := daemonclient.Reconcile(context.Background(), socketPath, "backend", true); err != nil {
t.Fatalf("submit reconcile apply: %v", err)
}
if !diagnoser.applied {
t.Fatal("reconcile --apply was not forwarded to diagnoser")
}
}
// shortSocketPath 创建一个短目录并返回其下的 daemon.sock 路径,用于规避 Unix Socket 路径长度上限。
// 目录会在测试结束时自动清理。
func shortSocketPath(t *testing.T) string {
@@ -259,3 +308,21 @@ func (u *fakeUpdater) Restart(_ context.Context, report backendupdate.ProgressRe
}
return u.record, u.err
}
// fakeDiagnoser backendDiagnoser 接口的测试替身,返回预设的空诊断结果。
type fakeDiagnoser struct {
diagnosis daemonapi.Diagnosis
err error
applied bool
}
// Diagnose 返回预设的诊断结果。
func (d *fakeDiagnoser) Diagnose(context.Context) (daemonapi.Diagnosis, error) {
return d.diagnosis, d.err
}
// Reconcile 返回预设的诊断结果,并记录 apply 标志。
func (d *fakeDiagnoser) Reconcile(_ context.Context, apply bool) (daemonapi.Diagnosis, error) {
d.applied = apply
return d.diagnosis, d.err
}