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 }