75f3dd141c
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CI and Release / lint-frontend (push) Has been cancelled
CI and Release / dockerfile-scan (push) Has been cancelled
CI and Release / test-frontend (push) Has been cancelled
CI and Release / lint-verification-agent (push) Has been cancelled
CI and Release / test-verification-agent (push) Has been cancelled
CI and Release / e2e-verification-agent (push) Has been cancelled
CI and Release / test-backend (push) Has been cancelled
CI and Release / build-dev-image (push) Has been cancelled
CI and Release / push-dev-image (push) Has been cancelled
CI and Release / build-image (push) Has been cancelled
CI and Release / build-verification-image (push) Has been cancelled
CI and Release / determine-version (push) Has been cancelled
CI and Release / push-image (push) Has been cancelled
CI and Release / push-verification-image (12) (push) Has been cancelled
CI and Release / lint-backend (push) Has been cancelled
CI and Release / push-verification-image (17) (push) Has been cancelled
CI and Release / push-verification-image (18) (push) Has been cancelled
CI and Release / release (push) Has been cancelled
CI and Release / publish-helm-chart (push) Has been cancelled
CI and Release / push-verification-image (13) (push) Has been cancelled
CI and Release / push-verification-image (14) (push) Has been cancelled
CI and Release / push-verification-image (15) (push) Has been cancelled
CI and Release / push-verification-image (16) (push) Has been cancelled
378 lines
12 KiB
Go
378 lines
12 KiB
Go
package databases
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"databasus-backend/internal/config"
|
|
"databasus-backend/internal/features/databases/databases/mariadb"
|
|
"databasus-backend/internal/features/databases/databases/mongodb"
|
|
postgresql_logical "databasus-backend/internal/features/databases/databases/postgresql/logical"
|
|
postgresql_physical "databasus-backend/internal/features/databases/databases/postgresql/physical"
|
|
postgresql_shared "databasus-backend/internal/features/databases/databases/postgresql/shared"
|
|
"databasus-backend/internal/features/notifiers"
|
|
"databasus-backend/internal/features/storages"
|
|
"databasus-backend/internal/util/tools"
|
|
)
|
|
|
|
func GetTestPostgresConfig() *postgresql_logical.PostgresqlLogicalDatabase {
|
|
env := config.GetEnv()
|
|
port, err := strconv.Atoi(env.TestLogicalPostgres16Port)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Failed to parse TEST_LOGICAL_POSTGRES_16_PORT: %v", err))
|
|
}
|
|
|
|
testDbName := "testdb"
|
|
return &postgresql_logical.PostgresqlLogicalDatabase{
|
|
Version: tools.PostgresqlVersion16,
|
|
Host: config.GetEnv().TestLocalhost,
|
|
Port: port,
|
|
Username: "testuser",
|
|
Password: "testpassword",
|
|
Database: &testDbName,
|
|
CpuCount: 1,
|
|
}
|
|
}
|
|
|
|
// physicalPostgresVersion maps a "17"/"18" tag to the typed version enum.
|
|
func physicalPostgresVersion(versionTag string) tools.PostgresqlVersion {
|
|
switch versionTag {
|
|
case "17":
|
|
return tools.PostgresqlVersion17
|
|
case "18":
|
|
return tools.PostgresqlVersion18
|
|
default:
|
|
panic(fmt.Sprintf("unsupported physical postgres version tag: %s (use \"17\" or \"18\")", versionTag))
|
|
}
|
|
}
|
|
|
|
// physicalPrimaryEndpoint resolves the shared compose primary source's host and port for versionTag.
|
|
// The ports are validated non-empty in config.go at startup (it os.Exit(1)s with the exact env var
|
|
// when one is unset), so a missing DSN fails the run there, never here and never as a skip. Only the
|
|
// shared-fixture callers use this — the throwaway testcontainers sources pass their own endpoint.
|
|
func physicalPrimaryEndpoint(versionTag string) (string, int) {
|
|
env := config.GetEnv()
|
|
|
|
var portStr string
|
|
|
|
switch versionTag {
|
|
case "17":
|
|
portStr = env.TestPhysicalPostgres17Port
|
|
case "18":
|
|
portStr = env.TestPhysicalPostgres18Port
|
|
default:
|
|
panic(fmt.Sprintf("unsupported physical postgres version tag: %s (use \"17\" or \"18\")", versionTag))
|
|
}
|
|
|
|
port, err := strconv.Atoi(portStr)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Failed to parse physical postgres %s port: %v", versionTag, err))
|
|
}
|
|
|
|
return env.TestLocalhost, port
|
|
}
|
|
|
|
// GetTestPhysicalPostgresConfigWithType builds a physical config at host:port with an explicit
|
|
// BackupType, so scheduler-driven tests can build chains (FULL_AND_INCREMENTAL) or stream WAL
|
|
// (FULL_INCREMENTAL_AND_WAL_STREAM) — the scheduler's incremental decision keys off this DB-level
|
|
// field, which the backup-config API cannot change. The caller owns the container lifecycle.
|
|
func GetTestPhysicalPostgresConfigWithType(
|
|
host string,
|
|
port int,
|
|
versionTag string,
|
|
backupType postgresql_physical.BackupType,
|
|
) *postgresql_physical.PostgresqlPhysicalDatabase {
|
|
return &postgresql_physical.PostgresqlPhysicalDatabase{
|
|
Version: physicalPostgresVersion(versionTag),
|
|
Host: host,
|
|
Port: port,
|
|
Username: "testuser",
|
|
Password: "testpassword",
|
|
BackupType: backupType,
|
|
}
|
|
}
|
|
|
|
// GetTestPhysicalPostgresConfigNoSummary builds a FULL_ONLY config at host:port pointed at a
|
|
// summarize_wal=off cluster, so an incremental pre-check reaches the SUMMARIZER_OFF branch.
|
|
func GetTestPhysicalPostgresConfigNoSummary(
|
|
host string,
|
|
port int,
|
|
versionTag string,
|
|
) *postgresql_physical.PostgresqlPhysicalDatabase {
|
|
return &postgresql_physical.PostgresqlPhysicalDatabase{
|
|
Version: physicalPostgresVersion(versionTag),
|
|
Host: host,
|
|
Port: port,
|
|
Username: "testuser",
|
|
Password: "testpassword",
|
|
BackupType: postgresql_physical.BackupTypeFullOnly,
|
|
}
|
|
}
|
|
|
|
// GetTestPhysicalPostgresConfigMtls builds a physical config at host:port pointed at the
|
|
// replication-capable mTLS cluster, with client certs read from the physical testdata/mtls fixtures.
|
|
// BackupType is FULL_ONLY; the WAL-stream-over-mTLS test constructs the streamer spec directly.
|
|
func GetTestPhysicalPostgresConfigMtls(host string, port int) *postgresql_physical.PostgresqlPhysicalDatabase {
|
|
clientCert, clientKey, rootCert := readPhysicalMtlsCerts()
|
|
|
|
// FULL_ONLY keeps the shared fixture's config-save valid (WAL_STREAM would
|
|
// require CHAINS retention + a lag threshold). The WAL-stream-over-mTLS test
|
|
// constructs the streamer spec directly against this DB, so the declared
|
|
// backup type does not gate it.
|
|
return &postgresql_physical.PostgresqlPhysicalDatabase{
|
|
Version: tools.PostgresqlVersion17,
|
|
Host: host,
|
|
Port: port,
|
|
Username: "testuser",
|
|
Password: "testpassword",
|
|
BackupType: postgresql_physical.BackupTypeFullOnly,
|
|
SslMode: postgresql_shared.PostgresSslModeVerifyCA,
|
|
SslClientCert: clientCert,
|
|
SslClientKey: clientKey,
|
|
SslRootCert: rootCert,
|
|
}
|
|
}
|
|
|
|
// readPhysicalMtlsCerts reads the committed client cert/key + CA from the
|
|
// physical mTLS testdata. Paths are resolved relative to this package so the
|
|
// helper works regardless of the calling test's directory.
|
|
func readPhysicalMtlsCerts() (clientCert, clientKey, rootCert string) {
|
|
read := func(name string) string {
|
|
content, err := os.ReadFile(filepath.Join(GetPhysicalMtlsTestdataDir(), name))
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to read physical mTLS test certificate %s: %v", name, err))
|
|
}
|
|
|
|
return string(content)
|
|
}
|
|
|
|
return read("client.crt"), read("client.key"), read("ca.crt")
|
|
}
|
|
|
|
// GetPhysicalMtlsTestdataDir resolves the committed physical mTLS testdata directory (server +
|
|
// client certs, pg_hba.conf, Dockerfile) from this source file's location, so it is independent of
|
|
// the caller's working directory. It is the single resolver for that directory: both the client-cert
|
|
// reader here and the mTLS server-image build context in the backup tests use it.
|
|
func GetPhysicalMtlsTestdataDir() string {
|
|
_, thisFile, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
panic("cannot resolve physical mTLS testdata dir: runtime.Caller failed")
|
|
}
|
|
|
|
return filepath.Join(
|
|
filepath.Dir(thisFile), "..", "tests", "physical", "testdata", "mtls",
|
|
)
|
|
}
|
|
|
|
// CreateTestPhysicalPostgresDatabaseMtls persists a physical DB row pointed at
|
|
// the mTLS replication cluster at host:port, for FULL- and WAL-stream-over-mTLS tests.
|
|
func CreateTestPhysicalPostgresDatabaseMtls(
|
|
host string,
|
|
port int,
|
|
workspaceID uuid.UUID,
|
|
notifier *notifiers.Notifier,
|
|
) *Database {
|
|
database := &Database{
|
|
WorkspaceID: &workspaceID,
|
|
Name: "test-physical-pg-mtls " + uuid.New().String(),
|
|
Type: DatabaseTypePostgresPhysical,
|
|
PostgresqlPhysical: GetTestPhysicalPostgresConfigMtls(host, port),
|
|
Notifiers: []notifiers.Notifier{
|
|
*notifier,
|
|
},
|
|
}
|
|
|
|
database, err := databaseRepository.Save(database)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return database
|
|
}
|
|
|
|
// GetTestMariadbConfig builds a MariaDB 10.11 database config pointing at host:port. The
|
|
// caller owns the container lifecycle (host/port come from a testcontainers endpoint),
|
|
// keeping this file free of the testcontainers dependency.
|
|
func GetTestMariadbConfig(host string, port int) *mariadb.MariadbDatabase {
|
|
testDbName := "testdb"
|
|
return &mariadb.MariadbDatabase{
|
|
Version: tools.MariadbVersion1011,
|
|
Host: host,
|
|
Port: port,
|
|
Username: "testuser",
|
|
Password: "testpassword",
|
|
Database: &testDbName,
|
|
}
|
|
}
|
|
|
|
// GetTestMongodbConfig builds a Mongo 7.0 database config pointing at host:port. The
|
|
// caller owns the container lifecycle (host/port come from a testcontainers endpoint),
|
|
// keeping this file free of the testcontainers dependency.
|
|
func GetTestMongodbConfig(host string, port int) *mongodb.MongodbDatabase {
|
|
return &mongodb.MongodbDatabase{
|
|
Version: tools.MongodbVersion7,
|
|
Host: host,
|
|
Port: &port,
|
|
Username: "root",
|
|
Password: "rootpassword",
|
|
Database: "testdb",
|
|
AuthDatabase: "admin",
|
|
IsHttps: false,
|
|
IsSrv: false,
|
|
CpuCount: 1,
|
|
}
|
|
}
|
|
|
|
func CreateTestDatabase(
|
|
workspaceID uuid.UUID,
|
|
storage *storages.Storage,
|
|
notifier *notifiers.Notifier,
|
|
) *Database {
|
|
database := &Database{
|
|
WorkspaceID: &workspaceID,
|
|
Name: "test " + uuid.New().String(),
|
|
Type: DatabaseTypePostgresLogical,
|
|
PostgresqlLogical: GetTestPostgresConfig(),
|
|
Notifiers: []notifiers.Notifier{
|
|
*notifier,
|
|
},
|
|
}
|
|
|
|
database, err := databaseRepository.Save(database)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return database
|
|
}
|
|
|
|
// CreateTestPhysicalPostgresDatabase persists a FULL_ONLY physical DB row pointed at the shared
|
|
// compose primary source for versionTag (the cross-suite fixture, like the logical PG-16 fixture).
|
|
func CreateTestPhysicalPostgresDatabase(
|
|
workspaceID uuid.UUID,
|
|
notifier *notifiers.Notifier,
|
|
versionTag string,
|
|
) *Database {
|
|
host, port := physicalPrimaryEndpoint(versionTag)
|
|
|
|
return CreateTestPhysicalPostgresDatabaseWithType(
|
|
host, port, workspaceID, notifier, versionTag, postgresql_physical.BackupTypeFullOnly)
|
|
}
|
|
|
|
// CreateTestPhysicalPostgresDatabaseWithType persists a physical DB row at host:port with an explicit
|
|
// BackupType, for scheduler-driven incremental / WAL-stream chains whose eligibility the scheduler
|
|
// reads from this DB-level field. The caller owns the container lifecycle (host/port may be a
|
|
// throwaway testcontainers endpoint), keeping this file free of the testcontainers dependency.
|
|
func CreateTestPhysicalPostgresDatabaseWithType(
|
|
host string,
|
|
port int,
|
|
workspaceID uuid.UUID,
|
|
notifier *notifiers.Notifier,
|
|
versionTag string,
|
|
backupType postgresql_physical.BackupType,
|
|
) *Database {
|
|
database := &Database{
|
|
WorkspaceID: &workspaceID,
|
|
Name: "test-physical-pg " + uuid.New().String(),
|
|
Type: DatabaseTypePostgresPhysical,
|
|
PostgresqlPhysical: GetTestPhysicalPostgresConfigWithType(host, port, versionTag, backupType),
|
|
Notifiers: []notifiers.Notifier{
|
|
*notifier,
|
|
},
|
|
}
|
|
|
|
database, err := databaseRepository.Save(database)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return database
|
|
}
|
|
|
|
// CreateTestPhysicalPostgresDatabaseNoSummary persists a physical DB row at host:port pointed at a
|
|
// summarize_wal=off cluster, so incremental pre-checks reach the SUMMARIZER_OFF branch
|
|
// deterministically (ALTER SYSTEM cannot override a command-line GUC on the standard fixture).
|
|
func CreateTestPhysicalPostgresDatabaseNoSummary(
|
|
host string,
|
|
port int,
|
|
workspaceID uuid.UUID,
|
|
notifier *notifiers.Notifier,
|
|
versionTag string,
|
|
) *Database {
|
|
database := &Database{
|
|
WorkspaceID: &workspaceID,
|
|
Name: "test-physical-pg-no-summary " + uuid.New().String(),
|
|
Type: DatabaseTypePostgresPhysical,
|
|
PostgresqlPhysical: GetTestPhysicalPostgresConfigNoSummary(host, port, versionTag),
|
|
Notifiers: []notifiers.Notifier{
|
|
*notifier,
|
|
},
|
|
}
|
|
|
|
database, err := databaseRepository.Save(database)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return database
|
|
}
|
|
|
|
func CreateTestMariadbDatabase(
|
|
host string,
|
|
port int,
|
|
workspaceID uuid.UUID,
|
|
notifier *notifiers.Notifier,
|
|
) *Database {
|
|
database := &Database{
|
|
WorkspaceID: &workspaceID,
|
|
Name: "test-mariadb " + uuid.New().String(),
|
|
Type: DatabaseTypeMariadb,
|
|
Mariadb: GetTestMariadbConfig(host, port),
|
|
Notifiers: []notifiers.Notifier{
|
|
*notifier,
|
|
},
|
|
}
|
|
|
|
database, err := databaseRepository.Save(database)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return database
|
|
}
|
|
|
|
func CreateTestMongodbDatabase(
|
|
host string,
|
|
port int,
|
|
workspaceID uuid.UUID,
|
|
notifier *notifiers.Notifier,
|
|
) *Database {
|
|
database := &Database{
|
|
WorkspaceID: &workspaceID,
|
|
Name: "test-mongodb " + uuid.New().String(),
|
|
Type: DatabaseTypeMongodb,
|
|
Mongodb: GetTestMongodbConfig(host, port),
|
|
Notifiers: []notifiers.Notifier{
|
|
*notifier,
|
|
},
|
|
}
|
|
|
|
database, err := databaseRepository.Save(database)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return database
|
|
}
|
|
|
|
func RemoveTestDatabase(database *Database) {
|
|
if err := databaseService.DeleteForTest(database.ID); err != nil {
|
|
panic(fmt.Sprintf("failed to delete test database: %v", err))
|
|
}
|
|
}
|