From 495f171c201e6b9d08471c17719b3ac26189bb5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Thu, 21 May 2026 12:56:57 +0200 Subject: [PATCH 1/8] PMM-14594 Compatibility fix. --- .../mysql-backup/mysql_prerequisites.md | 12 +- .../services/backup/compatibility_helpers.go | 113 +++++++++++++----- .../backup/compatibility_helpers_test.go | 102 ++++++++++++---- 3 files changed, 177 insertions(+), 50 deletions(-) diff --git a/documentation/docs/backup/mysql-backup/mysql_prerequisites.md b/documentation/docs/backup/mysql-backup/mysql_prerequisites.md index 3bc35b2fd25..bcdbca3959f 100644 --- a/documentation/docs/backup/mysql-backup/mysql_prerequisites.md +++ b/documentation/docs/backup/mysql-backup/mysql_prerequisites.md @@ -38,4 +38,14 @@ Before creating MySQL backups, make sure to: - [`qpress`][PERCONA_QPRESS]. !!! caution alert alert-warning "Important" - The versions of each must be compatible with the installed version of MySQL. \ No newline at end of file + The versions of each must be compatible with the installed version of MySQL. +Use the Percona XtraBackup version family that matches your MySQL version: + +| MySQL version | Percona XtraBackup version | +|---------------|----------------------------| +| MySQL 5.5, 5.6, or 5.7 | Percona XtraBackup 2.4.x | +| MySQL 8.0.0 through 8.0.21 | Percona XtraBackup 8.0.x, using the minimum version required for your MySQL release | +| MySQL 8.0.22 and newer 8.0 releases | Percona XtraBackup 8.0.x with the same or newer core version | +| MySQL 8.4.x | Percona XtraBackup 8.4.x | + +Percona XtraBackup 8.4.x supports MySQL 8.4 LTS releases, including future 8.4 patch releases. It does not support backing up MySQL 8.0 or 9.x servers. \ No newline at end of file diff --git a/managed/services/backup/compatibility_helpers.go b/managed/services/backup/compatibility_helpers.go index 94f0f188826..6647600feae 100644 --- a/managed/services/backup/compatibility_helpers.go +++ b/managed/services/backup/compatibility_helpers.go @@ -37,8 +37,9 @@ var ( // processing will be stopped and Percona XtraBackup will not be allowed to continue. // https://www.percona.com/blog/2020/08/18/aligning-percona-xtrabackup-versions-with-percona-server-for-mysql/ alignedXtrabackupVersion = version.Must(version.NewVersion("8.0.22")) - // Since there is no version 9 or greater let's limit aligning rule by this number. - maxAlignedXtrabackupVersion = version.Must(version.NewVersion("9.0")) + mysql81Version = version.Must(version.NewVersion("8.1.0")) + mysql84Version = version.Must(version.NewVersion("8.4.0")) + mysql85Version = version.Must(version.NewVersion("8.5.0")) pbmMinSupportedVersion = version.Must(version.NewVersion("2.0.1")) ) @@ -66,7 +67,7 @@ func init() { mysqlMinVersion: "8.0", mysqlMaxVersion: "8.0.20", xtrabackupMinVersion: "8.0.6", - xtrabackupMaxVersion: "9.0", + xtrabackupMaxVersion: "8.1.0", }, // Percona XtraBackup 8.0.12 now supports backup and restore processing for all versions of MySQL; // previous versions of Percona XtraBackup will not work with MySQL 8.0.20 and higher. @@ -78,7 +79,7 @@ func init() { mysqlMinVersion: "8.0.20", mysqlMaxVersion: "8.0.21", xtrabackupMinVersion: "8.0.12", - xtrabackupMaxVersion: "9.0", + xtrabackupMaxVersion: "8.1.0", }, // Percona XtraBackup 8.0.14 supports backup and restore processing for all versions of MySQL // and has been tested with the latest MySQL 8.0.21. @@ -87,7 +88,7 @@ func init() { mysqlMinVersion: "8.0.21", mysqlMaxVersion: "8.0.22", xtrabackupMinVersion: "8.0.14", - xtrabackupMaxVersion: "9.0", + xtrabackupMaxVersion: "8.1.0", }, } @@ -108,35 +109,98 @@ func init() { } func mysqlAndXtrabackupCompatible(mysqlVersionString, xtrabackupVersionString string) (bool, error) { - mysqlVersion, err := version.NewVersion(mysqlVersionString) + mysqlVersion, xtrabackupVersion, err := mysqlAndXtrabackupCoreVersions(mysqlVersionString, xtrabackupVersionString) if err != nil { return false, err } + + return mysqlAndXtrabackupCoreVersionsCompatible(mysqlVersion, xtrabackupVersion), nil +} + +func mysqlAndXtrabackupCoreVersions(mysqlVersionString, xtrabackupVersionString string) (*version.Version, *version.Version, error) { + mysqlVersion, err := version.NewVersion(mysqlVersionString) + if err != nil { + return nil, nil, err + } mysqlVersion = mysqlVersion.Core() xtrabackupVersion, err := version.NewVersion(xtrabackupVersionString) if err != nil { - return false, err + return nil, nil, err } xtrabackupVersion = xtrabackupVersion.Core() - // See comment to alignedVersion. - // Using compatibility rule. - if mysqlVersion.GreaterThanOrEqual(alignedXtrabackupVersion) { - if xtrabackupVersion.GreaterThanOrEqual(mysqlVersion) && xtrabackupVersion.LessThan(maxAlignedXtrabackupVersion) { - return true, nil + return mysqlVersion, xtrabackupVersion, nil +} + +func mysqlAndXtrabackupCoreVersionsCompatible(mysqlVersion, xtrabackupVersion *version.Version) bool { + switch { + case mysqlVersion.GreaterThanOrEqual(mysql84Version) && mysqlVersion.LessThan(mysql85Version): + return xtrabackupVersion.GreaterThanOrEqual(mysql84Version) && xtrabackupVersion.LessThan(mysql85Version) + case mysqlVersion.GreaterThanOrEqual(alignedXtrabackupVersion) && mysqlVersion.LessThan(mysql84Version): + return xtrabackupVersion.GreaterThanOrEqual(mysqlVersion) && xtrabackupVersion.LessThan(mysql81Version) + } + + for _, cv := range mysqlAndXtrabackupCompatibleVersions { + if (mysqlVersion.GreaterThanOrEqual(cv.dbMinVersion) && + mysqlVersion.LessThan(cv.dbMaxVersion)) && + xtrabackupVersion.GreaterThanOrEqual(cv.backupToolMinVersion) && + xtrabackupVersion.LessThan(cv.backupToolMaxVersion) { + return true } - } else { // Using compatibility matrix. - for _, cv := range mysqlAndXtrabackupCompatibleVersions { - if (mysqlVersion.GreaterThanOrEqual(cv.dbMinVersion) && - mysqlVersion.LessThan(cv.dbMaxVersion)) && - xtrabackupVersion.GreaterThanOrEqual(cv.backupToolMinVersion) && - xtrabackupVersion.LessThan(cv.backupToolMaxVersion) { - return true, nil - } + } + + return false +} + +func mysqlAndXtrabackupCompatibilityError(mysqlVersionString, xtrabackupVersionString string) error { + mysqlVersion, xtrabackupVersion, err := mysqlAndXtrabackupCoreVersions(mysqlVersionString, xtrabackupVersionString) + if err != nil { + return err + } + if mysqlAndXtrabackupCoreVersionsCompatible(mysqlVersion, xtrabackupVersion) { + return nil + } + + switch { + case mysqlVersion.GreaterThanOrEqual(mysql84Version) && mysqlVersion.LessThan(mysql85Version): + return errors.Wrapf( + ErrIncompatibleXtrabackup, + "Percona XtraBackup version %q is not compatible with MySQL version %q; use Percona XtraBackup 8.4.x for MySQL 8.4.x", + xtrabackupVersionString, + mysqlVersionString, + ) + case mysqlVersion.GreaterThanOrEqual(alignedXtrabackupVersion) && mysqlVersion.LessThan(mysql84Version): + if xtrabackupVersion.LessThan(mysqlVersion) { + return errors.Wrapf( + ErrIncompatibleXtrabackup, + "Percona XtraBackup version %q is older than MySQL version %q; for MySQL 8.0.22 and newer 8.0 releases, use Percona XtraBackup 8.0.x with the same or newer core version", + xtrabackupVersionString, + mysqlVersionString, + ) } + + return errors.Wrapf( + ErrIncompatibleXtrabackup, + "Percona XtraBackup version %q is not compatible with MySQL version %q; use Percona XtraBackup 8.0.x for MySQL 8.0.x", + xtrabackupVersionString, + mysqlVersionString, + ) + case mysqlVersion.LessThan(alignedXtrabackupVersion): + return errors.Wrapf( + ErrIncompatibleXtrabackup, + "Percona XtraBackup version %q is not compatible with MySQL version %q; install a Percona XtraBackup version supported for this MySQL version", + xtrabackupVersionString, + mysqlVersionString, + ) + default: + return errors.Wrapf( + ErrIncompatibleXtrabackup, + "PMM does not support Percona XtraBackup version %q with MySQL version %q yet", + xtrabackupVersionString, + mysqlVersionString, + ) } - return false, nil } func vendorToServiceType(vendor string) (models.ServiceType, error) { @@ -185,14 +249,9 @@ func mySQLBackupSoftwareInstalledAndCompatible(svm map[models.SoftwareName]strin svm[models.XtrabackupSoftwareName], svm[models.XbcloudSoftwareName]) } - ok, err := mysqlAndXtrabackupCompatible(svm[models.MysqldSoftwareName], svm[models.XtrabackupSoftwareName]) - if err != nil { + if err := mysqlAndXtrabackupCompatibilityError(svm[models.MysqldSoftwareName], svm[models.XtrabackupSoftwareName]); err != nil { return err } - if !ok { - return errors.Wrapf(ErrIncompatibleXtrabackup, "xtrabackup version %q is not compatible with mysql version %q", - svm[models.XtrabackupSoftwareName], svm[models.MysqldSoftwareName]) - } return nil } diff --git a/managed/services/backup/compatibility_helpers_test.go b/managed/services/backup/compatibility_helpers_test.go index 2c143501bbb..b00513eae9d 100644 --- a/managed/services/backup/compatibility_helpers_test.go +++ b/managed/services/backup/compatibility_helpers_test.go @@ -43,40 +43,44 @@ func TestMysqlAndXtrabackupCompatible(t *testing.T) { {"5.7", "2.4.20"}, {"5.7", "2.4.99"}, - // MySQL [8.0; 8.0.20), PXB [8.0.6; 9.0) + // MySQL [8.0; 8.0.20), PXB [8.0.6; 8.1.0) {"8.0", "8.0.6"}, {"8.0", "8.0.8"}, - {"8.0", "8.99.99"}, + {"8.0", "8.0.99"}, {"8.0.12", "8.0.6"}, {"8.0.12", "8.0.8"}, - {"8.0.12", "8.99.99"}, + {"8.0.12", "8.0.99"}, {"8.0.19", "8.0.6"}, {"8.0.19", "8.0.8"}, - {"8.0.19", "8.99.99"}, + {"8.0.19", "8.0.99"}, - // MySQL [8.0.20; 8.0.21), PXB [8.0.12; 9.0) + // MySQL [8.0.20; 8.0.21), PXB [8.0.12; 8.1.0) {"8.0.20", "8.0.12"}, {"8.0.20", "8.0.18"}, - {"8.0.20", "8.99.99"}, + {"8.0.20", "8.0.99"}, - // MySQL [8.0.21; 8.0.22), PXB [8.0.14; 9.0) + // MySQL [8.0.21; 8.0.22), PXB [8.0.14; 8.1.0) {"8.0.21", "8.0.14"}, {"8.0.21", "8.0.18"}, - {"8.0.21", "8.99.99"}, + {"8.0.21", "8.0.99"}, - // MySQL [8.0.22; 9.0), PXB [8.0.22; 9.0) + // MySQL [8.0.22; 8.4.0), PXB [MySQL version; 8.1.0) {"8.0.22", "8.0.22"}, {"8.0.22", "8.0.22-15.0"}, {"8.0.22", "8.0.50"}, - {"8.0.22", "8.99.99"}, {"8.0.22-13", "8.0.22"}, {"8.0.22-13", "8.0.22-15.0"}, {"8.0.22-13", "8.0.50"}, - {"8.0.22-13", "8.99.99"}, {"8.0.28", "8.0.28"}, {"8.0.28", "8.0.50"}, - {"8.0.28", "8.99.99"}, - {"8.99.99", "8.99.99"}, + + // MySQL [8.4.0; 8.5.0), PXB [8.4.0; 8.5.0) + {"8.4.0", "8.4.0"}, + {"8.4.0", "8.4.0-4"}, + {"8.4.5-5.1", "8.4.0-4"}, + {"8.4.6", "8.4.0-4"}, + {"8.4.6", "8.4.0-5"}, + {"8.4.6", "8.4.1"}, } incompatible := []mysqlAndPXBVersions{ @@ -102,7 +106,7 @@ func TestMysqlAndXtrabackupCompatible(t *testing.T) { {"5.8", "2.4.99"}, {"5.8", "2.5"}, - // MySQL [8.0; 8.0.20), PXB [8.0.6; 9.0) + // MySQL [8.0; 8.0.20), PXB [8.0.6; 8.1.0) {"7.99.99", "8.0.5"}, {"7.99.99", "8.0.6"}, {"7.99.99", "8.0.10"}, @@ -110,30 +114,58 @@ func TestMysqlAndXtrabackupCompatible(t *testing.T) { {"7.99.99", "9.0"}, // {"8.0", "8.0.5"}, + {"8.0", "8.1.0"}, + {"8.0", "8.3.0"}, + {"8.0", "8.4.0"}, {"8.0", "9.0"}, // {"8.0.10", "8.0.5"}, + {"8.0.10", "8.1.0"}, + {"8.0.10", "8.3.0"}, + {"8.0.10", "8.4.0"}, {"8.0.10", "9.0"}, // {"8.0.19", "8.0.5"}, + {"8.0.19", "8.1.0"}, + {"8.0.19", "8.3.0"}, + {"8.0.19", "8.4.0"}, {"8.0.19", "9.0"}, - // MySQL [8.0.20; 8.0.21), PXB [8.0.12; 9.0) + // MySQL [8.0.20; 8.0.21), PXB [8.0.12; 8.1.0) {"8.0.20", "8.0.11"}, + {"8.0.20", "8.1.0"}, + {"8.0.20", "8.3.0"}, + {"8.0.20", "8.4.0"}, {"8.0.20", "9.0"}, - // MySQL [8.0.21; 8.0.22), PXB [8.0.14; 9.0) + // MySQL [8.0.21; 8.0.22), PXB [8.0.14; 8.1.0) {"8.0.21", "8.0.13"}, + {"8.0.21", "8.1.0"}, + {"8.0.21", "8.3.0"}, + {"8.0.21", "8.4.0"}, {"8.0.21", "9.0"}, - // MySQL [8.0.22; 9.0), PXB [8.0.22; 9.0) + // MySQL [8.0.22; 8.4.0), PXB [MySQL version; 8.1.0) {"8.0.22", "8.0.21"}, + {"8.0.22", "8.1.0"}, + {"8.0.22", "8.3.0"}, + {"8.0.22", "8.4.0"}, {"8.0.22", "9.0"}, // {"8.0.28", "8.0.22-15.0"}, {"8.0.28", "8.0.27"}, + {"8.0.28", "8.1.0"}, + {"8.0.28", "8.3.0"}, + {"8.0.28", "8.4.0"}, {"8.0.28", "9.0"}, // + // MySQL [8.4.0; 8.5.0), PXB [8.4.0; 8.5.0) + {"8.4.0", "8.0.35"}, + {"8.4.5-5.1", "8.0.35"}, + {"8.4.6", "8.3.0"}, + {"8.4.6", "8.5.0"}, + {"8.4.6", "9.0"}, + // {"8.99.99", "8.99.98"}, {"8.99.99", "9.0"}, // @@ -225,9 +257,10 @@ func TestSoftwareVersionsToMap(t *testing.T) { func TestMySQLSoftwaresInstalledAndCompatible(t *testing.T) { for _, test := range []struct { - name string - input map[models.SoftwareName]string - err error + name string + input map[models.SoftwareName]string + err error + errString string }{ // mysql cases { @@ -240,6 +273,16 @@ func TestMySQLSoftwaresInstalledAndCompatible(t *testing.T) { }, err: nil, }, + { + name: "successful with mysql 8.4 and xtrabackup 8.4", + input: map[models.SoftwareName]string{ + models.SoftwareName("mysqld"): "8.4.5-5.1", + models.SoftwareName("xtrabackup"): "8.4.0-4", + models.SoftwareName("xbcloud"): "8.4.0-4", + models.SoftwareName("qpress"): "1.1", + }, + err: nil, + }, { name: "no xtrabackup", input: map[models.SoftwareName]string{ @@ -285,13 +328,28 @@ func TestMySQLSoftwaresInstalledAndCompatible(t *testing.T) { models.SoftwareName("xbcloud"): "8.0.24", models.SoftwareName("qpress"): "1.1", }, - err: ErrIncompatibleXtrabackup, + err: ErrIncompatibleXtrabackup, + errString: "older than MySQL", + }, + { + name: "incompatible xtrabackup family", + input: map[models.SoftwareName]string{ + models.SoftwareName("mysqld"): "8.4.5-5.1", + models.SoftwareName("xtrabackup"): "8.0.35-34", + models.SoftwareName("xbcloud"): "8.0.35-34", + models.SoftwareName("qpress"): "1.1", + }, + err: ErrIncompatibleXtrabackup, + errString: "use Percona XtraBackup 8.4.x for MySQL 8.4.x", }, } { t.Run(test.name, func(t *testing.T) { err := mySQLBackupSoftwareInstalledAndCompatible(test.input) if test.err != nil { - require.ErrorIs(t, err, test.err) + assert.ErrorIs(t, err, test.err) + if test.errString != "" { + assert.Contains(t, err.Error(), test.errString) + } } else { require.NoError(t, err) } From 1d0cd0e5f110c762922e17b9039b55f4680aa1bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 May 2026 18:52:24 +0000 Subject: [PATCH 2/8] PMM-14594 Fix documentation formatting - move compatibility table inside caution admonition Agent-Logs-Url: https://github.com/percona/pmm/sessions/26f00a58-ad66-4a7b-8e9a-573f1cef3d41 Co-authored-by: JiriCtvrtka <62988319+JiriCtvrtka@users.noreply.github.com> --- .../backup/mysql-backup/mysql_prerequisites.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/documentation/docs/backup/mysql-backup/mysql_prerequisites.md b/documentation/docs/backup/mysql-backup/mysql_prerequisites.md index bcdbca3959f..ccc4df13cd9 100644 --- a/documentation/docs/backup/mysql-backup/mysql_prerequisites.md +++ b/documentation/docs/backup/mysql-backup/mysql_prerequisites.md @@ -39,13 +39,13 @@ Before creating MySQL backups, make sure to: !!! caution alert alert-warning "Important" The versions of each must be compatible with the installed version of MySQL. -Use the Percona XtraBackup version family that matches your MySQL version: + Use the Percona XtraBackup version family that matches your MySQL version: -| MySQL version | Percona XtraBackup version | -|---------------|----------------------------| -| MySQL 5.5, 5.6, or 5.7 | Percona XtraBackup 2.4.x | -| MySQL 8.0.0 through 8.0.21 | Percona XtraBackup 8.0.x, using the minimum version required for your MySQL release | -| MySQL 8.0.22 and newer 8.0 releases | Percona XtraBackup 8.0.x with the same or newer core version | -| MySQL 8.4.x | Percona XtraBackup 8.4.x | + | MySQL version | Percona XtraBackup version | + |---------------|----------------------------| + | MySQL 5.5, 5.6, or 5.7 | Percona XtraBackup 2.4.x | + | MySQL 8.0.0 through 8.0.21 | Percona XtraBackup 8.0.x, using the minimum version required for your MySQL release | + | MySQL 8.0.22 and newer 8.0 releases | Percona XtraBackup 8.0.x with the same or newer core version | + | MySQL 8.4.x | Percona XtraBackup 8.4.x | -Percona XtraBackup 8.4.x supports MySQL 8.4 LTS releases, including future 8.4 patch releases. It does not support backing up MySQL 8.0 or 9.x servers. \ No newline at end of file + Percona XtraBackup 8.4.x supports MySQL 8.4 LTS releases, including future 8.4 patch releases. It does not support backing up MySQL 8.0 or 9.x servers. From bb0c25c8f9cedd5e49741f7d5914c1e87c0828b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Thu, 21 May 2026 21:18:03 +0200 Subject: [PATCH 3/8] PMM-14594 Lint. --- managed/services/backup/compatibility_helpers.go | 3 ++- managed/services/backup/compatibility_helpers_test.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/managed/services/backup/compatibility_helpers.go b/managed/services/backup/compatibility_helpers.go index 6647600feae..69cf5cd4194 100644 --- a/managed/services/backup/compatibility_helpers.go +++ b/managed/services/backup/compatibility_helpers.go @@ -174,7 +174,8 @@ func mysqlAndXtrabackupCompatibilityError(mysqlVersionString, xtrabackupVersionS if xtrabackupVersion.LessThan(mysqlVersion) { return errors.Wrapf( ErrIncompatibleXtrabackup, - "Percona XtraBackup version %q is older than MySQL version %q; for MySQL 8.0.22 and newer 8.0 releases, use Percona XtraBackup 8.0.x with the same or newer core version", + "Percona XtraBackup version %q is older than MySQL version %q; "+ + "for MySQL 8.0.22+, use Percona XtraBackup 8.0.x with the same or newer core version", xtrabackupVersionString, mysqlVersionString, ) diff --git a/managed/services/backup/compatibility_helpers_test.go b/managed/services/backup/compatibility_helpers_test.go index b00513eae9d..4d53eab5be1 100644 --- a/managed/services/backup/compatibility_helpers_test.go +++ b/managed/services/backup/compatibility_helpers_test.go @@ -346,9 +346,9 @@ func TestMySQLSoftwaresInstalledAndCompatible(t *testing.T) { t.Run(test.name, func(t *testing.T) { err := mySQLBackupSoftwareInstalledAndCompatible(test.input) if test.err != nil { - assert.ErrorIs(t, err, test.err) + require.ErrorIs(t, err, test.err) if test.errString != "" { - assert.Contains(t, err.Error(), test.errString) + require.Contains(t, err.Error(), test.errString) } } else { require.NoError(t, err) From 420bd5937c5df0f444a892d565e20d788383cac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Fri, 22 May 2026 09:28:19 +0200 Subject: [PATCH 4/8] PMM-14594 Refactor. --- .../services/backup/compatibility_helpers.go | 75 +++++++++++++------ 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/managed/services/backup/compatibility_helpers.go b/managed/services/backup/compatibility_helpers.go index 69cf5cd4194..4b182fdbef4 100644 --- a/managed/services/backup/compatibility_helpers.go +++ b/managed/services/backup/compatibility_helpers.go @@ -133,20 +133,51 @@ func mysqlAndXtrabackupCoreVersions(mysqlVersionString, xtrabackupVersionString return mysqlVersion, xtrabackupVersion, nil } -func mysqlAndXtrabackupCoreVersionsCompatible(mysqlVersion, xtrabackupVersion *version.Version) bool { +type mysqlXtrabackupBand int + +const ( + mysqlXtrabackupBand84 mysqlXtrabackupBand = iota + mysqlXtrabackupBand80Aligned + mysqlXtrabackupBandLegacy + mysqlXtrabackupBandUnsupported +) + +func mysqlXtrabackupBandFor(mysqlVersion *version.Version) mysqlXtrabackupBand { switch { case mysqlVersion.GreaterThanOrEqual(mysql84Version) && mysqlVersion.LessThan(mysql85Version): - return xtrabackupVersion.GreaterThanOrEqual(mysql84Version) && xtrabackupVersion.LessThan(mysql85Version) + return mysqlXtrabackupBand84 case mysqlVersion.GreaterThanOrEqual(alignedXtrabackupVersion) && mysqlVersion.LessThan(mysql84Version): - return xtrabackupVersion.GreaterThanOrEqual(mysqlVersion) && xtrabackupVersion.LessThan(mysql81Version) + return mysqlXtrabackupBand80Aligned + case mysqlVersion.LessThan(alignedXtrabackupVersion): + return mysqlXtrabackupBandLegacy + default: + return mysqlXtrabackupBandUnsupported } +} - for _, cv := range mysqlAndXtrabackupCompatibleVersions { - if (mysqlVersion.GreaterThanOrEqual(cv.dbMinVersion) && - mysqlVersion.LessThan(cv.dbMaxVersion)) && - xtrabackupVersion.GreaterThanOrEqual(cv.backupToolMinVersion) && - xtrabackupVersion.LessThan(cv.backupToolMaxVersion) { - return true +func incompatibleXtrabackupError(message, xtrabackupVersionString, mysqlVersionString string) error { + return errors.Wrapf( + ErrIncompatibleXtrabackup, + message, + xtrabackupVersionString, + mysqlVersionString, + ) +} + +func mysqlAndXtrabackupCoreVersionsCompatible(mysqlVersion, xtrabackupVersion *version.Version) bool { + switch mysqlXtrabackupBandFor(mysqlVersion) { + case mysqlXtrabackupBand84: + return xtrabackupVersion.GreaterThanOrEqual(mysql84Version) && xtrabackupVersion.LessThan(mysql85Version) + case mysqlXtrabackupBand80Aligned: + return xtrabackupVersion.GreaterThanOrEqual(mysqlVersion) && xtrabackupVersion.LessThan(mysql81Version) + case mysqlXtrabackupBandLegacy: + for _, cv := range mysqlAndXtrabackupCompatibleVersions { + if mysqlVersion.GreaterThanOrEqual(cv.dbMinVersion) && + mysqlVersion.LessThan(cv.dbMaxVersion) && + xtrabackupVersion.GreaterThanOrEqual(cv.backupToolMinVersion) && + xtrabackupVersion.LessThan(cv.backupToolMaxVersion) { + return true + } } } @@ -162,18 +193,16 @@ func mysqlAndXtrabackupCompatibilityError(mysqlVersionString, xtrabackupVersionS return nil } - switch { - case mysqlVersion.GreaterThanOrEqual(mysql84Version) && mysqlVersion.LessThan(mysql85Version): - return errors.Wrapf( - ErrIncompatibleXtrabackup, + switch mysqlXtrabackupBandFor(mysqlVersion) { + case mysqlXtrabackupBand84: + return incompatibleXtrabackupError( "Percona XtraBackup version %q is not compatible with MySQL version %q; use Percona XtraBackup 8.4.x for MySQL 8.4.x", xtrabackupVersionString, mysqlVersionString, ) - case mysqlVersion.GreaterThanOrEqual(alignedXtrabackupVersion) && mysqlVersion.LessThan(mysql84Version): + case mysqlXtrabackupBand80Aligned: if xtrabackupVersion.LessThan(mysqlVersion) { - return errors.Wrapf( - ErrIncompatibleXtrabackup, + return incompatibleXtrabackupError( "Percona XtraBackup version %q is older than MySQL version %q; "+ "for MySQL 8.0.22+, use Percona XtraBackup 8.0.x with the same or newer core version", xtrabackupVersionString, @@ -181,22 +210,20 @@ func mysqlAndXtrabackupCompatibilityError(mysqlVersionString, xtrabackupVersionS ) } - return errors.Wrapf( - ErrIncompatibleXtrabackup, + return incompatibleXtrabackupError( "Percona XtraBackup version %q is not compatible with MySQL version %q; use Percona XtraBackup 8.0.x for MySQL 8.0.x", xtrabackupVersionString, mysqlVersionString, ) - case mysqlVersion.LessThan(alignedXtrabackupVersion): - return errors.Wrapf( - ErrIncompatibleXtrabackup, - "Percona XtraBackup version %q is not compatible with MySQL version %q; install a Percona XtraBackup version supported for this MySQL version", + case mysqlXtrabackupBandLegacy: + return incompatibleXtrabackupError( + "Percona XtraBackup version %q is not compatible with MySQL version %q; "+ + "install a Percona XtraBackup version supported for this MySQL version", xtrabackupVersionString, mysqlVersionString, ) default: - return errors.Wrapf( - ErrIncompatibleXtrabackup, + return incompatibleXtrabackupError( "PMM does not support Percona XtraBackup version %q with MySQL version %q yet", xtrabackupVersionString, mysqlVersionString, From 9001b51da125e0f7a5488e4a9b81188345b71f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Fri, 22 May 2026 10:30:18 +0200 Subject: [PATCH 5/8] PMM-14594 Lint. --- managed/services/backup/compatibility_helpers.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/managed/services/backup/compatibility_helpers.go b/managed/services/backup/compatibility_helpers.go index 4b182fdbef4..13d472bf4eb 100644 --- a/managed/services/backup/compatibility_helpers.go +++ b/managed/services/backup/compatibility_helpers.go @@ -179,6 +179,8 @@ func mysqlAndXtrabackupCoreVersionsCompatible(mysqlVersion, xtrabackupVersion *v return true } } + case mysqlXtrabackupBandUnsupported: + return false } return false From 97c53733f5d13c5d5032d328cb077d4ccf4ac617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Mon, 25 May 2026 13:52:41 +0200 Subject: [PATCH 6/8] PMM-14594 Max suggested refactor. --- .../services/backup/compatibility_helpers.go | 74 +++++++------------ 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/managed/services/backup/compatibility_helpers.go b/managed/services/backup/compatibility_helpers.go index 13d472bf4eb..e5289180c5c 100644 --- a/managed/services/backup/compatibility_helpers.go +++ b/managed/services/backup/compatibility_helpers.go @@ -31,43 +31,40 @@ type compatibility struct { backupToolMaxVersion *version.Version } +func mustVersion(versionString string) *version.Version { + return version.Must(version.NewVersion(versionString)) +} + var ( - mysqlAndXtrabackupCompatibleVersions []compatibility // Starting from MySQL 8.0.22 if the Percona XtraBackup version is lower than the database version, // processing will be stopped and Percona XtraBackup will not be allowed to continue. // https://www.percona.com/blog/2020/08/18/aligning-percona-xtrabackup-versions-with-percona-server-for-mysql/ - alignedXtrabackupVersion = version.Must(version.NewVersion("8.0.22")) - mysql81Version = version.Must(version.NewVersion("8.1.0")) - mysql84Version = version.Must(version.NewVersion("8.4.0")) - mysql85Version = version.Must(version.NewVersion("8.5.0")) + alignedXtrabackupVersion = mustVersion("8.0.22") + mysql81Version = mustVersion("8.1.0") + mysql84Version = mustVersion("8.4.0") + mysql85Version = mustVersion("8.5.0") - pbmMinSupportedVersion = version.Must(version.NewVersion("2.0.1")) -) + pbmMinSupportedVersion = mustVersion("2.0.1") -func init() { - versionStrings := []struct { - mysqlMinVersion string // inclusively - mysqlMaxVersion string // exclusively - xtrabackupMinVersion string // inclusively - xtrabackupMaxVersion string // exclusively - }{ + mysqlAndXtrabackupCompatibleVersions = []compatibility{ // It can back up data from InnoDB, XtraDB, and MyISAM tables on MySQL 5.5, 5.6 and 5.7 servers, // as well as Percona Server for MySQL with XtraDB. // https://www.percona.com/doc/percona-xtrabackup/2.4/index.html { - mysqlMinVersion: "5.5", - mysqlMaxVersion: "5.8", - xtrabackupMinVersion: "2.4.18", // https://jira.percona.com/browse/PXB-1978 - xtrabackupMaxVersion: "2.5", + dbMinVersion: mustVersion("5.5"), + dbMaxVersion: mustVersion("5.8"), + // https://jira.percona.com/browse/PXB-1978 + backupToolMinVersion: mustVersion("2.4.18"), + backupToolMaxVersion: mustVersion("2.5"), }, // In version 8.0.6, Percona XtraBackup introduces the support of the MyRocks storage engine // with Percona Server for MySQL version 8.0.15-6 or higher. // https://www.percona.com/doc/percona-xtrabackup/8.0/release-notes/8.0/8.0.6.html { - mysqlMinVersion: "8.0", - mysqlMaxVersion: "8.0.20", - xtrabackupMinVersion: "8.0.6", - xtrabackupMaxVersion: "8.1.0", + dbMinVersion: mustVersion("8.0"), + dbMaxVersion: mustVersion("8.0.20"), + backupToolMinVersion: mustVersion("8.0.6"), + backupToolMaxVersion: mustVersion("8.1.0"), }, // Percona XtraBackup 8.0.12 now supports backup and restore processing for all versions of MySQL; // previous versions of Percona XtraBackup will not work with MySQL 8.0.20 and higher. @@ -76,37 +73,22 @@ func init() { // and has been tested with the latest MySQL 8.0.20. // https://www.percona.com/doc/percona-xtrabackup/8.0/release-notes/8.0/8.0.13.html { - mysqlMinVersion: "8.0.20", - mysqlMaxVersion: "8.0.21", - xtrabackupMinVersion: "8.0.12", - xtrabackupMaxVersion: "8.1.0", + dbMinVersion: mustVersion("8.0.20"), + dbMaxVersion: mustVersion("8.0.21"), + backupToolMinVersion: mustVersion("8.0.12"), + backupToolMaxVersion: mustVersion("8.1.0"), }, // Percona XtraBackup 8.0.14 supports backup and restore processing for all versions of MySQL // and has been tested with the latest MySQL 8.0.21. // https://www.percona.com/doc/percona-xtrabackup/8.0/release-notes/8.0/8.0.14.html { - mysqlMinVersion: "8.0.21", - mysqlMaxVersion: "8.0.22", - xtrabackupMinVersion: "8.0.14", - xtrabackupMaxVersion: "8.1.0", + dbMinVersion: mustVersion("8.0.21"), + dbMaxVersion: mustVersion("8.0.22"), + backupToolMinVersion: mustVersion("8.0.14"), + backupToolMaxVersion: mustVersion("8.1.0"), }, } - - mysqlAndXtrabackupCompatibleVersions = make([]compatibility, 0, len(versionStrings)) - for _, s := range versionStrings { - mysqlMinVersion := version.Must(version.NewVersion(s.mysqlMinVersion)) - mysqlMaxVersion := version.Must(version.NewVersion(s.mysqlMaxVersion)) - xtrabackupMinVersion := version.Must(version.NewVersion(s.xtrabackupMinVersion)) - xtrabackupMaxVersion := version.Must(version.NewVersion(s.xtrabackupMaxVersion)) - - mysqlAndXtrabackupCompatibleVersions = append(mysqlAndXtrabackupCompatibleVersions, compatibility{ - dbMinVersion: mysqlMinVersion, - dbMaxVersion: mysqlMaxVersion, - backupToolMinVersion: xtrabackupMinVersion, - backupToolMaxVersion: xtrabackupMaxVersion, - }) - } -} +) func mysqlAndXtrabackupCompatible(mysqlVersionString, xtrabackupVersionString string) (bool, error) { mysqlVersion, xtrabackupVersion, err := mysqlAndXtrabackupCoreVersions(mysqlVersionString, xtrabackupVersionString) From 72254d8ceb5b817d673b921ea4be41cfc37eebda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Tue, 26 May 2026 10:55:44 +0200 Subject: [PATCH 7/8] PMM-14594 Refactor. --- .../services/backup/compatibility_helpers.go | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/managed/services/backup/compatibility_helpers.go b/managed/services/backup/compatibility_helpers.go index e5289180c5c..361ab98878f 100644 --- a/managed/services/backup/compatibility_helpers.go +++ b/managed/services/backup/compatibility_helpers.go @@ -25,10 +25,17 @@ import ( ) type compatibility struct { - dbMinVersion *version.Version - dbMaxVersion *version.Version - backupToolMinVersion *version.Version - backupToolMaxVersion *version.Version + dbVersions versionRange + backupToolVersions versionRange +} + +type versionRange struct { + min *version.Version + max *version.Version +} + +func (r versionRange) contains(v *version.Version) bool { + return !v.LessThan(r.min) && v.LessThan(r.max) } func mustVersion(versionString string) *version.Version { @@ -51,20 +58,16 @@ var ( // as well as Percona Server for MySQL with XtraDB. // https://www.percona.com/doc/percona-xtrabackup/2.4/index.html { - dbMinVersion: mustVersion("5.5"), - dbMaxVersion: mustVersion("5.8"), + dbVersions: versionRange{min: mustVersion("5.5"), max: mustVersion("5.8")}, // https://jira.percona.com/browse/PXB-1978 - backupToolMinVersion: mustVersion("2.4.18"), - backupToolMaxVersion: mustVersion("2.5"), + backupToolVersions: versionRange{min: mustVersion("2.4.18"), max: mustVersion("2.5")}, }, // In version 8.0.6, Percona XtraBackup introduces the support of the MyRocks storage engine // with Percona Server for MySQL version 8.0.15-6 or higher. // https://www.percona.com/doc/percona-xtrabackup/8.0/release-notes/8.0/8.0.6.html { - dbMinVersion: mustVersion("8.0"), - dbMaxVersion: mustVersion("8.0.20"), - backupToolMinVersion: mustVersion("8.0.6"), - backupToolMaxVersion: mustVersion("8.1.0"), + dbVersions: versionRange{min: mustVersion("8.0"), max: mustVersion("8.0.20")}, + backupToolVersions: versionRange{min: mustVersion("8.0.6"), max: mustVersion("8.1.0")}, }, // Percona XtraBackup 8.0.12 now supports backup and restore processing for all versions of MySQL; // previous versions of Percona XtraBackup will not work with MySQL 8.0.20 and higher. @@ -73,19 +76,15 @@ var ( // and has been tested with the latest MySQL 8.0.20. // https://www.percona.com/doc/percona-xtrabackup/8.0/release-notes/8.0/8.0.13.html { - dbMinVersion: mustVersion("8.0.20"), - dbMaxVersion: mustVersion("8.0.21"), - backupToolMinVersion: mustVersion("8.0.12"), - backupToolMaxVersion: mustVersion("8.1.0"), + dbVersions: versionRange{min: mustVersion("8.0.20"), max: mustVersion("8.0.21")}, + backupToolVersions: versionRange{min: mustVersion("8.0.12"), max: mustVersion("8.1.0")}, }, // Percona XtraBackup 8.0.14 supports backup and restore processing for all versions of MySQL // and has been tested with the latest MySQL 8.0.21. // https://www.percona.com/doc/percona-xtrabackup/8.0/release-notes/8.0/8.0.14.html { - dbMinVersion: mustVersion("8.0.21"), - dbMaxVersion: mustVersion("8.0.22"), - backupToolMinVersion: mustVersion("8.0.14"), - backupToolMaxVersion: mustVersion("8.1.0"), + dbVersions: versionRange{min: mustVersion("8.0.21"), max: mustVersion("8.0.22")}, + backupToolVersions: versionRange{min: mustVersion("8.0.14"), max: mustVersion("8.1.0")}, }, } ) @@ -126,9 +125,9 @@ const ( func mysqlXtrabackupBandFor(mysqlVersion *version.Version) mysqlXtrabackupBand { switch { - case mysqlVersion.GreaterThanOrEqual(mysql84Version) && mysqlVersion.LessThan(mysql85Version): + case versionRange{min: mysql84Version, max: mysql85Version}.contains(mysqlVersion): return mysqlXtrabackupBand84 - case mysqlVersion.GreaterThanOrEqual(alignedXtrabackupVersion) && mysqlVersion.LessThan(mysql84Version): + case versionRange{min: alignedXtrabackupVersion, max: mysql84Version}.contains(mysqlVersion): return mysqlXtrabackupBand80Aligned case mysqlVersion.LessThan(alignedXtrabackupVersion): return mysqlXtrabackupBandLegacy @@ -149,15 +148,12 @@ func incompatibleXtrabackupError(message, xtrabackupVersionString, mysqlVersionS func mysqlAndXtrabackupCoreVersionsCompatible(mysqlVersion, xtrabackupVersion *version.Version) bool { switch mysqlXtrabackupBandFor(mysqlVersion) { case mysqlXtrabackupBand84: - return xtrabackupVersion.GreaterThanOrEqual(mysql84Version) && xtrabackupVersion.LessThan(mysql85Version) + return versionRange{min: mysql84Version, max: mysql85Version}.contains(xtrabackupVersion) case mysqlXtrabackupBand80Aligned: - return xtrabackupVersion.GreaterThanOrEqual(mysqlVersion) && xtrabackupVersion.LessThan(mysql81Version) + return versionRange{min: mysqlVersion, max: mysql81Version}.contains(xtrabackupVersion) case mysqlXtrabackupBandLegacy: for _, cv := range mysqlAndXtrabackupCompatibleVersions { - if mysqlVersion.GreaterThanOrEqual(cv.dbMinVersion) && - mysqlVersion.LessThan(cv.dbMaxVersion) && - xtrabackupVersion.GreaterThanOrEqual(cv.backupToolMinVersion) && - xtrabackupVersion.LessThan(cv.backupToolMaxVersion) { + if cv.dbVersions.contains(mysqlVersion) && cv.backupToolVersions.contains(xtrabackupVersion) { return true } } From a0b89e382821065b0631a7a9dea8f411e3a78057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C4=8Ctvrtka?= Date: Tue, 26 May 2026 11:11:00 +0200 Subject: [PATCH 8/8] PMM-14594 Rename. --- managed/services/backup/compatibility_helpers.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/managed/services/backup/compatibility_helpers.go b/managed/services/backup/compatibility_helpers.go index 361ab98878f..c3e4d3853aa 100644 --- a/managed/services/backup/compatibility_helpers.go +++ b/managed/services/backup/compatibility_helpers.go @@ -34,7 +34,7 @@ type versionRange struct { max *version.Version } -func (r versionRange) contains(v *version.Version) bool { +func (r versionRange) isSupported(v *version.Version) bool { return !v.LessThan(r.min) && v.LessThan(r.max) } @@ -125,9 +125,9 @@ const ( func mysqlXtrabackupBandFor(mysqlVersion *version.Version) mysqlXtrabackupBand { switch { - case versionRange{min: mysql84Version, max: mysql85Version}.contains(mysqlVersion): + case versionRange{min: mysql84Version, max: mysql85Version}.isSupported(mysqlVersion): return mysqlXtrabackupBand84 - case versionRange{min: alignedXtrabackupVersion, max: mysql84Version}.contains(mysqlVersion): + case versionRange{min: alignedXtrabackupVersion, max: mysql84Version}.isSupported(mysqlVersion): return mysqlXtrabackupBand80Aligned case mysqlVersion.LessThan(alignedXtrabackupVersion): return mysqlXtrabackupBandLegacy @@ -148,12 +148,12 @@ func incompatibleXtrabackupError(message, xtrabackupVersionString, mysqlVersionS func mysqlAndXtrabackupCoreVersionsCompatible(mysqlVersion, xtrabackupVersion *version.Version) bool { switch mysqlXtrabackupBandFor(mysqlVersion) { case mysqlXtrabackupBand84: - return versionRange{min: mysql84Version, max: mysql85Version}.contains(xtrabackupVersion) + return versionRange{min: mysql84Version, max: mysql85Version}.isSupported(xtrabackupVersion) case mysqlXtrabackupBand80Aligned: - return versionRange{min: mysqlVersion, max: mysql81Version}.contains(xtrabackupVersion) + return versionRange{min: mysqlVersion, max: mysql81Version}.isSupported(xtrabackupVersion) case mysqlXtrabackupBandLegacy: for _, cv := range mysqlAndXtrabackupCompatibleVersions { - if cv.dbVersions.contains(mysqlVersion) && cv.backupToolVersions.contains(xtrabackupVersion) { + if cv.dbVersions.isSupported(mysqlVersion) && cv.backupToolVersions.isSupported(xtrabackupVersion) { return true } }