Skip to content

Commit 9bd1527

Browse files
committed
Add emptyDir and hostPath as data volume specification
1 parent c26526e commit 9bd1527

File tree

10 files changed

+235
-28
lines changed

10 files changed

+235
-28
lines changed

config/crds/mysql_v1alpha1_mysqlcluster.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,20 @@ spec:
182182
support more data source types and the behavior of the provisioner
183183
may change.
184184
type: object
185+
emptyDir:
186+
description: EmptyDir to use as data volume for mysql. EmptyDir
187+
represents a temporary directory that shares a pod's lifetime.
188+
type: object
189+
hostPath:
190+
description: HostPath to use as data volume for mysql. HostPath
191+
represents a pre-existing file or directory on the host machine
192+
that is directly exposed to the container.
193+
type: object
194+
persistentVolumeClaim:
195+
description: PersistentVolumeClaim to specify PVC spec for the volume
196+
for mysql data. It has the highest level of precedence, followed
197+
by HostPath and EmptyDir. And represents the PVC specification.
198+
type: object
185199
resources:
186200
description: 'Resources represents the minimum resources the volume
187201
should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources'

examples/example-backup.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ spec:
77
# this field is required
88
clusterName: my-cluster
99

10-
## if backupUri is specified then the backup will be put
11-
## at this path, else the backup uri will be filled with
12-
## the cluster preset backupUri and a random name
13-
# backupUri: gs://bucket_name/path/to/backup.xtrabackup.gz
10+
## if backupURL is specified then the backup will be put
11+
## at this path, else the backup UR will be filled with
12+
## the cluster preset backupURL and a random name
13+
# backupURL: gs://bucket_name/path/to/backup.xtrabackup.gz
1414

1515
## specify a secret where to find credentials to access the
1616
## bucket

examples/example-cluster.yaml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,20 @@ spec:
3333

3434
## Specify additional volume specification
3535
# volumeSpec:
36-
# accessModes: [ "ReadWriteOnce" ]
37-
# resources:
38-
# requests:
39-
# storage: 1Gi
36+
# # https://godoc.org/k8s.io/api/core/v1#EmptyDirVolumeSource
37+
# emptyDir: {}
38+
39+
# # https://godoc.org/k8s.io/api/core/v1#HostPathVolumeSource
40+
# hostPath:
41+
# path:
42+
# type:
43+
44+
# # https://godoc.org/k8s.io/api/core/v1#PersistentVolumeClaimSpec
45+
# persistentVolumeClaim:
46+
# accessModes: [ "ReadWriteOnce" ]
47+
# resources:
48+
# requests:
49+
# storage: 1Gi
4050

4151
## Specify service objectives
4252
## If thoses SLO are not fulfilled by cluster node then that node is

pkg/apis/mysql/v1alpha1/mysqlcluster_defaults.go

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ limitations under the License.
1717
package v1alpha1
1818

1919
import (
20-
apiv1 "k8s.io/api/core/v1"
20+
corev1 "k8s.io/api/core/v1"
2121
"k8s.io/apimachinery/pkg/api/resource"
2222
)
2323

