Skip to content

Commit

Permalink
mysqlctl: Improve backup restore compatibility check (#15856)
Browse files Browse the repository at this point in the history
Signed-off-by: Dirkjan Bussink <d.bussink@gmail.com>
  • Loading branch information
dbussink authored May 7, 2024
1 parent dee6d81 commit 0d7af14
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 12 deletions.
12 changes: 0 additions & 12 deletions go/vt/mysqlctl/backup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,18 +423,6 @@ func TestRestoreManifestMySQLVersionValidation(t *testing.T) {
upgradeSafe bool
wantErr bool
}{
{
fromVersion: "mysqld Ver 5.6.42",
toVersion: "mysqld Ver 5.7.40",
upgradeSafe: false,
wantErr: true,
},
{
fromVersion: "mysqld Ver 5.6.42",
toVersion: "mysqld Ver 5.7.40",
upgradeSafe: true,
wantErr: false,
},
{
fromVersion: "mysqld Ver 5.7.42",
toVersion: "mysqld Ver 8.0.32",
Expand Down
51 changes: 51 additions & 0 deletions go/vt/mysqlctl/backupengine.go
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,15 @@ func FindBackupToRestore(ctx context.Context, params RestoreParams, bhs []backup
return restorePath, nil
}

// See https://github.com/mysql/mysql-server/commit/9a940abe085fc75e1ffe7b72286927fdc9f11207 for the
// importance of this specific version and why downgrades within patches are allowed since that version.
var mysql8035 = ServerVersion{Major: 8, Minor: 0, Patch: 35}
var ltsVersions = []ServerVersion{
{Major: 5, Minor: 7, Patch: 0},
{Major: 8, Minor: 0, Patch: 0},
{Major: 8, Minor: 4, Patch: 0},
}

func validateMySQLVersionUpgradeCompatible(to string, from string, upgradeSafe bool) error {
// It's always safe to use the same version.
if to == from {
Expand All @@ -616,6 +625,48 @@ func validateMySQLVersionUpgradeCompatible(to string, from string, upgradeSafe b
return nil
}

// If we're not on the same LTS stream, we have to do additional checks to see if it's safe to
// to upgrade. It can only be one newer LTS version for the destination and the backup
// has to be marked as upgrade safe.

// If something is across different LTS streams and not upgrade safe, we can't use it.
if !parsedFrom.isSameRelease(parsedTo) {
if !upgradeSafe {
if parsedTo.atLeast(parsedFrom) {
return fmt.Errorf("running MySQL version %q is newer than backup MySQL version %q which is not safe to upgrade", to, from)
}
return fmt.Errorf("running MySQL version %q is older than backup MySQL version %q", to, from)
}

// Alright, we're across different LTS streams and the backup is upgrade safe.
// We can only upgrade to the next LTS version.
for i, ltsVersion := range ltsVersions {
if parsedFrom.isSameRelease(ltsVersion) {
if i < len(ltsVersions)-1 && parsedTo.isSameRelease(ltsVersions[i+1]) {
return nil
}
if parsedTo.atLeast(parsedFrom) {
return fmt.Errorf("running MySQL version %q is too new for backup MySQL version %q", to, from)
}
return fmt.Errorf("running MySQL version %q is older than backup MySQL version %q", to, from)
}
}
if parsedTo.atLeast(parsedFrom) {
return fmt.Errorf("running MySQL version %q is newer than backup MySQL version %q which is not safe to upgrade", to, from)
}
return fmt.Errorf("running MySQL version %q is older than backup MySQL version %q", to, from)
}

// At this point we know the versions are not the same, but we're withing the same version stream
// and only the patch version number mismatches.

// Starting with MySQL 8.0.35, the data dictionary format is stable for 8.0.x, so we can upgrade
// from 8.0.35 or later here, also if the backup was taken with innodb_fast_shutdown=0.
// This also applies for any version newer like 8.4.x.
if parsedFrom.atLeast(mysql8035) && parsedTo.atLeast(mysql8035) {
return nil
}

if !parsedTo.atLeast(parsedFrom) {
return fmt.Errorf("running MySQL version %q is older than backup MySQL version %q", to, from)
}
Expand Down
215 changes: 215 additions & 0 deletions go/vt/mysqlctl/backupengine_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
package mysqlctl

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestValidateMySQLVersionUpgradeCompatible(t *testing.T) {
// Test that the MySQL version is compatible with the upgrade.
testCases := []struct {
name string
fromVersion string
toVersion string
upgradeSafe bool
error string
}{
{
name: "upgrade from 5.7 to 8.0",
fromVersion: "mysqld Ver 5.7.35",
toVersion: "mysqld Ver 8.0.23",
upgradeSafe: true,
},
{
name: "downgrade from 8.0 to 5.7",
fromVersion: "mysqld Ver 8.0.23",
toVersion: "mysqld Ver 5.7.35",
upgradeSafe: true,
error: `running MySQL version "mysqld Ver 5.7.35" is older than backup MySQL version "mysqld Ver 8.0.23"`,
},
{
name: "upgrade from 5.7 to 8.0",
fromVersion: "mysqld Ver 5.7.35",
toVersion: "mysqld Ver 8.0.23",
upgradeSafe: false,
error: `running MySQL version "mysqld Ver 8.0.23" is newer than backup MySQL version "mysqld Ver 5.7.35" which is not safe to upgrade`,
},
{
name: "downgrade from 8.0 to 5.7",
fromVersion: "mysqld Ver 8.0.23",
toVersion: "mysqld Ver 5.7.35",
upgradeSafe: false,
error: `running MySQL version "mysqld Ver 5.7.35" is older than backup MySQL version "mysqld Ver 8.0.23"`,
},
{
name: "upgrade from 8.0.23 to 8.0.34",
fromVersion: "mysqld Ver 8.0.23",
toVersion: "mysqld Ver 8.0.34",
upgradeSafe: true,
},
{
name: "downgrade from 8.0.34 to 8.0.23",
fromVersion: "mysqld Ver 8.0.34",
toVersion: "mysqld Ver 8.0.23",
upgradeSafe: true,
error: `running MySQL version "mysqld Ver 8.0.23" is older than backup MySQL version "mysqld Ver 8.0.34"`,
},
{
name: "upgrade from 8.0.23 to 8.0.34",
fromVersion: "mysqld Ver 8.0.23",
toVersion: "mysqld Ver 8.0.34",
upgradeSafe: false,
error: `running MySQL version "mysqld Ver 8.0.34" is newer than backup MySQL version "mysqld Ver 8.0.23" which is not safe to upgrade`,
},
{
name: "downgrade from 8.0.34 to 8.0.23",
fromVersion: "mysqld Ver 8.0.34",
toVersion: "mysqld Ver 8.0.23",
upgradeSafe: false,
error: `running MySQL version "mysqld Ver 8.0.23" is older than backup MySQL version "mysqld Ver 8.0.34"`,
},
{
name: "upgrade from 8.0.32 to 8.0.36",
fromVersion: "mysqld Ver 8.0.32",
toVersion: "mysqld Ver 8.0.36",
upgradeSafe: true,
},
{
name: "downgrade from 8.0.36 to 8.0.32",
fromVersion: "mysqld Ver 8.0.36",
toVersion: "mysqld Ver 8.0.32",
upgradeSafe: true,
error: `running MySQL version "mysqld Ver 8.0.32" is older than backup MySQL version "mysqld Ver 8.0.36"`,
},
{
name: "upgrade from 8.0.32 to 8.0.36",
fromVersion: "mysqld Ver 8.0.32",
toVersion: "mysqld Ver 8.0.36",
upgradeSafe: false,
error: `running MySQL version "mysqld Ver 8.0.36" is newer than backup MySQL version "mysqld Ver 8.0.32" which is not safe to upgrade`,
},
{
name: "downgrade from 8.0.36 to 8.0.32",
fromVersion: "mysqld Ver 8.0.36",
toVersion: "mysqld Ver 8.0.32",
upgradeSafe: false,
error: `running MySQL version "mysqld Ver 8.0.32" is older than backup MySQL version "mysqld Ver 8.0.36"`,
},
{
name: "upgrade from 8.0.35 to 8.0.36",
fromVersion: "mysqld Ver 8.0.35",
toVersion: "mysqld Ver 8.0.36",
upgradeSafe: true,
},
{
name: "downgrade from 8.0.36 to 8.0.35",
fromVersion: "mysqld Ver 8.0.36",
toVersion: "mysqld Ver 8.0.35",
upgradeSafe: true,
},
{
name: "upgrade from 8.0.35 to 8.0.36",
fromVersion: "mysqld Ver 8.0.35",
toVersion: "mysqld Ver 8.0.36",
upgradeSafe: false,
},
{
name: "downgrade from 8.0.36 to 8.0.35",
fromVersion: "mysqld Ver 8.0.36",
toVersion: "mysqld Ver 8.0.35",
upgradeSafe: false,
},
{
name: "upgrade from 8.4.0 to 8.4.1",
fromVersion: "mysqld Ver 8.4.0",
toVersion: "mysqld Ver 8.4.1",
upgradeSafe: true,
},
{
name: "downgrade from 8.4.1 to 8.4.0",
fromVersion: "mysqld Ver 8.4.1",
toVersion: "mysqld Ver 8.4.0",
upgradeSafe: true,
},
{
name: "upgrade from 8.4.0 to 8.4.1",
fromVersion: "mysqld Ver 8.4.0",
toVersion: "mysqld Ver 8.4.1",
upgradeSafe: false,
},
{
name: "downgrade from 8.4.1 to 8.4.0",
fromVersion: "mysqld Ver 8.4.1",
toVersion: "mysqld Ver 8.4.0",
upgradeSafe: false,
},
{
name: "upgrade from 8.0.35 to 8.4.0",
fromVersion: "mysqld Ver 8.0.32",
toVersion: "mysqld Ver 8.4.0",
upgradeSafe: true,
},
{
name: "downgrade from 8.4.0 to 8.0.32",
fromVersion: "mysqld Ver 8.4.0",
toVersion: "mysqld Ver 8.0.32",
upgradeSafe: true,
error: `running MySQL version "mysqld Ver 8.0.32" is older than backup MySQL version "mysqld Ver 8.4.0"`,
},
{
name: "upgrade from 8.0.32 to 8.4.0",
fromVersion: "mysqld Ver 8.0.32",
toVersion: "mysqld Ver 8.4.0",
upgradeSafe: false,
error: `running MySQL version "mysqld Ver 8.4.0" is newer than backup MySQL version "mysqld Ver 8.0.32" which is not safe to upgrade`,
},
{
name: "downgrade from 8.4.0 to 8.0.32",
fromVersion: "mysqld Ver 8.4.0",
toVersion: "mysqld Ver 8.0.32",
upgradeSafe: false,
error: `running MySQL version "mysqld Ver 8.0.32" is older than backup MySQL version "mysqld Ver 8.4.0"`,
},
{
name: "upgrade from 5.7.35 to 8.4.0",
fromVersion: "mysqld Ver 5.7.32",
toVersion: "mysqld Ver 8.4.0",
upgradeSafe: true,
error: `running MySQL version "mysqld Ver 8.4.0" is too new for backup MySQL version "mysqld Ver 5.7.32"`,
},
{
name: "downgrade from 8.4.0 to 5.7.32",
fromVersion: "mysqld Ver 8.4.0",
toVersion: "mysqld Ver 5.7.32",
upgradeSafe: true,
error: `running MySQL version "mysqld Ver 5.7.32" is older than backup MySQL version "mysqld Ver 8.4.0"`,
},
{
name: "upgrade from 5.7.32 to 8.4.0",
fromVersion: "mysqld Ver 5.7.32",
toVersion: "mysqld Ver 8.4.0",
upgradeSafe: false,
error: `running MySQL version "mysqld Ver 8.4.0" is newer than backup MySQL version "mysqld Ver 5.7.32" which is not safe to upgrade`,
},
{
name: "downgrade from 8.4.0 to 5.7.32",
fromVersion: "mysqld Ver 8.4.0",
toVersion: "mysqld Ver 5.7.32",
upgradeSafe: false,
error: `running MySQL version "mysqld Ver 5.7.32" is older than backup MySQL version "mysqld Ver 8.4.0"`,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := validateMySQLVersionUpgradeCompatible(tc.toVersion, tc.fromVersion, tc.upgradeSafe)
if tc.error == "" {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, tc.error)
}
})
}

}
4 changes: 4 additions & 0 deletions go/vt/mysqlctl/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ func (v *ServerVersion) atLeast(compare ServerVersion) bool {
}
return false
}

func (v *ServerVersion) isSameRelease(compare ServerVersion) bool {
return v.Major == compare.Major && v.Minor == compare.Minor
}

0 comments on commit 0d7af14

Please sign in to comment.