Skip to content

Commit 04c055c

Browse files
authored
Merge pull request #80 from sdowell/test-reconcile
test: add coverage for sandbox top level Reconcile
2 parents 8fd811e + f30efcb commit 04c055c

File tree

1 file changed

+267
-10
lines changed

1 file changed

+267
-10
lines changed

controllers/sandbox_controller_test.go

Lines changed: 267 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,39 @@ import (
1818
"errors"
1919
"testing"
2020

21+
"github.com/google/go-cmp/cmp"
22+
"github.com/google/go-cmp/cmp/cmpopts"
2123
"github.com/stretchr/testify/require"
2224
corev1 "k8s.io/api/core/v1"
25+
"k8s.io/apimachinery/pkg/api/resource"
2326
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2427
"k8s.io/apimachinery/pkg/runtime"
2528
"k8s.io/apimachinery/pkg/types"
2629
"k8s.io/utils/ptr"
2730
sandboxv1alpha1 "sigs.k8s.io/agent-sandbox/api/v1alpha1"
31+
ctrl "sigs.k8s.io/controller-runtime"
32+
"sigs.k8s.io/controller-runtime/pkg/client"
2833
"sigs.k8s.io/controller-runtime/pkg/client/fake"
2934
)
3035

36+
func newFakeClient(initialObjs ...runtime.Object) client.WithWatch {
37+
return fake.NewClientBuilder().
38+
WithScheme(Scheme).
39+
WithStatusSubresource(&sandboxv1alpha1.Sandbox{}).
40+
WithRuntimeObjects(initialObjs...).
41+
Build()
42+
}
43+
44+
func sandboxControllerRef(name string) metav1.OwnerReference {
45+
return metav1.OwnerReference{
46+
APIVersion: "agents.x-k8s.io/v1alpha1",
47+
Kind: "Sandbox",
48+
Name: name,
49+
Controller: ptr.To(true),
50+
BlockOwnerDeletion: ptr.To(true),
51+
}
52+
}
53+
3154
func TestComputeReadyCondition(t *testing.T) {
3255
r := &SandboxReconciler{}
3356

@@ -154,6 +177,248 @@ func TestComputeReadyCondition(t *testing.T) {
154177
}
155178
}
156179

