From 0eb61e12385a6bce7ef2abe3e554ef00dc29fee7 Mon Sep 17 00:00:00 2001 From: Adam Fraser Date: Mon, 24 Jun 2024 16:51:36 -0700 Subject: [PATCH] CBG-4020 Add init_in_progress, change 'reason' to 'require_resync' flag (#6916) Backports CBG-4019 to 3.1.8. --- docs/api/components/schemas.yaml | 13 ++++---- docs/api/paths/admin/db-.yaml | 3 ++ .../collections_admin_api_test.go | 8 ++--- rest/api.go | 11 ++++--- rest/database_init_manager.go | 3 ++ rest/indextest/index_test.go | 30 +++++++++++++++++++ rest/server_context.go | 7 +++-- 7 files changed, 59 insertions(+), 16 deletions(-) diff --git a/docs/api/components/schemas.yaml b/docs/api/components/schemas.yaml index 4be79ca410..2999b14040 100644 --- a/docs/api/components/schemas.yaml +++ b/docs/api/components/schemas.yaml @@ -2436,8 +2436,11 @@ CollectionNames: - Starting - Stopping - Resyncing - reason: - description: Optional reason a database is in a particular state. - enum: - - require_resync - - "" + require_resync: + description: Indicates whether the database requires resync before it can be brought online. + type: boolean + example: true + init_in_progress: + description: Indicates whether database initialization is in progress. + type: boolean + example: true diff --git a/docs/api/paths/admin/db-.yaml b/docs/api/paths/admin/db-.yaml index d74cada125..90ea68e249 100644 --- a/docs/api/paths/admin/db-.yaml +++ b/docs/api/paths/admin/db-.yaml @@ -66,6 +66,9 @@ get: description: Unique server identifier. type: string example: 995618a6a6cc9ac79731bd13240e19b5 + init_in_progress: + description: Indicates whether database initialization is in progress. + type: boolean scopes: description: 'Scopes that are used by the database.' type: object diff --git a/rest/adminapitest/collections_admin_api_test.go b/rest/adminapitest/collections_admin_api_test.go index 2f68726d99..63c8305e19 100644 --- a/rest/adminapitest/collections_admin_api_test.go +++ b/rest/adminapitest/collections_admin_api_test.go @@ -265,10 +265,10 @@ func TestRequireResync(t *testing.T) { // databases sorted alphabetically require.Equal(t, db1Name, allDBsSummary[0].DBName) require.Equal(t, db.RunStateString[db.DBOnline], allDBsSummary[0].State) - require.Equal(t, "", allDBsSummary[0].Reason) + require.Equal(t, false, allDBsSummary[0].RequireResync) require.Equal(t, db2Name, allDBsSummary[1].DBName) require.Equal(t, db.RunStateString[db.DBOffline], allDBsSummary[1].State) - require.Equal(t, rest.OfflineReasonRequireResync, allDBsSummary[1].Reason) + require.Equal(t, true, allDBsSummary[1].RequireResync) // Run resync for collection resyncCollections := make(db.ResyncCollections, 0) @@ -296,10 +296,10 @@ func TestRequireResync(t *testing.T) { // databases sorted alphabetically require.Equal(t, db1Name, allDBsSummary[0].DBName) require.Equal(t, db.RunStateString[db.DBOnline], allDBsSummary[0].State) - require.Equal(t, "", allDBsSummary[0].Reason) + require.Equal(t, false, allDBsSummary[0].RequireResync) require.Equal(t, db2Name, allDBsSummary[1].DBName) require.Equal(t, db.RunStateString[db.DBOffline], allDBsSummary[1].State) - require.Equal(t, "", allDBsSummary[1].Reason) + require.Equal(t, false, allDBsSummary[1].RequireResync) resp = rt.SendAdminRequest("GET", "/"+db2Name+"/", "") rest.RequireStatus(t, resp, http.StatusOK) diff --git a/rest/api.go b/rest/api.go index ca1dc70b27..1b7e05f05c 100644 --- a/rest/api.go +++ b/rest/api.go @@ -404,14 +404,16 @@ type DatabaseRoot struct { State string `json:"state"` ServerUUID string `json:"server_uuid,omitempty"` RequireResync []string `json:"require_resync,omitempty"` + InitializationActive bool `json:"init_in_progress,omitempty"` Scopes map[string]databaseRootScope `json:"scopes,omitempty"` // stats for each scope/collection } type DbSummary struct { - DBName string `json:"db_name"` - Bucket string `json:"bucket"` - State string `json:"state"` - Reason string `json:"reason,omitempty"` + DBName string `json:"db_name"` + Bucket string `json:"bucket"` + State string `json:"state"` + InitializationActive bool `json:"init_in_progress,omitempty"` + RequireResync bool `json:"require_resync,omitempty"` } type databaseRootScope struct { @@ -448,6 +450,7 @@ func (h *handler) handleGetDB() error { State: runState, ServerUUID: h.db.DatabaseContext.ServerUUID, RequireResync: h.db.RequireResync.ScopeAndCollectionNames(), + InitializationActive: h.server.DatabaseInitManager.HasActiveInitialization(h.db.Name), // TODO: If running with multiple scope/collections // Scopes: map[string]databaseRootScope{ diff --git a/rest/database_init_manager.go b/rest/database_init_manager.go index 32f2d8226b..2151932876 100644 --- a/rest/database_init_manager.go +++ b/rest/database_init_manager.go @@ -115,6 +115,9 @@ func (m *DatabaseInitManager) InitializeDatabase(ctx context.Context, startupCon } func (m *DatabaseInitManager) HasActiveInitialization(dbName string) bool { + if m == nil { + return false + } m.workersLock.Lock() defer m.workersLock.Unlock() _, ok := m.workers[dbName] diff --git a/rest/indextest/index_test.go b/rest/indextest/index_test.go index 98c76e1102..93703094db 100644 --- a/rest/indextest/index_test.go +++ b/rest/indextest/index_test.go @@ -426,6 +426,7 @@ func TestAsyncOnlineOffline(t *testing.T) { rest.WaitForChannel(t, initStarted, "waiting for initialization to start") log.Printf("initialization started") waitAndRequireDBState(t, dbName, db.DBOffline) + verifyInitializationActive(t, dbName, true) // Set up payloads for upserting db state onlineConfigUpsert := rest.DbConfig{ @@ -444,11 +445,13 @@ func TestAsyncOnlineOffline(t *testing.T) { resp = rest.BootstrapAdminRequest(t, http.MethodPost, "/"+dbName+"/_config", string(dbOnlineConfigPayload)) resp.RequireStatus(http.StatusCreated) waitAndRequireDBState(t, dbName, db.DBStarting) + verifyInitializationActive(t, dbName, true) // Take the database offline while async init is still in progress resp = rest.BootstrapAdminRequest(t, http.MethodPost, "/"+dbName+"/_config", string(dbOfflineConfigPayload)) resp.RequireStatus(http.StatusCreated) waitAndRequireDBState(t, dbName, db.DBOffline) + verifyInitializationActive(t, dbName, true) // Verify offline changes can still be made resp = rest.BootstrapAdminRequest(t, http.MethodGet, "/"+keyspace+"/_config/sync", "") @@ -458,10 +461,12 @@ func TestAsyncOnlineOffline(t *testing.T) { resp = rest.BootstrapAdminRequest(t, http.MethodPost, "/"+dbName+"/_config", string(dbOnlineConfigPayload)) resp.RequireStatus(http.StatusCreated) waitAndRequireDBState(t, dbName, db.DBStarting) + verifyInitializationActive(t, dbName, true) // Unblock initialization, verify status goes to Online close(unblockInit) waitAndRequireDBState(t, dbName, db.DBOnline) + verifyInitializationActive(t, dbName, true) // Verify only four collections were initialized (offline/online didn't trigger duplicate initialization) totalCount := atomic.LoadInt64(&collectionCount) @@ -471,11 +476,13 @@ func TestAsyncOnlineOffline(t *testing.T) { resp = rest.BootstrapAdminRequest(t, http.MethodPost, "/"+dbName+"/_config", string(dbOfflineConfigPayload)) resp.RequireStatus(http.StatusCreated) waitAndRequireDBState(t, dbName, db.DBOffline) + verifyInitializationActive(t, dbName, false) // Take database back online after init complete, verify successful resp = rest.BootstrapAdminRequest(t, http.MethodPost, "/"+dbName+"/_config", string(dbOnlineConfigPayload)) resp.RequireStatus(http.StatusCreated) waitAndRequireDBState(t, dbName, db.DBOnline) + verifyInitializationActive(t, dbName, false) } @@ -888,6 +895,29 @@ func TestAsyncInitRemoteConfigUpdates(t *testing.T) { waitAndRequireDBState(t, dbName, db.DBOnline) } +// verifyInitializationActive verifies the expected value of InitializationActive on db and verbose all_dbs responses +func verifyInitializationActive(t *testing.T, dbName string, expectedValue bool) { + + var dbResult rest.DatabaseRoot + resp := rest.BootstrapAdminRequest(t, http.MethodGet, "/"+dbName+"/", "") + resp.RequireStatus(http.StatusOK) + require.NoError(t, base.JSONUnmarshal([]byte(resp.Body), &dbResult)) + require.Equal(t, expectedValue, dbResult.InitializationActive) + + var allDbResult []rest.DbSummary + allDbResp := rest.BootstrapAdminRequest(t, http.MethodGet, "/_all_dbs?verbose=true", "") + allDbResp.RequireStatus(http.StatusOK) + require.NoError(t, base.JSONUnmarshal([]byte(allDbResp.Body), &allDbResult)) + dbFound := false + for _, dbSummary := range allDbResult { + if dbSummary.DBName == dbName { + dbFound = true + require.Equal(t, expectedValue, dbSummary.InitializationActive) + } + } + require.True(t, dbFound, "Database not found in _all_dbs response") +} + func makeDbConfig(t *testing.T, tb *base.TestBucket, syncFunction string, importFilter string) rest.DbConfig { scopesConfig := rest.GetCollectionsConfig(t, tb, 1) diff --git a/rest/server_context.go b/rest/server_context.go index 79e9513894..82f2a07327 100644 --- a/rest/server_context.go +++ b/rest/server_context.go @@ -43,8 +43,6 @@ const kStatsReportInterval = time.Hour const kDefaultSlowQueryWarningThreshold = 500 // ms const KDefaultNumShards = 16 -const OfflineReasonRequireResync = "require_resync" - var errCollectionsUnsupported = base.HTTPErrorf(http.StatusBadRequest, "Named collections specified in database config, but not supported by connected Couchbase Server.") var ErrSuspendingDisallowed = errors.New("database does not allow suspending") @@ -338,9 +336,12 @@ func (sc *ServerContext) allDatabaseSummaries() []DbSummary { } if state == db.RunStateString[db.DBOffline] { if len(dbctx.RequireResync.ScopeAndCollectionNames()) > 0 { - summary.Reason = OfflineReasonRequireResync + summary.RequireResync = true } } + if sc.DatabaseInitManager.HasActiveInitialization(name) { + summary.InitializationActive = true + } dbs = append(dbs, summary) } sort.Slice(dbs, func(i, j int) bool {