Improve appmgr UT coverage
[ric-plt/appmgr.git] / pkg / cm / cm_test.go
1 /*
2 ==================================================================================
3   Copyright (c) 2019 AT&T Intellectual Property.
4   Copyright (c) 2019 Nokia
5
6    Licensed under the Apache License, Version 2.0 (the "License");
7    you may not use this file except in compliance with the License.
8    You may obtain a copy of the License at
9
10        http://www.apache.org/licenses/LICENSE-2.0
11
12    Unless required by applicable law or agreed to in writing, software
13    distributed under the License is distributed on an "AS IS" BASIS,
14    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15    See the License for the specific language governing permissions and
16    limitations under the License.
17 ==================================================================================
18 */
19
20 package cm
21
22 import (
23         "encoding/json"
24         "errors"
25         "os"
26         "reflect"
27         "strings"
28         "testing"
29
30         "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/appmgr"
31         "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/models"
32         "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/util"
33 )
34
35 const (
36         expectedHelmSearchCmd = "search helm-repo"
37         expectedHelmFetchCmd  = `fetch --untar --untardir /tmp helm-repo/dummy-xapp`
38 )
39
40 var caughtKubeExecArgs []string
41 var kubeExecRetOut string
42 var kubeExecRetErr error
43 var caughtHelmExecArgs string
44 var helmExecRetOut string
45 var helmExecRetErr error
46
47 var expectedKubectlGetCmd []string = []string{
48         `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-anr-appconfig`,
49         `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-appmgr-appconfig`,
50         `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-dualco-appconfig`,
51         `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-reporter-appconfig`,
52         `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-uemgr-appconfig`,
53 }
54
55 var helmSearchOutput = `
56 helm-repo/anr           0.0.1           1.0             Helm Chart for Nokia ANR (Automatic Neighbour Relation) xAPP
57 helm-repo/appmgr        0.0.2           1.0             Helm Chart for xAppManager
58 helm-repo/dualco        0.0.1           1.0             Helm Chart for Nokia dualco xAPP
59 helm-repo/reporter      0.0.1           1.0             Helm Chart for Reporting xAPP
60 helm-repo/uemgr         0.0.1           1.0             Helm Chart for Nokia uemgr xAPP
61 `
62
63 var kubectlConfigmapOutput = `
64 {
65     "local": {
66         "host": ":8080"
67     },
68     "logger": {
69         "level": 3
70     },
71     "rmr": {
72        "protPort": "tcp:4560",
73        "maxSize": 2072,
74        "numWorkers": 1,
75        "txMessages": ["RIC_X2_LOAD_INFORMATION"],
76        "rxMessages": ["RIC_X2_LOAD_INFORMATION"],
77            "policies":   [11, 22, 33]
78     },
79     "db": {
80         "namespace": "ricxapp",
81         "host": "dbaas",
82         "port": 6379
83     }
84 }
85 `
86 var cfgData = `{
87         "active":true,
88         "interfaceId": {
89                 "globalENBId":{
90                         "plmnId": "1234",
91                         "eNBId":"55"
92                 }
93         }
94 }`
95
96 type ConfigSample struct {
97         Level int
98         Host  string
99 }
100
101 type MockedConfigMapper struct {
102 }
103
104 func (cm *MockedConfigMapper) ReadSchema(name string, c *models.XAppConfig) (err error) {
105         return
106 }
107
108 func (cm *MockedConfigMapper) UploadConfig() (cfg []models.XAppConfig) {
109         return
110 }
111
112 func (cm *MockedConfigMapper) UpdateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
113         return
114 }
115
116 func (cm *MockedConfigMapper) ReadConfigMap(name string, ns string, c *interface{}) (err error) {
117         return
118 }
119
120 func (cm *MockedConfigMapper) FetchChart(name string) (err error) {
121         return
122 }
123
124 func (cm *MockedConfigMapper) GetRtmData(name string) (msgs appmgr.RtmData) {
125         return
126 }
127
128 func (cm *MockedConfigMapper) GetNamespace(ns string) (n string) {
129         return
130 }
131
132 func (cm *MockedConfigMapper) GetNamesFromHelmRepo() (names []string) {
133         return
134 }
135
136 // Test cases
137 func TestMain(m *testing.M) {
138         appmgr.Init()
139         appmgr.Logger.SetLevel(0)
140
141         code := m.Run()
142         os.Exit(code)
143 }
144
145 func TestUploadConfigAllSuccess(t *testing.T) {
146         var cfg interface{}
147         var expectedResult models.AllXappConfig
148         ns := "ricxapp"
149         xapps := []string{"anr", "appmgr", "dualco", "reporter", "uemgr"}
150
151         if ret := json.Unmarshal([]byte(cfgData), &cfg); ret != nil {
152                 t.Errorf("UploadConfigAll Json unmarshal failed: %v", ret)
153         }
154
155         for i, _ := range xapps {
156                 expectedResult = append(expectedResult,
157                         &models.XAppConfig{
158                                 Config: cfg,
159                                 Metadata: &models.ConfigMetadata{
160                                         Namespace: &ns,
161                                         XappName:  &xapps[i],
162                                 },
163                         },
164                 )
165         }
166
167         defer func() { resetHelmExecMock() }()
168         helmExec = mockedHelmExec
169         //Fake helm search success
170         helmExecRetOut = helmSearchOutput
171
172         defer func() { resetKubeExecMock() }()
173         kubeExec = mockedKubeExec
174         //Fake 'kubectl get configmap' success
175         kubeExecRetOut = strings.ReplaceAll(cfgData, "\\", "")
176
177         result := NewCM().UploadConfigAll()
178         if !reflect.DeepEqual(result, expectedResult) {
179                 t.Errorf("UploadConfigAll failed: expected: %v, got: %v", expectedResult, result)
180         }
181         if caughtHelmExecArgs != expectedHelmSearchCmd {
182                 t.Errorf("UploadConfigAll failed: expected: %v, got: %v", expectedHelmSearchCmd, caughtHelmExecArgs)
183         }
184         if !reflect.DeepEqual(caughtKubeExecArgs, expectedKubectlGetCmd) {
185                 t.Errorf("UploadConfigAll failed: expected: %v, got: %v", expectedKubectlGetCmd, caughtKubeExecArgs)
186         }
187 }
188
189 func TestUploadConfigAllReturnsEmptyMapIfAllConfigMapReadsFail(t *testing.T) {
190         var expectedResult models.AllXappConfig
191
192         defer func() { resetHelmExecMock() }()
193         helmExec = mockedHelmExec
194         //Fake helm search success
195         helmExecRetOut = helmSearchOutput
196
197         defer func() { resetKubeExecMock() }()
198         kubeExec = mockedKubeExec
199         //Fake 'kubectl get configmap' failure
200         kubeExecRetErr = errors.New("some error")
201
202         result := NewCM().UploadConfigAll()
203         if !reflect.DeepEqual(result, expectedResult) {
204                 t.Errorf("UploadConfigAll failed: expected: %v, got: %v", expectedResult, result)
205         }
206 }
207
208 func TestUploadConfigElementSuccess(t *testing.T) {
209         var cfg interface{}
210         var expectedResult models.AllXappConfig
211         ns := "ricxapp"
212         xapps := []string{"anr", "appmgr", "dualco", "reporter", "uemgr"}
213
214         if ret := json.Unmarshal([]byte(cfgData), &cfg); ret != nil {
215                 t.Errorf("UploadConfigElement Json unmarshal failed: %v", ret)
216         }
217
218         for i, _ := range xapps {
219                 expectedResult = append(expectedResult,
220                         &models.XAppConfig{
221                                 Config: cfg.(map[string]interface{})["active"],
222                                 Metadata: &models.ConfigMetadata{
223                                         Namespace: &ns,
224                                         XappName:  &xapps[i],
225                                 },
226                         },
227                 )
228         }
229
230         defer func() { resetHelmExecMock() }()
231         helmExec = mockedHelmExec
232         //Fake helm search success
233         helmExecRetOut = helmSearchOutput
234
235         defer func() { resetKubeExecMock() }()
236         kubeExec = mockedKubeExec
237         //Fake 'kubectl get configmap' success
238         kubeExecRetOut = strings.ReplaceAll(cfgData, "\\", "")
239
240         result := NewCM().UploadConfigElement("active")
241         if !reflect.DeepEqual(result, expectedResult) {
242                 t.Errorf("UploadConfigElement failed: expected: %v, got: %v", expectedResult, result)
243         }
244         if caughtHelmExecArgs != expectedHelmSearchCmd {
245                 t.Errorf("UploadConfigElement failed: expected: %v, got: %v", expectedHelmSearchCmd, caughtHelmExecArgs)
246         }
247         if !reflect.DeepEqual(caughtKubeExecArgs, expectedKubectlGetCmd) {
248                 t.Errorf("UploadConfigElement failed: expected: %v, got: %v", expectedKubectlGetCmd, caughtKubeExecArgs)
249         }
250 }
251
252 func TestUploadConfigElementReturnsEmptyMapIfElementMissingFromConfigMap(t *testing.T) {
253         var expectedResult models.AllXappConfig
254
255         defer func() { resetHelmExecMock() }()
256         helmExec = mockedHelmExec
257         //Fake helm search success
258         helmExecRetOut = helmSearchOutput
259
260         defer func() { resetKubeExecMock() }()
261         kubeExec = mockedKubeExec
262         //Fake 'kubectl get configmap' success
263         kubeExecRetOut = strings.ReplaceAll(cfgData, "\\", "")
264
265         //Try to upload non-existing configuration element
266         result := NewCM().UploadConfigElement("some-not-existing-element")
267         if !reflect.DeepEqual(result, expectedResult) {
268                 t.Errorf("UploadConfigElement failed: expected: %v, got: %v", expectedResult, result)
269         }
270 }
271
272 func TestGetRtmDataSuccess(t *testing.T) {
273         expectedKubeCmd := []string{
274                 `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-dummy-xapp-appconfig`,
275         }
276         expectedMsgs := appmgr.RtmData{
277                 TxMessages: []string{"RIC_X2_LOAD_INFORMATION"},
278                 RxMessages: []string{"RIC_X2_LOAD_INFORMATION"},
279                 Policies:   []int64{11, 22, 33},
280         }
281
282         defer func() { resetKubeExecMock() }()
283         kubeExec = mockedKubeExec
284         //Fake 'kubectl get configmap' success
285         kubeExecRetOut = kubectlConfigmapOutput
286
287         result := NewCM().GetRtmData("dummy-xapp")
288         if !reflect.DeepEqual(result, expectedMsgs) {
289                 t.Errorf("GetRtmData failed: expected: %v, got: %v", expectedMsgs, result)
290         }
291         if !reflect.DeepEqual(caughtKubeExecArgs, expectedKubeCmd) {
292                 t.Errorf("GetRtmData failed: expected: '%v', got: '%v'", expectedKubeCmd, caughtKubeExecArgs)
293         }
294 }
295
296 func TestGetRtmDataReturnsNoDataIfConfigmapGetFails(t *testing.T) {
297         var expectedMsgs appmgr.RtmData
298
299         defer func() { resetKubeExecMock() }()
300         kubeExec = mockedKubeExec
301         //Fake 'kubectl get configmap' failure
302         kubeExecRetErr = errors.New("some error")
303
304         result := NewCM().GetRtmData("dummy-xapp")
305         if !reflect.DeepEqual(result, expectedMsgs) {
306                 t.Errorf("GetRtmData failed: expected: %v, got: %v", expectedMsgs, result)
307         }
308 }
309
310 func TestGetRtmDataReturnsNoDataIfJsonParseFails(t *testing.T) {
311         var expectedMsgs appmgr.RtmData
312
313         defer func() { resetKubeExecMock() }()
314         kubeExec = mockedKubeExec
315         //Fake 'kubectl get configmap' to return nothing what will cause JSON parse failure
316
317         result := NewCM().GetRtmData("dummy-xapp")
318         if !reflect.DeepEqual(result, expectedMsgs) {
319                 t.Errorf("GetRtmData failed: expected: %v, got: %v", expectedMsgs, result)
320         }
321 }
322
323 func TestHelmNamespace(t *testing.T) {
324         if NewCM().GetNamespace("pltxapp") != "pltxapp" {
325                 t.Errorf("getNamespace failed!")
326         }
327
328         if NewCM().GetNamespace("") != "ricxapp" {
329                 t.Errorf("getNamespace failed!")
330         }
331 }
332
333 func TestFetchChartFails(t *testing.T) {
334         if NewCM().FetchChart("dummy-xapp") == nil {
335                 t.Errorf("TestFetchChart failed!")
336         }
337 }
338
339 func TestFetchChartSuccess(t *testing.T) {
340         defer func() { resetHelmExecMock() }()
341         helmExec = mockedHelmExec
342
343         if NewCM().FetchChart("dummy-xapp") != nil {
344                 t.Errorf("TestFetchChart failed!")
345         }
346 }
347
348 func TestGetNamespaceSuccess(t *testing.T) {
349         if ns := NewCM().GetNamespace("my-ns"); ns != "my-ns" {
350                 t.Errorf("GetNamespace failed: expected: my-ns, got: %s", ns)
351         }
352 }
353
354 func TestGetNamespaceReturnsConfiguredNamespaceName(t *testing.T) {
355         if ns := NewCM().GetNamespace(""); ns != "ricxapp" {
356                 t.Errorf("GetNamespace failed: expected: ricxapp, got: %s", ns)
357         }
358 }
359
360 func TestGetNamesFromHelmRepoSuccess(t *testing.T) {
361         expectedResult := []string{"anr", "appmgr", "dualco", "reporter", "uemgr"}
362
363         defer func() { resetHelmExecMock() }()
364         helmExec = mockedHelmExec
365         //Fake helm search success
366         helmExecRetOut = helmSearchOutput
367
368         names := NewCM().GetNamesFromHelmRepo()
369         if !reflect.DeepEqual(names, expectedResult) {
370                 t.Errorf("GetNamesFromHelmRepo failed: expected %v, got %v", expectedResult, names)
371         }
372         if caughtHelmExecArgs != expectedHelmSearchCmd {
373                 t.Errorf("GetNamesFromHelmRepo failed: expected: %v, got: %v", expectedHelmSearchCmd, caughtHelmExecArgs)
374         }
375 }
376
377 func TestGetNamesFromHelmRepoFailure(t *testing.T) {
378         expectedResult := []string{}
379
380         defer func() { resetHelmExecMock() }()
381         helmExec = mockedHelmExec
382         helmExecRetOut = helmSearchOutput
383         helmExecRetErr = errors.New("Command failed!")
384
385         names := NewCM().GetNamesFromHelmRepo()
386         if names != nil {
387                 t.Errorf("GetNamesFromHelmRepo failed: expected %v, got %v", expectedResult, names)
388         }
389 }
390
391 func TestBuildConfigMapSuccess(t *testing.T) {
392         expectedKubeCmd := []string{
393                 `get configmap -o jsonpath='{.data.config-file\.json}' -n ricxapp  configmap-ricxapp-dummy-xapp-appconfig`,
394         }
395         name := "dummy-xapp"
396         namespace := "ricxapp"
397         m := models.ConfigMetadata{XappName: &name, Namespace: &namespace}
398         s := `{"Metadata": {"XappName": "ueec", "Namespace": "ricxapp"}, ` +
399                 `"Config": {"active": true, "interfaceId":{"globalENBId": {"eNBId": 77, "plmnId": "6666"}}}}`
400
401         defer func() { resetKubeExecMock() }()
402         kubeExec = mockedKubeExec
403         //Fake 'kubectl get configmap' success
404         kubeExecRetOut = `{"logger": {"level": 2}}`
405
406         cmString, err := NewCM().BuildConfigMap(models.XAppConfig{Metadata: &m, Config: s})
407         if err != nil {
408                 t.Errorf("BuildConfigMap failed: %v -> %v", err, cmString)
409         }
410         if !reflect.DeepEqual(caughtKubeExecArgs, expectedKubeCmd) {
411                 t.Errorf("BuildConfigMap failed: expected: %v, got: %v", expectedKubeCmd, caughtKubeExecArgs)
412         }
413 }
414
415 func TestBuildConfigMapReturnErrorIfJsonMarshalFails(t *testing.T) {
416         name := "dummy-xapp"
417         namespace := "ricxapp"
418         m := models.ConfigMetadata{XappName: &name, Namespace: &namespace}
419         //Give channel as a configuration input, this will fail JSON marshal
420         cmString, err := NewCM().BuildConfigMap(models.XAppConfig{Metadata: &m, Config: make(chan int)})
421         if err == nil {
422                 t.Errorf("BuildConfigMap failed: %v -> %v", err, cmString)
423         }
424 }
425
426 func TestBuildConfigMapReturnErrorIfKubectlGetConfigmapFails(t *testing.T) {
427         name := "dummy-xapp"
428         namespace := "ricxapp"
429         m := models.ConfigMetadata{XappName: &name, Namespace: &namespace}
430         s := `{"Metadata": {"XappName": "ueec", "Namespace": "ricxapp"}, ` +
431                 `"Config": {"active": true, "interfaceId":{"globalENBId": {"eNBId": 77, "plmnId": "6666"}}}}`
432
433         defer func() { resetKubeExecMock() }()
434         kubeExec = mockedKubeExec
435         //Fake 'kubectl get configmap' failure
436         kubeExecRetErr = errors.New("some error")
437
438         cmString, err := NewCM().BuildConfigMap(models.XAppConfig{Metadata: &m, Config: s})
439         if err == nil {
440                 t.Errorf("BuildConfigMap failed: %v -> %v", err, cmString)
441         } else if err.Error() != "some error" {
442                 t.Errorf("BuildConfigMap failed: expected: 'some error', got: '%s'", err.Error())
443         }
444 }
445
446 func TestBuildConfigMapReturnErrorIfJsonParserFails(t *testing.T) {
447         name := "dummy-xapp"
448         namespace := "ricxapp"
449         m := models.ConfigMetadata{XappName: &name, Namespace: &namespace}
450         s := `{"Metadata": {"XappName": "ueec", "Namespace": "ricxapp"}, ` +
451                 `"Config": {"active": true, "interfaceId":{"globalENBId": {"eNBId": 77, "plmnId": "6666"}}}}`
452
453         defer func() { resetKubeExecMock() }()
454         kubeExec = mockedKubeExec
455         //Return empty json that causes JSON parser to fail
456         kubeExecRetOut = ``
457
458         cmString, err := NewCM().BuildConfigMap(models.XAppConfig{Metadata: &m, Config: s})
459         if err == nil {
460                 t.Errorf("BuildConfigMap failed: %v -> %v", err, cmString)
461         }
462 }
463
464 func TestGenerateJSONFileSuccess(t *testing.T) {
465         err := NewCM().GenerateJSONFile("{}")
466         if err != nil {
467                 t.Errorf("GenerateJSONFile failed: %v", err)
468         }
469 }
470
471 func TestReplaceConfigMapSuccess(t *testing.T) {
472         name := "dummy-xapp"
473         namespace := "ricxapp"
474
475         defer func() { resetKubeExecMock() }()
476         kubeExec = mockedKubeExec
477         //Fake 'kubectl create configmap' success
478         kubeExecRetOut = ""
479
480         err := NewCM().ReplaceConfigMap(name, namespace)
481         if err != nil {
482                 t.Errorf("ReplaceConfigMap failed: %v", err)
483         }
484 }
485
486 func TestUpdateConfigMapReturnsErrorIfSchemaFileIsMissing(t *testing.T) {
487         name := "dummy-xapp"
488         namespace := "ricxapp"
489         config := models.XAppConfig{Metadata: &models.ConfigMetadata{XappName: &name, Namespace: &namespace}}
490
491         defer func() { resetHelmExecMock() }()
492         helmExec = mockedHelmExec
493         helmExecRetOut = `{}`
494
495         //Will fail at schema reading, because schema file is mission
496         validationErrors, err := NewCM().UpdateConfigMap(config)
497         if err == nil {
498                 t.Errorf("UpdateConfigMap failed: %v -> %v", err, validationErrors)
499         }
500         if caughtHelmExecArgs != expectedHelmFetchCmd {
501                 t.Errorf("UpdateConfigMap failed: expected: %v, got: %v", expectedHelmFetchCmd, caughtHelmExecArgs)
502         }
503 }
504
505 func TestUpdateConfigMapReturnsErrorIfHelmFetchChartFails(t *testing.T) {
506         name := "dummy-xapp"
507         namespace := "ricxapp"
508         config := models.XAppConfig{Metadata: &models.ConfigMetadata{XappName: &name, Namespace: &namespace}}
509
510         defer func() { resetHelmExecMock() }()
511         helmExec = mockedHelmExec
512         helmExecRetErr = errors.New("some error")
513
514         validationErrors, err := NewCM().UpdateConfigMap(config)
515         if err == nil {
516                 t.Errorf("UpdateConfigMap failed: %v -> %v", err, validationErrors)
517         }
518 }
519
520 func TestValidationSuccess(t *testing.T) {
521         var d interface{}
522         var cfg map[string]interface{}
523         err := json.Unmarshal([]byte(`{"active": true, "interfaceId":{"globalENBId": {"eNBId": 77, "plmnId": "6666"}}}`), &cfg)
524
525         err = NewCM().ReadFile("../../test/schema.json", &d)
526         if err != nil {
527                 t.Errorf("ReadFile failed: %v -> %v", err, d)
528         }
529
530         feedback, err := NewCM().doValidate(d, cfg)
531         if err != nil {
532                 t.Errorf("doValidate failed: %v -> %v", err, feedback)
533         }
534 }
535
536 func TestValidationFails(t *testing.T) {
537         var d interface{}
538         var cfg map[string]interface{}
539
540         err := json.Unmarshal([]byte(`{"active": "INVALID", "interfaceId":{"globalENBId": {"eNBId": 77, "plmnId": "6666"}}}`), &cfg)
541
542         err = NewCM().ReadFile("../../test/schema.json", &d)
543         if err != nil {
544                 t.Errorf("ConfigMetadata failed: %v -> %v", err, d)
545         }
546
547         feedback, err := NewCM().doValidate(d, cfg)
548         if err == nil {
549                 t.Errorf("doValidate should fail but didn't: %v -> %v", err, feedback)
550         }
551         appmgr.Logger.Debug("Feedbacks: %v", feedback)
552 }
553
554 func TestReadFileReturnsErrorIfFileReadFails(t *testing.T) {
555         var d interface{}
556
557         if err := NewCM().ReadFile("not/existing/test/schema.json", &d); err == nil {
558                 t.Errorf("ReadFile should fail but it didn't")
559         }
560 }
561
562 func TestReadFileReturnsErrorIfJsonUnmarshalFails(t *testing.T) {
563         var d interface{}
564
565         if err := NewCM().ReadFile("../../test/faulty_schema.json", &d); err == nil {
566                 t.Errorf("ReadFile should fail but it didn't")
567         }
568 }
569
570 func mockedKubeExec(args string) (out []byte, err error) {
571         caughtKubeExecArgs = append(caughtKubeExecArgs, args)
572         return []byte(kubeExecRetOut), kubeExecRetErr
573 }
574
575 func resetKubeExecMock() {
576         kubeExec = util.KubectlExec
577         caughtKubeExecArgs = nil
578         kubeExecRetOut = ""
579         kubeExecRetErr = nil
580 }
581
582 func mockedHelmExec(args string) (out []byte, err error) {
583         caughtHelmExecArgs = args
584         return []byte(helmExecRetOut), helmExecRetErr
585 }
586
587 func resetHelmExecMock() {
588         helmExec = util.HelmExec
589         caughtHelmExecArgs = ""
590         helmExecRetOut = ""
591         helmExecRetErr = nil
592 }