180+
func TestReconcile(t *testing.T) {
181+
sandboxName := "sandbox-name"
182+
sandboxNs := "sandbox-ns"
183+
testCases := []struct {
184+
name string
185+
initialObjs []runtime.Object
186+
sandboxSpec sandboxv1alpha1.SandboxSpec
187+
wantStatus sandboxv1alpha1.SandboxStatus
188+
wantObjs []client.Object
189+
}{
190+
{
191+
name: "minimal sandbox spec with Pod and Service",
192+
// Input sandbox spec
193+
sandboxSpec: sandboxv1alpha1.SandboxSpec{
194+
PodTemplate: sandboxv1alpha1.PodTemplate{
195+
Spec: corev1.PodSpec{
196+
Containers: []corev1.Container{
197+
{
198+
Name: "test-container",
199+
},
200+
},
201+
},
202+
},
203+
},
204+
// Verify Sandbox status
205+
wantStatus: sandboxv1alpha1.SandboxStatus{
206+
Service: sandboxName,
207+
ServiceFQDN: "sandbox-name.sandbox-ns.svc.cluster.local",
208+
Conditions: []metav1.Condition{
209+
{
210+
Type: "Ready",
211+
Status: "False",
212+
ObservedGeneration: 1,
213+
Reason: "DependenciesNotReady",
214+
Message: "Pod exists with phase: ; Service Exists",
215+
},
216+
},
217+
},
218+
wantObjs: []client.Object{
219+
// Verify Pod
220+
&corev1.Pod{
221+
ObjectMeta: metav1.ObjectMeta{
222+
Name: sandboxName,
223+
Namespace: sandboxNs,
224+
ResourceVersion: "1",
225+
Labels: map[string]string{
226+
"agents.x-k8s.io/sandbox-name-hash": "ab179450",
227+
},
228+
OwnerReferences: []metav1.OwnerReference{sandboxControllerRef(sandboxName)},
229+
},
230+
Spec: corev1.PodSpec{
231+
Containers: []corev1.Container{
232+
{
233+
Name: "test-container",
234+
},
235+
},
236+
},
237+
},
238+
// Verify Service
239+
&corev1.Service{
240+
ObjectMeta: metav1.ObjectMeta{
241+
Name: sandboxName,
242+
Namespace: sandboxNs,
243+
ResourceVersion: "1",
244+
Labels: map[string]string{
245+
"agents.x-k8s.io/sandbox-name-hash": "ab179450",
246+
},
247+
OwnerReferences: []metav1.OwnerReference{sandboxControllerRef(sandboxName)},
248+
},
249+
Spec: corev1.ServiceSpec{
250+
Selector: map[string]string{
251+
"agents.x-k8s.io/sandbox-name-hash": "ab179450",
252+
},
253+
ClusterIP: "None",
254+
},
255+
},
256+
},
257+
},
258+
{
259+
name: "sandbox spec with PVC, Pod, and Service",
260+
// Input sandbox spec
261+
sandboxSpec: sandboxv1alpha1.SandboxSpec{
262+
PodTemplate: sandboxv1alpha1.PodTemplate{
263+
Spec: corev1.PodSpec{
264+
Containers: []corev1.Container{
265+
{
266+
Name: "test-container",
267+
},
268+
},
269+
},
270+
ObjectMeta: sandboxv1alpha1.PodMetadata{
271+
Labels: map[string]string{
272+
"custom-label": "label-val",
273+
},
274+
Annotations: map[string]string{
275+
"custom-annotation": "anno-val",
276+
},
277+
},
278+
},
279+
VolumeClaimTemplates: []sandboxv1alpha1.PersistentVolumeClaimTemplate{
280+
{
281+
EmbeddedObjectMetadata: sandboxv1alpha1.EmbeddedObjectMetadata{
282+
Name: "my-pvc",
283+
},
284+
Spec: corev1.PersistentVolumeClaimSpec{
285+
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
286+
Resources: corev1.VolumeResourceRequirements{
287+
Requests: corev1.ResourceList{
288+
"storage": resource.MustParse("10Gi"),
289+
},
290+
},
291+
},
292+
},
293+
},
294+
},
295+
// Verify Sandbox status
296+
wantStatus: sandboxv1alpha1.SandboxStatus{
297+
Service: sandboxName,
298+
ServiceFQDN: "sandbox-name.sandbox-ns.svc.cluster.local",
299+
Conditions: []metav1.Condition{
300+
{
301+
Type: "Ready",
302+
Status: "False",
303+
ObservedGeneration: 1,
304+
Reason: "DependenciesNotReady",
305+
Message: "Pod exists with phase: ; Service Exists",
306+
},
307+
},
308+
},
309+
wantObjs: []client.Object{
310+
// Verify Pod
311+
&corev1.Pod{
312+
ObjectMeta: metav1.ObjectMeta{
313+
Name: sandboxName,
314+
Namespace: sandboxNs,
315+
ResourceVersion: "1",
316+
Labels: map[string]string{
317+
"agents.x-k8s.io/sandbox-name-hash": "ab179450",
318+
"custom-label": "label-val",
319+
},
320+
Annotations: map[string]string{
321+
"custom-annotation": "anno-val",
322+
},
323+
OwnerReferences: []metav1.OwnerReference{sandboxControllerRef(sandboxName)},
324+
},
325+
Spec: corev1.PodSpec{
326+
Containers: []corev1.Container{
327+
{
328+
Name: "test-container",
329+
},
330+
},
331+
Volumes: []corev1.Volume{
332+
{
333+
Name: "my-pvc",
334+
VolumeSource: corev1.VolumeSource{
335+
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
336+
ClaimName: "my-pvc-sandbox-name",
337+
ReadOnly: false,
338+
},
339+
},
340+
},
341+
},
342+
},
343+
},
344+
// Verify Service
345+
&corev1.Service{
346+
ObjectMeta: metav1.ObjectMeta{
347+
Name: sandboxName,
348+
Namespace: sandboxNs,
349+
ResourceVersion: "1",
350+
Labels: map[string]string{
351+
"agents.x-k8s.io/sandbox-name-hash": "ab179450",
352+
},
353+
OwnerReferences: []metav1.OwnerReference{sandboxControllerRef(sandboxName)},
354+
},
355+
Spec: corev1.ServiceSpec{
356+
Selector: map[string]string{
357+
"agents.x-k8s.io/sandbox-name-hash": "ab179450",
358+
},
359+
ClusterIP: "None",
360+
},
361+
},
362+
// Verify PVC
363+
&corev1.PersistentVolumeClaim{
364+
ObjectMeta: metav1.ObjectMeta{
365+
Name: "my-pvc-sandbox-name",
366+
Namespace: sandboxNs,
367+
ResourceVersion: "1",
368+
OwnerReferences: []metav1.OwnerReference{sandboxControllerRef(sandboxName)},
369+
},
370+
Spec: corev1.PersistentVolumeClaimSpec{
371+
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
372+
Resources: corev1.VolumeResourceRequirements{
373+
Requests: corev1.ResourceList{
374+
"storage": resource.MustParse("10Gi"),
375+
},
376+
},
377+
},
378+
},
379+
},
380+
},
381+
}
382+
383+
for _, tc := range testCases {
384+
t.Run(tc.name, func(t *testing.T) {
385+
sb := &sandboxv1alpha1.Sandbox{}
386+
sb.Name = sandboxName
387+
sb.Namespace = sandboxNs
388+
sb.Generation = 1
389+
sb.Spec = tc.sandboxSpec
390+
r := SandboxReconciler{
391+
Client: newFakeClient(append(tc.initialObjs, sb)...),
392+
Scheme: Scheme,
393+
}
394+
395+
_, err := r.Reconcile(t.Context(), ctrl.Request{
396+
NamespacedName: types.NamespacedName{
397+
Name: sandboxName,
398+
Namespace: sandboxNs,
399+
},
400+
})
401+
require.NoError(t, err)
402+
// Validate Sandbox status
403+
liveSandbox := &sandboxv1alpha1.Sandbox{}
404+
require.NoError(t, r.Get(t.Context(), types.NamespacedName{Name: sandboxName, Namespace: sandboxNs}, liveSandbox))
405+
opts := []cmp.Option{
406+
cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime"),
407+
}
408+
if diff := cmp.Diff(tc.wantStatus, liveSandbox.Status, opts...); diff != "" {
409+
t.Fatalf("unexpected sandbox status (-want,+got):\n%s", diff)
410+
}
411+
// Validate the other objects from the "cluster" (fake client)
412+
for _, obj := range tc.wantObjs {
413+
liveObj := obj.DeepCopyObject().(client.Object)
414+
err = r.Get(t.Context(), types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}, liveObj)
415+
require.NoError(t, err)
416+
require.Equal(t, obj, liveObj)
417+
}
418+
})
419+
}
420+
}
421+
157422
func TestReconcilePod(t *testing.T) {
158423
sandboxName := "sandbox-name"
159424
sandboxNs := "sandbox-ns"
@@ -238,15 +503,7 @@ func TestReconcilePod(t *testing.T) {
238503
Annotations: map[string]string{
239504
"custom-annotation": "anno-val",
240505
},
241-
OwnerReferences: []metav1.OwnerReference{
242-
{
243-
APIVersion: "agents.x-k8s.io/v1alpha1",
244-
Kind: "Sandbox",
245-
Name: sandboxName,
246-
Controller: ptr.To(true),
247-
BlockOwnerDeletion: ptr.To(true),
248-
},
249-
},
506+
OwnerReferences: []metav1.OwnerReference{sandboxControllerRef(sandboxName)},
250507
},
251508
Spec: corev1.PodSpec{
252509
Containers: []corev1.Container{
@@ -262,7 +519,7 @@ func TestReconcilePod(t *testing.T) {
262519
for _, tc := range testCases {
263520
t.Run(tc.name, func(t *testing.T) {
264521
r := SandboxReconciler{
265-
Client: fake.NewFakeClient(tc.initialObjs...),
522+
Client: newFakeClient(tc.initialObjs...),
266523
Scheme: Scheme,
267524
}
268525

0 commit comments

Comments
 (0)