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