1 package remoteagentconfig
2
3 import (
4 "context"
5 "fmt"
6 "io/fs"
7 "os"
8 "strings"
9 "testing"
10
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/mock"
13 corev1 "k8s.io/api/core/v1"
14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15
16 "edge-infra.dev/pkg/edge/info"
17 v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1"
18 "edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/config"
19 fakeFile "edge-infra.dev/pkg/sds/lib/os/file/fake"
20 "edge-infra.dev/test/f2"
21 )
22
23 var f f2.Framework
24
25 var tplConfig = `
26 provider: "{{ .Provider }}"
27
28 subscriptions:
29 - name: "sub_{{ .BannerID }}_{{ .StoreID }}_{{ .TerminalID }}_dsds-ea-request
30 bannerID: "{{ .BannerID }}"
31 storeID: "{{ .StoreID }}"
32 terminalID: "{{ .TerminalID }}"
33 CredentialsPath: "{{ .CredentialsPath }}"
34 handler:
35 ResponseTopic: "topic_{{ .BannerID }}_dsds-ea-request"
36 Type: "cli"
37 `
38
39 var expConfig = `
40 provider: "my-provider"
41
42 subscriptions:
43 - name: "sub_my-banner-id_my-store-id_my-terminal-id_dsds-ea-request
44 bannerID: "my-banner-id"
45 storeID: "my-store-id"
46 terminalID: "my-terminal-id"
47 CredentialsPath: "%s/%s"
48 handler:
49 ResponseTopic: "topic_my-banner-id_dsds-ea-request"
50 Type: "cli"
51 `
52
53
54 var adcKey = `
55 {
56 "type": "service_account",
57 "project_id": "project",
58 "private_key_id": "id",
59 "private_key": "-----BEGIN PRIVATE KEY-----\nKey Data\n-----END PRIVATE KEY-----\n",
60 "client_email": "sa-id@project.iam.gserviceaccount.com",
61 "client_id": "id",
62 "auth_uri": "https://accounts.google.com/o/oauth2/auth",
63 "token_uri": "https://oauth2.googleapis.com/token",
64 "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
65 "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/sa-id%40project.iam.gserviceaccount.com",
66 "universe_domain": "googleapis.com"
67 }
68 `
69
70 var (
71 provider = "my-provider"
72 bannerID = "my-banner-id"
73 storeID = "my-store-id"
74 terminalID = "my-terminal-id"
75
76 testHostname = "test-ienode"
77 )
78
79 func defaultSecret() corev1.Secret {
80 secret := corev1.Secret{
81 Data: map[string][]byte{
82 "config.yaml.tpl": []byte(tplConfig),
83 "key.json": []byte(adcKey),
84 },
85 }
86 return secret
87 }
88
89 type mockConf struct {
90 fakeIENode v1ien.IENode
91 err error
92 config.Config
93 }
94
95 func (mConf mockConf) GetHostIENode(_ context.Context) (*v1ien.IENode, error) {
96 return &mConf.fakeIENode, mConf.err
97 }
98
99 func TestMain(m *testing.M) {
100 f = f2.New(context.Background(), f2.WithExtensions()).
101 Setup().
102 Teardown()
103 os.Exit(f.Run(m))
104 }
105
106 func TestGetTerminalID(t *testing.T) {
107 feature := f2.NewFeature("Remoteagentconfig plugin").
108 Test("gets terminal ID", func(ctx f2.Context, t *testing.T) f2.Context {
109 fakeIENode := &v1ien.IENode{
110 ObjectMeta: metav1.ObjectMeta{
111 Name: testHostname,
112 Labels: map[string]string{"node.ncr.com/terminal-id": terminalID},
113 },
114 }
115 mConf := mockConf{
116 fakeIENode: *fakeIENode,
117 err: nil,
118 }
119 expTerminalInfo := map[string]string{"terminalID": terminalID}
120
121 gotTerminalInfo, err := getTerminalInfo(ctx, mConf)
122
123 assert.NoError(t, err)
124 assert.Equal(t, expTerminalInfo, gotTerminalInfo)
125
126 return ctx
127 }).Feature()
128
129 f.Test(t, feature)
130 }
131
132 func TestGetTerminalIDError(t *testing.T) {
133 feature := f2.NewFeature("Remoteagentconfig plugin").
134 Test("gets terminal ID error", func(ctx f2.Context, t *testing.T) f2.Context {
135 expErr := fmt.Errorf("GetHostIENode Error")
136 fakeIENode := &v1ien.IENode{
137 ObjectMeta: metav1.ObjectMeta{
138 Name: testHostname,
139 Labels: map[string]string{"node.ncr.com/terminal-id": terminalID},
140 },
141 }
142 mConf := mockConf{
143 fakeIENode: *fakeIENode,
144 err: expErr,
145 }
146
147 gotTerminalInfo, err := getTerminalInfo(ctx, mConf)
148
149 assert.ErrorIs(t, err, expErr)
150 assert.ErrorContains(t, err, "locating IENode")
151 assert.Empty(t, gotTerminalInfo)
152
153 return ctx
154 }).Feature()
155
156 f.Test(t, feature)
157 }
158
159 func TestIsTargetConfigMap(t *testing.T) {
160 feature := f2.NewFeature("Remoteagentconfig plugin").
161 Test("is target config map", func(ctx f2.Context, t *testing.T) f2.Context {
162 secret := corev1.Secret{
163 ObjectMeta: metav1.ObjectMeta{
164 Name: "remote-agent-configuration",
165 Namespace: "sds",
166 },
167 }
168
169 assert.True(t, isTargetSecret(&secret))
170
171 return ctx
172 }).Feature()
173
174 f.Test(t, feature)
175 }
176
177 func TestWrongName(t *testing.T) {
178 feature := f2.NewFeature("Remoteagentconfig plugin").
179 Test("wrong name", func(ctx f2.Context, t *testing.T) f2.Context {
180 secret := corev1.Secret{
181 ObjectMeta: metav1.ObjectMeta{
182 Name: "node-agent-plugins",
183 Namespace: "sds",
184 },
185 }
186
187 assert.False(t, isTargetSecret(&secret))
188
189 return ctx
190 }).Feature()
191
192 f.Test(t, feature)
193 }
194
195 func TestWrongNamespace(t *testing.T) {
196 feature := f2.NewFeature("Remoteagentconfig plugin").
197 Test("wrong namespace", func(ctx f2.Context, t *testing.T) f2.Context {
198 secret := corev1.Secret{
199 ObjectMeta: metav1.ObjectMeta{
200 Name: "remote-agent-configuration",
201 Namespace: "kube-public",
202 },
203 }
204
205 assert.False(t, isTargetSecret(&secret))
206
207 return ctx
208 }).Feature()
209
210 f.Test(t, feature)
211 }
212
213
214
215
216 func adcFileNameChecker(storedName *string) func(filename string) bool {
217 return func(filename string) bool {
218
219
220
221
222 if strings.HasSuffix(filename, ".adc.json") {
223 *storedName = filename
224 return true
225 }
226 return false
227 }
228 }
229
230
231 func configFileChecker(adcFilename *string) func(contents []byte) bool {
232 return func(contents []byte) bool {
233
234
235
236 if string(contents) == adcKey {
237 return false
238 }
239 return string(contents) == fmt.Sprintf(expConfig, "/data/remote-access-agent", *adcFilename)
240 }
241 }
242
243 func TestUpdateRemoteAgentConfig(t *testing.T) {
244 feature := f2.NewFeature("Remoteagentconfig plugin").
245 Test("updates remoteagentconfig", func(ctx f2.Context, t *testing.T) f2.Context {
246 secret := defaultSecret()
247 terminalInfo := map[string]string{"terminalID": terminalID}
248 fakeEdgeInfo := info.EdgeInfo{
249 BannerEdgeID: bannerID,
250 ClusterEdgeID: storeID,
251 ProjectID: provider,
252 }
253
254
255
256 var adcFilename = "PLACEHOLDER"
257
258 fakeFileHandler, fileMock := fakeFile.NewFake()
259
260 fileMock.On("ReadDir", remoteAgentHostDataDir).Return([]fs.DirEntry{}, nil).Once()
261 fileMock.On("SafeWrite", remoteAgentHostDataDir, mock.MatchedBy(adcFileNameChecker(&adcFilename)), []byte(adcKey), fs.FileMode(0644)).Return(nil).Once()
262 fileMock.On("Read", remoteAgentHostDataDir+"/"+remoteAgentConfigFileName).Return("", fs.ErrNotExist).Once()
263
264 fileMock.On("SafeWrite", remoteAgentHostDataDir, "config.yaml", mock.MatchedBy(configFileChecker(&adcFilename)), fs.FileMode(0644)).Return(nil).Once()
265
266 err := UpdateRemoteAgentConfig(context.Background(), &secret, terminalInfo, &fakeEdgeInfo, fakeFileHandler)
267
268 assert.NoError(t, err)
269
270 fileMock.AssertExpectations(t)
271
272 assert.NotEqual(t, adcFilename, "PLACEHOLDER")
273
274 return ctx
275 }).Feature()
276
277 f.Test(t, feature)
278 }
279
280 func TestUpdatesOldFile(t *testing.T) {
281 feature := f2.NewFeature("Remoteagentconfig plugin").
282 Test("updates old file", func(ctx f2.Context, t *testing.T) f2.Context {
283 secret := defaultSecret()
284 terminalInfo := map[string]string{"terminalID": terminalID}
285 fakeEdgeInfo := info.EdgeInfo{
286 BannerEdgeID: bannerID,
287 ClusterEdgeID: storeID,
288 ProjectID: provider,
289 }
290
291 var adcFilename = "PLACEHOLDER"
292
293 fakeFileHandler, fileMock := fakeFile.NewFake()
294 fileMock.On("ReadDir", remoteAgentHostDataDir).Return([]fs.DirEntry{}, nil).Once()
295 fileMock.On("SafeWrite", remoteAgentHostDataDir, mock.MatchedBy(adcFileNameChecker(&adcFilename)), []byte(adcKey), fs.FileMode(0644)).Return(nil).Once()
296
297 fileMock.On("Read", remoteAgentHostDataDir+"/"+remoteAgentConfigFileName).Return("provider: \"None\"", nil).Once()
298
299 fileMock.On("SafeWrite", remoteAgentHostDataDir, "config.yaml", mock.MatchedBy(configFileChecker(&adcFilename)), fs.FileMode(0644)).Return(nil).Once()
300
301 err := UpdateRemoteAgentConfig(context.Background(), &secret, terminalInfo, &fakeEdgeInfo, fakeFileHandler)
302
303 assert.NoError(t, err)
304
305 fileMock.AssertExpectations(t)
306
307 assert.NotEqual(t, adcFilename, "PLACEHOLDER")
308
309 return ctx
310 }).Feature()
311
312 f.Test(t, feature)
313 }
314
315 func TestUpdateRemoteAgentConfigError(t *testing.T) {
316 t.Parallel()
317
318 tests := map[string]struct {
319 mock func(*string, *mock.Mock)
320 }{
321 "Error writing adc key": {
322 mock: func(adcFilename *string, fileMock *mock.Mock) {
323 fileMock.On("ReadDir", remoteAgentHostDataDir).Return([]fs.DirEntry{}, nil).Once()
324 fileMock.On("SafeWrite", remoteAgentHostDataDir, mock.MatchedBy(adcFileNameChecker(adcFilename)), []byte(adcKey), fs.FileMode(0644)).Return(fmt.Errorf("error writing adc")).Once()
325 },
326 },
327 "Error writing config.yaml": {
328 mock: func(adcFilename *string, fileMock *mock.Mock) {
329 fileMock.On("ReadDir", remoteAgentHostDataDir).Return([]fs.DirEntry{}, nil).Once()
330 fileMock.On("SafeWrite", remoteAgentHostDataDir, mock.MatchedBy(adcFileNameChecker(adcFilename)), []byte(adcKey), fs.FileMode(0644)).Return(nil).Once()
331
332 fileMock.On("Read", remoteAgentHostDataDir+"/"+remoteAgentConfigFileName).Return("provider: \"None\"", nil).Once()
333
334 fileMock.On("SafeWrite", remoteAgentHostDataDir, "config.yaml", mock.MatchedBy(configFileChecker(adcFilename)), fs.FileMode(0644)).Return(fmt.Errorf("error writing config")).Once()
335 },
336 },
337 }
338
339 for name, tc := range tests {
340 tc := tc
341 t.Run(name, func(t *testing.T) {
342 t.Parallel()
343
344 secret := defaultSecret()
345 terminalInfo := map[string]string{"terminalID": terminalID}
346 fakeEdgeInfo := info.EdgeInfo{
347 BannerEdgeID: bannerID,
348 ClusterEdgeID: storeID,
349 ProjectID: provider,
350 }
351
352 var adcFilename = "PLACEHOLDER"
353
354 fakeFileHandler, fileMock := fakeFile.NewFake()
355
356 tc.mock(&adcFilename, fileMock)
357
358 err := UpdateRemoteAgentConfig(context.Background(), &secret, terminalInfo, &fakeEdgeInfo, fakeFileHandler)
359 assert.Error(t, err)
360
361 fileMock.AssertExpectations(t)
362
363 assert.NotEqual(t, adcFilename, "PLACEHOLDER")
364 })
365 }
366 }
367
368 func TestDoesntUpdateExistingConfig(t *testing.T) {
369 feature := f2.NewFeature("Remoteagentconfig plugin").
370 Test("doesnt update existing config", func(ctx f2.Context, t *testing.T) f2.Context {
371 secret := defaultSecret()
372 terminalInfo := map[string]string{"terminalID": terminalID}
373 fakeEdgeInfo := info.EdgeInfo{
374 BannerEdgeID: bannerID,
375 ClusterEdgeID: storeID,
376 ProjectID: provider,
377 }
378
379 adcFilename := "16.adc.json"
380
381 fakeFileHandler, fileMock := fakeFile.NewFake()
382 fileMock.On("ReadDir", remoteAgentHostDataDir).Return([]fs.DirEntry{dirEntryMock{name: adcFilename}}, nil).Once()
383 fileMock.On("Read", remoteAgentHostDataDir+"/"+adcFilename).Return(adcKey, nil).Once()
384
385 fileMock.On("Read", remoteAgentHostDataDir+"/"+remoteAgentConfigFileName).Return(fmt.Sprintf(expConfig, "/data/remote-access-agent", adcFilename), nil).Once()
386
387 err := UpdateRemoteAgentConfig(context.Background(), &secret, terminalInfo, &fakeEdgeInfo, fakeFileHandler)
388
389 assert.NoError(t, err)
390
391 fileMock.AssertExpectations(t)
392
393 return ctx
394 }).Feature()
395
396 f.Test(t, feature)
397 }
398
399 func TestMissingConfigTemplate(t *testing.T) {
400 feature := f2.NewFeature("Remoteagentconfig plugin").
401 Test("missing config template", func(ctx f2.Context, t *testing.T) f2.Context {
402 secret := corev1.Secret{
403 Data: map[string][]byte{
404 "config.yaml": []byte(tplConfig),
405 "key.json": []byte(adcKey),
406 },
407 }
408 terminalInfo := map[string]string{"terminalID": terminalID}
409 fakeEdgeInfo := info.EdgeInfo{
410 BannerEdgeID: bannerID,
411 ClusterEdgeID: storeID,
412 ProjectID: provider,
413 }
414
415 var adcFilename = "PLACEHOLDER"
416
417 fakeFileHandler, fileMock := fakeFile.NewFake()
418 fileMock.On("ReadDir", remoteAgentHostDataDir).Return([]fs.DirEntry{}, nil).Once()
419 fileMock.On("SafeWrite", remoteAgentHostDataDir, mock.MatchedBy(adcFileNameChecker(&adcFilename)), []byte(adcKey), fs.FileMode(0644)).Return(nil).Once()
420
421 err := UpdateRemoteAgentConfig(context.Background(), &secret, terminalInfo, &fakeEdgeInfo, fakeFileHandler)
422
423 assert.ErrorContains(t, err, "cannot find template config.yaml.tpl in secret")
424 fileMock.AssertExpectations(t)
425
426 assert.NotEqual(t, adcFilename, "PLACEHOLDER")
427
428 return ctx
429 }).Feature()
430
431 f.Test(t, feature)
432 }
433
434 func TestMissingADCKey(t *testing.T) {
435 feature := f2.NewFeature("Remoteagentconfig plugin").
436 Test("missing ADC key", func(ctx f2.Context, t *testing.T) f2.Context {
437 secret := corev1.Secret{
438 Data: map[string][]byte{
439 "config.yaml.tpl": []byte(tplConfig),
440 },
441 }
442 terminalInfo := map[string]string{"terminalID": terminalID}
443 fakeEdgeInfo := info.EdgeInfo{
444 BannerEdgeID: bannerID,
445 ClusterEdgeID: storeID,
446 ProjectID: provider,
447 }
448
449 fakeFileHandler, fileMock := fakeFile.NewFake()
450
451
452 err := UpdateRemoteAgentConfig(context.Background(), &secret, terminalInfo, &fakeEdgeInfo, fakeFileHandler)
453
454 assert.ErrorContains(t, err, "cannot find adc entry key.json in secret")
455 fileMock.AssertExpectations(t)
456
457 return ctx
458 }).Feature()
459
460 f.Test(t, feature)
461 }
462
463 func TestADCCleanup(t *testing.T) {
464 feature := f2.NewFeature("Remoteagentconfig plugin").
465 Test("ADC cleanup", func(ctx f2.Context, t *testing.T) f2.Context {
466 secret := defaultSecret()
467 terminalInfo := map[string]string{"terminalID": terminalID}
468 fakeEdgeInfo := info.EdgeInfo{
469 BannerEdgeID: bannerID,
470 ClusterEdgeID: storeID,
471 ProjectID: provider,
472 }
473
474 var adcFilename = "1687187279.adc.json"
475 var oldFilename = "1687187219.adc.json"
476
477 fakeFileHandler, fileMock := fakeFile.NewFake()
478
479 files := []fs.DirEntry{
480 dirEntryMock{name: "my.adc.json"},
481 dirEntryMock{name: "my.1687187279.adc.json"},
482 dirEntryMock{name: adcFilename},
483 dirEntryMock{name: "config.yaml"},
484 dirEntryMock{name: "config.yaml.bk"},
485 dirEntryMock{name: oldFilename},
486 }
487
488 fileMock.On("ReadDir", remoteAgentHostDataDir).Return(files, nil).Once()
489 fileMock.On("Read", remoteAgentHostDataDir+"/"+adcFilename).Return(adcKey, nil).Once()
490 fileMock.On("Read", remoteAgentHostDataDir+"/"+remoteAgentConfigFileName).Return(fmt.Sprintf(expConfig, "/data/remote-access-agent", adcFilename), nil).Once()
491
492
493
494 fileMock.On("Remove", remoteAgentHostDataDir+"/"+oldFilename).Return(nil, nil).Once()
495
496 err := UpdateRemoteAgentConfig(context.Background(), &secret, terminalInfo, &fakeEdgeInfo, fakeFileHandler)
497
498 assert.NoError(t, err)
499
500 fileMock.AssertExpectations(t)
501
502 return ctx
503 }).Feature()
504
505 f.Test(t, feature)
506 }
507
508 type dirEntryMock struct {
509 name string
510 fs.DirEntry
511 }
512
513 func (de dirEntryMock) Name() string {
514 return de.name
515 }
516
517 func TestSortedADCFiles(t *testing.T) {
518 feature := f2.NewFeature("Remoteagentconfig plugin").
519 Test("sorted ADC files", func(ctx f2.Context, t *testing.T) f2.Context {
520 fakeFileHandler, fileMock := fakeFile.NewFake()
521 fileMock.On("ReadDir", remoteAgentHostDataDir).Return([]fs.DirEntry{dirEntryMock{name: "my.adc.json"}, dirEntryMock{name: "my.1687187279.adc.json"}, dirEntryMock{name: "1687187279.adc.json"}, dirEntryMock{name: remoteAgentConfigFileName}, dirEntryMock{name: "1687187219.adc.json"}}, nil).Once()
522
523 res, err := sortedADCFiles(fakeFileHandler)
524 assert.NoError(t, err)
525
526 assert.Equal(t, []string{"1687187219.adc.json", "1687187279.adc.json"}, res)
527
528 return ctx
529 }).Feature()
530
531 f.Test(t, feature)
532 }
533
View as plain text