Repository: openshift/assisted-service (language: python)

Issue:
MGMT-17515: Fix the CPU architecture query for in GetReleaseImage to having the necessary CPU architecture in the list and the list must be of size one to exclude multi architecture release images
Currently, `GetReleaseImage` function the filtering of release images matching the requested CPU architecture is done as follows - https://github.com/openshift/assisted-service/blob/4c9b4149f48ba05d7c2b831e97815173f47d3808/internal/versions/rest_api_versions.go#L51
This PR fixes the CPU architecture query to having the necessary CPU architecture in the list and the list must be of size one to exclude multi architecture release images.


<!--
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.

You can refer to [Kubernetes community documentation] on writing good commit messages, which provides good tips and ideas.

Some PRs address specific issues. Please, refer to the [CONTRIBUTING] documentation for more
information on how to link a PR to an existing issue.

It's recommended to take a few extra minutes to provide more information about
how this code was tested. Here are some questions that may be worth answering:

- Should this PR be tested by the reviewer?
- Is this PR relying on CI for an e2e test run?
- Should this PR be tested in a specific environment?
- Any logs, screenshots, etc that can help with the review process?

-->

## List all the issues related to this PR

- [ ] New Feature <!-- new functionality -->
- [ ] Enhancement <!-- refactor, code changes, improvement, that won't add new features -->
- [x] Bug fix
- [ ] Tests
- [ ] Documentation
- [ ] CI/CD <!-- Notice that changes for Dockerfiles/Jenkinsfiles aren't tested in CI due to a known bug. -->

## What environments does this code impact?

- [ ] Automation (CI, tools, etc)
- [x] Cloud
- [ ] Operator Managed Deployments
- [ ] None

## How was this code tested?

<!-- Please, select one or more if needed: -->

- [ ] assisted-test-infra environment
- [ ] dev-scripts environment
- [ ] Reviewer's test appreciated
- [x] Waiting for CI to do a full test run
- [ ] Manual (Elaborate on how it was tested)
- [ ] No tests needed

## Checklist

- [x] Title and description added to both, commit and PR.
- [x] Relevant issues have been associated (see [CONTRIBUTING] guide)
- [x] This change does not require a documentation update (docstring, `docs`, README, etc)
- [x] Does this change include unit-tests (note that code changes require unit-tests)

## Reviewers Checklist

- Are the title and description (in both PR and commit) meaningful and clear?
- Is there a bug required (and linked) for this change?
- Should this PR be backported?

[Kubernetes community documentation]: https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#commit-message-guidelines
[CONTRIBUTING]: https://github.com/openshift/assisted-service/blob/master/CONTRIBUTING.md



Discussion / hints:

Fix patch:
diff --git a/internal/versions/rest_api_versions.go b/internal/versions/rest_api_versions.go
index 560104fe976..10e52814cb0 100644
--- a/internal/versions/rest_api_versions.go
+++ b/internal/versions/rest_api_versions.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/go-openapi/strfmt"
 	"github.com/go-openapi/swag"
+	"github.com/lib/pq"
 	"github.com/openshift/assisted-service/internal/common"
 	"github.com/openshift/assisted-service/internal/oc"
 	models "github.com/openshift/assisted-service/models"
