Skip to content

Commit 6a7fddd

Browse files
egegunesgkechhorsgithub-actions[bot]
authored
K8SPS-642: Point-in-time recovery (#1252)
--- Co-authored-by: George Kechagias <george.kechagias@percona.com> Co-authored-by: Viacheslav Sarzhan <slava.sarzhan@percona.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 679e3ae commit 6a7fddd

106 files changed

Lines changed: 5476 additions & 196 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/v1/perconaservermysql_types.go

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ func (t ClusterType) isValid() bool {
141141
return false
142142
}
143143

144+
// +kubebuilder:validation:XValidation:rule="has(self.image) && self.image != ''",message="mysql.image is required"
145+
// +kubebuilder:validation:XValidation:rule="has(self.size) && self.size > 0",message="mysql.size must be greater than 0"
144146
type MySQLSpec struct {
145147
// +kubebuilder:validation:Enum=group-replication;async
146148
// +kubebuilder:default=group-replication
@@ -176,6 +178,8 @@ type SidecarPVC struct {
176178
Spec corev1.PersistentVolumeClaimSpec `json:"spec"`
177179
}
178180

181+
// +kubebuilder:validation:XValidation:rule="!(has(self.enabled) && self.enabled) || (has(self.image) && self.image != '')",message="orchestrator.image is required when orchestrator is enabled"
182+
// +kubebuilder:validation:XValidation:rule="!(has(self.enabled) && self.enabled) || (has(self.size) && self.size > 0)",message="orchestrator.size must be greater than 0 when orchestrator is enabled"
179183
type OrchestratorSpec struct {
180184
Enabled bool `json:"enabled,omitempty"`
181185
Expose ServiceExpose `json:"expose,omitempty"`
@@ -184,7 +188,7 @@ type OrchestratorSpec struct {
184188
}
185189

186190
type ContainerSpec struct {
187-
Image string `json:"image"`
191+
Image string `json:"image,omitempty"`
188192
ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"`
189193
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
190194
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
@@ -200,7 +204,6 @@ type ContainerSpec struct {
200204
}
201205

202206
type PodSpec struct {
203-
// +kubebuilder:validation:Required
204207
Size int32 `json:"size,omitempty"`
205208
Annotations map[string]string `json:"annotations,omitempty"`
206209
Labels map[string]string `json:"labels,omitempty"`
@@ -286,9 +289,10 @@ func (s *PodSpec) GetInitSpec(cr *PerconaServerMySQL) InitContainerSpec {
286289
return *s.InitContainer
287290
}
288291

292+
// +kubebuilder:validation:XValidation:rule="!(has(self.enabled) && self.enabled) || (has(self.image) && self.image != '')",message="pmm.image is required when pmm is enabled"
289293
type PMMSpec struct {
290294
Enabled bool `json:"enabled,omitempty"`
291-
Image string `json:"image"`
295+
Image string `json:"image,omitempty"`
292296
MySQLParams string `json:"mysqlParams,omitempty"`
293297
ServerHost string `json:"serverHost,omitempty"`
294298
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
@@ -513,6 +517,10 @@ func (b *BackupStorageAzureSpec) ContainerAndPrefix() (string, string) {
513517
return container, prefix
514518
}
515519

520+
// +kubebuilder:validation:XValidation:rule="!(has(self.enabled) && self.enabled) || has(self.binlogServer)",message="binlogServer is required when pitr is enabled"
521+
// +kubebuilder:validation:XValidation:rule="!(has(self.enabled) && self.enabled) || !has(self.binlogServer) || (has(self.binlogServer.image) && self.binlogServer.image != '')",message="binlogServer.image is required when pitr is enabled"
522+
// +kubebuilder:validation:XValidation:rule="!(has(self.enabled) && self.enabled) || !has(self.binlogServer) || (has(self.binlogServer.size) && self.binlogServer.size > 0)",message="binlogServer.size is required when pitr is enabled"
523+
// +kubebuilder:validation:XValidation:rule="!(has(self.enabled) && self.enabled) || !has(self.binlogServer) || (has(self.binlogServer.serverId) && self.binlogServer.serverId > 0)",message="binlogServer.serverId is required when pitr is enabled"
516524
type PiTRSpec struct {
517525
Enabled bool `json:"enabled,omitempty"`
518526

@@ -523,19 +531,39 @@ type BinlogServerStorageSpec struct {
523531
S3 *BackupStorageS3Spec `json:"s3,omitempty"`
524532
}
525533

534+
// +kubebuilder:validation:XValidation:rule="!has(self.size) || self.size <= 1",message="binlogServer size cannot be more than 1"
526535
type BinlogServerSpec struct {
527-
Storage BinlogServerStorageSpec `json:"storage"`
536+
Storage BinlogServerStorageSpec `json:"storage,omitempty"`
528537

529538
// The number of seconds the MySQL client library will wait to establish a connection with a remote host
530-
ConnectTimeout int32 `json:"connectTimeout"`
539+
// +kubebuilder:default=30
540+
ConnectTimeout int32 `json:"connectTimeout,omitempty"`
531541
// The number of seconds the MySQL client library will wait to read data from a remote server.
532-
ReadTimeout int32 `json:"readTimeout"`
542+
// +kubebuilder:default=30
543+
ReadTimeout int32 `json:"readTimeout,omitempty"`
533544
// The number of seconds the MySQL client library will wait to write data to a remote server.
534-
WriteTimeout int32 `json:"writeTimeout"`
545+
// +kubebuilder:default=30
546+
WriteTimeout int32 `json:"writeTimeout,omitempty"`
535547
// Specifies the server ID that the utility will be using when connecting to a remote MySQL server
536-
ServerID int32 `json:"serverId"`
548+
ServerID int32 `json:"serverId,omitempty"`
537549
// The number of seconds the utility will spend in disconnected mode between reconnection attempts.
538-
IdleTime int32 `json:"idleTime"`
550+
// +kubebuilder:default=30
551+
IdleTime int32 `json:"idleTime,omitempty"`
552+
// SSLMode specifies the SSL mode for the connection to MySQL.
553+
// +kubebuilder:default="verify_identity"
554+
SSLMode string `json:"sslMode,omitempty"`
555+
// VerifyChecksum enables checksum verification during replication.
556+
// +kubebuilder:default=true
557+
VerifyChecksum *bool `json:"verifyChecksum,omitempty"`
558+
// RewriteFileSize specifies the maximum binlog file size for rewrite.
559+
// +kubebuilder:default="128M"
560+
RewriteFileSize string `json:"rewriteFileSize,omitempty"`
561+
// CheckpointSize specifies the storage checkpoint size.
562+
// +kubebuilder:default="16M"
563+
CheckpointSize string `json:"checkpointSize,omitempty"`
564+
// CheckpointInterval specifies the storage checkpoint interval.
565+
// +kubebuilder:default="30s"
566+
CheckpointInterval string `json:"checkpointInterval,omitempty"`
539567

540568
PodSpec `json:",inline"`
541569
}
@@ -545,6 +573,8 @@ type ProxySpec struct {
545573
HAProxy *HAProxySpec `json:"haproxy,omitempty"`
546574
}
547575

576+
// +kubebuilder:validation:XValidation:rule="!(has(self.enabled) && self.enabled) || (has(self.image) && self.image != '')",message="router.image is required when router is enabled"
577+
// +kubebuilder:validation:XValidation:rule="!(has(self.enabled) && self.enabled) || (has(self.size) && self.size > 0)",message="router.size must be greater than 0 when router is enabled"
548578
type MySQLRouterSpec struct {
549579
Enabled bool `json:"enabled,omitempty"`
550580

@@ -555,10 +585,13 @@ type MySQLRouterSpec struct {
555585
PodSpec `json:",inline"`
556586
}
557587

588+
// +kubebuilder:validation:XValidation:rule="has(self.image) && self.image != ''",message="toolkit.image is required"
558589
type ToolkitSpec struct {
559590
ContainerSpec `json:",inline"`
560591
}
561592

593+
// +kubebuilder:validation:XValidation:rule="!(has(self.enabled) && self.enabled) || (has(self.image) && self.image != '')",message="haproxy.image is required when haproxy is enabled"
594+
// +kubebuilder:validation:XValidation:rule="!(has(self.enabled) && self.enabled) || (has(self.size) && self.size > 0)",message="haproxy.size must be greater than 0 when haproxy is enabled"
562595
type HAProxySpec struct {
563596
Enabled bool `json:"enabled,omitempty"`
564597

@@ -645,6 +678,7 @@ type PerconaServerMySQLStatus struct { // INSERT ADDITIONAL STATUS FIELD - defin
645678
Orchestrator StatefulAppStatus `json:"orchestrator,omitempty"`
646679
HAProxy StatefulAppStatus `json:"haproxy,omitempty"`
647680
Router StatefulAppStatus `json:"router,omitempty"`
681+
BinlogServer StatefulAppStatus `json:"binlogServer,omitempty"`
648682
State StatefulAppState `json:"state,omitempty"`
649683
BackupVersion string `json:"backupVersion,omitempty"`
650684
PMMVersion string `json:"pmmVersion,omitempty"`
@@ -1014,11 +1048,46 @@ func (cr *PerconaServerMySQL) CheckNSetDefaults(_ context.Context, serverVersion
10141048
cr.Spec.Backup.PiTR.BinlogServer = new(BinlogServerSpec)
10151049
}
10161050

1051+
if cr.Spec.Backup.PiTR.BinlogServer != nil {
1052+
bls := cr.Spec.Backup.PiTR.BinlogServer
1053+
if bls.SSLMode == "" {
1054+
bls.SSLMode = "verify_identity"
1055+
}
1056+
if bls.VerifyChecksum == nil {
1057+
t := true
1058+
bls.VerifyChecksum = &t
1059+
}
1060+
if bls.RewriteFileSize == "" {
1061+
bls.RewriteFileSize = "128M"
1062+
}
1063+
if bls.CheckpointSize == "" {
1064+
bls.CheckpointSize = "16M"
1065+
}
1066+
if bls.CheckpointInterval == "" {
1067+
bls.CheckpointInterval = "30s"
1068+
}
1069+
if bls.ConnectTimeout == 0 {
1070+
bls.ConnectTimeout = 30
1071+
}
1072+
if bls.ReadTimeout == 0 {
1073+
bls.ReadTimeout = 30
1074+
}
1075+
if bls.WriteTimeout == 0 {
1076+
bls.WriteTimeout = 30
1077+
}
1078+
if bls.IdleTime == 0 {
1079+
bls.IdleTime = 30
1080+
}
1081+
}
1082+
10171083
if cr.Spec.Pause {
10181084
cr.Spec.MySQL.Size = 0
10191085
cr.Spec.Orchestrator.Size = 0
10201086
cr.Spec.Proxy.Router.Size = 0
10211087
cr.Spec.Proxy.HAProxy.Size = 0
1088+
if cr.Spec.Backup.PiTR.BinlogServer != nil {
1089+
cr.Spec.Backup.PiTR.BinlogServer.Size = 0
1090+
}
10221091
}
10231092

10241093
if cr.Spec.SecretsName == "" {
@@ -1290,7 +1359,6 @@ const (
12901359
UpgradeStrategyDisabled = "disabled"
12911360
UpgradeStrategyNever = "never"
12921361
UpgradeStrategyRecommended = "recommended"
1293-
UpgradeStrategyLatest = "latest"
12941362
)
12951363

12961364
func (s *BackupStorageSpec) Equals(other *BackupStorageSpec) bool {

api/v1/perconaservermysql_types_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,69 @@ func TestCheckNSetDefaults(t *testing.T) {
166166
err := cr.CheckNSetDefaults(t.Context(), nil)
167167
assert.NoError(t, err)
168168
})
169+
t.Run("binlog server defaults are set when binlogServer is configured", func(t *testing.T) {
170+
cr := new(PerconaServerMySQL)
171+
cr.Spec.MySQL.VolumeSpec = &VolumeSpec{
172+
PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{
173+
Resources: corev1.VolumeResourceRequirements{
174+
Requests: corev1.ResourceList{
175+
corev1.ResourceStorage: resource.MustParse("1G"),
176+
},
177+
},
178+
},
179+
}
180+
cr.Spec.Backup = &BackupSpec{
181+
PiTR: PiTRSpec{
182+
BinlogServer: &BinlogServerSpec{},
183+
},
184+
}
185+
186+
err := cr.CheckNSetDefaults(t.Context(), nil)
187+
assert.NoError(t, err)
188+
189+
bls := cr.Spec.Backup.PiTR.BinlogServer
190+
assert.Equal(t, "verify_identity", bls.SSLMode)
191+
assert.NotNil(t, bls.VerifyChecksum)
192+
assert.True(t, *bls.VerifyChecksum)
193+
assert.Equal(t, "128M", bls.RewriteFileSize)
194+
assert.Equal(t, "16M", bls.CheckpointSize)
195+
assert.Equal(t, "30s", bls.CheckpointInterval)
196+
})
197+
t.Run("binlog server explicit values are not overridden by defaults", func(t *testing.T) {
198+
cr := new(PerconaServerMySQL)
199+
cr.Spec.MySQL.VolumeSpec = &VolumeSpec{
200+
PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{
201+
Resources: corev1.VolumeResourceRequirements{
202+
Requests: corev1.ResourceList{
203+
corev1.ResourceStorage: resource.MustParse("1G"),
204+
},
205+
},
206+
},
207+
}
208+
f := false
209+
cr.Spec.Backup = &BackupSpec{
210+
PiTR: PiTRSpec{
211+
BinlogServer: &BinlogServerSpec{
212+
SSLMode: "required",
213+
VerifyChecksum: &f,
214+
RewriteFileSize: "256M",
215+
CheckpointSize: "4M",
216+
CheckpointInterval: "60s",
217+
},
218+
},
219+
}
220+
221+
err := cr.CheckNSetDefaults(t.Context(), nil)
222+
assert.NoError(t, err)
223+
224+
bls := cr.Spec.Backup.PiTR.BinlogServer
225+
assert.Equal(t, "required", bls.SSLMode)
226+
assert.NotNil(t, bls.VerifyChecksum)
227+
assert.False(t, *bls.VerifyChecksum)
228+
assert.Equal(t, "256M", bls.RewriteFileSize)
229+
assert.Equal(t, "4M", bls.CheckpointSize)
230+
assert.Equal(t, "60s", bls.CheckpointInterval)
231+
})
169232
}
170233

171234
func TestCanBackup(t *testing.T) {

api/v1/perconaservermysqlrestore_types.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,24 @@ type PerconaServerMySQLRestoreSpec struct {
3131
BackupName string `json:"backupName,omitempty"`
3232
BackupSource *PerconaServerMySQLBackupStatus `json:"backupSource,omitempty"`
3333
ContainerOptions *BackupContainerOptions `json:"containerOptions,omitempty"`
34+
PITR *RestorePITRSpec `json:"pitr,omitempty"`
3435
}
3536

37+
type RestorePITRSpec struct {
38+
// +kubebuilder:validation:Enum=gtid;date
39+
Type PITRType `json:"type"`
40+
Date string `json:"date,omitempty"`
41+
GTID string `json:"gtid,omitempty"`
42+
Force bool `json:"force,omitempty"`
43+
}
44+
45+
type PITRType string
46+
47+
const (
48+
PITRGtid PITRType = "gtid"
49+
PITRDate PITRType = "date"
50+
)
51+
3652
type RestoreState string
3753

3854
const (

api/v1/zz_generated.deepcopy.go

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ RUN GOOS=$GOOS GOARCH=$TARGETARCH CGO_ENABLED=$CGO_ENABLED GO_LDFLAGS=$GO_LDFLAG
5050
-o build/_output/bin/mysql-state-monitor \
5151
cmd/mysql-state-monitor/main.go \
5252
&& cp -r build/_output/bin/mysql-state-monitor /usr/local/bin/mysql-state-monitor
53+
RUN GOOS=$GOOS GOARCH=$TARGETARCH CGO_ENABLED=$CGO_ENABLED GO_LDFLAGS=$GO_LDFLAGS \
54+
go build -ldflags "-w -s -X main.GitCommit=$GIT_COMMIT -X main.GitBranch=$GIT_BRANCH -X main.BuildTime=$BUILD_TIME" \
55+
-o build/_output/bin/pitr \
56+
./cmd/pitr/ \
57+
&& cp -r build/_output/bin/pitr /usr/local/bin/pitr
5358

5459
FROM redhat/ubi9-minimal AS ubi9
5560
RUN microdnf -y update && microdnf clean all
@@ -91,5 +96,7 @@ COPY build/haproxy.cfg /opt/percona-server-mysql-operator/haproxy.cfg
9196
COPY build/haproxy-global.cfg /opt/percona-server-mysql-operator/haproxy-global.cfg
9297
COPY build/pmm-prerun.sh /opt/percona-server-mysql-operator/pmm-prerun.sh
9398
COPY build/binlog-server-entrypoint.sh /opt/percona-server-mysql-operator/binlog-server-entrypoint.sh
99+
COPY --from=go_builder /usr/local/bin/pitr /opt/percona-server-mysql-operator/pitr
100+
COPY build/run-pitr-restore.sh /opt/percona-server-mysql-operator/run-pitr-restore.sh
94101

95102
USER 2

build/ps-init-entrypoint.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,5 @@ install -o "$(id -u)" -g "$(id -g)" -m 0755 -D "${OPERATORDIR}/haproxy-global.cf
4040
install -o "$(id -u)" -g "$(id -g)" -m 0755 -D "${OPERATORDIR}/pmm-prerun.sh" "${BINDIR}/pmm-prerun.sh"
4141

4242
install -o "$(id -u)" -g "$(id -g)" -m 0755 -D "${OPERATORDIR}/binlog-server-entrypoint.sh" "${BINDIR}/binlog-server-entrypoint.sh"
43+
install -o "$(id -u)" -g "$(id -g)" -m 0755 -D "${OPERATORDIR}/pitr" "${BINDIR}/pitr"
44+
install -o "$(id -u)" -g "$(id -g)" -m 0755 -D "${OPERATORDIR}/run-pitr-restore.sh" "${BINDIR}/run-pitr-restore.sh"

0 commit comments

Comments
 (0)