Skip to content

Commit 955087a

Browse files
committed
Add additional volume for postgres pods
Change the API to allow users to specify preexisting PVCs to attach to specified containers in the postgres instance pods. Issues: [PGO-2558]
1 parent aa00067 commit 955087a

File tree

13 files changed

+529
-0
lines changed

13 files changed

+529
-0
lines changed

config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11027,6 +11027,44 @@ spec:
1102711027
type: array
1102811028
volumes:
1102911029
properties:
11030+
additional:
11031+
description: Additional pre-existing volumes to add to the
11032+
pod.
11033+
items:
11034+
properties:
11035+
claimName:
11036+
description: A reference to a preexisting PVC.
11037+
type: string
11038+
containers:
11039+
description: |-
11040+
The containers to attach this volume to.
11041+
A blank/unset `Containers` field matches all containers.
11042+
items:
11043+
type: string
11044+
maxItems: 10
11045+
type: array
11046+
x-kubernetes-list-type: atomic
11047+
name:
11048+
description: |-
11049+
The name of the volume used for mounting path.
11050+
Volumes are mounted in the pods at `volumes/<NAME>`
11051+
Must be unique.
11052+
maxLength: 253
11053+
minLength: 1
11054+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
11055+
type: string
11056+
readOnly:
11057+
description: Sets the write/read mode of the volume
11058+
type: boolean
11059+
required:
11060+
- claimName
11061+
- name
11062+
type: object
11063+
maxItems: 10
11064+
type: array
11065+
x-kubernetes-list-map-keys:
11066+
- name
11067+
x-kubernetes-list-type: map
1103011068
temp:
1103111069
description: |-
1103211070
An ephemeral volume for temporary files.
@@ -29598,6 +29636,44 @@ spec:
2959829636
type: array
2959929637
volumes:
2960029638
properties:
29639+
additional:
29640+
description: Additional pre-existing volumes to add to the
29641+
pod.
29642+
items:
29643+
properties:
29644+
claimName:
29645+
description: A reference to a preexisting PVC.
29646+
type: string
29647+
containers:
29648+
description: |-
29649+
The containers to attach this volume to.
29650+
A blank/unset `Containers` field matches all containers.
29651+
items:
29652+
type: string
29653+
maxItems: 10
29654+
type: array
29655+
x-kubernetes-list-type: atomic
29656+
name:
29657+
description: |-
29658+
The name of the volume used for mounting path.
29659+
Volumes are mounted in the pods at `volumes/<NAME>`
29660+
Must be unique.
29661+
maxLength: 253
29662+
minLength: 1
29663+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
29664+
type: string
29665+
readOnly:
29666+
description: Sets the write/read mode of the volume
29667+
type: boolean
29668+
required:
29669+
- claimName
29670+
- name
29671+
type: object
29672+
maxItems: 10
29673+
type: array
29674+
x-kubernetes-list-map-keys:
29675+
- name
29676+
x-kubernetes-list-type: map
2960129677
temp:
2960229678
description: |-
2960329679
An ephemeral volume for temporary files.

internal/controller/postgrescluster/instance.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,11 @@ func (r *Reconciler) reconcileInstance(
12531253
addDevSHM(&instance.Spec.Template)
12541254
}
12551255

1256+
// mount additional volumes to the Postgres instance containers
1257+
if err == nil && spec.Volumes != nil && len(spec.Volumes.Additional) > 0 {
1258+
addAdditionalVolumesToSpecifiedContainers(&instance.Spec.Template, spec.Volumes.Additional)
1259+
}
1260+
12561261
if err == nil {
12571262
err = errors.WithStack(r.apply(ctx, instance))
12581263
}

internal/controller/postgrescluster/util.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ import (
1313
corev1 "k8s.io/api/core/v1"
1414
"k8s.io/apimachinery/pkg/api/resource"
1515
"k8s.io/apimachinery/pkg/util/rand"
16+
"k8s.io/apimachinery/pkg/util/sets"
1617

1718
"github.com/crunchydata/postgres-operator/internal/initialize"
1819
"github.com/crunchydata/postgres-operator/internal/naming"
20+
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
1921
)
2022

