github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/datastore/proxy/schemacaching/watchingcache_test.go (about) 1 package schemacaching 2 3 import ( 4 "context" 5 "fmt" 6 "slices" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/stretchr/testify/require" 12 "go.uber.org/goleak" 13 14 "github.com/authzed/spicedb/pkg/cache" 15 "github.com/authzed/spicedb/pkg/datastore" 16 "github.com/authzed/spicedb/pkg/datastore/options" 17 corev1 "github.com/authzed/spicedb/pkg/proto/core/v1" 18 ) 19 20 var goleakIgnores = []goleak.Option{ 21 goleak.IgnoreTopFunction("github.com/golang/glog.(*loggingT).flushDaemon"), 22 goleak.IgnoreTopFunction("github.com/outcaste-io/ristretto.(*lfuPolicy).processItems"), 23 goleak.IgnoreTopFunction("github.com/outcaste-io/ristretto.(*Cache).processItems"), 24 goleak.IgnoreCurrent(), 25 } 26 27 func TestWatchingCacheBasicOperation(t *testing.T) { 28 defer goleak.VerifyNone(t, goleakIgnores...) 29 30 fakeDS := &fakeDatastore{ 31 headRevision: rev("0"), 32 namespaces: map[string][]fakeEntry[datastore.RevisionedNamespace, *corev1.NamespaceDefinition]{}, 33 caveats: map[string][]fakeEntry[datastore.RevisionedCaveat, *corev1.CaveatDefinition]{}, 34 schemaChan: make(chan *datastore.RevisionChanges, 1), 35 errChan: make(chan error, 1), 36 } 37 38 wcache := createWatchingCacheProxy(fakeDS, cache.NoopCache(), 1*time.Hour, 100*time.Millisecond) 39 require.NoError(t, wcache.startSync(context.Background())) 40 41 // Ensure no namespaces are found. 42 _, _, err := wcache.SnapshotReader(rev("1")).ReadNamespaceByName(context.Background(), "somenamespace") 43 require.ErrorAs(t, err, &datastore.ErrNamespaceNotFound{}) 44 require.False(t, wcache.namespaceCache.inFallbackMode) 45 46 // Ensure a re-read also returns not found, even before a checkpoint is received. 47 _, _, err = wcache.SnapshotReader(rev("1")).ReadNamespaceByName(context.Background(), "somenamespace") 48 require.ErrorAs(t, err, &datastore.ErrNamespaceNotFound{}) 49 50 // Send a checkpoint for revision 1. 51 fakeDS.sendCheckpoint(rev("1")) 52 53 // Write a namespace update at revision 2. 54 fakeDS.updateNamespace("somenamespace", &corev1.NamespaceDefinition{Name: "somenamespace"}, rev("2")) 55 56 // Ensure that reading at rev 2 returns found. 57 nsDef, _, err := wcache.SnapshotReader(rev("2")).ReadNamespaceByName(context.Background(), "somenamespace") 58 require.NoError(t, err) 59 require.Equal(t, "somenamespace", nsDef.Name) 60 61 // Disable reads. 62 fakeDS.disableReads() 63 64 // Ensure that reading at rev 3 returns an error, as with reads disabled the cache should not be hit. 65 _, _, err = wcache.SnapshotReader(rev("3")).ReadNamespaceByName(context.Background(), "somenamespace") 66 require.Error(t, err) 67 require.ErrorContains(t, err, "reads are disabled") 68 69 // Re-enable reads. 70 fakeDS.enableReads() 71 72 // Ensure that reading at rev 3 returns found, even though the cache should not yet be there. This will 73 // require a datastore fallback read because the cache is not yet checkedpointed to that revision. 74 nsDef, _, err = wcache.SnapshotReader(rev("3")).ReadNamespaceByName(context.Background(), "somenamespace") 75 require.NoError(t, err) 76 require.Equal(t, "somenamespace", nsDef.Name) 77 78 // Checkpoint to rev 4. 79 fakeDS.sendCheckpoint(rev("4")) 80 require.False(t, wcache.namespaceCache.inFallbackMode) 81 82 // Disable reads. 83 fakeDS.disableReads() 84 85 // Read again, which should now be via the cache. 86 nsDef, _, err = wcache.SnapshotReader(rev("3.0000000005")).ReadNamespaceByName(context.Background(), "somenamespace") 87 require.NoError(t, err) 88 require.Equal(t, "somenamespace", nsDef.Name) 89 90 // Read via a lookup. 91 nsDefs, err := wcache.SnapshotReader(rev("3.0000000005")).LookupNamespacesWithNames(context.Background(), []string{"somenamespace"}) 92 require.NoError(t, err) 93 require.Equal(t, "somenamespace", nsDefs[0].Definition.Name) 94 95 // Delete the namespace at revision 5. 96 fakeDS.updateNamespace("somenamespace", nil, rev("5")) 97 98 // Re-read at an earlier revision. 99 nsDef, _, err = wcache.SnapshotReader(rev("3.0000000005")).ReadNamespaceByName(context.Background(), "somenamespace") 100 require.NoError(t, err) 101 require.Equal(t, "somenamespace", nsDef.Name) 102 103 // Read at revision 5. 104 _, _, err = wcache.SnapshotReader(rev("5")).ReadNamespaceByName(context.Background(), "somenamespace") 105 require.Error(t, err) 106 require.ErrorAs(t, err, &datastore.ErrNamespaceNotFound{}, "missing not found in: %v", err) 107 108 // Lookup at revision 5. 109 nsDefs, err = wcache.SnapshotReader(rev("5")).LookupNamespacesWithNames(context.Background(), []string{"somenamespace"}) 110 require.NoError(t, err) 111 require.Empty(t, nsDefs) 112 113 // Update a caveat. 114 fakeDS.updateCaveat("somecaveat", &corev1.CaveatDefinition{Name: "somecaveat"}, rev("6")) 115 116 // Read at revision 6. 117 caveatDef, _, err := wcache.SnapshotReader(rev("6")).ReadCaveatByName(context.Background(), "somecaveat") 118 require.NoError(t, err) 119 require.Equal(t, "somecaveat", caveatDef.Name) 120 121 // Attempt to read at revision 1, which should require a read. 122 _, _, err = wcache.SnapshotReader(rev("1")).ReadCaveatByName(context.Background(), "somecaveat") 123 require.ErrorContains(t, err, "reads are disabled") 124 125 // Close the proxy and ensure the background goroutines are terminated. 126 wcache.Close() 127 time.Sleep(10 * time.Millisecond) 128 } 129 130 func TestWatchingCacheParallelOperations(t *testing.T) { 131 defer goleak.VerifyNone(t, goleakIgnores...) 132 133 fakeDS := &fakeDatastore{ 134 headRevision: rev("0"), 135 namespaces: map[string][]fakeEntry[datastore.RevisionedNamespace, *corev1.NamespaceDefinition]{}, 136 caveats: map[string][]fakeEntry[datastore.RevisionedCaveat, *corev1.CaveatDefinition]{}, 137 schemaChan: make(chan *datastore.RevisionChanges, 1), 138 errChan: make(chan error, 1), 139 } 140 141 wcache := createWatchingCacheProxy(fakeDS, cache.NoopCache(), 1*time.Hour, 100*time.Millisecond) 142 require.NoError(t, wcache.startSync(context.Background())) 143 144 // Run some operations in parallel. 145 var wg sync.WaitGroup 146 wg.Add(2) 147 148 go (func() { 149 // Read somenamespace (which should not be found) 150 _, _, err := wcache.SnapshotReader(rev("1")).ReadNamespaceByName(context.Background(), "somenamespace") 151 require.ErrorAs(t, err, &datastore.ErrNamespaceNotFound{}) 152 require.False(t, wcache.namespaceCache.inFallbackMode) 153 154 // Write somenamespace. 155 fakeDS.updateNamespace("somenamespace", &corev1.NamespaceDefinition{Name: "somenamespace"}, rev("2")) 156 157 // Read again (which should be found now) 158 nsDef, _, err := wcache.SnapshotReader(rev("2")).ReadNamespaceByName(context.Background(), "somenamespace") 159 require.NoError(t, err) 160 require.Equal(t, "somenamespace", nsDef.Name) 161 162 wg.Done() 163 })() 164 165 go (func() { 166 // Read anothernamespace (which should not be found) 167 _, _, err := wcache.SnapshotReader(rev("1")).ReadNamespaceByName(context.Background(), "anothernamespace") 168 require.ErrorAs(t, err, &datastore.ErrNamespaceNotFound{}) 169 require.False(t, wcache.namespaceCache.inFallbackMode) 170 171 // Read again (which should still not be found) 172 _, _, err = wcache.SnapshotReader(rev("3")).ReadNamespaceByName(context.Background(), "anothernamespace") 173 require.ErrorAs(t, err, &datastore.ErrNamespaceNotFound{}) 174 require.False(t, wcache.namespaceCache.inFallbackMode) 175 176 wg.Done() 177 })() 178 179 wg.Wait() 180 181 // Close the proxy and ensure the background goroutines are terminated. 182 wcache.Close() 183 time.Sleep(10 * time.Millisecond) 184 } 185 186 func TestWatchingCacheParallelReaderWriter(t *testing.T) { 187 defer goleak.VerifyNone(t, goleakIgnores...) 188 189 fakeDS := &fakeDatastore{ 190 headRevision: rev("0"), 191 namespaces: map[string][]fakeEntry[datastore.RevisionedNamespace, *corev1.NamespaceDefinition]{}, 192 caveats: map[string][]fakeEntry[datastore.RevisionedCaveat, *corev1.CaveatDefinition]{}, 193 schemaChan: make(chan *datastore.RevisionChanges, 1), 194 errChan: make(chan error, 1), 195 } 196 197 wcache := createWatchingCacheProxy(fakeDS, cache.NoopCache(), 1*time.Hour, 100*time.Millisecond) 198 require.NoError(t, wcache.startSync(context.Background())) 199 200 // Write somenamespace. 201 fakeDS.updateNamespace("somenamespace", &corev1.NamespaceDefinition{Name: "somenamespace"}, rev("0")) 202 203 // Run some operations in parallel. 204 var wg sync.WaitGroup 205 wg.Add(2) 206 207 go (func() { 208 // Start a loop to write a namespace a bunch of times. 209 for i := 0; i < 1000; i++ { 210 // Write somenamespace. 211 fakeDS.updateNamespace("somenamespace", &corev1.NamespaceDefinition{Name: "somenamespace"}, rev(fmt.Sprintf("%d", i+1))) 212 } 213 214 wg.Done() 215 })() 216 217 go (func() { 218 // Start a loop to read a namespace a bunch of times. 219 for i := 0; i < 1000; i++ { 220 headRevision, err := fakeDS.HeadRevision(context.Background()) 221 require.NoError(t, err) 222 223 nsDef, _, err := wcache.SnapshotReader(headRevision).ReadNamespaceByName(context.Background(), "somenamespace") 224 require.NoError(t, err) 225 require.Equal(t, "somenamespace", nsDef.Name) 226 } 227 228 wg.Done() 229 })() 230 231 wg.Wait() 232 233 // Close the proxy and ensure the background goroutines are terminated. 234 wcache.Close() 235 time.Sleep(10 * time.Millisecond) 236 } 237 238 func TestWatchingCacheFallbackToStandardCache(t *testing.T) { 239 defer goleak.VerifyNone(t, goleakIgnores...) 240 241 fakeDS := &fakeDatastore{ 242 headRevision: rev("0"), 243 namespaces: map[string][]fakeEntry[datastore.RevisionedNamespace, *corev1.NamespaceDefinition]{}, 244 caveats: map[string][]fakeEntry[datastore.RevisionedCaveat, *corev1.CaveatDefinition]{}, 245 schemaChan: make(chan *datastore.RevisionChanges, 1), 246 errChan: make(chan error, 1), 247 } 248 249 c, err := cache.NewCache(&cache.Config{ 250 NumCounters: 1000, 251 MaxCost: 1000, 252 DefaultTTL: 1000 * time.Second, 253 }) 254 require.NoError(t, err) 255 256 wcache := createWatchingCacheProxy(fakeDS, c, 1*time.Hour, 100*time.Millisecond) 257 require.NoError(t, wcache.startSync(context.Background())) 258 259 // Ensure the namespace is not found, but is cached in the fallback caching layer. 260 _, _, err = wcache.SnapshotReader(rev("1")).ReadNamespaceByName(context.Background(), "somenamespace") 261 require.ErrorAs(t, err, &datastore.ErrNamespaceNotFound{}) 262 require.False(t, wcache.namespaceCache.inFallbackMode) 263 264 entry, ok := c.Get("n:somenamespace@1") 265 require.True(t, ok) 266 require.NotNil(t, entry.(*cacheEntry).notFound) 267 268 // Disable reading and ensure it still works, via the fallback cache. 269 fakeDS.readsDisabled = true 270 271 _, _, err = wcache.SnapshotReader(rev("1")).ReadNamespaceByName(context.Background(), "somenamespace") 272 require.ErrorAs(t, err, &datastore.ErrNamespaceNotFound{}) 273 require.False(t, wcache.namespaceCache.inFallbackMode) 274 275 // Close the proxy and ensure the background goroutines are terminated. 276 wcache.Close() 277 time.Sleep(10 * time.Millisecond) 278 } 279 280 func TestWatchingCachePrepopulated(t *testing.T) { 281 defer goleak.VerifyNone(t, goleakIgnores...) 282 283 fakeDS := &fakeDatastore{ 284 headRevision: rev("4"), 285 namespaces: map[string][]fakeEntry[datastore.RevisionedNamespace, *corev1.NamespaceDefinition]{}, 286 caveats: map[string][]fakeEntry[datastore.RevisionedCaveat, *corev1.CaveatDefinition]{}, 287 schemaChan: make(chan *datastore.RevisionChanges, 1), 288 errChan: make(chan error, 1), 289 existingNamespaces: []datastore.RevisionedNamespace{ 290 datastore.RevisionedDefinition[*corev1.NamespaceDefinition]{ 291 Definition: &corev1.NamespaceDefinition{ 292 Name: "somenamespace", 293 }, 294 LastWrittenRevision: rev("1"), 295 }, 296 datastore.RevisionedDefinition[*corev1.NamespaceDefinition]{ 297 Definition: &corev1.NamespaceDefinition{ 298 Name: "anothernamespace", 299 }, 300 LastWrittenRevision: rev("2"), 301 }, 302 }, 303 } 304 305 c, err := cache.NewCache(&cache.Config{ 306 NumCounters: 1000, 307 MaxCost: 1000, 308 DefaultTTL: 1000 * time.Second, 309 }) 310 require.NoError(t, err) 311 312 wcache := createWatchingCacheProxy(fakeDS, c, 1*time.Hour, 100*time.Millisecond) 313 require.NoError(t, wcache.startSync(context.Background())) 314 315 // Ensure the namespace is found. 316 def, _, err := wcache.SnapshotReader(rev("4")).ReadNamespaceByName(context.Background(), "somenamespace") 317 require.NoError(t, err) 318 require.Equal(t, "somenamespace", def.Name) 319 320 // Close the proxy and ensure the background goroutines are terminated. 321 wcache.Close() 322 time.Sleep(10 * time.Millisecond) 323 } 324 325 type fakeDatastore struct { 326 headRevision datastore.Revision 327 328 namespaces map[string][]fakeEntry[datastore.RevisionedNamespace, *corev1.NamespaceDefinition] 329 caveats map[string][]fakeEntry[datastore.RevisionedCaveat, *corev1.CaveatDefinition] 330 331 schemaChan chan *datastore.RevisionChanges 332 errChan chan error 333 334 readsDisabled bool 335 existingNamespaces []datastore.RevisionedNamespace 336 337 lock sync.RWMutex 338 } 339 340 func (fds *fakeDatastore) updateNamespace(name string, def *corev1.NamespaceDefinition, revision datastore.Revision) { 341 fds.lock.Lock() 342 defer fds.lock.Unlock() 343 344 updateDef(fds.namespaces, name, def, def == nil, revision, fds.schemaChan) 345 fds.headRevision = revision 346 } 347 348 func (fds *fakeDatastore) updateCaveat(name string, def *corev1.CaveatDefinition, revision datastore.Revision) { 349 fds.lock.Lock() 350 defer fds.lock.Unlock() 351 352 updateDef(fds.caveats, name, def, def == nil, revision, fds.schemaChan) 353 fds.headRevision = revision 354 } 355 356 func (fds *fakeDatastore) sendCheckpoint(revision datastore.Revision) { 357 fds.schemaChan <- &datastore.RevisionChanges{ 358 Revision: revision, 359 IsCheckpoint: true, 360 } 361 time.Sleep(1 * time.Millisecond) 362 } 363 364 type fakeEntry[T datastore.RevisionedDefinition[Q], Q datastore.SchemaDefinition] struct { 365 value T 366 wasDeleted bool 367 } 368 369 type revisionGetter[T datastore.SchemaDefinition] interface { 370 datastore.RevisionedDefinition[T] 371 GetLastWrittenRevision() datastore.Revision 372 } 373 374 func updateDef[T datastore.SchemaDefinition]( 375 defs map[string][]fakeEntry[datastore.RevisionedDefinition[T], T], 376 name string, 377 def T, 378 isDelete bool, 379 revision datastore.Revision, 380 schemaChan chan *datastore.RevisionChanges, 381 ) { 382 slice, ok := defs[name] 383 if !ok { 384 slice = []fakeEntry[datastore.RevisionedDefinition[T], T]{} 385 } 386 387 slice = append(slice, fakeEntry[datastore.RevisionedDefinition[T], T]{ 388 value: datastore.RevisionedDefinition[T]{ 389 Definition: def, 390 LastWrittenRevision: revision, 391 }, 392 wasDeleted: isDelete, 393 }) 394 defs[name] = slice 395 396 if isDelete { 397 schemaChan <- &datastore.RevisionChanges{ 398 Revision: revision, 399 DeletedNamespaces: []string{name}, 400 } 401 } else { 402 schemaChan <- &datastore.RevisionChanges{ 403 Revision: revision, 404 ChangedDefinitions: []datastore.SchemaDefinition{def}, 405 } 406 } 407 time.Sleep(1 * time.Millisecond) 408 } 409 410 func readDefs[T datastore.SchemaDefinition, Q revisionGetter[T]](defs map[string][]fakeEntry[Q, T], names []string, revision datastore.Revision) []Q { 411 results := make([]Q, 0, len(names)) 412 for _, name := range names { 413 revisionedDefs, ok := defs[name] 414 if !ok { 415 continue 416 } 417 418 revisioned := []fakeEntry[Q, T]{} 419 for _, revisionedEntry := range revisionedDefs { 420 if revisionedEntry.value.GetLastWrittenRevision().LessThan(revision) || revisionedEntry.value.GetLastWrittenRevision().Equal(revision) { 421 revisioned = append(revisioned, revisionedEntry) 422 } 423 } 424 425 if len(revisioned) == 0 { 426 continue 427 } 428 429 slices.SortFunc(revisioned, func(a fakeEntry[Q, T], b fakeEntry[Q, T]) int { 430 if a.value.GetLastWrittenRevision().Equal(b.value.GetLastWrittenRevision()) { 431 return 0 432 } 433 434 if a.value.GetLastWrittenRevision().LessThan(b.value.GetLastWrittenRevision()) { 435 return -1 436 } 437 438 return 1 439 }) 440 441 entry := revisioned[len(revisioned)-1] 442 if !entry.wasDeleted { 443 results = append(results, entry.value) 444 } 445 } 446 447 return results 448 } 449 450 func (fds *fakeDatastore) readNamespaces(names []string, revision datastore.Revision) ([]datastore.RevisionedNamespace, error) { 451 fds.lock.RLock() 452 defer fds.lock.RUnlock() 453 454 if fds.readsDisabled { 455 return nil, fmt.Errorf("reads are disabled") 456 } 457 458 return readDefs(fds.namespaces, names, revision), nil 459 } 460 461 func (fds *fakeDatastore) readCaveats(names []string, revision datastore.Revision) ([]datastore.RevisionedCaveat, error) { 462 fds.lock.RLock() 463 defer fds.lock.RUnlock() 464 465 if fds.readsDisabled { 466 return nil, fmt.Errorf("reads are disabled") 467 } 468 469 return readDefs(fds.caveats, names, revision), nil 470 } 471 472 func (fds *fakeDatastore) disableReads() { 473 fds.lock.Lock() 474 defer fds.lock.Unlock() 475 476 fds.readsDisabled = true 477 } 478 479 func (fds *fakeDatastore) enableReads() { 480 fds.lock.Lock() 481 defer fds.lock.Unlock() 482 483 fds.readsDisabled = false 484 } 485 486 func (fds *fakeDatastore) SnapshotReader(rev datastore.Revision) datastore.Reader { 487 return &fakeSnapshotReader{fds, rev} 488 } 489 490 func (fds *fakeDatastore) HeadRevision(context.Context) (datastore.Revision, error) { 491 fds.lock.RLock() 492 defer fds.lock.RUnlock() 493 494 return fds.headRevision, nil 495 } 496 497 func (*fakeDatastore) ReadWriteTx(context.Context, datastore.TxUserFunc, ...options.RWTOptionsOption) (datastore.Revision, error) { 498 return nil, fmt.Errorf("not implemented") 499 } 500 501 func (*fakeDatastore) CheckRevision(context.Context, datastore.Revision) error { 502 return nil 503 } 504 505 func (*fakeDatastore) Close() error { 506 return nil 507 } 508 509 func (*fakeDatastore) Features(context.Context) (*datastore.Features, error) { 510 return nil, fmt.Errorf("not implemented") 511 } 512 513 func (*fakeDatastore) OptimizedRevision(context.Context) (datastore.Revision, error) { 514 return nil, fmt.Errorf("not implemented") 515 } 516 517 func (*fakeDatastore) ReadyState(context.Context) (datastore.ReadyState, error) { 518 return datastore.ReadyState{}, fmt.Errorf("not implemented") 519 } 520 521 func (*fakeDatastore) RevisionFromString(string) (datastore.Revision, error) { 522 return nil, fmt.Errorf("not implemented") 523 } 524 525 func (*fakeDatastore) Statistics(context.Context) (datastore.Stats, error) { 526 return datastore.Stats{}, fmt.Errorf("not implemented") 527 } 528 529 func (fds *fakeDatastore) Watch(_ context.Context, _ datastore.Revision, opts datastore.WatchOptions) (<-chan *datastore.RevisionChanges, <-chan error) { 530 if opts.Content&datastore.WatchSchema != datastore.WatchSchema { 531 panic("unexpected option") 532 } 533 534 return fds.schemaChan, fds.errChan 535 } 536 537 type fakeSnapshotReader struct { 538 fds *fakeDatastore 539 rev datastore.Revision 540 } 541 542 func (fsr *fakeSnapshotReader) LookupNamespacesWithNames(_ context.Context, nsNames []string) ([]datastore.RevisionedDefinition[*corev1.NamespaceDefinition], error) { 543 return fsr.fds.readNamespaces(nsNames, fsr.rev) 544 } 545 546 func (fsr *fakeSnapshotReader) ReadNamespaceByName(_ context.Context, nsName string) (ns *corev1.NamespaceDefinition, lastWritten datastore.Revision, err error) { 547 namespaces, err := fsr.fds.readNamespaces([]string{nsName}, fsr.rev) 548 if err != nil { 549 return nil, nil, err 550 } 551 552 if len(namespaces) == 0 { 553 return nil, nil, datastore.NewNamespaceNotFoundErr(nsName) 554 } 555 return namespaces[0].Definition, namespaces[0].LastWrittenRevision, nil 556 } 557 558 func (fsr *fakeSnapshotReader) LookupCaveatsWithNames(_ context.Context, names []string) ([]datastore.RevisionedDefinition[*corev1.CaveatDefinition], error) { 559 return fsr.fds.readCaveats(names, fsr.rev) 560 } 561 562 func (fsr *fakeSnapshotReader) ReadCaveatByName(_ context.Context, name string) (caveat *corev1.CaveatDefinition, lastWritten datastore.Revision, err error) { 563 caveats, err := fsr.fds.readCaveats([]string{name}, fsr.rev) 564 if err != nil { 565 return nil, nil, err 566 } 567 568 if len(caveats) == 0 { 569 return nil, nil, datastore.NewCaveatNameNotFoundErr(name) 570 } 571 return caveats[0].Definition, caveats[0].LastWrittenRevision, nil 572 } 573 574 func (*fakeSnapshotReader) ListAllCaveats(context.Context) ([]datastore.RevisionedDefinition[*corev1.CaveatDefinition], error) { 575 return []datastore.RevisionedDefinition[*corev1.CaveatDefinition]{}, nil 576 } 577 578 func (fsr *fakeSnapshotReader) ListAllNamespaces(context.Context) ([]datastore.RevisionedDefinition[*corev1.NamespaceDefinition], error) { 579 if fsr.fds.existingNamespaces != nil { 580 return fsr.fds.existingNamespaces, nil 581 } 582 583 return []datastore.RevisionedDefinition[*corev1.NamespaceDefinition]{}, nil 584 } 585 586 func (*fakeSnapshotReader) QueryRelationships(context.Context, datastore.RelationshipsFilter, ...options.QueryOptionsOption) (datastore.RelationshipIterator, error) { 587 return nil, fmt.Errorf("not implemented") 588 } 589 590 func (*fakeSnapshotReader) ReverseQueryRelationships(context.Context, datastore.SubjectsFilter, ...options.ReverseQueryOptionsOption) (datastore.RelationshipIterator, error) { 591 return nil, fmt.Errorf("not implemented") 592 }