Skip to content

Commit 56abeb9

Browse files
fristoniojoestringer
authored andcommitted
cni: add unit tests for deletion fallback client
Signed-off-by: Deepesh Pathak <deepesh.pathak@isovalent.com>
1 parent 3feec70 commit 56abeb9

1 file changed

Lines changed: 264 additions & 0 deletions

File tree

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright Authors of Cilium
3+
4+
package lib
5+
6+
import (
7+
"context"
8+
"crypto/sha256"
9+
"errors"
10+
"fmt"
11+
"path"
12+
"testing"
13+
"time"
14+
15+
"github.com/cilium/hive/hivetest"
16+
"github.com/stretchr/testify/require"
17+
18+
"github.com/cilium/cilium/api/v1/client/endpoint"
19+
"github.com/cilium/cilium/api/v1/models"
20+
"github.com/cilium/cilium/pkg/lock/lockfile"
21+
)
22+
23+
type errorCase int
24+
25+
const (
26+
errorCaseNever errorCase = iota
27+
errorCaseOnce
28+
errorCaseAlways
29+
)
30+
31+
type errorMock struct {
32+
errorCase errorCase
33+
err error
34+
35+
callCount int
36+
}
37+
38+
func (e *errorMock) call() error {
39+
e.callCount++
40+
41+
switch e.errorCase {
42+
case errorCaseNever:
43+
return nil
44+
case errorCaseOnce:
45+
if e.callCount == 1 {
46+
return e.err
47+
}
48+
return nil
49+
case errorCaseAlways:
50+
return e.err
51+
default:
52+
return nil
53+
}
54+
}
55+
56+
type fakeCiliumClient struct {
57+
errorMock
58+
}
59+
60+
func (f *fakeCiliumClient) EndpointDeleteMany(_ *models.EndpointBatchDeleteRequest) error {
61+
return f.call()
62+
}
63+
64+
type fakeCiliumClientCreator struct {
65+
errorMock
66+
67+
clientErrorMock errorMock
68+
}
69+
70+
func (f *fakeCiliumClientCreator) ToString() string {
71+
errMockStrs := []string{"Never", "Once", "Always"}
72+
73+
return fmt.Sprintf("NewClient: %s, EndpointDelete: %s",
74+
errMockStrs[f.errorMock.errorCase],
75+
errMockStrs[f.clientErrorMock.errorCase])
76+
}
77+
78+
func (f *fakeCiliumClientCreator) newClient(_ time.Duration) (ciliumClient, error) {
79+
err := f.call()
80+
if err != nil {
81+
return nil, err
82+
}
83+
84+
return &fakeCiliumClient{f.clientErrorMock}, nil
85+
}
86+
87+
func TestDeletionFallbackClient(t *testing.T) {
88+
logger := hivetest.Logger(t)
89+
90+
newDeletionClient := func(newClientFn newCiliumClientFn, testDir string) DeletionFallbackClient {
91+
deleteQueueLockfile := path.Join(testDir, "lockfile")
92+
return DeletionFallbackClient{
93+
logger: logger,
94+
95+
deleteQueueDir: testDir,
96+
deleteQueueLockfile: deleteQueueLockfile,
97+
98+
newCiliumClientFn: newClientFn,
99+
}
100+
}
101+
102+
newClientErr := errors.New("error creating cilium client")
103+
newClientErrorNever := errorMock{
104+
errorCase: errorCaseNever,
105+
}
106+
newClientErrorOnce := errorMock{
107+
errorCase: errorCaseOnce,
108+
err: newClientErr,
109+
}
110+
newClientErrorAlways := errorMock{
111+
errorCase: errorCaseAlways,
112+
err: newClientErr,
113+
}
114+
115+
deletionClientErr := &endpoint.DeleteEndpointServiceUnavailable{}
116+
deletionClientErrorNever := errorMock{
117+
errorCase: errorCaseNever,
118+
}
119+
deletionClientErrorOnce := errorMock{
120+
errorCase: errorCaseOnce,
121+
err: deletionClientErr,
122+
}
123+
deletionClientErrorAlways := errorMock{
124+
errorCase: errorCaseAlways,
125+
err: deletionClientErr,
126+
}
127+
128+
tt := []struct {
129+
newClientCreator fakeCiliumClientCreator
130+
shouldQueueDeletion bool
131+
}{
132+
{
133+
newClientCreator: fakeCiliumClientCreator{
134+
errorMock: newClientErrorNever,
135+
clientErrorMock: deletionClientErrorNever,
136+
},
137+
shouldQueueDeletion: false,
138+
},
139+
{
140+
newClientCreator: fakeCiliumClientCreator{
141+
errorMock: newClientErrorNever,
142+
clientErrorMock: deletionClientErrorOnce,
143+
},
144+
shouldQueueDeletion: false,
145+
},
146+
{
147+
newClientCreator: fakeCiliumClientCreator{
148+
errorMock: newClientErrorNever,
149+
clientErrorMock: deletionClientErrorAlways,
150+
},
151+
shouldQueueDeletion: true,
152+
},
153+
{
154+
newClientCreator: fakeCiliumClientCreator{
155+
errorMock: newClientErrorOnce,
156+
clientErrorMock: deletionClientErrorNever,
157+
},
158+
shouldQueueDeletion: false,
159+
},
160+
{
161+
newClientCreator: fakeCiliumClientCreator{
162+
errorMock: newClientErrorOnce,
163+
clientErrorMock: deletionClientErrorOnce,
164+
},
165+
shouldQueueDeletion: true,
166+
},
167+
{
168+
newClientCreator: fakeCiliumClientCreator{
169+
errorMock: newClientErrorOnce,
170+
clientErrorMock: deletionClientErrorAlways,
171+
},
172+
shouldQueueDeletion: true,
173+
},
174+
{
175+
newClientCreator: fakeCiliumClientCreator{
176+
errorMock: newClientErrorAlways,
177+
clientErrorMock: deletionClientErrorNever,
178+
},
179+
shouldQueueDeletion: true,
180+
},
181+
}
182+
183+
deleteReq := &models.EndpointBatchDeleteRequest{
184+
ContainerID: "test-container-id",
185+
}
186+
187+
contents, err := deleteReq.MarshalBinary()
188+
require.NoError(t, err)
189+
190+
h := sha256.New()
191+
h.Write(contents)
192+
deleteQueueFilename := fmt.Sprintf("%x.delete", h.Sum(nil))
193+
194+
for _, tc := range tt {
195+
testName := tc.newClientCreator.ToString()
196+
197+
t.Run(testName, func(t *testing.T) {
198+
testDir := t.TempDir()
199+
200+
dc := newDeletionClient(tc.newClientCreator.newClient, testDir)
201+
202+
err = dc.EndpointDeleteMany(deleteReq)
203+
require.NoError(t, err)
204+
205+
deleteQueueFile := path.Join(testDir, deleteQueueFilename)
206+
if tc.shouldQueueDeletion {
207+
require.FileExists(t, deleteQueueFile)
208+
} else {
209+
require.NoFileExists(t, deleteQueueFile)
210+
}
211+
})
212+
}
213+
}
214+
215+
func acquireExclusiveLock(t *testing.T, lockFile string) (*lockfile.Lockfile, error) {
216+
t.Helper()
217+
218+
lf, err := lockfile.NewLockfile(lockFile)
219+
require.NoError(t, err)
220+
require.NotNil(t, lf)
221+
222+
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
223+
defer cancel()
224+
err = lf.Lock(ctx, true)
225+
if err != nil {
226+
return nil, err
227+
}
228+
229+
return lf, nil
230+
}
231+
232+
func TestQueueLock(t *testing.T) {
233+
testDir := t.TempDir()
234+
235+
lockDir := path.Join(testDir, "deletion_queue")
236+
lockFile := path.Join(lockDir, "lockfile")
237+
238+
dc := &DeletionFallbackClient{
239+
logger: hivetest.Logger(t),
240+
deleteQueueDir: lockDir,
241+
deleteQueueLockfile: lockFile,
242+
}
243+
244+
lf, err := dc.tryQueueLock()
245+
require.NoError(t, err)
246+
require.NotNil(t, lf)
247+
248+
// CNI acquires shared lock.
249+
lf2, err := dc.tryQueueLock()
250+
require.NoError(t, err)
251+
require.NotNil(t, lf2)
252+
253+
lf3, err := acquireExclusiveLock(t, lockFile)
254+
require.Error(t, err)
255+
require.Nil(t, lf3)
256+
257+
lf.Unlock()
258+
lf2.Unlock()
259+
260+
lf3, err = acquireExclusiveLock(t, lockFile)
261+
require.NoError(t, err)
262+
require.NotNil(t, lf3)
263+
lf3.Unlock()
264+
}

0 commit comments

Comments
 (0)