-
Notifications
You must be signed in to change notification settings - Fork 1.1k
cubemaster: fix integration tests panicking on unopened dao #1383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking: this refactor drops the actionable error text for a missing DB config. The old path reported No caller matches on the old string, and in the app this path is unreachable with a nil config because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor diagnostic regression: the removed |
||
| } | ||
| if _, err := dao.Open(ctx, daoCfg); err != nil { | ||
| return fmt.Errorf("dao open: %w", err) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking robustness note: this fix rests on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| if _, err := dao.Open(mocktest_Ctx, daoCfg); err != nil { | ||
| stdlog.Fatalf("dao open fail:%v", err) | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fix is correct for the two known bootstrap paths, but it leaves |
||
| 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)) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trivial: the helper is generic over There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This helper is a second copy of the field list that |
||
| 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 | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor precision nit on the comment: the two call sites don't actually read the same snapshot.
mock_db()reads the live global viaconfig.GetDbConfig(), while this readscfg.InstanceDBConfigfrom thecfgcaptured atRun()start. WhatConfigFromDBConfigguarantees is the same mapping, not the same snapshot — if a config hot-reload landed in the window betweenMockInit()andapp.Run(), the two identities would still differ and this seconddao.Openwould fail withalready opened with ... (requested ...). The comment inmock_init.goalready acknowledges that window honestly; consider rewording to "same mapping as" so the claim here doesn't overstate the protection.