@@ -49,17 +50,16 @@ func (h *restAPIVersionsHandler) GetReleaseImage(_ context.Context, openshiftVer
 	if cpuArchitecture == common.MultiCPUArchitecture {
 		query = query.Where("cardinality(cpu_architectures) > 1")
 	} else {
-		query = query.Where(h.db.Session(&gorm.Session{}).Where("? = ANY(cpu_architectures)", cpuArchitecture).Or("cpu_architecture = ?", cpuArchitecture))
+		query = query.Where(h.db.Session(&gorm.Session{}).
+			Where("cpu_architectures @> ? AND ? @> cpu_architectures", pq.StringArray{cpuArchitecture}, pq.StringArray{cpuArchitecture}).
+			Or("cpu_architecture = ?", cpuArchitecture))
 	}
 
 	// Find the exact version
 	if versionFormat == common.MajorMinorPatchVersion {
-		editedVersion := openshiftVersion
-		// add multi suffix if missing
-		if cpuArchitecture == common.MultiCPUArchitecture && !strings.HasSuffix(openshiftVersion, "-multi") {
-			editedVersion = editedVersion + "-multi"
-		}
-		query = query.Where("version = ?", editedVersion)
+		query = query.Where(h.db.Session(&gorm.Session{}).
+			Where("version = ?", openshiftVersion).
+			Or("version = ?", openshiftVersion+"-multi"))
 		var releaseImage models.ReleaseImage
 		err := query.Take(&releaseImage).Error
 		if err != nil {


Test patch:
diff --git a/internal/versions/rest_api_versions_test.go b/internal/versions/rest_api_versions_test.go
index c3e2d97debf..ece86e3e4cb 100644
--- a/internal/versions/rest_api_versions_test.go
+++ b/internal/versions/rest_api_versions_test.go
@@ -103,7 +103,15 @@ var _ = Describe("GetReleaseImage", func() {
 				SupportLevel:     models.ReleaseImageSupportLevelProduction,
 				Default:          false,
 			},
-
+			{
+				OpenshiftVersion: swag.String("4.14-multi"),
+				Version:          swag.String("4.14.3-multi"),
+				CPUArchitecture:  swag.String(common.MultiCPUArchitecture),
+				CPUArchitectures: []string{common.X86CPUArchitecture, common.ARM64CPUArchitecture},
+				URL:              swag.String("quay.io/openshift-release-dev/ocp-release:4.14.3-multi"),
+				SupportLevel:     models.ReleaseImageSupportLevelProduction,
+				Default:          false,
+			},
 			{
 				OpenshiftVersion: swag.String("4.15"),
 				Version:          swag.String("4.15.1"),
@@ -122,6 +130,24 @@ var _ = Describe("GetReleaseImage", func() {
 				SupportLevel:     models.OpenshiftVersionSupportLevelBeta,
 				Default:          false,
 			},
+			{
+				OpenshiftVersion: swag.String("4.16-multi"),
+				Version:          swag.String("4.16.1-multi"),
+				CPUArchitecture:  swag.String(common.MultiCPUArchitecture),
+				CPUArchitectures: []string{common.X86CPUArchitecture, common.ARM64CPUArchitecture},
+				URL:              swag.String("quay.io/openshift-release-dev/ocp-release:4.16.1-multi"),
+				SupportLevel:     models.ReleaseImageSupportLevelProduction,
+				Default:          false,
+			},
+			{
+				OpenshiftVersion: swag.String("4.16"),
+				Version:          swag.String("4.16.1"),
+				CPUArchitecture:  swag.String(common.X86CPUArchitecture),
+				CPUArchitectures: []string{common.X86CPUArchitecture},
+				URL:              swag.String("quay.io/openshift-release-dev/ocp-release:4.16.1-x86_64"),
+				SupportLevel:     models.ReleaseImageSupportLevelProduction,
+				Default:          false,
+			},
 		}
 
 		err := db.Create(releaseImages).Error
@@ -134,6 +160,10 @@ var _ = Describe("GetReleaseImage", func() {
 		releaseImage, err = handler.GetReleaseImage(ctx, "4.15", common.X86CPUArchitecture, pullSecret)
 		Expect(err).ShouldNot(HaveOccurred())
 		Expect(*releaseImage.Version).Should(Equal("4.15.2"))
+
+		releaseImage, err = handler.GetReleaseImage(ctx, "4.16", common.X86CPUArchitecture, pullSecret)
+		Expect(err).ShouldNot(HaveOccurred())
+		Expect(*releaseImage.Version).Should(Equal("4.16.1"))
 	})
 
 	It("gets the exact matching release image with major.minor.patch / prerelease openshiftVersion", func() {
@@ -206,6 +236,40 @@ var _ = Describe("GetReleaseImage", func() {
 		Expect(*releaseImage.Version).Should(Equal("4.14.2"))
 	})
 
+	It("gets successfully image using major.minor.patch openshiftVersion in multiarch query with different version format", func() {
+		releaseImages := models.ReleaseImages{
+			{
+				OpenshiftVersion: swag.String("4.16.0-0.nightly-arm64-2024-04-08-123354"),
+				Version:          swag.String("4.16.0-0.nightly-arm64-2024-04-08-123354"),
+				CPUArchitecture:  swag.String(common.ARM64CPUArchitecture),
+				CPUArchitectures: []string{common.ARM64CPUArchitecture},
+				URL:              swag.String("quay.io/foobar/4.16.0-0.nightly-arm64-2024-04-08-123354@foobar"),
+				SupportLevel:     models.ReleaseImageSupportLevelProduction,
+				Default:          false,
+			},
+			{
+				OpenshiftVersion: swag.String("4.16.0-0.nightly-multi-2024-04-08-123354"),
+				Version:          swag.String("4.16.0-0.nightly-multi-2024-04-08-123354"),
+				CPUArchitecture:  swag.String(common.MultiCPUArchitecture),
+				CPUArchitectures: []string{common.X86CPUArchitecture, common.ARM64CPUArchitecture},
+				URL:              swag.String("quay.io/foobar/4.16.0-0.nightly-multi-2024-04-08-123354@foobar"),
+				SupportLevel:     models.ReleaseImageSupportLevelProduction,
+				Default:          false,
+			},
+		}
+
+		err := db.Create(releaseImages).Error
+		Expect(err).ShouldNot(HaveOccurred())
+
+		releaseImage, err := handler.GetReleaseImage(ctx, "4.16.0-0.nightly-arm64-2024-04-08-123354", common.ARM64CPUArchitecture, pullSecret)
+		Expect(err).ShouldNot(HaveOccurred())
+		Expect(*releaseImage.Version).Should(Equal("4.16.0-0.nightly-arm64-2024-04-08-123354"))
+
+		releaseImage, err = handler.GetReleaseImage(ctx, "4.16.0-0.nightly-multi-2024-04-08-123354", common.MultiCPUArchitecture, pullSecret)
+		Expect(err).ShouldNot(HaveOccurred())
+		Expect(*releaseImage.Version).Should(Equal("4.16.0-0.nightly-multi-2024-04-08-123354"))
+	})
+
 	It("gets release image successfully with major.minor.patch openshiftVersion and old syntax", func() {
 		releaseImages := models.ReleaseImages{
 			{
@@ -374,6 +438,40 @@ var _ = Describe("GetReleaseImage", func() {
 		Expect(*releaseImage.Version).Should(Equal("4.14.2-multi"))
 	})
 
+	It("gets successfully image using major.minor.patch openshiftVersion in multiarch query with different version format", func() {
+		releaseImages := models.ReleaseImages{
+			{
+				OpenshiftVersion: swag.String("4.16.0-0.nightly-arm64-2024-04-08-123354"),
+				Version:          swag.String("4.16.0-0.nightly-arm64-2024-04-08-123354"),
+				CPUArchitecture:  swag.String(common.ARM64CPUArchitecture),
+				CPUArchitectures: []string{common.ARM64CPUArchitecture},
+				URL:              swag.String("quay.io/foobar/4.16.0-0.nightly-arm64-2024-04-08-123354@foobar"),
+				SupportLevel:     models.ReleaseImageSupportLevelProduction,
+				Default:          false,
+			},
+			{
+				OpenshiftVersion: swag.String("4.16.0-0.nightly-multi-2024-04-08-123354"),
+				Version:          swag.String("4.16.0-0.nightly-multi-2024-04-08-123354"),
+				CPUArchitecture:  swag.String(common.MultiCPUArchitecture),
+				CPUArchitectures: []string{common.X86CPUArchitecture, common.ARM64CPUArchitecture},
+				URL:              swag.String("quay.io/foobar/4.16.0-0.nightly-multi-2024-04-08-123354@foobar"),
+				SupportLevel:     models.ReleaseImageSupportLevelProduction,
+				Default:          false,
+			},
+		}
+
+		err := db.Create(releaseImages).Error
+		Expect(err).ShouldNot(HaveOccurred())
+
+		releaseImage, err := handler.GetReleaseImage(ctx, "4.16.0-0.nightly-arm64-2024-04-08-123354", common.ARM64CPUArchitecture, pullSecret)
+		Expect(err).ShouldNot(HaveOccurred())
+		Expect(*releaseImage.Version).Should(Equal("4.16.0-0.nightly-arm64-2024-04-08-123354"))
+
+		releaseImage, err = handler.GetReleaseImage(ctx, "4.16.0-0.nightly-multi-2024-04-08-123354", common.MultiCPUArchitecture, pullSecret)
+		Expect(err).ShouldNot(HaveOccurred())
+		Expect(*releaseImage.Version).Should(Equal("4.16.0-0.nightly-multi-2024-04-08-123354"))
+	})
+
 	It("returns an error when using major.minor.patch openshiftVersion but no exact match found", func() {
 		releaseImages := models.ReleaseImages{
 			{
diff --git a/subsystem/cluster_v2_test.go b/subsystem/cluster_v2_test.go
index 6efa87dcce2..305a0ae71a7 100644
--- a/subsystem/cluster_v2_test.go
+++ b/subsystem/cluster_v2_test.go
@@ -536,7 +536,7 @@ var _ = Describe("[V2ClusterTests] multiarch", func() {
 		var tmpPullSecret string
 		var clusterID strfmt.UUID
 
-		registerClusterForMultiArch := func(cpuArchitecture string) *models.Cluster {
+		registerClusterForMultiArch := func() *models.Cluster {
 			clusterReq, err := user2BMClient.Installer.V2RegisterCluster(ctx, &installer.V2RegisterClusterParams{
 				NewClusterParams: &models.ClusterCreateParams{
 					Name:                  swag.String("test-cluster"),
@@ -544,7 +544,7 @@ var _ = Describe("[V2ClusterTests] multiarch", func() {
 					PullSecret:            swag.String(fmt.Sprintf(psTemplate, FakePS2)),
 					BaseDNSDomain:         "example.com",
 					UserManagedNetworking: swag.Bool(true),
-					CPUArchitecture:       cpuArchitecture,
+					CPUArchitecture:       common.MultiCPUArchitecture,
 				},
 			})
 			Expect(err).NotTo(HaveOccurred())
@@ -572,7 +572,7 @@ var _ = Describe("[V2ClusterTests] multiarch", func() {
 		})
 
 		It("Default image type on s390x", func() {
-			cluster := registerClusterForMultiArch(models.ClusterCPUArchitectureS390x)
+			cluster := registerClusterForMultiArch()
 			infraEnv := registerInfraEnvSpecificVersionAndArch(cluster.ID, "", common.S390xCPUArchitecture, "")
 			Expect(*infraEnv.Type).To(Equal(models.ImageTypeFullIso))
 
@@ -591,7 +591,7 @@ var _ = Describe("[V2ClusterTests] multiarch", func() {
 			Expect(err).To(HaveOccurred())
 		})
 		It("Default image type on ppc64le", func() {
-			cluster := registerClusterForMultiArch(models.ClusterCPUArchitecturePpc64le)
+			cluster := registerClusterForMultiArch()
 			infraEnv := registerInfraEnvSpecificVersionAndArch(cluster.ID, "", common.PowerCPUArchitecture, "")
 			Expect(*infraEnv.Type).To(Equal(models.ImageTypeFullIso))
 
diff --git a/subsystem/feature_support_levels_test.go b/subsystem/feature_support_levels_test.go
index 53099e99569..3f088e5b885 100644
--- a/subsystem/feature_support_levels_test.go
+++ b/subsystem/feature_support_levels_test.go
@@ -101,10 +101,10 @@ var _ = Describe("Feature support levels API", func() {
 		})
 
 		Context("Update cluster", func() {
-			It("Update umn true won't fail on 4.13 with s390x without infra-env", func() {
-				cluster, err := registerNewCluster("4.13", "s390x", models.ClusterHighAvailabilityModeFull, swag.Bool(true))
+			It("Update umn true won't fail on 4.13 with multi release without infra-env", func() {
+				cluster, err := registerNewCluster("4.13", common.MultiCPUArchitecture, models.ClusterHighAvailabilityModeFull, swag.Bool(true))
 				Expect(err).NotTo(HaveOccurred())
-				Expect(cluster.Payload.CPUArchitecture).To(Equal("multi"))
+				Expect(cluster.Payload.CPUArchitecture).To(Equal(common.MultiCPUArchitecture))
 
 				_, err = user2BMClient.Installer.V2UpdateCluster(ctx, &installer.V2UpdateClusterParams{
 					ClusterUpdateParams: &models.V2ClusterUpdateParams{
@@ -117,9 +117,9 @@ var _ = Describe("Feature support levels API", func() {
 
 			It("Update umn true fail on 4.13 with s390x with infra-env", func() {
 				expectedError := "cannot use Cluster Managed Networking because it's not compatible with the s390x architecture on version 4.13"
-				cluster, err := registerNewCluster("4.13", "s390x", models.ClusterHighAvailabilityModeFull, swag.Bool(true))
+				cluster, err := registerNewCluster("4.13", common.MultiCPUArchitecture, models.ClusterHighAvailabilityModeFull, swag.Bool(true))
 				Expect(err).NotTo(HaveOccurred())
-				Expect(cluster.Payload.CPUArchitecture).To(Equal("multi"))
+				Expect(cluster.Payload.CPUArchitecture).To(Equal(common.MultiCPUArchitecture))
 
 				infraEnv, err := registerNewInfraEnv(cluster.Payload.ID, "4.13", "s390x")
 				Expect(err).NotTo(HaveOccurred())
@@ -138,9 +138,9 @@ var _ = Describe("Feature support levels API", func() {
 
 			It("Create infra-env after updating OLM operators on s390x architecture ", func() {
 				expectedError := "cannot use OpenShift Virtualization because it's not compatible with the s390x architecture on version 4.13"
-				cluster, err := registerNewCluster("4.13", "s390x", models.ClusterHighAvailabilityModeFull, swag.Bool(true))
+				cluster, err := registerNewCluster("4.13", common.MultiCPUArchitecture, models.ClusterHighAvailabilityModeFull, swag.Bool(true))
 				Expect(err).NotTo(HaveOccurred())
-				Expect(cluster.Payload.CPUArchitecture).To(Equal("multi"))
+				Expect(cluster.Payload.CPUArchitecture).To(Equal(common.MultiCPUArchitecture))
 
 				_, err = user2BMClient.Installer.V2UpdateCluster(ctx, &installer.V2UpdateClusterParams{
 					ClusterUpdateParams: &models.V2ClusterUpdateParams{
@@ -212,13 +212,13 @@ var _ = Describe("Feature support levels API", func() {
 			It("Register cluster won't fail on 4.13 with s390x", func() {
 				cluster, err := registerNewCluster("4.13", "s390x", models.ClusterHighAvailabilityModeFull, swag.Bool(true))
 				Expect(err).NotTo(HaveOccurred())
-				Expect(cluster.Payload.CPUArchitecture).To(Equal("multi"))
+				Expect(cluster.Payload.CPUArchitecture).To(Equal(common.S390xCPUArchitecture))
 			})
 
 			It("Register cluster won't fail on 4.13 with s390x without UMN", func() {
 				cluster, err := registerNewCluster("4.13", "s390x", models.ClusterHighAvailabilityModeFull, nil)
 				Expect(err).NotTo(HaveOccurred())
-				Expect(cluster.Payload.CPUArchitecture).To(Equal("multi"))
+				Expect(cluster.Payload.CPUArchitecture).To(Equal(common.S390xCPUArchitecture))
 			})
 
 			It("SNO with s390x 4.10 fails on architecture- failure", func() {
@@ -231,7 +231,7 @@ var _ = Describe("Feature support levels API", func() {
 			It("SNO with s390x fails on SNO isn't compatible with architecture success on 4.13", func() {
 				cluster, err := registerNewCluster("4.13", "s390x", models.ClusterHighAvailabilityModeNone, nil)
 				Expect(err).NotTo(HaveOccurred())
-				Expect(cluster.Payload.CPUArchitecture).To(Equal("multi"))
+				Expect(cluster.Payload.CPUArchitecture).To(Equal(common.S390xCPUArchitecture))
 				Expect(swag.StringValue(cluster.Payload.HighAvailabilityMode)).To(Equal(models.ClusterHighAvailabilityModeNone))
 
 			})