Skip to content

Commit dc13125

Browse files
committed
hack,test: backup e2e test radondb#356
1 parent 77704a3 commit dc13125

File tree

6 files changed

+323
-24
lines changed

6 files changed

+323
-24
lines changed

hack/teste2e.sh

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export KUBECONFIG=~/.kube/config
2+
export KUBERNETES_SERVICE_HOST=
3+
export KUBERNETES_SERVICE_PORT=6443
4+
export S3ENDPOINT=
5+
export S3ACCESSKEY=
6+
export S3SECRETKEY=
7+
export ACK_GINKGO_DEPRECATIONS=1.16.5
8+
kubectl delete namespace radondb-mysql-e2e
9+
kubectl get clusterrole|grep mysql|awk '{print "kubectl delete clusterrole "$1}'|bash
10+
kubectl get clusterrolebindings|grep mysql|awk '{print "kubectl delete clusterrolebindings "$1}'|bash
11+
kubectl get crd|grep mysql|awk '{print "kubectl delete crd "$1}'|bash
12+
13+
make e2e-local

test/e2e/backup/backup.go

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*
2+
Copyright 2021 RadonDB.
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+
package backup
17+
18+
import (
19+
"context"
20+
"fmt"
21+
"math/rand"
22+
"regexp"
23+
"strings"
24+
"time"
25+
26+
. "github.com/onsi/ginkgo"
27+
. "github.com/onsi/gomega"
28+
apiv1alpha1 "github.com/radondb/radondb-mysql-kubernetes/api/v1alpha1"
29+
"github.com/radondb/radondb-mysql-kubernetes/test/e2e/framework"
30+
"github.com/radondb/radondb-mysql-kubernetes/utils"
31+
corev1 "k8s.io/api/core/v1"
32+
"k8s.io/apimachinery/pkg/types"
33+
)
34+
35+
var _ = Describe("MySQL Backup E2E Tests", func() {
36+
f := framework.NewFramework("mcbackup-1")
37+
_ = f
38+
39+
var (
40+
cluster *apiv1alpha1.MysqlCluster
41+
clusterKey types.NamespacedName
42+
name string
43+
backupSecret *corev1.Secret
44+
//timeout time.Duration
45+
POLLING = 2 * time.Second
46+
backupDir string
47+
leader string
48+
follower string
49+
)
50+
51+
BeforeEach(func() {
52+
// be careful, mysql allowed hostname lenght is <63
53+
name = fmt.Sprintf("bk-%d", rand.Int31()/1000)
54+
55+
//timeout = 350 * time.Second
56+
57+
By("create a new backup secret")
58+
backupSecret = f.NewBackupSecret()
59+
Expect(f.Client.Create(context.TODO(), backupSecret)).To(Succeed(), "create backup secret failed")
60+
By("creating a new cluster")
61+
cluster = framework.NewCluster(name, f.Namespace.Name)
62+
clusterKey = types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}
63+
cluster.Spec.BackupSecretName = backupSecret.Name
64+
Expect(f.Client.Create(context.TODO(), cluster)).To(Succeed(),
65+
"failed to create cluster '%s'", cluster.Name)
66+
By("waiting the cluster readiness")
67+
framework.WaitClusterReadiness(f, cluster)
68+
//get leader
69+
for _, node := range cluster.Status.Nodes {
70+
if node.RaftStatus.Role == string(utils.Leader) {
71+
leader = strings.Split(node.Name, ".")[0]
72+
} else if node.RaftStatus.Role == string(utils.Follower) {
73+
follower = strings.Split(node.Name, ".")[0]
74+
}
75+
}
76+
77+
Expect(f.Client.Get(context.TODO(), clusterKey, cluster)).To(Succeed(), "failed to get cluster %s", cluster.Name)
78+
79+
Eventually(f.RefreshClusterFn(cluster), f.Timeout, POLLING).Should(
80+
framework.HaveClusterReplicas(2))
81+
Eventually(f.RefreshClusterFn(cluster), f.Timeout, POLLING).Should(
82+
framework.HaveClusterCond(apiv1alpha1.ConditionReady, corev1.ConditionTrue))
83+
84+
// refresh cluster
85+
Expect(f.Client.Get(context.TODO(), clusterKey, cluster)).To(Succeed(),
86+
"failed to get cluster %s", cluster.Name)
87+
88+
})
89+
90+
It("backup to object store", func() {
91+
//exectute sql command in mysql pod
92+
By("executing insert data")
93+
_, err := f.ExecSQLOnNode(*cluster, leader, "create table testtable (id int)")
94+
Expect(err).To(BeNil())
95+
_, err = f.ExecSQLOnNode(*cluster, leader, "insert into testtable values (1),(2),(3)")
96+
Expect(err).To(BeNil())
97+
rows, err := f.ExecSQLOnNode(*cluster, leader, "select * from testtable")
98+
Expect(err).To(BeNil(), "failed to execute sql")
99+
defer rows.Close()
100+
var id int
101+
ids := make([]int, 0)
102+
for rows.Next() {
103+
if err := rows.Scan(&id); err != nil {
104+
Fail(err.Error())
105+
}
106+
ids = append(ids, id)
107+
}
108+
Expect(ids).To(Equal([]int{1, 2, 3}))
109+
Eventually(func() []int {
110+
rows, _ :=
111+
f.ExecSQLOnNode(*cluster, follower, "select * from testtable ")
112+
if rows == nil {
113+
return nil
114+
}
115+
defer rows.Close()
116+
var id int
117+
ids := make([]int, 0)
118+
for rows.Next() {
119+
if err := rows.Scan(&id); err != nil {
120+
Fail(err.Error())
121+
}
122+
ids = append(ids, id)
123+
}
124+
return ids
125+
}, f.Timeout, POLLING).Should(Equal([]int{1, 2, 3}))
126+
By("executing a backup ")
127+
// do the backup
128+
backup := framework.NewBackup(cluster, leader)
129+
Expect(f.Client.Create(context.TODO(), backup)).To(Succeed(),
130+
"failed to create backup '%s'", backup.Name)
131+
132+
Eventually(f.RefreshBackupFn(backup), f.Timeout, POLLING).Should(
133+
framework.HaveBackupComplete())
134+
135+
if str, err := framework.GetPodLogs(f.ClientSet, f.Namespace.Name, leader, "backup"); err == nil {
136+
r, _ := regexp.Compile("backup_[0-9]+")
137+
backupDir = r.FindString(str)
138+
}
139+
nameRestore := fmt.Sprintf("rs-%d", rand.Int31()/1000)
140+
By("creating a new cluster from backup")
141+
clusterRestore := framework.NewCluster(nameRestore, f.Namespace.Name)
142+
clusterKeyRestore := types.NamespacedName{Name: clusterRestore.Name, Namespace: clusterRestore.Namespace}
143+
clusterRestore.Spec.BackupSecretName = backupSecret.Name
144+
clusterRestore.Spec.RestoreFrom = backupDir
145+
Expect(f.Client.Create(context.TODO(), clusterRestore)).To(Succeed(),
146+
"failed to create clusterRestore '%s'", clusterRestore.Name)
147+
By("waiting the clusterRestore readiness")
148+
framework.WaitClusterReadiness(f, clusterRestore)
149+
Eventually(f.RefreshClusterFn(clusterRestore), f.Timeout, POLLING).Should(
150+
framework.HaveClusterReplicas(2))
151+
Eventually(f.RefreshClusterFn(clusterRestore), f.Timeout, POLLING).Should(
152+
framework.HaveClusterCond(apiv1alpha1.ConditionReady, corev1.ConditionTrue))
153+
Eventually(func() []int {
154+
rows, _ := f.ExecSQLOnNode(*clusterRestore, fmt.Sprintf("%s-mysql-0", nameRestore), "select * from testtable ")
155+
if rows == nil {
156+
return nil
157+
}
158+
defer rows.Close()
159+
var id int
160+
ids := make([]int, 0)
161+
for rows.Next() {
162+
if err := rows.Scan(&id); err != nil {
163+
Fail(err.Error())
164+
}
165+
ids = append(ids, id)
166+
}
167+
return ids
168+
}, f.Timeout, POLLING).Should(Equal([]int{1, 2, 3}))
169+
170+
// refresh clusterRestore
171+
Expect(f.Client.Get(context.TODO(), clusterKeyRestore, clusterRestore)).To(Succeed(),
172+
"failed to get clusterRestore %s", clusterRestore.Name)
173+
Expect(f.Client.Delete(context.TODO(), clusterRestore)).To(Succeed(),
174+
"failed to delete clusterRestore '%s'", clusterRestore.Name)
175+
})
176+
177+
})

