diff --git a/CubeMaster/cmd/cubemaster/app/main.go b/CubeMaster/cmd/cubemaster/app/main.go index e9e515f6e..ae6c90183 100644 --- a/CubeMaster/cmd/cubemaster/app/main.go +++ b/CubeMaster/cmd/cubemaster/app/main.go @@ -24,6 +24,7 @@ import ( "github.com/tencentcloud/CubeSandbox/CubeDB/migrate" "github.com/tencentcloud/CubeSandbox/CubeDB/tombstone" "github.com/tencentcloud/CubeSandbox/CubeMaster/pkg/base/config" + "github.com/tencentcloud/CubeSandbox/CubeMaster/pkg/base/db" "github.com/tencentcloud/CubeSandbox/CubeMaster/pkg/base/log" "github.com/tencentcloud/CubeSandbox/CubeMaster/pkg/base/recov" "github.com/tencentcloud/CubeSandbox/CubeMaster/pkg/cubelet/grpcconn" @@ -235,23 +236,16 @@ func initDatabaseSchema(ctx context.Context, cfg *config.Config) error { // covering the host/node inventory tables (t_cube_host_*, t_cube_node_*) // and the instance tables (t_cube_template_*, t_cube_instance_*, // t_cube_sandbox_spec, ...), all in the one configured database. - src := cfg.InstanceDBConfig - if src == nil { - return fmt.Errorf("dao: instance_db_config is not set") - } - daoCfg := dao.Config{ - Driver: src.Driver, - Addr: src.Addr, - User: src.User, - Pwd: src.Pwd, - DBName: src.DBName, - ConnTimeoutSeconds: src.ConnTimeout, - ReadTimeoutSeconds: src.ReadTimeout, - WriteTimeoutSeconds: src.WriteTimeout, - MaxIdleConns: src.MaxIdleConns, - MaxOpenConns: src.MaxOpenConns, - MaxConnLifeTimeSeconds: src.MaxConnLifeTimeSeconds, - MigrationLockTimeoutSeconds: src.MigrationLockTimeoutSeconds, + // Build the dao config through the same mapping as the integration / + // mock-debug bootstrap, so the two dao.Open identities cannot drift. + // The snapshots are still read at different times (mock_db reads the + // live global via config.GetDbConfig(); this reads cfg captured at + // Run() start), so a config hotswap landing between MockInit and here + // would still change the identity and fail this dao.Open with + // "dao: already opened with ... (requested ...)". + daoCfg, err := db.ConfigFromDBConfig(cfg.InstanceDBConfig) + if err != nil { + return fmt.Errorf("dao: %w", err) } if _, err := dao.Open(ctx, daoCfg); err != nil { return fmt.Errorf("dao open: %w", err) diff --git a/CubeMaster/integration/mock_init.go b/CubeMaster/integration/mock_init.go index 8278abcb2..c960c3c7c 100644 --- a/CubeMaster/integration/mock_init.go +++ b/CubeMaster/integration/mock_init.go @@ -22,6 +22,7 @@ import ( "github.com/alicebob/miniredis/v2" "github.com/gomodule/redigo/redis" "github.com/google/uuid" + "github.com/tencentcloud/CubeSandbox/CubeDB/dao" "github.com/tencentcloud/CubeSandbox/CubeMaster/api/services/cubebox/v1" cubeleterrorcode "github.com/tencentcloud/CubeSandbox/CubeMaster/api/services/errorcode/v1" "github.com/tencentcloud/CubeSandbox/CubeMaster/api/services/images/v1" @@ -654,10 +655,22 @@ func metricNow() []byte { } func mock_db() { + // Establish the shared dao handle before db.Init (a dao.Default() + // wrapper) needs it. dao.Open is idempotent for the same config + // identity, so the later open in app.Run's initDatabaseSchema is a + // no-op; schema migration (including t_cube_host_type) still runs + // there. Both call sites build their dao config through + // db.ConfigFromDBConfig so the identities cannot drift. + daoCfg, err := db.ConfigFromDBConfig(config.GetDbConfig()) + if err != nil { + stdlog.Fatalf("integration: dao config fail: %v", err) + } + if _, err := dao.Open(mocktest_Ctx, daoCfg); err != nil { + stdlog.Fatalf("dao open fail:%v", err) + } mocktest_OssDb = db.Init(config.GetDbConfig()) - // Schema (including t_cube_host_type) is owned by the dao.Migrate - // path that the integration test bootstrap runs before tests. } + func mock_getstr() string { return fmt.Sprintf("%d.%d.%d.%d", rand.Int31n(254), rand.Int31n(254), rand.Int31n(254), rand.Int31n(254)) } diff --git a/CubeMaster/pkg/base/db/db.go b/CubeMaster/pkg/base/db/db.go index 29cfb99fc..e176ab403 100644 --- a/CubeMaster/pkg/base/db/db.go +++ b/CubeMaster/pkg/base/db/db.go @@ -6,6 +6,8 @@ package db import ( + "errors" + "github.com/tencentcloud/CubeSandbox/CubeDB/dao" "github.com/tencentcloud/CubeSandbox/CubeMaster/pkg/base/config" "gorm.io/gorm" @@ -20,3 +22,28 @@ func Init(cfg *config.DBConfig) *gorm.DB { _ = cfg return dao.Default() } + +// ConfigFromDBConfig maps a config.DBConfig to the dao.Config used to open +// the shared database handle. dao.Open keys its idempotence on this config +// identity, so every call site that opens the handle before app startup +// (schema init, integration/mock-debug bootstrap) must build the dao config +// through this helper — keeping the two mappings from drifting. +func ConfigFromDBConfig(src *config.DBConfig) (dao.Config, error) { + if src == nil { + return dao.Config{}, errors.New("db config is nil") + } + return dao.Config{ + Driver: src.Driver, + Addr: src.Addr, + User: src.User, + Pwd: src.Pwd, + DBName: src.DBName, + ConnTimeoutSeconds: src.ConnTimeout, + ReadTimeoutSeconds: src.ReadTimeout, + WriteTimeoutSeconds: src.WriteTimeout, + MaxIdleConns: src.MaxIdleConns, + MaxOpenConns: src.MaxOpenConns, + MaxConnLifeTimeSeconds: src.MaxConnLifeTimeSeconds, + MigrationLockTimeoutSeconds: src.MigrationLockTimeoutSeconds, + }, nil +} diff --git a/CubeMaster/pkg/base/db/db_test.go b/CubeMaster/pkg/base/db/db_test.go index d0c0436c5..ffce757b5 100644 --- a/CubeMaster/pkg/base/db/db_test.go +++ b/CubeMaster/pkg/base/db/db_test.go @@ -6,6 +6,7 @@ package db_test import ( "context" + "reflect" "testing" "time" @@ -38,3 +39,45 @@ func TestInitReturnsDaoDefaultOnPostgreSQL(t *testing.T) { t.Fatal("db.Init must return the global dao handle opened by dao.Open") } } + +func TestConfigFromDBConfig(t *testing.T) { + if _, err := db.ConfigFromDBConfig(nil); err == nil { + t.Fatal("ConfigFromDBConfig(nil) must return an error") + } + + src := &config.DBConfig{ + Driver: "postgres", + Addr: "127.0.0.1:5432", + User: "cube", + Pwd: "cube_pass", + DBName: "cube_test", + ConnTimeout: 1, + ReadTimeout: 2, + WriteTimeout: 3, + MaxIdleConns: 4, + MaxOpenConns: 5, + MaxConnLifeTimeSeconds: 6, + MigrationLockTimeoutSeconds: 7, + } + got, err := db.ConfigFromDBConfig(src) + if err != nil { + t.Fatalf("ConfigFromDBConfig: %v", err) + } + want := dao.Config{ + Driver: "postgres", + Addr: "127.0.0.1:5432", + User: "cube", + Pwd: "cube_pass", + DBName: "cube_test", + ConnTimeoutSeconds: 1, + ReadTimeoutSeconds: 2, + WriteTimeoutSeconds: 3, + MaxIdleConns: 4, + MaxOpenConns: 5, + MaxConnLifeTimeSeconds: 6, + MigrationLockTimeoutSeconds: 7, + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("ConfigFromDBConfig mismatch:\n got: %+v\nwant: %+v", got, want) + } +}