@@ -36,7 +36,10 @@ var (
3636
func SetDefaults_MysqlCluster(c *MysqlCluster) {
3737

3838
c.setPodSpecDefaults(&(c.Spec.PodSpec))
39-
c.setVolumeSpecDefaults(&(c.Spec.VolumeSpec))
39+
40+
if c.Spec.VolumeSpec.PersistentVolumeClaim != nil {
41+
c.setVolumeSpecDefaults(c.Spec.VolumeSpec.PersistentVolumeClaim)
42+
}
4043

4144
if c.Spec.Replicas == nil {
4245
one := int32(1)
@@ -56,27 +59,27 @@ func SetDefaults_MysqlCluster(c *MysqlCluster) {
5659
// SetDefaults for PodSpec
5760
func (c *MysqlCluster) setPodSpecDefaults(spec *PodSpec) {
5861
if len(spec.Resources.Requests) == 0 {
59-
spec.Resources = apiv1.ResourceRequirements{
60-
Requests: apiv1.ResourceList{
61-
apiv1.ResourceCPU: resourceRequestCPU,
62-
apiv1.ResourceMemory: resourceRequestMemory,
62+
spec.Resources = corev1.ResourceRequirements{
63+
Requests: corev1.ResourceList{
64+
corev1.ResourceCPU: resourceRequestCPU,
65+
corev1.ResourceMemory: resourceRequestMemory,
6366
},
6467
}
6568
}
6669

6770
}
6871

6972
// SetDefaults for VolumeSpec
70-
func (c *MysqlCluster) setVolumeSpecDefaults(spec *VolumeSpec) {
73+
func (c *MysqlCluster) setVolumeSpecDefaults(spec *corev1.PersistentVolumeClaimSpec) {
7174
if len(spec.AccessModes) == 0 {
72-
spec.AccessModes = []apiv1.PersistentVolumeAccessMode{
73-
apiv1.ReadWriteOnce,
75+
spec.AccessModes = []corev1.PersistentVolumeAccessMode{
76+
corev1.ReadWriteOnce,
7477
}
7578
}
7679
if len(spec.Resources.Requests) == 0 {
77-
spec.Resources = apiv1.ResourceRequirements{
78-
Requests: apiv1.ResourceList{
79-
apiv1.ResourceStorage: resourceStorage,
80+
spec.Resources = corev1.ResourceRequirements{
81+
Requests: corev1.ResourceList{
82+
corev1.ResourceStorage: resourceStorage,
8083
},
8184
}
8285
}

pkg/apis/mysql/v1alpha1/mysqlcluster_types.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,29 @@ type PodSpec struct {
133133
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
134134
}
135135

136-
// VolumeSpec defines type for configure cluster pvc spec.
136+
// VolumeSpec is the desired spec for storing mysql data. Only one of its
137+
// members may be specified.
137138
type VolumeSpec struct {
139+
// DEPRECATED: use `persistentVolumeCalim` field instead to set PVC
140+
// specification
138141
core.PersistentVolumeClaimSpec `json:",inline"`
142+
143+
// EmptyDir to use as data volume for mysql. EmptyDir represents a temporary
144+
// directory that shares a pod's lifetime.
145+
// +optional
146+
EmptyDir *core.EmptyDirVolumeSource `json:"emptyDir,omitempty"`
147+
148+
// HostPath to use as data volume for mysql. HostPath represents a
149+
// pre-existing file or directory on the host machine that is directly
150+
// exposed to the container.
151+
// +optional
152+
HostPath *core.HostPathVolumeSource `json:"hostPath,omitempty"`
153+
154+
// PersistentVolumeClaim to specify PVC spec for the volume for mysql data.
155+
// It has the highest level of precedence, followed by HostPath and
156+
// EmptyDir. And represents the PVC specification.
157+
// +optional
158+
PersistentVolumeClaim *core.PersistentVolumeClaimSpec `json:"persistentVolumeClaim,omitempty"`
139159
}
140160

141161
// QueryLimits represents the pt-kill parameters, more info can be found

pkg/apis/mysql/v1alpha1/zz_generated.deepcopy.go

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

pkg/controller/mysqlcluster/internal/syncer/statefullset.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ func (s *sfsSyncer) SyncFn(in runtime.Object) error {
116116
return err
117117
}
118118

119-
out.Spec.VolumeClaimTemplates = s.ensureVolumeClaimTemplates(out.Spec.VolumeClaimTemplates)
119+
if s.cluster.Spec.VolumeSpec.PersistentVolumeClaim != nil {
120+
out.Spec.VolumeClaimTemplates = s.ensureVolumeClaimTemplates(out.Spec.VolumeClaimTemplates)
121+
}
120122

121123
return nil
122124
}
@@ -446,6 +448,20 @@ func (s *sfsSyncer) ensureContainersSpec() []core.Container {
446448

447449
func (s *sfsSyncer) ensureVolumes() []core.Volume {
448450
fileMode := int32(0644)
451+
dataVolume := core.VolumeSource{}
452+
453+
if s.cluster.Spec.VolumeSpec.PersistentVolumeClaim != nil {
454+
dataVolume.PersistentVolumeClaim = &core.PersistentVolumeClaimVolumeSource{
455+
ClaimName: dataVolumeName,
456+
}
457+
} else if s.cluster.Spec.VolumeSpec.HostPath != nil {
458+
dataVolume.HostPath = s.cluster.Spec.VolumeSpec.HostPath
459+
} else if s.cluster.Spec.VolumeSpec.EmptyDir != nil {
460+
dataVolume.EmptyDir = s.cluster.Spec.VolumeSpec.EmptyDir
461+
} else {
462+
log.Error(nil, "no volume spec is specified", ".spec.volumeSpec", s.cluster.Spec.VolumeSpec)
463+
}
464+
449465
return []core.Volume{
450466
ensureVolume(confVolumeName, core.VolumeSource{
451467
EmptyDir: &core.EmptyDirVolumeSource{},
@@ -460,11 +476,7 @@ func (s *sfsSyncer) ensureVolumes() []core.Volume {
460476
},
461477
}),
462478

463-
ensureVolume(dataVolumeName, core.VolumeSource{
464-
PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{
465-
ClaimName: dataVolumeName,
466-
},
467-
}),
479+
ensureVolume(dataVolumeName, dataVolume),
468480
}
469481
}
470482

@@ -497,7 +509,7 @@ func (s *sfsSyncer) ensureVolumeClaimTemplates(in []core.PersistentVolumeClaim)
497509
}
498510
}
499511

500-
data.Spec = s.cluster.Spec.VolumeSpec.PersistentVolumeClaimSpec
512+
data.Spec = *s.cluster.Spec.VolumeSpec.PersistentVolumeClaim
501513

502514
in[0] = data
503515

pkg/controller/mysqlcluster/mysqlcluster_controller.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,22 @@ func (r *ReconcileMysqlCluster) Reconcile(request reconcile.Request) (reconcile.
148148
}
149149
log.Info("syncing cluster", "cluster", request.NamespacedName.String())
150150

151+
// Update cluster spec that need to be saved
152+
spec := *cluster.Spec.DeepCopy()
153+
cluster.UpdateSpec()
154+
if !reflect.DeepEqual(spec, cluster.Spec) {
155+
sErr := r.Update(context.TODO(), cluster.Unwrap())
156+
if sErr != nil {
157+
log.Error(sErr, "failed to update cluster spec", "cluster", cluster)
158+
return reconcile.Result{}, sErr
159+
}
160+
return reconcile.Result{}, nil
161+
}
162+
151163
// Set defaults on cluster
152164
r.scheme.Default(cluster.Unwrap())
153165
cluster.SetDefaults(r.opt)
166+
154167
if err = cluster.Validate(); err != nil {
155168
return reconcile.Result{}, err
156169
}

pkg/controller/mysqlcluster/mysqlcluster_controller_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ var _ = Describe("MysqlCluster controller", func() {
107107
Spec: api.MysqlClusterSpec{
108108
Replicas: &two,
109109
SecretName: secret.Name,
110+
VolumeSpec: api.VolumeSpec{
111+
PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{},
112+
},
110113
},
111114
})
112115
clusterKey = types.NamespacedName{
@@ -356,6 +359,107 @@ var _ = Describe("MysqlCluster controller", func() {
356359
})
357360
})
358361
})
362+
363+
Context("with secret and uninitialized cluster", func() {
364+
var (
365+
expectedRequest reconcile.Request
366+
cluster *mysqlcluster.MysqlCluster
367+
clusterKey types.NamespacedName
368+
secret *corev1.Secret
369+
)
370+
371+
BeforeEach(func() {
372+
name := fmt.Sprintf("cluster-%d", rand.Int31())
373+
ns := "default"
374+
375+
expectedRequest = reconcile.Request{
376+
NamespacedName: types.NamespacedName{Name: name, Namespace: ns},
377+
}
378+
379+
secret = &corev1.Secret{
380+
ObjectMeta: metav1.ObjectMeta{Name: "the-secret", Namespace: ns},
381+
StringData: map[string]string{
382+
"ROOT_PASSWORD": "this-is-secret",
383+
},
384+
}
385+
386+
cluster = mysqlcluster.New(&api.MysqlCluster{
387+
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns},
388+
Spec: api.MysqlClusterSpec{
389+
Replicas: &two,
390+
SecretName: secret.Name,
391+
},
392+
})
393+
394+
clusterKey = types.NamespacedName{
395+
Name: cluster.Name,
396+
Namespace: cluster.Namespace,
397+
}
398+
399+
Expect(c.Create(context.TODO(), secret)).To(Succeed())
400+
})
401+
402+
AfterEach(func() {
403+
c.Delete(context.TODO(), secret)
404+
c.Delete(context.TODO(), cluster.Unwrap())
405+
})
406+
407+
It("should update cluster new fields from the deprecated ones", func() {
408+
backupURL := "gs://bucket/"
409+
accessModes := []corev1.PersistentVolumeAccessMode{
410+
corev1.ReadWriteOnce,
411+
}
412+
413+
cluster.Spec.BackupURI = backupURL
414+
cluster.Spec.VolumeSpec = api.VolumeSpec{
415+
// old PVC field
416+
PersistentVolumeClaimSpec: corev1.PersistentVolumeClaimSpec{
417+
AccessModes: accessModes,
418+
},
419+
}
420+
421+
// crete cluster
422+
Expect(c.Create(context.TODO(), cluster.Unwrap())).To(Succeed())
423+
Eventually(requests, timeout).Should(Receive(Equal(expectedRequest)))
424+
// one extra reconcile event because of spec updates
425+
Eventually(requests, timeout).Should(Receive(Equal(expectedRequest)))
426+
Eventually(requests, timeout).Should(Receive(Equal(expectedRequest)))
427+
428+
Expect(c.Get(context.TODO(), clusterKey, cluster.Unwrap())).To(Succeed())
429+
Expect(cluster.Spec.VolumeSpec.PersistentVolumeClaim.AccessModes).To(
430+
Equal(accessModes))
431+
Expect(cluster.Spec.BackupURL).To(Equal(backupURL))
432+
})
433+
434+
It("should set emptyDir as data volume", func() {
435+
cluster.Spec.VolumeSpec = api.VolumeSpec{
436+
EmptyDir: &corev1.EmptyDirVolumeSource{},
437+
}
438+
439+
// crete cluster
440+
Expect(c.Create(context.TODO(), cluster.Unwrap())).To(Succeed())
441+
Eventually(requests, timeout).Should(Receive(Equal(expectedRequest)))
442+
Eventually(requests, timeout).Should(Receive(Equal(expectedRequest)))
443+
444+
sts := &appsv1.StatefulSet{}
445+
stsKey := types.NamespacedName{
446+
Name: cluster.GetNameForResource(mysqlcluster.StatefulSet),
447+
Namespace: cluster.Namespace,
448+
}
449+
450+
Expect(c.Get(context.TODO(), stsKey, sts)).To(Succeed())
451+
452+
Expect(sts.Spec.Template.Spec.Volumes).To(ContainElement(Equal(
453+
corev1.Volume{
454+
Name: "data",
455+
VolumeSource: corev1.VolumeSource{
456+
EmptyDir: cluster.Spec.VolumeSpec.EmptyDir,
457+
},
458+
},
459+
)))
460+
})
461+
462+
})
359463
})
360464

361465
func removeAllCreatedResource(c client.Client, clusterComps []runtime.Object) {

pkg/internal/mysqlcluster/mysqlcluster.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,19 @@ func (c *MysqlCluster) GetMysqlImage() string {
166166
// this means the cluster has a wrong MysqlVersion set
167167
return ""
168168
}
169+
170+
// UpdateSpec updates the cluster specs that need to be saved
171+
func (c *MysqlCluster) UpdateSpec() {
172+
// TODO: when BackupURI is removed clear this
173+
if len(c.Spec.BackupURL) == 0 {
174+
c.Spec.BackupURL = c.Spec.BackupURI
175+
}
176+
177+
// TODO: delete this when when inlined PVC is removed from spec.
178+
if c.Spec.VolumeSpec.PersistentVolumeClaim == nil {
179+
if c.Spec.VolumeSpec.HostPath == nil && c.Spec.VolumeSpec.EmptyDir == nil {
180+
// pvc, hostPath and emptyDir not specified then set pvc init from inline
181+
c.Spec.VolumeSpec.PersistentVolumeClaim = &c.Spec.VolumeSpec.PersistentVolumeClaimSpec
182+
}
183+
}
184+
}

0 commit comments

Comments
 (0)