Skip to content

Commit 7b55745

Browse files
committed
feat: support take backup from specified node
1 parent 62657cc commit 7b55745

File tree

8 files changed

+104
-2
lines changed

8 files changed

+104
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88
### Added
99
* `MysqlDatabase` `MysqlUser` Add delete policy
1010
* Add `PtHeartbeatResources` in `.Spec.PodSpec` to allow the user specifying resources for pt-heartbeat.
11+
* Add `CandidateNode` field in `MysqlBackup.Spec` to allow the user specifying candidate node for backup.
1112
* Set `MysqlCluter.Spec.BackupSchedule` to empty string to disable recurrent backups
1213
### Changed
1314
* Set default MySQL server version to `5.7.35`

config/crd/bases/mysql.presslabs.org_mysqlbackups.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ spec:
3636
backupURL:
3737
description: BackupURL represents the URL to the backup location, this can be partially specifyied. Default is used the one specified in the cluster.
3838
type: string
39+
candidateNode:
40+
description: CandidateNode is the node host that will be used to take the backup. If not set, the operator will calculate by itself.
41+
type: string
3942
clusterName:
4043
description: ClustterName represents the cluster for which to take backup
4144
type: string

deploy/charts/mysql-operator/crds/mysql.presslabs.org_mysqlbackups.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ spec:
3737
backupURL:
3838
description: BackupURL represents the URL to the backup location, this can be partially specifyied. Default is used the one specified in the cluster.
3939
type: string
40+
candidateNode:
41+
description: CandidateNode is the node host that will be used to take the backup. If not set, the operator will calculate by itself.
42+
type: string
4043
clusterName:
4144
description: ClustterName represents the cluster for which to take backup
4245
type: string

pkg/apis/mysql/v1alpha1/mysqlbackup_types.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ type MysqlBackupSpec struct {
4444
// default it's used softDelete.
4545
// +optional
4646
RemoteDeletePolicy DeletePolicy `json:"remoteDeletePolicy,omitempty"`
47+
48+
// CandidateNode is the node host that will be used to take the backup.
49+
// If not set, the operator will calculate by itself.
50+
// +optional
51+
CandidateNode string `json:"candidateNode,omitempty"`
4752
}
4853

4954
// BackupCondition defines condition struct for backup resource
@@ -94,7 +99,6 @@ type MysqlBackupStatus struct {
9499

95100
// MysqlBackup is the Schema for the mysqlbackups API
96101
// +kubebuilder:object:root=true
97-
//
98102
type MysqlBackup struct {
99103
metav1.TypeMeta `json:",inline"`
100104
metav1.ObjectMeta `json:"metadata,omitempty"`
@@ -105,7 +109,6 @@ type MysqlBackup struct {
105109

106110
// MysqlBackupList contains a list of MysqlBackup
107111
// +kubebuilder:object:root=true
108-
//
109112
type MysqlBackupList struct {
110113
metav1.TypeMeta `json:",inline"`
111114
metav1.ListMeta `json:"metadata,omitempty"`

pkg/controller/mysqlbackup/internal/syncer/job.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ func (s *jobSyncer) SyncFn() error {
9191
// getBackupCandidate returns the hostname of the first not-lagged and
9292
// replicating slave node, else returns the master node.
9393
func (s *jobSyncer) getBackupCandidate() string {
94+
if s.backup.Spec.CandidateNode != "" {
95+
return s.backup.Spec.CandidateNode
96+
}
97+
9498
for _, node := range s.cluster.Status.Nodes {
9599
master := s.cluster.GetNodeCondition(node.Name, api.NodeConditionMaster)
96100
replicating := s.cluster.GetNodeCondition(node.Name, api.NodeConditionReplicating)

pkg/controller/mysqlbackup/mysqlbackup_controller.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ func (r *ReconcileMysqlBackup) Reconcile(ctx context.Context, request reconcile.
151151
// set defaults for the backup base on the related cluster
152152
backup.SetDefaults(cluster)
153153

154+
if err = backup.Validate(cluster); err != nil {
155+
return reconcile.Result{}, err
156+
}
157+
154158
syncers := []syncer.Interface{
155159
backupSyncer.NewDeleteJobSyncer(r.Client, r.scheme, backup, cluster, r.opt, r.recorder),
156160
backupSyncer.NewJobSyncer(r.Client, r.scheme, backup, cluster, r.opt),

pkg/controller/mysqlbackup/mysqlbackup_controller_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,48 @@ var _ = Describe("MysqlBackup controller", func() {
316316
Expect(c.Delete(context.TODO(), cluster.Unwrap())).To(Succeed())
317317
})
318318
})
319+
320+
When("candidate node is setted to wrong node", func() {
321+
BeforeEach(func() {
322+
backup.Spec.CandidateNode = cluster.GetPodHostname(3)
323+
// create a cluster and a backup
324+
Expect(c.Create(context.TODO(), backup.Unwrap())).To(Succeed())
325+
Expect(c.Create(context.TODO(), cluster.Unwrap())).To(Succeed())
326+
327+
// first reconcile request for the backup
328+
Eventually(requests, timeout).Should(Receive(Equal(expectedRequest)))
329+
testutil.DrainChan(requests)
330+
})
331+
332+
AfterEach(func() {
333+
Expect(c.Delete(context.TODO(), backup.Unwrap())).To(Succeed())
334+
c.Delete(context.TODO(), cluster.Unwrap())
335+
})
336+
337+
It("should skip creating job", func() {
338+
job := &batch.Job{}
339+
Expect(c.Get(context.TODO(), jobKey, job)).NotTo(Succeed())
340+
})
341+
342+
When("candidate node update to master", func() {
343+
BeforeEach(func() {
344+
backup.Spec.CandidateNode = cluster.GetPodHostname(0)
345+
// update backup
346+
Expect(c.Update(context.TODO(), backup.Unwrap())).To(Succeed())
347+
348+
// first reconcile request for the backup
349+
Eventually(requests, timeout).Should(Receive(Equal(expectedRequest)))
350+
testutil.DrainChan(requests)
351+
})
352+
353+
It("should take backup from master", func() {
354+
job := &batch.Job{}
355+
Expect(c.Get(context.TODO(), jobKey, job)).To(Succeed())
356+
Expect(job.Spec.Template.Spec.Containers[0].Args).To(ContainElement(Equal(cluster.GetPodHostname(0))))
357+
})
358+
})
359+
360+
})
319361
})
320362

321363
func refreshFn(c client.Client, backupKey types.NamespacedName) func() *api.MysqlBackup {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
Copyright 2018 Pressinfra SRL
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package mysqlbackup
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/bitpoke/mysql-operator/pkg/internal/mysqlcluster"
23+
)
24+
25+
// Validate checks if the backup spec is validated
26+
func (c *MysqlBackup) Validate(cluster *mysqlcluster.MysqlCluster) error {
27+
// TODO: this validation should be done in an admission web-hook
28+
29+
if c.Spec.CandidateNode != "" {
30+
validate := false
31+
for i := 0; i < int(*cluster.Spec.Replicas); i++ {
32+
if c.Spec.CandidateNode == cluster.GetPodHostname(i) {
33+
validate = true
34+
break
35+
}
36+
}
37+
if !validate {
38+
return fmt.Errorf("spec.candidateNode is not a valid node")
39+
}
40+
}
41+
return nil
42+
}

0 commit comments

Comments
 (0)