From 486f2d7e41ae23de22473b47fb7cfbbaafe33f82 Mon Sep 17 00:00:00 2001 From: Ben Luddy Date: Thu, 26 Aug 2021 14:00:05 -0400 Subject: [PATCH] Remove hard resolver dependency on the gRPC registry client. The most significant change is the introduction of a new Source interface, which represents a single source of cache entries. In the existing implementation, the cache was hardcoded to fetch content from the operator-registry gRPC API on misses. This was a barrier to off-cluster tools and other applications that should be able to perform resolution without running a registry server. A number of test fixtures were affected (for the better) because it's now possible to directly configure the real Cache implementation to retrieve test content without using of test doubles. Signed-off-by: Ben Luddy --- .../registry/resolver/cache/cache.go | 369 ++++----- .../registry/resolver/cache/cache_test.go | 277 +++---- pkg/controller/registry/resolver/resolver.go | 23 +- .../registry/resolver/resolver_test.go | 764 ++++++++---------- .../registry/resolver/source_registry.go | 109 +++ .../registry/resolver/step_resolver.go | 4 +- .../registry/resolver/step_resolver_test.go | 34 +- 7 files changed, 712 insertions(+), 868 deletions(-) create mode 100644 pkg/controller/registry/resolver/source_registry.go diff --git a/pkg/controller/registry/resolver/cache/cache.go b/pkg/controller/registry/resolver/cache/cache.go index 85970d6bb77..baba3e7c78e 100644 --- a/pkg/controller/registry/resolver/cache/cache.go +++ b/pkg/controller/registry/resolver/cache/cache.go @@ -2,24 +2,20 @@ package cache import ( "context" - "encoding/json" "fmt" + "io" "sort" "sync" "time" - "k8s.io/apimachinery/pkg/util/errors" - "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/util/errors" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1" - "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" - "github.com/operator-framework/operator-registry/pkg/api" - "github.com/operator-framework/operator-registry/pkg/client" - opregistry "github.com/operator-framework/operator-registry/pkg/registry" + "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister" ) -const ExistingOperatorKey = "@existing" +const existingOperatorKey = "@existing" type SourceKey struct { Name string @@ -40,42 +36,36 @@ func (k *SourceKey) Equal(compare SourceKey) bool { // Virtual indicates if this is a "virtual" catalog representing the currently installed operators in a namespace func (k *SourceKey) Virtual() bool { - return k.Name == ExistingOperatorKey && k.Namespace != "" + return k.Name == existingOperatorKey && k.Namespace != "" } func NewVirtualSourceKey(namespace string) SourceKey { return SourceKey{ - Name: ExistingOperatorKey, + Name: existingOperatorKey, Namespace: namespace, } } -type RegistryClientProvider interface { - ClientsForNamespaces(namespaces ...string) map[registry.CatalogKey]client.Interface +type Source interface { + Snapshot(context.Context) (*Snapshot, error) } type SourceProvider interface { - // TODO: Spun off from the old RegistryClientProvider in order - // to phase out the dependency on registry.CatalogKey. Subject - // to further change as the registry client dependency is also - // removed. - ClientsForNamespaces(namespaces ...string) map[SourceKey]client.Interface + // TODO: namespaces parameter is an artifact of SourceStore + Sources(namespaces ...string) map[SourceKey]Source } -type DefaultRegistryClientProvider struct { - s RegistryClientProvider -} - -func SourceProviderFromRegistryClientProvider(store RegistryClientProvider) *DefaultRegistryClientProvider { - return &DefaultRegistryClientProvider{ - s: store, - } -} +type StaticSourceProvider map[SourceKey]Source -func (rcp *DefaultRegistryClientProvider) ClientsForNamespaces(namespaces ...string) map[SourceKey]client.Interface { - result := make(map[SourceKey]client.Interface) - for key, client := range rcp.s.ClientsForNamespaces(namespaces...) { - result[SourceKey(key)] = client +func (p StaticSourceProvider) Sources(namespaces ...string) map[SourceKey]Source { + result := make(map[SourceKey]Source) + for key, source := range p { + for _, namespace := range namespaces { + if key.Namespace == namespace { + result[key] = source + break + } + } } return result } @@ -85,46 +75,67 @@ type OperatorCacheProvider interface { Expire(catalog SourceKey) } -type OperatorCache struct { - logger logrus.FieldLogger - rcp SourceProvider +type Cache struct { + logger logrus.StdLogger + sp SourceProvider catsrcLister v1alpha1.CatalogSourceLister - snapshots map[SourceKey]*CatalogSnapshot + snapshots map[SourceKey]*snapshotHeader ttl time.Duration sem chan struct{} m sync.RWMutex } -const defaultCatalogSourcePriority int = 0 - type catalogSourcePriority int -var _ OperatorCacheProvider = &OperatorCache{} +var _ OperatorCacheProvider = &Cache{} + +type Option func(*Cache) + +func WithLogger(logger logrus.StdLogger) Option { + return func(c *Cache) { + c.logger = logger + } +} -func NewOperatorCache(rcp SourceProvider, log logrus.FieldLogger, catsrcLister v1alpha1.CatalogSourceLister) *OperatorCache { +func WithCatalogSourceLister(catalogSourceLister v1alpha1.CatalogSourceLister) Option { + return func(c *Cache) { + c.catsrcLister = catalogSourceLister + } +} + +func New(sp SourceProvider, options ...Option) *Cache { const ( MaxConcurrentSnapshotUpdates = 4 ) - return &OperatorCache{ - logger: log, - rcp: rcp, - catsrcLister: catsrcLister, - snapshots: make(map[SourceKey]*CatalogSnapshot), + cache := Cache{ + logger: func() logrus.StdLogger { + logger := logrus.New() + logger.SetOutput(io.Discard) + return logger + }(), + sp: sp, + catsrcLister: operatorlister.NewLister().OperatorsV1alpha1().CatalogSourceLister(), + snapshots: make(map[SourceKey]*snapshotHeader), ttl: 5 * time.Minute, sem: make(chan struct{}, MaxConcurrentSnapshotUpdates), } + + for _, opt := range options { + opt(&cache) + } + + return &cache } type NamespacedOperatorCache struct { - Namespaces []string - existing *SourceKey - Snapshots map[SourceKey]*CatalogSnapshot + existing *SourceKey + snapshots map[SourceKey]*snapshotHeader } func (c *NamespacedOperatorCache) Error() error { var errs []error - for key, snapshot := range c.Snapshots { + for key, snapshot := range c.snapshots { snapshot.m.Lock() err := snapshot.err snapshot.m.Unlock() @@ -135,7 +146,7 @@ func (c *NamespacedOperatorCache) Error() error { return errors.NewAggregate(errs) } -func (c *OperatorCache) Expire(catalog SourceKey) { +func (c *Cache) Expire(catalog SourceKey) { c.m.Lock() defer c.m.Unlock() s, ok := c.snapshots[catalog] @@ -145,31 +156,30 @@ func (c *OperatorCache) Expire(catalog SourceKey) { s.expiry = time.Unix(0, 0) } -func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFinder { +func (c *Cache) Namespaced(namespaces ...string) MultiCatalogOperatorFinder { const ( CachePopulateTimeout = time.Minute ) now := time.Now() - clients := c.rcp.ClientsForNamespaces(namespaces...) + sources := c.sp.Sources(namespaces...) result := NamespacedOperatorCache{ - Namespaces: namespaces, - Snapshots: make(map[SourceKey]*CatalogSnapshot), + snapshots: make(map[SourceKey]*snapshotHeader), } var misses []SourceKey func() { c.m.RLock() defer c.m.RUnlock() - for key := range clients { + for key := range sources { snapshot, ok := c.snapshots[key] if ok { func() { snapshot.m.RLock() defer snapshot.m.RUnlock() - if !snapshot.Expired(now) && snapshot.Operators != nil && len(snapshot.Operators) > 0 { - result.Snapshots[key] = snapshot + if snapshot.Valid(now) { + result.snapshots[key] = snapshot } else { misses = append(misses, key) } @@ -191,7 +201,7 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin // Take the opportunity to clear expired snapshots while holding the lock. var expired []SourceKey for key, snapshot := range c.snapshots { - if snapshot.Expired(now) { + if !snapshot.Valid(now) { snapshot.Cancel() expired = append(expired, key) } @@ -203,8 +213,8 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin // Check for any snapshots that were populated while waiting to acquire the lock. var found int for i := range misses { - if snapshot, ok := c.snapshots[misses[i]]; ok && !snapshot.Expired(now) && snapshot.Operators != nil && len(snapshot.Operators) > 0 { - result.Snapshots[misses[i]] = snapshot + if hdr, ok := c.snapshots[misses[i]]; ok && hdr.Valid(now) { + result.snapshots[misses[i]] = hdr misses[found], misses[i] = misses[i], misses[found] found++ } @@ -214,101 +224,30 @@ func (c *OperatorCache) Namespaced(namespaces ...string) MultiCatalogOperatorFin for _, miss := range misses { ctx, cancel := context.WithTimeout(context.Background(), CachePopulateTimeout) - catsrcPriority := defaultCatalogSourcePriority - // Ignoring error and treat catsrc priority as 0 if not found. - catsrc, err := c.catsrcLister.CatalogSources(miss.Namespace).Get(miss.Name) - if err == nil { - catsrcPriority = catsrc.Spec.Priority - } - - s := CatalogSnapshot{ - logger: c.logger.WithField("catalog", miss), - Key: miss, - expiry: now.Add(c.ttl), - pop: cancel, - Priority: catalogSourcePriority(catsrcPriority), + hdr := snapshotHeader{ + key: miss, + expiry: now.Add(c.ttl), + pop: cancel, } - s.m.Lock() - c.snapshots[miss] = &s - result.Snapshots[miss] = &s - go c.populate(ctx, &s, clients[miss]) - } - - return &result -} -func (c *OperatorCache) populate(ctx context.Context, snapshot *CatalogSnapshot, registry client.Interface) { - defer snapshot.m.Unlock() - defer func() { - // Don't cache an errorred snapshot. - if snapshot.err != nil { - snapshot.expiry = time.Time{} + // Ignoring error and treat catsrc priority as 0 if not found. + if catsrc, _ := c.catsrcLister.CatalogSources(miss.Namespace).Get(miss.Name); catsrc != nil { + hdr.priority = catsrc.Spec.Priority } - }() - - c.sem <- struct{}{} - defer func() { <-c.sem }() - // Fetching default channels this way makes many round trips - // -- may need to either add a new API to fetch all at once, - // or embed the information into Bundle. - defaultChannels := make(map[string]string) + hdr.m.Lock() + c.snapshots[miss] = &hdr + result.snapshots[miss] = &hdr - it, err := registry.ListBundles(ctx) - if err != nil { - snapshot.logger.Errorf("failed to list bundles: %s", err.Error()) - snapshot.err = err - return - } - c.logger.WithField("catalog", snapshot.Key.String()).Debug("updating cache") - var operators []*Operator - for b := it.Next(); b != nil; b = it.Next() { - defaultChannel, ok := defaultChannels[b.PackageName] - if !ok { - if p, err := registry.GetPackage(ctx, b.PackageName); err != nil { - snapshot.logger.Warnf("failed to retrieve default channel for bundle, continuing: %v", err) - continue - } else { - defaultChannels[b.PackageName] = p.DefaultChannelName - defaultChannel = p.DefaultChannelName - } - } - o, err := NewOperatorFromBundle(b, "", snapshot.Key, defaultChannel) - if err != nil { - snapshot.logger.Warnf("failed to construct operator from bundle, continuing: %v", err) - continue - } - o.ProvidedAPIs = o.ProvidedAPIs.StripPlural() - o.RequiredAPIs = o.RequiredAPIs.StripPlural() - o.Replaces = b.Replaces - EnsurePackageProperty(o, b.PackageName, b.Version) - operators = append(operators, o) - } - if err := it.Error(); err != nil { - snapshot.logger.Warnf("error encountered while listing bundles: %s", err.Error()) - snapshot.err = err + go func(ctx context.Context, hdr *snapshotHeader, source Source) { + defer hdr.m.Unlock() + c.sem <- struct{}{} + defer func() { <-c.sem }() + hdr.snapshot, hdr.err = source.Snapshot(ctx) + }(ctx, &hdr, sources[miss]) } - snapshot.Operators = operators -} -func EnsurePackageProperty(o *Operator, name, version string) { - for _, p := range o.Properties { - if p.Type == opregistry.PackageType { - return - } - } - prop := opregistry.PackageProperty{ - PackageName: name, - Version: version, - } - bytes, err := json.Marshal(prop) - if err != nil { - return - } - o.Properties = append(o.Properties, &api.Property{ - Type: opregistry.PackageType, - Value: string(bytes), - }) + return &result } func (c *NamespacedOperatorCache) Catalog(k SourceKey) OperatorFinder { @@ -316,18 +255,18 @@ func (c *NamespacedOperatorCache) Catalog(k SourceKey) OperatorFinder { if k.Empty() { return c } - if snapshot, ok := c.Snapshots[k]; ok { + if snapshot, ok := c.snapshots[k]; ok { return snapshot } return EmptyOperatorFinder{} } -func (c *NamespacedOperatorCache) FindPreferred(preferred *SourceKey, p ...OperatorPredicate) []*Operator { +func (c *NamespacedOperatorCache) FindPreferred(preferred *SourceKey, preferredNamespace string, p ...OperatorPredicate) []*Operator { var result []*Operator if preferred != nil && preferred.Empty() { preferred = nil } - sorted := NewSortableSnapshots(c.existing, preferred, c.Namespaces, c.Snapshots) + sorted := newSortableSnapshots(c.existing, preferred, preferredNamespace, c.snapshots) sort.Sort(sorted) for _, snapshot := range sorted.snapshots { result = append(result, snapshot.Find(p...)...) @@ -335,65 +274,69 @@ func (c *NamespacedOperatorCache) FindPreferred(preferred *SourceKey, p ...Opera return result } -func (c *NamespacedOperatorCache) WithExistingOperators(snapshot *CatalogSnapshot) MultiCatalogOperatorFinder { +func (c *NamespacedOperatorCache) WithExistingOperators(snapshot *Snapshot, namespace string) MultiCatalogOperatorFinder { + key := NewVirtualSourceKey(namespace) o := &NamespacedOperatorCache{ - Namespaces: c.Namespaces, - existing: &snapshot.Key, - Snapshots: c.Snapshots, + existing: &key, + snapshots: map[SourceKey]*snapshotHeader{ + key: { + key: key, + snapshot: snapshot, + }, + }, + } + for k, v := range c.snapshots { + o.snapshots[k] = v } - o.Snapshots[snapshot.Key] = snapshot return o } func (c *NamespacedOperatorCache) Find(p ...OperatorPredicate) []*Operator { - return c.FindPreferred(nil, p...) + return c.FindPreferred(nil, "", p...) } -type CatalogSnapshot struct { - logger logrus.FieldLogger - Key SourceKey - expiry time.Time - Operators []*Operator - m sync.RWMutex - pop context.CancelFunc - Priority catalogSourcePriority - err error +type Snapshot struct { + Entries []*Operator } -func (s *CatalogSnapshot) Cancel() { - s.pop() +var _ Source = &Snapshot{} + +func (s *Snapshot) Snapshot(context.Context) (*Snapshot, error) { + return s, nil } -func (s *CatalogSnapshot) Expired(at time.Time) bool { - return !at.Before(s.expiry) +type snapshotHeader struct { + snapshot *Snapshot + + key SourceKey + expiry time.Time + m sync.RWMutex + pop context.CancelFunc + err error + priority int } -// NewRunningOperatorSnapshot creates a CatalogSnapshot that represents a set of existing installed operators -// in the cluster. -func NewRunningOperatorSnapshot(logger logrus.FieldLogger, key SourceKey, o []*Operator) *CatalogSnapshot { - return &CatalogSnapshot{ - logger: logger, - Key: key, - Operators: o, - } +func (hdr *snapshotHeader) Cancel() { + hdr.pop() } -type SortableSnapshots struct { - snapshots []*CatalogSnapshot - namespaces map[string]int - preferred *SourceKey - existing *SourceKey +func (hdr *snapshotHeader) Valid(at time.Time) bool { + return hdr.snapshot != nil && hdr.err == nil && at.Before(hdr.expiry) } -func NewSortableSnapshots(existing, preferred *SourceKey, namespaces []string, snapshots map[SourceKey]*CatalogSnapshot) SortableSnapshots { - sorted := SortableSnapshots{ - existing: existing, - preferred: preferred, - snapshots: make([]*CatalogSnapshot, 0), - namespaces: make(map[string]int, 0), - } - for i, n := range namespaces { - sorted.namespaces[n] = i +type sortableSnapshots struct { + snapshots []*snapshotHeader + preferredNamespace string + preferred *SourceKey + existing *SourceKey +} + +func newSortableSnapshots(existing, preferred *SourceKey, preferredNamespace string, snapshots map[SourceKey]*snapshotHeader) sortableSnapshots { + sorted := sortableSnapshots{ + existing: existing, + preferred: preferred, + snapshots: make([]*snapshotHeader, 0), + preferredNamespace: preferredNamespace, } for _, s := range snapshots { sorted.snapshots = append(sorted.snapshots, s) @@ -401,60 +344,64 @@ func NewSortableSnapshots(existing, preferred *SourceKey, namespaces []string, s return sorted } -var _ sort.Interface = SortableSnapshots{} +var _ sort.Interface = sortableSnapshots{} // Len is the number of elements in the collection. -func (s SortableSnapshots) Len() int { +func (s sortableSnapshots) Len() int { return len(s.snapshots) } // Less reports whether the element with // index i should sort before the element with index j. -func (s SortableSnapshots) Less(i, j int) bool { +func (s sortableSnapshots) Less(i, j int) bool { // existing operators are preferred over catalog operators if s.existing != nil && - s.snapshots[i].Key.Name == s.existing.Name && - s.snapshots[i].Key.Namespace == s.existing.Namespace { + s.snapshots[i].key.Name == s.existing.Name && + s.snapshots[i].key.Namespace == s.existing.Namespace { return true } if s.existing != nil && - s.snapshots[j].Key.Name == s.existing.Name && - s.snapshots[j].Key.Namespace == s.existing.Namespace { + s.snapshots[j].key.Name == s.existing.Name && + s.snapshots[j].key.Namespace == s.existing.Namespace { return false } // preferred catalog is less than all other catalogs if s.preferred != nil && - s.snapshots[i].Key.Name == s.preferred.Name && - s.snapshots[i].Key.Namespace == s.preferred.Namespace { + s.snapshots[i].key.Name == s.preferred.Name && + s.snapshots[i].key.Namespace == s.preferred.Namespace { return true } if s.preferred != nil && - s.snapshots[j].Key.Name == s.preferred.Name && - s.snapshots[j].Key.Namespace == s.preferred.Namespace { + s.snapshots[j].key.Name == s.preferred.Name && + s.snapshots[j].key.Namespace == s.preferred.Namespace { return false } // the rest are sorted first on priority, namespace and then by name - if s.snapshots[i].Priority != s.snapshots[j].Priority { - return s.snapshots[i].Priority > s.snapshots[j].Priority + if s.snapshots[i].priority != s.snapshots[j].priority { + return s.snapshots[i].priority > s.snapshots[j].priority } - if s.snapshots[i].Key.Namespace != s.snapshots[j].Key.Namespace { - return s.namespaces[s.snapshots[i].Key.Namespace] < s.namespaces[s.snapshots[j].Key.Namespace] + + if s.snapshots[i].key.Namespace != s.snapshots[j].key.Namespace { + return s.snapshots[i].key.Namespace < s.snapshots[j].key.Namespace } - return s.snapshots[i].Key.Name < s.snapshots[j].Key.Name + return s.snapshots[i].key.Name < s.snapshots[j].key.Name } // Swap swaps the elements with indexes i and j. -func (s SortableSnapshots) Swap(i, j int) { +func (s sortableSnapshots) Swap(i, j int) { s.snapshots[i], s.snapshots[j] = s.snapshots[j], s.snapshots[i] } -func (s *CatalogSnapshot) Find(p ...OperatorPredicate) []*Operator { +func (s *snapshotHeader) Find(p ...OperatorPredicate) []*Operator { s.m.RLock() defer s.m.RUnlock() - return Filter(s.Operators, p...) + if s.snapshot == nil { + return nil + } + return Filter(s.snapshot.Entries, p...) } type OperatorFinder interface { @@ -463,8 +410,8 @@ type OperatorFinder interface { type MultiCatalogOperatorFinder interface { Catalog(SourceKey) OperatorFinder - FindPreferred(*SourceKey, ...OperatorPredicate) []*Operator - WithExistingOperators(*CatalogSnapshot) MultiCatalogOperatorFinder + FindPreferred(preferred *SourceKey, preferredNamespace string, predicates ...OperatorPredicate) []*Operator + WithExistingOperators(snapshot *Snapshot, namespace string) MultiCatalogOperatorFinder Error() error OperatorFinder } diff --git a/pkg/controller/registry/resolver/cache/cache_test.go b/pkg/controller/registry/resolver/cache/cache_test.go index cff3b7157c2..f095456456c 100644 --- a/pkg/controller/registry/resolver/cache/cache_test.go +++ b/pkg/controller/registry/resolver/cache/cache_test.go @@ -4,112 +4,37 @@ import ( "context" "errors" "fmt" - "io" "math/rand" "strconv" "testing" "time" - "github.com/sirupsen/logrus" - "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister" "github.com/operator-framework/operator-registry/pkg/api" - "github.com/operator-framework/operator-registry/pkg/client" - opregistry "github.com/operator-framework/operator-registry/pkg/registry" ) -type BundleStreamStub struct { - Bundles []*api.Bundle -} - -func (s *BundleStreamStub) Recv() (*api.Bundle, error) { - if len(s.Bundles) == 0 { - return nil, io.EOF - } - b := s.Bundles[0] - s.Bundles = s.Bundles[1:] - return b, nil -} - -type RegistryClientStub struct { - BundleIterator *client.BundleIterator - - ListBundlesError error -} - -func (s *RegistryClientStub) Get() (client.Interface, error) { - return s, nil -} - -func (s *RegistryClientStub) GetBundle(ctx context.Context, packageName, channelName, csvName string) (*api.Bundle, error) { - return nil, nil -} - -func (s *RegistryClientStub) GetBundleInPackageChannel(ctx context.Context, packageName, channelName string) (*api.Bundle, error) { - return nil, nil -} - -func (s *RegistryClientStub) GetReplacementBundleInPackageChannel(ctx context.Context, currentName, packageName, channelName string) (*api.Bundle, error) { - return nil, nil -} - -func (s *RegistryClientStub) GetBundleThatProvides(ctx context.Context, group, version, kind string) (*api.Bundle, error) { - return nil, nil -} - -func (s *RegistryClientStub) ListBundles(ctx context.Context) (*client.BundleIterator, error) { - return s.BundleIterator, s.ListBundlesError -} - -func (s *RegistryClientStub) GetPackage(ctx context.Context, packageName string) (*api.Package, error) { - return &api.Package{Name: packageName}, nil -} - -func (s *RegistryClientStub) HealthCheck(ctx context.Context, reconnectTimeout time.Duration) (bool, error) { - return false, nil -} - -func (s *RegistryClientStub) Close() error { - return nil -} - -type RegistryClientProviderStub map[SourceKey]client.Interface - -func (s RegistryClientProviderStub) ClientsForNamespaces(namespaces ...string) map[SourceKey]client.Interface { - return s -} - func TestOperatorCacheConcurrency(t *testing.T) { const ( NWorkers = 64 ) - rcp := RegistryClientProviderStub{} - catsrcLister := operatorlister.NewLister().OperatorsV1alpha1().CatalogSourceLister() + + sp := make(StaticSourceProvider) var keys []SourceKey for i := 0; i < 128; i++ { for j := 0; j < 8; j++ { key := SourceKey{Namespace: strconv.Itoa(i), Name: strconv.Itoa(j)} keys = append(keys, key) - rcp[key] = &RegistryClientStub{ - BundleIterator: client.NewBundleIterator(&BundleStreamStub{ - Bundles: []*api.Bundle{{ - CsvName: fmt.Sprintf("%s/%s", key.Namespace, key.Name), - ProvidedApis: []*api.GroupVersionKind{{ - Group: "g", - Version: "v1", - Kind: "K", - Plural: "ks", - }}, - }}, - }), + sp[key] = &Snapshot{ + Entries: []*Operator{ + {Name: fmt.Sprintf("%s/%s", key.Namespace, key.Name)}, + }, } } } - c := NewOperatorCache(rcp, logrus.New(), catsrcLister) + c := New(sp) errs := make(chan error) for w := 0; w < NWorkers; w++ { @@ -142,56 +67,52 @@ func TestOperatorCacheConcurrency(t *testing.T) { } func TestOperatorCacheExpiration(t *testing.T) { - rcp := RegistryClientProviderStub{} - catsrcLister := operatorlister.NewLister().OperatorsV1alpha1().CatalogSourceLister() key := SourceKey{Namespace: "dummynamespace", Name: "dummyname"} - rcp[key] = &RegistryClientStub{ - BundleIterator: client.NewBundleIterator(&BundleStreamStub{ - Bundles: []*api.Bundle{{ - CsvName: "csvname", - ProvidedApis: []*api.GroupVersionKind{{ - Group: "g", - Version: "v1", - Kind: "K", - Plural: "ks", - }}, - }}, - }), - } - - c := NewOperatorCache(rcp, logrus.New(), catsrcLister) + ssp := make(StaticSourceProvider) + c := New(ssp) c.ttl = 0 // instantly stale - require.Len(t, c.Namespaced("dummynamespace").Catalog(key).Find(CSVNamePredicate("csvname")), 1) + ssp[key] = &Snapshot{ + Entries: []*Operator{ + {Name: "v1"}, + }, + } + require.Len(t, c.Namespaced("dummynamespace").Catalog(key).Find(CSVNamePredicate("v1")), 1) + + ssp[key] = &Snapshot{ + Entries: []*Operator{ + {Name: "v2"}, + }, + } + require.Len(t, c.Namespaced("dummynamespace").Catalog(key).Find(CSVNamePredicate("v1")), 0) } func TestOperatorCacheReuse(t *testing.T) { - rcp := RegistryClientProviderStub{} - catsrcLister := operatorlister.NewLister().OperatorsV1alpha1().CatalogSourceLister() key := SourceKey{Namespace: "dummynamespace", Name: "dummyname"} - rcp[key] = &RegistryClientStub{ - BundleIterator: client.NewBundleIterator(&BundleStreamStub{ - Bundles: []*api.Bundle{{ - CsvName: "csvname", - ProvidedApis: []*api.GroupVersionKind{{ - Group: "g", - Version: "v1", - Kind: "K", - Plural: "ks", - }}, - }}, - }), - } + ssp := make(StaticSourceProvider) + c := New(ssp) - c := NewOperatorCache(rcp, logrus.New(), catsrcLister) + ssp[key] = &Snapshot{ + Entries: []*Operator{ + {Name: "v1"}, + }, + } + require.Len(t, c.Namespaced("dummynamespace").Catalog(key).Find(CSVNamePredicate("v1")), 1) - require.Len(t, c.Namespaced("dummynamespace").Catalog(key).Find(CSVNamePredicate("csvname")), 1) + ssp[key] = &Snapshot{ + Entries: []*Operator{ + {Name: "v2"}, + }, + } + require.Len(t, c.Namespaced("dummynamespace").Catalog(key).Find(CSVNamePredicate("v1")), 1) } -func TestCatalogSnapshotExpired(t *testing.T) { +func TestCatalogSnapshotValid(t *testing.T) { type tc struct { Name string Expiry time.Time + Snapshot *Snapshot + Error error At time.Time Expected bool } @@ -200,25 +121,51 @@ func TestCatalogSnapshotExpired(t *testing.T) { { Name: "after expiry", Expiry: time.Unix(0, 1), + Snapshot: &Snapshot{}, + Error: nil, At: time.Unix(0, 2), - Expected: true, + Expected: false, }, { Name: "before expiry", Expiry: time.Unix(0, 2), + Snapshot: &Snapshot{}, + Error: nil, + At: time.Unix(0, 1), + Expected: true, + }, + { + Name: "nil snapshot", + Expiry: time.Unix(0, 2), + Snapshot: nil, + Error: errors.New(""), + At: time.Unix(0, 1), + Expected: false, + }, + { + Name: "non-nil error", + Expiry: time.Unix(0, 2), + Snapshot: &Snapshot{}, + Error: errors.New(""), At: time.Unix(0, 1), Expected: false, }, { Name: "at expiry", Expiry: time.Unix(0, 1), + Snapshot: &Snapshot{}, + Error: nil, At: time.Unix(0, 1), - Expected: true, + Expected: false, }, } { t.Run(tt.Name, func(t *testing.T) { - s := CatalogSnapshot{expiry: tt.Expiry} - assert.Equal(t, tt.Expected, s.Expired(tt.At)) + s := snapshotHeader{ + expiry: tt.Expiry, + snapshot: tt.Snapshot, + err: tt.Error, + } + assert.Equal(t, tt.Expected, s.Valid(tt.At)) }) } @@ -286,7 +233,7 @@ func TestCatalogSnapshotFind(t *testing.T) { }, } { t.Run(tt.Name, func(t *testing.T) { - s := CatalogSnapshot{Operators: tt.Operators} + s := snapshotHeader{snapshot: &Snapshot{Entries: tt.Operators}} assert.Equal(t, tt.Expected, s.Find(tt.Predicate)) }) } @@ -294,69 +241,41 @@ func TestCatalogSnapshotFind(t *testing.T) { } func TestStripPluralRequiredAndProvidedAPIKeys(t *testing.T) { - rcp := RegistryClientProviderStub{} - catsrcLister := operatorlister.NewLister().OperatorsV1alpha1().CatalogSourceLister() key := SourceKey{Namespace: "testnamespace", Name: "testname"} - rcp[key] = &RegistryClientStub{ - BundleIterator: client.NewBundleIterator(&BundleStreamStub{ - Bundles: []*api.Bundle{{ - CsvName: fmt.Sprintf("%s/%s", key.Namespace, key.Name), - ProvidedApis: []*api.GroupVersionKind{{ - Group: "g", - Version: "v1", - Kind: "K", - Plural: "ks", - }}, - RequiredApis: []*api.GroupVersionKind{{ - Group: "g2", - Version: "v2", - Kind: "K2", - Plural: "ks2", - }}, - Properties: APISetToProperties(map[opregistry.APIKey]struct{}{ - { - Group: "g", - Version: "v1", - Kind: "K", - Plural: "ks", - }: {}, - }, nil, false), - Dependencies: APISetToDependencies(map[opregistry.APIKey]struct{}{ - { - Group: "g2", - Version: "v2", - Kind: "K2", - Plural: "ks2", - }: {}, - }, nil), - }}, - }), - } - - c := NewOperatorCache(rcp, logrus.New(), catsrcLister) + o, err := NewOperatorFromBundle(&api.Bundle{ + CsvName: fmt.Sprintf("%s/%s", key.Namespace, key.Name), + ProvidedApis: []*api.GroupVersionKind{{ + Group: "g", + Version: "v1", + Kind: "K", + Plural: "ks", + }}, + RequiredApis: []*api.GroupVersionKind{{ + Group: "g2", + Version: "v2", + Kind: "K2", + Plural: "ks2", + }}, + }, "", key, "") - nc := c.Namespaced("testnamespace") - result, err := AtLeast(1, nc.Find(ProvidingAPIPredicate(opregistry.APIKey{Group: "g", Version: "v1", Kind: "K"}))) assert.NoError(t, err) - assert.Equal(t, 1, len(result)) - assert.Equal(t, "K.v1.g", result[0].ProvidedAPIs.String()) - assert.Equal(t, "K2.v2.g2", result[0].RequiredAPIs.String()) + assert.Equal(t, "K.v1.g", o.ProvidedAPIs.String()) + assert.Equal(t, "K2.v2.g2", o.RequiredAPIs.String()) +} + +type ErrorSource struct { + Error error +} + +func (s ErrorSource) Snapshot(context.Context) (*Snapshot, error) { + return nil, s.Error } func TestNamespaceOperatorCacheError(t *testing.T) { - rcp := RegistryClientProviderStub{} - catsrcLister := operatorlister.NewLister().OperatorsV1alpha1().CatalogSourceLister() key := SourceKey{Namespace: "dummynamespace", Name: "dummyname"} - rcp[key] = &RegistryClientStub{ - ListBundlesError: errors.New("testing"), - } + c := New(StaticSourceProvider{ + key: ErrorSource{Error: errors.New("testing")}, + }) - logger, _ := test.NewNullLogger() - c := NewOperatorCache(rcp, logger, catsrcLister) require.EqualError(t, c.Namespaced("dummynamespace").Error(), "error using catalog dummyname (in namespace dummynamespace): testing") - if snapshot, ok := c.snapshots[key]; !ok { - t.Fatalf("cache snapshot not found") - } else { - require.Zero(t, snapshot.expiry) - } } diff --git a/pkg/controller/registry/resolver/resolver.go b/pkg/controller/registry/resolver/resolver.go index de55e70a595..38e779acd3d 100644 --- a/pkg/controller/registry/resolver/resolver.go +++ b/pkg/controller/registry/resolver/resolver.go @@ -29,10 +29,10 @@ type SatResolver struct { log logrus.FieldLogger } -func NewDefaultSatResolver(rcp cache.SourceProvider, catsrcLister v1alpha1listers.CatalogSourceLister, log logrus.FieldLogger) *SatResolver { +func NewDefaultSatResolver(rcp cache.SourceProvider, catsrcLister v1alpha1listers.CatalogSourceLister, logger logrus.FieldLogger) *SatResolver { return &SatResolver{ - cache: cache.NewOperatorCache(rcp, log, catsrcLister), - log: log, + cache: cache.New(rcp, cache.WithLogger(logger), cache.WithCatalogSourceLister(catsrcLister)), + log: logger, } } @@ -60,9 +60,9 @@ func (r *SatResolver) SolveOperators(namespaces []string, csvs []*v1alpha1.Clust if err != nil { return nil, err } - namespacedCache := r.cache.Namespaced(namespaces...).WithExistingOperators(existingSnapshot) + namespacedCache := r.cache.Namespaced(namespaces...).WithExistingOperators(existingSnapshot, namespaces[0]) - _, existingInstallables, err := r.getBundleInstallables(cache.NewVirtualSourceKey(namespaces[0]), existingSnapshot.Find(), namespacedCache, visited) + _, existingInstallables, err := r.getBundleInstallables(namespaces[0], cache.Filter(existingSnapshot.Entries, cache.True()), namespacedCache, visited) if err != nil { return nil, err } @@ -258,7 +258,7 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu for _, o := range cache.Filter(sortedBundles, channelPredicates...) { predicates := append(cachePredicates, cache.CSVNamePredicate(o.Name)) stack := namespacedCache.Catalog(catalog).Find(predicates...) - id, installable, err := r.getBundleInstallables(catalog, stack, namespacedCache, visited) + id, installable, err := r.getBundleInstallables(sub.Namespace, stack, namespacedCache, visited) if err != nil { return nil, err } @@ -302,7 +302,7 @@ func (r *SatResolver) getSubscriptionInstallables(sub *v1alpha1.Subscription, cu return installables, nil } -func (r *SatResolver) getBundleInstallables(catalog cache.SourceKey, bundleStack []*cache.Operator, namespacedCache cache.MultiCatalogOperatorFinder, visited map[*cache.Operator]*BundleInstallable) (map[solver.Identifier]struct{}, map[solver.Identifier]*BundleInstallable, error) { +func (r *SatResolver) getBundleInstallables(preferredNamespace string, bundleStack []*cache.Operator, namespacedCache cache.MultiCatalogOperatorFinder, visited map[*cache.Operator]*BundleInstallable) (map[solver.Identifier]struct{}, map[solver.Identifier]*BundleInstallable, error) { errs := make([]error, 0) installables := make(map[solver.Identifier]*BundleInstallable, 0) // all installables, including dependencies @@ -369,7 +369,8 @@ func (r *SatResolver) getBundleInstallables(catalog cache.SourceKey, bundleStack )) } } - sortedBundles, err := r.sortBundles(namespacedCache.FindPreferred(&bundle.SourceInfo.Catalog, sourcePredicate)) + + sortedBundles, err := r.sortBundles(namespacedCache.FindPreferred(&bundle.SourceInfo.Catalog, preferredNamespace, sourcePredicate)) if err != nil { errs = append(errs, err) continue @@ -421,7 +422,7 @@ func (r *SatResolver) inferProperties(csv *v1alpha1.ClusterServiceVersion, subs // package against catalog contents, updates to the // Subscription spec could result in a bad package // inference. - for _, entry := range r.cache.Namespaced(sub.Namespace).Catalog(cache.SourceKey{Namespace: sub.Spec.CatalogSourceNamespace, Name: sub.Spec.CatalogSource}).Find(cache.And(cache.CSVNamePredicate(csv.Name), cache.PkgPredicate(sub.Spec.Package))) { + for _, entry := range r.cache.Namespaced(sub.Spec.CatalogSourceNamespace).Catalog(cache.SourceKey{Namespace: sub.Spec.CatalogSourceNamespace, Name: sub.Spec.CatalogSource}).Find(cache.And(cache.CSVNamePredicate(csv.Name), cache.PkgPredicate(sub.Spec.Package))) { if pkg := entry.Package(); pkg != "" { packages[pkg] = struct{}{} } @@ -454,7 +455,7 @@ func (r *SatResolver) inferProperties(csv *v1alpha1.ClusterServiceVersion, subs return properties, nil } -func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1.Subscription, csvs []*v1alpha1.ClusterServiceVersion) (*cache.CatalogSnapshot, error) { +func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1.Subscription, csvs []*v1alpha1.ClusterServiceVersion) (*cache.Snapshot, error) { existingOperatorCatalog := cache.NewVirtualSourceKey(namespace) // build a catalog snapshot of CSVs without subscriptions csvSubscriptions := make(map[*v1alpha1.ClusterServiceVersion]*v1alpha1.Subscription) @@ -516,7 +517,7 @@ func (r *SatResolver) newSnapshotForNamespace(namespace string, subs []*v1alpha1 r.log.Infof("considered csvs without properties annotation during resolution: %v", names) } - return cache.NewRunningOperatorSnapshot(r.log, existingOperatorCatalog, standaloneOperators), nil + return &cache.Snapshot{Entries: standaloneOperators}, nil } func (r *SatResolver) addInvariants(namespacedCache cache.MultiCatalogOperatorFinder, installables map[solver.Identifier]solver.Installable) { diff --git a/pkg/controller/registry/resolver/resolver_test.go b/pkg/controller/registry/resolver/resolver_test.go index cd133f526d2..1d80b0cd13f 100644 --- a/pkg/controller/registry/resolver/resolver_test.go +++ b/pkg/controller/registry/resolver/resolver_test.go @@ -12,9 +12,11 @@ import ( "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "github.com/operator-framework/api/pkg/lib/version" "github.com/operator-framework/api/pkg/operators/v1alpha1" + listersv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver" "github.com/operator-framework/operator-registry/pkg/api" @@ -34,20 +36,16 @@ func TestSolveOperators(t *testing.T) { newSub := newSub(namespace, "packageB", "alpha", catalog) subs := []*v1alpha1.Subscription{sub, newSub} - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Key: catalog, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.1", "", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{namespace}, csvs, subs) @@ -66,22 +64,18 @@ func TestDisjointChannelGraph(t *testing.T) { newSub := newSub(namespace, "packageA", "alpha", catalog) subs := []*v1alpha1.Subscription{newSub} - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Key: catalog, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.side1.v1", "0.0.1", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), genOperator("packageA.side1.v2", "0.0.2", "packageA.side1.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), genOperator("packageA.side2.v1", "1.0.0", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), genOperator("packageA.side2.v2", "2.0.0", "packageA.side2.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } _, err := satResolver.SolveOperators([]string{namespace}, nil, subs) @@ -103,17 +97,13 @@ func TestPropertiesAnnotationHonored(t *testing.T) { b := genOperator("packageB.v1", "1.0.1", "", "packageB", "alpha", "community", "olm", nil, nil, []*api.Dependency{{Type: "olm.package", Value: `{"packageName":"packageA","version":"1.0.0"}`}}, "", false) - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - community: { - Key: community, - Operators: []*cache.Operator{b}, - }, - }, - } satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + cache: cache.New(cache.StaticSourceProvider{ + community: &cache.Snapshot{ + Entries: []*cache.Operator{b}, + }, + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) @@ -138,21 +128,17 @@ func TestSolveOperators_MultipleChannels(t *testing.T) { newSub := newSub(namespace, "packageB", "alpha", catalog) subs := []*v1alpha1.Subscription{sub, newSub} - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Key: catalog, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "beta", "community", "olm", nil, nil, nil, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) @@ -179,28 +165,21 @@ func TestSolveOperators_FindLatestVersion(t *testing.T) { newSub := newSub(namespace, "packageB", "alpha", catalog) subs := []*v1alpha1.Subscription{sub, newSub} - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ cache.SourceKey{ Namespace: "olm", Name: "community", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community", - }, - Operators: []*cache.Operator{ + }: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v0.9.0", "0.9.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1.0.0", "1.0.0", "packageB.v0.9.0", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) @@ -244,17 +223,10 @@ func TestSolveOperators_FindLatestVersionWithDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - cache.SourceKey{ - Namespace: "olm", - Name: "community", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community", - }, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v0.9.0", "0.9.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1.0.0", "1.0.0", "packageB.v0.9.0", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), @@ -266,11 +238,8 @@ func TestSolveOperators_FindLatestVersionWithDependencies(t *testing.T) { genOperator("packageD.v1.0.2", "1.0.2", "packageD.v1.0.1", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) @@ -319,17 +288,10 @@ func TestSolveOperators_FindLatestVersionWithNestedDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - cache.SourceKey{ - Namespace: "olm", - Name: "community", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community", - }, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v0.9.0", "0.9.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1.0.0", "1.0.0", "packageB.v0.9.0", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), @@ -341,11 +303,8 @@ func TestSolveOperators_FindLatestVersionWithNestedDependencies(t *testing.T) { genOperator("packageE.v1.0.0", "1.0.0", "", "packageE", "alpha", "community", "olm", nil, nil, nil, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) @@ -365,6 +324,40 @@ func TestSolveOperators_FindLatestVersionWithNestedDependencies(t *testing.T) { } } +type stubCatalogSourceLister struct { + catsrcs []*v1alpha1.CatalogSource + namespace string +} + +func (l *stubCatalogSourceLister) List(labels.Selector) ([]*v1alpha1.CatalogSource, error) { + if l.namespace == "" { + return l.catsrcs, nil + } + var result []*v1alpha1.CatalogSource + for _, cs := range l.catsrcs { + if cs.Namespace == l.namespace { + result = append(result, cs) + } + } + return result, nil +} + +func (l *stubCatalogSourceLister) Get(name string) (*v1alpha1.CatalogSource, error) { + for _, cs := range l.catsrcs { + if cs.Name == name { + return cs, nil + } + } + return nil, errors.New("stub not found") +} + +func (l *stubCatalogSourceLister) CatalogSources(namespace string) listersv1alpha1.CatalogSourceNamespaceLister { + return &stubCatalogSourceLister{ + catsrcs: l.catsrcs, + namespace: namespace, + } +} + func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { opToAddVersionDeps := []*api.Dependency{ { @@ -378,54 +371,41 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { newSub := newSub(namespace, "packageA", "alpha", customCatalog) subs := []*v1alpha1.Subscription{newSub} - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - cache.SourceKey{ - Namespace: "olm", - Name: "community", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community", - }, - Operators: []*cache.Operator{ - genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", namespace, nil, - nil, opToAddVersionDeps, "", false), - }, + ssp := cache.StaticSourceProvider{ + cache.SourceKey{Namespace: "olm", Name: "community"}: &cache.Snapshot{ + Entries: []*cache.Operator{ + genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", namespace, nil, + nil, opToAddVersionDeps, "", false), }, - cache.SourceKey{ - Namespace: "olm", - Name: "community-operator", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community-operator", - }, - Operators: []*cache.Operator{ - genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "community-operator", - namespace, nil, nil, nil, "", false), - }, + }, + cache.SourceKey{Namespace: "olm", Name: "community-operator"}: &cache.Snapshot{ + Entries: []*cache.Operator{ + genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "community-operator", + namespace, nil, nil, nil, "", false), }, - cache.SourceKey{ - Namespace: "olm", - Name: "high-priority-operator", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "high-priority-operator", - }, - Priority: 100, - Operators: []*cache.Operator{ - genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "high-priority-operator", - namespace, nil, nil, nil, "", false), - }, + }, + cache.SourceKey{Namespace: "olm", Name: "high-priority-operator"}: &cache.Snapshot{ + Entries: []*cache.Operator{ + genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "high-priority-operator", + namespace, nil, nil, nil, "", false), }, }, } - // operators sorted by priority. satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), + cache: cache.New(ssp, cache.WithCatalogSourceLister(&stubCatalogSourceLister{ + catsrcs: []*v1alpha1.CatalogSource{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "olm", + Name: "high-priority-operator", + }, + Spec: v1alpha1.CatalogSourceSpec{ + Priority: 100, + }, + }, + }, + })), } operators, err := satResolver.SolveOperators([]string{"olm"}, []*v1alpha1.ClusterServiceVersion{}, subs) @@ -442,23 +422,39 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { } // Catsrc with the same priority, ns, different name - fakeNamespacedOperatorCache.Snapshots[cache.SourceKey{ + ssp[cache.SourceKey{ Namespace: "olm", Name: "community-operator", - }] = &cache.CatalogSnapshot{ - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community-operator", - }, - Priority: 100, - Operators: []*cache.Operator{ + }] = &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "community-operator", namespace, nil, nil, nil, "", false), }, } satResolver = SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), + cache: cache.New(ssp, cache.WithCatalogSourceLister(&stubCatalogSourceLister{ + catsrcs: []*v1alpha1.CatalogSource{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "olm", + Name: "high-priority-operator", + }, + Spec: v1alpha1.CatalogSourceSpec{ + Priority: 100, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "olm", + Name: "community-operator", + }, + Spec: v1alpha1.CatalogSourceSpec{ + Priority: 100, + }, + }, + }, + })), } operators, err = satResolver.SolveOperators([]string{"olm"}, []*v1alpha1.ClusterServiceVersion{}, subs) @@ -475,15 +471,11 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { } // operators from the same catalogs source should be prioritized. - fakeNamespacedOperatorCache.Snapshots[cache.SourceKey{ + ssp[cache.SourceKey{ Namespace: "olm", Name: "community", - }] = &cache.CatalogSnapshot{ - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community", - }, - Operators: []*cache.Operator{ + }] = &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", namespace, nil, nil, opToAddVersionDeps, "", false), genOperator("packageB.v1", "0.0.1", "", "packageB", "alpha", "community", @@ -492,7 +484,7 @@ func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { } satResolver = SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), + cache: cache.New(ssp), } operators, err = satResolver.SolveOperators([]string{"olm"}, []*v1alpha1.ClusterServiceVersion{}, subs) @@ -530,27 +522,17 @@ func TestSolveOperators_WithDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - cache.SourceKey{ - Namespace: "olm", - Name: "community", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community", - }, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) @@ -590,21 +572,17 @@ func TestSolveOperators_WithGVKDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - community: { - Key: community, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + community: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false), genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, Provides, nil, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) @@ -647,25 +625,15 @@ func TestSolveOperators_WithLabelDependencies(t *testing.T) { operatorBv1.Properties = append(operatorBv1.Properties, p) } - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - cache.SourceKey{ - Namespace: "olm", - Name: "community", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community", - }, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, deps, "", false), operatorBv1, }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), + }), } operators, err := satResolver.SolveOperators([]string{"olm"}, nil, subs) @@ -696,25 +664,15 @@ func TestSolveOperators_WithUnsatisfiableLabelDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - cache.SourceKey{ - Namespace: "olm", - Name: "community", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community", - }, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, deps, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, nil, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), + }), } operators, err := satResolver.SolveOperators([]string{"olm"}, nil, subs) @@ -749,17 +707,13 @@ func TestSolveOperators_WithNestedGVKDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ cache.SourceKey{ Namespace: "olm", Name: "community", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community", - }, - Operators: []*cache.Operator{ + }: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v1.0.1", "1.0.1", "packageA.v1", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1.0.0", "1.0.0", "", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false), genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false), @@ -771,22 +725,15 @@ func TestSolveOperators_WithNestedGVKDependencies(t *testing.T) { cache.SourceKey{ Namespace: "olm", Name: "certified", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "certified", - }, - Operators: []*cache.Operator{ + }: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageC.v1.0.0", "1.0.0", "", "packageC", "alpha", "certified", "olm", Provides2, Provides, deps2, "", false), genOperator("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "certified", "olm", Provides2, Provides, deps2, "", false), genOperator("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "certified", "olm", nil, Provides2, nil, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) @@ -836,35 +783,24 @@ func TestSolveOperators_IgnoreUnsatisfiableDependencies(t *testing.T) { }, } - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - community: { - Key: community, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + community: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "community", "olm", nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, nil, unsatisfiableVersionDeps, "", false), }, }, - { - Namespace: "olm", - Name: "certified", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "certified", - }, - Operators: []*cache.Operator{ + {Namespace: "olm", Name: "certified"}: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", "certified", "olm", nil, nil, nil, "", false), genOperator("packageB.v1", "1.0.0", "", "packageB", "alpha", "certified", "olm", nil, nil, opToAddVersionDeps, "", false), genOperator("packageC.v1", "0.1.0", "", "packageC", "alpha", "certified", "olm", nil, nil, nil, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) @@ -896,24 +832,20 @@ func TestSolveOperators_PreferCatalogInSameNamespace(t *testing.T) { sub := existingSub(namespace, "packageA.v1", "packageA", "alpha", catalog) subs := []*v1alpha1.Subscription{sub} - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, "", false), }, }, - altnsCatalog: { - Operators: []*cache.Operator{ + altnsCatalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", altnsCatalog.Name, altnsCatalog.Namespace, nil, Provides, nil, "", false), }, }, - }, - Namespaces: []string{namespace, altNamespace}, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{namespace}, csvs, subs) @@ -940,19 +872,15 @@ func TestSolveOperators_ResolveOnlyInCachedNamespaces(t *testing.T) { newSub := newSub(namespace, "packageA", "alpha", catalog) subs := []*v1alpha1.Subscription{newSub} - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", otherCatalog.Name, otherCatalog.Namespace, nil, Provides, nil, "", false), }, }, - }, - Namespaces: []string{otherCatalog.Namespace}, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{namespace}, csvs, subs) @@ -976,21 +904,17 @@ func TestSolveOperators_PreferDefaultChannelInResolution(t *testing.T) { newSub := newSub(namespace, "packageA", "", catalog) subs := []*v1alpha1.Subscription{newSub} - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ // Default channel is stable in this case genOperator("packageA.v0.0.2", "0.0.2", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), }, }, - }, - } - - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{namespace}, csvs, subs) @@ -1017,21 +941,18 @@ func TestSolveOperators_PreferDefaultChannelInResolutionForTransitiveDependencie subs := []*v1alpha1.Subscription{newSub} const defaultChannel = "stable" - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Operators: []*cache.Operator{ + + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v0.0.1", "0.0.1", "packageA.v1", "packageA", "alpha", catalog.Name, catalog.Namespace, Provides, nil, cache.APISetToDependencies(nil, Provides), defaultChannel, false), genOperator("packageB.v0.0.1", "0.0.1", "packageB.v1", "packageB", defaultChannel, catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), genOperator("packageB.v0.0.2", "0.0.2", "packageB.v0.0.1", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), }, }, - }, - } - - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{namespace}, csvs, subs) @@ -1064,26 +985,16 @@ func TestSolveOperators_SubscriptionlessOperatorsSatisfyDependencies(t *testing. }, } - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - cache.SourceKey{ - Namespace: "olm", - Name: "community", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community", - }, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageB.v1.0.0", "1.0.0", "", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false), genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", Provides, nil, deps, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) @@ -1110,26 +1021,16 @@ func TestSolveOperators_SubscriptionlessOperatorsCanConflict(t *testing.T) { newSub := newSub(namespace, "packageB", "alpha", catalog) subs := []*v1alpha1.Subscription{newSub} - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - cache.SourceKey{ - Namespace: "olm", - Name: "community", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community", - }, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageB.v1.0.0", "1.0.0", "", "packageB", "alpha", "community", "olm", nil, Provides, nil, "", false), genOperator("packageB.v1.0.1", "1.0.1", "packageB.v1.0.0", "packageB", "alpha", "community", "olm", nil, Provides, nil, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } _, err := satResolver.SolveOperators([]string{"olm"}, csvs, subs) @@ -1151,11 +1052,10 @@ func TestSolveOperators_PackageCannotSelfSatisfy(t *testing.T) { newSub := newSub(namespace, "packageA", "stable", catalog) subs := []*v1alpha1.Subscription{newSub} - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Key: catalog, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, RequiresBoth, nil, nil, "", false), // Despite satisfying dependencies of opA, this is not chosen because it is in the same package genOperator("opABC.v1.0.0", "1.0.0", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, ProvidesBoth, nil, "", false), @@ -1164,19 +1064,15 @@ func TestSolveOperators_PackageCannotSelfSatisfy(t *testing.T) { genOperator("opD.v1.0.0", "1.0.0", "", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false), }, }, - secondaryCatalog: { - Key: secondaryCatalog, - Operators: []*cache.Operator{ + secondaryCatalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("opC.v1.0.0", "1.0.0", "", "packageB", "stable", secondaryCatalog.Name, secondaryCatalog.Namespace, nil, Provides2, nil, "stable", false), genOperator("opE.v1.0.0", "1.0.0", "", "packageC", "stable", secondaryCatalog.Name, secondaryCatalog.Namespace, nil, Provides2, nil, "", false), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{"olm"}, nil, subs) @@ -1204,14 +1100,13 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) { phases := []struct { subs []*v1alpha1.Subscription - catalog *cache.CatalogSnapshot + catalog cache.Source expected cache.OperatorSet }{ { subs: []*v1alpha1.Subscription{newSub(namespace, "packageB", "stable", catalog)}, - catalog: &cache.CatalogSnapshot{ - Key: catalog, - Operators: []*cache.Operator{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false), genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false), }, @@ -1227,9 +1122,8 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) { existingSub(namespace, "opA.v1.0.0", "packageA", "stable", catalog), existingSub(namespace, "opB.v1.0.0", "packageB", "stable", catalog), }, - catalog: &cache.CatalogSnapshot{ - Key: catalog, - Operators: []*cache.Operator{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false), genOperator("opA.v1.0.1", "1.0.1", "opA.v1.0.0", "packageA", "stable", catalog.Name, catalog.Namespace, Requires1, nil, nil, "", false), genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false), @@ -1244,9 +1138,8 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) { existingSub(namespace, "opA.v1.0.0", "packageA", "stable", catalog), existingSub(namespace, "opB.v1.0.0", "packageB", "stable", catalog), }, - catalog: &cache.CatalogSnapshot{ - Key: catalog, - Operators: []*cache.Operator{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("opA.v1.0.0", "1.0.0", "", "packageA", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false), genOperator("opA.v1.0.1", "1.0.1", "opA.v1.0.0", "packageA", "stable", catalog.Name, catalog.Namespace, Requires1, nil, nil, "", false), genOperator("opB.v1.0.0", "1.0.0", "", "packageB", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false), @@ -1263,14 +1156,11 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) { var operators cache.OperatorSet for i, p := range phases { t.Run(fmt.Sprintf("phase %d", i+1), func(t *testing.T) { - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: p.catalog, - }, - } satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + cache: cache.New(cache.StaticSourceProvider{ + catalog: p.catalog, + }), + log: logrus.New(), } csvs := make([]*v1alpha1.ClusterServiceVersion, 0) for _, o := range operators { @@ -1294,24 +1184,6 @@ func TestSolveOperators_TransferApiOwnership(t *testing.T) { } } -type FakeOperatorCache struct { - fakedNamespacedOperatorCache cache.NamespacedOperatorCache -} - -func (f *FakeOperatorCache) Namespaced(namespaces ...string) cache.MultiCatalogOperatorFinder { - return &f.fakedNamespacedOperatorCache -} - -func (f *FakeOperatorCache) Expire(key cache.SourceKey) { - return -} - -func getFakeOperatorCache(fakedNamespacedOperatorCache cache.NamespacedOperatorCache) cache.OperatorCacheProvider { - return &FakeOperatorCache{ - fakedNamespacedOperatorCache: fakedNamespacedOperatorCache, - } -} - func genOperator(name, version, replaces, pkg, channel, catalogName, catalogNamespace string, requiredAPIs, providedAPIs cache.APISet, dependencies []*api.Dependency, defaultChannel string, deprecated bool) *cache.Operator { semversion, _ := semver.Make(version) properties := cache.APISetToProperties(providedAPIs, nil, deprecated) @@ -1351,7 +1223,7 @@ func genOperator(name, version, replaces, pkg, channel, catalogName, catalogName ProvidedAPIs: providedAPIs, RequiredAPIs: requiredAPIs, } - cache.EnsurePackageProperty(o, pkg, version) + EnsurePackageProperty(o, pkg, version) return o } @@ -1367,19 +1239,15 @@ func TestSolveOperators_WithoutDeprecated(t *testing.T) { newSub(catalog.Namespace, "packageA", "alpha", catalog), } - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Key: catalog, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ genOperator("packageA.v1", "0.0.1", "", "packageA", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", true), }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{catalog.Namespace}, nil, subs) @@ -1395,15 +1263,12 @@ func TestSolveOperatorsWithDeprecatedInnerChannelEntry(t *testing.T) { } logger, _ := test.NewNullLogger() resolver := SatResolver{ - cache: getFakeOperatorCache(cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Key: catalog, - Operators: []*cache.Operator{ - genOperator("a-1", "1.0.0", "", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), - genOperator("a-2", "2.0.0", "a-1", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", true), - genOperator("a-3", "3.0.0", "a-2", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), - }, + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ + genOperator("a-1", "1.0.0", "", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), + genOperator("a-2", "2.0.0", "a-1", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", true), + genOperator("a-3", "3.0.0", "a-2", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), }, }, }), @@ -1445,25 +1310,15 @@ func TestSolveOperators_WithSkipsAndStartingCSV(t *testing.T) { op5.Skips = []string{"packageA.v2", "packageA.v3", "packageA.v4"} op6 := genOperator("packageA.v6", "6.0.0", "packageA.v5", "packageA", "alpha", "community", "olm", nil, Provides, nil, "", false) - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - cache.SourceKey{ - Namespace: "olm", - Name: "community", - }: { - Key: cache.SourceKey{ - Namespace: "olm", - Name: "community", - }, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ opB, opB2, op1, op2, op3, op4, op5, op6, }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{"olm"}, nil, subs) @@ -1487,19 +1342,15 @@ func TestSolveOperators_WithSkips(t *testing.T) { opB2 := genOperator("packageB.v2", "2.0.0", "", "packageB", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false) opB2.Skips = []string{"packageB.v1"} - fakeNamespacedOperatorCache := cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Key: catalog, - Operators: []*cache.Operator{ + satResolver := SatResolver{ + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ opB, opB2, }, }, - }, - } - satResolver := SatResolver{ - cache: getFakeOperatorCache(fakeNamespacedOperatorCache), - log: logrus.New(), + }), + log: logrus.New(), } operators, err := satResolver.SolveOperators([]string{namespace}, nil, subs) @@ -1527,12 +1378,9 @@ func TestSolveOperatorsWithSkipsPreventingSelection(t *testing.T) { logger, _ := test.NewNullLogger() satResolver := SatResolver{ - cache: getFakeOperatorCache(cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Key: catalog, - Operators: []*cache.Operator{a1, b3, b2, b1}, - }, + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{a1, b3, b2, b1}, }, }), log: logger, @@ -1563,13 +1411,10 @@ func TestSolveOperatorsWithClusterServiceVersionHavingDependency(t *testing.T) { log, _ := test.NewNullLogger() r := SatResolver{ - cache: getFakeOperatorCache(cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Key: catalog, - Operators: []*cache.Operator{ - genOperator("b-2", "2.0.0", "b-1", "b", "default", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), - }, + cache: cache.New(cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ + genOperator("b-2", "2.0.0", "b-1", "b", "default", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), }, }, }), @@ -1586,7 +1431,7 @@ func TestInferProperties(t *testing.T) { for _, tc := range []struct { Name string - Cache cache.NamespacedOperatorCache + Cache cache.StaticSourceProvider CSV *v1alpha1.ClusterServiceVersion Subscriptions []*v1alpha1.Subscription Expected []*api.Property @@ -1663,16 +1508,13 @@ func TestInferProperties(t *testing.T) { }, { Name: "one matching subscription infers package property", - Cache: cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Key: catalog, - Operators: []*cache.Operator{ - { - Name: "a", - Bundle: &api.Bundle{ - PackageName: "x", - }, + Cache: cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ + { + Name: "a", + Bundle: &api.Bundle{ + PackageName: "x", }, }, }, @@ -1705,6 +1547,47 @@ func TestInferProperties(t *testing.T) { }, }, }, + { + Name: "one matching subscription to other-namespace catalogsource infers package property", + Cache: cache.StaticSourceProvider{ + {Namespace: "other-namespace", Name: "other-name"}: &cache.Snapshot{ + Entries: []*cache.Operator{ + { + Name: "a", + Bundle: &api.Bundle{ + PackageName: "x", + }, + }, + }, + }, + }, + CSV: &v1alpha1.ClusterServiceVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: "a", + }, + Spec: v1alpha1.ClusterServiceVersionSpec{ + Version: version.OperatorVersion{Version: semver.MustParse("1.2.3")}, + }, + }, + Subscriptions: []*v1alpha1.Subscription{ + { + Spec: &v1alpha1.SubscriptionSpec{ + Package: "x", + CatalogSource: "other-name", + CatalogSourceNamespace: "other-namespace", + }, + Status: v1alpha1.SubscriptionStatus{ + InstalledCSV: "a", + }, + }, + }, + Expected: []*api.Property{ + { + Type: "olm.package", + Value: `{"packageName":"x","version":"1.2.3"}`, + }, + }, + }, { Name: "one matching subscription without catalog entry infers no properties", CSV: &v1alpha1.ClusterServiceVersion{ @@ -1728,16 +1611,13 @@ func TestInferProperties(t *testing.T) { }, { Name: "one matching subscription infers package property without csv version", - Cache: cache.NamespacedOperatorCache{ - Snapshots: map[cache.SourceKey]*cache.CatalogSnapshot{ - catalog: { - Key: catalog, - Operators: []*cache.Operator{ - { - Name: "a", - Bundle: &api.Bundle{ - PackageName: "x", - }, + Cache: cache.StaticSourceProvider{ + catalog: &cache.Snapshot{ + Entries: []*cache.Operator{ + { + Name: "a", + Bundle: &api.Bundle{ + PackageName: "x", }, }, }, @@ -1772,10 +1652,8 @@ func TestInferProperties(t *testing.T) { require := require.New(t) logger, _ := test.NewNullLogger() r := SatResolver{ - log: logger, - cache: &FakeOperatorCache{ - fakedNamespacedOperatorCache: tc.Cache, - }, + log: logger, + cache: cache.New(tc.Cache), } actual, err := r.inferProperties(tc.CSV, tc.Subscriptions) require.NoError(err) diff --git a/pkg/controller/registry/resolver/source_registry.go b/pkg/controller/registry/resolver/source_registry.go new file mode 100644 index 00000000000..6c5c11998d5 --- /dev/null +++ b/pkg/controller/registry/resolver/source_registry.go @@ -0,0 +1,109 @@ +package resolver + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" + "github.com/operator-framework/operator-registry/pkg/api" + "github.com/operator-framework/operator-registry/pkg/client" + opregistry "github.com/operator-framework/operator-registry/pkg/registry" + "github.com/sirupsen/logrus" +) + +type RegistryClientProvider interface { + ClientsForNamespaces(namespaces ...string) map[registry.CatalogKey]client.Interface +} + +type registryClientAdapter struct { + rcp RegistryClientProvider + logger logrus.StdLogger +} + +func SourceProviderFromRegistryClientProvider(rcp RegistryClientProvider, logger logrus.StdLogger) cache.SourceProvider { + return ®istryClientAdapter{ + rcp: rcp, + logger: logger, + } +} + +type registrySource struct { + key cache.SourceKey + client client.Interface + logger logrus.StdLogger +} + +func (s *registrySource) Snapshot(ctx context.Context) (*cache.Snapshot, error) { + // Fetching default channels this way makes many round trips + // -- may need to either add a new API to fetch all at once, + // or embed the information into Bundle. + defaultChannels := make(map[string]string) + + it, err := s.client.ListBundles(ctx) + if err != nil { + return nil, fmt.Errorf("failed to list bundles: %w", err) + } + + var operators []*cache.Operator + for b := it.Next(); b != nil; b = it.Next() { + defaultChannel, ok := defaultChannels[b.PackageName] + if !ok { + if p, err := s.client.GetPackage(ctx, b.PackageName); err != nil { + s.logger.Printf("failed to retrieve default channel for bundle, continuing: %v", err) + continue + } else { + defaultChannels[b.PackageName] = p.DefaultChannelName + defaultChannel = p.DefaultChannelName + } + } + o, err := cache.NewOperatorFromBundle(b, "", s.key, defaultChannel) + if err != nil { + s.logger.Printf("failed to construct operator from bundle, continuing: %v", err) + continue + } + o.ProvidedAPIs = o.ProvidedAPIs.StripPlural() + o.RequiredAPIs = o.RequiredAPIs.StripPlural() + o.Replaces = b.Replaces + EnsurePackageProperty(o, b.PackageName, b.Version) + operators = append(operators, o) + } + if err := it.Error(); err != nil { + return nil, fmt.Errorf("error encountered while listing bundles: %w", err) + } + + return &cache.Snapshot{Entries: operators}, nil +} + +func (a *registryClientAdapter) Sources(namespaces ...string) map[cache.SourceKey]cache.Source { + result := make(map[cache.SourceKey]cache.Source) + for key, client := range a.rcp.ClientsForNamespaces(namespaces...) { + result[cache.SourceKey(key)] = ®istrySource{ + key: cache.SourceKey(key), + client: client, + logger: a.logger, + } + } + return result +} + +func EnsurePackageProperty(o *cache.Operator, name, version string) { + for _, p := range o.Properties { + if p.Type == opregistry.PackageType { + return + } + } + prop := opregistry.PackageProperty{ + PackageName: name, + Version: version, + } + bytes, err := json.Marshal(prop) + if err != nil { + return + } + o.Properties = append(o.Properties, &api.Property{ + Type: opregistry.PackageType, + Value: string(bytes), + }) +} diff --git a/pkg/controller/registry/resolver/step_resolver.go b/pkg/controller/registry/resolver/step_resolver.go index 8d2c64fa52f..7574dda57c1 100644 --- a/pkg/controller/registry/resolver/step_resolver.go +++ b/pkg/controller/registry/resolver/step_resolver.go @@ -47,7 +47,7 @@ type OperatorStepResolver struct { var _ StepResolver = &OperatorStepResolver{} func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versioned.Interface, kubeclient kubernetes.Interface, - globalCatalogNamespace string, provider cache.RegistryClientProvider, log logrus.FieldLogger) *OperatorStepResolver { + globalCatalogNamespace string, provider RegistryClientProvider, log logrus.FieldLogger) *OperatorStepResolver { return &OperatorStepResolver{ subLister: lister.OperatorsV1alpha1().SubscriptionLister(), csvLister: lister.OperatorsV1alpha1().ClusterServiceVersionLister(), @@ -55,7 +55,7 @@ func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versio client: client, kubeclient: kubeclient, globalCatalogNamespace: globalCatalogNamespace, - satResolver: NewDefaultSatResolver(cache.SourceProviderFromRegistryClientProvider(provider), lister.OperatorsV1alpha1().CatalogSourceLister(), log), + satResolver: NewDefaultSatResolver(SourceProviderFromRegistryClientProvider(provider, log), lister.OperatorsV1alpha1().CatalogSourceLister(), log), log: log, } } diff --git a/pkg/controller/registry/resolver/step_resolver_test.go b/pkg/controller/registry/resolver/step_resolver_test.go index 838cc2f7e31..77faa38b9cc 100644 --- a/pkg/controller/registry/resolver/step_resolver_test.go +++ b/pkg/controller/registry/resolver/step_resolver_test.go @@ -733,7 +733,7 @@ func TestResolver(t *testing.T) { }, bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{ catalog: { - bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil), + bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil), bundle("a.v2", "a", "alpha", "a.v1", nil, Requires1, nil, nil), bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil), bundle("b.v2", "b", "beta", "b.v1", Provides1, nil, nil, nil), @@ -825,26 +825,21 @@ func TestResolver(t *testing.T) { lister.OperatorsV1alpha1().RegisterClusterServiceVersionLister(namespace, informerFactory.Operators().V1alpha1().ClusterServiceVersions().Lister()) kClientFake := k8sfake.NewSimpleClientset() - stubSnapshot := &resolvercache.CatalogSnapshot{} - for _, bundles := range tt.bundlesByCatalog { + ssp := make(resolvercache.StaticSourceProvider) + for catalog, bundles := range tt.bundlesByCatalog { + snapshot := &resolvercache.Snapshot{} for _, bundle := range bundles { op, err := resolvercache.NewOperatorFromBundle(bundle, "", catalog, "") if err != nil { t.Fatalf("unexpected error: %v", err) } - stubSnapshot.Operators = append(stubSnapshot.Operators, op) + snapshot.Entries = append(snapshot.Entries, op) } - } - stubCache := &stubOperatorCacheProvider{ - noc: &resolvercache.NamespacedOperatorCache{ - Snapshots: map[resolvercache.SourceKey]*resolvercache.CatalogSnapshot{ - catalog: stubSnapshot, - }, - }, + ssp[catalog] = snapshot } log := logrus.New() satresolver := &SatResolver{ - cache: stubCache, + cache: resolvercache.New(ssp), log: log, } resolver := NewOperatorStepResolver(lister, clientFake, kClientFake, "", nil, log) @@ -977,23 +972,18 @@ func TestNamespaceResolverRBAC(t *testing.T) { lister.OperatorsV1alpha1().RegisterSubscriptionLister(namespace, informerFactory.Operators().V1alpha1().Subscriptions().Lister()) lister.OperatorsV1alpha1().RegisterClusterServiceVersionLister(namespace, informerFactory.Operators().V1alpha1().ClusterServiceVersions().Lister()) - stubSnapshot := &resolvercache.CatalogSnapshot{} + stubSnapshot := &resolvercache.Snapshot{} for _, bundle := range tt.bundlesInCatalog { op, err := resolvercache.NewOperatorFromBundle(bundle, "", catalog, "") if err != nil { t.Fatalf("unexpected error: %v", err) } - stubSnapshot.Operators = append(stubSnapshot.Operators, op) - } - stubCache := &stubOperatorCacheProvider{ - noc: &resolvercache.NamespacedOperatorCache{ - Snapshots: map[resolvercache.SourceKey]*resolvercache.CatalogSnapshot{ - catalog: stubSnapshot, - }, - }, + stubSnapshot.Entries = append(stubSnapshot.Entries, op) } satresolver := &SatResolver{ - cache: stubCache, + cache: resolvercache.New(resolvercache.StaticSourceProvider{ + catalog: stubSnapshot, + }), } resolver := NewOperatorStepResolver(lister, clientFake, kClientFake, "", nil, logrus.New()) resolver.satResolver = satresolver