Skip to content

Commit 7926b18

Browse files
authored
feat: additional containers and volumes support (#345)
* feat: additional containers and volumes add support for providing additional containers and volumes for dragonfly cluster pods * feat: additional containers and volumes support
1 parent eb70eb5 commit 7926b18

File tree

9 files changed

+9858
-0
lines changed

9 files changed

+9858
-0
lines changed

api/v1alpha1/dragonfly_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ type DragonflySpec struct {
7272
// +kubebuilder:validation:Optional
7373
Env []corev1.EnvVar `json:"env,omitempty"`
7474

75+
// (Optional) Additional containers to add to dragonflycluster. Replace container on name collision.
76+
// +optional
77+
// +kubebuilder:validation:Optional
78+
AdditionalContainers []corev1.Container `json:"additionalContainers,omitempty"`
79+
80+
// (Optional) Additional volumes to add to dragonflycluster. Replace volume on name collision.
81+
// +optional
82+
// +kubebuilder:validation:Optional
83+
AdditionalVolumes []corev1.Volume `json:"additionalVolumes,omitempty"`
84+
7585
// (Optional) Dragonfly container resource limits. Any container limits
7686
// can be specified.
7787
// +optional

api/v1alpha1/zz_generated.deepcopy.go

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

config/crd/bases/dragonflydb.io_dragonflies.yaml

Lines changed: 3209 additions & 0 deletions
Large diffs are not rendered by default.

e2e/dragonfly_controller_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"encoding/pem"
2626
"fmt"
2727
"math/big"
28+
"strings"
2829
"time"
2930

3031
resourcesv1 "github.com/dragonflydb/dragonfly-operator/api/v1alpha1"
@@ -892,6 +893,114 @@ var _ = Describe("Dragonfly PVC Test with single replica", Ordered, FlakeAttempt
892893
})
893894
})
894895

