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