Skip to content

Commit 89b417c

Browse files
committed
centralize validation
1 parent e8ea42b commit 89b417c

8 files changed

Lines changed: 64 additions & 26 deletions

File tree

db/background_mgr_resync_dcp.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import (
3030
// Resync Implementation of Background Manager Process using DCP stream
3131
// =====================================================================
3232

33-
const DefaultResyncPartitions = 64
33+
const DefaultResyncPartitions uint16 = 64
3434

3535
type ResyncManagerDCP struct {
3636
db *DatabaseContext
@@ -413,12 +413,7 @@ func (r *ResyncManagerDCP) Run(ctx context.Context, options ResyncOptions, persi
413413
return fmt.Errorf("Error generating CBGT index name: %v", err)
414414
}
415415

416-
var partitionCount uint16
417-
if db.Options.UnsupportedOptions != nil && db.Options.UnsupportedOptions.ResyncPartitions != nil && *db.Options.UnsupportedOptions.ResyncPartitions > 0 {
418-
partitionCount = *db.Options.UnsupportedOptions.ResyncPartitions
419-
} else {
420-
partitionCount = DefaultResyncPartitions
421-
}
416+
partitionCount := r.db.GetResyncPartitionCount()
422417
base.DebugfCtx(ctx, base.KeyAll, "Using %d partitions for resync", partitionCount)
423418

424419
opts := base.ShardedDCPOptions{

db/background_mgr_resync_dcp_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ func TestResyncManagerDCPStart(t *testing.T) {
243243
require.NoError(t, db.ResyncManager.Start(ctx, options))
244244
stats := waitForResyncState(t, db, BackgroundProcessStateCompleted)
245245

246+
assert.Equal(t, DefaultResyncPartitions, db.GetResyncPartitionCount())
247+
246248
assert.GreaterOrEqual(t, stats.DocsProcessed, int64(docsToCreate)) // may be processing tombstones from previous tests
247249
assert.Equal(t, int64(0), stats.DocsChanged)
248250
assert.GreaterOrEqual(t, stats.DocsTargeted, uint64(docsToCreate))
@@ -284,6 +286,8 @@ func TestResyncManagerDCPStart(t *testing.T) {
284286

285287
RequireBackgroundManagerState(t, db.ResyncManager, BackgroundProcessStateCompleted)
286288

289+
assert.Equal(t, DefaultResyncPartitions, db.GetResyncPartitionCount())
290+
287291
stats := getResyncStats(t, db)
288292
// If there are tombstones from older docs which have been deleted from the bucket, processed docs will
289293
// be greater than DocsChanged

db/database.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,11 @@ func NewDatabaseContext(ctx context.Context, dbName string, bucket base.Bucket,
471471
if err != nil {
472472
return nil, err
473473
}
474+
if dbContext.useShardedDCP() {
475+
if partitionCount := dbContext.GetResyncPartitionCount(); partitionCount > dbContext.numVBuckets {
476+
return nil, NewDatabaseError(DatabaseInvalidResyncPartitions)
477+
}
478+
}
474479
err = dbContext.updateCCVSettings(ctx)
475480
if err != nil {
476481
return nil, err
@@ -2726,6 +2731,15 @@ func (db *DatabaseContext) NumVBuckets() uint16 {
27262731
return db.numVBuckets
27272732
}
27282733

2734+
// GetResyncPartitionCount returns the number of partitions to use for the DCP resync feed.
2735+
// Returns the configured ResyncPartitions value if set, otherwise DefaultResyncPartitions.
2736+
func (db *DatabaseContext) GetResyncPartitionCount() uint16 {
2737+
if db.Options.UnsupportedOptions != nil && db.Options.UnsupportedOptions.ResyncPartitions != nil && *db.Options.UnsupportedOptions.ResyncPartitions > 0 {
2738+
return *db.Options.UnsupportedOptions.ResyncPartitions
2739+
}
2740+
return DefaultResyncPartitions
2741+
}
2742+
27292743
// InitializeOfflineMode starts polling the database state when the database transitions to offline mode.
27302744
// This enables the database to detect state changes (such as resync requests) from other nodes in the cluster
27312745
// while it is offline. The polling mechanism watches the metadata store for updates to the database state

db/database_error.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,7 @@ func NewDatabaseError(code databaseErrorCode) *DatabaseError {
5353
Code: code,
5454
}
5555
}
56+
57+
func (e *DatabaseError) Error() string {
58+
return e.ErrMsg
59+
}

docs/api/components/schemas.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1725,7 +1725,7 @@ Database:
17251725
type: boolean
17261726
resync_partitions:
17271727
description: |-
1728-
Number of partitions to use for distributed DCP resync. If not set, defaults to the import partitions value.
1728+
Number of partitions to use for distributed DCP resync. Maximum number is the number of vBuckets for the backing bucket.
17291729
type: integer
17301730
minimum: 1
17311731
maximum: 1024

rest/adminapitest/admin_api_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,9 @@ func TestResyncUsingDCPStream(t *testing.T) {
751751

752752
resyncManagerStatus := rt.WaitForResyncDCPStatus(db.BackgroundProcessStateCompleted)
753753

754+
dbCtx := rt.GetDatabase()
755+
assert.Equal(t, db.DefaultResyncPartitions, dbCtx.GetResyncPartitionCount())
756+
754757
assert.Equal(t, testCase.docsCreated, int(rt.GetDatabase().DbStats.Database().SyncFunctionCount.Value()))
755758
if !base.UnitTestUrlIsWalrus() && !base.TestsDisableGSI() {
756759
// It is possible for Couchbase Server GSI runs which use DCP purge to two DCP events from a previous

rest/adminapitest/resync_test.go

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -467,23 +467,44 @@ func TestResyncRequireResyncDefaultMetadataID(t *testing.T) {
467467
rt.WaitForDatabaseState(db2Name, db.RunStateString[db.DBOnline])
468468
}
469469

470-
func TestResyncPartitionsMaximumValidation(t *testing.T) {
470+
func TestResyncPartitionsValidation(t *testing.T) {
471471
rt := rest.NewRestTester(t, &rest.RestTesterConfig{
472472
PersistentConfig: true,
473473
})
474474
defer rt.Close()
475475

476-
dbConfig := rt.NewDbConfig()
477-
numVBuckets, err := rt.TestBucket.GetMaxVbno(base.TestCtx(t))
476+
numVBuckets, err := rt.TestBucket.GetMaxVbno(rt.Context())
478477
require.NoError(t, err)
479-
invalidPartitions := numVBuckets + 1
480-
dbConfig.Unsupported = &db.UnsupportedOptions{
481-
ResyncPartitions: &invalidPartitions,
482-
}
483478

484-
resp := rt.CreateDatabase("db1", dbConfig)
485-
rest.RequireStatus(t, resp, http.StatusInternalServerError)
486-
assert.Contains(t, resp.Body.String(), "resync_partitions must be between 1 and")
479+
t.Run("invalid partitions exceeding vbucket count are rejected", func(t *testing.T) {
480+
// Partition count validation only applies when using sharded DCP (EE + CBS).
481+
if !base.IsEnterpriseEdition() || base.UnitTestUrlIsWalrus() {
482+
t.Skip("Partition count validation only applies to EE with CBS")
483+
}
484+
dbConfig := rt.NewDbConfig()
485+
invalidPartitions := numVBuckets + 1
486+
dbConfig.Unsupported = &db.UnsupportedOptions{
487+
ResyncPartitions: &invalidPartitions,
488+
}
489+
resp := rt.CreateDatabase("db1", dbConfig)
490+
rest.RequireStatus(t, resp, http.StatusInternalServerError)
491+
assert.Contains(t, resp.Body.String(), "resync_partitions must be between 1 and")
492+
})
493+
494+
t.Run("valid custom partitions are used by ResyncManagerDCP", func(t *testing.T) {
495+
dbConfig := rt.NewDbConfig()
496+
customPartitions := uint16(16)
497+
dbConfig.Unsupported = &db.UnsupportedOptions{
498+
ResyncPartitions: &customPartitions,
499+
}
500+
resp := rt.CreateDatabase("db2", dbConfig)
501+
rest.RequireStatus(t, resp, http.StatusCreated)
502+
503+
dbCtx, err := rt.ServerContext().GetDatabase(rt.Context(), "db2")
504+
require.NoError(t, err)
505+
506+
assert.Equal(t, customPartitions, dbCtx.GetResyncPartitionCount())
507+
})
487508
}
488509

489510
// TestDistributedResync is a long-running dev-time test used to validate cbgt rebalance behavior during resync.

rest/server_context.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,7 +1037,11 @@ func (sc *ServerContext) _getOrAddDatabaseFromConfig(ctx context.Context, config
10371037
dbcontext, err = db.NewDatabaseContext(ctx, dbName, bucket, autoImport, contextOptions)
10381038
if err != nil {
10391039
if options.loadFromBucket {
1040-
sc._handleInvalidDatabaseConfig(ctx, spec.BucketName, config, db.NewDatabaseError(db.DatabaseCreateDatabaseContextError))
1040+
var dbErr *db.DatabaseError
1041+
if !errors.As(err, &dbErr) {
1042+
dbErr = db.NewDatabaseError(db.DatabaseCreateDatabaseContextError)
1043+
}
1044+
sc._handleInvalidDatabaseConfig(ctx, spec.BucketName, config, dbErr)
10411045
}
10421046
return nil, err
10431047
}
@@ -1093,13 +1097,6 @@ func (sc *ServerContext) _getOrAddDatabaseFromConfig(ctx context.Context, config
10931097
}
10941098
}
10951099

1096-
if config.Unsupported != nil && config.Unsupported.ResyncPartitions != nil && *config.Unsupported.ResyncPartitions > dbcontext.NumVBuckets() {
1097-
if options.loadFromBucket {
1098-
sc._handleInvalidDatabaseConfig(ctx, spec.BucketName, config, db.NewDatabaseError(db.DatabaseInvalidResyncPartitions))
1099-
}
1100-
return nil, fmt.Errorf("resync_partitions must be between 1 and %d, got %d", dbcontext.NumVBuckets(), *config.Unsupported.ResyncPartitions)
1101-
}
1102-
11031100
if config.CORS != nil {
11041101
dbcontext.CORS = config.DbConfig.CORS
11051102
} else {

0 commit comments

Comments
 (0)