Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ spec:
date:
type: string
gtid:
maxLength: 1024
type: string
type:
enum:
Expand All @@ -574,6 +575,13 @@ spec:
- message: Date and GTID should not be set when type is 'latest'
rule: self.type != 'latest' || ((!has(self.date) || size(self.date)
== 0) && (!has(self.gtid) || size(self.gtid) == 0))
- message: GTID for type 'transaction' must be a single transaction
identifier in the form 'UUID:N' (e.g. 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:42')
rule: self.type != 'transaction' || self.gtid.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:[1-9][0-9]*$')
- message: GTID for type 'skip' must be a valid MySQL GTID set (e.g.
'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-10', 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:Domain_1:1-10',
or 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1,bbbbbbbb-bbbb-cccc-dddd-eeeeeeeeeeee:1-5')
rule: self.type != 'skip' || self.gtid.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?(:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?)*(,[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?(:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?)*)*$')
pxcCluster:
type: string
resources:
Expand Down
8 changes: 8 additions & 0 deletions deploy/bundle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,7 @@ spec:
date:
type: string
gtid:
maxLength: 1024
type: string
type:
enum:
Expand All @@ -965,6 +966,13 @@ spec:
- message: Date and GTID should not be set when type is 'latest'
rule: self.type != 'latest' || ((!has(self.date) || size(self.date)
== 0) && (!has(self.gtid) || size(self.gtid) == 0))
- message: GTID for type 'transaction' must be a single transaction
identifier in the form 'UUID:N' (e.g. 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:42')
rule: self.type != 'transaction' || self.gtid.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:[1-9][0-9]*$')
- message: GTID for type 'skip' must be a valid MySQL GTID set (e.g.
'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-10', 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:Domain_1:1-10',
or 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1,bbbbbbbb-bbbb-cccc-dddd-eeeeeeeeeeee:1-5')
rule: self.type != 'skip' || self.gtid.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?(:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?)*(,[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?(:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?)*)*$')
pxcCluster:
type: string
resources:
Expand Down
8 changes: 8 additions & 0 deletions deploy/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,7 @@ spec:
date:
type: string
gtid:
maxLength: 1024
type: string
type:
enum:
Expand All @@ -965,6 +966,13 @@ spec:
- message: Date and GTID should not be set when type is 'latest'
rule: self.type != 'latest' || ((!has(self.date) || size(self.date)
== 0) && (!has(self.gtid) || size(self.gtid) == 0))
- message: GTID for type 'transaction' must be a single transaction
identifier in the form 'UUID:N' (e.g. 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:42')
rule: self.type != 'transaction' || self.gtid.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:[1-9][0-9]*$')
- message: GTID for type 'skip' must be a valid MySQL GTID set (e.g.
'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-10', 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:Domain_1:1-10',
or 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1,bbbbbbbb-bbbb-cccc-dddd-eeeeeeeeeeee:1-5')
rule: self.type != 'skip' || self.gtid.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?(:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?)*(,[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?(:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?)*)*$')
pxcCluster:
type: string
resources:
Expand Down
8 changes: 8 additions & 0 deletions deploy/cw-bundle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,7 @@ spec:
date:
type: string
gtid:
maxLength: 1024
type: string
type:
enum:
Expand All @@ -965,6 +966,13 @@ spec:
- message: Date and GTID should not be set when type is 'latest'
rule: self.type != 'latest' || ((!has(self.date) || size(self.date)
== 0) && (!has(self.gtid) || size(self.gtid) == 0))
- message: GTID for type 'transaction' must be a single transaction
identifier in the form 'UUID:N' (e.g. 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:42')
rule: self.type != 'transaction' || self.gtid.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:[1-9][0-9]*$')
- message: GTID for type 'skip' must be a valid MySQL GTID set (e.g.
'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-10', 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:Domain_1:1-10',
or 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1,bbbbbbbb-bbbb-cccc-dddd-eeeeeeeeeeee:1-5')
rule: self.type != 'skip' || self.gtid.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?(:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?)*(,[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?(:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?)*)*$')
pxcCluster:
type: string
resources:
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/pxc/v1/pxc_prestore_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@ const (
// +kubebuilder:validation:XValidation:rule="self.type != 'date' || (has(self.date) && self.date.matches('^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) ([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$'))",message="Date is required for type 'date' and should be in format YYYY-MM-DD HH:MM:SS with valid ranges (MM: 01-12, DD: 01-31, HH: 00-23, MM/SS: 00-59)"
// +kubebuilder:validation:XValidation:rule="(self.type != 'transaction' && self.type != 'skip') || (has(self.gtid) && size(self.gtid) > 0)",message="GTID is required for types 'transaction' and 'skip'"
// +kubebuilder:validation:XValidation:rule="self.type != 'latest' || ((!has(self.date) || size(self.date) == 0) && (!has(self.gtid) || size(self.gtid) == 0))",message="Date and GTID should not be set when type is 'latest'"
// +kubebuilder:validation:XValidation:rule="self.type != 'transaction' || self.gtid.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:[1-9][0-9]*$')",message="GTID for type 'transaction' must be a single transaction identifier in the form 'UUID:N' (e.g. 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:42')"
// +kubebuilder:validation:XValidation:rule="self.type != 'skip' || self.gtid.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?(:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?)*(,[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?(:([a-zA-Z_][a-zA-Z0-9_]{0,31}:)?[1-9][0-9]*(-[1-9][0-9]*)?)*)*$')",message="GTID for type 'skip' must be a valid MySQL GTID set (e.g. 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-10', 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:Domain_1:1-10', or 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1,bbbbbbbb-bbbb-cccc-dddd-eeeeeeeeeeee:1-5')"
type PITR struct {
BackupSource *PXCBackupStatus `json:"backupSource"`
// +kubebuilder:validation:Enum={latest,date,transaction,skip}
Type string `json:"type"`
Date string `json:"date"`
// +kubebuilder:validation:MaxLength=1024
GTID string `json:"gtid"`
}

Expand Down
29 changes: 24 additions & 5 deletions pkg/controller/pxcrestore/pitr_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,15 @@ var _ = Describe("PerconaXtraDBClusterRestore PITR CRD validation", Ordered, fun
},
Entry("type latest", "valid-latest", &pxcv1.PITR{Type: "latest"}),
Entry("type date with valid format", "valid-date", &pxcv1.PITR{Type: "date", Date: "2024-01-15 12:30:00"}),
Entry("type transaction with gtid", "valid-transaction", &pxcv1.PITR{Type: "transaction", GTID: "abc123:1-10"}),
Entry("type skip with gtid", "valid-skip", &pxcv1.PITR{Type: "skip", GTID: "abc123:1-10"}),
// transaction: single UUID:N only (recoverer uses SplitN+ParseInt, tagged format UUID:tag:N not supported)
Entry("type transaction with single gtid", "valid-transaction", &pxcv1.PITR{Type: "transaction", GTID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:42"}),
// skip: full GTID set syntax is allowed, including tags (MySQL 8.4)
Entry("type skip with single gtid", "valid-skip-single", &pxcv1.PITR{Type: "skip", GTID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:42"}),
Entry("type skip with gtid range", "valid-skip-range", &pxcv1.PITR{Type: "skip", GTID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-10"}),
Entry("type skip with multiple intervals", "valid-skip-intervals", &pxcv1.PITR{Type: "skip", GTID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-5:7-9"}),
Entry("type skip with multi-source gtid set", "valid-skip-multi-source", &pxcv1.PITR{Type: "skip", GTID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-10,bbbbbbbb-bbbb-cccc-dddd-eeeeeeeeeeee:1-5"}),
Entry("type skip with tagged gtid", "valid-skip-tagged", &pxcv1.PITR{Type: "skip", GTID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:Domain_1:1-10"}),
Entry("type skip with multiple tags", "valid-skip-multi-tag", &pxcv1.PITR{Type: "skip", GTID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:Domain_1:1-3:Domain_2:8-52"}),
)

DescribeTable("invalid PITR configurations",
Expand All @@ -103,14 +110,26 @@ var _ = Describe("PerconaXtraDBClusterRestore PITR CRD validation", Ordered, fun
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(errMsg))
},
// date type
Entry("type date with empty date", "invalid-date-empty", &pxcv1.PITR{Type: "date"}, "Date is required"),
Entry("type date with wrong format", "invalid-date-format", &pxcv1.PITR{Type: "date", Date: "15-01-2024 12:30:00"}, "format YYYY-MM-DD"),
Entry("type date with invalid month", "invalid-date-month", &pxcv1.PITR{Type: "date", Date: "2024-27-30 12:30:00"}, "format YYYY-MM-DD"),
Entry("type date with no time)", "invalid-date-no-time", &pxcv1.PITR{Type: "date", Date: "2024-12-30"}, "format YYYY-MM-DD"),
Entry("type date with no time", "invalid-date-no-time", &pxcv1.PITR{Type: "date", Date: "2024-12-30"}, "format YYYY-MM-DD"),
// latest type
Entry("type latest with date set", "invalid-latest-date", &pxcv1.PITR{Type: "latest", Date: "2024-01-15 12:30:00"}, "Date and GTID should not be set"),
Entry("type latest with gtid set", "invalid-latest-gtid", &pxcv1.PITR{Type: "latest", GTID: "abc123:1"}, "Date and GTID should not be set"),
Entry("type latest with gtid set", "invalid-latest-gtid", &pxcv1.PITR{Type: "latest", GTID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1"}, "Date and GTID should not be set"),
// unknown type
Entry("unknown type", "invalid-unknown-type", &pxcv1.PITR{Type: "unknown"}, "Unsupported value"),
// transaction: GTID is required and must be UUID:N or UUID:tag:N (N >= 1)
Entry("type transaction without gtid", "invalid-transaction-no-gtid", &pxcv1.PITR{Type: "transaction"}, "GTID is required"),
Entry("type transaction with non-uuid gtid", "invalid-transaction-bad-uuid", &pxcv1.PITR{Type: "transaction", GTID: "notauuid:42"}, "single transaction identifier"),
Entry("type transaction with gtid range", "invalid-transaction-range", &pxcv1.PITR{Type: "transaction", GTID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1-10"}, "single transaction identifier"),
Entry("type transaction with multi-source gtid set", "invalid-transaction-multi-source", &pxcv1.PITR{Type: "transaction", GTID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:1,bbbbbbbb-bbbb-cccc-dddd-eeeeeeeeeeee:1"}, "single transaction identifier"),
Entry("type transaction with zero transaction id", "invalid-transaction-zero", &pxcv1.PITR{Type: "transaction", GTID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:0"}, "single transaction identifier"),
Entry("type transaction with tagged gtid", "invalid-transaction-tagged", &pxcv1.PITR{Type: "transaction", GTID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:Domain_1:42"}, "single transaction identifier"),
// skip: GTID is required and must be a valid MySQL GTID set
Entry("type skip without gtid", "invalid-skip-no-gtid", &pxcv1.PITR{Type: "skip"}, "GTID is required"),
Entry("unknown type", "invalid-unknown-type", &pxcv1.PITR{Type: "unknown"}, "Unsupported value"),
Entry("type skip with non-uuid gtid", "invalid-skip-bad-uuid", &pxcv1.PITR{Type: "skip", GTID: "notauuid:1-10"}, "valid MySQL GTID set"),
Entry("type skip with malformed gtid", "invalid-skip-malformed", &pxcv1.PITR{Type: "skip", GTID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"}, "valid MySQL GTID set"),
)
})
Loading