Skip to content

Commit 83f342d

Browse files
committed
operator: add more tests for reconciler
Signed-off-by: Tuomas Katila <[email protected]>
1 parent fa72af2 commit 83f342d

File tree

1 file changed

+351
-0
lines changed

1 file changed

+351
-0
lines changed

pkg/controllers/reconciler_test.go

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

0 commit comments

Comments
 (0)