62001801cd2628f2922fa6991f54b3d772438582
[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         if v.Exists("rmr") {
231                 for _, m := range v.GetArray("rmr", "txMessages") {
232                         msgs.TxMessages = append(msgs.TxMessages, strings.Trim(m.String(), `"`))
233                 }
234
235                 for _, m := range v.GetArray("rmr", "rxMessages") {
236                         msgs.RxMessages = append(msgs.RxMessages, strings.Trim(m.String(), `"`))
237                 }
238
239                 for _, m := range v.GetArray("rmr", "policies") {
240                         if val, err := strconv.Atoi(strings.Trim(m.String(), `"`)); err == nil {
241                                 msgs.Policies = append(msgs.Policies, int64(val))
242                         }
243                 }
244         } else {
245                 for _, p := range v.GetArray("messaging", "ports") {
246                         appmgr.Logger.Info("txMessages=%v, rxMessages=%v", p.GetArray("txMessages"), p.GetArray("rxMessages"))
247                         for _, m := range p.GetArray("txMessages") {
248                                 msgs.TxMessages = append(msgs.TxMessages, strings.Trim(m.String(), `"`))
249                         }
250
251                         for _, m := range p.GetArray("rxMessages") {
252                                 msgs.RxMessages = append(msgs.RxMessages, strings.Trim(m.String(), `"`))
253                         }
254
255                         for _, m := range p.GetArray("policies") {
256                                 if val, err := strconv.Atoi(strings.Trim(m.String(), `"`)); err == nil {
257                                         msgs.Policies = append(msgs.Policies, int64(val))
258                                 }
259                         }
260                 }
261         }
262         return
263 }
264
265 func (cm *CM) GetConfigMapName(xappName, namespace string) string {
266         return " configmap-" + namespace + "-" + xappName + "-appconfig"
267 }
268
269 func (cm *CM) GetNamespace(ns string) string {
270         if ns != "" {
271                 return ns
272         }
273
274         ns = viper.GetString("xapp.namespace")
275         if ns == "" {
276                 ns = "ricxapp"
277         }
278         return ns
279 }
280
281 func (cm *CM) GetNamesFromHelmRepo() (names []string) {
282         rname := viper.GetString("helm.repo-name")
283
284         cmdArgs := strings.Join([]string{"search ", rname}, "")
285         out, err := helmExec(cmdArgs)
286         if err != nil {
287                 return
288         }
289
290         re := regexp.MustCompile(rname + `/.*`)
291         result := re.FindAllStringSubmatch(string(out), -1)
292         if result != nil {
293                 var tmp string
294                 for _, v := range result {
295                         fmt.Sscanf(v[0], "%s", &tmp)
296                         names = append(names, strings.Split(tmp, "/")[1])
297                 }
298         }
299         return names
300 }
301
302 func (cm *CM) Validate(req models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
303         var desc interface{}
304         err = cm.ReadSchema(*req.Metadata.XappName, &desc)
305         if err != nil {
306                 appmgr.Logger.Info("No schema file found for '%s', aborting ...", *req.Metadata.XappName)
307                 return
308         }
309         return cm.doValidate(desc, req.Config)
310 }
311
312 func (cm *CM) doValidate(schema, cfg interface{}) (errList models.ConfigValidationErrors, err error) {
313         schemaLoader := gojsonschema.NewGoLoader(schema)
314         documentLoader := gojsonschema.NewGoLoader(cfg)
315
316         result, err := gojsonschema.Validate(schemaLoader, documentLoader)
317         if err != nil {
318                 appmgr.Logger.Info("Validation failed: %v", err)
319                 return
320         }
321
322         if result.Valid() == false {
323                 appmgr.Logger.Info("The document is not valid, Errors: %v", result.Errors())
324                 for _, desc := range result.Errors() {
325                         field := desc.Field()
326                         validationError := desc.Description()
327                         errList = append(errList, &models.ConfigValidationError{Field: &field, Error: &validationError})
328                 }
329                 return errList, errors.New("Validation failed!")
330         }
331         appmgr.Logger.Info("Config validation successful!")
332
333         return
334 }