Skip to content

Commit 8fe538a

Browse files
committed
feat(cluster): modify the transformer of the statefulset
fix: radondb#551
1 parent daa8ce7 commit 8fe538a

File tree

4 files changed

+417
-48
lines changed

4 files changed

+417
-48
lines changed

mysqlcluster/syncer/statefulset.go

+53-48
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"github.com/go-logr/logr"
2626
"github.com/iancoleman/strcase"
2727
"github.com/imdario/mergo"
28-
"github.com/presslabs/controller-util/pkg/mergo/transformers"
2928
"github.com/presslabs/controller-util/pkg/syncer"
3029

3130
appsv1 "k8s.io/api/apps/v1"
@@ -367,43 +366,46 @@ func (s *StatefulSetSyncer) updatePod(ctx context.Context) error {
367366

368367
// mutate set the statefulset.
369368
func (s *StatefulSetSyncer) mutate() error {
370-
s.sfs.Spec.ServiceName = s.GetNameForResource(utils.StatefulSet)
371-
s.sfs.Spec.Replicas = s.Spec.Replicas
372-
s.sfs.Spec.Selector = metav1.SetAsLabelSelector(s.GetSelectorLabels())
373-
s.sfs.Spec.UpdateStrategy = appsv1.StatefulSetUpdateStrategy{
374-
Type: appsv1.OnDeleteStatefulSetStrategyType,
375-
}
376-
377-
s.sfs.Spec.Template.ObjectMeta.Labels = s.GetLabels()
369+
// build lables.
370+
podLables := s.GetLabels()
378371
for k, v := range s.Spec.PodPolicy.Labels {
379-
s.sfs.Spec.Template.ObjectMeta.Labels[k] = v
372+
podLables[k] = v
380373
}
381-
s.sfs.Spec.Template.ObjectMeta.Labels["role"] = string(utils.Candidate)
382-
s.sfs.Spec.Template.ObjectMeta.Labels["healthy"] = "no"
383-
384-
s.sfs.Spec.Template.Annotations = s.Spec.PodPolicy.Annotations
385-
if len(s.sfs.Spec.Template.ObjectMeta.Annotations) == 0 {
386-
s.sfs.Spec.Template.ObjectMeta.Annotations = make(map[string]string)
374+
podLables["role"] = string(utils.Follower)
375+
podLables["healthy"] = "no"
376+
// build annotations.
377+
podAnnotations := make(map[string]string)
378+
if len(s.Spec.PodPolicy.Annotations) > 0 {
379+
podAnnotations = s.Spec.PodPolicy.Annotations
387380
}
388381
if s.Spec.MetricsOpts.Enabled {
389-
s.sfs.Spec.Template.ObjectMeta.Annotations["prometheus.io/scrape"] = "true"
390-
s.sfs.Spec.Template.ObjectMeta.Annotations["prometheus.io/port"] = fmt.Sprintf("%d", utils.MetricsPort)
391-
}
392-
s.sfs.Spec.Template.ObjectMeta.Annotations["config_rev"] = s.cmRev
393-
s.sfs.Spec.Template.ObjectMeta.Annotations["secret_rev"] = s.sctRev
394-
395-
err := mergo.Merge(&s.sfs.Spec.Template.Spec, s.ensurePodSpec(), mergo.WithTransformers(transformers.PodSpec))
396-
if err != nil {
397-
return err
382+
podAnnotations["prometheus.io/scrape"] = "true"
383+
podAnnotations["prometheus.io/port"] = fmt.Sprintf("%d", utils.MetricsPort)
384+
}
385+
podAnnotations["config_rev"] = s.cmRev
386+
podAnnotations["secret_rev"] = s.sctRev
387+
388+
templateSpec := appsv1.StatefulSetSpec{
389+
Replicas: s.Spec.Replicas,
390+
ServiceName: s.GetNameForResource(utils.StatefulSet),
391+
Selector: metav1.SetAsLabelSelector(s.GetSelectorLabels()),
392+
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
393+
Type: appsv1.OnDeleteStatefulSetStrategyType,
394+
},
395+
Template: corev1.PodTemplateSpec{
396+
ObjectMeta: metav1.ObjectMeta{
397+
Labels: podLables,
398+
Annotations: podAnnotations,
399+
},
400+
Spec: ensurePodSpec(s.MysqlCluster),
401+
},
398402
}
399-
s.sfs.Spec.Template.Spec.Tolerations = s.Spec.PodPolicy.Tolerations
400-
401403
if s.Spec.Persistence.Enabled {
402-
if s.sfs.Spec.VolumeClaimTemplates, err = s.EnsureVolumeClaimTemplates(s.cli.Scheme()); err != nil {
404+
var err error
405+
if templateSpec.VolumeClaimTemplates, err = s.EnsureVolumeClaimTemplates(s.cli.Scheme()); err != nil {
403406
return err
404407
}
405408
}
406-
407409
// Set owner reference only if owner resource is not being deleted, otherwise the owner
408410
// reference will be reset in case of deleting with cascade=false.
409411
if s.Unwrap().GetDeletionTimestamp().IsZero() {
@@ -415,38 +417,38 @@ func (s *StatefulSetSyncer) mutate() error {
415417
// will not delete it again because has no owner reference set.
416418
return fmt.Errorf("owner is deleted")
417419
}
418-
return nil
420+
return mergo.Merge(&s.sfs.Spec, templateSpec, mergo.WithTransformers(utils.StsSpec))
419421
}
420422

421423
// ensurePodSpec used to ensure the podspec.
422-
func (s *StatefulSetSyncer) ensurePodSpec() corev1.PodSpec {
423-
initSidecar := container.EnsureContainer(utils.ContainerInitSidecarName, s.MysqlCluster)
424-
initMysql := container.EnsureContainer(utils.ContainerInitMysqlName, s.MysqlCluster)
424+
func ensurePodSpec(c *mysqlcluster.MysqlCluster) corev1.PodSpec {
425+
initSidecar := container.EnsureContainer(utils.ContainerInitSidecarName, c)
426+
initMysql := container.EnsureContainer(utils.ContainerInitMysqlName, c)
425427
initContainers := []corev1.Container{initSidecar, initMysql}
426428

427-
mysql := container.EnsureContainer(utils.ContainerMysqlName, s.MysqlCluster)
428-
xenon := container.EnsureContainer(utils.ContainerXenonName, s.MysqlCluster)
429-
backup := container.EnsureContainer(utils.ContainerBackupName, s.MysqlCluster)
429+
mysql := container.EnsureContainer(utils.ContainerMysqlName, c)
430+
xenon := container.EnsureContainer(utils.ContainerXenonName, c)
431+
backup := container.EnsureContainer(utils.ContainerBackupName, c)
430432
containers := []corev1.Container{mysql, xenon, backup}
431-
if s.Spec.MetricsOpts.Enabled {
432-
containers = append(containers, container.EnsureContainer(utils.ContainerMetricsName, s.MysqlCluster))
433+
if c.Spec.MetricsOpts.Enabled {
434+
containers = append(containers, container.EnsureContainer(utils.ContainerMetricsName, c))
433435
}
434-
if s.Spec.PodPolicy.SlowLogTail {
435-
containers = append(containers, container.EnsureContainer(utils.ContainerSlowLogName, s.MysqlCluster))
436+
if c.Spec.PodPolicy.SlowLogTail {
437+
containers = append(containers, container.EnsureContainer(utils.ContainerSlowLogName, c))
436438
}
437-
if s.Spec.PodPolicy.AuditLogTail {
438-
containers = append(containers, container.EnsureContainer(utils.ContainerAuditLogName, s.MysqlCluster))
439+
if c.Spec.PodPolicy.AuditLogTail {
440+
containers = append(containers, container.EnsureContainer(utils.ContainerAuditLogName, c))
439441
}
440442

441443
return corev1.PodSpec{
442444
InitContainers: initContainers,
443445
Containers: containers,
444-
Volumes: s.EnsureVolumes(),
445-
SchedulerName: s.Spec.PodPolicy.SchedulerName,
446-
ServiceAccountName: s.GetNameForResource(utils.ServiceAccount),
447-
Affinity: s.Spec.PodPolicy.Affinity,
448-
PriorityClassName: s.Spec.PodPolicy.PriorityClassName,
449-
Tolerations: s.Spec.PodPolicy.Tolerations,
446+
Volumes: c.EnsureVolumes(),
447+
SchedulerName: c.Spec.PodPolicy.SchedulerName,
448+
ServiceAccountName: c.GetNameForResource(utils.ServiceAccount),
449+
Affinity: c.Spec.PodPolicy.Affinity,
450+
PriorityClassName: c.Spec.PodPolicy.PriorityClassName,
451+
Tolerations: c.Spec.PodPolicy.Tolerations,
450452
}
451453
}
452454

@@ -589,6 +591,9 @@ func (s *StatefulSetSyncer) backupIsRunning(ctx context.Context) (bool, error) {
589591

590592
// Updates to statefulset spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden.
591593
func (s *StatefulSetSyncer) sfsUpdated(existing *appsv1.StatefulSet) bool {
594+
if s.sfs.Status.UpdateRevision != s.sfs.Status.CurrentRevision {
595+
return true
596+
}
592597
var resizeVolume = false
593598
// TODO: this is a temporary workaround until we figure out a better way to do this.
594599
if len(existing.Spec.VolumeClaimTemplates) > 0 && len(s.sfs.Spec.VolumeClaimTemplates) > 0 {

utils/transform_suite_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package utils_test
2+
3+
import (
4+
"testing"
5+
6+
ginkgo "github.com/onsi/ginkgo/v2"
7+
gomega "github.com/onsi/gomega"
8+
)
9+
10+
func TestTrasformer(t *testing.T) {
11+
gomega.RegisterFailHandler(ginkgo.Fail)
12+
ginkgo.RunSpecs(t, "transformer")
13+
}

utils/transformer_test.go

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package utils_test
2+
3+
import (
4+
"github.com/imdario/mergo"
5+
ginkgo "github.com/onsi/ginkgo/v2"
6+
"github.com/onsi/gomega"
7+
appsv1 "k8s.io/api/apps/v1"
8+
corev1 "k8s.io/api/core/v1"
9+
"k8s.io/apimachinery/pkg/api/resource"
10+
11+
"github.com/radondb/radondb-mysql-kubernetes/utils"
12+
)
13+
14+
var _ = ginkgo.Describe("transformer", func() {
15+
var two int32 = 2
16+
var three int32 = 3
17+
18+
templatePodSpec := corev1.PodSpec{
19+
Containers: []corev1.Container{
20+
{
21+
Name: "mysql",
22+
Image: "percona:latest",
23+
Command: []string{
24+
"cmd1",
25+
},
26+
Env: []corev1.EnvVar{
27+
{
28+
Name: "MYSQL_ROOT_PASSWORD",
29+
ValueFrom: &corev1.EnvVarSource{
30+
SecretKeyRef: &corev1.SecretKeySelector{
31+
LocalObjectReference: corev1.LocalObjectReference{
32+
Name: "sample-password",
33+
},
34+
Key: "MYSQL_ROOT_PASSWORD",
35+
},
36+
},
37+
},
38+
},
39+
Resources: corev1.ResourceRequirements{
40+
Limits: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1")},
41+
},
42+
},
43+
{
44+
Name: "xenon",
45+
Image: "xenon:latest",
46+
Env: []corev1.EnvVar{
47+
{
48+
Name: "oldenv",
49+
Value: "oldenv",
50+
},
51+
},
52+
},
53+
},
54+
}
55+
templateStsSpec := appsv1.StatefulSetSpec{
56+
Replicas: &two,
57+
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
58+
Type: appsv1.OnDeleteStatefulSetStrategyType,
59+
},
60+
Template: corev1.PodTemplateSpec{
61+
Spec: templatePodSpec,
62+
},
63+
}
64+
65+
ginkgo.It("init should successfully", func() {
66+
actualStsSpec := appsv1.StatefulSetSpec{}
67+
mergo.Merge(&actualStsSpec, templateStsSpec, mergo.WithTransformers(utils.StsSpec))
68+
69+
gomega.Expect(actualStsSpec).Should(gomega.Equal(templateStsSpec))
70+
})
71+
72+
ginkgo.It("merge n times should successfully", func() {
73+
actualStsSpec := appsv1.StatefulSetSpec{}
74+
mergo.Merge(&actualStsSpec, templateStsSpec, mergo.WithTransformers(utils.StsSpec))
75+
gomega.Expect(actualStsSpec.Template.Spec.Containers[0].Command[0]).Should(gomega.Equal("cmd1"))
76+
77+
actualStsSpec.Template.Spec.Containers[0].Command = append(actualStsSpec.Template.Spec.Containers[0].Command, "cmd2")
78+
mergo.Merge(&actualStsSpec, templateStsSpec, mergo.WithTransformers(utils.StsSpec))
79+
mergo.Merge(&actualStsSpec, templateStsSpec, mergo.WithTransformers(utils.StsSpec))
80+
81+
gomega.Expect(len(actualStsSpec.Template.Spec.Containers[0].Command)).Should(gomega.Equal(2))
82+
gomega.Expect(actualStsSpec.Template.Spec.Containers[0].Command[0]).Should(gomega.Equal("cmd1"))
83+
gomega.Expect(actualStsSpec.Template.Spec.Containers[0].Command[1]).Should(gomega.Equal("cmd2"))
84+
})
85+
86+
ginkgo.It("add containers should successfully", func() {
87+
actualStsSpec := *templateStsSpec.DeepCopy()
88+
actualStsSpec.Template.Spec.Containers = append(actualStsSpec.Template.Spec.Containers, corev1.Container{Name: "test"})
89+
mergo.Merge(&actualStsSpec, templateStsSpec, mergo.WithTransformers(utils.StsSpec))
90+
91+
gomega.Expect(len(actualStsSpec.Template.Spec.Containers)).Should(gomega.Equal(3))
92+
gomega.Expect(actualStsSpec.Template.Spec.Containers[2].Name).Should(gomega.Equal("test"))
93+
})
94+
95+
ginkgo.It("modify envs should successfully", func() {
96+
actualStsSpec := *templateStsSpec.DeepCopy()
97+
actualStsSpec.Template.Spec.Containers[0].Env[0].ValueFrom = &corev1.EnvVarSource{}
98+
actualStsSpec.Template.Spec.Containers[1].Env = append(actualStsSpec.Template.Spec.Containers[1].Env, corev1.EnvVar{Name: "newenv", Value: "newenv"})
99+
mergo.Merge(&actualStsSpec, templateStsSpec, mergo.WithTransformers(utils.StsSpec))
100+
101+
gomega.Expect(actualStsSpec.Template.Spec.Containers[0].Env[0].ValueFrom).ShouldNot(gomega.BeNil())
102+
gomega.Expect(len(actualStsSpec.Template.Spec.Containers[1].Env)).Should(gomega.Equal(2))
103+
})
104+
105+
ginkgo.It("modify container image should not successfully", func() {
106+
actualStsSpec := *templateStsSpec.DeepCopy()
107+
actualStsSpec.Template.Spec.Containers[0].Image = "mysql:latest"
108+
mergo.Merge(&actualStsSpec, templateStsSpec, mergo.WithTransformers(utils.StsSpec))
109+
110+
gomega.Expect(len(actualStsSpec.Template.Spec.Containers)).Should(gomega.Equal(2))
111+
gomega.Expect(actualStsSpec.Template.Spec.Containers[0].Image).ShouldNot(gomega.Equal("mysql:latest"))
112+
})
113+
114+
ginkgo.It("merge replicas,updateStrategy should not successfully", func() {
115+
actualStsSpec := *templateStsSpec.DeepCopy()
116+
actualStsSpec.Replicas = &three
117+
actualStsSpec.UpdateStrategy = appsv1.StatefulSetUpdateStrategy{
118+
Type: appsv1.RollingUpdateStatefulSetStrategyType,
119+
}
120+
mergo.Merge(&actualStsSpec, templateStsSpec, mergo.WithTransformers(utils.StsSpec))
121+
122+
gomega.Expect(actualStsSpec.Replicas).Should(gomega.Equal(&two))
123+
gomega.Expect(actualStsSpec.UpdateStrategy.Type).Should(gomega.Equal(appsv1.OnDeleteStatefulSetStrategyType))
124+
})
125+
126+
ginkgo.It("modify resources should not successfully", func() {
127+
actualStsSpec := *templateStsSpec.DeepCopy()
128+
testResources := corev1.ResourceRequirements{
129+
Limits: corev1.ResourceList{
130+
corev1.ResourceCPU: resource.MustParse("3"),
131+
},
132+
}
133+
actualStsSpec.Template.Spec.Containers[0].Resources = testResources
134+
mergo.Merge(&actualStsSpec, templateStsSpec, mergo.WithTransformers(utils.StsSpec))
135+
136+
gomega.Expect(actualStsSpec.Template.Spec.Containers[0].Resources.Limits).ShouldNot(gomega.Equal(testResources))
137+
})
138+
})

0 commit comments

Comments
 (0)