github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/resolver_test.go (about) 1 package resolver 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "math/rand" 8 "testing" 9 "time" 10 11 "github.com/blang/semver/v4" 12 "github.com/sirupsen/logrus" 13 "github.com/sirupsen/logrus/hooks/test" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 18 "github.com/operator-framework/api/pkg/constraints" 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-lifecycle-manager/pkg/controller/registry/resolver/cache" 22 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver" 23 "github.com/operator-framework/operator-registry/pkg/api" 24 opregistry "github.com/operator-framework/operator-registry/pkg/registry" 25 ) 26 27 var testGVKKey = opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"} 28 var rnd = rand.New(rand.NewSource(time.Now().UnixNano())) 29 30 // tests can directly specify fixtures as cache entries instead of depending on this translation 31 func csvSnapshotOrPanic(ns string, subs []*v1alpha1.Subscription, csvs ...*v1alpha1.ClusterServiceVersion) *cache.Snapshot { 32 var entries []*cache.Entry 33 for _, csv := range csvs { 34 entry, err := newEntryFromV1Alpha1CSV(csv) 35 if err != nil { 36 panic(err) 37 } 38 entry.SourceInfo.Catalog = cache.NewVirtualSourceKey(ns) 39 for _, sub := range subs { 40 if sub.Status.InstalledCSV == entry.Name { 41 entry.SourceInfo.Subscription = sub 42 } 43 } 44 entries = append(entries, entry) 45 } 46 return &cache.Snapshot{Entries: entries} 47 } 48 49 func TestSolveOperators(t *testing.T) { 50 APISet := cache.APISet{testGVKKey: struct{}{}} 51 Provides := APISet 52 53 const namespace = "test-namespace" 54 catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace} 55 56 csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil) 57 sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog) 58 newSub := newSub(namespace, "another-package", "alpha", catalog) 59 subs := []*v1alpha1.Subscription{sub, newSub} 60 61 resolver := Resolver{ 62 cache: cache.New(cache.StaticSourceProvider{ 63 catalog: &cache.Snapshot{ 64 Entries: []*cache.Entry{ 65 genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), 66 genEntry("another-package.v1", "1.0.1", "", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), 67 }, 68 }, 69 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv), 70 }), 71 log: logrus.New(), 72 } 73 74 operators, err := resolver.Resolve([]string{namespace}, subs) 75 assert.NoError(t, err) 76 77 expected := []*cache.Entry{ 78 genEntry("another-package.v1", "1.0.1", "", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), 79 } 80 require.ElementsMatch(t, expected, operators) 81 } 82 83 // ConstraintProviderFunc is a simple implementation of ConstraintProvider 84 type ConstraintProviderFunc func(e *cache.Entry) ([]solver.Constraint, error) 85 86 func (c ConstraintProviderFunc) Constraints(e *cache.Entry) ([]solver.Constraint, error) { 87 return c(e) 88 } 89 90 func TestSolveOperators_WithSystemConstraints(t *testing.T) { 91 const namespace = "test-namespace" 92 catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace} 93 94 packageASub := newSub(namespace, "test-package", "alpha", catalog) 95 packageDSub := existingSub(namespace, "packageD.v1", "packageD", "alpha", catalog) 96 97 APISet := cache.APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}} 98 99 // test-package requires an API that can be provided by B or C 100 testPackage := genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", catalog.Name, catalog.Namespace, APISet, nil, nil, "", false) 101 anotherPackage := genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, APISet, nil, "", false) 102 packageC := genEntry("packageC.v1", "1.0.0", "", "packageC", "alpha", catalog.Name, catalog.Namespace, nil, APISet, nil, "", false) 103 104 // Existing operators 105 packageD := genEntry("packageD.v1", "1.0.0", "", "packageD", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false) 106 existingPackageD := existingOperator(namespace, "packageD.v1", "packageD", "alpha", "", nil, nil, nil, nil) 107 existingPackageD.Annotations = map[string]string{"operatorframework.io/properties": `{"properties":[{"type":"olm.package","value":{"packageName":"packageD","version":"1.0.0"}}]}`} 108 109 whiteListConstraintProvider := func(whiteList ...*cache.Entry) ConstraintProviderFunc { 110 return func(entry *cache.Entry) ([]solver.Constraint, error) { 111 for _, whiteListedEntry := range whiteList { 112 if whiteListedEntry.Package() == entry.Package() && 113 whiteListedEntry.Name == entry.Name && 114 whiteListedEntry.Version == entry.Version { 115 return nil, nil 116 } 117 } 118 return []solver.Constraint{PrettyConstraint( 119 solver.Prohibited(), 120 fmt.Sprintf("package: %s is not white listed", entry.Package()), 121 )}, nil 122 } 123 } 124 125 testCases := []struct { 126 title string 127 systemConstraintsProvider constraintProvider 128 expectedOperators []*cache.Entry 129 csvs []*v1alpha1.ClusterServiceVersion 130 subs []*v1alpha1.Subscription 131 snapshotEntries []*cache.Entry 132 err string 133 }{ 134 { 135 title: "No runtime constraints", 136 snapshotEntries: []*cache.Entry{testPackage, anotherPackage, packageC, packageD}, 137 systemConstraintsProvider: nil, 138 expectedOperators: []*cache.Entry{testPackage, anotherPackage}, 139 csvs: nil, 140 subs: []*v1alpha1.Subscription{packageASub}, 141 err: "", 142 }, 143 { 144 title: "Runtime constraints only accept packages A and C", 145 snapshotEntries: []*cache.Entry{testPackage, anotherPackage, packageC, packageD}, 146 systemConstraintsProvider: whiteListConstraintProvider(testPackage, packageC), 147 expectedOperators: []*cache.Entry{testPackage, packageC}, 148 csvs: nil, 149 subs: []*v1alpha1.Subscription{packageASub}, 150 err: "", 151 }, 152 { 153 title: "Existing packages are ignored", 154 snapshotEntries: []*cache.Entry{testPackage, anotherPackage, packageC, packageD}, 155 systemConstraintsProvider: whiteListConstraintProvider(testPackage, packageC), 156 expectedOperators: []*cache.Entry{testPackage, packageC}, 157 csvs: []*v1alpha1.ClusterServiceVersion{existingPackageD}, 158 subs: []*v1alpha1.Subscription{packageASub, packageDSub}, 159 err: "", 160 }, 161 { 162 title: "Runtime constraints don't allow A", 163 snapshotEntries: []*cache.Entry{testPackage, anotherPackage, packageC, packageD}, 164 systemConstraintsProvider: whiteListConstraintProvider(anotherPackage, packageC, packageD), 165 expectedOperators: nil, 166 csvs: nil, 167 subs: []*v1alpha1.Subscription{packageASub}, 168 err: "test-package is not white listed", 169 }, 170 } 171 172 for _, testCase := range testCases { 173 resolver := Resolver{ 174 cache: cache.New(cache.StaticSourceProvider{ 175 catalog: &cache.Snapshot{ 176 Entries: testCase.snapshotEntries, 177 }, 178 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, testCase.subs, testCase.csvs...), 179 }), 180 log: logrus.New(), 181 systemConstraintsProvider: testCase.systemConstraintsProvider, 182 } 183 operators, err := resolver.Resolve([]string{namespace}, testCase.subs) 184 185 if testCase.err != "" { 186 require.Containsf(t, err.Error(), testCase.err, "Test %s failed", testCase.title) 187 } else { 188 require.NoErrorf(t, err, "Test %s failed", testCase.title) 189 } 190 require.ElementsMatch(t, testCase.expectedOperators, operators, "Test %s failed", testCase.title) 191 } 192 } 193 194 func TestDisjointChannelGraph(t *testing.T) { 195 const namespace = "test-namespace" 196 catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace} 197 198 newSub := newSub(namespace, "test-package", "alpha", catalog) 199 subs := []*v1alpha1.Subscription{newSub} 200 201 resolver := Resolver{ 202 cache: cache.New(cache.StaticSourceProvider{ 203 catalog: &cache.Snapshot{ 204 Entries: []*cache.Entry{ 205 genEntry("test-package.side1.v1", "0.0.1", "", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), 206 genEntry("test-package.side1.v2", "0.0.2", "test-package.side1.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), 207 genEntry("test-package.side2.v1", "1.0.0", "", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), 208 genEntry("test-package.side2.v2", "2.0.0", "test-package.side2.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), 209 }, 210 }, 211 }), 212 log: logrus.New(), 213 } 214 215 _, err := resolver.Resolve([]string{namespace}, subs) 216 require.Error(t, err, "a unique replacement chain within a channel is required to determine the relative order between channel entries, but 2 replacement chains were found in channel \"alpha\" of package \"test-package\": test-package.side1.v2...test-package.side1.v1, test-package.side2.v2...test-package.side2.v1") 217 } 218 219 func TestSolveOperators_MultipleChannels(t *testing.T) { 220 APISet := cache.APISet{testGVKKey: struct{}{}} 221 Provides := APISet 222 223 namespace := "olm" 224 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 225 226 csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil) 227 sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog) 228 newSub := newSub(namespace, "another-package", "alpha", catalog) 229 subs := []*v1alpha1.Subscription{sub, newSub} 230 231 resolver := Resolver{ 232 cache: cache.New(cache.StaticSourceProvider{ 233 catalog: &cache.Snapshot{ 234 Entries: []*cache.Entry{ 235 genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false), 236 genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false), 237 genEntry("another-package.v1", "1.0.0", "", "another-package", "beta", "community", "olm", nil, nil, nil, "", false), 238 }, 239 }, 240 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv), 241 }), 242 log: logrus.New(), 243 } 244 245 operators, err := resolver.Resolve([]string{"olm"}, subs) 246 assert.NoError(t, err) 247 expected := []*cache.Entry{ 248 genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false), 249 } 250 assert.ElementsMatch(t, expected, operators) 251 } 252 253 func TestSolveOperators_FindLatestVersion(t *testing.T) { 254 APISet := cache.APISet{testGVKKey: struct{}{}} 255 Provides := APISet 256 257 namespace := "olm" 258 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 259 260 csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil) 261 sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog) 262 newSub := newSub(namespace, "another-package", "alpha", catalog) 263 subs := []*v1alpha1.Subscription{sub, newSub} 264 265 resolver := Resolver{ 266 cache: cache.New(cache.StaticSourceProvider{ 267 cache.SourceKey{ 268 Namespace: "olm", 269 Name: "community", 270 }: &cache.Snapshot{ 271 Entries: []*cache.Entry{ 272 genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false), 273 genEntry("another-package.v0.9.0", "0.9.0", "", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false), 274 genEntry("another-package.v1.0.0", "1.0.0", "another-package.v0.9.0", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false), 275 genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false), 276 }, 277 }, 278 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv), 279 }), 280 log: logrus.New(), 281 } 282 283 operators, err := resolver.Resolve([]string{"olm"}, subs) 284 assert.NoError(t, err) 285 assert.Equal(t, 2, len(operators)) 286 for _, op := range operators { 287 assert.Equal(t, "1.0.1", op.Version.String()) 288 } 289 290 expected := []*cache.Entry{ 291 genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false), 292 genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false), 293 } 294 assert.ElementsMatch(t, expected, operators) 295 } 296 297 func TestSolveOperators_FindLatestVersionWithDependencies(t *testing.T) { 298 APISet := cache.APISet{testGVKKey: struct{}{}} 299 Provides := APISet 300 301 namespace := "olm" 302 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 303 304 csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil) 305 sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog) 306 newSub := newSub(namespace, "another-package", "alpha", catalog) 307 subs := []*v1alpha1.Subscription{sub, newSub} 308 309 opToAddVersionDeps := []*api.Dependency{ 310 { 311 Type: "olm.package", 312 Value: `{"packageName":"packageC","version":"1.0.1"}`, 313 }, 314 { 315 Type: "olm.package", 316 Value: `{"packageName":"packageD","version":"1.0.1"}`, 317 }, 318 } 319 320 resolver := Resolver{ 321 cache: cache.New(cache.StaticSourceProvider{ 322 catalog: &cache.Snapshot{ 323 Entries: []*cache.Entry{ 324 genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false), 325 genEntry("another-package.v0.9.0", "0.9.0", "", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false), 326 genEntry("another-package.v1.0.0", "1.0.0", "another-package.v0.9.0", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false), 327 genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), 328 genEntry("packageC.v1.0.0", "1.0.0", "", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false), 329 genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false), 330 genEntry("packageD.v1.0.0", "1.0.0", "", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false), 331 genEntry("packageD.v1.0.1", "1.0.1", "packageD.v1.0.0", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false), 332 genEntry("packageD.v1.0.2", "1.0.2", "packageD.v1.0.1", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false), 333 }, 334 }, 335 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv), 336 }), 337 log: logrus.New(), 338 } 339 340 operators, err := resolver.Resolve([]string{"olm"}, subs) 341 assert.NoError(t, err) 342 assert.Equal(t, 4, len(operators)) 343 344 expected := []*cache.Entry{ 345 genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false), 346 genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), 347 genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false), 348 genEntry("packageD.v1.0.1", "1.0.1", "packageD.v1.0.0", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false), 349 } 350 assert.ElementsMatch(t, expected, operators) 351 } 352 353 func TestSolveOperators_FindLatestVersionWithNestedDependencies(t *testing.T) { 354 APISet := cache.APISet{testGVKKey: struct{}{}} 355 Provides := APISet 356 357 namespace := "olm" 358 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 359 360 csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil) 361 sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog) 362 newSub := newSub(namespace, "another-package", "alpha", catalog) 363 subs := []*v1alpha1.Subscription{sub, newSub} 364 365 opToAddVersionDeps := []*api.Dependency{ 366 { 367 Type: "olm.package", 368 Value: `{"packageName":"packageC","version":"1.0.1"}`, 369 }, 370 { 371 Type: "olm.package", 372 Value: `{"packageName":"packageD","version":"1.0.1"}`, 373 }, 374 } 375 nestedVersionDeps := []*api.Dependency{ 376 { 377 Type: "olm.package", 378 Value: `{"packageName":"packageE","version":"1.0.1"}`, 379 }, 380 } 381 382 resolver := Resolver{ 383 cache: cache.New(cache.StaticSourceProvider{ 384 catalog: &cache.Snapshot{ 385 Entries: []*cache.Entry{ 386 genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false), 387 genEntry("another-package.v0.9.0", "0.9.0", "", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false), 388 genEntry("another-package.v1.0.0", "1.0.0", "another-package.v0.9.0", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false), 389 genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), 390 genEntry("packageC.v1.0.0", "1.0.0", "", "packageC", "alpha", "community", "olm", nil, nil, nestedVersionDeps, "", false), 391 genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", nil, nil, nestedVersionDeps, "", false), 392 genEntry("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false), 393 genEntry("packageE.v1.0.1", "1.0.1", "packageE.v1.0.0", "packageE", "alpha", "community", "olm", nil, nil, nil, "", false), 394 genEntry("packageE.v1.0.0", "1.0.0", "", "packageE", "alpha", "community", "olm", nil, nil, nil, "", false), 395 }, 396 }, 397 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv), 398 }), 399 log: logrus.New(), 400 } 401 402 operators, err := resolver.Resolve([]string{"olm"}, subs) 403 assert.NoError(t, err) 404 405 expected := []*cache.Entry{ 406 genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false), 407 genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), 408 genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", nil, nil, nestedVersionDeps, "", false), 409 genEntry("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "community", "olm", nil, nil, nil, "", false), 410 genEntry("packageE.v1.0.1", "1.0.1", "packageE.v1.0.0", "packageE", "alpha", "community", "olm", nil, nil, nil, "", false), 411 } 412 assert.ElementsMatch(t, expected, operators) 413 } 414 415 type stubSourcePriorityProvider map[cache.SourceKey]int 416 417 func (spp stubSourcePriorityProvider) Priority(k cache.SourceKey) int { 418 return spp[k] 419 } 420 421 func TestSolveOperators_CatsrcPrioritySorting(t *testing.T) { 422 opToAddVersionDeps := []*api.Dependency{ 423 { 424 Type: "olm.package", 425 Value: `{"packageName":"another-package","version":"0.0.1"}`, 426 }, 427 } 428 429 namespace := "olm" 430 customCatalog := cache.SourceKey{Name: "community", Namespace: namespace} 431 newSub := newSub(namespace, "test-package", "alpha", customCatalog) 432 subs := []*v1alpha1.Subscription{newSub} 433 434 ssp := cache.StaticSourceProvider{ 435 cache.SourceKey{Namespace: "olm", Name: "community"}: &cache.Snapshot{ 436 Entries: []*cache.Entry{ 437 genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", namespace, nil, 438 nil, opToAddVersionDeps, "", false), 439 }, 440 }, 441 cache.SourceKey{Namespace: "olm", Name: "community-operator"}: &cache.Snapshot{ 442 Entries: []*cache.Entry{ 443 genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "community-operator", 444 namespace, nil, nil, nil, "", false), 445 }, 446 }, 447 cache.SourceKey{Namespace: "olm", Name: "high-priority-operator"}: &cache.Snapshot{ 448 Entries: []*cache.Entry{ 449 genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "high-priority-operator", 450 namespace, nil, nil, nil, "", false), 451 }, 452 }, 453 } 454 455 resolver := Resolver{ 456 cache: cache.New(ssp, cache.WithSourcePriorityProvider(stubSourcePriorityProvider{cache.SourceKey{Namespace: "olm", Name: "high-priority-operator"}: 100})), 457 } 458 459 operators, err := resolver.Resolve([]string{"olm"}, subs) 460 assert.NoError(t, err) 461 expected := []*cache.Entry{ 462 genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", "olm", 463 nil, nil, opToAddVersionDeps, "", false), 464 genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "high-priority-operator", "olm", 465 nil, nil, nil, "", false), 466 } 467 assert.ElementsMatch(t, expected, operators) 468 469 // Catsrc with the same priority, ns, different name 470 ssp[cache.SourceKey{ 471 Namespace: "olm", 472 Name: "community-operator", 473 }] = &cache.Snapshot{ 474 Entries: []*cache.Entry{ 475 genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "community-operator", 476 namespace, nil, nil, nil, "", false), 477 }, 478 } 479 480 resolver = Resolver{ 481 cache: cache.New(ssp, cache.WithSourcePriorityProvider(stubSourcePriorityProvider{ 482 cache.SourceKey{Namespace: "olm", Name: "high-priority-operator"}: 100, 483 cache.SourceKey{Namespace: "olm", Name: "community-operator"}: 100, 484 })), 485 } 486 487 operators, err = resolver.Resolve([]string{"olm"}, subs) 488 assert.NoError(t, err) 489 expected = []*cache.Entry{ 490 genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", "olm", 491 nil, nil, opToAddVersionDeps, "", false), 492 genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "community-operator", "olm", 493 nil, nil, nil, "", false), 494 } 495 assert.ElementsMatch(t, expected, operators) 496 497 // operators from the same catalogs source should be prioritized. 498 ssp[cache.SourceKey{ 499 Namespace: "olm", 500 Name: "community", 501 }] = &cache.Snapshot{ 502 Entries: []*cache.Entry{ 503 genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", namespace, nil, 504 nil, opToAddVersionDeps, "", false), 505 genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "community", 506 namespace, nil, nil, nil, "", false), 507 }, 508 } 509 510 resolver = Resolver{ 511 cache: cache.New(ssp), 512 } 513 514 operators, err = resolver.Resolve([]string{"olm"}, subs) 515 assert.NoError(t, err) 516 expected = []*cache.Entry{ 517 genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", "olm", 518 nil, nil, opToAddVersionDeps, "", false), 519 genEntry("another-package.v1", "0.0.1", "", "another-package", "alpha", "community", "olm", 520 nil, nil, nil, "", false), 521 } 522 assert.ElementsMatch(t, expected, operators) 523 } 524 525 func TestSolveOperators_WithPackageDependencies(t *testing.T) { 526 APISet := cache.APISet{testGVKKey: struct{}{}} 527 Provides := APISet 528 529 namespace := "olm" 530 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 531 532 csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil) 533 sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog) 534 newSub := newSub(namespace, "another-package", "alpha", catalog) 535 subs := []*v1alpha1.Subscription{sub, newSub} 536 537 opToAddVersionDeps := []*api.Dependency{ 538 { 539 Type: "olm.package", 540 Value: `{"packageName":"packageC","version":"0.1.0"}`, 541 }, 542 } 543 544 resolver := Resolver{ 545 cache: cache.New(cache.StaticSourceProvider{ 546 catalog: &cache.Snapshot{ 547 Entries: []*cache.Entry{ 548 genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false), 549 genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), 550 genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false), 551 }, 552 }, 553 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv), 554 }), 555 log: logrus.New(), 556 } 557 558 operators, err := resolver.Resolve([]string{"olm"}, subs) 559 assert.NoError(t, err) 560 assert.Equal(t, 3, len(operators)) 561 562 expected := []*cache.Entry{ 563 genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false), 564 genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), 565 genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, nil, nil, "", false), 566 } 567 assert.ElementsMatch(t, expected, operators) 568 } 569 570 func TestSolveOperators_WithGVKDependencies(t *testing.T) { 571 APISet := cache.APISet{testGVKKey: struct{}{}} 572 Provides := APISet 573 574 namespace := "olm" 575 community := cache.SourceKey{Name: "community", Namespace: namespace} 576 577 subs := []*v1alpha1.Subscription{ 578 existingSub(namespace, "test-package.v1", "test-package", "alpha", community), 579 newSub(namespace, "another-package", "alpha", community), 580 } 581 582 deps := []*api.Dependency{ 583 { 584 Type: "olm.gvk", 585 Value: `{"group":"g","kind":"k","version":"v"}`, 586 }, 587 } 588 589 resolver := Resolver{ 590 cache: cache.New(cache.StaticSourceProvider{ 591 community: &cache.Snapshot{ 592 Entries: []*cache.Entry{ 593 genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false), 594 genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false), 595 genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, Provides, nil, "", false), 596 }, 597 }, 598 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic( 599 namespace, 600 subs, 601 existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", nil, nil, nil, nil), 602 ), 603 }), 604 log: logrus.New(), 605 } 606 607 operators, err := resolver.Resolve([]string{"olm"}, subs) 608 assert.NoError(t, err) 609 610 expected := []*cache.Entry{ 611 genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false), 612 genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, Provides, nil, "", false), 613 } 614 assert.ElementsMatch(t, expected, operators) 615 } 616 617 func TestSolveOperators_WithLabelDependencies(t *testing.T) { 618 namespace := "olm" 619 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 620 621 newSub := newSub(namespace, "test-package", "alpha", catalog) 622 subs := []*v1alpha1.Subscription{newSub} 623 624 deps := []*api.Dependency{ 625 { 626 Type: "olm.label", 627 Value: `{"label":"lts"}`, 628 }, 629 } 630 631 props := []*api.Property{ 632 { 633 Type: "olm.label", 634 Value: `{"label":"lts"}`, 635 }, 636 } 637 638 operatorBv1 := genEntry("another-package.v1", "1.0.0", "", "another-package", "beta", "community", "olm", nil, nil, nil, "", false) 639 operatorBv1.Properties = append(operatorBv1.Properties, props...) 640 641 resolver := Resolver{ 642 cache: cache.New(cache.StaticSourceProvider{ 643 catalog: &cache.Snapshot{ 644 Entries: []*cache.Entry{ 645 genEntry("test-package", "0.0.1", "", "test-package", "alpha", "community", "olm", nil, nil, deps, "", false), 646 operatorBv1, 647 }, 648 }, 649 }), 650 } 651 652 operators, err := resolver.Resolve([]string{"olm"}, subs) 653 assert.NoError(t, err) 654 assert.Equal(t, 2, len(operators)) 655 656 expected := []*cache.Entry{ 657 genEntry("test-package", "0.0.1", "", "test-package", "alpha", "community", "olm", nil, nil, deps, "", false), 658 operatorBv1, 659 } 660 assert.ElementsMatch(t, expected, operators) 661 } 662 663 func TestSolveOperators_WithUnsatisfiableLabelDependencies(t *testing.T) { 664 namespace := "olm" 665 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 666 667 newSub := newSub(namespace, "test-package", "alpha", catalog) 668 subs := []*v1alpha1.Subscription{newSub} 669 670 deps := []*api.Dependency{ 671 { 672 Type: "olm.label", 673 Value: `{"label":"lts"}`, 674 }, 675 } 676 677 resolver := Resolver{ 678 cache: cache.New(cache.StaticSourceProvider{ 679 catalog: &cache.Snapshot{ 680 Entries: []*cache.Entry{ 681 genEntry("test-package", "0.0.1", "", "test-package", "alpha", "community", "olm", nil, nil, deps, "", false), 682 genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, nil, "", false), 683 }, 684 }, 685 }), 686 } 687 688 operators, err := resolver.Resolve([]string{"olm"}, subs) 689 assert.Error(t, err) 690 assert.Equal(t, 0, len(operators)) 691 } 692 693 func TestSolveOperators_WithNestedGVKDependencies(t *testing.T) { 694 APISet := cache.APISet{testGVKKey: struct{}{}} 695 Provides := APISet 696 697 namespace := "olm" 698 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 699 700 csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil) 701 sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog) 702 newSub := newSub(namespace, "another-package", "alpha", catalog) 703 subs := []*v1alpha1.Subscription{sub, newSub} 704 705 deps := []*api.Dependency{ 706 { 707 Type: "olm.gvk", 708 Value: `{"group":"g","kind":"k","version":"v"}`, 709 }, 710 } 711 712 deps2 := []*api.Dependency{ 713 { 714 Type: "olm.gvk", 715 Value: `{"group":"g2","kind":"k2","version":"v2"}`, 716 }, 717 } 718 719 resolver := Resolver{ 720 cache: cache.New(cache.StaticSourceProvider{ 721 cache.SourceKey{ 722 Namespace: "olm", 723 Name: "community", 724 }: &cache.Snapshot{ 725 Entries: []*cache.Entry{ 726 genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false), 727 genEntry("another-package.v1.0.0", "1.0.0", "", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false), 728 genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false), 729 genEntry("packageC.v1.0.0", "1.0.0", "", "packageC", "alpha", "community", "olm", Provides2, Provides, deps2, "", false), 730 genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", Provides2, Provides, deps2, "", false), 731 genEntry("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "community", "olm", nil, Provides2, deps2, "", false), 732 }, 733 }, 734 cache.SourceKey{ 735 Namespace: "olm", 736 Name: "certified", 737 }: &cache.Snapshot{ 738 Entries: []*cache.Entry{ 739 genEntry("packageC.v1.0.0", "1.0.0", "", "packageC", "alpha", "certified", "olm", Provides2, Provides, deps2, "", false), 740 genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "certified", "olm", Provides2, Provides, deps2, "", false), 741 genEntry("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "certified", "olm", nil, Provides2, nil, "", false), 742 }, 743 }, 744 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv), 745 }), 746 log: logrus.New(), 747 } 748 749 operators, err := resolver.Resolve([]string{"olm"}, subs) 750 assert.NoError(t, err) 751 expected := []*cache.Entry{ 752 genEntry("test-package.v1.0.1", "1.0.1", "test-package.v1", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false), 753 genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false), 754 genEntry("packageC.v1.0.1", "1.0.1", "packageC.v1.0.0", "packageC", "alpha", "community", "olm", Provides2, Provides, deps2, "", false), 755 genEntry("packageD.v1.0.1", "1.0.1", "", "packageD", "alpha", "community", "olm", nil, Provides2, deps2, "", false), 756 } 757 assert.ElementsMatch(t, expected, operators) 758 } 759 760 type entryGenerator struct { 761 name, version string 762 replaces string 763 pkg, channel, defaultChannel string 764 catName, catNamespace string 765 requiredAPIs, providedAPIs cache.APISet 766 properties []*api.Property 767 deprecated bool 768 } 769 770 func (g entryGenerator) gen() *cache.Entry { 771 entry := genEntry(g.name, g.version, g.replaces, g.pkg, g.channel, g.catName, g.catNamespace, g.requiredAPIs, g.providedAPIs, nil, g.defaultChannel, g.deprecated) 772 entry.Properties = append(entry.Properties, g.properties...) 773 return entry 774 } 775 776 func genEntriesRandom(ops ...entryGenerator) []*cache.Entry { 777 entries := make([]*cache.Entry, len(ops)) 778 // Randomize entry order to fuzz input operators over time. 779 idxs := rnd.Perm(len(ops)) 780 for destIdx, srcIdx := range idxs { 781 entries[destIdx] = ops[srcIdx].gen() 782 } 783 return entries 784 } 785 786 func TestSolveOperators_OLMConstraint_CompoundAll(t *testing.T) { 787 namespace := "olm" 788 csName := "community" 789 catalog := cache.SourceKey{Name: csName, Namespace: namespace} 790 791 newOperatorGens := []entryGenerator{{ 792 name: "bar.v1.0.0", version: "1.0.0", 793 pkg: "bar", channel: "stable", 794 catName: csName, catNamespace: namespace, 795 properties: []*api.Property{{ 796 Type: constraints.OLMConstraintType, 797 Value: `{"failureMessage": "all constraint", 798 "all": {"constraints": [ 799 {"package": {"packageName": "foo", "versionRange": ">=1.0.0"}}, 800 {"gvk": {"group": "g1", "version": "v1", "kind": "k1"}}, 801 {"gvk": {"group": "g2", "version": "v2", "kind": "k2"}} 802 ]} 803 }`, 804 }}, 805 }} 806 dependeeOperatorGens := []entryGenerator{{ 807 name: "foo.v1.0.1", version: "1.0.1", 808 pkg: "foo", channel: "stable", replaces: "foo.v1.0.0", 809 catName: csName, catNamespace: namespace, 810 providedAPIs: cache.APISet{ 811 opregistry.APIKey{Group: "g1", Version: "v1", Kind: "k1"}: {}, 812 opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {}, 813 opregistry.APIKey{Group: "g3", Version: "v3", Kind: "k3"}: {}, 814 }, 815 }} 816 817 inputs := append(dependeeOperatorGens, newOperatorGens...) 818 819 resolver := Resolver{ 820 cache: cache.New(cache.StaticSourceProvider{ 821 catalog: &cache.Snapshot{ 822 Entries: genEntriesRandom(append( 823 inputs, 824 entryGenerator{ 825 name: "foo.v0.99.0", version: "0.99.0", 826 pkg: "foo", channel: "stable", 827 catName: csName, catNamespace: namespace, 828 providedAPIs: cache.APISet{ 829 opregistry.APIKey{Group: "g1", Version: "v1", Kind: "k1"}: {}, 830 opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {}, 831 opregistry.APIKey{Group: "g3", Version: "v3", Kind: "k3"}: {}, 832 }, 833 }, 834 entryGenerator{ 835 name: "foo.v1.0.0", version: "1.0.0", 836 pkg: "foo", channel: "stable", replaces: "foo.v0.99.0", 837 catName: csName, catNamespace: namespace, 838 providedAPIs: cache.APISet{ 839 opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {}, 840 opregistry.APIKey{Group: "g3", Version: "v3", Kind: "k3"}: {}, 841 }, 842 }, 843 )...), 844 }, 845 }), 846 log: logrus.New(), 847 } 848 849 newSub := newSub(namespace, "bar", "stable", catalog) 850 subs := []*v1alpha1.Subscription{newSub} 851 852 operators, err := resolver.Resolve([]string{namespace}, subs) 853 require.NoError(t, err) 854 855 expected := make([]*cache.Entry, len(inputs)) 856 for i, gen := range inputs { 857 expected[i] = gen.gen() 858 } 859 assert.ElementsMatch(t, expected, operators) 860 } 861 862 func TestSolveOperators_OLMConstraint_CompoundAny(t *testing.T) { 863 namespace := "olm" 864 csName := "community" 865 catalog := cache.SourceKey{Name: csName, Namespace: namespace} 866 867 newOperatorGens := []entryGenerator{{ 868 name: "bar.v1.0.0", version: "1.0.0", 869 pkg: "bar", channel: "stable", 870 catName: csName, catNamespace: namespace, 871 properties: []*api.Property{{ 872 Type: constraints.OLMConstraintType, 873 Value: `{"failureMessage": "any constraint", 874 "any": {"constraints": [ 875 {"gvk": {"group": "g1", "version": "v1", "kind": "k1"}}, 876 {"gvk": {"group": "g2", "version": "v2", "kind": "k2"}} 877 ]} 878 }`, 879 }}, 880 }} 881 dependeeOperatorGens := []entryGenerator{{ 882 name: "foo.v1.0.1", version: "1.0.1", 883 pkg: "foo", channel: "stable", replaces: "foo.v1.0.0", 884 catName: csName, catNamespace: namespace, 885 providedAPIs: cache.APISet{ 886 opregistry.APIKey{Group: "g1", Version: "v1", Kind: "k1"}: {}, 887 opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {}, 888 opregistry.APIKey{Group: "g3", Version: "v3", Kind: "k3"}: {}, 889 }, 890 }} 891 892 inputs := append(dependeeOperatorGens, newOperatorGens...) 893 894 resolver := Resolver{ 895 cache: cache.New(cache.StaticSourceProvider{ 896 catalog: &cache.Snapshot{ 897 Entries: genEntriesRandom(append( 898 inputs, 899 entryGenerator{ 900 name: "foo.v0.99.0", version: "0.99.0", 901 pkg: "foo", channel: "stable", 902 catName: csName, catNamespace: namespace, 903 providedAPIs: cache.APISet{ 904 opregistry.APIKey{Group: "g0", Version: "v0", Kind: "k0"}: {}, 905 }, 906 }, 907 entryGenerator{ 908 name: "foo.v1.0.0", version: "1.0.0", 909 pkg: "foo", channel: "stable", replaces: "foo.v0.99.0", 910 catName: csName, catNamespace: namespace, 911 providedAPIs: cache.APISet{ 912 opregistry.APIKey{Group: "g1", Version: "v1", Kind: "k1"}: {}, 913 opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {}, 914 }, 915 }, 916 )...), 917 }, 918 }), 919 log: logrus.New(), 920 } 921 922 newSub := newSub(namespace, "bar", "stable", catalog) 923 subs := []*v1alpha1.Subscription{newSub} 924 925 operators, err := resolver.Resolve([]string{namespace}, subs) 926 require.NoError(t, err) 927 assert.Equal(t, 2, len(operators)) 928 929 expected := make([]*cache.Entry, len(inputs)) 930 for i, gen := range inputs { 931 expected[i] = gen.gen() 932 } 933 assert.ElementsMatch(t, expected, operators) 934 } 935 936 func TestSolveOperators_OLMConstraint_CompoundNot(t *testing.T) { 937 namespace := "olm" 938 csName := "community" 939 catalog := cache.SourceKey{Name: csName, Namespace: namespace} 940 941 newOperatorGens := []entryGenerator{{ 942 name: "bar.v1.0.0", version: "1.0.0", 943 pkg: "bar", channel: "stable", 944 catName: csName, catNamespace: namespace, 945 properties: []*api.Property{ 946 { 947 Type: constraints.OLMConstraintType, 948 Value: `{"failureMessage": "compound not constraint", 949 "all": {"constraints": [ 950 {"gvk": {"group": "g0", "version": "v0", "kind": "k0"}}, 951 {"not": {"constraints": [ 952 {"gvk": {"group": "g1", "version": "v1", "kind": "k1"}}, 953 {"gvk": {"group": "g2", "version": "v2", "kind": "k2"}} 954 ]}} 955 ]} 956 }`, 957 }, 958 }, 959 }} 960 dependeeOperatorGens := []entryGenerator{{ 961 name: "foo.v0.99.0", version: "0.99.0", 962 pkg: "foo", channel: "stable", 963 catName: csName, catNamespace: namespace, 964 providedAPIs: cache.APISet{ 965 opregistry.APIKey{Group: "g0", Version: "v0", Kind: "k0"}: {}, 966 }, 967 }} 968 969 inputs := append(dependeeOperatorGens, newOperatorGens...) 970 971 resolver := Resolver{ 972 cache: cache.New(cache.StaticSourceProvider{ 973 catalog: &cache.Snapshot{ 974 Entries: genEntriesRandom(append( 975 inputs, 976 entryGenerator{ 977 name: "foo.v1.0.0", version: "1.0.0", 978 pkg: "foo", channel: "stable", replaces: "foo.v0.99.0", 979 catName: csName, catNamespace: namespace, 980 providedAPIs: cache.APISet{ 981 opregistry.APIKey{Group: "g0", Version: "v0", Kind: "k0"}: {}, 982 opregistry.APIKey{Group: "g1", Version: "v1", Kind: "k1"}: {}, 983 opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {}, 984 }, 985 }, 986 entryGenerator{ 987 name: "foo.v1.0.1", version: "1.0.1", 988 pkg: "foo", channel: "stable", replaces: "foo.v1.0.0", 989 catName: csName, catNamespace: namespace, 990 providedAPIs: cache.APISet{ 991 opregistry.APIKey{Group: "g0", Version: "v0", Kind: "k0"}: {}, 992 opregistry.APIKey{Group: "g1", Version: "v1", Kind: "k1"}: {}, 993 opregistry.APIKey{Group: "g2", Version: "v2", Kind: "k2"}: {}, 994 opregistry.APIKey{Group: "g3", Version: "v3", Kind: "k3"}: {}, 995 }, 996 }, 997 )...), 998 }, 999 }), 1000 log: logrus.New(), 1001 } 1002 1003 newSub := newSub(namespace, "bar", "stable", catalog) 1004 subs := []*v1alpha1.Subscription{newSub} 1005 1006 operators, err := resolver.Resolve([]string{namespace}, subs) 1007 require.NoError(t, err) 1008 assert.Equal(t, 2, len(operators)) 1009 1010 expected := make([]*cache.Entry, len(inputs)) 1011 for i, gen := range inputs { 1012 expected[i] = gen.gen() 1013 } 1014 assert.ElementsMatch(t, expected, operators) 1015 } 1016 1017 func TestSolveOperators_OLMConstraint_Unknown(t *testing.T) { 1018 namespace := "olm" 1019 csName := "community" 1020 catalog := cache.SourceKey{Name: csName, Namespace: namespace} 1021 1022 newOperatorGens := []entryGenerator{{ 1023 name: "bar.v1.0.0", version: "1.0.0", 1024 pkg: "bar", channel: "stable", 1025 catName: csName, catNamespace: namespace, 1026 properties: []*api.Property{{ 1027 Type: constraints.OLMConstraintType, 1028 Value: `{"failureMessage": "unknown constraint", "unknown": {"foo": "bar"}}`, 1029 }}, 1030 }} 1031 1032 resolver := Resolver{ 1033 cache: cache.New(cache.StaticSourceProvider{ 1034 catalog: &cache.Snapshot{ 1035 Entries: genEntriesRandom(newOperatorGens...), 1036 }, 1037 }), 1038 log: logrus.New(), 1039 } 1040 1041 newSub := newSub(namespace, "bar", "stable", catalog) 1042 subs := []*v1alpha1.Subscription{newSub} 1043 1044 _, err := resolver.Resolve([]string{namespace}, subs) 1045 require.Error(t, err) 1046 require.Contains(t, err.Error(), `json: unknown field "unknown"`) 1047 } 1048 1049 func TestSolveOperators_IgnoreUnsatisfiableDependencies(t *testing.T) { 1050 const namespace = "olm" 1051 1052 Provides := cache.APISet{testGVKKey: struct{}{}} 1053 community := cache.SourceKey{Name: "community", Namespace: namespace} 1054 1055 subs := []*v1alpha1.Subscription{ 1056 existingSub(namespace, "test-package.v1", "test-package", "alpha", community), 1057 newSub(namespace, "another-package", "alpha", community), 1058 } 1059 1060 opToAddVersionDeps := []*api.Dependency{ 1061 { 1062 Type: "olm.package", 1063 Value: `{"packageName":"packageC","version":"0.1.0"}`, 1064 }, 1065 } 1066 unsatisfiableVersionDeps := []*api.Dependency{ 1067 { 1068 Type: "olm.package", 1069 Value: `{"packageName":"packageD","version":"0.1.0"}`, 1070 }, 1071 } 1072 1073 resolver := Resolver{ 1074 cache: cache.New(cache.StaticSourceProvider{ 1075 community: &cache.Snapshot{ 1076 Entries: []*cache.Entry{ 1077 genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "community", "olm", nil, nil, nil, "", false), 1078 genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), 1079 genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "community", "olm", nil, nil, unsatisfiableVersionDeps, "", false), 1080 }, 1081 }, 1082 {Namespace: "olm", Name: "certified"}: &cache.Snapshot{ 1083 Entries: []*cache.Entry{ 1084 genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", "certified", "olm", nil, nil, nil, "", false), 1085 genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "certified", "olm", nil, nil, opToAddVersionDeps, "", false), 1086 genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "certified", "olm", nil, nil, nil, "", false), 1087 }, 1088 }, 1089 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic( 1090 namespace, 1091 subs, 1092 existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil), 1093 ), 1094 }), 1095 log: logrus.New(), 1096 } 1097 1098 operators, err := resolver.Resolve([]string{"olm"}, subs) 1099 assert.NoError(t, err) 1100 expected := []*cache.Entry{ 1101 genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false), 1102 genEntry("packageC.v1", "0.1.0", "", "packageC", "alpha", "certified", "olm", nil, nil, nil, "", false), 1103 } 1104 assert.ElementsMatch(t, expected, operators) 1105 } 1106 1107 // Behavior: The resolver should prefer catalogs in the same namespace as the subscription. 1108 // It should also prefer the same catalog over global catalogs in terms of the operator cache. 1109 func TestSolveOperators_PreferCatalogInSameNamespace(t *testing.T) { 1110 APISet := cache.APISet{testGVKKey: struct{}{}} 1111 Provides := APISet 1112 1113 namespace := "olm" 1114 altNamespace := "alt-olm" 1115 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 1116 altnsCatalog := cache.SourceKey{Name: "alt-community", Namespace: altNamespace} 1117 1118 csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil) 1119 sub := existingSub(namespace, "test-package.v1", "test-package", "alpha", catalog) 1120 subs := []*v1alpha1.Subscription{sub} 1121 1122 resolver := Resolver{ 1123 cache: cache.New(cache.StaticSourceProvider{ 1124 catalog: &cache.Snapshot{ 1125 Entries: []*cache.Entry{ 1126 genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, "", false), 1127 }, 1128 }, 1129 altnsCatalog: &cache.Snapshot{ 1130 Entries: []*cache.Entry{ 1131 genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "alpha", altnsCatalog.Name, altnsCatalog.Namespace, nil, Provides, nil, "", false), 1132 }, 1133 }, 1134 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv), 1135 }), 1136 log: logrus.New(), 1137 } 1138 1139 operators, err := resolver.Resolve([]string{namespace}, subs) 1140 assert.NoError(t, err) 1141 1142 expected := []*cache.Entry{ 1143 genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, "", false), 1144 } 1145 require.ElementsMatch(t, expected, operators) 1146 } 1147 1148 // Behavior: The resolver should not look in catalogs not in the same namespace or the global catalog namespace when resolving the subscription. 1149 // This test should not result in a successful resolution because the catalog fulfilling the subscription is not in the operator cache. 1150 func TestSolveOperators_ResolveOnlyInCachedNamespaces(t *testing.T) { 1151 APISet := cache.APISet{testGVKKey: struct{}{}} 1152 Provides := APISet 1153 1154 namespace := "olm" 1155 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 1156 otherCatalog := cache.SourceKey{Name: "secret", Namespace: "secret"} 1157 1158 csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil) 1159 newSub := newSub(namespace, "test-package", "alpha", catalog) 1160 subs := []*v1alpha1.Subscription{newSub} 1161 1162 resolver := Resolver{ 1163 cache: cache.New(cache.StaticSourceProvider{ 1164 catalog: &cache.Snapshot{ 1165 Entries: []*cache.Entry{ 1166 genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "alpha", otherCatalog.Name, otherCatalog.Namespace, nil, Provides, nil, "", false), 1167 }, 1168 }, 1169 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv), 1170 }), 1171 log: logrus.New(), 1172 } 1173 1174 operators, err := resolver.Resolve([]string{namespace}, subs) 1175 assert.Error(t, err) 1176 assert.Equal(t, err.Error(), "expected exactly one operator, got 0", "did not expect to receive a resolution") 1177 assert.Len(t, operators, 0) 1178 } 1179 1180 // Behavior: the resolver should always prefer the default channel for the subscribed bundle (unless we implement ordering for channels) 1181 func TestSolveOperators_PreferDefaultChannelInResolution(t *testing.T) { 1182 APISet := cache.APISet{testGVKKey: struct{}{}} 1183 Provides := APISet 1184 1185 namespace := "olm" 1186 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 1187 1188 const defaultChannel = "stable" 1189 // do not specify a channel explicitly on the subscription 1190 newSub := newSub(namespace, "test-package", "", catalog) 1191 subs := []*v1alpha1.Subscription{newSub} 1192 1193 resolver := Resolver{ 1194 cache: cache.New(cache.StaticSourceProvider{ 1195 catalog: &cache.Snapshot{ 1196 Entries: []*cache.Entry{ 1197 // Default channel is stable in this case 1198 genEntry("test-package.v0.0.2", "0.0.2", "test-package.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), 1199 genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "stable", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), 1200 }, 1201 }, 1202 }), 1203 log: logrus.New(), 1204 } 1205 1206 operators, err := resolver.Resolve([]string{namespace}, subs) 1207 assert.NoError(t, err) 1208 1209 // operator should be from the default stable channel 1210 expected := []*cache.Entry{ 1211 genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "stable", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), 1212 } 1213 require.ElementsMatch(t, expected, operators) 1214 } 1215 1216 // Behavior: the resolver should always prefer the default channel for bundles satisfying transitive dependencies 1217 func TestSolveOperators_PreferDefaultChannelInResolutionForTransitiveDependencies(t *testing.T) { 1218 APISet := cache.APISet{testGVKKey: struct{}{}} 1219 Provides := APISet 1220 1221 namespace := "olm" 1222 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 1223 1224 newSub := newSub(namespace, "test-package", "alpha", catalog) 1225 subs := []*v1alpha1.Subscription{newSub} 1226 1227 const defaultChannel = "stable" 1228 1229 resolver := Resolver{ 1230 cache: cache.New(cache.StaticSourceProvider{ 1231 catalog: &cache.Snapshot{ 1232 Entries: []*cache.Entry{ 1233 genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, Provides, nil, apiSetToDependencies(nil, Provides), defaultChannel, false), 1234 genEntry("another-package.v0.0.1", "0.0.1", "another-package.v1", "another-package", defaultChannel, catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), 1235 genEntry("another-package.v0.0.2", "0.0.2", "another-package.v0.0.1", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), 1236 }, 1237 }, 1238 }), 1239 log: logrus.New(), 1240 } 1241 1242 operators, err := resolver.Resolve([]string{namespace}, subs) 1243 assert.NoError(t, err) 1244 1245 // operator should be from the default stable channel 1246 expected := []*cache.Entry{ 1247 genEntry("test-package.v0.0.1", "0.0.1", "test-package.v1", "test-package", "alpha", catalog.Name, catalog.Namespace, Provides, nil, apiSetToDependencies(nil, Provides), defaultChannel, false), 1248 genEntry("another-package.v0.0.1", "0.0.1", "another-package.v1", "another-package", defaultChannel, catalog.Name, catalog.Namespace, nil, Provides, nil, defaultChannel, false), 1249 } 1250 require.ElementsMatch(t, expected, operators) 1251 } 1252 1253 func TestSolveOperators_SubscriptionlessOperatorsSatisfyDependencies(t *testing.T) { 1254 APISet := cache.APISet{testGVKKey: struct{}{}} 1255 Provides := APISet 1256 1257 namespace := "olm" 1258 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 1259 1260 csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil) 1261 newSub := newSub(namespace, "another-package", "alpha", catalog) 1262 subs := []*v1alpha1.Subscription{newSub} 1263 1264 deps := []*api.Dependency{ 1265 { 1266 Type: "olm.gvk", 1267 Value: `{"group":"g","kind":"k","version":"v"}`, 1268 }, 1269 } 1270 1271 resolver := Resolver{ 1272 cache: cache.New(cache.StaticSourceProvider{ 1273 catalog: &cache.Snapshot{ 1274 Entries: []*cache.Entry{ 1275 genEntry("another-package.v1.0.0", "1.0.0", "", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false), 1276 genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", Provides, nil, deps, "", false), 1277 }, 1278 }, 1279 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv), 1280 }), 1281 log: logrus.New(), 1282 } 1283 1284 operators, err := resolver.Resolve([]string{"olm"}, subs) 1285 assert.NoError(t, err) 1286 expected := []*cache.Entry{ 1287 genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", catalog.Name, catalog.Namespace, Provides, nil, apiSetToDependencies(Provides, nil), "", false), 1288 } 1289 assert.ElementsMatch(t, expected, operators) 1290 } 1291 1292 func TestSolveOperators_SubscriptionlessOperatorsCanConflict(t *testing.T) { 1293 APISet := cache.APISet{testGVKKey: struct{}{}} 1294 Provides := APISet 1295 1296 namespace := "olm" 1297 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 1298 1299 csv := existingOperator(namespace, "test-package.v1", "test-package", "alpha", "", Provides, nil, nil, nil) 1300 newSub := newSub(namespace, "another-package", "alpha", catalog) 1301 subs := []*v1alpha1.Subscription{newSub} 1302 1303 resolver := Resolver{ 1304 cache: cache.New(cache.StaticSourceProvider{ 1305 catalog: &cache.Snapshot{ 1306 Entries: []*cache.Entry{ 1307 genEntry("another-package.v1.0.0", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, Provides, nil, "", false), 1308 genEntry("another-package.v1.0.1", "1.0.1", "another-package.v1.0.0", "another-package", "alpha", "community", "olm", nil, Provides, nil, "", false), 1309 }, 1310 }, 1311 cache.NewVirtualSourceKey(namespace): csvSnapshotOrPanic(namespace, subs, csv), 1312 }), 1313 log: logrus.New(), 1314 } 1315 1316 _, err := resolver.Resolve([]string{"olm"}, subs) 1317 assert.Error(t, err) 1318 } 1319 1320 func TestSolveOperators_PackageCannotSelfSatisfy(t *testing.T) { 1321 Provides1 := cache.APISet{testGVKKey: struct{}{}} 1322 Requires1 := cache.APISet{testGVKKey: struct{}{}} 1323 Provides2 := cache.APISet{opregistry.APIKey{Group: "g2", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}} 1324 Requires2 := cache.APISet{opregistry.APIKey{Group: "g2", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}} 1325 ProvidesBoth := Provides1.Union(Provides2) 1326 RequiresBoth := Requires1.Union(Requires2) 1327 1328 namespace := "olm" 1329 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 1330 secondaryCatalog := cache.SourceKey{Namespace: "olm", Name: "secondary"} 1331 1332 newSub := newSub(namespace, "test-package", "stable", catalog) 1333 subs := []*v1alpha1.Subscription{newSub} 1334 1335 resolver := Resolver{ 1336 cache: cache.New(cache.StaticSourceProvider{ 1337 catalog: &cache.Snapshot{ 1338 Entries: []*cache.Entry{ 1339 genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, RequiresBoth, nil, nil, "", false), 1340 // Despite satisfying dependencies of opA, this is not chosen because it is in the same package 1341 genEntry("opABC.v1.0.0", "1.0.0", "", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, ProvidesBoth, nil, "", false), 1342 1343 genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false), 1344 genEntry("opD.v1.0.0", "1.0.0", "", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false), 1345 }, 1346 }, 1347 secondaryCatalog: &cache.Snapshot{ 1348 Entries: []*cache.Entry{ 1349 genEntry("opC.v1.0.0", "1.0.0", "", "another-package", "stable", secondaryCatalog.Name, secondaryCatalog.Namespace, nil, Provides2, nil, "stable", false), 1350 1351 genEntry("opE.v1.0.0", "1.0.0", "", "packageC", "stable", secondaryCatalog.Name, secondaryCatalog.Namespace, nil, Provides2, nil, "", false), 1352 }, 1353 }, 1354 }), 1355 log: logrus.New(), 1356 } 1357 1358 operators, err := resolver.Resolve([]string{"olm"}, subs) 1359 assert.NoError(t, err) 1360 expected := []*cache.Entry{ 1361 genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, RequiresBoth, nil, nil, "", false), 1362 genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false), 1363 genEntry("opE.v1.0.0", "1.0.0", "", "packageC", "stable", secondaryCatalog.Name, secondaryCatalog.Namespace, nil, Provides2, nil, "", false), 1364 } 1365 assert.ElementsMatch(t, expected, operators) 1366 } 1367 1368 func TestSolveOperators_TransferApiOwnership(t *testing.T) { 1369 Provides1 := cache.APISet{testGVKKey: struct{}{}} 1370 Requires1 := cache.APISet{testGVKKey: struct{}{}} 1371 Provides2 := cache.APISet{opregistry.APIKey{Group: "g2", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}} 1372 ProvidesBoth := Provides1.Union(Provides2) 1373 1374 namespace := "olm" 1375 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 1376 og := &operatorsv1.OperatorGroup{ 1377 ObjectMeta: metav1.ObjectMeta{ 1378 Name: "og", 1379 Namespace: namespace, 1380 }, 1381 } 1382 1383 phases := []struct { 1384 subs []*v1alpha1.Subscription 1385 catalog cache.Source 1386 expected []*cache.Entry 1387 }{ 1388 { 1389 subs: []*v1alpha1.Subscription{newSub(namespace, "another-package", "stable", catalog)}, 1390 catalog: &cache.Snapshot{ 1391 Entries: []*cache.Entry{ 1392 genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false), 1393 genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false), 1394 }, 1395 }, 1396 expected: []*cache.Entry{ 1397 genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false), 1398 genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false), 1399 }, 1400 }, 1401 { 1402 // will have two existing subs after resolving once 1403 subs: []*v1alpha1.Subscription{ 1404 existingSub(namespace, "opA.v1.0.0", "test-package", "stable", catalog), 1405 existingSub(namespace, "opB.v1.0.0", "another-package", "stable", catalog), 1406 }, 1407 catalog: &cache.Snapshot{ 1408 Entries: []*cache.Entry{ 1409 genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false), 1410 genEntry("opA.v1.0.1", "1.0.1", "opA.v1.0.0", "test-package", "stable", catalog.Name, catalog.Namespace, Requires1, nil, nil, "", false), 1411 genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false), 1412 }, 1413 }, 1414 // nothing new to do here 1415 expected: nil, 1416 }, 1417 { 1418 // will have two existing subs after resolving once 1419 subs: []*v1alpha1.Subscription{ 1420 existingSub(namespace, "opA.v1.0.0", "test-package", "stable", catalog), 1421 existingSub(namespace, "opB.v1.0.0", "another-package", "stable", catalog), 1422 }, 1423 catalog: &cache.Snapshot{ 1424 Entries: []*cache.Entry{ 1425 genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false), 1426 genEntry("opA.v1.0.1", "1.0.1", "opA.v1.0.0", "test-package", "stable", catalog.Name, catalog.Namespace, Requires1, nil, nil, "", false), 1427 genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, Requires1, Provides2, nil, "stable", false), 1428 genEntry("opB.v1.0.1", "1.0.1", "opB.v1.0.0", "another-package", "stable", catalog.Name, catalog.Namespace, nil, ProvidesBoth, nil, "stable", false), 1429 }, 1430 }, 1431 expected: []*cache.Entry{ 1432 genEntry("opA.v1.0.1", "1.0.1", "opA.v1.0.0", "test-package", "stable", catalog.Name, catalog.Namespace, Requires1, nil, nil, "", false), 1433 genEntry("opB.v1.0.1", "1.0.1", "opB.v1.0.0", "another-package", "stable", catalog.Name, catalog.Namespace, nil, ProvidesBoth, nil, "stable", false), 1434 }, 1435 }, 1436 } 1437 1438 var csvs fakeCSVLister 1439 var operators []*cache.Entry 1440 for i, p := range phases { 1441 t.Run(fmt.Sprintf("phase %d", i+1), func(t *testing.T) { 1442 logger, _ := test.NewNullLogger() 1443 resolver := Resolver{ 1444 cache: cache.New(cache.StaticSourceProvider{ 1445 catalog: p.catalog, 1446 // todo: test depends on csvSource 1447 cache.NewVirtualSourceKey(namespace): &csvSource{ 1448 key: cache.NewVirtualSourceKey(namespace), 1449 csvLister: &csvs, 1450 subLister: fakeSubscriptionLister(p.subs), 1451 ogLister: fakeOperatorGroupLister{og}, 1452 logger: logger, 1453 listSubscriptions: func(ctx context.Context) (*v1alpha1.SubscriptionList, error) { 1454 return &v1alpha1.SubscriptionList{ 1455 Items: func(ptrs []*v1alpha1.Subscription) []v1alpha1.Subscription { 1456 var out []v1alpha1.Subscription 1457 for _, sub := range ptrs { 1458 out = append(out, *sub) 1459 } 1460 return out 1461 }(p.subs), 1462 }, nil 1463 }, 1464 }, 1465 }), 1466 log: logger, 1467 } 1468 csvs = csvs[:0] 1469 for _, o := range operators { 1470 var pkg, channel string 1471 if si := o.SourceInfo; si != nil { 1472 pkg = si.Package 1473 channel = si.Channel 1474 } 1475 csv := existingOperator(namespace, o.Name, pkg, channel, o.Replaces, o.ProvidedAPIs, o.RequiredAPIs, nil, nil) 1476 csvs = append(csvs, csv) 1477 if o.SourceInfo != nil && o.SourceInfo.Subscription != nil { 1478 o.SourceInfo.Subscription.Status = v1alpha1.SubscriptionStatus{ 1479 InstalledCSV: csv.Name, 1480 } 1481 } 1482 } 1483 1484 var err error 1485 operators, err = resolver.Resolve([]string{"olm"}, p.subs) 1486 assert.NoError(t, err) 1487 assert.ElementsMatch(t, p.expected, operators) 1488 }) 1489 } 1490 } 1491 1492 func genEntry(name, version, replaces, pkg, channel, catalogName, catalogNamespace string, requiredAPIs, providedAPIs cache.APISet, dependencies []*api.Dependency, defaultChannel string, deprecated bool) *cache.Entry { 1493 semversion, _ := semver.Make(version) 1494 properties := apiSetToProperties(providedAPIs, nil, deprecated) 1495 if len(dependencies) == 0 { 1496 ps, err := requiredAPIsToProperties(requiredAPIs) 1497 if err != nil { 1498 panic(err) 1499 } 1500 properties = append(properties, ps...) 1501 } else { 1502 ps, err := legacyDependenciesToProperties(dependencies) 1503 if err != nil { 1504 panic(err) 1505 } 1506 properties = append(properties, ps...) 1507 } 1508 o := &cache.Entry{ 1509 Name: name, 1510 Version: &semversion, 1511 Replaces: replaces, 1512 Properties: properties, 1513 SourceInfo: &cache.OperatorSourceInfo{ 1514 Catalog: cache.SourceKey{ 1515 Name: catalogName, 1516 Namespace: catalogNamespace, 1517 }, 1518 DefaultChannel: defaultChannel != "" && channel == defaultChannel, 1519 Package: pkg, 1520 Channel: channel, 1521 }, 1522 ProvidedAPIs: providedAPIs, 1523 RequiredAPIs: requiredAPIs, 1524 } 1525 EnsurePackageProperty(o, pkg, version) 1526 return o 1527 } 1528 1529 func TestSolveOperators_WithoutDeprecated(t *testing.T) { 1530 catalog := cache.SourceKey{Name: "catalog", Namespace: "namespace"} 1531 1532 subs := []*v1alpha1.Subscription{ 1533 newSub(catalog.Namespace, "test-package", "alpha", catalog), 1534 } 1535 1536 resolver := Resolver{ 1537 cache: cache.New(cache.StaticSourceProvider{ 1538 catalog: &cache.Snapshot{ 1539 Entries: []*cache.Entry{ 1540 genEntry("test-package.v1", "0.0.1", "", "test-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", true), 1541 }, 1542 }, 1543 }), 1544 log: logrus.New(), 1545 } 1546 1547 operators, err := resolver.Resolve([]string{catalog.Namespace}, subs) 1548 assert.Empty(t, operators) 1549 assert.IsType(t, solver.NotSatisfiable{}, err) 1550 } 1551 1552 func TestSolveOperatorsWithDeprecatedInnerChannelEntry(t *testing.T) { 1553 catalog := cache.SourceKey{Name: "catalog", Namespace: "namespace"} 1554 1555 subs := []*v1alpha1.Subscription{ 1556 newSub(catalog.Namespace, "a", "c", catalog), 1557 } 1558 logger, _ := test.NewNullLogger() 1559 resolver := Resolver{ 1560 cache: cache.New(cache.StaticSourceProvider{ 1561 catalog: &cache.Snapshot{ 1562 Entries: []*cache.Entry{ 1563 genEntry("a-1", "1.0.0", "", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), 1564 genEntry("a-2", "2.0.0", "a-1", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", true), 1565 genEntry("a-3", "3.0.0", "a-2", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), 1566 }, 1567 }, 1568 }), 1569 log: logger, 1570 } 1571 1572 operators, err := resolver.Resolve([]string{catalog.Namespace}, subs) 1573 assert.NoError(t, err) 1574 assert.ElementsMatch(t, []*cache.Entry{genEntry("a-3", "3.0.0", "a-2", "a", "c", catalog.Name, catalog.Namespace, nil, nil, nil, "", false)}, operators) 1575 } 1576 1577 func TestSolveOperators_WithSkipsAndStartingCSV(t *testing.T) { 1578 APISet := cache.APISet{testGVKKey: struct{}{}} 1579 Provides := APISet 1580 1581 namespace := "olm" 1582 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 1583 1584 newSub := newSub(namespace, "another-package", "alpha", catalog, withStartingCSV("another-package.v1")) 1585 subs := []*v1alpha1.Subscription{newSub} 1586 1587 opToAddVersionDeps := []*api.Dependency{ 1588 { 1589 Type: "olm.gvk", 1590 Value: `{"group":"g","kind":"k","version":"v"}`, 1591 }, 1592 } 1593 1594 opB := genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false) 1595 opB2 := genEntry("another-package.v2", "2.0.0", "", "another-package", "alpha", "community", "olm", nil, nil, opToAddVersionDeps, "", false) 1596 opB2.Skips = []string{"another-package.v1"} 1597 op1 := genEntry("test-package.v1", "1.0.0", "", "test-package", "alpha", "community", "olm", nil, Provides, nil, "", false) 1598 op2 := genEntry("test-package.v2", "2.0.0", "test-package.v1", "test-package", "alpha", "community", "olm", nil, Provides, nil, "", false) 1599 op3 := genEntry("test-package.v3", "3.0.0", "test-package.v2", "test-package", "alpha", "community", "olm", nil, Provides, nil, "", false) 1600 op4 := genEntry("test-package.v4", "4.0.0", "test-package.v3", "test-package", "alpha", "community", "olm", nil, Provides, nil, "", false) 1601 op4.Skips = []string{"test-package.v3"} 1602 op5 := genEntry("test-package.v5", "5.0.0", "test-package.v4", "test-package", "alpha", "community", "olm", nil, Provides, nil, "", false) 1603 op5.Skips = []string{"test-package.v2", "test-package.v3", "test-package.v4"} 1604 op6 := genEntry("test-package.v6", "6.0.0", "test-package.v5", "test-package", "alpha", "community", "olm", nil, Provides, nil, "", false) 1605 1606 resolver := Resolver{ 1607 cache: cache.New(cache.StaticSourceProvider{ 1608 catalog: &cache.Snapshot{ 1609 Entries: []*cache.Entry{ 1610 opB, opB2, op1, op2, op3, op4, op5, op6, 1611 }, 1612 }, 1613 }), 1614 log: logrus.New(), 1615 } 1616 1617 operators, err := resolver.Resolve([]string{"olm"}, subs) 1618 assert.NoError(t, err) 1619 opB.SourceInfo.StartingCSV = "another-package.v1" 1620 expected := []*cache.Entry{opB, op6} 1621 require.ElementsMatch(t, expected, operators) 1622 } 1623 1624 func TestSolveOperators_WithSkips(t *testing.T) { 1625 const namespace = "test-namespace" 1626 catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace} 1627 1628 newSub := newSub(namespace, "another-package", "alpha", catalog) 1629 subs := []*v1alpha1.Subscription{newSub} 1630 1631 opB := genEntry("another-package.v1", "1.0.0", "", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false) 1632 opB2 := genEntry("another-package.v2", "2.0.0", "", "another-package", "alpha", catalog.Name, catalog.Namespace, nil, nil, nil, "", false) 1633 opB2.Skips = []string{"another-package.v1"} 1634 1635 resolver := Resolver{ 1636 cache: cache.New(cache.StaticSourceProvider{ 1637 catalog: &cache.Snapshot{ 1638 Entries: []*cache.Entry{ 1639 opB, opB2, 1640 }, 1641 }, 1642 }), 1643 log: logrus.New(), 1644 } 1645 1646 operators, err := resolver.Resolve([]string{namespace}, subs) 1647 assert.NoError(t, err) 1648 expected := []*cache.Entry{opB2} 1649 require.ElementsMatch(t, expected, operators) 1650 } 1651 1652 func TestSolveOperatorsWithSkipsPreventingSelection(t *testing.T) { 1653 const namespace = "test-namespace" 1654 catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace} 1655 gvks := cache.APISet{testGVKKey: struct{}{}} 1656 1657 // Subscription candidate a-1 requires a GVK provided 1658 // exclusively by b-1, but b-1 is skipped by b-3 and can't be 1659 // chosen. 1660 subs := []*v1alpha1.Subscription{newSub(namespace, "a", "channel", catalog)} 1661 a1 := genEntry("a-1", "1.0.0", "", "a", "channel", catalog.Name, catalog.Namespace, gvks, nil, nil, "", false) 1662 b3 := genEntry("b-3", "3.0.0", "b-2", "b", "channel", catalog.Name, catalog.Namespace, nil, nil, nil, "", false) 1663 b3.Skips = []string{"b-1"} 1664 b2 := genEntry("b-2", "2.0.0", "b-1", "b", "channel", catalog.Name, catalog.Namespace, nil, nil, nil, "", false) 1665 b1 := genEntry("b-1", "1.0.0", "", "b", "channel", catalog.Name, catalog.Namespace, nil, gvks, nil, "", false) 1666 1667 logger, _ := test.NewNullLogger() 1668 resolver := Resolver{ 1669 cache: cache.New(cache.StaticSourceProvider{ 1670 catalog: &cache.Snapshot{ 1671 Entries: []*cache.Entry{a1, b3, b2, b1}, 1672 }, 1673 }), 1674 log: logger, 1675 } 1676 1677 _, err := resolver.Resolve([]string{namespace}, subs) 1678 assert.IsType(t, solver.NotSatisfiable{}, err) 1679 } 1680 1681 func TestSolveOperatorsWithClusterServiceVersionHavingDependency(t *testing.T) { 1682 const namespace = "test-namespace" 1683 catalog := cache.SourceKey{Name: "test-catalog", Namespace: namespace} 1684 virtual := cache.NewVirtualSourceKey(namespace) 1685 1686 subs := []*v1alpha1.Subscription{ 1687 existingSub(namespace, "b-1", "b", "default", catalog), 1688 } 1689 1690 log, _ := test.NewNullLogger() 1691 r := Resolver{ 1692 cache: cache.New(cache.StaticSourceProvider{ 1693 catalog: &cache.Snapshot{ 1694 Entries: []*cache.Entry{ 1695 { 1696 Name: "b-2", 1697 Replaces: "b-1", 1698 Version: &semver.Version{}, 1699 SourceInfo: &cache.OperatorSourceInfo{ 1700 Package: "b", 1701 Channel: "default", 1702 Catalog: catalog, 1703 }, 1704 }, 1705 }, 1706 }, 1707 virtual: &cache.Snapshot{ 1708 Entries: []*cache.Entry{ 1709 { 1710 Name: "a-1", 1711 Properties: []*api.Property{ 1712 {Type: "olm.package.required", Value: `{"packageName":"b","versionRange":"1.0.0"}`}, 1713 }, 1714 Version: &semver.Version{}, 1715 SourceInfo: &cache.OperatorSourceInfo{ 1716 Catalog: virtual, 1717 }, 1718 }, 1719 { 1720 Name: "b-1", 1721 Properties: []*api.Property{ 1722 {Type: "olm.package", Value: `{"packageName":"b","version":"1.0.0"}`}, 1723 }, 1724 Version: &semver.Version{}, 1725 SourceInfo: &cache.OperatorSourceInfo{ 1726 Catalog: virtual, 1727 }, 1728 }, 1729 }, 1730 }, 1731 }), 1732 log: log, 1733 } 1734 1735 operators, err := r.Resolve([]string{namespace}, subs) 1736 assert.NoError(t, err) 1737 require.Empty(t, operators) 1738 } 1739 1740 func TestSortChannel(t *testing.T) { 1741 for _, tc := range []struct { 1742 Name string 1743 In []*cache.Entry 1744 Out []*cache.Entry 1745 Err error 1746 }{ 1747 { 1748 Name: "wrinkle-free", 1749 In: []*cache.Entry{ 1750 { 1751 Name: "b", 1752 SourceInfo: &cache.OperatorSourceInfo{ 1753 Package: "package", 1754 Channel: "channel", 1755 }, 1756 }, 1757 { 1758 Name: "a", 1759 Replaces: "b", 1760 SourceInfo: &cache.OperatorSourceInfo{ 1761 Package: "package", 1762 Channel: "channel", 1763 }, 1764 }, 1765 }, 1766 Out: []*cache.Entry{ 1767 { 1768 Name: "a", 1769 Replaces: "b", 1770 SourceInfo: &cache.OperatorSourceInfo{ 1771 Package: "package", 1772 Channel: "channel", 1773 }, 1774 }, 1775 { 1776 Name: "b", 1777 SourceInfo: &cache.OperatorSourceInfo{ 1778 Package: "package", 1779 Channel: "channel", 1780 }, 1781 }, 1782 }, 1783 }, 1784 { 1785 Name: "empty", 1786 In: nil, 1787 Out: nil, 1788 }, 1789 { 1790 Name: "replacement cycle", 1791 In: []*cache.Entry{ 1792 { 1793 Name: "a", 1794 Replaces: "b", 1795 SourceInfo: &cache.OperatorSourceInfo{ 1796 Package: "package", 1797 Channel: "channel", 1798 }, 1799 }, 1800 { 1801 Name: "b", 1802 Replaces: "a", 1803 SourceInfo: &cache.OperatorSourceInfo{ 1804 Package: "package", 1805 Channel: "channel", 1806 }, 1807 }, 1808 }, 1809 Err: errors.New(`no channel heads (entries not replaced by another entry) found in channel "channel" of package "package"`), 1810 }, 1811 { 1812 Name: "replacement cycle", 1813 In: []*cache.Entry{ 1814 { 1815 Name: "a", 1816 Replaces: "b", 1817 SourceInfo: &cache.OperatorSourceInfo{ 1818 Package: "package", 1819 Channel: "channel", 1820 }, 1821 }, 1822 { 1823 Name: "b", 1824 Replaces: "c", 1825 SourceInfo: &cache.OperatorSourceInfo{ 1826 Package: "package", 1827 Channel: "channel", 1828 }, 1829 }, 1830 { 1831 Name: "c", 1832 Replaces: "b", 1833 SourceInfo: &cache.OperatorSourceInfo{ 1834 Package: "package", 1835 Channel: "channel", 1836 }, 1837 }, 1838 }, 1839 Err: errors.New(`a cycle exists in the chain of replacement beginning with "a" in channel "channel" of package "package"`), 1840 }, 1841 { 1842 Name: "skipped and replaced entry omitted", 1843 In: []*cache.Entry{ 1844 { 1845 Name: "a", 1846 Replaces: "b", 1847 Skips: []string{"b"}, 1848 }, 1849 { 1850 Name: "b", 1851 }, 1852 }, 1853 Out: []*cache.Entry{ 1854 { 1855 Name: "a", 1856 Replaces: "b", 1857 Skips: []string{"b"}, 1858 }, 1859 }, 1860 }, 1861 { 1862 Name: "skipped entry omitted", 1863 In: []*cache.Entry{ 1864 { 1865 Name: "a", 1866 Replaces: "b", 1867 Skips: []string{"c"}, 1868 }, 1869 { 1870 Name: "b", 1871 Replaces: "c", 1872 }, 1873 { 1874 Name: "c", 1875 }, 1876 }, 1877 Out: []*cache.Entry{ 1878 { 1879 Name: "a", 1880 Replaces: "b", 1881 Skips: []string{"c"}, 1882 }, 1883 { 1884 Name: "b", 1885 Replaces: "c", 1886 }, 1887 }, 1888 }, 1889 { 1890 Name: "two replaces chains", 1891 In: []*cache.Entry{ 1892 { 1893 Name: "a", 1894 SourceInfo: &cache.OperatorSourceInfo{ 1895 Package: "package", 1896 Channel: "channel", 1897 }, 1898 }, 1899 { 1900 Name: "b", 1901 Replaces: "c", 1902 SourceInfo: &cache.OperatorSourceInfo{ 1903 Package: "package", 1904 Channel: "channel", 1905 }, 1906 }, 1907 { 1908 Name: "c", 1909 SourceInfo: &cache.OperatorSourceInfo{ 1910 Package: "package", 1911 Channel: "channel", 1912 }, 1913 }, 1914 }, 1915 Err: errors.New(`a unique replacement chain within a channel is required to determine the relative order between channel entries, but 2 replacement chains were found in channel "channel" of package "package": a, b...c`), 1916 }, 1917 } { 1918 t.Run(tc.Name, func(t *testing.T) { 1919 assert := assert.New(t) 1920 actual, err := sortChannel(tc.In) 1921 if tc.Err == nil { 1922 assert.NoError(err) 1923 } else { 1924 assert.EqualError(err, tc.Err.Error()) 1925 } 1926 assert.Equal(tc.Out, actual) 1927 }) 1928 } 1929 } 1930 1931 func TestSolveOperators_GenericConstraint(t *testing.T) { 1932 Provides1 := cache.APISet{opregistry.APIKey{Group: "g", Version: "v", Kind: "k", Plural: "ks"}: struct{}{}} 1933 namespace := "olm" 1934 catalog := cache.SourceKey{Name: "community", Namespace: namespace} 1935 1936 deps1 := []*api.Dependency{ 1937 { 1938 Type: "olm.constraint", 1939 Value: `{"failureMessage":"gvk-constraint", 1940 "cel":{"rule":"properties.exists(p, p.type == 'olm.gvk' && p.value == {'group': 'g', 'version': 'v', 'kind': 'k'})"}}`, 1941 }, 1942 } 1943 deps2 := []*api.Dependency{ 1944 { 1945 Type: "olm.constraint", 1946 Value: `{"failureMessage":"gvk2-constraint", 1947 "cel":{"rule":"properties.exists(p, p.type == 'olm.gvk' && p.value == {'group': 'g2', 'version': 'v', 'kind': 'k'})"}}`, 1948 }, 1949 } 1950 deps3 := []*api.Dependency{ 1951 { 1952 Type: "olm.constraint", 1953 Value: `{"failureMessage":"package-constraint", 1954 "cel":{"rule":"properties.exists(p, p.type == 'olm.package' && p.value.packageName == 'another-package' && (semver_compare(p.value.version, '1.0.1') == 0))"}}`, 1955 }, 1956 } 1957 1958 tests := []struct { 1959 name string 1960 isErr bool 1961 subs []*v1alpha1.Subscription 1962 catalog cache.Source 1963 expected []*cache.Entry 1964 message string 1965 }{ 1966 { 1967 // generic constraint for satisfiable gvk dependency 1968 name: "Generic Constraint/Satisfiable GVK Dependency", 1969 isErr: false, 1970 subs: []*v1alpha1.Subscription{ 1971 newSub(namespace, "test-package", "stable", catalog), 1972 }, 1973 catalog: &cache.Snapshot{ 1974 Entries: []*cache.Entry{ 1975 genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, nil, deps1, "", false), 1976 genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false), 1977 }, 1978 }, 1979 expected: []*cache.Entry{ 1980 genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, nil, deps1, "", false), 1981 genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "stable", false), 1982 }, 1983 }, 1984 { 1985 // generic constraint for NotSatisfiable gvk dependency 1986 name: "Generic Constraint/NotSatisfiable GVK Dependency", 1987 isErr: true, 1988 subs: []*v1alpha1.Subscription{ 1989 newSub(namespace, "test-package", "stable", catalog), 1990 }, 1991 catalog: &cache.Snapshot{ 1992 Entries: []*cache.Entry{ 1993 genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, nil, deps2, "", false), 1994 genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, nil, Provides1, nil, "", false), 1995 }, 1996 }, 1997 // unable to find satisfiable gvk dependency 1998 // resolve into nothing 1999 expected: nil, 2000 message: "gvk2-constraint", 2001 }, 2002 { 2003 // generic constraint for package constraint 2004 name: "Generic Constraint/Satisfiable Package Dependency", 2005 isErr: false, 2006 subs: []*v1alpha1.Subscription{ 2007 newSub(namespace, "test-package", "stable", catalog), 2008 }, 2009 catalog: &cache.Snapshot{ 2010 Entries: []*cache.Entry{ 2011 genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, nil, deps3, "", false), 2012 genEntry("opB.v1.0.0", "1.0.0", "", "another-package", "stable", catalog.Name, catalog.Namespace, nil, nil, nil, "", false), 2013 genEntry("opB.v1.0.1", "1.0.1", "opB.v1.0.0", "another-package", "stable", catalog.Name, catalog.Namespace, nil, nil, nil, "stable", false), 2014 genEntry("opB.v1.0.2", "1.0.2", "opB.v1.0.1", "another-package", "stable", catalog.Name, catalog.Namespace, nil, nil, nil, "stable", false), 2015 }, 2016 }, 2017 expected: []*cache.Entry{ 2018 genEntry("opA.v1.0.0", "1.0.0", "", "test-package", "stable", catalog.Name, catalog.Namespace, nil, nil, deps3, "", false), 2019 genEntry("opB.v1.0.1", "1.0.1", "opB.v1.0.0", "another-package", "stable", catalog.Name, catalog.Namespace, nil, nil, nil, "stable", false), 2020 }, 2021 }, 2022 } 2023 2024 for _, tt := range tests { 2025 t.Run(tt.name, func(t *testing.T) { 2026 resolver := Resolver{ 2027 cache: cache.New(cache.StaticSourceProvider{ 2028 catalog: tt.catalog, 2029 }), 2030 log: logrus.New(), 2031 pc: &predicateConverter{ 2032 celEnv: constraints.NewCelEnvironment(), 2033 }, 2034 } 2035 2036 operators, err := resolver.Resolve([]string{namespace}, tt.subs) 2037 if tt.isErr { 2038 assert.Error(t, err) 2039 assert.Contains(t, err.Error(), tt.message) 2040 } else { 2041 assert.NoError(t, err) 2042 } 2043 assert.ElementsMatch(t, tt.expected, operators) 2044 }) 2045 } 2046 }