diff --git a/staging/operator-lifecycle-manager/pkg/controller/operators/openshift/helpers_test.go b/staging/operator-lifecycle-manager/pkg/controller/operators/openshift/helpers_test.go index 43d8c5e63f..3ac1946700 100644 --- a/staging/operator-lifecycle-manager/pkg/controller/operators/openshift/helpers_test.go +++ b/staging/operator-lifecycle-manager/pkg/controller/operators/openshift/helpers_test.go @@ -815,3 +815,20 @@ func TestNormalizeOCPVersion(t *testing.T) { }) } } + +func TestSkewsStringIsDeterministic(t *testing.T) { + // The informer cache lists CSVs in map order. The condition message must not + // depend on that order, or every reorder counts as a condition transition. + a := skew{namespace: "openshift-logging", name: "cluster-logging.v6.6.1", maxOpenShiftVersion: "4.23"} + b := skew{namespace: "openshift-cluster-csi-drivers", name: "secrets-store-csi-driver-operator.v4.22.0", maxOpenShiftVersion: "5.0"} + invalid := skew{namespace: "openshift-logging", name: "broken.v1.0.0"} + + expected := "ClusterServiceVersions blocking minor version upgrades to 5.1 or higher:\n" + + invalid.String() + "\n" + // error skews first + b.String() + "\n" + // then by namespace/name + a.String() + + require.Equal(t, expected, skews{a, b, invalid}.String()) + require.Equal(t, expected, skews{invalid, b, a}.String()) + require.Equal(t, expected, skews{b, invalid, a}.String()) +} diff --git a/tests-extension/pkg/bindata/qe/bindata.go b/tests-extension/pkg/bindata/qe/bindata.go index bbc3d2fcd2..ac093de318 100644 --- a/tests-extension/pkg/bindata/qe/bindata.go +++ b/tests-extension/pkg/bindata/qe/bindata.go @@ -359,6 +359,7 @@ objects: spec: image: "${ADDRESS}" grpcPodConfig: + securityContextConfig: restricted extractContent: catalogDir: /configs secrets: @@ -412,6 +413,7 @@ objects: spec: image: "${ADDRESS}" grpcPodConfig: + securityContextConfig: restricted extractContent: cacheDir: /tmp/cache catalogDir: /configs @@ -465,15 +467,17 @@ objects: namespace: "${NAMESPACE}" spec: image: "${ADDRESS}" + grpcPodConfig: + securityContextConfig: restricted secrets: - - "${SECRET}" + - "${SECRET}" displayName: "${DISPLAYNAME}" icon: base64data: "" mediatype: "" publisher: "${PUBLISHER}" sourceType: "${SOURCETYPE}" - updateStrategy: + updateStrategy: registryPoll: {} parameters: - name: NAME @@ -482,7 +486,8 @@ parameters: - name: DISPLAYNAME - name: PUBLISHER - name: SOURCETYPE -- name: SECRET`) +- name: SECRET +`) func testQeTestdataOlmCatalogsourceImageIncorrectUpdatestrategyYamlBytes() ([]byte, error) { return _testQeTestdataOlmCatalogsourceImageIncorrectUpdatestrategyYaml, nil @@ -510,6 +515,10 @@ objects: name: "${NAME}" namespace: "${NAMESPACE}" spec: + grpcPodConfig: + securityContextConfig: restricted + nodeSelector: + kubernetes.io/arch: "${ARCH}" image: "${ADDRESS}" secrets: - "${SECRET}" @@ -532,6 +541,7 @@ parameters: - name: SECRET - name: INTERVAL value: "10m0s" +- name: ARCH `) func testQeTestdataOlmCatalogsourceImageYamlBytes() ([]byte, error) { @@ -641,6 +651,7 @@ objects: namespace: "${NAMESPACE}" spec: grpcPodConfig: + securityContextConfig: restricted extractContent: cacheDir: /tmp/cache catalogDir: /configs @@ -6728,8 +6739,10 @@ objects: namespace: "${NAMESPACE}" spec: image: "${ADDRESS}" + grpcPodConfig: + securityContextConfig: restricted secrets: - - "${SECRET}" + - "${SECRET}" displayName: "${DISPLAYNAME}" publisher: "${PUBLISHER}" sourceType: "${SOURCETYPE}" @@ -6820,8 +6833,10 @@ objects: namespace: "${NAMESPACE}" spec: image: "${ADDRESS}" + grpcPodConfig: + securityContextConfig: restricted secrets: - - "${SECRET}" + - "${SECRET}" displayName: "${DISPLAYNAME}" publisher: "${PUBLISHER}" sourceType: "${SOURCETYPE}" @@ -6952,6 +6967,8 @@ objects: name: "${NAME}" namespace: "${NAMESPACE}" spec: + nodeSelector: + kubernetes.io/arch: "${ARCH}" output: to: kind: ImageStreamTag @@ -6968,6 +6985,7 @@ parameters: - name: NAME - name: NAMESPACE - name: BASE_IMAGE +- name: ARCH `) func testQeTestdataOlmCustomSchemaBuildconfigYamlBytes() ([]byte, error) { diff --git a/tests-extension/test/qe/specs/olmv0_custom_schema.go b/tests-extension/test/qe/specs/olmv0_custom_schema.go index 3171a227b9..2819e99ee6 100644 --- a/tests-extension/test/qe/specs/olmv0_custom_schema.go +++ b/tests-extension/test/qe/specs/olmv0_custom_schema.go @@ -53,7 +53,10 @@ var _ = g.Describe("[sig-operator][Jira:OLM][OCPFeatureGate:OLMLifecycleAndCompa fbcContent, err := os.ReadFile(exutil.FixturePath("testdata", "custom-schema", "index.json")) o.Expect(err).NotTo(o.HaveOccurred()) - imageRef := olmv0util.BuildCustomCatalogImage(oc, namespace, catalogName, baseImage, fbcContent) + testArch := olmv0util.GetNodeArch(oc) + e2e.Logf("test running on architecture: %s", testArch) + + imageRef := olmv0util.BuildCustomCatalogImage(oc, namespace, catalogName, baseImage, testArch, fbcContent) e2e.Logf("built catalog image: %s", imageRef) // Register build resources for cleanup @@ -67,6 +70,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM][OCPFeatureGate:OLMLifecycleAndCompa SourceType: "grpc", Address: imageRef, Template: exutil.FixturePath("testdata", "olm", "catalogsource-image.yaml"), + Arch: testArch, } catsrc.CreateWithCheck(oc, itName, dr) diff --git a/tests-extension/test/qe/testdata/olm/catalogsource-image-cacheless.yaml b/tests-extension/test/qe/testdata/olm/catalogsource-image-cacheless.yaml index a63f50fa1b..03e8bf8fb9 100644 --- a/tests-extension/test/qe/testdata/olm/catalogsource-image-cacheless.yaml +++ b/tests-extension/test/qe/testdata/olm/catalogsource-image-cacheless.yaml @@ -11,6 +11,7 @@ objects: spec: image: "${ADDRESS}" grpcPodConfig: + securityContextConfig: restricted extractContent: catalogDir: /configs secrets: diff --git a/tests-extension/test/qe/testdata/olm/catalogsource-image-extract.yaml b/tests-extension/test/qe/testdata/olm/catalogsource-image-extract.yaml index e01c47ff2e..f359383c29 100644 --- a/tests-extension/test/qe/testdata/olm/catalogsource-image-extract.yaml +++ b/tests-extension/test/qe/testdata/olm/catalogsource-image-extract.yaml @@ -11,6 +11,7 @@ objects: spec: image: "${ADDRESS}" grpcPodConfig: + securityContextConfig: restricted extractContent: cacheDir: /tmp/cache catalogDir: /configs diff --git a/tests-extension/test/qe/testdata/olm/catalogsource-image-incorrect-updatestrategy.yaml b/tests-extension/test/qe/testdata/olm/catalogsource-image-incorrect-updatestrategy.yaml index e98530a23e..2b2ef167eb 100644 --- a/tests-extension/test/qe/testdata/olm/catalogsource-image-incorrect-updatestrategy.yaml +++ b/tests-extension/test/qe/testdata/olm/catalogsource-image-incorrect-updatestrategy.yaml @@ -10,15 +10,17 @@ objects: namespace: "${NAMESPACE}" spec: image: "${ADDRESS}" + grpcPodConfig: + securityContextConfig: restricted secrets: - - "${SECRET}" + - "${SECRET}" displayName: "${DISPLAYNAME}" icon: base64data: "" mediatype: "" publisher: "${PUBLISHER}" sourceType: "${SOURCETYPE}" - updateStrategy: + updateStrategy: registryPoll: {} parameters: - name: NAME @@ -27,4 +29,4 @@ parameters: - name: DISPLAYNAME - name: PUBLISHER - name: SOURCETYPE -- name: SECRET \ No newline at end of file +- name: SECRET diff --git a/tests-extension/test/qe/testdata/olm/catalogsource-image.yaml b/tests-extension/test/qe/testdata/olm/catalogsource-image.yaml index ebb5d8624f..4c3e1bb433 100644 --- a/tests-extension/test/qe/testdata/olm/catalogsource-image.yaml +++ b/tests-extension/test/qe/testdata/olm/catalogsource-image.yaml @@ -9,6 +9,10 @@ objects: name: "${NAME}" namespace: "${NAMESPACE}" spec: + grpcPodConfig: + securityContextConfig: restricted + nodeSelector: + kubernetes.io/arch: "${ARCH}" image: "${ADDRESS}" secrets: - "${SECRET}" @@ -31,3 +35,4 @@ parameters: - name: SECRET - name: INTERVAL value: "10m0s" +- name: ARCH diff --git a/tests-extension/test/qe/testdata/olm/catalogsource-opm.yaml b/tests-extension/test/qe/testdata/olm/catalogsource-opm.yaml index dfc1edfc4e..eee5cff217 100644 --- a/tests-extension/test/qe/testdata/olm/catalogsource-opm.yaml +++ b/tests-extension/test/qe/testdata/olm/catalogsource-opm.yaml @@ -10,6 +10,7 @@ objects: namespace: "${NAMESPACE}" spec: grpcPodConfig: + securityContextConfig: restricted extractContent: cacheDir: /tmp/cache catalogDir: /configs diff --git a/tests-extension/test/qe/testdata/olm/cs-image-template.yaml b/tests-extension/test/qe/testdata/olm/cs-image-template.yaml index e08034128a..dac6362fe0 100644 --- a/tests-extension/test/qe/testdata/olm/cs-image-template.yaml +++ b/tests-extension/test/qe/testdata/olm/cs-image-template.yaml @@ -12,8 +12,10 @@ objects: namespace: "${NAMESPACE}" spec: image: "${ADDRESS}" + grpcPodConfig: + securityContextConfig: restricted secrets: - - "${SECRET}" + - "${SECRET}" displayName: "${DISPLAYNAME}" publisher: "${PUBLISHER}" sourceType: "${SOURCETYPE}" diff --git a/tests-extension/test/qe/testdata/olm/cs-without-interval.yaml b/tests-extension/test/qe/testdata/olm/cs-without-interval.yaml index 4a3f9dd0c5..5ec3e4a025 100644 --- a/tests-extension/test/qe/testdata/olm/cs-without-interval.yaml +++ b/tests-extension/test/qe/testdata/olm/cs-without-interval.yaml @@ -12,8 +12,10 @@ objects: namespace: "${NAMESPACE}" spec: image: "${ADDRESS}" + grpcPodConfig: + securityContextConfig: restricted secrets: - - "${SECRET}" + - "${SECRET}" displayName: "${DISPLAYNAME}" publisher: "${PUBLISHER}" sourceType: "${SOURCETYPE}" diff --git a/tests-extension/test/qe/testdata/olm/custom-schema-buildconfig.yaml b/tests-extension/test/qe/testdata/olm/custom-schema-buildconfig.yaml index bed1592c29..1415f72ba7 100644 --- a/tests-extension/test/qe/testdata/olm/custom-schema-buildconfig.yaml +++ b/tests-extension/test/qe/testdata/olm/custom-schema-buildconfig.yaml @@ -9,6 +9,8 @@ objects: name: "${NAME}" namespace: "${NAMESPACE}" spec: + nodeSelector: + kubernetes.io/arch: "${ARCH}" output: to: kind: ImageStreamTag @@ -25,3 +27,4 @@ parameters: - name: NAME - name: NAMESPACE - name: BASE_IMAGE +- name: ARCH diff --git a/tests-extension/test/qe/util/olmv0util/catalog_source.go b/tests-extension/test/qe/util/olmv0util/catalog_source.go index 4db214c31e..361fa3fd88 100644 --- a/tests-extension/test/qe/util/olmv0util/catalog_source.go +++ b/tests-extension/test/qe/util/olmv0util/catalog_source.go @@ -6,6 +6,7 @@ package olmv0util import ( "context" "fmt" + "runtime" "strings" "time" @@ -33,6 +34,25 @@ type CatalogSourceDescription struct { Interval string // Update interval for the catalog source ImageTemplate string // Image template for catalog updates ClusterType string // Target cluster type (e.g., "microshift") + Arch string // Architecture for node selector +} + +// Query architecture directly from a worker node's status. +// Use worker nodes specifically since BuildConfig and CatalogSource +// pods are scheduled on worker nodes. +func GetNodeArch(oc *exutil.CLI) string { + arch, err := oc.AsAdmin().WithoutNamespace().Run("get").Args( + "nodes", + "-l", "node-role.kubernetes.io/worker", + "--field-selector", "spec.unschedulable!=true", + "-o", "jsonpath={.items[0].status.nodeInfo.architecture}", + ).Output() + if err == nil && len(strings.TrimSpace(arch)) > 0 { + return strings.TrimSpace(arch) + } + + e2e.Logf("failed to get node architecture from cluster (%v), falling back to runtime.GOARCH (%s)", err, runtime.GOARCH) + return runtime.GOARCH } // Create creates a CatalogSource resource using the provided template and parameters @@ -49,6 +69,11 @@ func (catsrc *CatalogSourceDescription) Create(oc *exutil.CLI, itName string, dr catsrc.Interval = "10m0s" e2e.Logf("set interval to be 10m0s") } + + if catsrc.Arch == "" { + catsrc.Arch = GetNodeArch(oc) + e2e.Logf("set catalogsource node selector arch to %s", catsrc.Arch) + } // Choose appropriate template application function based on cluster type applyFn := ApplyResourceFromTemplate if strings.Compare(catsrc.ClusterType, "microshift") == 0 { @@ -63,7 +88,7 @@ func (catsrc *CatalogSourceDescription) Create(oc *exutil.CLI, itName string, dr err := applyFn(oc, "--ignore-unknown-parameters=true", "-f", catsrc.Template, "-p", "NAME="+catsrc.Name, "NAMESPACE="+catsrc.Namespace, "ADDRESS="+catsrc.Address, "SECRET="+catsrc.Secret, "DISPLAYNAME="+"\""+catsrc.DisplayName+"\"", "PUBLISHER="+"\""+catsrc.Publisher+"\"", "SOURCETYPE="+catsrc.SourceType, - "INTERVAL="+catsrc.Interval, "IMAGETEMPLATE="+imageTemplate) + "INTERVAL="+catsrc.Interval, "IMAGETEMPLATE="+imageTemplate, "ARCH="+catsrc.Arch) o.Expect(err).NotTo(o.HaveOccurred()) // Configure security context constraints for non-microshift clusters if strings.Compare(catsrc.ClusterType, "microshift") != 0 { diff --git a/tests-extension/test/qe/util/olmv0util/custom_schema_grpc.go b/tests-extension/test/qe/util/olmv0util/custom_schema_grpc.go index 6138cb8c1b..c19a972e87 100644 --- a/tests-extension/test/qe/util/olmv0util/custom_schema_grpc.go +++ b/tests-extension/test/qe/util/olmv0util/custom_schema_grpc.go @@ -123,7 +123,7 @@ func GetOPMBaseImage(oc *exutil.CLI) string { // BuildConfig. It creates an ImageStream and BuildConfig, starts a binary build // from the provided FBC content, and waits for completion. // Returns the internal registry image reference. -func BuildCustomCatalogImage(oc *exutil.CLI, namespace, name, baseImage string, fbcContent []byte) string { +func BuildCustomCatalogImage(oc *exutil.CLI, namespace, name, baseImage, arch string, fbcContent []byte) string { // Create ImageStream isTemplate := exutil.FixturePath("testdata", "olm", "custom-schema-imagestream.yaml") err := ApplyResourceFromTemplate(oc, "--ignore-unknown-parameters=true", "-f", isTemplate, @@ -132,11 +132,14 @@ func BuildCustomCatalogImage(oc *exutil.CLI, namespace, name, baseImage string, e2e.Logf("created ImageStream %s/%s", namespace, name) // Create BuildConfig + if arch == "" { + arch = GetNodeArch(oc) + } bcTemplate := exutil.FixturePath("testdata", "olm", "custom-schema-buildconfig.yaml") err = ApplyResourceFromTemplate(oc, "--ignore-unknown-parameters=true", "-f", bcTemplate, - "-p", "NAME="+name, "NAMESPACE="+namespace, "BASE_IMAGE="+baseImage) + "-p", "NAME="+name, "NAMESPACE="+namespace, "BASE_IMAGE="+baseImage, "ARCH="+arch) o.Expect(err).NotTo(o.HaveOccurred()) - e2e.Logf("created BuildConfig %s/%s with base image %s", namespace, name, baseImage) + e2e.Logf("created BuildConfig %s/%s with base image %s for arch %s", namespace, name, baseImage, arch) // Prepare build directory buildDir, err := os.MkdirTemp("", "custom-schema-build-") diff --git a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/openshift/helpers.go b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/openshift/helpers.go index 2e85462db2..5d18bfe297 100644 --- a/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/openshift/helpers.go +++ b/vendor/github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/openshift/helpers.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "sort" "strings" "sync" @@ -77,22 +78,45 @@ func versionsMatch(a []configv1.OperandVersion, b []configv1.OperandVersion) boo type skews []skew func (s skews) String() string { - msg := make([]string, len(s)) - i, j := 0, len(s)-1 - for _, sk := range s { - m := sk.String() - // Partial order: error skews first - if sk.err != nil { - msg[i] = m - i++ - continue + // Sorting depends on a populated .err, to sort invalid skews + // first. incompatibleOperators sets .err through one call path, + // but check again here, to ensure reliability, regardless of call-chain. + var minVersion *semver.Version + for i, skew := range s { + if release, err := semver.ParseTolerant(skew.maxOpenShiftVersion); err != nil { + s[i].err = err + } else if minVersion == nil || normalizeOCPVersion(release).LT(*minVersion) { + v := normalizeOCPVersion(release) + minVersion = &v + } + } + + // Sort so the message does not depend on the (unstable) order in which the + // CSVs were listed: error skews first, then by namespace/name. A different + // order would be a different Message, and so a condition transition. + sorted := make(skews, len(s)) + copy(sorted, s) + sort.SliceStable(sorted, func(i, j int) bool { + a, b := sorted[i], sorted[j] + if (a.err != nil) != (b.err != nil) { + return a.err != nil + } + if a.namespace != b.namespace { + return a.namespace < b.namespace } - msg[j] = m - j-- + return a.name < b.name + }) + + msg := make([]string, len(sorted)) + for i, sk := range sorted { + msg[i] = sk.String() } - // TODO Revisit this message after 4.23 release once release schedule is determined for future possible minor versions. - return fmt.Sprintf("ClusterServiceVersions blocking minor version upgrades to 4.23 or major version upgrades to 5.0:\n%s", strings.Join(msg, "\n")) + if minVersion == nil { + return fmt.Sprintf("ClusterServiceVersions blocking major or minor version upgrades beyond the invalid SemVer %q (%s):\n%s", s[0].maxOpenShiftVersion, s[0].err, strings.Join(msg, "\n")) + } + nextY := nextY(*minVersion) + return fmt.Sprintf("ClusterServiceVersions blocking minor version upgrades to %d.%d or higher:\n%s", nextY.Major, nextY.Minor, strings.Join(msg, "\n")) } type skew struct { @@ -103,10 +127,17 @@ type skew struct { } func (s skew) String() string { - if s.err != nil { - return fmt.Sprintf("- %s/%s has invalid %s properties: %s", s.namespace, s.name, MaxOpenShiftVersionProperty, s.err) + // Sorting depends on a populated .err, to sort invalid skews + // first. incompatibleOperators sets .err through one call path, + // but check again here, to ensure reliability, regardless of call-chain. + if s.err == nil { + if _, err := semver.ParseTolerant(s.maxOpenShiftVersion); err != nil { + s.err = err + } else { + return fmt.Sprintf("- maximum supported OCP version for %s/%s is %s", s.namespace, s.name, s.maxOpenShiftVersion) + } } - return fmt.Sprintf("- maximum supported OCP version for %s/%s is %s", s.namespace, s.name, s.maxOpenShiftVersion) + return fmt.Sprintf("- %s/%s has invalid %s properties: %s", s.namespace, s.name, MaxOpenShiftVersionProperty, s.err) } type transientError struct { @@ -152,6 +183,7 @@ func incompatibleOperators(ctx context.Context, cli client.Client) (skews, error } max, err := maxOpenShiftVersion(&csv) if err != nil { + s.maxOpenShiftVersion = max s.err = err incompatible = append(incompatible, s) continue