Skip to content

Commit 5932322

Browse files
committed
Add multiorch controller
1 parent d58faf2 commit 5932322

File tree

9 files changed

+1739
-0
lines changed

9 files changed

+1739
-0
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package multiorch
2+
3+
import (
4+
"fmt"
5+
6+
appsv1 "k8s.io/api/apps/v1"
7+
corev1 "k8s.io/api/core/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/apimachinery/pkg/runtime"
10+
ctrl "sigs.k8s.io/controller-runtime"
11+
12+
multigresv1alpha1 "github.com/numtide/multigres-operator/api/v1alpha1"
13+
"github.com/numtide/multigres-operator/pkg/resource-handler/controller/metadata"
14+
)
15+
16+
const (
17+
// ComponentName is the component label value for multiorch resources
18+
ComponentName = "multiorch"
19+
20+
// DefaultReplicas is the default number of multiorch replicas
21+
DefaultReplicas int32 = 1
22+
23+
// DefaultImage is the default multiorch container image
24+
DefaultImage = "ghcr.io/multigres/multiorch:latest"
25+
)
26+
27+
// BuildDeployment creates a Deployment for the MultiOrch cluster.
28+
// Returns a deterministic Deployment based on the MultiOrch spec.
29+
func BuildDeployment(
30+
multiorch *multigresv1alpha1.MultiOrch,
31+
scheme *runtime.Scheme,
32+
) (*appsv1.Deployment, error) {
33+
replicas := DefaultReplicas
34+
if multiorch.Spec.Replicas != nil {
35+
replicas = *multiorch.Spec.Replicas
36+
}
37+
38+
image := DefaultImage
39+
if multiorch.Spec.Image != "" {
40+
image = multiorch.Spec.Image
41+
}
42+
43+
labels := metadata.BuildStandardLabels(multiorch.Name, ComponentName, multiorch.Spec.CellName)
44+
podLabels := metadata.MergeLabels(labels, multiorch.Spec.PodLabels)
45+
46+
deployment := &appsv1.Deployment{
47+
ObjectMeta: metav1.ObjectMeta{
48+
Name: multiorch.Name,
49+
Namespace: multiorch.Namespace,
50+
Labels: labels,
51+
},
52+
Spec: appsv1.DeploymentSpec{
53+
Replicas: &replicas,
54+
Selector: &metav1.LabelSelector{
55+
MatchLabels: labels,
56+
},
57+
Template: corev1.PodTemplateSpec{
58+
ObjectMeta: metav1.ObjectMeta{
59+
Labels: podLabels,
60+
Annotations: multiorch.Spec.PodAnnotations,
61+
},
62+
Spec: corev1.PodSpec{
63+
ServiceAccountName: multiorch.Spec.ServiceAccountName,
64+
ImagePullSecrets: multiorch.Spec.ImagePullSecrets,
65+
Containers: []corev1.Container{
66+
{
67+
Name: "multiorch",
68+
Image: image,
69+
Resources: multiorch.Spec.Resources,
70+
Ports: buildContainerPorts(multiorch),
71+
},
72+
},
73+
Affinity: multiorch.Spec.Affinity,
74+
Tolerations: multiorch.Spec.Tolerations,
75+
NodeSelector: multiorch.Spec.NodeSelector,
76+
TopologySpreadConstraints: multiorch.Spec.TopologySpreadConstraints,
77+
},
78+
},
79+
},
80+
}
81+
82+
if err := ctrl.SetControllerReference(multiorch, deployment, scheme); err != nil {
83+
return nil, fmt.Errorf("failed to set controller reference: %w", err)
84+
}
85+
86+
return deployment, nil
87+
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
package multiorch
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/go-cmp/cmp"
7+
appsv1 "k8s.io/api/apps/v1"
8+
corev1 "k8s.io/api/core/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/apimachinery/pkg/runtime"
11+
12+
multigresv1alpha1 "github.com/numtide/multigres-operator/api/v1alpha1"
13+
)
14+
15+
func int32Ptr(i int32) *int32 {
16+
return &i
17+
}
18+
19+
func boolPtr(b bool) *bool {
20+
return &b
21+
}
22+
23+
func TestBuildDeployment(t *testing.T) {
24+
scheme := runtime.NewScheme()
25+
_ = multigresv1alpha1.AddToScheme(scheme)
26+
27+
tests := map[string]struct {
28+
multiorch *multigresv1alpha1.MultiOrch
29+
scheme *runtime.Scheme
30+
want *appsv1.Deployment
31+
wantErr bool
32+
}{
33+
"minimal spec - all defaults": {
34+
multiorch: &multigresv1alpha1.MultiOrch{
35+
ObjectMeta: metav1.ObjectMeta{
36+
Name: "test-multiorch",
37+
Namespace: "default",
38+
UID: "test-uid",
39+
},
40+
Spec: multigresv1alpha1.MultiOrchSpec{},
41+
},
42+
scheme: scheme,
43+
want: &appsv1.Deployment{
44+
ObjectMeta: metav1.ObjectMeta{
45+
Name: "test-multiorch",
46+
Namespace: "default",
47+
Labels: map[string]string{
48+
"app.kubernetes.io/name": "multigres",
49+
"app.kubernetes.io/instance": "test-multiorch",
50+
"app.kubernetes.io/component": "multiorch",
51+
"app.kubernetes.io/part-of": "multigres",
52+
"app.kubernetes.io/managed-by": "multigres-operator",
53+
"multigres.com/cell": "multigres-global-topo",
54+
},
55+
OwnerReferences: []metav1.OwnerReference{
56+
{
57+
APIVersion: "multigres.com/v1alpha1",
58+
Kind: "MultiOrch",
59+
Name: "test-multiorch",
60+
UID: "test-uid",
61+
Controller: boolPtr(true),
62+
BlockOwnerDeletion: boolPtr(true),
63+
},
64+
},
65+
},
66+
Spec: appsv1.DeploymentSpec{
67+
Replicas: int32Ptr(1),
68+
Selector: &metav1.LabelSelector{
69+
MatchLabels: map[string]string{
70+
"app.kubernetes.io/name": "multigres",
71+
"app.kubernetes.io/instance": "test-multiorch",
72+
"app.kubernetes.io/component": "multiorch",
73+
"app.kubernetes.io/part-of": "multigres",
74+
"app.kubernetes.io/managed-by": "multigres-operator",
75+
"multigres.com/cell": "multigres-global-topo",
76+
},
77+
},
78+
Template: corev1.PodTemplateSpec{
79+
ObjectMeta: metav1.ObjectMeta{
80+
Labels: map[string]string{
81+
"app.kubernetes.io/name": "multigres",
82+
"app.kubernetes.io/instance": "test-multiorch",
83+
"app.kubernetes.io/component": "multiorch",
84+
"app.kubernetes.io/part-of": "multigres",
85+
"app.kubernetes.io/managed-by": "multigres-operator",
86+
"multigres.com/cell": "multigres-global-topo",
87+
},
88+
},
89+
Spec: corev1.PodSpec{
90+
Containers: []corev1.Container{
91+
{
92+
Name: "multiorch",
93+
Image: DefaultImage,
94+
Resources: corev1.ResourceRequirements{},
95+
Ports: buildContainerPorts(&multigresv1alpha1.MultiOrch{}),
96+
},
97+
},
98+
},
99+
},
100+
},
101+
},
102+
},
103+
"custom replicas and image": {
104+
multiorch: &multigresv1alpha1.MultiOrch{
105+
ObjectMeta: metav1.ObjectMeta{
106+
Name: "test-multiorch",
107+
Namespace: "default",
108+
UID: "test-uid",
109+
},
110+
Spec: multigresv1alpha1.MultiOrchSpec{
111+
Replicas: int32Ptr(3),
112+
Image: "custom/multiorch:v2.0.0",
113+
},
114+
},
115+
scheme: scheme,
116+
want: &appsv1.Deployment{
117+
ObjectMeta: metav1.ObjectMeta{
118+
Name: "test-multiorch",
119+
Namespace: "default",
120+
Labels: map[string]string{
121+
"app.kubernetes.io/name": "multigres",
122+
"app.kubernetes.io/instance": "test-multiorch",
123+
"app.kubernetes.io/component": "multiorch",
124+
"app.kubernetes.io/part-of": "multigres",
125+
"app.kubernetes.io/managed-by": "multigres-operator",
126+
"multigres.com/cell": "multigres-global-topo",
127+
},
128+
OwnerReferences: []metav1.OwnerReference{
129+
{
130+
APIVersion: "multigres.com/v1alpha1",
131+
Kind: "MultiOrch",
132+
Name: "test-multiorch",
133+
UID: "test-uid",
134+
Controller: boolPtr(true),
135+
BlockOwnerDeletion: boolPtr(true),
136+
},
137+
},
138+
},
139+
Spec: appsv1.DeploymentSpec{
140+
Replicas: int32Ptr(3),
141+
Selector: &metav1.LabelSelector{
142+
MatchLabels: map[string]string{
143+
"app.kubernetes.io/name": "multigres",
144+
"app.kubernetes.io/instance": "test-multiorch",
145+
"app.kubernetes.io/component": "multiorch",
146+
"app.kubernetes.io/part-of": "multigres",
147+
"app.kubernetes.io/managed-by": "multigres-operator",
148+
"multigres.com/cell": "multigres-global-topo",
149+
},
150+
},
151+
Template: corev1.PodTemplateSpec{
152+
ObjectMeta: metav1.ObjectMeta{
153+
Labels: map[string]string{
154+
"app.kubernetes.io/name": "multigres",
155+
"app.kubernetes.io/instance": "test-multiorch",
156+
"app.kubernetes.io/component": "multiorch",
157+
"app.kubernetes.io/part-of": "multigres",
158+
"app.kubernetes.io/managed-by": "multigres-operator",
159+
"multigres.com/cell": "multigres-global-topo",
160+
},
161+
},
162+
Spec: corev1.PodSpec{
163+
Containers: []corev1.Container{
164+
{
165+
Name: "multiorch",
166+
Image: "custom/multiorch:v2.0.0",
167+
Resources: corev1.ResourceRequirements{},
168+
Ports: buildContainerPorts(&multigresv1alpha1.MultiOrch{}),
169+
},
170+
},
171+
},
172+
},
173+
},
174+
},
175+
},
176+
"scheme with incorrect type - should error": {
177+
multiorch: &multigresv1alpha1.MultiOrch{
178+
ObjectMeta: metav1.ObjectMeta{
179+
Name: "test-multiorch",
180+
Namespace: "default",
181+
},
182+
Spec: multigresv1alpha1.MultiOrchSpec{},
183+
},
184+
scheme: runtime.NewScheme(), // empty scheme with incorrect type
185+
wantErr: true,
186+
},
187+
}
188+
189+
for name, tc := range tests {
190+
t.Run(name, func(t *testing.T) {
191+
got, err := BuildDeployment(tc.multiorch, tc.scheme)
192+
193+
if (err != nil) != tc.wantErr {
194+
t.Errorf("BuildDeployment() error = %v, wantErr %v", err, tc.wantErr)
195+
return
196+
}
197+
198+
if tc.wantErr {
199+
return
200+
}
201+
202+
if diff := cmp.Diff(tc.want, got); diff != "" {
203+
t.Errorf("BuildDeployment() mismatch (-want +got):\n%s", diff)
204+
}
205+
})
206+
}
207+
}

0 commit comments

Comments
 (0)