@@ -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+
3154func 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+
157422func 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