Skip to content

Commit 9e55251

Browse files
committed
operator: add more tests for reconciler
Signed-off-by: Tuomas Katila <[email protected]>
1 parent 281ca08 commit 9e55251

File tree

1 file changed

+359
-0
lines changed

1 file changed

+359
-0
lines changed

pkg/controllers/reconciler_test.go

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,367 @@ import (
2020
"testing"
2121

2222
v1 "k8s.io/api/core/v1"
23+
24+
"errors"
25+
26+
apps "k8s.io/api/apps/v1"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/apimachinery/pkg/runtime/schema"
30+
"k8s.io/apimachinery/pkg/types"
31+
ctrl "sigs.k8s.io/controller-runtime"
32+
"sigs.k8s.io/controller-runtime/pkg/client"
2333
)
2434

35+
type mockPlugin struct {
36+
client.Object
37+
}
38+
39+
func (m *mockPlugin) GetNamespace() string {
40+
return "default"
41+
}
42+
43+
func (m *mockPlugin) GetObjectKind() schema.ObjectKind {
44+
return &metav1.TypeMeta{
45+
Kind: "MockPlugin",
46+
APIVersion: "v1",
47+
}
48+
}
49+
50+
func (m *mockPlugin) GetName() string {
51+
return "mock"
52+
}
53+
54+
func (m *mockPlugin) GetUID() types.UID {
55+
return "mock-uid"
56+
}
57+
58+
type mockController struct {
59+
statusErr error
60+
updated bool
61+
upgrade bool
62+
}
63+
64+
func (m *mockController) CreateEmptyObject() client.Object {
65+
return &mockPlugin{}
66+
}
67+
68+
func (m *mockController) NewDaemonSet(rawObj client.Object) *apps.DaemonSet {
69+
return &apps.DaemonSet{
70+
ObjectMeta: metav1.ObjectMeta{
71+
Name: "mock-ds",
72+
Namespace: "default",
73+
},
74+
Spec: apps.DaemonSetSpec{
75+
Selector: &metav1.LabelSelector{
76+
MatchLabels: map[string]string{"app": "mock"},
77+
},
78+
Template: v1.PodTemplateSpec{
79+
ObjectMeta: metav1.ObjectMeta{
80+
Labels: map[string]string{"app": "mock"},
81+
},
82+
Spec: v1.PodSpec{
83+
Containers: []v1.Container{
84+
{
85+
Name: "mock",
86+
Image: "intel/intel-mock-plugin:latest",
87+
},
88+
},
89+
},
90+
},
91+
},
92+
}
93+
}
94+
95+
func (m *mockController) UpdateDaemonSet(rawObj client.Object, ds *apps.DaemonSet) (updated bool) {
96+
return m.updated
97+
}
98+
99+
func (m *mockController) UpdateStatus(rawObj client.Object, ds *apps.DaemonSet, messages []string) (updated bool, err error) {
100+
if m.statusErr != nil {
101+
return false, m.statusErr
102+
}
103+
104+
return true, nil
105+
}
106+
107+
func (m *mockController) Upgrade(ctx context.Context, obj client.Object) bool {
108+
return m.upgrade
109+
}
110+
111+
type fakeStatusWriter struct{}
112+
113+
func (f *fakeStatusWriter) Create(ctx context.Context, obj client.Object, obj2 client.Object, opts ...client.SubResourceCreateOption) error {
114+
return nil
115+
}
116+
func (f *fakeStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
117+
return nil
118+
}
119+
func (f *fakeStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error {
120+
return nil
121+
}
122+
func (f *fakeStatusWriter) Status() client.StatusWriter {
123+
return f
124+
}
125+
126+
type fakeClient struct {
127+
client.StatusWriter
128+
client.Client
129+
getErr error
130+
listErr error
131+
updateErr error
132+
createErr error
133+
statusErr error
134+
ds []*apps.DaemonSet
135+
pods []*v1.Pod
136+
createCalled bool
137+
}
138+
139+
func (f *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
140+
if f.getErr != nil {
141+
return f.getErr
142+
}
143+
144+
return nil
145+
}
146+
func (f *fakeClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
147+
if f.listErr != nil {
148+
return f.listErr
149+
}
150+
switch l := list.(type) {
151+
case *apps.DaemonSetList:
152+
l.Items = []apps.DaemonSet{}
153+
154+
for _, ds := range f.ds {
155+
l.Items = append(l.Items, *ds)
156+
}
157+
case *v1.PodList:
158+
l.Items = []v1.Pod{}
159+
160+
for _, pod := range f.pods {
161+
l.Items = append(l.Items, *pod)
162+
}
163+
}
164+
return nil
165+
}
166+
func (f *fakeClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
167+
return f.updateErr
168+
}
169+
func (f *fakeClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
170+
f.createCalled = true
171+
return f.createErr
172+
}
173+
func (f *fakeClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
174+
return nil
175+
}
176+
func (f *fakeClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
177+
return nil
178+
}
179+
func (f *fakeClient) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
180+
return f.statusErr
181+
}
182+
func (f *fakeClient) Status() client.StatusWriter {
183+
return f.StatusWriter
184+
}
185+
func (f *fakeClient) Scheme() *runtime.Scheme {
186+
s := runtime.NewScheme()
187+
s.AddKnownTypeWithName(schema.GroupVersionKind{
188+
Group: "deviceplugin.intel.com",
189+
Version: "v1",
190+
Kind: "MockPlugin",
191+
}, &mockPlugin{})
192+
193+
return s
194+
}
195+
196+
func fillDaemonSets() []*apps.DaemonSet {
197+
return []*apps.DaemonSet{
198+
{
199+
ObjectMeta: metav1.ObjectMeta{
200+
Name: "mock-ds",
201+
Namespace: "default",
202+
},
203+
Spec: apps.DaemonSetSpec{
204+
Selector: &metav1.LabelSelector{
205+
MatchLabels: map[string]string{"app": "mock"},
206+
},
207+
Template: v1.PodTemplateSpec{
208+
ObjectMeta: metav1.ObjectMeta{
209+
Labels: map[string]string{"app": "mock"},
210+
},
211+
Spec: v1.PodSpec{
212+
Containers: []v1.Container{
213+
{
214+
Name: "mock",
215+
Image: "intel/intel-mock-plugin:latest",
216+
},
217+
},
218+
},
219+
},
220+
},
221+
},
222+
}
223+
}
224+
225+
func fillPods() []*v1.Pod {
226+
return []*v1.Pod{
227+
{
228+
ObjectMeta: metav1.ObjectMeta{
229+
Name: "mock-pod",
230+
Namespace: "default",
231+
OwnerReferences: []metav1.OwnerReference{
232+
{
233+
APIVersion: "apps/v1",
234+
Kind: "DaemonSet",
235+
Name: "mock-ds",
236+
UID: "mock-uid",
237+
},
238+
},
239+
},
240+
Spec: v1.PodSpec{
241+
NodeName: "node1",
242+
Containers: []v1.Container{
243+
{
244+
Name: "mock",
245+
Image: "intel/intel-mock-plugin:latest",
246+
},
247+
},
248+
},
249+
},
250+
}
251+
}
252+
253+
func TestReconciler_Reconcile_CreateDaemonSet(t *testing.T) {
254+
controller := &mockController{}
255+
c := &fakeClient{
256+
StatusWriter: &fakeStatusWriter{},
257+
ds: []*apps.DaemonSet{},
258+
}
259+
r := &reconciler{
260+
controller: controller,
261+
Client: c,
262+
scheme: c.Scheme(),
263+
pluginKind: "MockPlugin",
264+
ownerKey: "owner",
265+
}
266+
req := ctrl.Request{NamespacedName: types.NamespacedName{Name: "mock", Namespace: "default"}}
267+
268+
res, err := r.Reconcile(context.Background(), req)
269+
if err != nil {
270+
t.Errorf("expected no error, got %v", err)
271+
}
272+
if res != (ctrl.Result{}) {
273+
t.Errorf("expected empty result, got %v", res)
274+
}
275+
if c.createCalled == false {
276+
t.Error("expected create to be called, but it was not")
277+
}
278+
}
279+
280+
func TestReconciler_Reconcile_UpdateDaemonSetAndStatus(t *testing.T) {
281+
controller := &mockController{
282+
updated: true,
283+
upgrade: false,
284+
}
285+
c := &fakeClient{
286+
StatusWriter: &fakeStatusWriter{},
287+
ds: fillDaemonSets(),
288+
pods: fillPods(),
289+
}
290+
r := &reconciler{
291+
controller: controller,
292+
Client: c,
293+
scheme: c.Scheme(),
294+
pluginKind: "MockPlugin",
295+
ownerKey: "owner",
296+
}
297+
req := ctrl.Request{NamespacedName: client.ObjectKey{Name: "mock", Namespace: "default"}}
298+
299+
res, err := r.Reconcile(context.Background(), req)
300+
if err != nil {
301+
t.Errorf("expected no error, got %v", err)
302+
}
303+
if res != (ctrl.Result{}) {
304+
t.Errorf("expected empty result, got %v", res)
305+
}
306+
}
307+
308+
type getError struct {
309+
error
310+
}
311+
type listError struct {
312+
error
313+
}
314+
type updatestatusError struct {
315+
error
316+
}
317+
318+
func TestReconciler_Reconcile_GetError(t *testing.T) {
319+
controller := &mockController{}
320+
c := &fakeClient{
321+
getErr: getError{},
322+
StatusWriter: &fakeStatusWriter{},
323+
}
324+
r := &reconciler{
325+
controller: controller,
326+
Client: c,
327+
scheme: c.Scheme(),
328+
pluginKind: "MockPlugin",
329+
ownerKey: "owner",
330+
}
331+
req := ctrl.Request{NamespacedName: client.ObjectKey{Name: "mock", Namespace: "default"}}
332+
333+
_, err := r.Reconcile(context.Background(), req)
334+
if err == nil || errors.Is(err, c.getErr) == false {
335+
t.Errorf("expected get error, got %v", err)
336+
}
337+
}
338+
339+
func TestReconciler_Reconcile_ListError(t *testing.T) {
340+
controller := &mockController{}
341+
c := &fakeClient{
342+
listErr: listError{},
343+
StatusWriter: &fakeStatusWriter{},
344+
}
345+
r := &reconciler{
346+
controller: controller,
347+
Client: c,
348+
scheme: c.Scheme(),
349+
pluginKind: "MockPlugin",
350+
ownerKey: "owner",
351+
}
352+
req := ctrl.Request{NamespacedName: client.ObjectKey{Name: "mock", Namespace: "default"}}
353+
354+
_, err := r.Reconcile(context.Background(), req)
355+
if err == nil || errors.Is(err, c.listErr) == false {
356+
t.Errorf("expected list error, got %v", err)
357+
}
358+
}
359+
360+
func TestReconciler_Reconcile_UpdateStatusError(t *testing.T) {
361+
controller := &mockController{
362+
statusErr: updatestatusError{},
363+
}
364+
c := &fakeClient{
365+
StatusWriter: &fakeStatusWriter{},
366+
ds: fillDaemonSets(),
367+
pods: fillPods(),
368+
}
369+
r := &reconciler{
370+
controller: controller,
371+
Client: c,
372+
scheme: c.Scheme(),
373+
pluginKind: "MockPlugin",
374+
ownerKey: "owner",
375+
}
376+
req := ctrl.Request{NamespacedName: client.ObjectKey{Name: "mock", Namespace: "default"}}
377+
378+
_, err := r.Reconcile(context.Background(), req)
379+
if err == nil || errors.Is(err, controller.statusErr) == false {
380+
t.Errorf("expected status update error, got %v", err)
381+
}
382+
}
383+
25384
func TestUpgrade(test *testing.T) {
26385
image := "intel/intel-dsa-plugin"
27386
initimage := "intel/intel-idxd-config-initcontainer"

0 commit comments

Comments
 (0)