Skip to content

Commit 956aea3

Browse files
author
Nikolas De Giorgis
authored
CLOUDP-65312: arbitrary statefulset configuration (#118)
1 parent 459582c commit 956aea3

File tree

9 files changed

+601
-0
lines changed

9 files changed

+601
-0
lines changed

.evergreen.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ task_groups:
152152
- e2e_test_replica_set_tls
153153
- e2e_test_replica_set_tls_upgrade
154154
- e2e_test_replica_set_tls_rotate
155+
- e2e_test_statefulset_arbitrary_config
156+
- e2e_test_statefulset_arbitrary_config_update
155157
teardown_task:
156158
- func: upload_e2e_logs
157159

@@ -284,6 +286,18 @@ tasks:
284286
vars:
285287
test: replica_set_tls_rotate
286288

289+
- name: e2e_test_statefulset_arbitrary_config
290+
commands:
291+
- func: run_e2e_test
292+
vars:
293+
test: statefulset_arbitrary_config
294+
295+
- name: e2e_test_statefulset_arbitrary_config_update
296+
commands:
297+
- func: run_e2e_test
298+
vars:
299+
test: statefulset_arbitrary_config_update
300+
287301
buildvariants:
288302
- name: go_unit_tests
289303
display_name: go_unit_tests

pkg/apis/mongodb/v1/mongodb_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"strings"
66

7+
appsv1 "k8s.io/api/apps/v1"
78
"k8s.io/apimachinery/pkg/types"
89

910
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -44,6 +45,15 @@ type MongoDBSpec struct {
4445
// Users specifies the MongoDB users that should be configured in your deployment
4546
// +required
4647
Users []MongoDBUser `json:"users"`
48+
49+
// +optional
50+
StatefulSetConfiguration StatefulSetConfiguration `json:"statefulset,omitempty"`
51+
}
52+
53+
// StatefulSetConfiguration holds the optional custom StatefulSet
54+
// that should be merged into the operator created one.
55+
type StatefulSetConfiguration struct {
56+
Spec appsv1.StatefulSetSpec `json:"spec"`
4757
}
4858

4959
type MongoDBUser struct {

pkg/controller/mongodb/replica_set_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,7 @@ func buildStatefulSetModificationFunction(mdb mdbv1.MongoDB) statefulset.Modific
604604
buildScramPodSpecModification(mdb),
605605
),
606606
),
607+
statefulset.WithCustomSpecs(mdb.Spec.StatefulSetConfiguration.Spec),
607608
)
608609
}
609610

pkg/kube/podtemplatespec/podspec_template.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package podtemplatespec
22

33
import (
4+
"github.com/imdario/mergo"
45
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/container"
56
corev1 "k8s.io/api/core/v1"
67
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -227,6 +228,127 @@ func WithVolumeMounts(containerName string, volumeMounts ...corev1.VolumeMount)
227228
}
228229
}
229230