test/e2e/cluster/cluster.go

+5-22
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424

2525
. "github.com/onsi/ginkgo"
2626
. "github.com/onsi/gomega"
27-
corev1 "k8s.io/api/core/v1"
2827
"k8s.io/apimachinery/pkg/types"
2928

3029
apiv1alpha1 "github.com/radondb/radondb-mysql-kubernetes/api/v1alpha1"
@@ -57,46 +56,30 @@ var _ = Describe("MySQL Cluster E2E Tests", func() {
5756
Expect(f.Client.Create(context.TODO(), cluster)).To(Succeed(), "failed to create cluster '%s'", cluster.Name)
5857

5958
By("testing the cluster readiness")
60-
waitClusterReadiness(f, cluster)
59+
framework.WaitClusterReadiness(f, cluster)
6160
Expect(f.Client.Get(context.TODO(), clusterKey, cluster)).To(Succeed(), "failed to get cluster %s", cluster.Name)
6261
})
6362

6463
It("scale out/in a cluster, 2 -> 3 -> 5 -> 3 -> 2", func() {
6564
By("test cluster is ready after scale out 2 -> 3")
6665
cluster.Spec.Replicas = &three
6766
Expect(f.Client.Update(context.TODO(), cluster)).To(Succeed())
68-
fmt.Println("scale time: ", waitClusterReadiness(f, cluster))
67+
fmt.Println("scale time: ", framework.WaitClusterReadiness(f, cluster))
6968

7069
By("test cluster is ready after scale out 3 -> 5")
7170
cluster.Spec.Replicas = &five
7271
Expect(f.Client.Update(context.TODO(), cluster)).To(Succeed())
73-
fmt.Println("scale time: ", waitClusterReadiness(f, cluster))
72+
fmt.Println("scale time: ", framework.WaitClusterReadiness(f, cluster))
7473

7574
By("test cluster is ready after scale in 5 -> 3")
7675
cluster.Spec.Replicas = &three
7776
Expect(f.Client.Update(context.TODO(), cluster)).To(Succeed())
78-
fmt.Println("scale time: ", waitClusterReadiness(f, cluster))
77+
fmt.Println("scale time: ", framework.WaitClusterReadiness(f, cluster))
7978

8079
By("test cluster is ready after scale in 3 -> 2")
8180
cluster.Spec.Replicas = &two
8281
Expect(f.Client.Update(context.TODO(), cluster)).To(Succeed())
83-
fmt.Println("scale time: ", waitClusterReadiness(f, cluster))
82+
fmt.Println("scale time: ", framework.WaitClusterReadiness(f, cluster))
8483
})
8584

