Enhance config handling 13/2213/3
authorMohamed Abukar <abukar.mohamed@nokia.com>
Tue, 14 Jan 2020 09:10:16 +0000 (11:10 +0200)
committerMohamed Abukar <abukar.mohamed@nokia.com>
Sun, 19 Jan 2020 16:52:15 +0000 (18:52 +0200)
Change-Id: Id63d2cc4461daaab4d0b6a2990a5f108bbe97e73
Signed-off-by: Mohamed Abukar <abukar.mohamed@nokia.com>
api/appmgr_rest_api.yaml
container-tag.yaml
go.mod
go.sum
pkg/appmgr/appmgr.go
pkg/cm/cm.go
pkg/cm/cm_test.go
pkg/helm/helm.go
pkg/helm/helm_test.go
pkg/restful/restful.go
test/schema.json

index 41947b2..d744539 100755 (executable)
@@ -1,7 +1,7 @@
 swagger: '2.0'
 info:
   description: This is a draft API for RIC appmgr
-  version: 0.2.0
+  version: 0.3.3
   title: RIC appmgr
   license:
     name: Apache 2.0
@@ -158,89 +158,7 @@ paths:
           description: Xapp not found
         '500':
           description: Internal error
-  /xapps/{xAppName}/instances/{xAppInstanceName}/start:
-    put:
-      summary: Start given xapp instance
-      tags:
-        - xapp
-      operationId: startXappInstanceByName
-      produces:
-        - application/json
-      parameters:
-        - name: xAppName
-          in: path
-          description: Name of xApp
-          required: true
-          type: string
-        - name: xAppInstanceName
-          in: path
-          description: Name of xApp instance to get information
-          required: true
-          type: string
-      responses:
-        '200':
-          description: successful operation
-        '400':
-          description: Invalid name supplied
-        '404':
-          description: Xapp not found
-        '500':
-          description: Internal error
-  /xapps/{xAppName}/instances/{xAppInstanceName}/stop:
-    put:
-      summary: Stop given xapp instance
-      tags:
-        - xapp
-      operationId: stopXappInstanceByName
-      produces:
-        - application/json
-      parameters:
-        - name: xAppName
-          in: path
-          description: Name of xApp
-          required: true
-          type: string
-        - name: xAppInstanceName
-          in: path
-          description: Name of xApp instance to get information
-          required: true
-          type: string
-      responses:
-        '200':
-          description: successful operation
-        '400':
-          description: Invalid name supplied
-        '404':
-          description: Xapp not found
-        '500':
-          description: Internal error
   /config:
-    post:
-      summary: Create xApp config
-      tags:
-        - xapp
-      operationId: createXappConfig
-      consumes:
-        - application/json
-      produces:
-        - application/json
-      parameters:
-        - name: XAppConfig
-          in: body
-          description: xApp config
-          schema:
-            $ref: '#/definitions/XAppConfig'
-      responses:
-        '201':
-          description: xApp config successfully created
-          schema:
-            $ref: '#/definitions/ConfigValidationErrors'
-        '400':
-          description: Invalid input
-        '422':
-          description: Validation of configuration failed
-        '500':
-          description: Internal error
     put:
       summary: Modify xApp config
       tags:
@@ -281,43 +199,25 @@ paths:
             $ref: '#/definitions/AllXappConfig'
         '500':
           description: Internal error
-    delete:
-      summary: Delete xApp configuration
-      tags:
-        - xapp
-      operationId: deleteXappConfig
-      parameters:
-        - name: ConfigMetadata
-          in: body
-          description: xApp configuration information
-          schema:
-            $ref: '#/definitions/ConfigMetadata'
-      responses:
-        '204':
-          description: Successful deletion of xApp config
-        '400':
-          description: Invalid parameters supplied
-        '500':
-          description: Internal error
-  /config/{configName}:
+  /config/{element}:
     get:
-      summary: Returns the configuration of a single xapp
+      summary: Returns the given element of the configuration
       tags:
         - xapp
-      operationId: getXappConfig
+      operationId: GetConfigElement
       produces:
         - application/json
       parameters:
-        - name: configName
+        - name: element
           in: path
-          description: Name of xApp
+          description: Name of configuration element
           required: true
           type: string
       responses:
         '200':
-          description: successful query of xApp config
+          description: successful query of config elements
           schema:
-            $ref: '#/definitions/XAppConfig'
+            $ref: '#/definitions/AllXappConfig'
         '500':
           description: Internal error
   /subscriptions:
@@ -522,14 +422,12 @@ definitions:
   ConfigMetadata:
     type: object
     required:
-      - name
+      - xappName
+      - namespace
     properties:
-      name:
+      xappName:
         type: string
         description: Name of the xApp
-      configName:
-        type: string
-        description: Name of the config map
       namespace:
         type: string
         description: Name of the namespace
@@ -553,14 +451,10 @@ definitions:
     type: object
     required:
       - metadata
-      - descriptor
       - config
     properties:
       metadata:
         $ref: '#/definitions/ConfigMetadata'
-      descriptor:
-        type: object
-        description: Schema of configuration in JSON format
       config:
         type: object
         description: Configuration in JSON format
index 61d72fb..fe1699b 100755 (executable)
@@ -1,4 +1,4 @@
 # The Jenkins job uses this string for the tag in the image name
 # for example nexus3.o-ran-sc.org:10004/my-image-name:my-tag
 ---