231+
func MergePodTemplateSpecs(defaultTemplate, overrideTemplate corev1.PodTemplateSpec) (corev1.PodTemplateSpec, error) {
232+
// Containers need to be merged manually
233+
mergedContainers, err := mergeContainers(defaultTemplate.Spec.Containers, overrideTemplate.Spec.Containers)
234+
if err != nil {
235+
return corev1.PodTemplateSpec{}, err
236+
}
237+
238+
// InitContainers need to be merged manually
239+
mergedInitContainers, err := mergeContainers(defaultTemplate.Spec.InitContainers, overrideTemplate.Spec.InitContainers)
240+
if err != nil {
241+
return corev1.PodTemplateSpec{}, err
242+
}
243+
244+
// Affinity needs to be merged manually
245+
mergedAffinity, err := mergeAffinity(defaultTemplate.Spec.Affinity, overrideTemplate.Spec.Affinity)
246+
if err != nil {
247+
return corev1.PodTemplateSpec{}, err
248+
}
249+
250+
// Everything else can be merged with mergo
251+
mergedPodTemplateSpec := *defaultTemplate.DeepCopy()
252+
if err = mergo.Merge(&mergedPodTemplateSpec, overrideTemplate, mergo.WithOverride, mergo.WithAppendSlice); err != nil {
253+
return corev1.PodTemplateSpec{}, err
254+
}
255+
256+
mergedPodTemplateSpec.Spec.Containers = mergedContainers
257+
mergedPodTemplateSpec.Spec.InitContainers = mergedInitContainers
258+
mergedPodTemplateSpec.Spec.Affinity = mergedAffinity
259+
return mergedPodTemplateSpec, nil
260+
}
261+
262+
func mergeVolumeMounts(defaultMounts, overrideMounts []corev1.VolumeMount) ([]corev1.VolumeMount, error) {
263+
defaultMountsMap := createMountsMap(defaultMounts)
264+
overrideMountsMap := createMountsMap(overrideMounts)
265+
mergedVolumeMounts := []corev1.VolumeMount{}
266+
for _, defaultMount := range defaultMounts {
267+
if overrideMount, ok := overrideMountsMap[defaultMount.Name]; ok {
268+
// needs merge
269+
if err := mergo.Merge(&defaultMount, overrideMount, mergo.WithAppendSlice); err != nil { //nolint
270+
return nil, err
271+
}
272+
}
273+
mergedVolumeMounts = append(mergedVolumeMounts, defaultMount)
274+
}
275+
for _, overrideMount := range overrideMounts {
276+
if _, ok := defaultMountsMap[overrideMount.Name]; ok {
277+
// already merged
278+
continue
279+
}
280+
mergedVolumeMounts = append(mergedVolumeMounts, overrideMount)
281+
}
282+
return mergedVolumeMounts, nil
283+
}
284+
285+
func createMountsMap(volumeMounts []corev1.VolumeMount) map[string]corev1.VolumeMount {
286+
mountMap := make(map[string]corev1.VolumeMount)
287+
for _, m := range volumeMounts {
288+
mountMap[m.Name] = m
289+
}
290+
return mountMap
291+
}
292+
293+
func mergeContainers(defaultContainers, customContainers []corev1.Container) ([]corev1.Container, error) {
294+
defaultMap := createContainerMap(defaultContainers)
295+
customMap := createContainerMap(customContainers)
296+
mergedContainers := []corev1.Container{}
297+
for _, defaultContainer := range defaultContainers {
298+
if customContainer, ok := customMap[defaultContainer.Name]; ok {
299+
// The container is present in both maps, so we need to merge
300+
// Merge mounts
301+
mergedMounts, err := mergeVolumeMounts(defaultContainer.VolumeMounts, customContainer.VolumeMounts)
302+
if err != nil {
303+
return nil, err
304+
}
305+
if err := mergo.Merge(&defaultContainer, customContainer, mergo.WithOverride); err != nil { //nolint
306+
return nil, err
307+
}
308+
// completely override any resources that were provided
309+
// this prevents issues with custom requests giving errors due
310+
// to the defaulted limits
311+
defaultContainer.Resources = customContainer.Resources
312+
defaultContainer.VolumeMounts = mergedMounts
313+
}
314+
// The default container was not modified by the override, so just add it
315+
mergedContainers = append(mergedContainers, defaultContainer)
316+
}
317+
318+
// Look for customContainers that were not merged into existing ones
319+
for _, customContainer := range customContainers {
320+
if _, ok := defaultMap[customContainer.Name]; ok {
321+
continue
322+
}
323+
// Need to add it
324+
mergedContainers = append(mergedContainers, customContainer)
325+
}
326+
327+
return mergedContainers, nil
328+
}
329+
330+
func createContainerMap(containers []corev1.Container) map[string]corev1.Container {
331+
containerMap := make(map[string]corev1.Container)
332+
for _, c := range containers {
333+
containerMap[c.Name] = c
334+
}
335+
return containerMap
336+
}
337+
338+
func mergeAffinity(defaultAffinity, overrideAffinity *corev1.Affinity) (*corev1.Affinity, error) {
339+
if defaultAffinity == nil {
340+
return overrideAffinity, nil
341+
}
342+
if overrideAffinity == nil {
343+
return defaultAffinity, nil
344+
}
345+
mergedAffinity := defaultAffinity.DeepCopy()
346+
if err := mergo.Merge(mergedAffinity, *overrideAffinity, mergo.WithOverride); err != nil {
347+
return nil, err
348+
}
349+
return mergedAffinity, nil
350+
}
351+
230352
// findContainerByName will find either a container or init container by name in a pod template spec
231353
func findContainerByName(name string, podTemplateSpec *corev1.PodTemplateSpec) *corev1.Container {
232354
containerIdx := findIndexByName(name, podTemplateSpec.Spec.Containers)

0 commit comments

Comments
 (0)