8685
})
87-
88-
// waitClusterReadiness determine whether the cluster is ready.
89-
func waitClusterReadiness(f *framework.Framework, cluster *apiv1alpha1.MysqlCluster) time.Duration {
90-
startTime := time.Now()
91-
timeout := f.Timeout
92-
if *cluster.Spec.Replicas > 0 {
93-
timeout = time.Duration(*cluster.Spec.Replicas) * f.Timeout
94-
}
95-
// Wait for pods to be ready.
96-
f.ClusterEventuallyReplicas(cluster, timeout)
97-
// Wait for xenon to be ready.
98-
f.ClusterEventuallyRaftStatus(cluster)
99-
// Wait for condition to be ready.
100-
f.ClusterEventuallyCondition(cluster, apiv1alpha1.ConditionReady, corev1.ConditionTrue, f.Timeout)
101-
return time.Since(startTime)
102-
}

test/e2e/e2e_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ import (
2020
"testing"
2121

2222
// _ "github.com/radondb/radondb-mysql-kubernetes/test/e2e/cluster"
23+
_ "github.com/radondb/radondb-mysql-kubernetes/test/e2e/backup"
2324
"github.com/radondb/radondb-mysql-kubernetes/test/e2e/framework"
24-
_ "github.com/radondb/radondb-mysql-kubernetes/test/e2e/simplecase"
25+
// _ "github.com/radondb/radondb-mysql-kubernetes/test/e2e/simplecase"
2526
)
2627

