Improve appmgr UT coverage
[ric-plt/appmgr.git] / pkg / cm / cm.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         "fmt"
26         "github.com/spf13/viper"
27         "github.com/valyala/fastjson"
28         "github.com/xeipuuv/gojsonschema"
29         "io/ioutil"
30         "os"
31         "path"
32         "regexp"
33         "strconv"
34         "strings"
35
36         "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/appmgr"
37         "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/models"
38         "gerrit.o-ran-sc.org/r/ric-plt/appmgr/pkg/util"
39 )
40
41 var kubeExec = util.KubectlExec
42 var helmExec = util.HelmExec
43
44 type CM struct{}
45
46 func NewCM() *CM {
47         return &CM{}
48 }
49
50 func (cm *CM) UploadConfigAll() (configList models.AllXappConfig) {
51         return cm.UploadConfigElement("")
52 }
53
54 func (cm *CM) UploadConfigElement(Element string) (configList models.AllXappConfig) {
55         namespace := cm.GetNamespace("")
56         for _, name := range cm.GetNamesFromHelmRepo() {
57                 var activeConfig interface{}
58                 xAppName := name
59                 if err := cm.GetConfigmap(xAppName, namespace, &activeConfig); err != nil {
60                         appmgr.Logger.Info("No active configMap found for '%s', ignoring ...", xAppName)
61                         continue
62                 }
63
64                 if Element != "" {
65                         m := activeConfig.(map[string]interface{})
66                         if m[Element] == nil {
67                                 appmgr.Logger.Info("xApp '%s' doesn't have requested element '%s' in config", name, Element)
68                                 continue
69                         }
70                         activeConfig = m[Element]
71                 }
72
73                 c := models.XAppConfig{
74                         Metadata: &models.ConfigMetadata{XappName: &xAppName, Namespace: &namespace},
75                         Config:   activeConfig,
76                 }
77                 configList = append(configList, &c)
78         }
79         return
80 }
81
82 func (cm *CM) GetConfigmap(name, namespace string, c *interface{}) (err error) {
83         cmJson, err := cm.ReadConfigmap(name, namespace)
84         if err != nil {
85                 return err
86         }
87
88         return json.Unmarshal([]byte(cmJson), &c)
89 }
90
91 func (cm *CM) ReadSchema(name string, desc *interface{}) (err error) {
92         if err = cm.FetchChart(name); err != nil {
93                 return
94         }
95
96         tarDir := viper.GetString("xapp.tarDir")
97         err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.schema")), desc)
98         if err != nil {
99                 return
100         }
101
102         if err = os.RemoveAll(path.Join(tarDir, name)); err != nil {
103                 appmgr.Logger.Info("RemoveAll failed: %v", err)
104         }
105
106         return
107 }
108
109 func (cm *CM) UpdateConfigMap(r models.XAppConfig) (models.ConfigValidationErrors, error) {
110         fmt.Printf("Configmap update: xappName=%s namespace=%s config: %v\n", *r.Metadata.XappName, *r.Metadata.Namespace, r.Config)
111         if validationErrors, err := cm.Validate(r); err != nil {
112                 return validationErrors, err
113         }
114
115         cmContent, err := cm.BuildConfigMap(r)
116         if err != nil {
117                 return nil, err
118         }
119
120         if err := cm.GenerateJSONFile(cmContent); err != nil {
121                 return nil, err
122         }
123         err = cm.ReplaceConfigMap(*r.Metadata.XappName, *r.Metadata.Namespace)
124
125         return nil, err
126 }
127
128 func (cm *CM) BuildConfigMap(r models.XAppConfig) (string, error) {
129         configJson, err := json.Marshal(r.Config)
130         if err != nil {
131                 appmgr.Logger.Info("Config marshalling failed: %v", err)
132                 return "", err
133         }
134
135         cmContent, err := cm.ReadConfigmap(*r.Metadata.XappName, *r.Metadata.Namespace)
136         if err != nil {
137                 return "", err
138         }
139
140         v, err := cm.ParseJson(cmContent)
141         if err == nil {
142                 v.Set("controls", fastjson.MustParse(string(configJson)))
143                 fmt.Println(v.String())
144                 return v.String(), nil
145         }
146
147         return "", err
148 }
149
150 func (cm *CM) ParseJson(dsContent string) (*fastjson.Value, error) {
151         var p fastjson.Parser
152         v, err := p.Parse(dsContent)
153         if err != nil {
154                 appmgr.Logger.Info("fastjson.Parser failed: %v", err)
155         }
156         return v, err
157 }
158
159 func (cm *CM) GenerateJSONFile(jsonString string) error {
160         cmJson, err := json.RawMessage(jsonString).MarshalJSON()
161         if err != nil {
162                 appmgr.Logger.Error("Config marshalling failed: %v", err)
163                 return err
164         }
165
166         err = ioutil.WriteFile(viper.GetString("xapp.tmpConfig"), cmJson, 0644)
167         if err != nil {
168                 appmgr.Logger.Error("WriteFile failed: %v", err)
169                 return err
170         }
171
172         return nil
173 }
174
175 func (cm *CM) ReadFile(name string, data interface{}) (err error) {
176         f, err := ioutil.ReadFile(name)
177         if err != nil {
178                 appmgr.Logger.Info("Reading '%s' file failed: %v", name, err)
179                 return
180         }
181
182         err = json.Unmarshal(f, &data)
183         if err != nil {
184                 appmgr.Logger.Info("Unmarshalling '%s' file failed: %v", name, err)
185                 return
186         }
187
188         return
189 }
190
191 func (cm *CM) ReadConfigmap(name string, ns string) (string, error) {
192         args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, cm.GetConfigMapName(name, ns))
193         out, err := kubeExec(args)
194         return string(out), err
195 }
196
197 func (cm *CM) ReplaceConfigMap(name, ns string) error {
198         cmd := " create configmap -n %s %s --from-file=%s -o json --dry-run | kubectl replace -f -"
199         args := fmt.Sprintf(cmd, ns, cm.GetConfigMapName(name, ns), viper.GetString("xapp.tmpConfig"))
200         _, err := kubeExec(args)
201         return err
202 }
203
204 func (cm *CM) FetchChart(name string) (err error) {
205         tarDir := viper.GetString("xapp.tarDir")
206         repo := viper.GetString("helm.repo-name")
207         fetchArgs := fmt.Sprintf("--untar --untardir %s %s/%s", tarDir, repo, name)
208
209         _, err = helmExec(strings.Join([]string{"fetch ", fetchArgs}, ""))
210         return
211 }
212
213 func (cm *CM) GetRtmData(name string) (msgs appmgr.RtmData) {
214         appmgr.Logger.Info("Fetching RT data for xApp=%s", name)
215
216         ns := cm.GetNamespace("")
217         args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, cm.GetConfigMapName(name, ns))
218         out, err := kubeExec(args)
219         if err != nil {
220                 return
221         }
222
223         var p fastjson.Parser
224         v, err := p.Parse(string(out))
225         if err != nil {
226                 appmgr.Logger.Info("fastjson.Parser for '%s' failed: %v", name, err)
227                 return
228         }
229
230         for _, m := range v.GetArray("rmr", "txMessages") {
231                 msgs.TxMessages = append(msgs.TxMessages, strings.Trim(m.String(), `"`))
232         }
233
234         for _, m := range v.GetArray("rmr", "rxMessages") {
235                 msgs.RxMessages = append(msgs.RxMessages, strings.Trim(m.String(), `"`))
236         }
237
238         for _, m := range v.GetArray("rmr", "policies") {
239                 if val, err := strconv.Atoi(strings.Trim(m.String(), `"`)); err == nil {
240                         msgs.Policies = append(msgs.Policies, int64(val))
241                 }
242         }
243
244         return
245 }
246
247 func (cm *CM) GetConfigMapName(xappName, namespace string) string {
248         return " configmap-" + namespace + "-" + xappName + "-appconfig"
249 }
250
251 func (cm *CM) GetNamespace(ns string) string {
252         if ns != "" {
253                 return ns
254         }
255
256         ns = viper.GetString("xapp.namespace")
257         if ns == "" {
258                 ns = "ricxapp"
259         }
260         return ns
261 }
262
263 func (cm *CM) GetNamesFromHelmRepo() (names []string) {
264         rname := viper.GetString("helm.repo-name")
265
266         cmdArgs := strings.Join([]string{"search ", rname}, "")
267         out, err := helmExec(cmdArgs)
268         if err != nil {
269                 return
270         }
271
272         re := regexp.MustCompile(rname + `/.*`)
273         result := re.FindAllStringSubmatch(string(out), -1)
274         if result != nil {
275                 var tmp string
276                 for _, v := range result {
277                         fmt.Sscanf(v[0], "%s", &tmp)
278                         names = append(names, strings.Split(tmp, "/")[1])
279                 }
280         }
281         return names
282 }
283
284 func (cm *CM) Validate(req models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
285         var desc interface{}
286         err = cm.ReadSchema(*req.Metadata.XappName, &desc)
287         if err != nil {
288                 appmgr.Logger.Info("No schema file found for '%s', aborting ...", *req.Metadata.XappName)
289                 return
290         }
291         return cm.doValidate(desc, req.Config)
292 }
293
294 func (cm *CM) doValidate(schema, cfg interface{}) (errList models.ConfigValidationErrors, err error) {
295         schemaLoader := gojsonschema.NewGoLoader(schema)
296         documentLoader := gojsonschema.NewGoLoader(cfg)
297
298         result, err := gojsonschema.Validate(schemaLoader, documentLoader)
299         if err != nil {
300                 appmgr.Logger.Info("Validation failed: %v", err)
301                 return
302         }
303
304         if result.Valid() == false {
305                 appmgr.Logger.Info("The document is not valid, Errors: %v", result.Errors())
306                 for _, desc := range result.Errors() {
307                         field := desc.Field()
308                         validationError := desc.Description()
309                         errList = append(errList, &models.ConfigValidationError{Field: &field, Error: &validationError})
310                 }
311                 return errList, errors.New("Validation failed!")
312         }
313         appmgr.Logger.Info("Config validation successful!")
314
315         return
316 }