Skip to content
Open
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
13 changes: 9 additions & 4 deletions api/v1/perconaservermysql_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,10 +439,11 @@ type BackupSpec struct {
ServiceAccountName string `json:"serviceAccountName,omitempty"`
ContainerSecurityContext *corev1.SecurityContext `json:"containerSecurityContext,omitempty"`
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
Storages map[string]*BackupStorageSpec `json:"storages,omitempty"`
BackoffLimit *int32 `json:"backoffLimit,omitempty"`
PiTR PiTRSpec `json:"pitr,omitempty"`
Schedule []BackupSchedule `json:"schedule,omitempty"`
// +kubebuilder:validation:MaxProperties=100
Storages map[string]*BackupStorageSpec `json:"storages,omitempty"`
BackoffLimit *int32 `json:"backoffLimit,omitempty"`
PiTR PiTRSpec `json:"pitr,omitempty"`
Schedule []BackupSchedule `json:"schedule,omitempty"`

// Deprecated: not supported since v0.12.0. Use initContainer instead
InitImage string `json:"initImage,omitempty"`
Expand Down Expand Up @@ -556,6 +557,10 @@ func (args BackupContainerArgs) GetXtrabackupFlagValue(flag string) string {
}

type BackupContainerArgs struct {
// XtraBackup requires --defaults-file to precede every other option.
// +kubebuilder:validation:MaxItems=100
// +kubebuilder:validation:items:MaxLength=1024
// +kubebuilder:validation:XValidation:rule="!self.exists(arg, arg == '--defaults-file' || arg.startsWith('--defaults-file=')) || (self[0].startsWith('--defaults-file=') && self[0] != '--defaults-file=')",message="--defaults-file must use --defaults-file=<path> syntax and be the first xtrabackup argument"
Xtrabackup []string `json:"xtrabackup,omitempty"`
Xbcloud []string `json:"xbcloud,omitempty"`
Xbstream []string `json:"xbstream,omitempty"`
Expand Down
22 changes: 11 additions & 11 deletions build/run-restore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ decrypt() {
local targetdir=$1
if [ -n "${ENCRYPTION_ALGORITHM}" ]; then
# shellcheck disable=SC2086
xtrabackup --decrypt=${ENCRYPTION_ALGORITHM} --encrypt-key-file=${encryption_key_file} --target-dir="${targetdir}" --parallel="${PARALLEL}" ${XB_EXTRA_ARGS}
xtrabackup ${XB_EXTRA_ARGS} --decrypt=${ENCRYPTION_ALGORITHM} --encrypt-key-file=${encryption_key_file} --target-dir="${targetdir}" --parallel="${PARALLEL}"
find "${targetdir}" -name '*.xbcrypt' -delete
fi
}
Expand Down Expand Up @@ -79,15 +79,15 @@ restore_full() {
decrypt "${tmpdir}"

# shellcheck disable=SC2086
xtrabackup --decompress --remove-original --parallel="${PARALLEL}" --target-dir="${tmpdir}" ${XB_EXTRA_ARGS}
xtrabackup ${XB_EXTRA_ARGS} --decompress --remove-original --parallel="${PARALLEL}" --target-dir="${tmpdir}"

local keyring
keyring=$(get_keyring_arg)

# shellcheck disable=SC2086
xtrabackup --prepare --rollback-prepared-trx --target-dir="${tmpdir}" ${XB_EXTRA_ARGS} ${keyring}
xtrabackup ${XB_EXTRA_ARGS} --prepare --rollback-prepared-trx --target-dir="${tmpdir}" ${keyring}
# shellcheck disable=SC2086
xtrabackup --datadir="${DATADIR}" --move-back --force-non-empty-directories --target-dir="${tmpdir}" ${XB_EXTRA_ARGS}
xtrabackup ${XB_EXTRA_ARGS} --datadir="${DATADIR}" --move-back --force-non-empty-directories --target-dir="${tmpdir}"

rm -rf "${tmpdir}"

Expand All @@ -109,17 +109,17 @@ restore_incremental() {
decrypt "${basedir}"

# shellcheck disable=SC2086
xtrabackup --decompress --remove-original --parallel="${PARALLEL}" --target-dir="${basedir}" ${XB_EXTRA_ARGS}
xtrabackup ${XB_EXTRA_ARGS} --decompress --remove-original --parallel="${PARALLEL}" --target-dir="${basedir}"

local keyring
keyring=$(get_keyring_arg)

# Prepare the base backup with --apply-log-only (redo only, no rollback)
# shellcheck disable=SC2086
xtrabackup --prepare --apply-log-only --target-dir="${basedir}" ${XB_EXTRA_ARGS} ${keyring}
xtrabackup ${XB_EXTRA_ARGS} --prepare --apply-log-only --target-dir="${basedir}" ${keyring}

# Parse the comma-separated list of incremental destinations
IFS=',' read -ra INCR_DESTS <<< "${BACKUP_INCREMENTALS_DEST}"
IFS=',' read -ra INCR_DESTS <<<"${BACKUP_INCREMENTALS_DEST}"
local total=${#INCR_DESTS[@]}
local count=0

Expand All @@ -134,24 +134,24 @@ restore_incremental() {
decrypt "${incrdir}"

# shellcheck disable=SC2086
xtrabackup --decompress --remove-original --parallel="${PARALLEL}" --target-dir="${incrdir}" ${XB_EXTRA_ARGS}
xtrabackup ${XB_EXTRA_ARGS} --decompress --remove-original --parallel="${PARALLEL}" --target-dir="${incrdir}"

if [ "${count}" -lt "${total}" ]; then
# Not the last incremental: use --apply-log-only
# shellcheck disable=SC2086
xtrabackup --prepare --apply-log-only --target-dir="${basedir}" --incremental-dir="${incrdir}" ${XB_EXTRA_ARGS} ${keyring}
xtrabackup ${XB_EXTRA_ARGS} --prepare --apply-log-only --target-dir="${basedir}" --incremental-dir="${incrdir}" ${keyring}
else
# Last incremental: omit --apply-log-only to allow rollback of uncommitted transactions
# shellcheck disable=SC2086
xtrabackup --prepare --target-dir="${basedir}" --incremental-dir="${incrdir}" ${XB_EXTRA_ARGS} ${keyring}
xtrabackup ${XB_EXTRA_ARGS} --prepare --target-dir="${basedir}" --incremental-dir="${incrdir}" ${keyring}
fi

rm -rf "${incrdir}"
done

# Move the prepared backup to the data directory
# shellcheck disable=SC2086
xtrabackup --datadir="${DATADIR}" --move-back --force-non-empty-directories --target-dir="${basedir}" ${XB_EXTRA_ARGS}
xtrabackup ${XB_EXTRA_ARGS} --datadir="${DATADIR}" --move-back --force-non-empty-directories --target-dir="${basedir}"

rm -rf "${basedir}"

Expand Down
20 changes: 16 additions & 4 deletions cmd/sidecar/handler/backup/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,16 +229,28 @@ func xtrabackupArgs(user, pass string, conf *xb.BackupConfig) []string {
if _, err := os.Stat(mysql.CustomMyCnfPath); err == nil {
args = append([]string{"--defaults-extra-file=" + mysql.CustomMyCnfPath}, args...)
}
if conf != nil && conf.EncryptionKeyFile != "" {

if conf == nil {
return args
}
if conf.EncryptionKeyFile != "" {
args = append(args, fmt.Sprintf("--encrypt-key-file=%s", conf.EncryptionKeyFile))
if conf.ContainerOptions.GetArgs().GetXtrabackupFlagValue("--encrypt") == "" {
args = append(args, "--encrypt=AES256")
}
}
if conf != nil && conf.ContainerOptions != nil {
args = append(args, conf.ContainerOptions.Args.Xtrabackup...)
if conf.ContainerOptions != nil {
customArgs := conf.ContainerOptions.Args.Xtrabackup
// kubebuilder validation guarantees that --defaults-file=<path> is the first custom argument if specified.
// https://docs.percona.com/percona-xtrabackup/8.0/xtrabackup-option-reference.html#defaults-file
// We should move it to the beginning of args. Other custom arguments should be appended after the generated arguments.
if len(customArgs) > 0 && strings.HasPrefix(customArgs[0], "--defaults-file=") && customArgs[0] != "--defaults-file=" {
args = append([]string{customArgs[0]}, args...)
customArgs = customArgs[1:]
}
args = append(args, customArgs...)
Comment on lines +242 to +251

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're re-ordering it anyway, do we need a CRD validation? Can we also do the same for restore?

}
if conf != nil && conf.IncrementalLsn != "" {
if conf.IncrementalLsn != "" {
args = append(args, fmt.Sprintf("--incremental-lsn=%s", conf.IncrementalLsn))
}

Expand Down
161 changes: 161 additions & 0 deletions cmd/sidecar/handler/backup/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package backup

import (
"testing"

"github.com/stretchr/testify/assert"

apiv1 "github.com/percona/percona-server-mysql-operator/api/v1"
xb "github.com/percona/percona-server-mysql-operator/pkg/xtrabackup"
)

func TestXtrabackupArgs(t *testing.T) {
defaultArgs := []string{
"--backup",
"--stream=xbstream",
"--safe-slave-backup",
"--slave-info",
"--target-dir=/backup/",
"--databases-exclude=lost+found",
"--user=backup-user",
"--password=backup-password",
}

tests := map[string]struct {
conf *xb.BackupConfig
want []string
}{
"nil config": {
want: defaultArgs,
},
"empty config": {
conf: &xb.BackupConfig{},
want: defaultArgs,
},
"empty container options": {
conf: &xb.BackupConfig{
ContainerOptions: &apiv1.BackupContainerOptions{},
},
want: defaultArgs,
},
"custom arguments": {
conf: &xb.BackupConfig{
ContainerOptions: &apiv1.BackupContainerOptions{
Args: apiv1.BackupContainerArgs{Xtrabackup: []string{"--compress", "--parallel=2"}},
},
},
want: append(defaultArgs, "--compress", "--parallel=2"),
},
"defaults file with equals is first": {
conf: &xb.BackupConfig{
ContainerOptions: &apiv1.BackupContainerOptions{
Args: apiv1.BackupContainerArgs{
Xtrabackup: []string{"--defaults-file=/etc/my.cnf", "--compress", "--parallel=2"},
},
},
},
want: append(
[]string{"--defaults-file=/etc/my.cnf"},
append(defaultArgs, "--compress", "--parallel=2")...,
),
},
"defaults file with separate value is not promoted": {
conf: &xb.BackupConfig{
ContainerOptions: &apiv1.BackupContainerOptions{
Args: apiv1.BackupContainerArgs{
Xtrabackup: []string{"--defaults-file", "/etc/my.cnf", "--compress", "--parallel=2"},
},
},
},
want: append(defaultArgs, "--defaults-file", "/etc/my.cnf", "--compress", "--parallel=2"),
},
"defaults file without value is not promoted": {
conf: &xb.BackupConfig{
ContainerOptions: &apiv1.BackupContainerOptions{
Args: apiv1.BackupContainerArgs{Xtrabackup: []string{"--defaults-file"}},
},
},
want: append(defaultArgs, "--defaults-file"),
},
"defaults file followed by flag is not promoted": {
conf: &xb.BackupConfig{
ContainerOptions: &apiv1.BackupContainerOptions{
Args: apiv1.BackupContainerArgs{Xtrabackup: []string{"--defaults-file", "--compress"}},
},
},
want: append(defaultArgs, "--defaults-file", "--compress"),
},
"defaults file with empty equals value is not promoted": {
conf: &xb.BackupConfig{
ContainerOptions: &apiv1.BackupContainerOptions{
Args: apiv1.BackupContainerArgs{Xtrabackup: []string{"--defaults-file="}},
},
},
want: append(defaultArgs, "--defaults-file="),
},
"defaults file with separate empty value is not promoted": {
conf: &xb.BackupConfig{
ContainerOptions: &apiv1.BackupContainerOptions{
Args: apiv1.BackupContainerArgs{Xtrabackup: []string{"--defaults-file", ""}},
},
},
want: append(defaultArgs, "--defaults-file", ""),
},
"encryption uses default algorithm": {
conf: &xb.BackupConfig{
EncryptionKeyFile: "/etc/mysql/encryption-key",
},
want: append(
defaultArgs,
"--encrypt-key-file=/etc/mysql/encryption-key",
"--encrypt=AES256",
),
},
"custom encryption algorithm overrides default": {
conf: &xb.BackupConfig{
EncryptionKeyFile: "/etc/mysql/encryption-key",
ContainerOptions: &apiv1.BackupContainerOptions{
Args: apiv1.BackupContainerArgs{Xtrabackup: []string{"--encrypt=AES192"}},
},
},
want: append(
defaultArgs,
"--encrypt-key-file=/etc/mysql/encryption-key",
"--encrypt=AES192",
),
},
"incremental backup": {
conf: &xb.BackupConfig{
IncrementalLsn: "123:456",
},
want: append(defaultArgs, "--incremental-lsn=123:456"),
},
"all optional arguments preserve required ordering": {
conf: &xb.BackupConfig{
EncryptionKeyFile: "/etc/mysql/encryption-key",
ContainerOptions: &apiv1.BackupContainerOptions{
Args: apiv1.BackupContainerArgs{
Xtrabackup: []string{"--defaults-file=/etc/my.cnf", "--encrypt=AES192", "--parallel=2"},
},
},
IncrementalLsn: "123:456",
},
want: append(
[]string{"--defaults-file=/etc/my.cnf"},
append(
defaultArgs,
"--encrypt-key-file=/etc/mysql/encryption-key",
"--encrypt=AES192",
"--parallel=2",
"--incremental-lsn=123:456",
)...,
),
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
assert.Equal(t, test.want, xtrabackupArgs("backup-user", "backup-password", test.conf))
})
}
}
16 changes: 16 additions & 0 deletions config/crd/bases/ps.percona.com_perconaservermysqlbackups.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,16 @@ spec:
type: array
xtrabackup:
items:
maxLength: 1024
type: string
maxItems: 100
type: array
x-kubernetes-validations:
- message: --defaults-file must use --defaults-file=<path>
syntax and be the first xtrabackup argument
rule: '!self.exists(arg, arg == ''--defaults-file'' || arg.startsWith(''--defaults-file=''))
|| (self[0].startsWith(''--defaults-file='') && self[0]
!= ''--defaults-file='')'
type: object
env:
items:
Expand Down Expand Up @@ -700,8 +708,16 @@ spec:
type: array
xtrabackup:
items:
maxLength: 1024
type: string
maxItems: 100
type: array
x-kubernetes-validations:
- message: --defaults-file must use --defaults-file=<path>
syntax and be the first xtrabackup argument
rule: '!self.exists(arg, arg == ''--defaults-file''
|| arg.startsWith(''--defaults-file='')) || (self[0].startsWith(''--defaults-file='')
&& self[0] != ''--defaults-file='')'
type: object
env:
items:
Expand Down
16 changes: 16 additions & 0 deletions config/crd/bases/ps.percona.com_perconaservermysqlrestores.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -565,8 +565,16 @@ spec:
type: array
xtrabackup:
items:
maxLength: 1024
type: string
maxItems: 100
type: array
x-kubernetes-validations:
- message: --defaults-file must use --defaults-file=<path>
syntax and be the first xtrabackup argument
rule: '!self.exists(arg, arg == ''--defaults-file''
|| arg.startsWith(''--defaults-file='')) || (self[0].startsWith(''--defaults-file='')
&& self[0] != ''--defaults-file='')'
type: object
env:
items:
Expand Down Expand Up @@ -1099,8 +1107,16 @@ spec:
type: array
xtrabackup:
items:
maxLength: 1024
type: string
maxItems: 100
type: array
x-kubernetes-validations:
- message: --defaults-file must use --defaults-file=<path>
syntax and be the first xtrabackup argument
rule: '!self.exists(arg, arg == ''--defaults-file'' || arg.startsWith(''--defaults-file=''))
|| (self[0].startsWith(''--defaults-file='') && self[0]
!= ''--defaults-file='')'
type: object
env:
items:
Expand Down
10 changes: 10 additions & 0 deletions config/crd/bases/ps.percona.com_perconaservermysqls.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2119,8 +2119,17 @@ spec:
type: array
xtrabackup:
items:
maxLength: 1024
type: string
maxItems: 100
type: array
x-kubernetes-validations:
- message: --defaults-file must use --defaults-file=<path>
syntax and be the first xtrabackup argument
rule: '!self.exists(arg, arg == ''--defaults-file''
|| arg.startsWith(''--defaults-file='')) ||
(self[0].startsWith(''--defaults-file='') &&
self[0] != ''--defaults-file='')'
type: object
env:
items:
Expand Down Expand Up @@ -2634,6 +2643,7 @@ spec:
required:
- type
type: object
maxProperties: 100
type: object
type: object
clusterServiceDNSSuffix:
Expand Down
Loading
Loading