2728
func init() {

test/e2e/framework/backup_util.go

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
package framework
17+
18+
import (
19+
"context"
20+
"fmt"
21+
"os"
22+
23+
. "github.com/onsi/gomega"
24+
. "github.com/onsi/gomega/gstruct"
25+
gomegatypes "github.com/onsi/gomega/types"
26+
apiv1alpha "github.com/radondb/radondb-mysql-kubernetes/api/v1alpha1"
27+
corev1 "k8s.io/api/core/v1"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/types"
30+
)
31+
32+
func GetS3EndPointName() string {
33+
S3 := os.Getenv("S3ENDPOINT")
34+
if len(S3) == 0 {
35+
Logf("S3ENDPOINT not set! Backups tests will not work")
36+
}
37+
return S3
38+
}
39+
40+
func GetS3AccessKey() string {
41+
S3AccessKey := os.Getenv("S3ACCESSKEY")
42+
if len(S3AccessKey) == 0 {
43+
Logf("S3ACCESSKEY not set! Backups tests will not work")
44+
}
45+
return S3AccessKey
46+
}
47+
48+
func GetS3SecretKey() string {
49+
S3SecretKey := os.Getenv("S3SECRETKEY")
50+
if len(S3SecretKey) == 0 {
51+
Logf("S3SECRETKEY not set! Backups tests will not work")
52+
}
53+
return S3SecretKey
54+
}
55+
56+
func (f *Framework) NewBackupSecret() *corev1.Secret {
57+
// s3-endpoint:
58+
// s3-access-key:
59+
// s3-secret-key:
60+
// s3-bucket:
61+
return &corev1.Secret{
62+
ObjectMeta: metav1.ObjectMeta{
63+
Name: fmt.Sprintf("%s-backup-secret", f.BaseName),
64+
Namespace: f.Namespace.Name,
65+
},
66+
StringData: map[string]string{
67+
"s3-endpoint": GetS3EndPointName(),
68+
"s3-access-key": GetS3AccessKey(),
69+
"s3-secret-key": GetS3SecretKey(),
70+
"s3-bucket": "radondb-backups",
71+
},
72+
Type: corev1.SecretTypeOpaque,
73+
}
74+
}
75+
76+
func NewBackup(cluster *apiv1alpha.MysqlCluster, hostname string) *apiv1alpha.Backup {
77+
return &apiv1alpha.Backup{
78+
ObjectMeta: metav1.ObjectMeta{
79+
Name: cluster.Name,
80+
Namespace: cluster.Namespace,
81+
},
82+
Spec: apiv1alpha.BackupSpec{
83+
ClusterName: cluster.Name,
84+
HostName: hostname,
85+
Image: TestContext.SidecarImage,
86+
},
87+
}
88+
}
89+
90+
func (f *Framework) RefreshBackupFn(backup *apiv1alpha.Backup) func() *apiv1alpha.Backup {
91+
return func() *apiv1alpha.Backup {
92+
key := types.NamespacedName{
93+
Name: backup.Name,
94+
Namespace: backup.Namespace,
95+
}
96+
b := &apiv1alpha.Backup{}
97+
f.Client.Get(context.TODO(), key, b)
98+
return b
99+
}
100+
}
101+
102+
func HaveBackupComplete() gomegatypes.GomegaMatcher {
103+
return PointTo(MatchFields(IgnoreExtras, Fields{
104+
"Status": MatchFields(IgnoreExtras, Fields{
105+
"Completed": Equal(true),
106+
})},
107+
))
108+
}

0 commit comments

Comments
 (0)