4783f3e32e
实现 Lifecycle 类型及其全部公开方法,覆盖 6 个状态迁移动作: MarkProbing / MarkUp / MarkDraining / MarkDown / MarkDestroyed / MarkBlockedSuspect。每次合法迁移在同一个 SQL 事务中原子执行 UPDATE nodes + INSERT node_events + BumpVersion;MarkDestroyed 事务提交后异步调用 mtls.CRL.Revoke() 并清除 Redis 负载缓存与 命令队列键。配套集成测试覆盖 5 类场景(合法迁移、非法迁移、 并发写、后置钩子、blocked_suspect 幂等),全部通过。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
87 lines
2.5 KiB
Go
87 lines
2.5 KiB
Go
//go:build integration
|
|
|
|
package nodes_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestMain starts a throw-away MySQL 8 Docker container when PANGOLIN_TEST_DSN
|
|
// is not already set, runs all tests, and tears the container down on exit.
|
|
//
|
|
// If Docker is unavailable the tests are skipped gracefully (requireDSN in
|
|
// lifecycle_test.go handles the skip).
|
|
func TestMain(m *testing.M) {
|
|
if os.Getenv("PANGOLIN_TEST_DSN") != "" {
|
|
// Caller already provided a DSN — run directly.
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
const (
|
|
containerName = "pangolin-lc-test-mysql"
|
|
hostPort = "13399"
|
|
dbName = "pangolin_test"
|
|
rootPwd = "secret"
|
|
)
|
|
|
|
ctx := context.Background()
|
|
|
|
// Remove any leftover container from a previous crashed run.
|
|
_ = exec.CommandContext(ctx, "docker", "rm", "-f", containerName).Run()
|
|
|
|
// Start MySQL 8.
|
|
startCmd := exec.CommandContext(ctx, "docker", "run", "-d",
|
|
"--name", containerName,
|
|
"-e", "MYSQL_ROOT_PASSWORD="+rootPwd,
|
|
"-e", "MYSQL_DATABASE="+dbName,
|
|
"-p", hostPort+":3306",
|
|
"mysql:8",
|
|
)
|
|
if out, err := startCmd.CombinedOutput(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "docker run failed: %v\n%s\n", err, out)
|
|
fmt.Fprintln(os.Stderr, "skipping lifecycle integration tests (no MySQL)")
|
|
os.Exit(0) // graceful skip
|
|
}
|
|
|
|
// Tear down on exit regardless of test outcome.
|
|
defer func() {
|
|
_ = exec.Command("docker", "stop", containerName).Run()
|
|
_ = exec.Command("docker", "rm", containerName).Run()
|
|
}()
|
|
|
|
// multiStatements=true is required by golang-migrate's MySQL driver so it
|
|
// can execute migration files containing multiple SQL statements in one call.
|
|
// store.Open / buildDSN preserves unrecognised params, so this flag
|
|
// survives the DSN round-trip through buildDSN.
|
|
dsn := fmt.Sprintf("root:%s@tcp(127.0.0.1:%s)/%s?multiStatements=true",
|
|
rootPwd, hostPort, dbName)
|
|
|
|
// Wait until MySQL accepts connections (up to 60 s).
|
|
deadline := time.Now().Add(60 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
ping := exec.CommandContext(ctx, "docker", "exec", containerName,
|
|
"mysqladmin", "ping", "--silent")
|
|
if err := ping.Run(); err == nil {
|
|
break
|
|
}
|
|
time.Sleep(2 * time.Second)
|
|
}
|
|
|
|
// Verify connectivity.
|
|
check := exec.CommandContext(ctx, "docker", "exec", containerName,
|
|
"mysqladmin", "ping", "--silent")
|
|
if err := check.Run(); err != nil {
|
|
fmt.Fprintln(os.Stderr, "MySQL did not become ready within 60 s; skipping tests")
|
|
os.Exit(0)
|
|
}
|
|
|
|
os.Setenv("PANGOLIN_TEST_DSN", dsn)
|
|
|
|
os.Exit(m.Run())
|
|
}
|