github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/step_resolver_test.go (about)

     1  package resolver
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/sirupsen/logrus"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  	corev1 "k8s.io/api/core/v1"
    14  	rbacv1 "k8s.io/api/rbac/v1"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/labels"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  
    19  	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
    20  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    21  	"github.com/operator-framework/operator-registry/pkg/api"
    22  	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
    23  
    24  	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
    25  	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/fake"
    26  	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions"
    27  	controllerbundle "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle"
    28  	resolvercache "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
    29  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver"
    30  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
    31  	"k8s.io/client-go/tools/cache"
    32  )
    33  
    34  var (
    35  	// conventions for tests: packages are letters (a,b,c) and apis are numbers (1,2,3)
    36  
    37  	// APISets used for tests
    38  	APISet1   = resolvercache.APISet{testGVKKey: struct{}{}}
    39  	Provides1 = APISet1
    40  	Requires1 = APISet1
    41  	APISet2   = resolvercache.APISet{opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2", Plural: "k2s"}: struct{}{}}
    42  	Provides2 = APISet2
    43  	Requires2 = APISet2
    44  	APISet3   = resolvercache.APISet{opregistry.APIKey{Group: "g3", Version: "v3", Kind: "k3", Plural: "k3s"}: struct{}{}}
    45  	Provides3 = APISet3
    46  	Requires3 = APISet3
    47  	APISet4   = resolvercache.APISet{opregistry.APIKey{Group: "g4", Version: "v4", Kind: "k4", Plural: "k4s"}: struct{}{}}
    48  	Provides4 = APISet4
    49  	Requires4 = APISet4
    50  )
    51  
    52  func TestIsReplacementChainThatEndsInFailure(t *testing.T) {
    53  	type out struct {
    54  		b   bool
    55  		err error
    56  	}
    57  
    58  	tests := []struct {
    59  		name             string
    60  		csv              *v1alpha1.ClusterServiceVersion
    61  		csvToReplacement map[string]*v1alpha1.ClusterServiceVersion
    62  		expected         out
    63  	}{
    64  		{
    65  			name:             "NilCSV",
    66  			csv:              nil,
    67  			csvToReplacement: nil,
    68  			expected: out{
    69  				b:   false,
    70  				err: fmt.Errorf("csv cannot be nil"),
    71  			},
    72  		},
    73  		{
    74  			name: "OneEntryReplacementChainEndsInFailure",
    75  			csv: &v1alpha1.ClusterServiceVersion{
    76  				ObjectMeta: metav1.ObjectMeta{
    77  					Name:      "foo-v1",
    78  					Namespace: "bar",
    79  				},
    80  				Status: v1alpha1.ClusterServiceVersionStatus{
    81  					Phase: v1alpha1.CSVPhaseFailed,
    82  				},
    83  			},
    84  			csvToReplacement: nil,
    85  			expected: out{
    86  				b:   true,
    87  				err: nil,
    88  			},
    89  		},
    90  		{
    91  			name: "OneEntryReplacementChainEndsInSuccess",
    92  			csv: &v1alpha1.ClusterServiceVersion{
    93  				ObjectMeta: metav1.ObjectMeta{
    94  					Name:      "foo-v1",
    95  					Namespace: "bar",
    96  				},
    97  				Status: v1alpha1.ClusterServiceVersionStatus{
    98  					Phase: v1alpha1.CSVPhaseSucceeded,
    99  				},
   100  			},
   101  			csvToReplacement: nil,
   102  			expected: out{
   103  				b:   false,
   104  				err: nil,
   105  			},
   106  		},
   107  		{
   108  			name: "ReplacementChainEndsInSuccess",
   109  			csv: &v1alpha1.ClusterServiceVersion{
   110  				ObjectMeta: metav1.ObjectMeta{
   111  					Name:      "foo-v1",
   112  					Namespace: "bar",
   113  				},
   114  				Status: v1alpha1.ClusterServiceVersionStatus{
   115  					Phase: v1alpha1.CSVPhaseReplacing,
   116  				},
   117  			},
   118  			csvToReplacement: map[string]*v1alpha1.ClusterServiceVersion{
   119  				"foo-v1": {
   120  					ObjectMeta: metav1.ObjectMeta{
   121  						Name:      "foo-v2",
   122  						Namespace: "bar",
   123  					},
   124  					Spec: v1alpha1.ClusterServiceVersionSpec{
   125  						Replaces: "foo-v1",
   126  					},
   127  					Status: v1alpha1.ClusterServiceVersionStatus{
   128  						Phase: v1alpha1.CSVPhaseReplacing,
   129  					},
   130  				},
   131  				"foo-v2": {
   132  					ObjectMeta: metav1.ObjectMeta{
   133  						Name:      "foo-v3",
   134  						Namespace: "bar",
   135  					},
   136  					Spec: v1alpha1.ClusterServiceVersionSpec{
   137  						Replaces: "foo-v2",
   138  					},
   139  					Status: v1alpha1.ClusterServiceVersionStatus{
   140  						Phase: v1alpha1.CSVPhaseSucceeded,
   141  					},
   142  				},
   143  			},
   144  			expected: out{
   145  				b:   false,
   146  				err: nil,
   147  			},
   148  		},
   149  		{
   150  			name: "ReplacementChainEndsInFailure",
   151  			csv: &v1alpha1.ClusterServiceVersion{
   152  				ObjectMeta: metav1.ObjectMeta{
   153  					Name:      "foo-v1",
   154  					Namespace: "bar",
   155  				},
   156  				Status: v1alpha1.ClusterServiceVersionStatus{
   157  					Phase: v1alpha1.CSVPhaseReplacing,
   158  				},
   159  			},
   160  			csvToReplacement: map[string]*v1alpha1.ClusterServiceVersion{
   161  				"foo-v1": {
   162  					ObjectMeta: metav1.ObjectMeta{
   163  						Name:      "foo-v2",
   164  						Namespace: "bar",
   165  					},
   166  					Spec: v1alpha1.ClusterServiceVersionSpec{
   167  						Replaces: "foo-v1",
   168  					},
   169  					Status: v1alpha1.ClusterServiceVersionStatus{
   170  						Phase: v1alpha1.CSVPhaseReplacing,
   171  					},
   172  				},
   173  				"foo-v2": {
   174  					ObjectMeta: metav1.ObjectMeta{
   175  						Name:      "foo-v3",
   176  						Namespace: "bar",
   177  					},
   178  					Spec: v1alpha1.ClusterServiceVersionSpec{
   179  						Replaces: "foo-v2",
   180  					},
   181  					Status: v1alpha1.ClusterServiceVersionStatus{
   182  						Phase: v1alpha1.CSVPhaseFailed,
   183  					},
   184  				},
   185  			},
   186  			expected: out{
   187  				b:   true,
   188  				err: nil,
   189  			},
   190  		},
   191  		{
   192  			name: "ReplacementChainBrokenByFailedCSVInMiddle",
   193  			csv: &v1alpha1.ClusterServiceVersion{
   194  				ObjectMeta: metav1.ObjectMeta{
   195  					Name:      "foo-v1",
   196  					Namespace: "bar",
   197  				},
   198  				Status: v1alpha1.ClusterServiceVersionStatus{
   199  					Phase: v1alpha1.CSVPhaseReplacing,
   200  				},
   201  			},
   202  			csvToReplacement: map[string]*v1alpha1.ClusterServiceVersion{
   203  				"foo-v1": {
   204  					ObjectMeta: metav1.ObjectMeta{
   205  						Name:      "foo-v2",
   206  						Namespace: "bar",
   207  					},
   208  					Spec: v1alpha1.ClusterServiceVersionSpec{
   209  						Replaces: "foo-v1",
   210  					},
   211  					Status: v1alpha1.ClusterServiceVersionStatus{
   212  						Phase: v1alpha1.CSVPhaseFailed,
   213  					},
   214  				},
   215  				"foo-v2": {
   216  					ObjectMeta: metav1.ObjectMeta{
   217  						Name:      "foo-v3",
   218  						Namespace: "bar",
   219  					},
   220  					Spec: v1alpha1.ClusterServiceVersionSpec{
   221  						Replaces: "foo-v2",
   222  					},
   223  					Status: v1alpha1.ClusterServiceVersionStatus{
   224  						Phase: v1alpha1.CSVPhaseFailed,
   225  					},
   226  				},
   227  			},
   228  			expected: out{
   229  				b:   false,
   230  				err: fmt.Errorf("csv bar/foo-v2 in phase Failed instead of Replacing"),
   231  			},
   232  		},
   233  		{
   234  			name: "InfiniteLoopReplacementChain",
   235  			csv: &v1alpha1.ClusterServiceVersion{
   236  				ObjectMeta: metav1.ObjectMeta{
   237  					Name:      "foo-v1",
   238  					Namespace: "bar",
   239  				},
   240  				Status: v1alpha1.ClusterServiceVersionStatus{
   241  					Phase: v1alpha1.CSVPhaseReplacing,
   242  				},
   243  			},
   244  			csvToReplacement: map[string]*v1alpha1.ClusterServiceVersion{
   245  				"foo-v1": {
   246  					ObjectMeta: metav1.ObjectMeta{
   247  						Name:      "foo-v2",
   248  						Namespace: "bar",
   249  					},
   250  					Spec: v1alpha1.ClusterServiceVersionSpec{
   251  						Replaces: "foo-v1",
   252  					},
   253  					Status: v1alpha1.ClusterServiceVersionStatus{
   254  						Phase: v1alpha1.CSVPhaseReplacing,
   255  					},
   256  				},
   257  				"foo-v2": {
   258  					ObjectMeta: metav1.ObjectMeta{
   259  						Name:      "foo-v1",
   260  						Namespace: "bar",
   261  					},
   262  					Spec: v1alpha1.ClusterServiceVersionSpec{
   263  						Replaces: "foo-v2",
   264  					},
   265  					Status: v1alpha1.ClusterServiceVersionStatus{
   266  						Phase: v1alpha1.CSVPhaseReplacing,
   267  					},
   268  				},
   269  			},
   270  			expected: out{
   271  				b:   false,
   272  				err: fmt.Errorf("csv bar/foo-v1 has already been seen"),
   273  			},
   274  		},
   275  	}
   276  
   277  	for _, tt := range tests {
   278  		t.Run(tt.name, func(t *testing.T) {
   279  			endsInFailure, err := isReplacementChainThatEndsInFailure(tt.csv, tt.csvToReplacement)
   280  			require.Equal(t, tt.expected.b, endsInFailure)
   281  			require.Equal(t, tt.expected.err, err)
   282  		})
   283  	}
   284  }
   285  
   286  func TestInitHooks(t *testing.T) {
   287  	clientFake := fake.NewSimpleClientset()
   288  	lister := operatorlister.NewLister()
   289  	log := logrus.New()
   290  
   291  	// no init hooks
   292  	resolver := NewOperatorStepResolver(lister, clientFake, "", nil, log)
   293  	require.NotNil(t, resolver.resolver)
   294  
   295  	// with init hook
   296  	var testHook stepResolverInitHook = func(resolver *OperatorStepResolver) error {
   297  		resolver.resolver = nil
   298  		return nil
   299  	}
   300  
   301  	// defined in step_resolver.go
   302  	initHooks = append(initHooks, testHook)
   303  	defer func() {
   304  		// reset initHooks
   305  		initHooks = nil
   306  	}()
   307  
   308  	resolver = NewOperatorStepResolver(lister, clientFake, "", nil, log)
   309  	require.Nil(t, resolver.resolver)
   310  }
   311  
   312  func TestResolver(t *testing.T) {
   313  	const namespace = "catsrc-namespace"
   314  	catalog := resolvercache.SourceKey{Name: "catsrc", Namespace: namespace}
   315  
   316  	type resolverTestOut struct {
   317  		steps       [][]*v1alpha1.Step
   318  		lookups     []v1alpha1.BundleLookup
   319  		subs        []*v1alpha1.Subscription
   320  		errAssert   func(*testing.T, error)
   321  		solverError solver.NotSatisfiable
   322  	}
   323  	type resolverTest struct {
   324  		name             string
   325  		clusterState     []runtime.Object
   326  		bundlesByCatalog map[resolvercache.SourceKey][]*api.Bundle
   327  		out              resolverTestOut
   328  	}
   329  
   330  	nothing := resolverTestOut{
   331  		steps:   [][]*v1alpha1.Step{},
   332  		lookups: []v1alpha1.BundleLookup{},
   333  		subs:    []*v1alpha1.Subscription{},
   334  	}
   335  	tests := []resolverTest{
   336  		{
   337  			name: "SubscriptionOmitsChannel",
   338  			clusterState: []runtime.Object{
   339  				newSub(namespace, "package", "", catalog),
   340  				newOperatorGroup("foo", namespace),
   341  			},
   342  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   343  				catalog: {
   344  					bundle("bundle", "package", "channel", "", nil, nil, nil, nil),
   345  				},
   346  			},
   347  			out: resolverTestOut{
   348  				steps: [][]*v1alpha1.Step{
   349  					bundleSteps(bundle("bundle", "package", "channel", "", nil, nil, nil, nil), namespace, "", catalog),
   350  				},
   351  				subs: []*v1alpha1.Subscription{
   352  					updatedSub(namespace, "bundle", "", "package", "", catalog),
   353  				},
   354  			},
   355  		},
   356  		{
   357  			name: "SubscriptionWithNoCandidates/Error",
   358  			clusterState: []runtime.Object{
   359  				newSub(namespace, "a", "alpha", catalog),
   360  				newOperatorGroup("foo", namespace),
   361  			},
   362  			out: resolverTestOut{
   363  				solverError: solver.NotSatisfiable{
   364  					{
   365  						Variable:   NewSubscriptionVariable("a", nil),
   366  						Constraint: PrettyConstraint(solver.Mandatory(), "subscription a-alpha exists"),
   367  					},
   368  					{
   369  						Variable:   NewSubscriptionVariable("a", nil),
   370  						Constraint: PrettyConstraint(solver.Dependency(), "no operators found from catalog catsrc in namespace catsrc-namespace referenced by subscription a-alpha"),
   371  					},
   372  				},
   373  			},
   374  		},
   375  		{
   376  			name: "SubscriptionWithNoCandidatesInPackage/Error",
   377  			clusterState: []runtime.Object{
   378  				newSub(namespace, "a", "alpha", catalog),
   379  				newOperatorGroup("foo", namespace),
   380  			},
   381  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   382  				catalog: {
   383  					bundle("bundle", "package", "channel", "", nil, nil, nil, nil),
   384  				},
   385  			},
   386  			out: resolverTestOut{
   387  				solverError: solver.NotSatisfiable{
   388  					{
   389  						Variable:   NewSubscriptionVariable("a", nil),
   390  						Constraint: PrettyConstraint(solver.Mandatory(), "subscription a-alpha exists"),
   391  					},
   392  					{
   393  						Variable:   NewSubscriptionVariable("a", nil),
   394  						Constraint: PrettyConstraint(solver.Dependency(), "no operators found in package a in the catalog referenced by subscription a-alpha"),
   395  					},
   396  				},
   397  			},
   398  		},
   399  		{
   400  			name: "SubscriptionWithNoCandidatesInChannel/Error",
   401  			clusterState: []runtime.Object{
   402  				newSub(namespace, "a", "alpha", catalog),
   403  				newOperatorGroup("foo", namespace),
   404  			},
   405  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   406  				catalog: {
   407  					bundle("bundle", "a", "channel", "", nil, nil, nil, nil),
   408  				},
   409  			},
   410  			out: resolverTestOut{
   411  				solverError: solver.NotSatisfiable{
   412  					{
   413  						Variable:   NewSubscriptionVariable("a", nil),
   414  						Constraint: PrettyConstraint(solver.Mandatory(), "subscription a-alpha exists"),
   415  					},
   416  					{
   417  						Variable:   NewSubscriptionVariable("a", nil),
   418  						Constraint: PrettyConstraint(solver.Dependency(), "no operators found in channel alpha of package a in the catalog referenced by subscription a-alpha"),
   419  					},
   420  				},
   421  			},
   422  		},
   423  		{
   424  			name: "SubscriptionWithNoCandidatesWithStartingCSVName/Error",
   425  			clusterState: []runtime.Object{
   426  				newSub(namespace, "a", "alpha", catalog, withStartingCSV("notfound")),
   427  				newOperatorGroup("foo", namespace),
   428  			},
   429  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   430  				catalog: {
   431  					bundle("bundle", "a", "alpha", "", nil, nil, nil, nil),
   432  				},
   433  			},
   434  			out: resolverTestOut{
   435  				solverError: solver.NotSatisfiable{
   436  					{
   437  						Variable:   NewSubscriptionVariable("a", nil),
   438  						Constraint: PrettyConstraint(solver.Mandatory(), "subscription a-alpha exists"),
   439  					},
   440  					{
   441  						Variable:   NewSubscriptionVariable("a", nil),
   442  						Constraint: PrettyConstraint(solver.Dependency(), "no operators found with name notfound in channel alpha of package a in the catalog referenced by subscription a-alpha"),
   443  					},
   444  				},
   445  			},
   446  		},
   447  		{
   448  			name: "SingleNewSubscription/NoDeps",
   449  			clusterState: []runtime.Object{
   450  				newSub(namespace, "a", "alpha", catalog),
   451  				newOperatorGroup("foo", namespace),
   452  			},
   453  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   454  				catalog: {
   455  					bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil),
   456  				},
   457  			},
   458  			out: resolverTestOut{
   459  				steps: [][]*v1alpha1.Step{
   460  					bundleSteps(bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil), namespace, "", catalog),
   461  				},
   462  				subs: []*v1alpha1.Subscription{
   463  					updatedSub(namespace, "a.v1", "", "a", "alpha", catalog),
   464  				},
   465  			},
   466  		},
   467  		{
   468  			name: "SingleNewSubscription/ResolveOne",
   469  			clusterState: []runtime.Object{
   470  				newSub(namespace, "a", "alpha", catalog),
   471  				newOperatorGroup("foo", namespace),
   472  			},
   473  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   474  				catalog: {
   475  					bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil),
   476  					bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
   477  				},
   478  			},
   479  			out: resolverTestOut{
   480  				steps: [][]*v1alpha1.Step{
   481  					bundleSteps(bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil), namespace, "", catalog),
   482  					bundleSteps(bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil), namespace, "", catalog),
   483  					subSteps(namespace, "b.v1", "b", "beta", catalog),
   484  				},
   485  				subs: []*v1alpha1.Subscription{
   486  					updatedSub(namespace, "a.v1", "", "a", "alpha", catalog),
   487  				},
   488  			},
   489  		},
   490  		{
   491  			name: "SingleNewSubscription/ResolveOne/BundlePath",
   492  			clusterState: []runtime.Object{
   493  				newSub(namespace, "a", "alpha", catalog),
   494  				newOperatorGroup("foo", namespace),
   495  			},
   496  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   497  				catalog: {
   498  					bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
   499  					stripManifests(withBundlePath(bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil), "quay.io/test/bundle@sha256:abcd")),
   500  				},
   501  			},
   502  			out: resolverTestOut{
   503  				steps: [][]*v1alpha1.Step{
   504  					bundleSteps(bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil), namespace, "", catalog),
   505  					subSteps(namespace, "b.v1", "b", "beta", catalog),
   506  				},
   507  				lookups: []v1alpha1.BundleLookup{
   508  					{
   509  						Path:       "quay.io/test/bundle@sha256:abcd",
   510  						Identifier: "b.v1",
   511  						Properties: `{"properties":[{"type":"olm.gvk","value":{"group":"g","kind":"k","version":"v"}},{"type":"olm.package","value":{"packageName":"b","version":"0.0.0"}}]}`,
   512  						CatalogSourceRef: &corev1.ObjectReference{
   513  							Namespace: catalog.Namespace,
   514  							Name:      catalog.Name,
   515  						},
   516  						Conditions: []v1alpha1.BundleLookupCondition{
   517  							{
   518  								Type:    BundleLookupConditionPacked,
   519  								Status:  corev1.ConditionTrue,
   520  								Reason:  controllerbundle.NotUnpackedReason,
   521  								Message: controllerbundle.NotUnpackedMessage,
   522  							},
   523  							{
   524  								Type:    v1alpha1.BundleLookupPending,
   525  								Status:  corev1.ConditionTrue,
   526  								Reason:  controllerbundle.JobNotStartedReason,
   527  								Message: controllerbundle.JobNotStartedMessage,
   528  							},
   529  						},
   530  					},
   531  				},
   532  				subs: []*v1alpha1.Subscription{
   533  					updatedSub(namespace, "a.v1", "", "a", "alpha", catalog),
   534  				},
   535  			},
   536  		},
   537  		{
   538  			name: "SingleNewSubscription/ResolveOne/AdditionalBundleObjects",
   539  			clusterState: []runtime.Object{
   540  				newSub(namespace, "a", "alpha", catalog),
   541  				newOperatorGroup("foo", namespace),
   542  			},
   543  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   544  				catalog: {
   545  					withBundleObject(bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil), u(&rbacv1.RoleBinding{TypeMeta: metav1.TypeMeta{Kind: "RoleBinding", APIVersion: "rbac.authorization.k8s.io/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "test-rb"}})),
   546  					bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
   547  				},
   548  			},
   549  			out: resolverTestOut{
   550  				steps: [][]*v1alpha1.Step{
   551  					bundleSteps(bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil), namespace, "", catalog),
   552  					bundleSteps(withBundleObject(bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil), u(&rbacv1.RoleBinding{TypeMeta: metav1.TypeMeta{Kind: "RoleBinding", APIVersion: "rbac.authorization.k8s.io/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "test-rb"}})), namespace, "", catalog),
   553  					subSteps(namespace, "b.v1", "b", "beta", catalog),
   554  				},
   555  				subs: []*v1alpha1.Subscription{
   556  					updatedSub(namespace, "a.v1", "", "a", "alpha", catalog),
   557  				},
   558  			},
   559  		},
   560  		{
   561  			name: "SingleNewSubscription/ResolveOne/AdditionalBundleObjects/Service",
   562  			clusterState: []runtime.Object{
   563  				newSub(namespace, "a", "alpha", catalog),
   564  				newOperatorGroup("foo", namespace),
   565  			},
   566  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   567  				catalog: {
   568  					withBundleObject(bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil), u(&corev1.Service{TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: ""}, ObjectMeta: metav1.ObjectMeta{Name: "test-service"}})),
   569  					bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
   570  				},
   571  			},
   572  			out: resolverTestOut{
   573  				steps: [][]*v1alpha1.Step{
   574  					bundleSteps(bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil), namespace, "", catalog),
   575  					bundleSteps(withBundleObject(bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil), u(&corev1.Service{TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: ""}, ObjectMeta: metav1.ObjectMeta{Name: "test-service"}})), namespace, "", catalog),
   576  					subSteps(namespace, "b.v1", "b", "beta", catalog),
   577  				},
   578  				subs: []*v1alpha1.Subscription{
   579  					updatedSub(namespace, "a.v1", "", "a", "alpha", catalog),
   580  				},
   581  			},
   582  		},
   583  		{
   584  			name: "SingleNewSubscription/DependencyMissing",
   585  			clusterState: []runtime.Object{
   586  				newSub(namespace, "a", "alpha", catalog),
   587  				newOperatorGroup("foo", namespace),
   588  			},
   589  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   590  				catalog: {
   591  					bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
   592  				},
   593  			},
   594  			out: resolverTestOut{
   595  				steps:   [][]*v1alpha1.Step{},
   596  				lookups: []v1alpha1.BundleLookup{},
   597  				subs:    []*v1alpha1.Subscription{},
   598  				solverError: solver.NotSatisfiable([]solver.AppliedConstraint{
   599  					{
   600  						Variable:   NewSubscriptionVariable("a", []solver.Identifier{"catsrc/catsrc-namespace/alpha/a.v1"}),
   601  						Constraint: PrettyConstraint(solver.Dependency("catsrc/catsrc-namespace/alpha/a.v1"), "subscription a-alpha requires catsrc/catsrc-namespace/alpha/a.v1"),
   602  					},
   603  					{
   604  						Variable: &BundleVariable{
   605  							identifier:  "catsrc/catsrc-namespace/alpha/a.v1",
   606  							constraints: []solver.Constraint{solver.Dependency()},
   607  						},
   608  						Constraint: PrettyConstraint(solver.Dependency(), "bundle a.v1 requires an operator providing an API with group: g, version: v, kind: k"),
   609  					},
   610  					{
   611  						Variable:   NewSubscriptionVariable("a", []solver.Identifier{"catsrc/catsrc-namespace/alpha/a.v1"}),
   612  						Constraint: PrettyConstraint(solver.Mandatory(), "subscription a-alpha exists"),
   613  					},
   614  				}),
   615  			},
   616  		},
   617  		{
   618  			name: "InstalledSub/NoUpdates",
   619  			clusterState: []runtime.Object{
   620  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
   621  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   622  				newOperatorGroup("foo", namespace),
   623  			},
   624  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   625  				catalog: {
   626  					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   627  				},
   628  			},
   629  			out: nothing,
   630  		},
   631  		{
   632  			name: "SecondSubscriptionConflictsWithExistingResolvedSubscription",
   633  			clusterState: []runtime.Object{
   634  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
   635  				existingSub(namespace, "b.v1", "b", "alpha", catalog),
   636  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   637  				newOperatorGroup("foo", namespace),
   638  			},
   639  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   640  				catalog: {
   641  					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   642  					bundle("b.v1", "b", "alpha", "", Provides1, nil, nil, nil),
   643  				},
   644  			},
   645  			out: resolverTestOut{
   646  				errAssert: func(t *testing.T, err error) {
   647  					assert.IsType(t, solver.NotSatisfiable{}, err)
   648  				},
   649  			},
   650  		},
   651  		{
   652  			name: "ConflictingSubscriptionsToSamePackage",
   653  			clusterState: []runtime.Object{
   654  				newSub(namespace, "a", "alpha", catalog),
   655  				newSub(namespace, "a", "beta", catalog),
   656  				newOperatorGroup("foo", namespace),
   657  			},
   658  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   659  				catalog: {
   660  					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   661  					bundle("a.v2", "a", "beta", "", Provides1, nil, nil, nil),
   662  				},
   663  			},
   664  			out: resolverTestOut{
   665  				errAssert: func(t *testing.T, err error) {
   666  					fmt.Println(err)
   667  					assert.IsType(t, solver.NotSatisfiable{}, err)
   668  				},
   669  			},
   670  		},
   671  		{
   672  			// No two operators from the same package may run at the same time, but it's possible to have two
   673  			// subscriptions to the same package as long as it's possible to find a bundle that satisfies both
   674  			// constraints
   675  			name: "SatisfiableSubscriptionsToSamePackage",
   676  			clusterState: []runtime.Object{
   677  				newSub(namespace, "a", "alpha", catalog),
   678  				func() (s *v1alpha1.Subscription) {
   679  					s = newSub(namespace, "a", "alpha", catalog)
   680  					s.Name = s.Name + "-2"
   681  					return
   682  				}(),
   683  				newOperatorGroup("foo", namespace),
   684  			},
   685  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   686  				catalog: {
   687  					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   688  				},
   689  			},
   690  			out: resolverTestOut{
   691  				steps: [][]*v1alpha1.Step{
   692  					bundleSteps(bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil), namespace, "", catalog),
   693  				},
   694  				subs: []*v1alpha1.Subscription{
   695  					updatedSub(namespace, "a.v1", "", "a", "alpha", catalog),
   696  					func() (s *v1alpha1.Subscription) {
   697  						s = updatedSub(namespace, "a.v1", "", "a", "alpha", catalog)
   698  						s.Name = s.Name + "-2"
   699  						return
   700  					}(),
   701  				},
   702  			},
   703  		},
   704  		{
   705  			name: "TwoExistingOperatorsWithSameName/NoError",
   706  			clusterState: []runtime.Object{
   707  				existingOperator("ns1", "a.v1", "a", "alpha", "", nil, nil, nil, nil),
   708  				existingOperator("ns2", "a.v1", "a", "alpha", "", nil, nil, nil, nil),
   709  				newOperatorGroup("foo", namespace),
   710  			},
   711  			out: nothing,
   712  		},
   713  		{
   714  			name: "InstalledSub/UpdateAvailable",
   715  			clusterState: []runtime.Object{
   716  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
   717  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   718  				newOperatorGroup("foo", namespace),
   719  			},
   720  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   721  				catalog: {
   722  					bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil),
   723  					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   724  				},
   725  			},
   726  			out: resolverTestOut{
   727  				steps: [][]*v1alpha1.Step{
   728  					bundleSteps(bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil), namespace, "", catalog),
   729  				},
   730  				subs: []*v1alpha1.Subscription{
   731  					updatedSub(namespace, "a.v2", "a.v1", "a", "alpha", catalog),
   732  				},
   733  			},
   734  		},
   735  		{
   736  			name: "InstalledSub/UpdateAvailable/FromBundlePath",
   737  			clusterState: []runtime.Object{
   738  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
   739  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   740  				newOperatorGroup("foo", namespace),
   741  			},
   742  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
   743  				stripManifests(withBundlePath(bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil), "quay.io/test/bundle@sha256:abcd"))},
   744  			},
   745  			out: resolverTestOut{
   746  				steps: [][]*v1alpha1.Step{},
   747  				lookups: []v1alpha1.BundleLookup{
   748  					{
   749  						Path:       "quay.io/test/bundle@sha256:abcd",
   750  						Identifier: "a.v2",
   751  						Replaces:   "a.v1",
   752  						Properties: `{"properties":[{"type":"olm.gvk","value":{"group":"g","kind":"k","version":"v"}},{"type":"olm.package","value":{"packageName":"a","version":"0.0.0"}}]}`,
   753  						CatalogSourceRef: &corev1.ObjectReference{
   754  							Namespace: catalog.Namespace,
   755  							Name:      catalog.Name,
   756  						},
   757  						Conditions: []v1alpha1.BundleLookupCondition{
   758  							{
   759  								Type:    BundleLookupConditionPacked,
   760  								Status:  corev1.ConditionTrue,
   761  								Reason:  controllerbundle.NotUnpackedReason,
   762  								Message: controllerbundle.NotUnpackedMessage,
   763  							},
   764  							{
   765  								Type:    v1alpha1.BundleLookupPending,
   766  								Status:  corev1.ConditionTrue,
   767  								Reason:  controllerbundle.JobNotStartedReason,
   768  								Message: controllerbundle.JobNotStartedMessage,
   769  							},
   770  						},
   771  					},
   772  				},
   773  				subs: []*v1alpha1.Subscription{
   774  					updatedSub(namespace, "a.v2", "a.v1", "a", "alpha", catalog),
   775  				},
   776  			},
   777  		},
   778  		{
   779  			name: "InstalledSub/NoRunningOperator",
   780  			clusterState: []runtime.Object{
   781  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
   782  				newOperatorGroup("foo", namespace),
   783  			},
   784  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   785  				catalog: {
   786  					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   787  				},
   788  			},
   789  			out: resolverTestOut{
   790  				steps: [][]*v1alpha1.Step{
   791  					bundleSteps(bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil), namespace, "", catalog),
   792  				},
   793  				// no updated subs because existingSub already points the right CSV, it just didn't exist for some reason
   794  				subs: []*v1alpha1.Subscription{},
   795  			},
   796  		},
   797  		{
   798  			name: "InstalledSub/UpdateFound/UpdateRequires/ResolveOne",
   799  			clusterState: []runtime.Object{
   800  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
   801  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   802  				newOperatorGroup("foo", namespace),
   803  			},
   804  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   805  				catalog: {
   806  					bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil),
   807  					bundle("a.v2", "a", "alpha", "a.v1", nil, Requires1, nil, nil),
   808  					bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil),
   809  				},
   810  			},
   811  			out: resolverTestOut{
   812  				steps: [][]*v1alpha1.Step{
   813  					bundleSteps(bundle("a.v2", "a", "alpha", "a.v1", nil, Requires1, nil, nil), namespace, "", catalog),
   814  					bundleSteps(bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil), namespace, "", catalog),
   815  					subSteps(namespace, "b.v1", "b", "beta", catalog),
   816  				},
   817  				subs: []*v1alpha1.Subscription{
   818  					updatedSub(namespace, "a.v2", "a.v1", "a", "alpha", catalog),
   819  				},
   820  			},
   821  		},
   822  		{
   823  			name: "InstalledSub/UpdateFound/UpdateRequires/ResolveOne/APIServer",
   824  			clusterState: []runtime.Object{
   825  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
   826  				existingOperator(namespace, "a.v1", "a", "alpha", "", nil, nil, Provides1, nil),
   827  				newOperatorGroup("foo", namespace),
   828  			},
   829  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   830  				catalog: {
   831  					bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil),
   832  					bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, Requires1),
   833  					bundle("b.v1", "b", "beta", "", nil, nil, Provides1, nil),
   834  				},
   835  			},
   836  			out: resolverTestOut{
   837  				steps: [][]*v1alpha1.Step{
   838  					bundleSteps(bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, Requires1), namespace, "", catalog),
   839  					bundleSteps(bundle("b.v1", "b", "beta", "", nil, nil, Provides1, nil), namespace, "", catalog),
   840  					subSteps(namespace, "b.v1", "b", "beta", catalog),
   841  				},
   842  				subs: []*v1alpha1.Subscription{
   843  					updatedSub(namespace, "a.v2", "a.v1", "a", "alpha", catalog),
   844  				},
   845  			},
   846  		},
   847  		{
   848  			name: "InstalledSub/SingleNewSubscription/UpdateAvailable/ResolveOne",
   849  			clusterState: []runtime.Object{
   850  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
   851  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   852  				newSub(namespace, "b", "beta", catalog),
   853  				newOperatorGroup("foo", namespace),
   854  			},
   855  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   856  				catalog: {
   857  					bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil),
   858  					bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, nil),
   859  					bundle("b.v1", "b", "beta", "", nil, nil, nil, nil),
   860  				},
   861  			},
   862  			out: resolverTestOut{
   863  				steps: [][]*v1alpha1.Step{
   864  					bundleSteps(bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, nil), namespace, "", catalog),
   865  					bundleSteps(bundle("b.v1", "b", "beta", "", nil, nil, nil, nil), namespace, "", catalog),
   866  				},
   867  				subs: []*v1alpha1.Subscription{
   868  					updatedSub(namespace, "a.v2", "a.v1", "a", "alpha", catalog),
   869  					updatedSub(namespace, "b.v1", "", "b", "beta", catalog),
   870  				},
   871  			},
   872  		},
   873  		{
   874  			name: "InstalledSub/SingleNewSubscription/NoRunningOperator/ResolveOne",
   875  			clusterState: []runtime.Object{
   876  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
   877  				newSub(namespace, "b", "beta", catalog),
   878  				newOperatorGroup("foo", namespace),
   879  			},
   880  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   881  				catalog: {
   882  					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   883  					bundle("b.v1", "b", "beta", "", nil, nil, nil, nil),
   884  				},
   885  			},
   886  			out: resolverTestOut{
   887  				steps: [][]*v1alpha1.Step{
   888  					bundleSteps(bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil), namespace, "", catalog),
   889  					bundleSteps(bundle("b.v1", "b", "beta", "", nil, nil, nil, nil), namespace, "", catalog),
   890  				},
   891  				subs: []*v1alpha1.Subscription{
   892  					updatedSub(namespace, "b.v1", "", "b", "beta", catalog),
   893  				},
   894  			},
   895  		},
   896  		{
   897  			name: "InstalledSub/SingleNewSubscription/NoRunningOperator/ResolveOne/APIServer",
   898  			clusterState: []runtime.Object{
   899  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
   900  				newSub(namespace, "b", "beta", catalog),
   901  				newOperatorGroup("foo", namespace),
   902  			},
   903  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   904  				catalog: {
   905  					bundle("a.v1", "a", "alpha", "", nil, nil, Provides1, nil),
   906  					bundle("b.v1", "b", "beta", "", nil, nil, nil, nil),
   907  				},
   908  			},
   909  			out: resolverTestOut{
   910  				steps: [][]*v1alpha1.Step{
   911  					bundleSteps(bundle("a.v1", "a", "alpha", "", nil, nil, Provides1, nil), namespace, "", catalog),
   912  					bundleSteps(bundle("b.v1", "b", "beta", "", nil, nil, nil, nil), namespace, "", catalog),
   913  				},
   914  				subs: []*v1alpha1.Subscription{
   915  					updatedSub(namespace, "b.v1", "", "b", "beta", catalog),
   916  				},
   917  			},
   918  		},
   919  		{
   920  			// This test verifies that version deadlock that could happen with the previous algorithm can't happen here
   921  			name: "NoMoreVersionDeadlock",
   922  			clusterState: []runtime.Object{
   923  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
   924  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, Requires2, nil, nil),
   925  				existingSub(namespace, "b.v1", "b", "alpha", catalog),
   926  				existingOperator(namespace, "b.v1", "b", "alpha", "", Provides2, Requires1, nil, nil),
   927  				newOperatorGroup("foo", namespace),
   928  			},
   929  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   930  				catalog: {
   931  					bundle("a.v2", "a", "alpha", "a.v1", Provides3, Requires4, nil, nil),
   932  					bundle("b.v2", "b", "alpha", "b.v1", Provides4, Requires3, nil, nil),
   933  				},
   934  			},
   935  			out: resolverTestOut{
   936  				steps: [][]*v1alpha1.Step{
   937  					bundleSteps(bundle("a.v2", "a", "alpha", "a.v1", Provides3, Requires4, nil, nil), namespace, "", catalog),
   938  					bundleSteps(bundle("b.v2", "b", "alpha", "b.v1", Provides4, Requires3, nil, nil), namespace, "", catalog),
   939  				},
   940  				subs: []*v1alpha1.Subscription{
   941  					updatedSub(namespace, "a.v2", "a.v1", "a", "alpha", catalog),
   942  					updatedSub(namespace, "b.v2", "b.v1", "b", "alpha", catalog),
   943  				},
   944  			},
   945  		},
   946  		{
   947  			// This test verifies that ownership of an api can be migrated between two operators
   948  			name: "OwnedAPITransfer",
   949  			clusterState: []runtime.Object{
   950  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
   951  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   952  				existingSub(namespace, "b.v1", "b", "alpha", catalog),
   953  				existingOperator(namespace, "b.v1", "b", "alpha", "", nil, Requires1, nil, nil),
   954  				newOperatorGroup("foo", namespace),
   955  			},
   956  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   957  				catalog: {
   958  					bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, nil),
   959  					bundle("b.v2", "b", "alpha", "b.v1", Provides1, nil, nil, nil),
   960  				},
   961  			},
   962  			out: resolverTestOut{
   963  				steps: [][]*v1alpha1.Step{
   964  					bundleSteps(bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, nil), namespace, "", catalog),
   965  					bundleSteps(bundle("b.v2", "b", "alpha", "b.v1", Provides1, nil, nil, nil), namespace, "", catalog),
   966  				},
   967  				subs: []*v1alpha1.Subscription{
   968  					updatedSub(namespace, "a.v2", "a.v1", "a", "alpha", catalog),
   969  					updatedSub(namespace, "b.v2", "b.v1", "b", "alpha", catalog),
   970  				},
   971  			},
   972  		},
   973  		{
   974  			name: "PicksOlderProvider",
   975  			clusterState: []runtime.Object{
   976  				newSub(namespace, "b", "alpha", catalog),
   977  				newOperatorGroup("foo", namespace),
   978  			},
   979  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
   980  				catalog: {
   981  					bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
   982  					bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, nil),
   983  					bundle("b.v1", "b", "alpha", "", nil, Requires1, nil, nil),
   984  				},
   985  			},
   986  			out: resolverTestOut{
   987  				steps: [][]*v1alpha1.Step{
   988  					bundleSteps(bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil), namespace, "", catalog),
   989  					bundleSteps(bundle("b.v1", "b", "alpha", "", nil, Requires1, nil, nil), namespace, "", catalog),
   990  					subSteps(namespace, "a.v1", "a", "alpha", catalog),
   991  				},
   992  				subs: []*v1alpha1.Subscription{
   993  					updatedSub(namespace, "b.v1", "", "b", "alpha", catalog),
   994  				},
   995  			},
   996  		},
   997  		{
   998  			name: "InstalledSub/UpdateInHead/SkipRange",
   999  			clusterState: []runtime.Object{
  1000  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
  1001  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
  1002  				newOperatorGroup("foo", namespace),
  1003  			},
  1004  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
  1005  				bundle("a.v3", "a", "alpha", "a.v2", nil, nil, nil, nil, withVersion("1.0.0"), withSkipRange("< 1.0.0")),
  1006  			}},
  1007  			out: resolverTestOut{
  1008  				steps: [][]*v1alpha1.Step{
  1009  					bundleSteps(bundle("a.v3", "a", "alpha", "a.v2", nil, nil, nil, nil, withVersion("1.0.0"), withSkipRange("< 1.0.0")), namespace, "a.v1", catalog),
  1010  				},
  1011  				subs: []*v1alpha1.Subscription{
  1012  					updatedSub(namespace, "a.v3", "a.v1", "a", "alpha", catalog),
  1013  				},
  1014  			},
  1015  		},
  1016  		{
  1017  			name: "InstalledSubs/ExistingOperators/OldCSVsReplaced",
  1018  			clusterState: []runtime.Object{
  1019  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
  1020  				existingSub(namespace, "b.v1", "b", "beta", catalog),
  1021  				existingOperator(namespace, "a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
  1022  				existingOperator(namespace, "b.v1", "b", "beta", "", Provides1, nil, nil, nil),
  1023  				newOperatorGroup("foo", namespace),
  1024  			},
  1025  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{
  1026  				catalog: {
  1027  					bundle("a.v1", "a", "alpha", "", nil, Requires1, nil, nil),
  1028  					bundle("a.v2", "a", "alpha", "a.v1", nil, Requires1, nil, nil),
  1029  					bundle("b.v1", "b", "beta", "", Provides1, nil, nil, nil),
  1030  					bundle("b.v2", "b", "beta", "b.v1", Provides1, nil, nil, nil),
  1031  				},
  1032  			},
  1033  			out: resolverTestOut{
  1034  				steps: [][]*v1alpha1.Step{
  1035  					bundleSteps(bundle("a.v2", "a", "alpha", "a.v1", nil, Requires1, nil, nil), namespace, "", catalog),
  1036  					bundleSteps(bundle("b.v2", "b", "beta", "b.v1", Provides1, nil, nil, nil), namespace, "", catalog),
  1037  				},
  1038  				subs: []*v1alpha1.Subscription{
  1039  					updatedSub(namespace, "a.v2", "a.v1", "a", "alpha", catalog),
  1040  					updatedSub(namespace, "b.v2", "b.v1", "b", "beta", catalog),
  1041  				},
  1042  			},
  1043  		},
  1044  		{
  1045  			name: "InstalledSub/UpdatesAvailable/SkipRangeNotInHead",
  1046  			clusterState: []runtime.Object{
  1047  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
  1048  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
  1049  				newOperatorGroup("foo", namespace),
  1050  			},
  1051  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
  1052  				bundle("a.v2", "a", "alpha", "", nil, nil, nil, nil, withVersion("1.0.0"), withSkipRange("< 1.0.0")),
  1053  				bundle("a.v3", "a", "alpha", "a.v2", nil, nil, nil, nil, withVersion("1.0.0"), withSkipRange("< 1.0.0")),
  1054  				bundle("a.v4", "a", "alpha", "a.v3", nil, nil, nil, nil, withVersion("1.0.0"), withSkipRange("< 1.0.0 !0.0.0")),
  1055  			}},
  1056  			out: resolverTestOut{
  1057  				steps: [][]*v1alpha1.Step{
  1058  					bundleSteps(bundle("a.v3", "a", "alpha", "", nil, nil, nil, nil, withVersion("1.0.0"), withSkipRange("< 1.0.0")), namespace, "a.v1", catalog),
  1059  				},
  1060  				subs: []*v1alpha1.Subscription{
  1061  					updatedSub(namespace, "a.v3", "a.v1", "a", "alpha", catalog),
  1062  				},
  1063  			},
  1064  		},
  1065  		{
  1066  			name: "NewSub/StartingCSV",
  1067  			clusterState: []runtime.Object{
  1068  				newSub(namespace, "a", "alpha", catalog, withStartingCSV("a.v2")),
  1069  				newOperatorGroup("foo", namespace),
  1070  			},
  1071  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
  1072  				bundle("a.v1", "a", "alpha", "", nil, nil, nil, nil),
  1073  				bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, nil),
  1074  				bundle("a.v3", "a", "alpha", "a.v2", nil, nil, nil, nil, withVersion("1.0.0"), withSkipRange("< 1.0.0")),
  1075  			}},
  1076  			out: resolverTestOut{
  1077  				steps: [][]*v1alpha1.Step{
  1078  					bundleSteps(bundle("a.v2", "a", "alpha", "a.v1", nil, nil, nil, nil), namespace, "a.v1", catalog),
  1079  				},
  1080  				subs: []*v1alpha1.Subscription{
  1081  					updatedSub(namespace, "a.v2", "", "a", "alpha", catalog, withStartingCSV("a.v2")),
  1082  				},
  1083  			},
  1084  		},
  1085  		{
  1086  			name: "InstalledSub/UpdatesAvailable/SpecifiedSkips",
  1087  			clusterState: []runtime.Object{
  1088  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
  1089  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil),
  1090  				newOperatorGroup("foo", namespace),
  1091  			},
  1092  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
  1093  				bundle("a.v2", "a", "alpha", "", nil, nil, nil, nil, withVersion("1.0.0"), withSkips([]string{"a.v1"})),
  1094  				bundle("a.v3", "a", "alpha", "a.v2", nil, nil, nil, nil, withVersion("1.0.0"), withSkips([]string{"a.v1"})),
  1095  			}},
  1096  			out: resolverTestOut{
  1097  				steps: [][]*v1alpha1.Step{
  1098  					bundleSteps(bundle("a.v3", "a", "alpha", "", nil, nil, nil, nil, withVersion("1.0.0")), namespace, "a.v1", catalog),
  1099  				},
  1100  				subs: []*v1alpha1.Subscription{
  1101  					updatedSub(namespace, "a.v3", "a.v1", "a", "alpha", catalog),
  1102  				},
  1103  			},
  1104  		},
  1105  		{
  1106  			name: "FailForwardDisabled/2EntryReplacementChain/NotSatisfiable",
  1107  			clusterState: []runtime.Object{
  1108  				existingSub(namespace, "a.v2", "a", "alpha", catalog),
  1109  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)),
  1110  				existingOperator(namespace, "a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)),
  1111  				newOperatorGroup("foo", namespace),
  1112  			},
  1113  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
  1114  				bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")),
  1115  				bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withVersion("2.0.0")),
  1116  				bundle("a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withVersion("3.0.0")),
  1117  			}},
  1118  			out: resolverTestOut{
  1119  				steps: [][]*v1alpha1.Step{},
  1120  				subs:  []*v1alpha1.Subscription{},
  1121  				errAssert: func(t *testing.T, err error) {
  1122  					assert.IsType(t, solver.NotSatisfiable{}, err)
  1123  					assert.Contains(t, err.Error(), "constraints not satisfiable")
  1124  					assert.Contains(t, err.Error(), "provide k (g/v)")
  1125  					assert.Contains(t, err.Error(), "clusterserviceversion a.v1 exists and is not referenced by a subscription")
  1126  					assert.Contains(t, err.Error(), "subscription a-alpha requires at least one of catsrc/catsrc-namespace/alpha/a.v3 or @existing/catsrc-namespace//a.v2")
  1127  				},
  1128  			},
  1129  		},
  1130  		{
  1131  			name: "FailForwardEnabled/2EntryReplacementChain/Satisfiable",
  1132  			clusterState: []runtime.Object{
  1133  				existingSub(namespace, "a.v2", "a", "alpha", catalog),
  1134  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)),
  1135  				existingOperator(namespace, "a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)),
  1136  				newOperatorGroup("foo", namespace, withUpgradeStrategy(operatorsv1.UpgradeStrategyUnsafeFailForward)),
  1137  			},
  1138  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
  1139  				bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")),
  1140  				bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withVersion("2.0.0")),
  1141  				bundle("a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withVersion("3.0.0")),
  1142  			}},
  1143  			out: resolverTestOut{
  1144  				steps: [][]*v1alpha1.Step{
  1145  					bundleSteps(bundle("a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withVersion("3.0.0")), namespace, "a.v2", catalog),
  1146  				},
  1147  				subs: []*v1alpha1.Subscription{
  1148  					updatedSub(namespace, "a.v3", "a.v2", "a", "alpha", catalog),
  1149  				},
  1150  			},
  1151  		},
  1152  		{
  1153  			name: "FailForwardDisabled/3EntryReplacementChain/NotSatisfiable",
  1154  			clusterState: []runtime.Object{
  1155  				existingSub(namespace, "a.v3", "a", "alpha", catalog),
  1156  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)),
  1157  				existingOperator(namespace, "a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)),
  1158  				existingOperator(namespace, "a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)),
  1159  				newOperatorGroup("foo", namespace),
  1160  			},
  1161  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
  1162  				bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")),
  1163  				bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withVersion("2.0.0")),
  1164  				bundle("a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withVersion("3.0.0")),
  1165  				bundle("a.v4", "a", "alpha", "a.v3", Provides1, nil, nil, nil, withVersion("4.0.0")),
  1166  			}},
  1167  			out: resolverTestOut{
  1168  				steps: [][]*v1alpha1.Step{},
  1169  				subs:  []*v1alpha1.Subscription{},
  1170  				errAssert: func(t *testing.T, err error) {
  1171  					assert.IsType(t, solver.NotSatisfiable{}, err)
  1172  					assert.Contains(t, err.Error(), "constraints not satisfiable")
  1173  					assert.Contains(t, err.Error(), "provide k (g/v)")
  1174  					assert.Contains(t, err.Error(), "exists and is not referenced by a subscription")
  1175  				},
  1176  			},
  1177  		},
  1178  		{
  1179  			name: "FailForwardEnabled/3EntryReplacementChain/Satisfiable",
  1180  			clusterState: []runtime.Object{
  1181  				existingSub(namespace, "a.v3", "a", "alpha", catalog),
  1182  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)),
  1183  				existingOperator(namespace, "a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)),
  1184  				existingOperator(namespace, "a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)),
  1185  				newOperatorGroup("foo", namespace, withUpgradeStrategy(operatorsv1.UpgradeStrategyUnsafeFailForward)),
  1186  			},
  1187  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
  1188  				bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")),
  1189  				bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withVersion("2.0.0")),
  1190  				bundle("a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withVersion("3.0.0")),
  1191  				bundle("a.v4", "a", "alpha", "a.v3", Provides1, nil, nil, nil, withVersion("4.0.0")),
  1192  			}},
  1193  			out: resolverTestOut{
  1194  				steps: [][]*v1alpha1.Step{
  1195  					bundleSteps(bundle("a.v4", "a", "alpha", "a.v3", Provides1, nil, nil, nil, withVersion("4.0.0")), namespace, "a.v3", catalog),
  1196  				},
  1197  				subs: []*v1alpha1.Subscription{
  1198  					updatedSub(namespace, "a.v4", "a.v3", "a", "alpha", catalog),
  1199  				},
  1200  			},
  1201  		},
  1202  		{
  1203  			name: "FailForwardEnabled/3EntryReplacementChain/ReplacementChainBroken/NotSatisfiable",
  1204  			clusterState: []runtime.Object{
  1205  				existingSub(namespace, "a.v3", "a", "alpha", catalog),
  1206  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)),
  1207  				existingOperator(namespace, "a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)),
  1208  				existingOperator(namespace, "a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)),
  1209  				newOperatorGroup("foo", namespace, withUpgradeStrategy(operatorsv1.UpgradeStrategyUnsafeFailForward)),
  1210  			},
  1211  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
  1212  				bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")),
  1213  				bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withVersion("2.0.0")),
  1214  				bundle("a.v3", "a", "alpha", "a.v2", Provides1, nil, nil, nil, withVersion("3.0.0")),
  1215  				bundle("a.v4", "a", "alpha", "a.v3", Provides1, nil, nil, nil, withVersion("4.0.0")),
  1216  			}},
  1217  			out: resolverTestOut{
  1218  				steps: [][]*v1alpha1.Step{},
  1219  				subs:  []*v1alpha1.Subscription{},
  1220  				errAssert: func(t *testing.T, err error) {
  1221  					assert.Contains(t, err.Error(), "error using catalogsource catsrc-namespace/@existing: csv")
  1222  					assert.Contains(t, err.Error(), "in phase Failed instead of Replacing")
  1223  				},
  1224  			},
  1225  		},
  1226  		{
  1227  			name: "FailForwardEnabled/MultipleReplaces/ReplacementChainEndsInFailure/ConflictingProvider/NoUpgrade",
  1228  			clusterState: []runtime.Object{
  1229  				existingSub(namespace, "a.v1", "a", "alpha", catalog),
  1230  				existingOperator(namespace, "b.v1", "b", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseReplacing)),
  1231  				existingOperator(namespace, "a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withPhase(v1alpha1.CSVPhaseFailed)),
  1232  				newOperatorGroup("foo", namespace, withUpgradeStrategy(operatorsv1.UpgradeStrategyUnsafeFailForward)),
  1233  			},
  1234  			bundlesByCatalog: map[resolvercache.SourceKey][]*api.Bundle{catalog: {
  1235  				bundle("a.v1", "a", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")),
  1236  				bundle("a.v2", "a", "alpha", "a.v1", Provides1, nil, nil, nil, withVersion("2.0.0")),
  1237  				bundle("b.v1", "b", "alpha", "", Provides1, nil, nil, nil, withVersion("1.0.0")),
  1238  			}},
  1239  			out: resolverTestOut{
  1240  				steps: [][]*v1alpha1.Step{},
  1241  				subs:  []*v1alpha1.Subscription{},
  1242  				errAssert: func(t *testing.T, err error) {
  1243  					assert.IsType(t, solver.NotSatisfiable{}, err)
  1244  					assert.Contains(t, err.Error(), "constraints not satisfiable")
  1245  					assert.Contains(t, err.Error(), "provide k (g/v)")
  1246  					assert.Contains(t, err.Error(), "clusterserviceversion b.v1 exists and is not referenced by a subscription")
  1247  				},
  1248  			},
  1249  		},
  1250  	}
  1251  	for _, tt := range tests {
  1252  		t.Run(tt.name, func(t *testing.T) {
  1253  			stopc := make(chan struct{})
  1254  			defer func() {
  1255  				stopc <- struct{}{}
  1256  			}()
  1257  			expectedSteps := []*v1alpha1.Step{}
  1258  			for _, steps := range tt.out.steps {
  1259  				expectedSteps = append(expectedSteps, steps...)
  1260  			}
  1261  			clientFake, informerFactory, _ := StartResolverInformers(namespace, stopc, tt.clusterState...)
  1262  			lister := operatorlister.NewLister()
  1263  			lister.OperatorsV1alpha1().RegisterSubscriptionLister(namespace, informerFactory.Operators().V1alpha1().Subscriptions().Lister())
  1264  			lister.OperatorsV1alpha1().RegisterClusterServiceVersionLister(namespace, informerFactory.Operators().V1alpha1().ClusterServiceVersions().Lister())
  1265  			lister.OperatorsV1().RegisterOperatorGroupLister(namespace, informerFactory.Operators().V1().OperatorGroups().Lister())
  1266  
  1267  			ssp := make(resolvercache.StaticSourceProvider)
  1268  			for catalog, bundles := range tt.bundlesByCatalog {
  1269  				snapshot := &resolvercache.Snapshot{}
  1270  				for _, bundle := range bundles {
  1271  					op, err := newOperatorFromBundle(bundle, "", catalog, "")
  1272  					if err != nil {
  1273  						t.Fatalf("unexpected error: %v", err)
  1274  					}
  1275  					snapshot.Entries = append(snapshot.Entries, op)
  1276  				}
  1277  				ssp[catalog] = snapshot
  1278  			}
  1279  			log := logrus.New()
  1280  			ssp[resolvercache.NewVirtualSourceKey(namespace)] = &csvSource{
  1281  				key:       resolvercache.NewVirtualSourceKey(namespace),
  1282  				csvLister: lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(namespace),
  1283  				subLister: lister.OperatorsV1alpha1().SubscriptionLister().Subscriptions(namespace),
  1284  				ogLister:  lister.OperatorsV1().OperatorGroupLister().OperatorGroups(namespace),
  1285  				listSubscriptions: func(ctx context.Context) (*v1alpha1.SubscriptionList, error) {
  1286  					items, err := lister.OperatorsV1alpha1().SubscriptionLister().Subscriptions(namespace).List(labels.Everything())
  1287  					if err != nil {
  1288  						return nil, err
  1289  					}
  1290  					var out []v1alpha1.Subscription
  1291  					for _, sub := range items {
  1292  						out = append(out, *sub)
  1293  					}
  1294  					return &v1alpha1.SubscriptionList{
  1295  						Items: out,
  1296  					}, nil
  1297  				},
  1298  				logger: log,
  1299  			}
  1300  			satresolver := &Resolver{
  1301  				cache: resolvercache.New(ssp),
  1302  				log:   log,
  1303  			}
  1304  			resolver := NewOperatorStepResolver(lister, clientFake, "", nil, log)
  1305  			resolver.resolver = satresolver
  1306  
  1307  			steps, lookups, subs, err := resolver.ResolveSteps(namespace)
  1308  			if tt.out.solverError == nil {
  1309  				if tt.out.errAssert == nil {
  1310  					assert.NoError(t, err)
  1311  				} else {
  1312  					tt.out.errAssert(t, err)
  1313  				}
  1314  			} else {
  1315  				// the solver outputs useful information on a failed resolution, which is different from the old resolver
  1316  				require.NotNil(t, err)
  1317  				expectedStrings := []string{}
  1318  				for _, e := range tt.out.solverError {
  1319  					expectedStrings = append(expectedStrings, e.String())
  1320  				}
  1321  				actualStrings := []string{}
  1322  				for _, e := range err.(solver.NotSatisfiable) {
  1323  					actualStrings = append(actualStrings, e.String())
  1324  				}
  1325  				require.ElementsMatch(t, expectedStrings, actualStrings)
  1326  			}
  1327  			requireStepsEqual(t, expectedSteps, steps)
  1328  			require.ElementsMatch(t, tt.out.lookups, lookups)
  1329  			require.ElementsMatch(t, tt.out.subs, subs)
  1330  		})
  1331  	}
  1332  }
  1333  
  1334  func TestNamespaceResolverRBAC(t *testing.T) {
  1335  	namespace := "catsrc-namespace"
  1336  	catalog := resolvercache.SourceKey{Name: "catsrc", Namespace: namespace}
  1337  
  1338  	simplePermissions := []v1alpha1.StrategyDeploymentPermissions{
  1339  		{
  1340  			ServiceAccountName: "test-sa",
  1341  			Rules: []rbacv1.PolicyRule{
  1342  				{
  1343  					Verbs:     []string{"get", "list"},
  1344  					APIGroups: []string{""},
  1345  					Resources: []string{"configmaps"},
  1346  				},
  1347  			},
  1348  		},
  1349  	}
  1350  	bundle := bundleWithPermissions("a.v1", "a", "alpha", "", nil, nil, nil, nil, simplePermissions, simplePermissions)
  1351  	defaultServiceAccountPermissions := []v1alpha1.StrategyDeploymentPermissions{
  1352  		{
  1353  			ServiceAccountName: "default",
  1354  			Rules: []rbacv1.PolicyRule{
  1355  				{
  1356  					Verbs:     []string{"get", "list"},
  1357  					APIGroups: []string{""},
  1358  					Resources: []string{"configmaps"},
  1359  				},
  1360  			},
  1361  		},
  1362  	}
  1363  	bundleWithDefaultServiceAccount := bundleWithPermissions("a.v1", "a", "alpha", "", nil, nil, nil, nil, defaultServiceAccountPermissions, defaultServiceAccountPermissions)
  1364  	type out struct {
  1365  		steps [][]*v1alpha1.Step
  1366  		subs  []*v1alpha1.Subscription
  1367  		err   error
  1368  	}
  1369  	tests := []struct {
  1370  		name               string
  1371  		clusterState       []runtime.Object
  1372  		bundlesInCatalog   []*api.Bundle
  1373  		failForwardEnabled bool
  1374  		out                out
  1375  	}{
  1376  		{
  1377  			name: "NewSubscription/Permissions/ClusterPermissions",
  1378  			clusterState: []runtime.Object{
  1379  				newSub(namespace, "a", "alpha", catalog),
  1380  				newOperatorGroup("test-og", namespace),
  1381  			},
  1382  			bundlesInCatalog: []*api.Bundle{bundle},
  1383  			out: out{
  1384  				steps: [][]*v1alpha1.Step{
  1385  					bundleSteps(bundle, namespace, "", catalog),
  1386  				},
  1387  				subs: []*v1alpha1.Subscription{
  1388  					updatedSub(namespace, "a.v1", "", "a", "alpha", catalog),
  1389  				},
  1390  			},
  1391  		},
  1392  		{
  1393  			name: "don't create default service accounts",
  1394  			clusterState: []runtime.Object{
  1395  				newSub(namespace, "a", "alpha", catalog),
  1396  				newOperatorGroup("test-og", namespace),
  1397  			},
  1398  			bundlesInCatalog: []*api.Bundle{bundleWithDefaultServiceAccount},
  1399  			out: out{
  1400  				steps: [][]*v1alpha1.Step{
  1401  					withoutResourceKind("ServiceAccount", bundleSteps(bundleWithDefaultServiceAccount, namespace, "", catalog)),
  1402  				},
  1403  				subs: []*v1alpha1.Subscription{
  1404  					updatedSub(namespace, "a.v1", "", "a", "alpha", catalog),
  1405  				},
  1406  			},
  1407  		},
  1408  	}
  1409  	for _, tt := range tests {
  1410  		t.Run(tt.name, func(t *testing.T) {
  1411  			stopc := make(chan struct{})
  1412  			defer func() {
  1413  				stopc <- struct{}{}
  1414  			}()
  1415  			expectedSteps := []*v1alpha1.Step{}
  1416  			for _, steps := range tt.out.steps {
  1417  				expectedSteps = append(expectedSteps, steps...)
  1418  			}
  1419  			clientFake, informerFactory, _ := StartResolverInformers(namespace, stopc, tt.clusterState...)
  1420  			lister := operatorlister.NewLister()
  1421  			lister.OperatorsV1alpha1().RegisterSubscriptionLister(namespace, informerFactory.Operators().V1alpha1().Subscriptions().Lister())
  1422  			lister.OperatorsV1alpha1().RegisterClusterServiceVersionLister(namespace, informerFactory.Operators().V1alpha1().ClusterServiceVersions().Lister())
  1423  			lister.OperatorsV1().RegisterOperatorGroupLister(namespace, informerFactory.Operators().V1().OperatorGroups().Lister())
  1424  
  1425  			stubSnapshot := &resolvercache.Snapshot{}
  1426  			for _, bundle := range tt.bundlesInCatalog {
  1427  				op, err := newOperatorFromBundle(bundle, "", catalog, "")
  1428  				if err != nil {
  1429  					t.Fatalf("unexpected error: %v", err)
  1430  				}
  1431  				stubSnapshot.Entries = append(stubSnapshot.Entries, op)
  1432  			}
  1433  			satresolver := &Resolver{
  1434  				cache: resolvercache.New(resolvercache.StaticSourceProvider{
  1435  					catalog: stubSnapshot,
  1436  				}),
  1437  			}
  1438  			resolver := NewOperatorStepResolver(lister, clientFake, "", nil, logrus.New())
  1439  			resolver.resolver = satresolver
  1440  			steps, _, subs, err := resolver.ResolveSteps(namespace)
  1441  			require.Equal(t, tt.out.err, err)
  1442  			requireStepsEqual(t, expectedSteps, steps)
  1443  			require.ElementsMatch(t, tt.out.subs, subs)
  1444  		})
  1445  	}
  1446  }
  1447  
  1448  // Helpers for resolver tests
  1449  
  1450  func StartResolverInformers(namespace string, stopCh <-chan struct{}, objs ...runtime.Object) (versioned.Interface, externalversions.SharedInformerFactory, []cache.InformerSynced) {
  1451  	// Create client fakes
  1452  	clientFake := fake.NewSimpleClientset(objs...)
  1453  
  1454  	var hasSyncedCheckFns []cache.InformerSynced
  1455  	nsInformerFactory := externalversions.NewSharedInformerFactoryWithOptions(clientFake, time.Second, externalversions.WithNamespace(namespace))
  1456  	informers := []cache.SharedIndexInformer{
  1457  		nsInformerFactory.Operators().V1alpha1().Subscriptions().Informer(),
  1458  		nsInformerFactory.Operators().V1alpha1().ClusterServiceVersions().Informer(),
  1459  		nsInformerFactory.Operators().V1().OperatorGroups().Informer(),
  1460  	}
  1461  
  1462  	for _, informer := range informers {
  1463  		hasSyncedCheckFns = append(hasSyncedCheckFns, informer.HasSynced)
  1464  		go informer.Run(stopCh)
  1465  	}
  1466  	if ok := cache.WaitForCacheSync(stopCh, hasSyncedCheckFns...); !ok {
  1467  		panic("failed to wait for caches to sync")
  1468  	}
  1469  
  1470  	return clientFake, nsInformerFactory, hasSyncedCheckFns
  1471  }
  1472  
  1473  type subOption func(*v1alpha1.Subscription)
  1474  
  1475  func withStartingCSV(name string) subOption {
  1476  	return func(s *v1alpha1.Subscription) {
  1477  		s.Spec.StartingCSV = name
  1478  	}
  1479  }
  1480  
  1481  func newSub(namespace, pkg, channel string, catalog resolvercache.SourceKey, option ...subOption) *v1alpha1.Subscription {
  1482  	s := &v1alpha1.Subscription{
  1483  		ObjectMeta: metav1.ObjectMeta{
  1484  			Name:      pkg + "-" + channel,
  1485  			Namespace: namespace,
  1486  		},
  1487  		Spec: &v1alpha1.SubscriptionSpec{
  1488  			Package:                pkg,
  1489  			Channel:                channel,
  1490  			CatalogSource:          catalog.Name,
  1491  			CatalogSourceNamespace: catalog.Namespace,
  1492  		},
  1493  	}
  1494  	for _, o := range option {
  1495  		o(s)
  1496  	}
  1497  	return s
  1498  }
  1499  
  1500  type ogOption func(*operatorsv1.OperatorGroup)
  1501  
  1502  func withUpgradeStrategy(upgradeStrategy operatorsv1.UpgradeStrategy) ogOption {
  1503  	return func(og *operatorsv1.OperatorGroup) {
  1504  		og.Spec.UpgradeStrategy = upgradeStrategy
  1505  	}
  1506  }
  1507  
  1508  func newOperatorGroup(name, namespace string, option ...ogOption) *operatorsv1.OperatorGroup {
  1509  	og := &operatorsv1.OperatorGroup{
  1510  		ObjectMeta: metav1.ObjectMeta{
  1511  			Name:      name,
  1512  			Namespace: namespace,
  1513  		},
  1514  	}
  1515  	for _, o := range option {
  1516  		o(og)
  1517  	}
  1518  	return og
  1519  }
  1520  
  1521  func updatedSub(namespace, currentOperatorName, installedOperatorName, pkg, channel string, catalog resolvercache.SourceKey, option ...subOption) *v1alpha1.Subscription {
  1522  	s := &v1alpha1.Subscription{
  1523  		ObjectMeta: metav1.ObjectMeta{
  1524  			Name:      pkg + "-" + channel,
  1525  			Namespace: namespace,
  1526  		},
  1527  		Spec: &v1alpha1.SubscriptionSpec{
  1528  			Package:                pkg,
  1529  			Channel:                channel,
  1530  			CatalogSource:          catalog.Name,
  1531  			CatalogSourceNamespace: catalog.Namespace,
  1532  		},
  1533  		Status: v1alpha1.SubscriptionStatus{
  1534  			CurrentCSV:   currentOperatorName,
  1535  			InstalledCSV: installedOperatorName,
  1536  		},
  1537  	}
  1538  	for _, o := range option {
  1539  		o(s)
  1540  	}
  1541  	return s
  1542  }
  1543  
  1544  func existingSub(namespace, operatorName, pkg, channel string, catalog resolvercache.SourceKey) *v1alpha1.Subscription {
  1545  	return &v1alpha1.Subscription{
  1546  		ObjectMeta: metav1.ObjectMeta{
  1547  			Name:      pkg + "-" + channel,
  1548  			Namespace: namespace,
  1549  		},
  1550  		Spec: &v1alpha1.SubscriptionSpec{
  1551  			Package:                pkg,
  1552  			Channel:                channel,
  1553  			CatalogSource:          catalog.Name,
  1554  			CatalogSourceNamespace: catalog.Namespace,
  1555  		},
  1556  		Status: v1alpha1.SubscriptionStatus{
  1557  			CurrentCSV:   operatorName,
  1558  			InstalledCSV: operatorName,
  1559  		},
  1560  	}
  1561  }
  1562  
  1563  type csvOption func(*v1alpha1.ClusterServiceVersion)
  1564  
  1565  func withPhase(phase v1alpha1.ClusterServiceVersionPhase) csvOption {
  1566  	return func(csv *v1alpha1.ClusterServiceVersion) {
  1567  		csv.Status.Phase = phase
  1568  	}
  1569  }
  1570  
  1571  func existingOperator(namespace, operatorName, pkg, channel, replaces string, providedCRDs, requiredCRDs, providedAPIs, requiredAPIs resolvercache.APISet, option ...csvOption) *v1alpha1.ClusterServiceVersion {
  1572  	bundleForOperator := bundle(operatorName, pkg, channel, replaces, providedCRDs, requiredCRDs, providedAPIs, requiredAPIs)
  1573  	csv, err := V1alpha1CSVFromBundle(bundleForOperator)
  1574  	if err != nil {
  1575  		panic(err)
  1576  	}
  1577  	csv.SetNamespace(namespace)
  1578  	for _, o := range option {
  1579  		o(csv)
  1580  	}
  1581  	return csv
  1582  }
  1583  
  1584  func bundleSteps(bundle *api.Bundle, ns, replaces string, catalog resolvercache.SourceKey) []*v1alpha1.Step {
  1585  	if replaces == "" {
  1586  		csv, _ := V1alpha1CSVFromBundle(bundle)
  1587  		replaces = csv.Spec.Replaces
  1588  	}
  1589  	stepresources, err := NewStepResourceFromBundle(bundle, ns, replaces, catalog.Name, catalog.Namespace)
  1590  	if err != nil {
  1591  		panic(err)
  1592  	}
  1593  
  1594  	steps := make([]*v1alpha1.Step, 0)
  1595  	for _, sr := range stepresources {
  1596  		steps = append(steps, &v1alpha1.Step{
  1597  			Resolving: bundle.CsvName,
  1598  			Resource:  sr,
  1599  			Status:    v1alpha1.StepStatusUnknown,
  1600  		})
  1601  	}
  1602  	return steps
  1603  }
  1604  
  1605  func withoutResourceKind(kind string, steps []*v1alpha1.Step) []*v1alpha1.Step {
  1606  	filtered := make([]*v1alpha1.Step, 0)
  1607  
  1608  	for i, s := range steps {
  1609  		if s.Resource.Kind != kind {
  1610  			filtered = append(filtered, steps[i])
  1611  		}
  1612  	}
  1613  
  1614  	return filtered
  1615  }
  1616  
  1617  func subSteps(namespace, operatorName, pkgName, channelName string, catalog resolvercache.SourceKey) []*v1alpha1.Step {
  1618  	sub := &v1alpha1.Subscription{
  1619  		ObjectMeta: metav1.ObjectMeta{
  1620  			Name:      strings.Join([]string{pkgName, channelName, catalog.Name, catalog.Namespace}, "-"),
  1621  			Namespace: namespace,
  1622  		},
  1623  		Spec: &v1alpha1.SubscriptionSpec{
  1624  			Package:                pkgName,
  1625  			Channel:                channelName,
  1626  			CatalogSource:          catalog.Name,
  1627  			CatalogSourceNamespace: catalog.Namespace,
  1628  			StartingCSV:            operatorName,
  1629  			InstallPlanApproval:    v1alpha1.ApprovalAutomatic,
  1630  		},
  1631  	}
  1632  	stepresource, err := NewStepResourceFromObject(sub, catalog.Name, catalog.Namespace)
  1633  	if err != nil {
  1634  		panic(err)
  1635  	}
  1636  	return []*v1alpha1.Step{{
  1637  		Resolving: operatorName,
  1638  		Resource:  stepresource,
  1639  		Status:    v1alpha1.StepStatusUnknown,
  1640  	}}
  1641  }
  1642  
  1643  // requireStepsEqual is similar to require.ElementsMatch, but produces better error messages
  1644  func requireStepsEqual(t *testing.T, expectedSteps, steps []*v1alpha1.Step) {
  1645  	for _, s := range expectedSteps {
  1646  		require.Contains(t, steps, s, "step in expected not found in steps")
  1647  	}
  1648  	for _, s := range steps {
  1649  		require.Contains(t, expectedSteps, s, "step in steps not found in expected")
  1650  	}
  1651  }