Skip to content

Commit d109b26

Browse files
committed
adding unit tests for operator deployable
1 parent a8ce23c commit d109b26

File tree

2 files changed

+243
-76
lines changed

2 files changed

+243
-76
lines changed

pkg/operator/operands/deployable/deployable.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,15 @@ func (d *DeployableOperands) Deploy(
6666
Controller: ptr.To(true),
6767
}
6868

69-
if createObjectsInCluster(ctx, runtimeClient, reconcilerAsOwnerReference, objectsToCreate) != nil {
69+
if err := createObjectsInCluster(ctx, runtimeClient, reconcilerAsOwnerReference, objectsToCreate); err != nil {
7070
return err
7171
}
7272

73-
if deleteObjectsInCluster(ctx, runtimeClient, objectsToDelete) != nil {
73+
if err := deleteObjectsInCluster(ctx, runtimeClient, objectsToDelete); err != nil {
7474
return err
7575
}
7676

77-
if updateObjectsInCluster(ctx, runtimeClient, reconcilerAsOwnerReference, objectsToUpdate) != nil {
77+
if err := updateObjectsInCluster(ctx, runtimeClient, reconcilerAsOwnerReference, objectsToUpdate); err != nil {
7878
return err
7979
}
8080
return nil

pkg/operator/operands/deployable/deployable_test.go

Lines changed: 240 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package deployable
55

66
import (
77
"context"
8+
"errors"
89
"testing"
910

1011
kaiv1 "github.com/NVIDIA/KAI-scheduler/pkg/apis/kai/v1"
@@ -24,6 +25,7 @@ import (
2425
"k8s.io/utils/ptr"
2526
"sigs.k8s.io/controller-runtime/pkg/client"
2627
"sigs.k8s.io/controller-runtime/pkg/client/fake"
28+
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
2729
)
2830

2931
func TestDeployable(t *testing.T) {
@@ -62,92 +64,154 @@ var _ = Describe("Deployable", func() {
6264
deployable *DeployableOperands
6365
fakeClient client.Client
6466
)
65-
BeforeEach(func() {
66-
operand := &fakeOperand{}
67+
Context("object creation successfull", func() {
6768

68-
deployable = New([]operands.Operand{operand}, known_types.KAIConfigRegisteredCollectible)
69-
fakeClient = getFakeClient(fakeClientBuilder, known_types.KAIConfigRegisteredCollectible)
70-
})
71-
It("should deploy operands desired state", func() {
72-
Expect(deployable.Deploy(context.TODO(), fakeClient, kaiConfig, kaiConfig)).To(Succeed())
73-
podsList := &v1.PodList{}
74-
err := fakeClient.List(context.TODO(), podsList)
75-
Expect(err).ToNot(HaveOccurred())
76-
Expect(len(podsList.Items)).To(Equal(1))
69+
BeforeEach(func() {
70+
operand := &fakeOperand{}
7771

78-
configMapList := &v1.ConfigMapList{}
79-
err = fakeClient.List(context.TODO(), configMapList)
80-
Expect(err).ToNot(HaveOccurred())
81-
Expect(len(configMapList.Items)).To(Equal(1))
82-
})
83-
It("should not deploy operands on custom field inheritor", func() {
84-
desiredWithAnnotation := &v1.ConfigMap{
85-
TypeMeta: metav1.TypeMeta{
86-
Kind: "ConfigMap",
87-
APIVersion: "v1",
88-
},
89-
ObjectMeta: metav1.ObjectMeta{
90-
Name: "foo",
91-
Namespace: "bar",
92-
Annotations: map[string]string{"A": "a"},
93-
OwnerReferences: []metav1.OwnerReference{
94-
{
95-
APIVersion: kaiConfig.GetObjectKind().GroupVersionKind().GroupVersion().String(),
96-
Kind: kaiConfig.GetObjectKind().GroupVersionKind().Kind,
97-
Name: kaiConfig.GetName(),
98-
UID: kaiConfig.GetUID(),
99-
Controller: ptr.To(true),
72+
deployable = New([]operands.Operand{operand}, known_types.KAIConfigRegisteredCollectible)
73+
fakeClient = getFakeClient(fakeClientBuilder, known_types.KAIConfigRegisteredCollectible)
74+
})
75+
It("should deploy operands desired state", func() {
76+
Expect(deployable.Deploy(context.TODO(), fakeClient, kaiConfig, kaiConfig)).To(Succeed())
77+
podsList := &v1.PodList{}
78+
err := fakeClient.List(context.TODO(), podsList)
79+
Expect(err).ToNot(HaveOccurred())
80+
Expect(len(podsList.Items)).To(Equal(1))
81+
82+
configMapList := &v1.ConfigMapList{}
83+
err = fakeClient.List(context.TODO(), configMapList)
84+
Expect(err).ToNot(HaveOccurred())
85+
Expect(len(configMapList.Items)).To(Equal(1))
86+
})
87+
It("should not deploy operands on custom field inheritor", func() {
88+
desiredWithAnnotation := &v1.ConfigMap{
89+
TypeMeta: metav1.TypeMeta{
90+
Kind: "ConfigMap",
91+
APIVersion: "v1",
92+
},
93+
ObjectMeta: metav1.ObjectMeta{
94+
Name: "foo",
95+
Namespace: "bar",
96+
Annotations: map[string]string{"A": "a"},
97+
OwnerReferences: []metav1.OwnerReference{
98+
{
99+
APIVersion: kaiConfig.GetObjectKind().GroupVersionKind().GroupVersion().String(),
100+
Kind: kaiConfig.GetObjectKind().GroupVersionKind().Kind,
101+
Name: kaiConfig.GetName(),
102+
UID: kaiConfig.GetUID(),
103+
Controller: ptr.To(true),
104+
},
100105
},
101106
},
102-
},
103-
}
104-
Expect(fakeClient.Create(context.TODO(), desiredWithAnnotation)).To(Succeed())
105-
106-
deployable.RegisterFieldsInheritFromClusterObjects(&v1.ConfigMap{}, func(current, desired client.Object) {
107-
currentCm := current.(*v1.ConfigMap)
108-
desiredCm := desired.(*v1.ConfigMap)
109-
if desiredCm.Annotations == nil {
110-
desiredCm.Annotations = map[string]string{}
111107
}
112-
maps.Copy(desiredCm.Annotations, currentCm.Annotations)
108+
Expect(fakeClient.Create(context.TODO(), desiredWithAnnotation)).To(Succeed())
109+
110+
deployable.RegisterFieldsInheritFromClusterObjects(&v1.ConfigMap{}, func(current, desired client.Object) {
111+
currentCm := current.(*v1.ConfigMap)
112+
desiredCm := desired.(*v1.ConfigMap)
113+
if desiredCm.Annotations == nil {
114+
desiredCm.Annotations = map[string]string{}
115+
}
116+
maps.Copy(desiredCm.Annotations, currentCm.Annotations)
117+
})
118+
Expect(deployable.Deploy(context.TODO(), fakeClient, kaiConfig, kaiConfig)).To(Succeed())
119+
120+
cmList := &v1.ConfigMapList{}
121+
err := fakeClient.List(context.TODO(), cmList)
122+
Expect(err).ToNot(HaveOccurred())
123+
Expect(len(cmList.Items)).To(Equal(1))
124+
Expect(len(cmList.Items[0].Annotations)).To(Equal(1))
113125
})
114-
Expect(deployable.Deploy(context.TODO(), fakeClient, kaiConfig, kaiConfig)).To(Succeed())
115126

116-
cmList := &v1.ConfigMapList{}
117-
err := fakeClient.List(context.TODO(), cmList)
118-
Expect(err).ToNot(HaveOccurred())
119-
Expect(len(cmList.Items)).To(Equal(1))
120-
Expect(len(cmList.Items[0].Annotations)).To(Equal(1))
127+
It("should delete other resources", func() {
128+
otherConfigMap := &v1.ConfigMap{
129+
ObjectMeta: metav1.ObjectMeta{
130+
Name: "foo2",
131+
Namespace: "bar2",
132+
OwnerReferences: []metav1.OwnerReference{
133+
{
134+
APIVersion: kaiConfig.GetObjectKind().GroupVersionKind().GroupVersion().String(),
135+
Kind: kaiConfig.GetObjectKind().GroupVersionKind().Kind,
136+
Name: kaiConfig.GetName(),
137+
UID: kaiConfig.GetUID(),
138+
Controller: ptr.To(true),
139+
},
140+
},
141+
},
142+
}
143+
Expect(fakeClient.Create(context.TODO(), otherConfigMap)).To(Succeed())
144+
145+
Expect(deployable.Deploy(context.TODO(), fakeClient, kaiConfig, kaiConfig)).To(Succeed())
146+
147+
configMapList := &v1.ConfigMapList{}
148+
err := fakeClient.List(context.TODO(), configMapList)
149+
Expect(err).ToNot(HaveOccurred())
150+
Expect(len(configMapList.Items)).To(Equal(1))
151+
152+
for _, item := range configMapList.Items {
153+
Expect(item.Name).ToNot(Equal(otherConfigMap.Name))
154+
}
155+
})
121156
})
122157

123-
It("should delete other resources", func() {
124-
otherConfigMap := &v1.ConfigMap{
125-
ObjectMeta: metav1.ObjectMeta{
126-
Name: "foo2",
127-
Namespace: "bar2",
128-
OwnerReferences: []metav1.OwnerReference{
129-
{
130-
APIVersion: kaiConfig.GetObjectKind().GroupVersionKind().GroupVersion().String(),
131-
Kind: kaiConfig.GetObjectKind().GroupVersionKind().Kind,
132-
Name: kaiConfig.GetName(),
133-
UID: kaiConfig.GetUID(),
134-
Controller: ptr.To(true),
158+
Context("Object creation fails", func() {
159+
var (
160+
fakeClient client.Client
161+
createCalls int
162+
updateCalls int
163+
createError error
164+
updateError error
165+
)
166+
BeforeEach(func() {
167+
createCalls = 0
168+
updateCalls = 0
169+
createError = nil
170+
updateError = nil
171+
172+
operand := &fakeOperand{}
173+
deployable = New([]operands.Operand{operand}, known_types.KAIConfigRegisteredCollectible)
174+
175+
fakeClient = getFakeClient(fakeClientBuilder.
176+
WithInterceptorFuncs(interceptor.Funcs{
177+
Create: func(ctx context.Context, client client.WithWatch, obj client.Object, opts ...client.CreateOption) error {
178+
createCalls++
179+
return createError
135180
},
136-
},
137-
},
138-
}
139-
Expect(fakeClient.Create(context.TODO(), otherConfigMap)).To(Succeed())
181+
Update: func(ctx context.Context, client client.WithWatch, obj client.Object, opts ...client.UpdateOption) error {
182+
updateCalls++
183+
return updateError
184+
},
185+
}), known_types.KAIConfigRegisteredCollectible)
186+
})
140187

141-
Expect(deployable.Deploy(context.TODO(), fakeClient, kaiConfig, kaiConfig)).To(Succeed())
188+
It("should create object successfully", func() {
189+
Expect(deployable.Deploy(context.TODO(), fakeClient, kaiConfig, kaiConfig)).To(Succeed())
190+
Expect(createCalls).To(Equal(2)) // Pod and ConfigMap creates
191+
Expect(updateCalls).To(Equal(0))
192+
})
142193

143-
configMapList := &v1.ConfigMapList{}
144-
err := fakeClient.List(context.TODO(), configMapList)
145-
Expect(err).ToNot(HaveOccurred())
146-
Expect(len(configMapList.Items)).To(Equal(1))
194+
It("should update object if create fails due to existing resource", func() {
195+
createError = errors.New("already exists")
196+
defer func() { createError = nil }()
197+
Expect(deployable.Deploy(context.TODO(), fakeClient, kaiConfig, kaiConfig)).To(Succeed())
198+
Expect(createCalls).To(Equal(2))
199+
Expect(updateCalls).To(Equal(2))
200+
})
147201

148-
for _, item := range configMapList.Items {
149-
Expect(item.Name).ToNot(Equal(otherConfigMap.Name))
150-
}
202+
It("should fail if both create and update fail", func() {
203+
createError = errors.New("already exists")
204+
updateError = errors.New("update failed")
205+
defer func() {
206+
createError = nil
207+
updateError = nil
208+
}()
209+
210+
err := deployable.Deploy(context.TODO(), fakeClient, kaiConfig, kaiConfig)
211+
Expect(err).To(HaveOccurred())
212+
Expect(createCalls).To(Equal(1))
213+
Expect(updateCalls).To(Equal(1))
214+
})
151215
})
152216
})
153217

@@ -198,6 +262,64 @@ var _ = Describe("Deployable", func() {
198262
})
199263
})
200264

265+
Describe("Monitor", func() {
266+
var (
267+
deployable *DeployableOperands
268+
fakeClient client.Client
269+
)
270+
BeforeEach(func() {
271+
operand := &fakeOperand{}
272+
deployable = New([]operands.Operand{operand}, known_types.KAIConfigRegisteredCollectible)
273+
fakeClient = getFakeClient(fakeClientBuilder, known_types.KAIConfigRegisteredCollectible)
274+
})
275+
276+
It("should not return error when monitoring", func() {
277+
Expect(deployable.Monitor(context.TODO(), fakeClient, kaiConfig)).To(Succeed())
278+
})
279+
280+
It("should return error if operand's Monitor fails", func() {
281+
errOperand := &fakeOperandWithMonitorError{monitorErr: errors.New("monitor failed")}
282+
deployable = New([]operands.Operand{errOperand}, known_types.KAIConfigRegisteredCollectible)
283+
Expect(deployable.Monitor(context.TODO(), fakeClient, kaiConfig)).To(MatchError(ContainSubstring("monitor failed")))
284+
Expect(errOperand.monitorErr).To(HaveOccurred())
285+
})
286+
})
287+
288+
Describe("HasMissingDependencies", func() {
289+
var (
290+
deployable *DeployableOperands
291+
fakeClient client.Reader
292+
)
293+
BeforeEach(func() {
294+
operand := &fakeOperand{}
295+
deployable = New([]operands.Operand{operand}, known_types.KAIConfigRegisteredCollectible)
296+
fakeClient = getFakeClient(fakeClientBuilder, known_types.KAIConfigRegisteredCollectible)
297+
})
298+
299+
It("should return no missing dependencies if all operands report none", func() {
300+
missing, err := deployable.HasMissingDependencies(context.TODO(), fakeClient, kaiConfig)
301+
Expect(missing).To(BeEmpty())
302+
Expect(err).ToNot(HaveOccurred())
303+
})
304+
305+
It("should aggregate missing dependencies from operands", func() {
306+
operand1 := &fakeOperandWithDeps{missingDeps: "dep1", name: "operand1"}
307+
operand2 := &fakeOperandWithDeps{missingDeps: "dep2", name: "operand2"}
308+
deployable = New([]operands.Operand{operand1, operand2}, known_types.KAIConfigRegisteredCollectible)
309+
missing, err := deployable.HasMissingDependencies(context.TODO(), fakeClient, kaiConfig)
310+
Expect(missing).To(ContainSubstring("operand1 is missing dep1"))
311+
Expect(missing).To(ContainSubstring("operand2 is missing dep2"))
312+
Expect(err).ToNot(HaveOccurred())
313+
})
314+
315+
It("should handle error from operand's HasMissingDependencies", func() {
316+
errOperand := &fakeOperandWithDeps{hasDepErr: errors.New("dependency check failed"), name: "errOperand"}
317+
deployable = New([]operands.Operand{errOperand}, known_types.KAIConfigRegisteredCollectible)
318+
_, err := deployable.HasMissingDependencies(context.TODO(), fakeClient, kaiConfig)
319+
Expect(err).To(MatchError(ContainSubstring("dependency check failed")))
320+
})
321+
})
322+
201323
Describe("SortObjectByCreationOrder", func() {
202324
var (
203325
orderDefinition []string
@@ -375,3 +497,48 @@ func getFakeClient(builder *fake.ClientBuilder, collectables []*known_types.Coll
375497
}
376498
return builder.Build()
377499
}
500+
501+
type fakeOperandWithMonitorError struct {
502+
monitorErr error
503+
}
504+
505+
func (f *fakeOperandWithMonitorError) Monitor(_ context.Context, _ client.Reader, _ *kaiv1.Config) error {
506+
return f.monitorErr
507+
}
508+
509+
func (f *fakeOperandWithMonitorError) Name() string { return "fakeOperandWithMonitorError" }
510+
func (f *fakeOperandWithMonitorError) DesiredState(_ context.Context, _ client.Reader, _ *kaiv1.Config) ([]client.Object, error) {
511+
return nil, nil
512+
}
513+
func (f *fakeOperandWithMonitorError) IsDeployed(_ context.Context, _ client.Reader) (bool, error) {
514+
return true, nil
515+
}
516+
func (f *fakeOperandWithMonitorError) IsAvailable(_ context.Context, _ client.Reader) (bool, error) {
517+
return true, nil
518+
}
519+
func (f *fakeOperandWithMonitorError) HasMissingDependencies(context.Context, client.Reader, *kaiv1.Config) (string, error) {
520+
return "", nil
521+
}
522+
523+
type fakeOperandWithDeps struct {
524+
missingDeps string
525+
hasDepErr error
526+
name string
527+
}
528+
529+
func (f *fakeOperandWithDeps) HasMissingDependencies(_ context.Context, _ client.Reader, _ *kaiv1.Config) (string, error) {
530+
return f.missingDeps, f.hasDepErr
531+
}
532+
func (f *fakeOperandWithDeps) Name() string { return f.name }
533+
func (f *fakeOperandWithDeps) DesiredState(_ context.Context, _ client.Reader, _ *kaiv1.Config) ([]client.Object, error) {
534+
return nil, nil
535+
}
536+
func (f *fakeOperandWithDeps) IsDeployed(_ context.Context, _ client.Reader) (bool, error) {
537+
return true, nil
538+
}
539+
func (f *fakeOperandWithDeps) IsAvailable(_ context.Context, _ client.Reader) (bool, error) {
540+
return true, nil
541+
}
542+
func (f *fakeOperandWithDeps) Monitor(_ context.Context, _ client.Reader, _ *kaiv1.Config) error {
543+
return nil
544+
}

0 commit comments

Comments
 (0)