diff --git a/internal/server/handlers/validation/database_cluster.go b/internal/server/handlers/validation/database_cluster.go index 08f017d8e..6972e552b 100644 --- a/internal/server/handlers/validation/database_cluster.go +++ b/internal/server/handlers/validation/database_cluster.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strconv" "strings" goversion "github.com/hashicorp/go-version" @@ -536,7 +537,7 @@ func (h *validateHandler) validateDatabaseClusterOnUpdate( newVersion := dbc.Spec.Engine.Version oldVersion := oldDB.Spec.Engine.Version if newVersion != "" && newVersion != oldVersion { - if err := validateDBEngineVersionUpgrade(newVersion, oldVersion); err != nil { + if err := validateDBEngineVersionUpgrade(oldDB.Spec.Engine.Type, newVersion, oldVersion); err != nil { return err } } @@ -557,7 +558,7 @@ func (h *validateHandler) validateDatabaseClusterOnUpdate( } // validateDBEngineVersionUpgrade validates if upgrade of DBEngine from `oldVersion` to `newVersion` is allowed. -func validateDBEngineVersionUpgrade(newVersion, oldVersion string) error { +func validateDBEngineVersionUpgrade(engineType everestv1alpha1.EngineType, newVersion, oldVersion string) error { // Ensure a "v" prefix so that it is a valid semver. if !strings.HasPrefix(newVersion, "v") { newVersion = "v" + newVersion @@ -575,15 +576,21 @@ func validateDBEngineVersionUpgrade(newVersion, oldVersion string) error { if semver.Compare(newVersion, oldVersion) < 0 { return errDBEngineDowngrade } - // We will not allow major upgrades. - // Major upgrades are handled differently for different operators, so for now we simply won't allow it. - // For example: - // - PXC operator allows major upgrades. - // - PSMDB operator allows major upgrades, but we need to handle FCV. - // - PG operator does not allow major upgrades. - if semver.Major(oldVersion) != semver.Major(newVersion) { + // We will not allow major upgrades for PXC and PG. + // - PXC: Major upgrades are not supported. + // - PG: Major upgrades are in technical preview. https://docs.percona.com/percona-operator-for-postgresql/2.0/update.html#major-version-upgrade + if engineType != everestv1alpha1.DatabaseEnginePSMDB && semver.Major(oldVersion) != semver.Major(newVersion) { return errDBEngineMajorVersionUpgrade } + + // It's fine to ignore the errors here because we have already validated the version. + newMajorInt, _ := strconv.Atoi(semver.Major(newVersion)[1:]) + oldMajorInt, _ := strconv.Atoi(semver.Major(oldVersion)[1:]) + // We will not allow major upgrades if the versions are not sequential. + if newMajorInt-oldMajorInt > 1 { + fmt.Println("errDBEngineMajorUpgradeNotSeq") + return errDBEngineMajorUpgradeNotSeq + } return nil } diff --git a/internal/server/handlers/validation/database_engine_test.go b/internal/server/handlers/validation/database_engine_test.go index 6c5f30728..312728219 100644 --- a/internal/server/handlers/validation/database_engine_test.go +++ b/internal/server/handlers/validation/database_engine_test.go @@ -92,42 +92,63 @@ func TestValidateDBEngineUpgrade(t *testing.T) { t.Parallel() testCases := []struct { name string + engineType everestv1alpha1.EngineType oldVersion string newVersion string err error }{ { name: "invalid version", + engineType: everestv1alpha1.DatabaseEnginePXC, oldVersion: "1.0.0", newVersion: "1!00;", err: errInvalidVersion, }, { - name: "major upgrade", + name: "major upgrade PXC", + engineType: everestv1alpha1.DatabaseEnginePXC, oldVersion: "8.0.22", newVersion: "9.0.0", err: errDBEngineMajorVersionUpgrade, }, + { + name: "major upgrade PSMDB", + engineType: everestv1alpha1.DatabaseEnginePSMDB, + oldVersion: "8.0.22", + newVersion: "9.0.0", + err: nil, + }, + { + name: "skipping major upgrade PSMDB", + engineType: everestv1alpha1.DatabaseEnginePSMDB, + oldVersion: "8.0.22", + newVersion: "10.0.0", + err: errDBEngineMajorUpgradeNotSeq, + }, { name: "downgrade", + engineType: everestv1alpha1.DatabaseEnginePXC, oldVersion: "8.0.22", newVersion: "8.0.21", err: errDBEngineDowngrade, }, { name: "valid upgrade", + engineType: everestv1alpha1.DatabaseEnginePXC, oldVersion: "8.0.22", newVersion: "8.0.23", err: nil, }, { name: "valid upgrade (with 'v' prefix)", + engineType: everestv1alpha1.DatabaseEnginePXC, oldVersion: "v8.0.22", newVersion: "v8.0.23", err: nil, }, { name: "major version downgrade", + engineType: everestv1alpha1.DatabaseEnginePXC, oldVersion: "16.1", newVersion: "15.5", err: errDBEngineDowngrade, @@ -136,7 +157,7 @@ func TestValidateDBEngineUpgrade(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() - err := validateDBEngineVersionUpgrade(tc.newVersion, tc.oldVersion) + err := validateDBEngineVersionUpgrade(tc.engineType, tc.newVersion, tc.oldVersion) assert.ErrorIs(t, err, tc.err) }) } diff --git a/internal/server/handlers/validation/errors.go b/internal/server/handlers/validation/errors.go index 58cb4fe87..b29a6636f 100644 --- a/internal/server/handlers/validation/errors.go +++ b/internal/server/handlers/validation/errors.go @@ -40,6 +40,7 @@ var ( errInvalidBucketName = fmt.Errorf("invalid bucketName") errInvalidVersion = errors.New("invalid database engine version provided") errDBEngineMajorVersionUpgrade = errors.New("database engine cannot be upgraded to a major version") + errDBEngineMajorUpgradeNotSeq = errors.New("database engine major version upgrade is not supported for non-sequential versions") errDBEngineDowngrade = errors.New("database engine version cannot be downgraded") errDuplicatedSchedules = errors.New("duplicated backup schedules are not allowed") errDuplicatedStoragePG = errors.New("postgres clusters can't use the same storage for the different schedules") diff --git a/ui/apps/everest/src/components/cluster-form/db-version/utils.test.ts b/ui/apps/everest/src/components/cluster-form/db-version/utils.test.ts index 68584883b..aaa152042 100644 --- a/ui/apps/everest/src/components/cluster-form/db-version/utils.test.ts +++ b/ui/apps/everest/src/components/cluster-form/db-version/utils.test.ts @@ -33,57 +33,63 @@ describe('DBVersion Available filter test', () => { expect( filterAvailableDbVersionsForDbEngineEdition( generateDbEngineWithVersions( - ['5.0.0', '4.0.0', '4.3.9', '4.4.1'], + ['8.0.39-30.1', '8.0.36-28.1', '8.0.35-27.1', '5.7.44-31.65'], DbEngineType.PXC ), - '4.4.0' + '8.0.39-30.1' ).map(({ version }) => version) - ).toEqual(['5.0.0', '4.4.1']); - }); - - it('should coerce PG versions to semver', () => { + ).toEqual(['8.0.39-30.1']); expect( filterAvailableDbVersionsForDbEngineEdition( generateDbEngineWithVersions( - ['13.0', '14.0', '14.1', '15.0'], + ['7.0.15-9', '7.0.14-8', '7.0.12-7', '6.0.19-16'], + DbEngineType.PSMDB + ), + '7.0.15-9' + ).map(({ version }) => version) + ).toEqual(['7.0.15-9']); + expect( + filterAvailableDbVersionsForDbEngineEdition( + generateDbEngineWithVersions( + ['16.4', '16.3', '15.8'], DbEngineType.POSTGRESQL ), - '14.1' + '16.4' ).map(({ version }) => version) - ).toEqual(['14.1']); + ).toEqual(['16.4']); }); - it('should allow major upgrades for PXC', () => { + it('should allow major upgrade to the next version for PSMDB', () => { expect( filterAvailableDbVersionsForDbEngineEdition( generateDbEngineWithVersions( - ['5.0.0', '4.0.0', '4.3.9', '4.4.1'], - DbEngineType.PXC + ['8.0.4-1', '7.0.15-9', '7.0.14-8', '6.0.19-16', '6.0.18-15'], + DbEngineType.PSMDB ), - '4.4.0' + '6.0.18-15' ).map(({ version }) => version) - ).toEqual(['5.0.0', '4.4.1']); + ).toEqual(['7.0.15-9', '7.0.14-8', '6.0.19-16', '6.0.18-15']); }); - it('should rule out major upgrades/downgrades for PSMDB/PG', () => { + it('should rule out major upgrades for PXC/PG', () => { expect( filterAvailableDbVersionsForDbEngineEdition( generateDbEngineWithVersions( - [ - '1.0.0', - '1.2.0', - '2.3.3', - '2.5.0', - '2.9.0', - '3.1.0', - '4.3.0', - '5.4.1', - ], - DbEngineType.PSMDB + ['9.0.0', '8.4.2-2.1', '8.0.39-30.1', '8.0.36-28.1', '8.0.35-27.1'], + DbEngineType.PXC + ), + '8.0.36-28.1' + ).map(({ version }) => version) + ).toEqual(['8.4.2-2.1', '8.0.39-30.1', '8.0.36-28.1']); + expect( + filterAvailableDbVersionsForDbEngineEdition( + generateDbEngineWithVersions( + ['16.4', '16.3', '15.8', '15.7'], + DbEngineType.POSTGRESQL ), - '2.3.0' + '15.7' ).map(({ version }) => version) - ).toEqual(['2.3.3', '2.5.0', '2.9.0']); + ).toEqual(['15.8', '15.7']); }); }); }); diff --git a/ui/apps/everest/src/components/cluster-form/db-version/utils.ts b/ui/apps/everest/src/components/cluster-form/db-version/utils.ts index 624c17b73..26233dd96 100644 --- a/ui/apps/everest/src/components/cluster-form/db-version/utils.ts +++ b/ui/apps/everest/src/components/cluster-form/db-version/utils.ts @@ -35,13 +35,19 @@ export const filterAvailableDbVersionsForDbEngineEdition = ( return semverVersion ? gte(semverVersion, currentSemverVersion) : true; }); - // If the engine is PSMDB or PG, major version upgrades are also ruled out - if ([DbEngineType.PSMDB, DbEngineType.POSTGRESQL].includes(dbType)) { + // If the engine is PXC or PG, major version upgrades are also ruled out + if ([DbEngineType.PXC, DbEngineType.POSTGRESQL].includes(dbType)) { versions = versions.filter(({ version }) => { const semverVersion = coerce(version); return semverVersion ? semverVersion.major === currentMajor : true; }); } + // Rule out skipping major versions + versions = versions.filter(({ version }) => { + const semverVersion = coerce(version); + return semverVersion ? semverVersion.major - currentMajor <= 1 : true; + }); + return versions; };