feat: runtime manager

This commit is contained in:
Ilia Denisov
2026-04-28 20:39:18 +02:00
committed by GitHub
parent e0a99b346b
commit a7cee15115
289 changed files with 45660 additions and 2207 deletions
+224
View File
@@ -0,0 +1,224 @@
// Package harness exposes the testcontainers / Docker / image-build
// scaffolding shared by the Runtime Manager service-local integration
// suite under [`galaxy/rtmanager/integration`](..).
//
// Only `_test.go` files (and the harness itself) reference this
// package; production code paths in `cmd/rtmanager` never import it.
// The package therefore stays out of the production binary's import
// graph, identical to the in-package `pgtest` and `integration/internal/harness`
// patterns it mirrors.
package harness
import (
"context"
"database/sql"
"net/url"
"os"
"sync"
"testing"
"time"
"galaxy/postgres"
"galaxy/rtmanager/internal/adapters/postgres/migrations"
testcontainers "github.com/testcontainers/testcontainers-go"
tcpostgres "github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/testcontainers/testcontainers-go/wait"
)
const (
pgImage = "postgres:16-alpine"
pgSuperUser = "galaxy"
pgSuperPassword = "galaxy"
pgSuperDatabase = "galaxy_rtmanager_it"
pgServiceRole = "rtmanagerservice"
pgServicePassword = "rtmanagerservice"
pgServiceSchema = "rtmanager"
pgStartupTimeout = 90 * time.Second
// pgOperationTimeout bounds the per-statement deadline used by every
// pool the harness opens. Short enough to surface a runaway
// integration test promptly, long enough to absorb laptop-grade I/O.
pgOperationTimeout = 10 * time.Second
)
// PostgresEnv carries the per-package PostgreSQL fixture. The container
// is started lazily on the first EnsurePostgres call and torn down by
// ShutdownPostgres at TestMain exit.
type PostgresEnv struct {
container *tcpostgres.PostgresContainer
pool *sql.DB
scopedDSN string
}
// Pool returns the harness-owned `*sql.DB` scoped to the rtmanager
// schema. Tests use it to read durable state directly through the
// existing store adapters.
func (env *PostgresEnv) Pool() *sql.DB { return env.pool }
// DSN returns the rtmanager-role-scoped DSN suitable for
// `RTMANAGER_POSTGRES_PRIMARY_DSN`. Both this DSN and Pool address the
// same database; the pool is reused across tests, while the runtime
// under test opens its own pool through this DSN.
func (env *PostgresEnv) DSN() string { return env.scopedDSN }
var (
pgOnce sync.Once
pgEnv *PostgresEnv
pgErr error
)
// EnsurePostgres starts the per-package PostgreSQL container on first
// invocation and applies the embedded goose migrations. Subsequent
// invocations reuse the same container. When Docker is unavailable the
// helper calls `t.Skip` so the suite stays green on hosts without a
// daemon (mirrors the contract from `internal/adapters/postgres/internal/pgtest`).
func EnsurePostgres(t testing.TB) *PostgresEnv {
t.Helper()
pgOnce.Do(func() {
pgEnv, pgErr = startPostgres()
})
if pgErr != nil {
t.Skipf("rtmanager integration: postgres container start failed (Docker unavailable?): %v", pgErr)
}
return pgEnv
}
// TruncatePostgres wipes every Runtime Manager table inside the shared
// pool, leaving the schema and indexes intact. Tests call this from
// their setup so each scenario starts on an empty state.
func TruncatePostgres(t testing.TB) {
t.Helper()
env := EnsurePostgres(t)
const stmt = `TRUNCATE TABLE runtime_records, operation_log, health_snapshots RESTART IDENTITY CASCADE`
if _, err := env.pool.ExecContext(context.Background(), stmt); err != nil {
t.Fatalf("truncate rtmanager tables: %v", err)
}
}
// ShutdownPostgres terminates the shared container and closes the pool.
// `TestMain` invokes it after `m.Run` so the container is released even
// if individual tests panic.
func ShutdownPostgres() {
if pgEnv == nil {
return
}
if pgEnv.pool != nil {
_ = pgEnv.pool.Close()
}
if pgEnv.container != nil {
_ = testcontainers.TerminateContainer(pgEnv.container)
}
pgEnv = nil
}
// RunMain is a convenience helper for the integration package
// `TestMain`: it runs the suite, captures the exit code, tears every
// shared container down, and exits. Wiring it through one helper keeps
// `TestMain` to two lines and centralises ordering.
func RunMain(m *testing.M) {
code := m.Run()
ShutdownRedis()
ShutdownPostgres()
ShutdownDocker()
os.Exit(code)
}
func startPostgres() (*PostgresEnv, error) {
ctx := context.Background()
container, err := tcpostgres.Run(ctx, pgImage,
tcpostgres.WithDatabase(pgSuperDatabase),
tcpostgres.WithUsername(pgSuperUser),
tcpostgres.WithPassword(pgSuperPassword),
testcontainers.WithWaitStrategy(
wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).
WithStartupTimeout(pgStartupTimeout),
),
)
if err != nil {
return nil, err
}
baseDSN, err := container.ConnectionString(ctx, "sslmode=disable")
if err != nil {
_ = testcontainers.TerminateContainer(container)
return nil, err
}
if err := provisionRoleAndSchema(ctx, baseDSN); err != nil {
_ = testcontainers.TerminateContainer(container)
return nil, err
}
scopedDSN, err := scopedDSNForRole(baseDSN)
if err != nil {
_ = testcontainers.TerminateContainer(container)
return nil, err
}
cfg := postgres.DefaultConfig()
cfg.PrimaryDSN = scopedDSN
cfg.OperationTimeout = pgOperationTimeout
pool, err := postgres.OpenPrimary(ctx, cfg)
if err != nil {
_ = testcontainers.TerminateContainer(container)
return nil, err
}
if err := postgres.Ping(ctx, pool, pgOperationTimeout); err != nil {
_ = pool.Close()
_ = testcontainers.TerminateContainer(container)
return nil, err
}
if err := postgres.RunMigrations(ctx, pool, migrations.FS(), "."); err != nil {
_ = pool.Close()
_ = testcontainers.TerminateContainer(container)
return nil, err
}
return &PostgresEnv{
container: container,
pool: pool,
scopedDSN: scopedDSN,
}, nil
}
func provisionRoleAndSchema(ctx context.Context, baseDSN string) error {
cfg := postgres.DefaultConfig()
cfg.PrimaryDSN = baseDSN
cfg.OperationTimeout = pgOperationTimeout
db, err := postgres.OpenPrimary(ctx, cfg)
if err != nil {
return err
}
defer func() { _ = db.Close() }()
statements := []string{
`DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'rtmanagerservice') THEN
CREATE ROLE rtmanagerservice LOGIN PASSWORD 'rtmanagerservice';
END IF;
END $$;`,
`CREATE SCHEMA IF NOT EXISTS rtmanager AUTHORIZATION rtmanagerservice;`,
`GRANT USAGE ON SCHEMA rtmanager TO rtmanagerservice;`,
}
for _, statement := range statements {
if _, err := db.ExecContext(ctx, statement); err != nil {
return err
}
}
return nil
}
func scopedDSNForRole(baseDSN string) (string, error) {
parsed, err := url.Parse(baseDSN)
if err != nil {
return "", err
}
values := url.Values{}
values.Set("search_path", pgServiceSchema)
values.Set("sslmode", "disable")
scoped := url.URL{
Scheme: parsed.Scheme,
User: url.UserPassword(pgServiceRole, pgServicePassword),
Host: parsed.Host,
Path: parsed.Path,
RawQuery: values.Encode(),
}
return scoped.String(), nil
}