-tag: '0.3.2'
+tag: '0.3.3'
diff --git a/go.mod b/go.mod
index 253e4a2..535dd89 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,7 @@ require (
        gerrit.o-ran-sc.org/r/com/golog v0.0.1
        gerrit.oran-osc.org/r/ric-plt/sdlgo v0.0.0
        github.com/BurntSushi/toml v0.3.1 // indirect
+       github.com/RaveNoX/go-jsonmerge v1.0.0
        github.com/fsnotify/fsnotify v1.4.7
        github.com/ghodss/yaml v1.0.0
        github.com/go-openapi/errors v0.19.2
diff --git a/go.sum b/go.sum
index 3ffdc4b..73d7342 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -10,10 +10,14 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
+github.com/RaveNoX/go-jsonmerge v1.0.0 h1:2e0nqnadoGUP8rAvcA0hkQelZreVO5X3BHomT2XMrAk=
+github.com/RaveNoX/go-jsonmerge v1.0.0/go.mod h1:qYM/NA77LhO4h51JJM7Z+xBU3ovqrNIACZe+SkSNVFo=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -100,6 +104,7 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
@@ -137,6 +142,7 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
 github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
index 03bcdcf..2bd1d59 100755 (executable)
@@ -68,5 +68,5 @@ func loadConfig() {
 func Init() {
        loadConfig()
        Logger = logger.NewLogger("appmgr")
-       Logger.SetMdc("xm", "0.3.0")
+       Logger.SetMdc("xm", "0.3.3")
 }
index a073c0a..fb80276 100755 (executable)
@@ -23,16 +23,15 @@ import (
        "encoding/json"
        "errors"
        "fmt"
-       "github.com/spf13/viper"
-       "github.com/valyala/fastjson"
-       "github.com/xeipuuv/gojsonschema"
        "io/ioutil"
        "os"
        "path"
        "regexp"
        "strings"
        "strconv"
-       "time"
+       "github.com/spf13/viper"
+       "github.com/valyala/fastjson"
+       "github.com/xeipuuv/gojsonschema"
 
        "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
        "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
@@ -45,44 +44,54 @@ func NewCM() *CM {
        return &CM{}
 }
 
-func (cm *CM) UploadConfig() (cfg models.AllXappConfig) {
-       ns := cm.GetNamespace("")
+func (cm *CM) UploadConfigAll() (configList models.AllXappConfig) {
+       return cm.UploadConfigElement("")
+}
+
+func (cm *CM) UploadConfigElement(Element string) (configList models.AllXappConfig) {
+       namespace := cm.GetNamespace("")
        for _, name := range cm.GetNamesFromHelmRepo() {
-               if name == "appmgr" {
+               var activeConfig interface{}
+               xAppName := name
+               if err := cm.GetConfigmap(xAppName, namespace, &activeConfig); err != nil {
+                       appmgr.Logger.Info("No active configMap found for '%s', ignoring ...", xAppName)
                        continue
                }
 
-               c := models.XAppConfig{
-                       Metadata: &models.ConfigMetadata{Name: &name, Namespace: ns, ConfigName: cm.GetConfigMapName(name, ns)},
-               }
-
-               err := cm.ReadSchema(name, &c)
-               if err != nil {
-                       continue
+               if Element != "" {
+                       m := activeConfig.(map[string]interface{})
+                       if m[Element] == nil {
+                               appmgr.Logger.Info("xApp '%s' doesn't have requested element '%s' in config", name, Element)
+                               continue
+                       }
+                       activeConfig = m[Element]
                }
 
-               err = cm.ReadConfigMap(c.Metadata.ConfigName, ns, &c.Config)
-               if err != nil {
-                       appmgr.Logger.Info("No active configMap found, using default!")
+               c := models.XAppConfig{
+                       Metadata: &models.ConfigMetadata{XappName: &xAppName, Namespace: &namespace},
+                       Config: activeConfig,
                }
-
-               cfg = append(cfg, &c)
+               configList = append(configList, &c)
        }
        return
 }
 
-func (cm *CM) ReadSchema(name string, c *models.XAppConfig) (err error) {
-       if err = cm.FetchChart(name); err != nil {
-               return
+func (cm *CM) GetConfigmap(name, namespace string, c *interface{}) (err error) {
+       cmJson, err := cm.ReadConfigmap(name, namespace)
+       if err != nil {
+               return err
        }
 
-       tarDir := viper.GetString("xapp.tarDir")
-       err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.schema")), &c.Descriptor)
-       if err != nil {
+       return json.Unmarshal([]byte(cmJson), &c)
+}
+
+func (cm *CM) ReadSchema(name string, desc *interface{}) (err error) {
+       if err = cm.FetchChart(name); err != nil {
                return
        }
 
-       err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.config")), &c.Config)
+       tarDir := viper.GetString("xapp.tarDir")
+       err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.schema")), desc)
        if err != nil {
                return
        }
@@ -94,148 +103,71 @@ func (cm *CM) ReadSchema(name string, c *models.XAppConfig) (err error) {
        return
 }
 
-func (cm *CM) ReadConfigMap(ConfigName string, ns string, c *interface{}) (err error) {
-       args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, ConfigName)
-       configMapJson, err := util.KubectlExec(args)
-       if err != nil {
-               return
+func (cm *CM) UpdateConfigMap(r models.XAppConfig) (models.ConfigValidationErrors, error) {
+       fmt.Printf("Configmap update: xappName=%s namespace=%s config: %v", *r.Metadata.XappName, *r.Metadata.Namespace, r.Config)
+       if validationErrors, err := cm.Validate(r); err != nil {
+               return validationErrors, err
        }
 
-       err = json.Unmarshal([]byte(configMapJson), &c)
+       cmContent, err := cm.BuildConfigMap(r)
        if err != nil {
-               return
+               return nil, err
        }
 
-       return
-}
-
-func (cm *CM) ApplyConfigMap(r models.XAppConfig, action string) (err error) {
-       c := appmgr.ConfigMap{
-               Kind:       "ConfigMap",
-               ApiVersion: "v1",
-               Metadata:   appmgr.CMMetadata{Name: *r.Metadata.Name, Namespace: r.Metadata.Namespace},
-               Data:       r.Config,
+       if err := cm.GenerateJSONFile(cmContent); err != nil {
+               return nil, err
        }
+       err = cm.ReplaceConfigMap(*r.Metadata.XappName, *r.Metadata.Namespace)
 
-       cmJson, err := json.Marshal(c.Data)
-       if err != nil {
-               appmgr.Logger.Info("Config marshalling failed: %v", err)
-               return
-       }
+       return nil, err
+}
 
-       cmFile := viper.GetString("xapp.tmpConfig")
-       err = ioutil.WriteFile(cmFile, cmJson, 0644)
+func (cm *CM) BuildConfigMap(r models.XAppConfig) (string, error) {
+       configJson, err := json.Marshal(r.Config)
        if err != nil {
-               appmgr.Logger.Info("WriteFile failed: %v", err)
-               return
+               appmgr.Logger.Info("Config marshalling failed: %v", err)
+               return "", err
        }
 
-       cmd := " create configmap -n %s %s --from-file=%s -o json --dry-run | kubectl %s -f -"
-       args := fmt.Sprintf(cmd, r.Metadata.Namespace, r.Metadata.ConfigName, cmFile, action)
-       _, err = util.KubectlExec(args)
+       cmContent, err := cm.ReadConfigmap(*r.Metadata.XappName, *r.Metadata.Namespace)
        if err != nil {
-               return
-       }
-       appmgr.Logger.Info("Configmap changes done!")
-
-       return
-}
-
-func (cm *CM) GetConfigMap(m models.XappDescriptor, c *interface{}) (err error) {
-       return cm.ReadConfigMap(cm.GetConfigMapName(*m.XappName, m.Namespace), m.Namespace, c)
-}
-
-func (cm *CM) CreateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
-       if errList, err = cm.Validate(r); err != nil {
-               return
-       }
-       err = cm.ApplyConfigMap(r, "create")
-       return
-}
-
-func (cm *CM) UpdateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
-       if errList, err = cm.Validate(r); err != nil {
-               return
+               return "", err
        }
 
-       // Re-create the configmap with the new parameters
-       err = cm.ApplyConfigMap(r, "apply")
-       return
-}
-
-func (cm *CM) DeleteConfigMap(r models.ConfigMetadata) (c interface{}, err error) {
-       err = cm.ReadConfigMap(r.ConfigName, r.Namespace, &c)
+       v, err := cm.ParseJson(cmContent)
        if err == nil {
-               args := fmt.Sprintf(" delete configmap --namespace=%s %s", r.Namespace, r.ConfigName)
-               _, err = util.KubectlExec(args)
+               v.Set("controls", fastjson.MustParse(string(configJson)))
+               fmt.Println(v.String())
+               return v.String(), nil
        }
-       return
-}
-
-func (cm *CM) PurgeConfigMap(m models.XappDescriptor) (c interface{}, err error) {
-       md := models.ConfigMetadata{Name: m.XappName, Namespace: m.Namespace, ConfigName: cm.GetConfigMapName(*m.XappName, m.Namespace)}
 
-       return cm.DeleteConfigMap(md)
+       return "", err
 }
 
-func (cm *CM) RestoreConfigMap(m models.XappDescriptor, c interface{}) (err error) {
-       md := &models.ConfigMetadata{Name: m.XappName, Namespace: m.Namespace, ConfigName: cm.GetConfigMapName(*m.XappName, m.Namespace)}
-       time.Sleep(time.Duration(10 * time.Second))
-
-       return cm.ApplyConfigMap(models.XAppConfig{Metadata: md, Config: c}, "create")
-}
-
-func (cm *CM) GetNamesFromHelmRepo() (names []string) {
-       rname := viper.GetString("helm.repo-name")
-
-       cmdArgs := strings.Join([]string{"search ", rname}, "")
-       out, err := util.HelmExec(cmdArgs)
+func (cm *CM) ParseJson(dsContent string) (*fastjson.Value, error) {
+       var p fastjson.Parser
+       v, err := p.Parse(dsContent)
        if err != nil {
-               return
+               appmgr.Logger.Info("fastjson.Parser failed: %v", err)
        }
-
-       re := regexp.MustCompile(rname + `/.*`)
-       result := re.FindAllStringSubmatch(string(out), -1)
-       if result != nil {
-               var tmp string
-               for _, v := range result {
-                       fmt.Sscanf(v[0], "%s", &tmp)
-                       names = append(names, strings.Split(tmp, "/")[1])
-               }
-       }
-       return names
+       return v, err
 }
 
-func (cm *CM) Validate(req models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
-       c := models.XAppConfig{}
-       err = cm.ReadSchema(*req.Metadata.Name, &c)
+
+func (cm *CM) GenerateJSONFile(jsonString string) error {
+       cmJson, err := json.RawMessage(jsonString).MarshalJSON()
        if err != nil {
-               appmgr.Logger.Info("No schema file found for '%s', aborting ...", *req.Metadata.Name)
-               return
+               appmgr.Logger.Error("Config marshalling failed: %v", err)
+               return err
        }
-       return cm.doValidate(c.Descriptor, req.Config)
-}
-
-func (cm *CM) doValidate(schema, cfg interface{}) (errList models.ConfigValidationErrors, err error) {
-       schemaLoader := gojsonschema.NewGoLoader(schema)
-       documentLoader := gojsonschema.NewGoLoader(cfg)
 
-       result, err := gojsonschema.Validate(schemaLoader, documentLoader)
+       err = ioutil.WriteFile(viper.GetString("xapp.tmpConfig"), cmJson, 0644)
        if err != nil {
-               appmgr.Logger.Info("Validation failed: %v", err)
-               return
+               appmgr.Logger.Error("WriteFile failed: %v", err)
+               return err
        }
 
-       if result.Valid() == false {
-               appmgr.Logger.Info("The document is not valid, Errors: %v", result.Errors())
-               for _, desc := range result.Errors() {
-                       field := desc.Field()
-                       validationError := desc.Description()
-                       errList = append(errList, &models.ConfigValidationError{Field: &field, Error: &validationError})
-               }
-               return errList, errors.New("Validation failed!")
-       }
-       return
+       return nil
 }
 
 func (cm *CM) ReadFile(name string, data interface{}) (err error) {
@@ -254,6 +186,19 @@ func (cm *CM) ReadFile(name string, data interface{}) (err error) {
        return
 }
 
+func (cm *CM) ReadConfigmap(name string, ns string) (string, error) {
+       args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, cm.GetConfigMapName(name, ns))
+       out, err := util.KubectlExec(args)
+       return string(out), err
+}
+
+func (cm *CM) ReplaceConfigMap(name, ns string) (error) {
+       cmd := " create configmap -n %s %s --from-file=%s -o json --dry-run | kubectl replace -f -"
+       args := fmt.Sprintf(cmd, ns, cm.GetConfigMapName(name, ns), viper.GetString("xapp.tmpConfig"))
+       _, err := util.KubectlExec(args)
+       return err
+}
+
 func (cm *CM) FetchChart(name string) (err error) {
        tarDir := viper.GetString("xapp.tarDir")
        repo := viper.GetString("helm.repo-name")
@@ -283,9 +228,11 @@ func (cm *CM) GetRtmData(name string) (msgs appmgr.RtmData) {
        for _, m := range v.GetArray("rmr", "txMessages") {
                msgs.TxMessages = append(msgs.TxMessages, strings.Trim(m.String(), `"`))
        }
+
        for _, m := range v.GetArray("rmr", "rxMessages") {
                msgs.RxMessages = append(msgs.RxMessages, strings.Trim(m.String(), `"`))
        }
+
        for _, m := range v.GetArray("rmr", "policies") {
                if val, err := strconv.Atoi(strings.Trim(m.String(), `"`)); err == nil {
                        msgs.Policies = append(msgs.Policies, int64(val))
@@ -310,3 +257,58 @@ func (cm *CM) GetNamespace(ns string) string {
        }
        return ns
 }
+
+func (cm *CM) GetNamesFromHelmRepo() (names []string) {
+       rname := viper.GetString("helm.repo-name")
+
+       cmdArgs := strings.Join([]string{"search ", rname}, "")
+       out, err := util.HelmExec(cmdArgs)
+       if err != nil {
+               return
+       }
+
+       re := regexp.MustCompile(rname + `/.*`)
+       result := re.FindAllStringSubmatch(string(out), -1)
+       if result != nil {
+               var tmp string
+               for _, v := range result {
+                       fmt.Sscanf(v[0], "%s", &tmp)
+                       names = append(names, strings.Split(tmp, "/")[1])
+               }
+       }
+       return names
+}
+
+func (cm *CM) Validate(req models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
+       var desc interface{}
+       err = cm.ReadSchema(*req.Metadata.XappName, &desc)
+       if err != nil {
+               appmgr.Logger.Info("No schema file found for '%s', aborting ...", *req.Metadata.XappName)
+               return
+       }
+       return cm.doValidate(desc, req.Config)
+}
+
+func (cm *CM) doValidate(schema, cfg interface{}) (errList models.ConfigValidationErrors, err error) {
+       schemaLoader := gojsonschema.NewGoLoader(schema)
+       documentLoader := gojsonschema.NewGoLoader(cfg)
+
+       result, err := gojsonschema.Validate(schemaLoader, documentLoader)
+       if err != nil {
+               appmgr.Logger.Info("Validation failed: %v", err)
+               return
+       }
+
+       if result.Valid() == false {
+               appmgr.Logger.Info("The document is not valid, Errors: %v", result.Errors())
+               for _, desc := range result.Errors() {
+                       field := desc.Field()
+                       validationError := desc.Description()
+                       errList = append(errList, &models.ConfigValidationError{Field: &field, Error: &validationError})
+               }
+               return errList, errors.New("Validation failed!")
+       }
+       appmgr.Logger.Info("Config validation successful!")
+
+       return
+}
\ No newline at end of file
index 21d1d61..56b2250 100755 (executable)
@@ -79,38 +79,14 @@ func (cm *MockedConfigMapper) UploadConfig() (cfg []models.XAppConfig) {
        return
 }
 
-func (cm *MockedConfigMapper) CreateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
-       return
-}
-
-func (cm *MockedConfigMapper) GetConfigMap(m models.XappDescriptor, c *interface{}) (err error) {
-       return
-}
-
 func (cm *MockedConfigMapper) UpdateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
        return
 }
 
-func (cm *MockedConfigMapper) DeleteConfigMap(r models.XAppConfig) (c interface{}, err error) {
-       return
-}
-
-func (cm *MockedConfigMapper) PurgeConfigMap(m models.XappDescriptor) (c interface{}, err error) {
-       return
-}
-
-func (cm *MockedConfigMapper) RestoreConfigMap(m models.XappDescriptor, c interface{}) (err error) {
-       return
-}
-
 func (cm *MockedConfigMapper) ReadConfigMap(name string, ns string, c *interface{}) (err error) {
        return
 }
 
-func (cm *MockedConfigMapper) ApplyConfigMap(r models.XAppConfig, action string) (err error) {
-       return
-}
-
 func (cm *MockedConfigMapper) FetchChart(name string) (err error) {
        return
 }
@@ -203,87 +179,37 @@ func TestGetNamesFromHelmRepoFailure(t *testing.T) {
        }
 }
 
-func TestApplyConfigMapSuccess(t *testing.T) {
-       name := "dummy-xapp"
-       m := models.ConfigMetadata{Name: &name, Namespace: "ricxapp"}
-       s := ConfigSample{5, "localhost"}
-
-       util.KubectlExec = func(args string) (out []byte, err error) {
-               return []byte(`{"logger": {"level": 2}}`), nil
-       }
-
-       err := NewCM().ApplyConfigMap(models.XAppConfig{Metadata: &m, Config: s}, "create")
-       if err != nil {
-               t.Errorf("ApplyConfigMap failed: %v", err)
-       }
-}
-
-func TestRestoreConfigMapSuccess(t *testing.T) {
+func TestBuildConfigMapSuccess(t *testing.T) {
        name := "dummy-xapp"
-       m := models.XappDescriptor{XappName: &name, Namespace: "ricxapp"}
-       s := ConfigSample{5, "localhost"}
+       namespace := "ricxapp"
+       m := models.ConfigMetadata{XappName: &name, Namespace: &namespace}
+       s := `{"Metadata": {"XappName": "ueec", "Namespace": "ricxapp"}, "Config": {"active": true, "interfaceId":{"globalENBId": {"eNBId": 77, "plmnId": "6666"}}}}`
 
        util.KubectlExec = func(args string) (out []byte, err error) {
                return []byte(`{"logger": {"level": 2}}`), nil
        }
 
-       err := NewCM().RestoreConfigMap(m, s)
+       cmString, err := NewCM().BuildConfigMap(models.XAppConfig{Metadata: &m, Config: s})
        if err != nil {
-               t.Errorf("RestoreConfigMap failed: %v", err)
-       }
-}
-
-func TestDeleteConfigMapSuccess(t *testing.T) {
-       util.HelmExec = func(args string) (out []byte, err error) {
-               return []byte("ok"), nil
-       }
-
-       util.KubectlExec = func(args string) (out []byte, err error) {
-               return []byte(`{"logger": {"level": 2}}`), nil
-       }
-
-       validationErrors, err := NewCM().DeleteConfigMap(models.ConfigMetadata{})
-       if err != nil {
-               t.Errorf("DeleteConfigMap failed: %v -> %v", err, validationErrors)
-       }
-}
-
-func TestPurgeConfigMapSuccess(t *testing.T) {
-       util.HelmExec = func(args string) (out []byte, err error) {
-               return []byte("ok"), nil
-       }
-
-       util.KubectlExec = func(args string) (out []byte, err error) {
-               return []byte(`{"logger": {"level": 2}}`), nil
-       }
-
-       name := "dummy-xapp"
-       validationErrors, err := NewCM().PurgeConfigMap(models.XappDescriptor{XappName: &name})
-       if err != nil {
-               t.Errorf("PurgeConfigMap failed: %v -> %v", err, validationErrors)
-       }
-}
-
-func TestCreateConfigMapFails(t *testing.T) {
-       name := "dummy-xapp"
-       validationErrors, err := NewCM().CreateConfigMap(models.XAppConfig{Metadata: &models.ConfigMetadata{Name: &name}})
-       if err == nil {
-               t.Errorf("CreateConfigMap failed: %v -> %v", err, validationErrors)
+               t.Errorf("BuildConfigMap failed: %v -> %v", err, cmString)
        }
 }
 
 func TestUpdateConfigMapFails(t *testing.T) {
        name := "dummy-xapp"
-       validationErrors, err := NewCM().UpdateConfigMap(models.XAppConfig{Metadata: &models.ConfigMetadata{Name: &name}})
+       namespace := "ricxapp"
+       config := models.XAppConfig{Metadata: &models.ConfigMetadata{XappName: &name, Namespace: &namespace}}
+
+       validationErrors, err := NewCM().UpdateConfigMap(config)
        if err == nil {
-               t.Errorf("CreateConfigMap failed: %v -> %v", err, validationErrors)
+               t.Errorf("UpdateConfigMap failed: %v -> %v", err, validationErrors)
        }
 }
 
 func TestValidationSuccess(t *testing.T) {
        var d interface{}
        var cfg map[string]interface{}
-       err := json.Unmarshal([]byte(`{"local": {"host": ":8080"}, "logger": {"level": 3}}`), &cfg)
+       err := json.Unmarshal([]byte(`{"active": true, "interfaceId":{"globalENBId": {"eNBId": 77, "plmnId": "6666"}}}`), &cfg)
 
        err = NewCM().ReadFile("../../test/schema.json", &d)
        if err != nil {
@@ -299,7 +225,7 @@ func TestValidationSuccess(t *testing.T) {
 func TestValidationFails(t *testing.T) {
        var d interface{}
        var cfg map[string]interface{}
-       err := json.Unmarshal([]byte(`{"local": {"host": ":8080"}, "logger": {"level": "INVALID"}}`), &cfg)
+       err := json.Unmarshal([]byte(`{"active": "INVALID", "interfaceId":{"globalENBId": {"eNBId": 77, "plmnId": "6666"}}}`), &cfg)
 
        err = NewCM().ReadFile("../../test/schema.json", &d)
        if err != nil {
index 4f847a2..886d015 100755 (executable)
@@ -85,28 +85,25 @@ func (h *Helm) Init() (out []byte, err error) {
 
 func (h *Helm) AddRepo() (out []byte, err error) {
        // Get helm repo user name and password from files mounted by secret object
-       credFile, err := ioutil.ReadFile(viper.GetString("helm.helm-username-file"))
+       username, err := ioutil.ReadFile(viper.GetString("helm.helm-username-file"))
        if err != nil {
                appmgr.Logger.Info("helm_repo_username ReadFile failed: %v", err.Error())
                return
        }
-       username := " --username " + string(credFile)
 
-       credFile, err = ioutil.ReadFile(viper.GetString("helm.helm-password-file"))
+       password, err := ioutil.ReadFile(viper.GetString("helm.helm-password-file"))
        if err != nil {
                appmgr.Logger.Info("helm_repo_password ReadFile failed: %v", err.Error())
                return
        }
-       pwd := " --password " + string(credFile)
 
-       rname := viper.GetString("helm.repo-name")
-       repo := viper.GetString("helm.repo")
+       repoArgs := fmt.Sprintf(" %s %s ", viper.GetString("helm.repo-name"), viper.GetString("helm.repo"))
+       credentials := fmt.Sprintf(" --username %s --password %s", string(username), string(password))
 
-       return util.HelmExec(strings.Join([]string{"repo add ", rname, " ", repo, username, pwd}, ""))
+       return util.HelmExec(strings.Join([]string{"repo add ", repoArgs, credentials}, ""))
 }
 
 func (h *Helm) Install(m models.XappDescriptor) (xapp models.Xapp, err error) {
-       var c interface{}
        m.Namespace = h.cm.GetNamespace(m.Namespace)
 
        out, err := h.Run(strings.Join([]string{"repo update "}, ""))
@@ -114,29 +111,10 @@ func (h *Helm) Install(m models.XappDescriptor) (xapp models.Xapp, err error) {
                return
        }
 
-       if err = h.cm.GetConfigMap(m, &c); err != nil {
-               out, err = h.Run(h.GetInstallArgs(m, false))
-               if err != nil {
-                       return
-               }
-               return h.ParseStatus(*m.XappName, string(out))
-       }
-
-       // ConfigMap exists, try to override
-       out, err = h.Run(h.GetInstallArgs(m, true))
-       if err == nil {
-               return h.ParseStatus(*m.XappName, string(out))
-       }
-
-       c, cmErr := h.cm.PurgeConfigMap(m)
        out, err = h.Run(h.GetInstallArgs(m, false))
        if err != nil {
                return
        }
-
-       if cmErr == nil {
-               cmErr = h.cm.RestoreConfigMap(m, c)
-       }
        return h.ParseStatus(*m.XappName, string(out))
 }
 
@@ -308,7 +286,8 @@ func (h *Helm) ParseStatus(name string, out string) (xapp models.Xapp, err error
 func (h *Helm) parseAllStatus(names []string) (xapps models.AllDeployedXapps, err error) {
        xapps = models.AllDeployedXapps{}
        for _, name := range names {
-               err := h.cm.ReadSchema(name, &models.XAppConfig{})
+               var desc interface{}
+               err := h.cm.ReadSchema(name, &desc)
                if err != nil {
                        continue
                }
@@ -333,19 +312,19 @@ func (h *Helm) AddTillerEnv() (err error) {
 }
 
 func (h *Helm) GetInstallArgs(x models.XappDescriptor, cmOverride bool) (args string) {
-       args = args + " --namespace=" + x.Namespace
+       args = fmt.Sprintf("%s --namespace=%s", args, x.Namespace)
        if x.HelmVersion != "" {
-               args = args + " --version=" + x.HelmVersion
+               args = fmt.Sprintf("%s --version=%s", args, x.HelmVersion)
        }
 
        if x.ReleaseName != "" {
-               args = args + " --name=" + x.ReleaseName
+               args = fmt.Sprintf("%s --name=%s", args, x.ReleaseName)
        } else {
-               args = args + " --name=" + *x.XappName
+               args = fmt.Sprintf("%s --name=%s", args, *x.XappName)
        }
 
        if cmOverride == true {
-               args = args + " --set ricapp.appconfig.override=" + *x.XappName + "-appconfig"
+               args = fmt.Sprintf("%s ---set ricapp.appconfig.override=%s-appconfig", args, *x.XappName)
        }
 
        if x.OverrideFile != nil {
index 9bc1226..2c08afb 100755 (executable)
@@ -85,7 +85,6 @@ func TestMain(m *testing.M) {
 }
 
 func TestHelmStatus(t *testing.T) {
-       //NewHelm().SetCM(&ConfigMap{})
        util.KubectlExec = func(args string) (out []byte, err error) {
                return []byte("10.102.184.212"), nil
        }
index b2a94e3..e4c149e 100755 (executable)
@@ -20,7 +20,6 @@
 package restful
 
 import (
-       //"github.com/spf13/viper"
        "log"
        "os"
        "time"
@@ -77,7 +76,8 @@ func (r *Restful) SetupHandler() *operations.AppManagerAPI {
                func(params health.GetHealthAliveParams) middleware.Responder {
                        return health.NewGetHealthAliveOK()
                })
-       api.HealthGetHealthReadyHandler = health.GetHealthReadyHandlerFunc(
+
+               api.HealthGetHealthReadyHandler = health.GetHealthReadyHandlerFunc(
                func(params health.GetHealthReadyParams) middleware.Responder {
                        return health.NewGetHealthReadyOK()
                })
@@ -87,6 +87,7 @@ func (r *Restful) SetupHandler() *operations.AppManagerAPI {
                func(params operations.GetSubscriptionsParams) middleware.Responder {
                        return operations.NewGetSubscriptionsOK().WithPayload(r.rh.GetAllSubscriptions())
                })
+
        api.GetSubscriptionByIDHandler = operations.GetSubscriptionByIDHandlerFunc(
                func(params operations.GetSubscriptionByIDParams) middleware.Responder {
                        if result, found := r.rh.GetSubscriptionById(params.SubscriptionID); found {
@@ -94,10 +95,12 @@ func (r *Restful) SetupHandler() *operations.AppManagerAPI {
                        }
                        return operations.NewGetSubscriptionByIDNotFound()
                })
+
        api.AddSubscriptionHandler = operations.AddSubscriptionHandlerFunc(
                func(params operations.AddSubscriptionParams) middleware.Responder {
                        return operations.NewAddSubscriptionCreated().WithPayload(r.rh.AddSubscription(*params.SubscriptionRequest))
                })
+
        api.ModifySubscriptionHandler = operations.ModifySubscriptionHandlerFunc(
                func(params operations.ModifySubscriptionParams) middleware.Responder {
                        if _, ok := r.rh.ModifySubscription(params.SubscriptionID, *params.SubscriptionRequest); ok {
@@ -105,6 +108,7 @@ func (r *Restful) SetupHandler() *operations.AppManagerAPI {
                        }
                        return operations.NewModifySubscriptionBadRequest()
                })
+
        api.DeleteSubscriptionHandler = operations.DeleteSubscriptionHandlerFunc(
                func(params operations.DeleteSubscriptionParams) middleware.Responder {
                        if _, ok := r.rh.DeleteSubscription(params.SubscriptionID); ok {
@@ -121,6 +125,7 @@ func (r *Restful) SetupHandler() *operations.AppManagerAPI {
                        }
                        return xapp.NewGetAllXappsInternalServerError()
                })
+
        api.XappListAllXappsHandler = xapp.ListAllXappsHandlerFunc(
                func(params xapp.ListAllXappsParams) middleware.Responder {
                        if result := r.helm.SearchAll(); err == nil {
@@ -128,6 +133,7 @@ func (r *Restful) SetupHandler() *operations.AppManagerAPI {
                        }
                        return xapp.NewListAllXappsInternalServerError()
                })
+
        api.XappGetXappByNameHandler = xapp.GetXappByNameHandlerFunc(
                func(params xapp.GetXappByNameParams) middleware.Responder {
                        if result, err := r.helm.Status(params.XAppName); err == nil {
@@ -135,6 +141,7 @@ func (r *Restful) SetupHandler() *operations.AppManagerAPI {
                        }
                        return xapp.NewGetXappByNameNotFound()
                })
+
        api.XappGetXappInstanceByNameHandler = xapp.GetXappInstanceByNameHandlerFunc(
                func(params xapp.GetXappInstanceByNameParams) middleware.Responder {
                        if result, err := r.helm.Status(params.XAppName); err == nil {
@@ -146,6 +153,7 @@ func (r *Restful) SetupHandler() *operations.AppManagerAPI {
                        }
                        return xapp.NewGetXappInstanceByNameNotFound()
                })
+
        api.XappDeployXappHandler = xapp.DeployXappHandlerFunc(
                func(params xapp.DeployXappParams) middleware.Responder {
                        if result, err := r.helm.Install(*params.XappDescriptor); err == nil {
@@ -154,6 +162,7 @@ func (r *Restful) SetupHandler() *operations.AppManagerAPI {
                        }
                        return xapp.NewUndeployXappInternalServerError()
                })
+
        api.XappUndeployXappHandler = xapp.UndeployXappHandlerFunc(
                func(params xapp.UndeployXappParams) middleware.Responder {
                        if result, err := r.helm.Delete(params.XAppName); err == nil {
@@ -166,25 +175,18 @@ func (r *Restful) SetupHandler() *operations.AppManagerAPI {
        // URL: /ric/v1/config
        api.XappGetAllXappConfigHandler = xapp.GetAllXappConfigHandlerFunc(
                func(params xapp.GetAllXappConfigParams) middleware.Responder {
-                       return xapp.NewGetAllXappConfigOK().WithPayload(r.cm.UploadConfig())
+                       return xapp.NewGetAllXappConfigOK().WithPayload(r.cm.UploadConfigAll())
                })
-       api.XappCreateXappConfigHandler = xapp.CreateXappConfigHandlerFunc(
-               func(params xapp.CreateXappConfigParams) middleware.Responder {
-                       result, err := r.cm.CreateConfigMap(*params.XAppConfig)
-                       if err == nil {
-                               if err.Error() != "Validation failed!" {
-                                       return xapp.NewCreateXappConfigInternalServerError()
-                               } else {
-                                       return xapp.NewCreateXappConfigUnprocessableEntity()
-                               }
-                       }
-                       r.rh.PublishSubscription(models.Xapp{}, models.EventTypeCreated)
-                       return xapp.NewCreateXappConfigCreated().WithPayload(result)
+
+       api.XappGetConfigElementHandler = xapp.GetConfigElementHandlerFunc(
+               func(params xapp.GetConfigElementParams) middleware.Responder {
+                       return xapp.NewGetConfigElementOK().WithPayload(r.cm.UploadConfigElement(params.Element))
                })
+
        api.XappModifyXappConfigHandler = xapp.ModifyXappConfigHandlerFunc(
                func(params xapp.ModifyXappConfigParams) middleware.Responder {
                        result, err := r.cm.UpdateConfigMap(*params.XAppConfig)
-                       if err == nil {
+                       if err != nil {
                                if err.Error() != "Validation failed!" {
                                        return xapp.NewModifyXappConfigInternalServerError()
                                } else {
@@ -194,25 +196,6 @@ func (r *Restful) SetupHandler() *operations.AppManagerAPI {
                        r.rh.PublishSubscription(models.Xapp{}, models.EventTypeModified)
                        return xapp.NewModifyXappConfigOK().WithPayload(result)
                })
-       api.XappDeleteXappConfigHandler = xapp.DeleteXappConfigHandlerFunc(
-               func(params xapp.DeleteXappConfigParams) middleware.Responder {
-                       _, err := r.cm.DeleteConfigMap(*params.ConfigMetadata)
-                       if err == nil {
-                               return xapp.NewDeleteXappConfigInternalServerError()
-                       }
-                       r.rh.PublishSubscription(models.Xapp{}, models.EventTypeDeleted)
-                       return xapp.NewDeleteXappConfigNoContent()
-               })
-
-       // LCM: /xapps/{xAppName}/instances/{xAppInstanceName}/stop/start
-       api.XappStartXappInstanceByNameHandler = xapp.StartXappInstanceByNameHandlerFunc(
-               func(params xapp.StartXappInstanceByNameParams) middleware.Responder {
-                       return xapp.NewStartXappInstanceByNameOK()
-               })
-       api.XappStopXappInstanceByNameHandler = xapp.StopXappInstanceByNameHandlerFunc(
-               func(params xapp.StopXappInstanceByNameParams) middleware.Responder {
-                       return xapp.NewStopXappInstanceByNameOK()
-               })
 
        return api
 }
@@ -232,11 +215,11 @@ func (r *Restful) PublishXappCreateEvent(params xapp.DeployXappParams) {
        }
 
        for i := 0; i < 5; i++ {
+               time.Sleep(time.Duration(5) * time.Second)
                if result, _ := r.helm.Status(name); result.Instances != nil {
                        r.rh.PublishSubscription(result, models.EventTypeDeployed)
                        break
                }
-               time.Sleep(time.Duration(5) * time.Second)
        }
 }
 
index bc89da8..f7b8ce4 100755 (executable)
@@ -5,42 +5,58 @@
   "type": "object",
   "title": "The Root Schema",
   "required": [
-    "local",
-    "logger"
+    "active",
+    "interfaceId"
   ],
   "properties": {
-    "local": {
-      "$id": "#/properties/local",
-      "type": "object",
-      "title": "The Local Schema",
-      "required": [
-        "host"
-      ],
-      "properties": {
-        "host": {
-          "$id": "#/properties/local/properties/host",
-          "type": "string",
-          "title": "The Host Schema",
-          "default": "",
-          "pattern": "^(.*)$"
-        }
-      }
+    "active": {
+      "$id": "#/properties/active",
+      "type": "boolean",
+      "title": "The Active Schema",
+      "default": false,
+      "examples": [
+        false
+      ]
     },
-    "logger": {
-      "$id": "#/properties/logger",
+    "interfaceId": {
+      "$id": "#/properties/interfaceId",
       "type": "object",
-      "title": "The Logger Schema",
+      "title": "The Interfaceid Schema",
       "required": [
-        "level"
+        "globalENBId"
       ],
       "properties": {
-        "level": {
-          "$id": "#/properties/logger/properties/level",
-          "type": "integer",
-          "title": "The Level Schema",
-          "default": 0
+        "globalENBId": {
+          "$id": "#/properties/interfaceId/properties/globalENBId",
+          "type": "object",
+          "title": "The Globalenbid Schema",
+          "required": [
+            "plmnId",
+            "eNBId"
+          ],
+          "properties": {
+            "plmnId": {
+              "$id": "#/properties/interfaceId/properties/globalENBId/properties/plmnId",
+              "type": "string",
+              "title": "The Plmnid Schema",
+              "default": "",
+              "examples": [
+                "310150"
+              ],
+              "pattern": "^(.*)$"
+            },
+            "eNBId": {
+              "$id": "#/properties/interfaceId/properties/globalENBId/properties/eNBId",
+              "type": "integer",
+              "title": "The Enbid Schema",
+              "default": 0,
+              "examples": [
+                202251
+              ]
+            }
+          }
         }
       }
     }
   }
-}
\ No newline at end of file
+}