Skip to content

Commit 0fb3663

Browse files
bbrksCopilot
andauthored
[4.1.0 Backport] CBG-5484: Support resync invalidate principals on both metadata collections (#8383)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent d07bb47 commit 0fb3663

6 files changed

Lines changed: 163 additions & 85 deletions

File tree

base/constants.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ const (
3636
TestEnvTLSSkipVerify = "SG_TEST_TLS_SKIP_VERIFY"
3737
DefaultTestTLSSkipVerify = true
3838

39+
// Env variable to enable _system._mobile collection for SG metadata
40+
TestEnvUseSystemMetadataCollection = "SG_TEST_USE_SYSTEM_METADATA_COLLECTION"
41+
DefaultTestUseSystemMetadataCollection = false
42+
3943
// Walrus by default, but can set to "Couchbase" to have it use http://127.0.0.1:8091
4044
TestEnvSyncGatewayBackingStore = "SG_TEST_BACKING_STORE"
4145
TestEnvBackingStoreCouchbase = "Couchbase"

base/util_testing.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,21 @@ func TestTLSSkipVerify() bool {
227227
return val
228228
}
229229

230+
// TestUseSystemMetadataCollection returns true if Sync Gateway should use the system metadata collection in tests. Default: DefaultTestUseSystemMetadataCollection
231+
func TestUseSystemMetadataCollection() bool {
232+
useSystemMetadataCollection, isSet := os.LookupEnv(TestEnvUseSystemMetadataCollection)
233+
if !isSet {
234+
return DefaultTestUseSystemMetadataCollection
235+
}
236+
237+
val, err := strconv.ParseBool(useSystemMetadataCollection)
238+
if err != nil {
239+
panic(fmt.Sprintf("unable to parse %q value %q: %v", TestEnvUseSystemMetadataCollection, useSystemMetadataCollection, err))
240+
}
241+
242+
return val
243+
}
244+
230245
func TestUseCouchbaseServerDockerName() (bool, string) {
231246
testX509CouchbaseServerDockerName, isSet := os.LookupEnv(TestEnvCouchbaseServerDockerName)
232247
if !isSet {

db/background_mgr_resync_dcp.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -834,10 +834,18 @@ type ResyncManagerStatusDocDCP struct {
834834

835835
// initializePrincipalDocsIndex creates the metadata indexes required for resync
836836
func initializePrincipalDocsIndex(ctx context.Context, db *DatabaseContext) error {
837-
n1qlStore, ok := base.AsN1QLStore(db.MetadataStore)
838-
if !ok {
839-
return errors.New("Cannot create indexes on non-Couchbase data store.")
837+
var dataStores []base.DataStore
838+
839+
if metadataStore, ok := db.MetadataStore.(*base.MetadataStore); ok {
840+
// need to ensure both primary and fallback have index
841+
dataStores = append(dataStores, metadataStore.Primary())
842+
if !metadataStore.MigrationComplete() {
843+
dataStores = append(dataStores, metadataStore.Fallback())
844+
}
845+
} else {
846+
dataStores = append(dataStores, db.MetadataStore)
840847
}
848+
841849
options := InitializeIndexOptions{
842850
WaitForIndexesOnlineOption: base.WaitForIndexesDefault,
843851
NumReplicas: db.Options.NumIndexReplicas,
@@ -846,7 +854,17 @@ func initializePrincipalDocsIndex(ctx context.Context, db *DatabaseContext) erro
846854
NumPartitions: db.numIndexPartitions(),
847855
}
848856

849-
return InitializeIndexes(ctx, n1qlStore, options)
857+
var me *base.MultiError
858+
for _, ds := range dataStores {
859+
n1qlStore, ok := base.AsN1QLStore(ds)
860+
if !ok {
861+
return fmt.Errorf("Cannot create indexes on non-Couchbase data store.")
862+
}
863+
if err := InitializeIndexes(ctx, n1qlStore, options); err != nil {
864+
me = me.Append(err)
865+
}
866+
}
867+
return me.ErrorOrNil()
850868
}
851869

852870
// getResyncDCPClientOptions returns the default set of DCPClientOptions suitable for resync. collectionIDs

db/indexes.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,16 @@ func InitializeIndexes(ctx context.Context, n1QLStore base.N1QLStore, options In
413413
return nil
414414
}
415415

416-
indexesMeta, err := base.GetIndexesMeta(ctx, n1QLStore, requiredIndexes.FullIndexNames())
416+
var (
417+
indexesMeta map[string]base.IndexMeta
418+
err error
419+
)
420+
// TODO: Could probably always route through `GetSystemCollectionIndexesMeta` but will defer for post-4.1 cleanup?
421+
if base.IsMobileSystemCollection(n1QLStore) {
422+
indexesMeta, err = base.GetSystemCollectionIndexesMeta(ctx, n1QLStore, n1QLStore.ScopeName(), n1QLStore.CollectionName(), requiredIndexes.FullIndexNames())
423+
} else {
424+
indexesMeta, err = base.GetIndexesMeta(ctx, n1QLStore, requiredIndexes.FullIndexNames())
425+
}
417426
if err != nil {
418427
base.WarnfCtx(ctx, "Error getting indexes meta: %v - continuing to create", err)
419428
}
@@ -432,7 +441,7 @@ func InitializeIndexes(ctx context.Context, n1QLStore base.N1QLStore, options In
432441

433442
if len(requiredIndexes) == 0 {
434443
// all indexes are already online so we can return early without any per-index queries
435-
base.InfofCtx(ctx, base.KeyAll, "Indexes ready - already online when checked")
444+
base.InfofCtx(ctx, base.KeyAll, "Indexes ready for %q - already online when checked", base.MD(n1QLStore.GetName()))
436445
return nil
437446
}
438447

rest/adminapitest/resync_test.go

Lines changed: 77 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,20 @@ func TestResyncRegenerateSequencesPrincipals(t *testing.T) {
222222
}
223223

224224
func TestResyncInvalidatePrincipals(t *testing.T) {
225+
var tests = []struct {
226+
name string
227+
useSystemScopeMetadataCollection bool
228+
}{
229+
{
230+
name: "useSystemScopeMetadataCollection=false",
231+
useSystemScopeMetadataCollection: false,
232+
},
233+
{
234+
name: "useSystemScopeMetadataCollection=true",
235+
useSystemScopeMetadataCollection: true,
236+
},
237+
}
238+
225239
initialSyncFn := `
226240
function(doc) {
227241
access(doc.userName, "channelABC");
@@ -236,71 +250,82 @@ func TestResyncInvalidatePrincipals(t *testing.T) {
236250
role(doc.userName, "role:roleDEF");
237251
}`
238252

239-
rt := rest.NewRestTester(t, &rest.RestTesterConfig{
240-
PersistentConfig: true,
241-
SyncFn: initialSyncFn,
242-
})
243-
defer rt.Close()
253+
for _, test := range tests {
254+
t.Run(test.name, func(t *testing.T) {
255+
// this condition is not allowed via regular db config-level validation
256+
// but RestTester code doesn't perform full dbconfig validation to block this
257+
if test.useSystemScopeMetadataCollection && base.TestsDisableGSI() {
258+
t.Skip("use_views not supported with system scoped metadata collection")
259+
}
244260

245-
dbConfig := rt.NewDbConfig()
246-
ds := rt.TestBucket.GetSingleDataStore()
247-
scopeName := ds.ScopeName()
248-
collectionName := ds.CollectionName()
261+
rt := rest.NewRestTester(t, &rest.RestTesterConfig{
262+
PersistentConfig: true,
263+
SyncFn: initialSyncFn,
264+
UseSystemScopeMetadataCollection: base.Ptr(test.useSystemScopeMetadataCollection),
265+
})
266+
defer rt.Close()
267+
268+
dbConfig := rt.NewDbConfig()
269+
ds := rt.TestBucket.GetSingleDataStore()
270+
scopeName := ds.ScopeName()
271+
collectionName := ds.CollectionName()
249272

250-
rest.RequireStatus(t, rt.CreateDatabase("db1", dbConfig), http.StatusCreated)
273+
rest.RequireStatus(t, rt.CreateDatabase("db1", dbConfig), http.StatusCreated)
251274

252-
// Set up user and role
253-
username := "alice"
254-
rolename := "foo"
255-
grantingDocID := "grantDoc"
256-
grantingDocBody := `{
275+
// Set up user and role
276+
username := "alice"
277+
rolename := "foo"
278+
grantingDocID := "grantDoc"
279+
grantingDocBody := `{
257280
"userName":"alice",
258281
"roleName":"foo"
259282
}`
260-
rt.CreateUser(username, nil)
283+
rt.CreateUser(username, nil)
261284

262-
response := rt.SendAdminRequest(http.MethodPut, "/{{.db}}/_role/"+rolename, rest.GetRolePayload(t, rolename, rt.GetSingleDataStore(), nil))
263-
rest.RequireStatus(t, response, http.StatusCreated)
285+
response := rt.SendAdminRequest(http.MethodPut, "/{{.db}}/_role/"+rolename, rest.GetRolePayload(t, rolename, rt.GetSingleDataStore(), nil))
286+
rest.RequireStatus(t, response, http.StatusCreated)
264287

265-
// Write doc to perform dynamic grants
266-
response = rt.SendAdminRequest(http.MethodPut, "/{{.keyspace}}/"+grantingDocID, grantingDocBody)
267-
rest.RequireStatus(t, response, http.StatusCreated)
288+
// Write doc to perform dynamic grants
289+
response = rt.SendAdminRequest(http.MethodPut, "/{{.keyspace}}/"+grantingDocID, grantingDocBody)
290+
rest.RequireStatus(t, response, http.StatusCreated)
268291

269-
ctx := rt.Context()
270-
user, err := rt.GetDatabase().Authenticator(ctx).GetUser(username)
271-
require.NoError(t, err)
272-
channels := user.CollectionChannels(scopeName, collectionName)
273-
_, ok := channels["channelABC"]
274-
require.True(t, ok, "user should have channel channelABC")
275-
roles := user.RoleNames()
276-
_, ok = roles["roleABC"]
277-
require.True(t, ok, "user should have role roleABC")
292+
ctx := rt.Context()
293+
user, err := rt.GetDatabase().Authenticator(ctx).GetUser(username)
294+
require.NoError(t, err)
295+
channels := user.CollectionChannels(scopeName, collectionName)
296+
_, ok := channels["channelABC"]
297+
require.True(t, ok, "user should have channel channelABC")
298+
roles := user.RoleNames()
299+
_, ok = roles["roleABC"]
300+
require.True(t, ok, "user should have role roleABC")
278301

279-
rt.TakeDbOffline()
302+
rt.TakeDbOffline()
280303

281-
// Update the sync function
282-
rt.SyncFn = updatedSyncFn
283-
updatedDbConfig := rt.NewDbConfig()
284-
rt.UpsertDbConfig("db1", updatedDbConfig)
285-
rt.TakeDbOffline()
304+
// Update the sync function
305+
rt.SyncFn = updatedSyncFn
306+
updatedDbConfig := rt.NewDbConfig()
307+
rt.UpsertDbConfig("db1", updatedDbConfig)
308+
rt.TakeDbOffline()
286309

287-
// Run resync
288-
rest.RequireStatus(t, rt.SendAdminRequest(http.MethodPost, "/{{.db}}/_resync?action=start", ""), http.StatusOK)
289-
_ = rt.WaitForResyncDCPStatus(db.BackgroundProcessStateCompleted)
310+
// Run resync
311+
rest.RequireStatus(t, rt.SendAdminRequest(http.MethodPost, "/{{.db}}/_resync?action=start", ""), http.StatusOK)
312+
_ = rt.WaitForResyncDCPStatus(db.BackgroundProcessStateCompleted)
290313

291-
// validate user channels and roles have been updated
292-
user, err = rt.GetDatabase().Authenticator(ctx).GetUser(username)
293-
require.NoError(t, err)
294-
channels = user.CollectionChannels(scopeName, collectionName)
295-
_, ok = channels["channelABC"]
296-
require.False(t, ok, "user should not have channel channelABC")
297-
_, ok = channels["channelDEF"]
298-
require.True(t, ok, "user should have channel channelDEF")
299-
roles = user.RoleNames()
300-
_, ok = roles["roleABC"]
301-
require.False(t, ok, "user should not have role roleABC")
302-
_, ok = roles["roleDEF"]
303-
require.True(t, ok, "user should have role roleDEF")
314+
// validate user channels and roles have been updated
315+
user, err = rt.GetDatabase().Authenticator(ctx).GetUser(username)
316+
require.NoError(t, err)
317+
channels = user.CollectionChannels(scopeName, collectionName)
318+
_, ok = channels["channelABC"]
319+
require.False(t, ok, "user should not have channel channelABC")
320+
_, ok = channels["channelDEF"]
321+
require.True(t, ok, "user should have channel channelDEF")
322+
roles = user.RoleNames()
323+
_, ok = roles["roleABC"]
324+
require.False(t, ok, "user should not have role roleABC")
325+
_, ok = roles["roleDEF"]
326+
require.True(t, ok, "user should have role roleDEF")
327+
})
328+
}
304329
}
305330

306331
func TestResyncDoesNotWriteDocBody(t *testing.T) {

rest/utilities_testing.go

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -51,33 +51,34 @@ import (
5151

5252
// RestTesterConfig represents configuration for sync gateway
5353
type RestTesterConfig struct {
54-
GuestEnabled bool // If this is true, Admin Party is in full effect
55-
SyncFn string // put the sync() function source in here (optional)
56-
ImportFilter string // put the import filter function source in here (optional)
57-
DatabaseConfig *DatabaseConfig // Supports additional config options. BucketConfig, Name, Sync, Unsupported will be ignored (overridden)
58-
MutateStartupConfig func(config *StartupConfig) // Function to mutate the startup configuration before the server context gets created. This overrides options the RT sets.
59-
InitSyncSeq uint64 // If specified, initializes _sync:seq on bucket creation. Not supported when running against walrus
60-
AllowConflicts bool // Enable conflicts mode. By default, conflicts will not allowed
61-
EnableUserQueries bool // Enable the feature-flag for user N1QL/etc queries
62-
CustomTestBucket *base.TestBucket // If set, use this bucket instead of requesting a new one.
63-
LeakyBucketConfig *base.LeakyBucketConfig // Set to create and use a leaky bucket on the RT and DB. A test bucket cannot be passed in if using this option.
64-
adminInterface string // adminInterface overrides the default admin interface.
65-
SgReplicateEnabled bool // SgReplicateManager disabled by default for RestTester
66-
AutoImport *bool
67-
HideProductInfo bool
68-
AdminInterfaceAuthentication bool
69-
metricsInterfaceAuthentication bool
70-
enableAdminAuthPermissionsCheck bool
71-
useTLSServer bool // If true, TLS will be required for communications with CBS. Default: false
72-
PersistentConfig bool
73-
GroupID *string
74-
serverless bool // Runs SG in serverless mode. Must be used in conjunction with persistent config
75-
collectionConfig collectionConfiguration
76-
numCollections int
77-
nodeClusterCompatVersion *base.ClusterCompatVersion // alternate cluster compat version this node identifies as. Defaults to base.NodeClusterCompatVersion.
78-
allowDbConfigEnvVars *bool
79-
maxConcurrentRevs *int
80-
UseXattrConfig bool
54+
GuestEnabled bool // If this is true, Admin Party is in full effect
55+
SyncFn string // put the sync() function source in here (optional)
56+
ImportFilter string // put the import filter function source in here (optional)
57+
DatabaseConfig *DatabaseConfig // Supports additional config options. BucketConfig, Name, Sync, Unsupported will be ignored (overridden)
58+
MutateStartupConfig func(config *StartupConfig) // Function to mutate the startup configuration before the server context gets created. This overrides options the RT sets.
59+
InitSyncSeq uint64 // If specified, initializes _sync:seq on bucket creation. Not supported when running against walrus
60+
AllowConflicts bool // Enable conflicts mode. By default, conflicts will not allowed
61+
EnableUserQueries bool // Enable the feature-flag for user N1QL/etc queries
62+
CustomTestBucket *base.TestBucket // If set, use this bucket instead of requesting a new one.
63+
LeakyBucketConfig *base.LeakyBucketConfig // Set to create and use a leaky bucket on the RT and DB. A test bucket cannot be passed in if using this option.
64+
adminInterface string // adminInterface overrides the default admin interface.
65+
SgReplicateEnabled bool // SgReplicateManager disabled by default for RestTester
66+
AutoImport *bool
67+
HideProductInfo bool
68+
AdminInterfaceAuthentication bool
69+
metricsInterfaceAuthentication bool
70+
enableAdminAuthPermissionsCheck bool
71+
useTLSServer bool // If true, TLS will be required for communications with CBS. Default: false
72+
PersistentConfig bool
73+
GroupID *string
74+
serverless bool // Runs SG in serverless mode. Must be used in conjunction with persistent config
75+
collectionConfig collectionConfiguration
76+
numCollections int
77+
nodeClusterCompatVersion *base.ClusterCompatVersion // alternate cluster compat version this node identifies as. Defaults to base.NodeClusterCompatVersion.
78+
allowDbConfigEnvVars *bool
79+
maxConcurrentRevs *int
80+
UseXattrConfig bool
81+
UseSystemScopeMetadataCollection *bool
8182
}
8283

8384
type collectionConfiguration uint8
@@ -274,6 +275,12 @@ func (rt *RestTester) Bucket() base.Bucket {
274275
}
275276
}
276277

278+
if rt.RestTesterConfig.UseSystemScopeMetadataCollection != nil {
279+
sc.Bootstrap.UseSystemMetadataCollection = rt.RestTesterConfig.UseSystemScopeMetadataCollection
280+
} else {
281+
sc.Bootstrap.UseSystemMetadataCollection = base.Ptr(base.TestUseSystemMetadataCollection())
282+
}
283+
277284
if rt.RestTesterConfig.GroupID != nil {
278285
sc.Bootstrap.ConfigGroupID = *rt.RestTesterConfig.GroupID
279286
} else if rt.RestTesterConfig.PersistentConfig {

0 commit comments

Comments
 (0)