2123
var tmpDirSizeLimit = resource.MustParse("16Mi")
@@ -285,3 +287,61 @@ func safeHash32(content func(w io.Writer) error) (string, error) {
285287
}
286288
return rand.SafeEncodeString(fmt.Sprint(hash.Sum32())), nil
287289
}
290+
291+
// AdditionalVolumeMount returns the name and mount path of the additional volume.
292+
func AdditionalVolumeMount(name string, readOnly bool) corev1.VolumeMount {
293+
return corev1.VolumeMount{
294+
Name: fmt.Sprintf("volumes-%s", name),
295+
MountPath: "/volumes/" + name,
296+
ReadOnly: readOnly,
297+
}
298+
}
299+
300+
// addAdditionalVolumesToSpecifiedContainers adds additional volumes to the specified
301+
// containers in the specified pod
302+
// addAdditionalVolumesToSpecifiedContainers adds the volumes to the pod
303+
// as `volumes-<additionalVolumeRequest.Name>`
304+
// and adds the directory to the path `/volumes/<additionalVolumeRequest.Name>`
305+
func addAdditionalVolumesToSpecifiedContainers(template *corev1.PodTemplateSpec,
306+
additionalVolumes []v1beta1.AdditionalVolume) {
307+
308+
for _, additionalVolumeRequest := range additionalVolumes {
309+
310+
additionalVolumeMount := AdditionalVolumeMount(
311+
additionalVolumeRequest.Name,
312+
additionalVolumeRequest.ReadOnly,
313+
)
314+
315+
additionalVolume := corev1.Volume{
316+
Name: additionalVolumeMount.Name,
317+
VolumeSource: corev1.VolumeSource{
318+
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
319+
ClaimName: additionalVolumeRequest.ClaimName,
320+
ReadOnly: additionalVolumeMount.ReadOnly,
321+
},
322+
},
323+
}
324+
325+
names := sets.New(additionalVolumeRequest.Containers...)
326+
327+
for i := range template.Spec.Containers {
328+
if names.Len() == 0 || names.Has(template.Spec.Containers[i].Name) {
329+
template.Spec.Containers[i].VolumeMounts = append(
330+
template.Spec.Containers[i].VolumeMounts,
331+
additionalVolumeMount)
332+
}
333+
}
334+
335+
for i := range template.Spec.InitContainers {
336+
if names.Len() == 0 || names.Has(template.Spec.InitContainers[i].Name) {
337+
template.Spec.InitContainers[i].VolumeMounts = append(
338+
template.Spec.InitContainers[i].VolumeMounts,
339+
additionalVolumeMount)
340+
}
341+
}
342+
343+
template.Spec.Volumes = append(
344+
template.Spec.Volumes,
345+
additionalVolume)
346+
}
347+
}

internal/controller/postgrescluster/util_test.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"github.com/crunchydata/postgres-operator/internal/naming"
1818
"github.com/crunchydata/postgres-operator/internal/testing/cmp"
19+
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
1920
)
2021

