// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package cache import ( "sync" "github.com/hashicorp/consul/internal/controller/cache/index" "github.com/hashicorp/consul/internal/controller/cache/indexers" "github.com/hashicorp/consul/proto-public/pbresource" ) const IDIndex = "id" type kindIndices struct { mu sync.RWMutex it unversionedType indices map[string]*index.IndexedData } func newKindIndices() *kindIndices { kind := &kindIndices{ indices: make(map[string]*index.IndexedData), } // add the id index kind.indices[IDIndex] = indexers.IDIndex(IDIndex, index.IndexRequired).IndexedData() return kind } func (k *kindIndices) addIndex(i *index.Index) error { _, found := k.indices[i.Name()] if found { return DuplicateIndexError{name: i.Name()} } k.indices[i.Name()] = i.IndexedData() return nil } func (k *kindIndices) get(indexName string, args ...any) (*pbresource.Resource, error) { k.mu.RLock() defer k.mu.RUnlock() idx, err := k.getIndex(indexName) if err != nil { return nil, err } r, err := idx.Txn().Get(args...) if err != nil { return nil, IndexError{err: err, name: indexName} } return r, nil } func (k *kindIndices) listIterator(indexName string, args ...any) (ResourceIterator, error) { k.mu.RLock() defer k.mu.RUnlock() idx, err := k.getIndex(indexName) if err != nil { return nil, err } iter, err := idx.Txn().ListIterator(args...) if err != nil { return nil, IndexError{err: err, name: indexName} } return iter, nil } func (k *kindIndices) parentsIterator(indexName string, args ...any) (ResourceIterator, error) { k.mu.RLock() defer k.mu.RUnlock() idx, err := k.getIndex(indexName) if err != nil { return nil, err } iter, err := idx.Txn().ParentsIterator(args...) if err != nil { return nil, IndexError{err: err, name: indexName} } return iter, nil } func (k *kindIndices) insert(r *pbresource.Resource) error { k.mu.Lock() defer k.mu.Unlock() idx, err := k.getIndex(IDIndex) if err != nil { return err } existing, err := idx.Txn().Get(r.Id) if err != nil { return err } commit := false for name, idx := range k.indices { txn := idx.Txn() // Delete the previous version of the resource from the index. if existing != nil { if err := txn.Delete(existing); err != nil { return IndexError{name: name, err: err} } } // Now insert the new version into the index. err := txn.Insert(r) if err != nil { return IndexError{name: name, err: err} } // commit all radix trees once we know all index applies were successful. This is // still while holding the big write lock for this resource type so the order that // the radix tree updates occur shouldn't matter. defer func() { if commit { txn.Commit() } }() } // set commit to true so that the deferred funcs will commit all the radix tree transactions commit = true return nil } func (k *kindIndices) delete(r *pbresource.Resource) error { k.mu.Lock() defer k.mu.Unlock() idx, err := k.getIndex(IDIndex) if err != nil { return err } idTxn := idx.Txn() existing, err := idTxn.Get(r.Id) if err != nil { return err } if existing == nil { return nil } commit := false for name, idx := range k.indices { txn := idx.Txn() if err := txn.Delete(existing); err != nil { return IndexError{name: name, err: err} } // commit all radix trees once we know all index applies were successful. This is // still while holding the big write lock for this resource type so the order that // the radix tree updates occur shouldn't matter. defer func() { if commit { txn.Commit() } }() } // set commit to true so that the deferred txn commits will get executed commit = true return nil } func (k *kindIndices) getIndex(name string) (*index.IndexedData, error) { idx, ok := k.indices[name] if !ok { return nil, CacheTypeError{err: IndexNotFoundError{name: name}, it: k.it} } return idx, nil }