896+
var _ = Describe("Dragonfly with additional container and volume", Ordered, FlakeAttempts(3), func() {
897+
ctx := context.Background()
898+
const (
899+
name = "df-addons"
900+
namespace = "default"
901+
902+
sidecarName = "helper"
903+
sidecarImage = "busybox:1.36"
904+
volumeName = "extra-storage"
905+
volumeMountPt = "/opt/data"
906+
)
907+
908+
Context("Create DF and verify additional resources are merged", func() {
909+
It("Should create Dragonfly with an extra container and volume", func() {
910+
df := &resourcesv1.Dragonfly{
911+
ObjectMeta: metav1.ObjectMeta{
912+
Name: name,
913+
Namespace: namespace,
914+
},
915+
Spec: resourcesv1.DragonflySpec{
916+
Replicas: 1,
917+
AdditionalContainers: []corev1.Container{
918+
{
919+
Name: sidecarName,
920+
Image: sidecarImage,
921+
Command: []string{"sh", "-c", "sleep 3600"},
922+
VolumeMounts: []corev1.VolumeMount{
923+
{Name: volumeName, MountPath: volumeMountPt},
924+
},
925+
},
926+
},
927+
AdditionalVolumes: []corev1.Volume{
928+
{
929+
Name: volumeName,
930+
VolumeSource: corev1.VolumeSource{
931+
EmptyDir: &corev1.EmptyDirVolumeSource{},
932+
},
933+
},
934+
},
935+
},
936+
}
937+
Expect(k8sClient.Create(ctx, df)).To(Succeed())
938+
})
939+
940+
It("Resources should include the additional container and volume", func() {
941+
// Wait for reconciliation to create SS and mark DF resources created
942+
waitForDragonflyPhase(ctx, k8sClient, name, namespace, controller.PhaseResourcesCreated, 2*time.Minute)
943+
waitForStatefulSetReady(ctx, k8sClient, name, namespace, 2*time.Minute)
944+
945+
// Fetch StatefulSet
946+
var ss appsv1.StatefulSet
947+
Expect(k8sClient.Get(ctx, types.NamespacedName{
948+
Name: name,
949+
Namespace: namespace,
950+
}, &ss)).To(Succeed())
951+
952+
// --- Verify additional container exists
953+
containers := ss.Spec.Template.Spec.Containers
954+
var helperIdx = -1
955+
containerNames := make([]string, 0, len(containers))
956+
for i, c := range containers {
957+
containerNames = append(containerNames, c.Name)
958+
if c.Name == sidecarName {
959+
helperIdx = i
960+
}
961+
}
962+
By("Listing containers: " + strings.Join(containerNames, ", "))
963+
Expect(helperIdx).To(BeNumerically(">=", 0), "expected sidecar container %q to exist", sidecarName)
964+
965+
helper := containers[helperIdx]
966+
Expect(helper.Image).To(Equal(sidecarImage))
967+
// Check the volume mount by name + path (avoid brittle deep equality)
968+
var hasMount bool
969+
for _, vm := range helper.VolumeMounts {
970+
if vm.Name == volumeName && vm.MountPath == volumeMountPt {
971+
hasMount = true
972+
break
973+
}
974+
}
975+
Expect(hasMount).To(BeTrue(), "expected sidecar to mount %q at %q", volumeName, volumeMountPt)
976+
977+
// --- Verify additional volume exists
978+
vols := ss.Spec.Template.Spec.Volumes
979+
var volIdx = -1
980+
volNames := make([]string, 0, len(vols))
981+
for i, v := range vols {
982+
volNames = append(volNames, v.Name)
983+
if v.Name == volumeName {
984+
volIdx = i
985+
}
986+
}
987+
By("Listing volumes: " + strings.Join(volNames, ", "))
988+
Expect(volIdx).To(BeNumerically(">=", 0), "expected volume %q to exist", volumeName)
989+
990+
Expect(vols[volIdx].EmptyDir).ToNot(BeNil(), "expected %q to be an EmptyDir", volumeName)
991+
})
992+
993+
It("Cleanup", func() {
994+
var df resourcesv1.Dragonfly
995+
Expect(k8sClient.Get(ctx, types.NamespacedName{
996+
Name: name,
997+
Namespace: namespace,
998+
}, &df)).To(Succeed())
999+
Expect(k8sClient.Delete(ctx, &df)).To(Succeed())
1000+
})
1001+
})
1002+
})
1003+
8951004
var _ = Describe("Dragonfly Server TLS tests", Ordered, FlakeAttempts(3), func() {
8961005
ctx := context.Background()
8971006
name := "df-tls"

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/onsi/gomega v1.36.1
99
github.com/pkg/errors v0.9.1
1010
github.com/redis/go-redis/v9 v9.7.3
11+
github.com/stretchr/testify v1.10.0
1112
k8s.io/api v0.32.3
1213
k8s.io/apimachinery v0.32.3
1314
k8s.io/client-go v0.32.3
@@ -46,6 +47,7 @@ require (
4647
github.com/modern-go/reflect2 v1.0.2 // indirect
4748
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
4849
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
50+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
4951
github.com/prometheus/client_golang v1.21.1 // indirect
5052
github.com/prometheus/client_model v0.6.1 // indirect
5153
github.com/prometheus/common v0.63.0 // indirect

internal/resources/resources.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,14 @@ func GenerateDragonflyResources(df *resourcesv1.Dragonfly) ([]client.Object, err
369369
}
370370
}
371371

372+
statefulset.Spec.Template.Spec.Containers = mergeNamedSlices(
373+
statefulset.Spec.Template.Spec.Containers, df.Spec.AdditionalContainers,
374+
func(c corev1.Container) string { return c.Name })
375+
376+
statefulset.Spec.Template.Spec.Volumes = mergeNamedSlices(
377+
statefulset.Spec.Template.Spec.Volumes, df.Spec.AdditionalVolumes,
378+
func(v corev1.Volume) string { return v.Name })
379+
372380
resources = append(resources, &statefulset)
373381

374382
serviceName := df.Name
@@ -458,6 +466,25 @@ func GenerateDragonflyResources(df *resourcesv1.Dragonfly) ([]client.Object, err
458466
return resources, nil
459467
}
460468

469+
// mergeNamedSlices will merge base into override, override takes precendence
470+
func mergeNamedSlices[T any](base, override []T, getName func(T) string) []T {
471+
existing := make(map[string]bool, len(override))
472+
for _, item := range override {
473+
existing[getName(item)] = true
474+
}
475+
476+
result := make([]T, len(override))
477+
copy(result, override)
478+
479+
for _, item := range base {
480+
if !existing[getName(item)] {
481+
result = append(result, item)
482+
}
483+
}
484+
485+
return result
486+
}
487+
461488
func generateResourceLabels(df *resourcesv1.Dragonfly) map[string]string {
462489
return map[string]string{
463490
KubernetesAppComponentLabelKey: KubernetesAppComponent,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package resources
2+
3+
import (
4+
"testing"
5+
6+
corev1 "k8s.io/api/core/v1"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestMergeNamedSlices_Containers(t *testing.T) {
12+
base := []corev1.Container{
13+
{Name: "main", Image: "main:1"},
14+
{Name: "sidecar", Image: "sidecar:1"},
15+
}
16+
override := []corev1.Container{
17+
{Name: "main", Image: "main:custom"},
18+
{Name: "metrics", Image: "metrics:1"},
19+
}
20+
21+
result := mergeNamedSlices(base, override, func(c corev1.Container) string { return c.Name })
22+
23+
assert.Len(t, result, 3)
24+
assert.Equal(t, "main:custom", result[0].Image)
25+
assert.Equal(t, "metrics:1", result[1].Image)
26+
assert.Equal(t, "sidecar:1", result[2].Image)
27+
}
28+
29+
func TestMergeNamedSlices_Volumes(t *testing.T) {
30+
base := []corev1.Volume{
31+
{Name: "config"},
32+
{Name: "data"},
33+
}
34+
override := []corev1.Volume{
35+
{Name: "config"}, // override
36+
{Name: "logs"},
37+
}
38+
39+
result := mergeNamedSlices(base, override, func(v corev1.Volume) string { return v.Name })
40+
41+
assert.Len(t, result, 3)
42+
assert.Equal(t, "config", result[0].Name)
43+
assert.Equal(t, "logs", result[1].Name)
44+
assert.Equal(t, "data", result[2].Name)
45+
}
46+
47+
func TestMergeNamedSlices_EmptyOverride(t *testing.T) {
48+
base := []corev1.Volume{
49+
{Name: "default"},
50+
}
51+
override := []corev1.Volume{}
52+
53+
result := mergeNamedSlices(base, override, func(v corev1.Volume) string { return v.Name })
54+
55+
assert.Len(t, result, 1)
56+
assert.Equal(t, "default", result[0].Name)
57+
}
58+
59+
func TestMergeNamedSlices_EmptyBase(t *testing.T) {
60+
base := []corev1.Container{}
61+
override := []corev1.Container{
62+
{Name: "user", Image: "user:1"},
63+
}
64+
65+
result := mergeNamedSlices(base, override, func(c corev1.Container) string { return c.Name })
66+
67+
assert.Len(t, result, 1)
68+
assert.Equal(t, "user", result[0].Name)
69+
}

0 commit comments

Comments
 (0)