2122
func TestSafeHash32(t *testing.T) {
@@ -378,3 +379,167 @@ func TestJobFailed(t *testing.T) {
378379
})
379380
}
380381
}
382+
383+
func TestAddAdditionalVolumesToSpecifiedContainers(t *testing.T) {
384+
385+
podTemplate := &corev1.PodTemplateSpec{
386+
Spec: corev1.PodSpec{
387+
InitContainers: []corev1.Container{
388+
{Name: "startup"},
389+
{Name: "config"},
390+
},
391+
Containers: []corev1.Container{
392+
{Name: "database"},
393+
{Name: "other"},
394+
}}}
395+
396+
testCases := []struct {
397+
tcName string
398+
additionalVolumes []v1beta1.AdditionalVolume
399+
expectedContainers string
400+
expectedInitContainers string
401+
expectedVolumes string
402+
}{{
403+
tcName: "all",
404+
additionalVolumes: []v1beta1.AdditionalVolume{{
405+
Containers: []string{},
406+
ClaimName: "required",
407+
Name: "required",
408+
}},
409+
expectedContainers: `- name: database
410+
resources: {}
411+
volumeMounts:
412+
- mountPath: /volumes/required
413+
name: volumes-required
414+
- name: other
415+
resources: {}
416+
volumeMounts:
417+
- mountPath: /volumes/required
418+
name: volumes-required`,
419+
expectedInitContainers: `- name: startup
420+
resources: {}
421+
volumeMounts:
422+
- mountPath: /volumes/required
423+
name: volumes-required
424+
- name: config
425+
resources: {}
426+
volumeMounts:
427+
- mountPath: /volumes/required
428+
name: volumes-required`,
429+
expectedVolumes: `- name: volumes-required
430+
persistentVolumeClaim:
431+
claimName: required`,
432+
}, {
433+
tcName: "multiple additional volumes",
434+
additionalVolumes: []v1beta1.AdditionalVolume{{
435+
Containers: []string{},
436+
ClaimName: "required",
437+
Name: "required",
438+
}, {
439+
Containers: []string{},
440+
ClaimName: "also",
441+
Name: "other",
442+
}},
443+
expectedContainers: `- name: database
444+
resources: {}
445+
volumeMounts:
446+
- mountPath: /volumes/required
447+
name: volumes-required
448+
- mountPath: /volumes/other
449+
name: volumes-other
450+
- name: other
451+
resources: {}
452+
volumeMounts:
453+
- mountPath: /volumes/required
454+
name: volumes-required
455+
- mountPath: /volumes/other
456+
name: volumes-other`,
457+
expectedInitContainers: `- name: startup
458+
resources: {}
459+
volumeMounts:
460+
- mountPath: /volumes/required
461+
name: volumes-required
462+
- mountPath: /volumes/other
463+
name: volumes-other
464+
- name: config
465+
resources: {}
466+
volumeMounts:
467+
- mountPath: /volumes/required
468+
name: volumes-required
469+
- mountPath: /volumes/other
470+
name: volumes-other`,
471+
expectedVolumes: `- name: volumes-required
472+
persistentVolumeClaim:
473+
claimName: required
474+
- name: volumes-other
475+
persistentVolumeClaim:
476+
claimName: also`,
477+
}, {
478+
tcName: "database container only",
479+
additionalVolumes: []v1beta1.AdditionalVolume{{
480+
Containers: []string{"database"},
481+
ClaimName: "required",
482+
Name: "required",
483+
}},
484+
expectedContainers: `- name: database
485+
resources: {}
486+
volumeMounts:
487+
- mountPath: /volumes/required
488+
name: volumes-required
489+
- name: other
490+
resources: {}`,
491+
expectedInitContainers: `- name: startup
492+
resources: {}
493+
- name: config
494+
resources: {}`,
495+
expectedVolumes: `- name: volumes-required
496+
persistentVolumeClaim:
497+
claimName: required`,
498+
}, {
499+
tcName: "readonly",
500+
additionalVolumes: []v1beta1.AdditionalVolume{{
501+
Containers: []string{"database"},
502+
ClaimName: "required",
503+
Name: "required",
504+
ReadOnly: true,
505+
}},
506+
expectedContainers: `- name: database
507+
resources: {}
508+
volumeMounts:
509+
- mountPath: /volumes/required
510+
name: volumes-required
511+
readOnly: true
512+
- name: other
513+
resources: {}`,
514+
expectedInitContainers: `- name: startup
515+
resources: {}
516+
- name: config
517+
resources: {}`,
518+
expectedVolumes: `- name: volumes-required
519+
persistentVolumeClaim:
520+
claimName: required
521+
readOnly: true`,
522+
}}
523+
524+
for _, tc := range testCases {
525+
t.Run(tc.tcName, func(t *testing.T) {
526+
527+
copyPodTemplate := podTemplate.DeepCopy()
528+
529+
addAdditionalVolumesToSpecifiedContainers(
530+
copyPodTemplate,
531+
tc.additionalVolumes,
532+
)
533+
534+
assert.Assert(t, cmp.MarshalMatches(
535+
copyPodTemplate.Spec.Containers,
536+
tc.expectedContainers))
537+
assert.Assert(t, cmp.MarshalMatches(
538+
copyPodTemplate.Spec.InitContainers,
539+
tc.expectedInitContainers))
540+
assert.Assert(t, cmp.MarshalMatches(
541+
copyPodTemplate.Spec.Volumes,
542+
tc.expectedVolumes))
543+
})
544+
}
545+
}

pkg/apis/postgres-operator.crunchydata.com/v1/postgrescluster_types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,14 @@ type PostgresVolumesSpec struct {
534534
// ---
535535
// +optional
536536
Temp *v1beta1.VolumeClaimSpec `json:"temp,omitempty"`
537+
538+
// Additional pre-existing volumes to add to the pod.
539+
// ---
540+
// +optional
541+
// +listType=map
542+
// +listMapKey=name
543+
// +kubebuilder:validation:MaxItems=10
544+
Additional []v1beta1.AdditionalVolume `json:"additional,omitempty"`
537545
}
538546

539547
type TablespaceVolume struct {

pkg/apis/postgres-operator.crunchydata.com/v1/zz_generated.deepcopy.go

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

0 commit comments

Comments
 (0)