Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mysqlctl: Improve backup restore compatibility check #15856

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
Loading