Skip to content

Commit 1bf19cc

Browse files
fgiudiciclaude
andauthored
OPRUN-4519: block upgrades from 4.23 to 5.0 (#3803)
* Fix maxOpenShiftVersion blocking at OCP 4.23/5.0 boundary OCP 4.23 and 5.0 represent the same release. Add normalizeOCPVersion() to map 4.23 to 5.0 before computing the next Y-stream and comparing an operator's declared max version, so both cluster and operator versions are evaluated consistently. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Francesco Giudici <fgiudici@redhat.com> * Add unit tests for OCP 4.23/5.0 upgrade edge compatibility Cover cluster versions 4.22, 4.23, 5.0, and 5.1 against operators declaring maxOpenShiftVersion 4.23, 5.0, and 5.1. Add TestNormalizeOCPVersion for the new helper. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Francesco Giudici <fgiudici@redhat.com> --------- Signed-off-by: Francesco Giudici <fgiudici@redhat.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4baa8d4 commit 1bf19cc

File tree

2 files changed

+208
-1
lines changed

2 files changed

+208
-1
lines changed

pkg/controller/operators/openshift/helpers.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ func incompatibleOperators(ctx context.Context, cli client.Client) (skews, error
136136
return nil, &transientError{fmt.Errorf("failed to list ClusterServiceVersions: %w", err)}
137137
}
138138

139+
// Normalize the current cluster version: OCP 4.23 and 5.0 represent the same release.
140+
normalizedCurrent := normalizeOCPVersion(*current)
141+
nextMinor := nextY(normalizedCurrent)
142+
139143
var incompatible skews
140144
for _, csv := range csvList.Items {
141145
if csv.IsCopied() {
@@ -153,7 +157,13 @@ func incompatibleOperators(ctx context.Context, cli client.Client) (skews, error
153157
continue
154158
}
155159

156-
if max == nil || max.GTE(nextY(*current)) {
160+
if max == nil {
161+
continue
162+
}
163+
164+
// Normalize the operator's maxOpenShiftVersion: 4.23 and 5.0 are equivalent.
165+
normalizedMax := normalizeOCPVersion(*max)
166+
if normalizedMax.GTE(nextMinor) {
157167
continue
158168
}
159169

@@ -223,6 +233,15 @@ func nextY(v semver.Version) semver.Version {
223233
return semver.Version{Major: v.Major, Minor: v.Minor + 1} // Sets Y=Y+1
224234
}
225235

236+
// normalizeOCPVersion maps OCP 4.23 to 5.0, since they represent the same release.
237+
// All other versions are returned with only the major and minor components.
238+
func normalizeOCPVersion(v semver.Version) semver.Version {
239+
if v.Major == 4 && v.Minor == 23 {
240+
return semver.Version{Major: 5, Minor: 0}
241+
}
242+
return semver.Version{Major: v.Major, Minor: v.Minor}
243+
}
244+
226245
const (
227246
MaxOpenShiftVersionProperty = "olm.maxOpenShiftVersion"
228247
)

pkg/controller/operators/openshift/helpers_test.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,141 @@ func TestIncompatibleOperators(t *testing.T) {
442442
},
443443
},
444444
},
445+
// OCP 4.23 and OCP 5.0 represent the same release
446+
{
447+
description: "OCP4.22",
448+
version: "4.22.0",
449+
in: skews{
450+
{
451+
name: "almond",
452+
namespace: "default",
453+
maxOpenShiftVersion: "4.23",
454+
},
455+
{
456+
name: "beech",
457+
namespace: "default",
458+
maxOpenShiftVersion: "5.0",
459+
},
460+
{
461+
name: "chestnut",
462+
namespace: "default",
463+
maxOpenShiftVersion: "5.1",
464+
},
465+
},
466+
expect: expect{
467+
incompatible: nil,
468+
},
469+
},
470+
{
471+
description: "OCP4.23",
472+
version: "4.23.0",
473+
in: skews{
474+
{
475+
name: "almond",
476+
namespace: "default",
477+
maxOpenShiftVersion: "4.23",
478+
},
479+
{
480+
name: "beech",
481+
namespace: "default",
482+
maxOpenShiftVersion: "5.0",
483+
},
484+
{
485+
name: "chestnut",
486+
namespace: "default",
487+
maxOpenShiftVersion: "5.1",
488+
},
489+
},
490+
expect: expect{
491+
incompatible: skews{
492+
{
493+
name: "almond",
494+
namespace: "default",
495+
maxOpenShiftVersion: "4.23",
496+
},
497+
{
498+
name: "beech",
499+
namespace: "default",
500+
maxOpenShiftVersion: "5.0",
501+
},
502+
},
503+
},
504+
},
505+
{
506+
description: "OCP5.0",
507+
version: "5.0.0",
508+
in: skews{
509+
{
510+
name: "almond",
511+
namespace: "default",
512+
maxOpenShiftVersion: "4.23",
513+
},
514+
{
515+
name: "beech",
516+
namespace: "default",
517+
maxOpenShiftVersion: "5.0",
518+
},
519+
{
520+
name: "chestnut",
521+
namespace: "default",
522+
maxOpenShiftVersion: "5.1",
523+
},
524+
},
525+
expect: expect{
526+
incompatible: skews{
527+
{
528+
name: "almond",
529+
namespace: "default",
530+
maxOpenShiftVersion: "4.23",
531+
},
532+
{
533+
name: "beech",
534+
namespace: "default",
535+
maxOpenShiftVersion: "5.0",
536+
},
537+
},
538+
},
539+
},
540+
{
541+
description: "OCP5.1",
542+
version: "5.1.0",
543+
in: skews{
544+
{
545+
name: "almond",
546+
namespace: "default",
547+
maxOpenShiftVersion: "4.23",
548+
},
549+
{
550+
name: "beech",
551+
namespace: "default",
552+
maxOpenShiftVersion: "5.0",
553+
},
554+
{
555+
name: "chestnut",
556+
namespace: "default",
557+
maxOpenShiftVersion: "5.1",
558+
},
559+
},
560+
expect: expect{
561+
incompatible: skews{
562+
{
563+
name: "almond",
564+
namespace: "default",
565+
maxOpenShiftVersion: "4.23",
566+
},
567+
{
568+
name: "beech",
569+
namespace: "default",
570+
maxOpenShiftVersion: "5.0",
571+
},
572+
{
573+
name: "chestnut",
574+
namespace: "default",
575+
maxOpenShiftVersion: "5.1",
576+
},
577+
},
578+
},
579+
},
445580
} {
446581
t.Run(tt.description, func(t *testing.T) {
447582
objs := []client.Object{}
@@ -620,10 +755,63 @@ func TestOCPVersionNextY(t *testing.T) {
620755
inVersion: semver.MustParse("4.16.0-rc1"),
621756
expectedVersion: semver.MustParse("4.17.0"),
622757
},
758+
{
759+
description: "Version: 4.23.0. Expected output: 4.24",
760+
inVersion: semver.MustParse("4.23.0"),
761+
expectedVersion: semver.MustParse("4.24.0"),
762+
},
763+
{
764+
description: "Version: 5.0.0. Expected output: 5.1",
765+
inVersion: semver.MustParse("5.0.0"),
766+
expectedVersion: semver.MustParse("5.1.0"),
767+
},
623768
} {
624769
t.Run(tc.description, func(t *testing.T) {
625770
outVersion := nextY(tc.inVersion)
626771
require.Equal(t, outVersion, tc.expectedVersion)
627772
})
628773
}
629774
}
775+
776+
func TestNormalizeOCPVersion(t *testing.T) {
777+
for _, tc := range []struct {
778+
description string
779+
in semver.Version
780+
expected semver.Version
781+
}{
782+
{
783+
description: "4.22 stays 4.22",
784+
in: semver.MustParse("4.22.0"),
785+
expected: semver.Version{Major: 4, Minor: 22},
786+
},
787+
{
788+
description: "4.22.0-rc1 normalizes to 4.22",
789+
in: semver.MustParse("4.22.0-rc1"),
790+
expected: semver.Version{Major: 4, Minor: 22},
791+
},
792+
{
793+
description: "4.23 normalizes to 5.0",
794+
in: semver.MustParse("4.23.0"),
795+
expected: semver.Version{Major: 5, Minor: 0},
796+
},
797+
{
798+
description: "5.0 stays 5.0",
799+
in: semver.MustParse("5.0.0"),
800+
expected: semver.Version{Major: 5, Minor: 0},
801+
},
802+
{
803+
description: "5.1 stays 5.1",
804+
in: semver.MustParse("5.1.0"),
805+
expected: semver.Version{Major: 5, Minor: 1},
806+
},
807+
{
808+
description: "4.23.0-rc1 normalizes to 5.0",
809+
in: semver.MustParse("4.23.0-rc1"),
810+
expected: semver.Version{Major: 5, Minor: 0},
811+
},
812+
} {
813+
t.Run(tc.description, func(t *testing.T) {
814+
require.Equal(t, tc.expected, normalizeOCPVersion(tc.in))
815+
})
816+
}
817+
}

0 commit comments

Comments
 (0)