--- /dev/null
+build/cache/*
+cache/*
"swagger": "2.0",
"info": {
"description": "This is a draft API for RIC appmgr",
- "version": "0.0.11",
+ "version": "0.1.2",
"title": "RIC appmgr",
"license": {
"name": "Apache 2.0",
"/health": {
"get": {
"summary": "Health check of xApp Manager",
+ "tags": [
+ "health"
+ ],
"operationId": "getHealth",
"responses": {
"200": {
"/xapps": {
"post": {
"summary": "Deploy a xapp",
+ "tags": [
+ "xapp"
+ ],
"operationId": "deployXapp",
"consumes": [
"application/json"
},
"get": {
"summary": "Returns the status of all xapps",
+ "tags": [
+ "xapp"
+ ],
"operationId": "getAllXapps",
"produces": [
"application/json"
"/xapps/{xAppName}": {
"get": {
"summary": "Returns the status of a given xapp",
+ "tags": [
+ "xapp"
+ ],
"operationId": "getXappByName",
"produces": [
"application/json"
},
"delete": {
"summary": "Undeploy an existing xapp",
+ "tags": [
+ "xapp"
+ ],
"operationId": "undeployXapp",
"parameters": [
{
"/xapps/{xAppName}/instances/{xAppInstanceName}": {
"get": {
"summary": "Returns the status of a given xapp",
+ "tags": [
+ "xapp"
+ ],
"operationId": "getXappInstanceByName",
"produces": [
"application/json"
}
}
},
+ "/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/xAppConfig"
+ }
+ },
+ "400": {
+ "description": "Invalid input"
+ },
+ "422": {
+ "description": "Validation of configuration failed"
+ },
+ "500": {
+ "description": "Internal error"
+ }
+ }
+ },
+ "put": {
+ "summary": "Modify xApp config",
+ "tags": [
+ "xapp"
+ ],
+ "operationId": "ModifyXappConfig",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "xAppConfig",
+ "in": "body",
+ "description": "xApp config",
+ "schema": {
+ "$ref": "#/definitions/xAppConfig"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "xApp config successfully modified",
+ "schema": {
+ "$ref": "#/definitions/xAppConfig"
+ }
+ },
+ "400": {
+ "description": "Invalid input"
+ },
+ "422": {
+ "description": "Validation of configuration failed"
+ },
+ "500": {
+ "description": "Internal error"
+ }
+ }
+ },
+ "get": {
+ "summary": "Returns the configuration of all xapps",
+ "tags": [
+ "xapp"
+ ],
+ "operationId": "getAllXappConfig",
+ "produces": [
+ "application/json"
+ ],
+ "responses": {
+ "200": {
+ "description": "successful query of xApp config",
+ "schema": {
+ "$ref": "#/definitions/AllXappConfig"
+ }
+ },
+ "500": {
+ "description": "Internal error"
+ }
+ }
+ },
+ "delete": {
+ "summary": "Delete xApp configuration",
+ "tags": [
+ "xapp"
+ ],
+ "operationId": "deleteXappConfig",
+ "parameters": [
+ {
+ "name": "xAppConfigInfo",
+ "in": "body",
+ "description": "xApp configuration information",
+ "schema": {
+ "$ref": "#/definitions/xAppConfigInfo"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Successful deletion of xApp"
+ },
+ "400": {
+ "description": "Invalid parameters supplied"
+ },
+ "500": {
+ "description": "Internal error"
+ }
+ }
+ }
+ },
"/subscriptions": {
"post": {
"summary": "Subscribe event",
+ "tags": [
+ "xapp",
+ "subscriptions"
+ ],
"operationId": "addSubscription",
"consumes": [
"application/json"
},
"get": {
"summary": "Returns all subscriptions",
+ "tags": [
+ "xapp",
+ "subscriptions"
+ ],
"operationId": "getSubscriptions",
"produces": [
"application/json"
"/subscriptions/{subscriptionId}": {
"get": {
"summary": "Returns the information of subscription",
+ "tags": [
+ "xapp",
+ "subscriptions"
+ ],
"operationId": "getSubscriptionById",
"produces": [
"application/json"
},
"put": {
"summary": "Modify event subscription",
+ "tags": [
+ "xapp",
+ "subscriptions"
+ ],
"operationId": "modifySubscription",
"consumes": [
"application/json"
},
"delete": {
"summary": "Unsubscribe event",
+ "tags": [
+ "xapp",
+ "subscriptions"
+ ],
"description": "",
"operationId": "deleteSubscription",
"parameters": [
}
}
},
+ "xAppConfigInfo": {
+ "type": "object",
+ "required": [
+ "xAppName",
+ "configMapName",
+ "namespace"
+ ],
+ "properties": {
+ "xAppName": {
+ "type":"string",
+ "description":"Name of the xApp",
+ "example": "xapp-dummy"
+ },
+ "configMapName": {
+ "type":"string",
+ "description":"Name of the config map",
+ "example": "xapp-dummy-config-map"
+ },
+ "namespace": {
+ "type":"string",
+ "description":"Name of the namespace",
+ "example": "ricxapp"
+ }
+ }
+ },
+ "xAppConfig": {
+ "type": "object",
+ "required": [
+ "xAppConfigInfo",
+ "configSchema",
+ "configMap"
+ ],
+ "properties": {
+ "xAppConfigInfo": {
+ "$ref": "#/definitions/xAppConfigInfo"
+ },
+ "configSchema": {
+ "type":"object",
+ "description":"Schema of configuration in JSON format"
+ },
+ "configMap": {
+ "type":"object",
+ "description":"Configuration in JSON format"
+ }
+ }
+ },
+ "AllXappConfig": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/xAppConfig"
+ }
+ },
"subscriptionRequest": {
"type": "object",
"required": [
--- /dev/null
+swagger: '2.0'
+info:
+ description: This is a draft API for RIC appmgr
+ version: 0.1.2
+ title: RIC appmgr
+ license:
+ name: Apache 2.0
+ url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
+host: hostname
+basePath: /ric/v1
+schemes:
+ - https
+ - http
+paths:
+ /health:
+ get:
+ summary: Health check of xApp Manager
+ tags:
+ - health
+ operationId: getHealth
+ responses:
+ '200':
+ description: Status of xApp Manager is ok
+ /xapps:
+ post:
+ summary: Deploy a xapp
+ tags:
+ - xapp
+ operationId: deployXapp
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - name: xAppInfo
+ in: body
+ description: xApp information
+ schema:
+ type: object
+ required:
+ - xAppName
+ properties:
+ xAppName:
+ type: string
+ description: Name of the xApp
+ example: xapp-dummy
+ responses:
+ '201':
+ description: xApp successfully created
+ schema:
+ $ref: '#/definitions/Xapp'
+ '400':
+ description: Invalid input
+ '500':
+ description: Internal error
+ get:
+ summary: Returns the status of all xapps
+ tags:
+ - xapp
+ operationId: getAllXapps
+ produces:
+ - application/json
+ responses:
+ '200':
+ description: successful query of xApps
+ schema:
+ $ref: '#/definitions/AllXapps'
+ '500':
+ description: Internal error
+ '/xapps/{xAppName}':
+ get:
+ summary: Returns the status of a given xapp
+ tags:
+ - xapp
+ operationId: getXappByName
+ produces:
+ - application/json
+ parameters:
+ - name: xAppName
+ in: path
+ description: Name of xApp
+ required: true
+ type: string
+ responses:
+ '200':
+ description: successful operation
+ schema:
+ $ref: '#/definitions/Xapp'
+ '400':
+ description: Invalid ID supplied
+ '404':
+ description: Xapp not found
+ '500':
+ description: Internal error
+ delete:
+ summary: Undeploy an existing xapp
+ tags:
+ - xapp
+ operationId: undeployXapp
+ parameters:
+ - name: xAppName
+ in: path
+ description: Xapp to be undeployed
+ required: true
+ type: string
+ responses:
+ '204':
+ description: Successful deletion of xApp
+ '400':
+ description: Invalid xApp name supplied
+ '500':
+ description: Internal error
+ '/xapps/{xAppName}/instances/{xAppInstanceName}':
+ get:
+ summary: Returns the status of a given xapp
+ tags:
+ - xapp
+ operationId: getXappInstanceByName
+ 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
+ schema:
+ $ref: '#/definitions/XappInstance'
+ '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/xAppConfig'
+ '400':
+ description: Invalid input
+ '422':
+ description: Validation of configuration failed
+ '500':
+ description: Internal error
+ put:
+ summary: Modify xApp config
+ tags:
+ - xapp
+ operationId: ModifyXappConfig
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - name: xAppConfig
+ in: body
+ description: xApp config
+ schema:
+ $ref: '#/definitions/xAppConfig'
+ responses:
+ '200':
+ description: xApp config successfully modified
+ schema:
+ $ref: '#/definitions/xAppConfig'
+ '400':
+ description: Invalid input
+ '422':
+ description: Validation of configuration failed
+ '500':
+ description: Internal error
+ get:
+ summary: Returns the configuration of all xapps
+ tags:
+ - xapp
+ operationId: getAllXappConfig
+ produces:
+ - application/json
+ responses:
+ '200':
+ description: successful query of xApp config
+ schema:
+ $ref: '#/definitions/AllXappConfig'
+ '500':
+ description: Internal error
+ delete:
+ summary: Delete xApp configuration
+ tags:
+ - xapp
+ operationId: deleteXappConfig
+ parameters:
+ - name: xAppConfigInfo
+ in: body
+ description: xApp configuration information
+ schema:
+ $ref: '#/definitions/xAppConfigInfo'
+ responses:
+ '204':
+ description: Successful deletion of xApp
+ '400':
+ description: Invalid parameters supplied
+ '500':
+ description: Internal error
+ /subscriptions:
+ post:
+ summary: Subscribe event
+ tags:
+ - xapp
+ - subscriptions
+ operationId: addSubscription
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - name: subscriptionRequest
+ in: body
+ description: New subscription
+ required: true
+ schema:
+ $ref: '#/definitions/subscriptionRequest'
+ responses:
+ '200':
+ description: Subscription successful
+ schema:
+ $ref: '#/definitions/subscriptionResponse'
+ '400':
+ description: Invalid input
+ get:
+ summary: Returns all subscriptions
+ tags:
+ - xapp
+ - subscriptions
+ operationId: getSubscriptions
+ produces:
+ - application/json
+ responses:
+ '200':
+ description: successful query of subscriptions
+ schema:
+ $ref: '#/definitions/allSubscriptions'
+ '/subscriptions/{subscriptionId}':
+ get:
+ summary: Returns the information of subscription
+ tags:
+ - xapp
+ - subscriptions
+ operationId: getSubscriptionById
+ produces:
+ - application/json
+ parameters:
+ - name: subscriptionId
+ in: path
+ description: ID of subscription
+ required: true
+ type: integer
+ responses:
+ '200':
+ description: successful operation
+ schema:
+ $ref: '#/definitions/subscription'
+ '400':
+ description: Invalid ID supplied
+ '404':
+ description: Subscription not found
+ put:
+ summary: Modify event subscription
+ tags:
+ - xapp
+ - subscriptions
+ operationId: modifySubscription
+ consumes:
+ - application/json
+ produces:
+ - application/json
+ parameters:
+ - name: subscriptionId
+ in: path
+ description: ID of subscription
+ required: true
+ type: integer
+ - in: body
+ name: subscriptionRequest
+ description: Modified subscription
+ required: true
+ schema:
+ $ref: '#/definitions/subscriptionRequest'
+ responses:
+ '200':
+ description: Subscription modification successful
+ schema:
+ $ref: '#/definitions/subscriptionResponse'
+ '400':
+ description: Invalid input
+ delete:
+ summary: Unsubscribe event
+ tags:
+ - xapp
+ - subscriptions
+ description: ''
+ operationId: deleteSubscription
+ parameters:
+ - name: subscriptionId
+ in: path
+ description: ID of subscription
+ required: true
+ type: integer
+ responses:
+ '204':
+ description: Successful deletion of subscription
+ '400':
+ description: Invalid subscription supplied
+definitions:
+ AllXapps:
+ type: array
+ items:
+ $ref: '#/definitions/Xapp'
+ Xapp:
+ type: object
+ required:
+ - name
+ properties:
+ name:
+ type: string
+ example: xapp-dummy
+ status:
+ type: string
+ description: xapp status in the RIC
+ enum:
+ - unknown
+ - deployed
+ - deleted
+ - superseded
+ - failed
+ - deleting
+ version:
+ type: string
+ example: 1.2.3
+ instances:
+ type: array
+ items:
+ $ref: '#/definitions/XappInstance'
+ XappInstance:
+ type: object
+ required:
+ - name
+ properties:
+ name:
+ type: string
+ example: xapp-dummy-6cd577d9-4v255
+ status:
+ type: string
+ description: xapp instance status
+ enum:
+ - pending
+ - running
+ - succeeded
+ - failed
+ - unknown
+ - completed
+ - crashLoopBackOff
+ ip:
+ type: string
+ example: 192.168.0.1
+ port:
+ type: integer
+ example: 32300
+ txMessages:
+ type: array
+ items:
+ type: string
+ example: ControlIndication
+ rxMessages:
+ type: array
+ items:
+ type: string
+ example: LoadIndication
+ xAppConfigInfo:
+ type: object
+ required:
+ - xAppName
+ - configMapName
+ - namespace
+ properties:
+ xAppName:
+ type: string
+ description: Name of the xApp
+ example: xapp-dummy
+ configMapName:
+ type: string
+ description: Name of the config map
+ example: xapp-dummy-config-map
+ namespace:
+ type: string
+ description: Name of the namespace
+ example: ricxapp
+ xAppConfig:
+ type: object
+ required:
+ - xAppConfigInfo
+ - configSchema
+ - configMap
+ properties:
+ xAppConfigInfo:
+ $ref: '#/definitions/xAppConfigInfo'
+ configSchema:
+ type: object
+ description: Schema of configuration in JSON format
+ configMap:
+ type: object
+ description: Configuration in JSON format
+ AllXappConfig:
+ type: array
+ items:
+ $ref: '#/definitions/xAppConfig'
+ subscriptionRequest:
+ type: object
+ required:
+ - targetUrl
+ - eventType
+ - maxRetries
+ - retryTimer
+ properties:
+ targetUrl:
+ type: string
+ example: 'http://localhost:11111/apps/webhook/'
+ eventType:
+ type: string
+ description: Event which is subscribed
+ enum:
+ - created
+ - deleted
+ - all
+ maxRetries:
+ type: integer
+ description: Maximum number of retries
+ example: 11
+ retryTimer:
+ type: integer
+ description: Time in seconds to wait before next retry
+ example: 22
+ subscriptionResponse:
+ type: object
+ properties:
+ id:
+ type: string
+ example: 1ILBltYYzEGzWRrVPZKmuUmhwcc
+ version:
+ type: integer
+ example: 2
+ eventType:
+ type: string
+ description: Event which is subscribed
+ enum:
+ - created
+ - deleted
+ - updated
+ - all
+ allSubscriptions:
+ type: array
+ items:
+ $ref: '#/definitions/subscription'
+ subscription:
+ type: object
+ properties:
+ id:
+ type: string
+ example: 1ILBltYYzEGzWRrVPZKmuUmhwcc
+ targetUrl:
+ type: string
+ example: 'http://localhost:11111/apps/webhook/'
+ eventType:
+ type: string
+ description: Event which is subscribed
+ enum:
+ - created
+ - deleted
+ - updated
+ - all
+ maxRetries:
+ type: integer
+ description: Maximum number of retries
+ example: 11
+ retryTimer:
+ type: integer
+ description: Time in seconds to wait before next retry
+ example: 22
+ subscriptionNotification:
+ type: object
+ properties:
+ id:
+ type: string
+ example: 1ILBltYYzEGzWRrVPZKmuUmhwcc
+ version:
+ type: integer
+ example: 2
+ eventType:
+ type: string
+ description: Event to be notified
+ enum:
+ - created
+ - deleted
+ - updated
+ xApps:
+ $ref: '#/definitions/AllXapps'
// API functions
-func (m *XappManager) Initialize(h Helmer) {
+func (m *XappManager) Initialize(h Helmer, cm ConfigMapper) {
+ m.cm = cm
+ m.helm = h
+ m.helm.SetCM(cm)
+
m.router = mux.NewRouter().StrictSlash(true)
resources := []Resource{
{"GET", "/ric/v1/config", m.getConfig},
{"POST", "/ric/v1/config", m.createConfig},
+ {"PUT", "/ric/v1/config", m.updateConfig},
{"DELETE", "/ric/v1/config", m.deleteConfig},
}
m.sd = SubscriptionDispatcher{}
m.sd.Initialize()
- m.helm = h
m.helm.Initialize()
m.notifyClients()
return
}
- var cm ConfigMetadata
+ var cm XappDeploy
if err := json.NewDecoder(r.Body).Decode(&cm); err != nil {
mdclog(MdclogErr, "Invalid xapp data in request body - url="+r.URL.RequestURI())
respondWithError(w, http.StatusMethodNotAllowed, "Invalid xapp data!")
}
func (m *XappManager) getConfig(w http.ResponseWriter, r *http.Request) {
- respondWithJSON(w, http.StatusOK, UploadConfig())
+ cfg := m.cm.UploadConfig()
+ respondWithJSON(w, http.StatusOK, cfg)
}
func (m *XappManager) createConfig(w http.ResponseWriter, r *http.Request) {
return
}
- if err := CreateConfigMap(c); err != nil {
- respondWithError(w, http.StatusInternalServerError, err.Error())
+ if errList, err := m.cm.CreateConfigMap(c); err != nil {
+ if err.Error() != "Validation failed!" {
+ respondWithError(w, http.StatusInternalServerError, err.Error())
+ } else {
+ respondWithJSON(w, http.StatusUnprocessableEntity, errList)
+ }
return
}
respondWithJSON(w, http.StatusCreated, nil)
}
+func (m *XappManager) updateConfig(w http.ResponseWriter, r *http.Request) {
+ var c XAppConfig
+ if parseConfig(w, r, &c) != nil {
+ return
+ }
+
+ if errList, err := m.cm.UpdateConfigMap(c); err != nil {
+ if err.Error() != "Validation failed!" {
+ respondWithError(w, http.StatusInternalServerError, err.Error())
+ } else {
+ respondWithJSON(w, http.StatusUnprocessableEntity, errList)
+ }
+ return
+ }
+ respondWithJSON(w, http.StatusOK, nil)
+}
+
func (m *XappManager) deleteConfig(w http.ResponseWriter, r *http.Request) {
var c XAppConfig
if parseConfig(w, r, &c) != nil {
return
}
- if _, err := DeleteConfigMap(c); err != nil {
+ if _, err := m.cm.DeleteConfigMap(c); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
type MockedHelmer struct {
}
+func (h *MockedHelmer) SetCM(cm ConfigMapper) {
+}
+
func (sd *MockedHelmer) Initialize() {
}
return names, helmError
}
-func (h *MockedHelmer) Install(m ConfigMetadata) (Xapp, error) {
+func (h *MockedHelmer) Install(m XappDeploy) (Xapp, error) {
return xapp, helmError
}
xapp = Xapp{}
xapps = []Xapp{}
+ cm := MockedConfigMapper{}
h := MockedHelmer{}
x = XappManager{}
- x.Initialize(&h)
+ x.Initialize(&h, &cm)
// Just run on the background (for coverage)
go x.Run()
checkResponseCode(t, http.StatusNotFound, response.Code)
}
+func TestGetConfigReturnsEmpty(t *testing.T) {
+ req, _ := http.NewRequest("GET", "/ric/v1/config", nil)
+ response := executeRequest(req)
+
+ checkResponseCode(t, http.StatusOK, response.Code)
+}
+
+func TestCreateConfigFailsWithMethodNotAllowed(t *testing.T) {
+ req, _ := http.NewRequest("POST", "/ric/v1/config", nil)
+ response := executeRequest(req)
+
+ checkResponseCode(t, http.StatusMethodNotAllowed, response.Code)
+}
+
+func TestCreateConfigOk(t *testing.T) {
+ payload := []byte(`{"name":"dummy-xapp"}`)
+ req, _ := http.NewRequest("POST", "/ric/v1/config", bytes.NewBuffer(payload))
+ response := executeRequest(req)
+
+ checkResponseCode(t, http.StatusCreated, response.Code)
+}
+
+func TestDeleteConfigOk(t *testing.T) {
+ payload := []byte(`{"name":"dummy-xapp"}`)
+ req, _ := http.NewRequest("DELETE", "/ric/v1/config", bytes.NewBuffer(payload))
+ response := executeRequest(req)
+
+ checkResponseCode(t, http.StatusNotFound, response.Code)
+}
+
// Error handling
func TestGetXappReturnsError(t *testing.T) {
helmError = errors.New("Not found")
x.Status = status
x.Version = ver
p, _ := strconv.Atoi(port)
+ var msgs MessageTypes
+
instance := XappInstance{
Name: iname,
Status: istatus,
Ip: ip,
Port: p,
- TxMessages: []string{"RIC_E2_TERMINATION_HC_REQUEST", "RIC_E2_MANAGER_HC_REQUEST"},
- RxMessages: []string{"RIC_E2_TERMINATION_HC_RESPONSE", "RIC_E2_MANAGER_HC_RESPONSE"},
+ TxMessages: msgs.TxMessages,
+ RxMessages: msgs.RxMessages,
}
x.Instances = append(x.Instances, instance)
Namespace string `json:"namespace"`
}
-func UploadConfig() (cfg []XAppConfig) {
- for _, name := range GetNamesFromHelmRepo() {
+type CMError struct {
+ Field string `json:"field"`
+ Description string `json:"description"`
+}
+
+func (cm *ConfigMap) UploadConfig() (cfg []XAppConfig) {
+ for _, name := range cm.GetNamesFromHelmRepo() {
if name == "appmgr" {
continue
}
Metadata: ConfigMetadata{Name: name, Namespace: "ricxapp", ConfigName: name + "-appconfig"},
}
- err := ReadSchema(name, &c)
+ err := cm.ReadSchema(name, &c)
if err != nil {
continue
}
- err = ReadConfigMap(name, "ricxapp", &c.Configuration)
+ err = cm.ReadConfigMap(c.Metadata.ConfigName, "ricxapp", &c.Configuration)
if err != nil {
log.Println("No active configMap found, using default!")
}
return
}
-func ReadSchema(name string, c *XAppConfig) (err error) {
- if err = FetchChart(name); err != nil {
+func (cm *ConfigMap) ReadSchema(name string, c *XAppConfig) (err error) {
+ if err = cm.FetchChart(name); err != nil {
return
}
tarDir := viper.GetString("xapp.tarDir")
- err = ReadFile(path.Join(tarDir, name, viper.GetString("xapp.schema")), &c.Descriptor)
+ err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.schema")), &c.Descriptor)
if err != nil {
return
}
- err = ReadFile(path.Join(tarDir, name, viper.GetString("xapp.config")), &c.Configuration)
+ err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.config")), &c.Configuration)
if err != nil {
return
}
return
}
-func ReadConfigMap(name string, ns string, c *interface{}) (err error) {
- args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s-appconfig", ns, name)
+func (cm *ConfigMap) 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 := KubectlExec(args)
if err != nil {
return
return
}
-func ApplyConfigMap(r XAppConfig) (err error) {
- cm := ConfigMap{
+func (cm *ConfigMap) ApplyConfigMap(r XAppConfig, action string) (err error) {
+ c := ConfigMap{
Kind: "ConfigMap",
ApiVersion: "v1",
Metadata: CMMetadata{Name: r.Metadata.Name, Namespace: r.Metadata.Namespace},
Data: r.Configuration,
}
- cmJson, err := json.Marshal(cm)
+ cmJson, err := json.Marshal(c)
if err != nil {
log.Println("Config marshalling failed: ", err)
return
return
}
- cmd := " create configmap -n %s %s --from-file=%s -o json --dry-run | kubectl apply -f -"
- args := fmt.Sprintf(cmd, r.Metadata.Namespace, r.Metadata.ConfigName, cmFile)
+ 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 = KubectlExec(args)
if err != nil {
return
}
- log.Println("Configmap changes created!")
+ log.Println("Configmap changes done!")
+
+ return
+}
+func (cm *ConfigMap) CreateConfigMap(r XAppConfig) (errList []CMError, err error) {
+ if errList, err = cm.Validate(r); err != nil {
+ return
+ }
+ err = cm.ApplyConfigMap(r, "create")
return
}
-func CreateConfigMap(r XAppConfig) (err error) {
- if err = Validate(r); err != nil {
+func (cm *ConfigMap) UpdateConfigMap(r XAppConfig) (errList []CMError, err error) {
+ if errList, err = cm.Validate(r); err != nil {
return
}
- return ApplyConfigMap(r)
+
+ // Re-create the configmap with the new parameters
+ err = cm.ApplyConfigMap(r, "apply")
+ return
}
-func DeleteConfigMap(r XAppConfig) (cm interface{}, err error) {
- err = ReadConfigMap(r.Metadata.Name, r.Metadata.Namespace, &cm)
+func (cm *ConfigMap) DeleteConfigMap(r XAppConfig) (c interface{}, err error) {
+ err = cm.ReadConfigMap(r.Metadata.ConfigName, r.Metadata.Namespace, &c)
if err == nil {
args := fmt.Sprintf(" delete configmap --namespace=%s %s", r.Metadata.Namespace, r.Metadata.ConfigName)
_, err = KubectlExec(args)
return
}
-func PurgeConfigMap(m ConfigMetadata) (cm interface{}, err error) {
+func (cm *ConfigMap) PurgeConfigMap(m XappDeploy) (c interface{}, err error) {
if m.ConfigName == "" {
m.ConfigName = m.Name + "-appconfig"
}
- return DeleteConfigMap(XAppConfig{Metadata: m})
+ md := ConfigMetadata{Name: m.Name, Namespace: m.Namespace, ConfigName: m.ConfigName}
+
+ return cm.DeleteConfigMap(XAppConfig{Metadata: md})
}
-func RestoreConfigMap(m ConfigMetadata, cm interface{}) (err error) {
+func (cm *ConfigMap) RestoreConfigMap(m XappDeploy, c interface{}) (err error) {
if m.ConfigName == "" {
m.ConfigName = m.Name + "-appconfig"
}
+ md := ConfigMetadata{Name: m.Name, Namespace: m.Namespace, ConfigName: m.ConfigName}
time.Sleep(time.Duration(10 * time.Second))
- return ApplyConfigMap(XAppConfig{Metadata: m, Configuration: cm})
+ return cm.ApplyConfigMap(XAppConfig{Metadata: md, Configuration: c}, "create")
}
-func GetNamesFromHelmRepo() (names []string) {
+func (cm *ConfigMap) GetNamesFromHelmRepo() (names []string) {
rname := viper.GetString("helm.repo-name")
cmdArgs := strings.Join([]string{"search ", rname}, "")
return names
}
-func Validate(req XAppConfig) (err error) {
+func (cm *ConfigMap) Validate(req XAppConfig) (errList []CMError, err error) {
c := XAppConfig{}
- err = ReadSchema(req.Metadata.Name, &c)
+ err = cm.ReadSchema(req.Metadata.Name, &c)
if err != nil {
log.Printf("No schema file found for '%s', aborting ...", req.Metadata.Name)
- return err
+ return
}
+ return cm.doValidate(c.Descriptor, req.Configuration)
+}
- schemaLoader := gojsonschema.NewGoLoader(c.Descriptor)
- documentLoader := gojsonschema.NewGoLoader(req.Configuration)
+func (cm *ConfigMap) doValidate(schema, cfg interface{}) (errList []CMError, err error) {
+ schemaLoader := gojsonschema.NewGoLoader(schema)
+ documentLoader := gojsonschema.NewGoLoader(cfg)
- log.Println("Starting validation ...")
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
if err != nil {
log.Println("Validation failed: ", err)
return
}
- log.Println("validation done ...", err, result.Valid())
if result.Valid() == false {
log.Println("The document is not valid, Errors: ", result.Errors())
- s := make([]string, 3)
- for i, desc := range result.Errors() {
- s = append(s, fmt.Sprintf(" (%d): %s.\n", i, desc.String()))
+ for _, desc := range result.Errors() {
+ errList = append(errList, CMError{Field: desc.Field(), Description: desc.Description()})
}
- return errors.New(strings.Join(s, " "))
+ return errList, errors.New("Validation failed!")
}
return
}
-func ReadFile(name string, data interface{}) (err error) {
+func (cm *ConfigMap) ReadFile(name string, data interface{}) (err error) {
f, err := ioutil.ReadFile(name)
if err != nil {
log.Printf("Reading '%s' file failed: %v", name, err)
return
}
-func FetchChart(name string) (err error) {
+func (cm *ConfigMap) FetchChart(name string) (err error) {
tarDir := viper.GetString("xapp.tarDir")
repo := viper.GetString("helm.repo-name")
fetchArgs := fmt.Sprintf("--untar --untardir %s %s/%s", tarDir, repo, name)
return
}
-func GetMessages(name string) (msgs MessageTypes, err error) {
+func (cm *ConfigMap) GetMessages(name string) (msgs MessageTypes) {
log.Println("Fetching tx/rx messages for: ", name)
return
}
--- /dev/null
+/*
+==================================================================================
+ Copyright (c) 2019 AT&T Intellectual Property.
+ Copyright (c) 2019 Nokia
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================================
+*/
+
+package main
+
+import (
+ "testing"
+ "reflect"
+ "errors"
+ "encoding/json"
+ "log"
+)
+
+var helmSearchOutput = `
+helm-repo/anr 0.0.1 1.0 Helm Chart for Nokia ANR (Automatic Neighbour Relation) xAPP
+helm-repo/appmgr 0.0.2 1.0 Helm Chart for xAppManager
+helm-repo/dualco 0.0.1 1.0 Helm Chart for Nokia dualco xAPP
+helm-repo/reporter 0.0.1 1.0 Helm Chart for Reporting xAPP
+helm-repo/uemgr 0.0.1 1.0 Helm Chart for Nokia uemgr xAPP
+`
+type ConfigSample struct {
+ Level int
+ Host string
+}
+
+type MockedConfigMapper struct {
+}
+
+func (cm *MockedConfigMapper) UploadConfig() (cfg []XAppConfig) {
+ return
+}
+
+func (cm *MockedConfigMapper) CreateConfigMap(r XAppConfig) (errList []CMError, err error){
+ return
+}
+
+func (cm *MockedConfigMapper) UpdateConfigMap(r XAppConfig) (errList []CMError, err error){
+ return
+}
+
+func (cm *MockedConfigMapper) DeleteConfigMap(r XAppConfig) (c interface{}, err error){
+ return
+}
+
+func (cm *MockedConfigMapper) PurgeConfigMap(m XappDeploy) (c interface{}, err error){
+ return
+}
+
+func (cm *MockedConfigMapper) RestoreConfigMap(m XappDeploy, c interface{}) (err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) ReadConfigMap(name string, ns string, c *interface{}) (err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) ApplyConfigMap(r XAppConfig, action string) (err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) FetchChart(name string) (err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) GetMessages(name string) (msgs MessageTypes) {
+ return
+}
+
+// Test cases
+func TestGetMessages(t *testing.T) {
+ cm := ConfigMap{}
+ expectedMsgs := MessageTypes{}
+
+ if !reflect.DeepEqual(cm.GetMessages("dummy-xapp"), expectedMsgs) {
+ t.Errorf("TestGetMessages failed!")
+ }
+}
+
+func TestFetchChartFails(t *testing.T) {
+ cm := ConfigMap{}
+
+ if cm.FetchChart("dummy-xapp") == nil {
+ t.Errorf("TestFetchChart failed!")
+ }
+}
+
+func TestFetchChartSuccess(t *testing.T) {
+ cm := ConfigMap{}
+
+ HelmExec = func(args string) (out []byte, err error) {
+ return
+ }
+
+ if cm.FetchChart("dummy-xapp") != nil {
+ t.Errorf("TestFetchChart failed!")
+ }
+}
+
+func TestGetNamesFromHelmRepoSuccess(t *testing.T) {
+ cm := ConfigMap{}
+ expectedResult := []string{"anr", "appmgr", "dualco", "reporter", "uemgr"}
+ HelmExec = func(args string) (out []byte, err error) {
+ return []byte(helmSearchOutput), nil
+ }
+
+ names := cm.GetNamesFromHelmRepo()
+ if !reflect.DeepEqual(names, expectedResult) {
+ t.Errorf("GetNamesFromHelmRepo failed: expected %v, got %v", expectedResult, names)
+ }
+}
+
+func TestGetNamesFromHelmRepoFailure(t *testing.T) {
+ cm := ConfigMap{}
+ expectedResult := []string{}
+ HelmExec = func(args string) (out []byte, err error) {
+ return []byte(helmSearchOutput), errors.New("Command failed!")
+ }
+
+ names := cm.GetNamesFromHelmRepo()
+ if names != nil {
+ t.Errorf("GetNamesFromHelmRepo failed: expected %v, got %v", expectedResult, names)
+ }
+}
+
+func TestApplyConfigMapSuccess(t *testing.T) {
+ cm := ConfigMap{}
+ m := ConfigMetadata{Name: "dummy-xapp", Namespace: "ricxapp"}
+ s := ConfigSample{5, "localhost"}
+
+ KubectlExec = func(args string) (out []byte, err error) {
+ log.Println("TestApplyConfigMapSuccess: ", args)
+ return []byte(`{"logger": {"level": 2}}`), nil
+ }
+
+ err := cm.ApplyConfigMap(XAppConfig{Metadata: m, Configuration: s}, "create")
+ if err != nil {
+ t.Errorf("ApplyConfigMap failed: %v", err)
+ }
+}
+
+func TestRestoreConfigMapSuccess(t *testing.T) {
+ cm := ConfigMap{}
+ m := XappDeploy{Name: "dummy-xapp", Namespace: "ricxapp"}
+ s := ConfigSample{5, "localhost"}
+
+ KubectlExec = func(args string) (out []byte, err error) {
+ log.Println("TestRestoreConfigMapSuccess: ", args)
+ return []byte(`{"logger": {"level": 2}}`), nil
+ }
+
+ err := cm.RestoreConfigMap(m, s)
+ if err != nil {
+ t.Errorf("RestoreConfigMap failed: %v", err)
+ }
+}
+
+func TestDeleteConfigMapSuccess(t *testing.T) {
+ cm := ConfigMap{}
+
+ HelmExec = func(args string) (out []byte, err error) {
+ return []byte("ok"), nil
+ }
+
+ KubectlExec = func(args string) (out []byte, err error) {
+ log.Println("TestDeleteConfigMapSuccess: ", args)
+ return []byte(`{"logger": {"level": 2}}`), nil
+ }
+
+ c, err := cm.DeleteConfigMap(XAppConfig{})
+ if err != nil {
+ t.Errorf("DeleteConfigMap failed: %v -> %v", err, c)
+ }
+}
+
+func TestPurgeConfigMapSuccess(t *testing.T) {
+ cm := ConfigMap{}
+
+ HelmExec = func(args string) (out []byte, err error) {
+ return []byte("ok"), nil
+ }
+
+ KubectlExec = func(args string) (out []byte, err error) {
+ return []byte(`{"logger": {"level": 2}}`), nil
+ }
+
+ c, err := cm.PurgeConfigMap(XappDeploy{})
+ if err != nil {
+ t.Errorf("PurgeConfigMap failed: %v -> %v", err, c)
+ }
+}
+
+func TestCreateConfigMapFails(t *testing.T) {
+ cm := ConfigMap{}
+
+ c, err := cm.CreateConfigMap(XAppConfig{})
+ if err == nil {
+ t.Errorf("CreateConfigMap failed: %v -> %v", err, c)
+ }
+}
+
+func TestUpdateConfigMapFails(t *testing.T) {
+ cm := ConfigMap{}
+
+ c, err := cm.UpdateConfigMap(XAppConfig{})
+ if err == nil {
+ t.Errorf("CreateConfigMap failed: %v -> %v", err, c)
+ }
+}
+
+func TestValidationSuccess(t *testing.T) {
+ cm := ConfigMap{}
+ var d interface{}
+ var cfg map[string]interface{}
+
+ err := json.Unmarshal([]byte(`{"local": {"host": ":8080"}, "logger": {"level": 3}}`), &cfg)
+
+ err = cm.ReadFile("./test/schema.json", &d)
+ if err != nil {
+ t.Errorf("ReadFile failed: %v -> %v", err, d)
+ }
+
+ feedback, err := cm.doValidate(d, cfg)
+ if err != nil {
+ t.Errorf("doValidate failed: %v -> %v", err, feedback)
+ }
+}
+
+func TestValidationFails(t *testing.T) {
+ cm := ConfigMap{}
+ var d interface{}
+ var cfg map[string]interface{}
+
+ err := json.Unmarshal([]byte(`{"local": {"host": ":8080"}, "logger": {"level": "INVALID"}}`), &cfg)
+
+ err = cm.ReadFile("./test/schema.json", &d)
+ if err != nil {
+ t.Errorf("ConfigMetadata failed: %v -> %v", err, d)
+ }
+
+ feedback, err := cm.doValidate(d, cfg)
+ if err == nil {
+ t.Errorf("doValidate should faile but didn't: %v -> %v", err, feedback)
+ }
+
+ log.Println("Feedbacks: ", feedback)
+}
\ No newline at end of file
return stdout.Bytes(), errors.New(stderr.String())
}
-func HelmExec(args string) (out []byte, err error) {
+var HelmExec = func(args string) (out []byte, err error) {
return Exec(strings.Join([]string{"helm", args}, " "))
}
-func KubectlExec(args string) (out []byte, err error) {
+var KubectlExec = func(args string) (out []byte, err error) {
return Exec(strings.Join([]string{"kubectl", args}, " "))
}
+func (h *Helm) SetCM(cm ConfigMapper) {
+ h.cm = cm
+}
+
func (h *Helm) Initialize() {
if h.initDone == true {
return
// API functions
func (h *Helm) Init() (out []byte, err error) {
-
// Add Tiller address as environment variable
if err := addTillerEnv(); err != nil {
return out, err
}
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"))
if err != nil {
return HelmExec(strings.Join([]string{"repo add ", rname, " ", repo, username, pwd}, ""))
}
-func (h *Helm) Install(m ConfigMetadata) (xapp Xapp, err error) {
+func (h *Helm) Install(m XappDeploy) (xapp Xapp, err error) {
out, err := h.Run(strings.Join([]string{"repo update "}, ""))
if err != nil {
return
}
- m.Namespace = getNamespace(m.Namespace)
- cm, cmErr := PurgeConfigMap(m)
+ var cm interface{}
+ if err = h.cm.ReadConfigMap(m.Name, m.Namespace, &cm); err != nil {
+ out, err = h.Run(getInstallArgs(m, false))
+ if err != nil {
+ return
+ }
+ return h.ParseStatus(m.Name, string(out))
+ }
- ns := " --namespace=" + m.Namespace
- rname := viper.GetString("helm.repo-name")
- out, err = h.Run(strings.Join([]string{"install ", rname, "/", m.Name, " --name ", m.Name, ns}, ""))
+ // ConfigMap exists, try to override
+ out, err = h.Run(getInstallArgs(m, true))
+ if err == nil {
+ return h.ParseStatus(m.Name, string(out))
+ }
+
+ cm, cmErr := h.cm.PurgeConfigMap(m)
+ out, err = h.Run(getInstallArgs(m, false))
if err != nil {
return
}
if cmErr == nil {
- cmErr = RestoreConfigMap(m, cm)
+ cmErr = h.cm.RestoreConfigMap(m, cm)
}
return h.ParseStatus(m.Name, string(out))
}
func (h *Helm) Status(name string) (xapp Xapp, err error) {
-
out, err := h.Run(strings.Join([]string{"status ", name}, ""))
if err != nil {
mdclog(MdclogErr, formatLog("Getting xapps status", "", err.Error()))
}
func (h *Helm) List() (names []string, err error) {
-
ns := getNamespace("")
out, err := h.Run(strings.Join([]string{"list --all --output yaml --namespace=", ns}, ""))
if err != nil {
// Helper functions
func (h *Helm) GetVersion(name string) (version string) {
-
ns := getNamespace("")
out, err := h.Run(strings.Join([]string{"list --output yaml --namespace=", ns, " ", name}, ""))
if err != nil {
return
}
- re := regexp.MustCompile(name + "-(\\d+).*")
+ re := regexp.MustCompile(name + "-(\\w+-\\w+).*")
resources := re.FindAllStringSubmatch(string(result[0]), -1)
if resources != nil {
for _, v := range resources {
}
func (h *Helm) ParseStatus(name string, out string) (xapp Xapp, err error) {
-
xapp.Name = name
xapp.Version = h.GetVersion(name)
xapp.Status = h.GetState(out)
- types, err := GetMessages(name)
- if err != nil {
- // xAPP can still be deployed if the msg_type file is missing.
- mdclog(MdclogWarn, formatLog("GetMessages Failed....", "", err.Error()))
-
- //Set err back to nil, so it does not cause issues in called functions.
- err = nil
- }
-
- h.FillInstanceData(name, out, &xapp, types)
+ h.FillInstanceData(name, out, &xapp, h.cm.GetMessages(name))
return
}
}
func addTillerEnv() (err error) {
-
service := viper.GetString("helm.tiller-service")
namespace := viper.GetString("helm.tiller-namespace")
port := viper.GetString("helm.tiller-port")
return ns
}
+func getInstallArgs(x XappDeploy, cmOverride bool) (args string) {
+ x.Namespace = getNamespace(x.Namespace)
+ args = args + " --namespace=" + x.Namespace
+
+ if x.ImageRepo != "" {
+ args = args + " --set image.repository=" + x.ImageRepo
+ }
+
+ if x.ServiceName != "" {
+ args = args + " --set service.name=" + x.ServiceName
+ }
+
+ if x.Hostname != "" {
+ args = args + " --set hostname=" + x.Hostname
+ }
+
+ if cmOverride == true {
+ args = args + " --set appconfig.override=true"
+ }
+
+ rname := viper.GetString("helm.repo-name")
+ return fmt.Sprintf("install %s/%s --name=%s %s", rname, x.Name, x.Name, args)
+}
+
func formatLog(text string, args string, err string) string {
return fmt.Sprintf("Helm: %s: args=%s err=%s\n", text, args, err)
}
--- /dev/null
+/*
+==================================================================================
+ Copyright (c) 2019 AT&T Intellectual Property.
+ Copyright (c) 2019 Nokia
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================================
+*/
+
+package main
+
+import (
+ "testing"
+ "reflect"
+)
+
+
+var helmStatusOutput = `
+LAST DEPLOYED: Sat Mar 9 06:50:45 2019
+NAMESPACE: default
+STATUS: DEPLOYED
+
+RESOURCES:
+==> v1/Pod(related)
+NAME READY STATUS RESTARTS AGE
+dummy-xapp-8984fc9fd-bkcbp 1/1 Running 0 55m
+dummy-xapp-8984fc9fd-l6xch 1/1 Running 0 55m
+dummy-xapp-8984fc9fd-pp4hg 1/1 Running 0 55m
+
+==> v1/Service
+NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
+dummy-xapp-dummy-xapp-chart ClusterIP 10.102.184.212 <none> 80/TCP 55m
+
+==> v1beta1/Deployment
+NAME READY UP-TO-DATE AVAILABLE AGE
+dummy-xapp 3/3 3 3 55m
+`
+
+var helListOutput = `Next: ""
+Releases:
+- AppVersion: "1.0"
+ Chart: dummy-xapp-chart-0.1.0
+ Name: dummy-xapp
+ Namespace: default
+ Revision: 1
+ Status: DEPLOYED
+ Updated: Mon Mar 11 06:55:05 2019
+- AppVersion: "2.0"
+ Chart: dummy-xapp-chart-0.1.0
+ Name: dummy-xapp2
+ Namespace: default
+ Revision: 1
+ Status: DEPLOYED
+ Updated: Mon Mar 11 06:55:05 2019
+- AppVersion: "1.0"
+ Chart: appmgr-0.0.1
+ Name: appmgr
+ Namespace: default
+ Revision: 1
+ Status: DEPLOYED
+ Updated: Sun Mar 24 07:17:00 2019`
+
+
+var h = Helm{}
+
+func TestHelmStatus(t *testing.T) {
+ h.SetCM(&ConfigMap{})
+ xapp, err := h.ParseStatus("dummy-xapp", helmStatusOutput)
+ if err != nil {
+ t.Errorf("Helm install failed: %v", err)
+ }
+
+ x := getXappData()
+ xapp.Version = "1.0"
+
+ if !reflect.DeepEqual(xapp, x) {
+ t.Errorf("\n%v \n%v", xapp, x)
+ }
+}
+
+func TestHelmLists(t *testing.T) {
+ names, err := h.GetNames(helListOutput)
+ if err != nil {
+ t.Errorf("Helm status failed: %v", err)
+ }
+
+ if !reflect.DeepEqual(names, []string{"dummy-xapp", "dummy-xapp2"}) {
+ t.Errorf("Helm status failed: %v", err)
+ }
+}
+
+func TestHelmNamespace(t *testing.T) {
+ if getNamespace("pltxapp") != "pltxapp" {
+ t.Errorf("getNamespace failed!")
+ }
+
+ if getNamespace("") != "ricxapp" {
+ t.Errorf("getNamespace failed!")
+ }
+}
+
+func TestAddTillerEnv(t *testing.T) {
+ if addTillerEnv() != nil {
+ t.Errorf("TestAddTillerEnv failed!")
+ }
+}
+
+func TestGetInstallArgs(t *testing.T) {
+ x := XappDeploy{Name: "dummy-xapp"}
+
+ expectedArgs := "install helm-repo/dummy-xapp --name=dummy-xapp --namespace=ricxapp"
+ if args := getInstallArgs(x, false); args != expectedArgs {
+ t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+ }
+
+ x.ImageRepo = "localhost:5000"
+ expectedArgs = expectedArgs + " --set image.repository=" + "localhost:5000"
+ if args := getInstallArgs(x, false); args != expectedArgs {
+ t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+ }
+
+ x.ServiceName = "xapp"
+ expectedArgs = expectedArgs + " --set service.name=" + "xapp"
+ if args := getInstallArgs(x, false); args != expectedArgs {
+ t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+ }
+
+ x.ServiceName = "xapp"
+ expectedArgs = expectedArgs + " --set appconfig.override=true"
+ if args := getInstallArgs(x, true); args != expectedArgs {
+ t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+ }
+}
+
+func getXappData() (x Xapp) {
+ x = generateXapp("dummy-xapp", "deployed", "1.0", "dummy-xapp-8984fc9fd-bkcbp", "running", "10.102.184.212", "80")
+ x.Instances = append(x.Instances, x.Instances[0])
+ x.Instances = append(x.Instances, x.Instances[0])
+ x.Instances[1].Name = "dummy-xapp-8984fc9fd-l6xch"
+ x.Instances[2].Name = "dummy-xapp-8984fc9fd-pp4hg"
+
+ return x
+}
+
loadConfig()
m := XappManager{}
- m.Initialize(&Helm{})
+ m.Initialize(&Helm{}, &ConfigMap{})
m.Run()
}
}
type Xapp struct {
- Name string `json:"name"`
- ConfigName string `json:"configName, omitempty"`
- Namespace string `json:"namespace, omitempty"`
- Status string `json:"status"`
- Version string `json:"version"`
- Instances []XappInstance `json:"instances"`
+ Name string `json:"name"`
+ Status string `json:"status"`
+ Version string `json:"version"`
+ Instances []XappInstance `json:"instances"`
}
type XappInstance struct {
RxMessages []string `json:"rxMessages"`
}
+type XappDeploy struct {
+ Name string `json:"name"`
+ ConfigName string `json:"configName, omitempty"`
+ Namespace string `json:"namespace, omitempty"`
+ ServiceName string `json:"serviceName, omitempty"`
+ ImageRepo string `json:"imageRepo, omitempty"`
+ Hostname string `json:"hostname, omitempty"`
+}
+
type XappManager struct {
router *mux.Router
helm Helmer
+ cm ConfigMapper
sd SubscriptionDispatcher
opts CmdOptions
ready bool
}
+type ConfigMapper interface {
+ UploadConfig() (cfg []XAppConfig)
+ CreateConfigMap(r XAppConfig) (errList []CMError, err error)
+ UpdateConfigMap(r XAppConfig) (errList []CMError, err error)
+ DeleteConfigMap(r XAppConfig) (cm interface{}, err error)
+ PurgeConfigMap(m XappDeploy) (cm interface{}, err error)
+ RestoreConfigMap(m XappDeploy, cm interface{}) (err error)
+ ReadConfigMap(name string, ns string, c *interface{}) (err error)
+ ApplyConfigMap(r XAppConfig, action string) (err error)
+ GetMessages(name string) (msgs MessageTypes)
+}
+
type Helmer interface {
+ SetCM(ConfigMapper)
Initialize()
- Install(m ConfigMetadata) (xapp Xapp, err error)
+ Install(m XappDeploy) (xapp Xapp, err error)
Status(name string) (xapp Xapp, err error)
StatusAll() (xapps []Xapp, err error)
List() (xapps []string, err error)
host string
chartPath string
initDone bool
+ cm ConfigMapper
}
type SubscriptionReq struct {
+++ /dev/null
-# Copyright (c) 2019 AT&T Intellectual Property.
-# Copyright (c) 2019 Nokia.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#----------------------------------------------------------
-#
-#----------------------------------------------------------
-FROM ubuntu:16.04 as ubuntubase
-
-RUN apt-get update -y && \
- apt-get install -y wget
-
-
-RUN sed -i -e "s,http://archive.ubuntu.com/ubuntu,$(wget -qO - mirrors.ubuntu.com/mirrors.txt | head -1)," /etc/apt/sources.list
-RUN sed -i -e "s,http://security.ubuntu.com/ubuntu,$(wget -qO - mirrors.ubuntu.com/mirrors.txt | head -1)," /etc/apt/sources.list
-
-#
-# packages
-#
-RUN apt-get update -y && \
- apt-get upgrade -y && \
- apt-get install -y \
- build-essential \
- apt-utils \
- cmake \
- make \
- autoconf \
- autoconf-archive \
- gawk \
- libtool \
- automake \
- pkg-config \
- sudo \
- wget \
- nano \
- git \
- jq
-
-
-#
-# go
-#
-RUN wget https://dl.google.com/go/go1.12.linux-amd64.tar.gz && \
- tar -C /usr/local -xvf ./go1.12.linux-amd64.tar.gz
-
-ENV PATH="/usr/local/go/bin:${PATH}"
-
-#
-# rancodev libs
-#
-RUN mkdir -p /opt/build \
- && cd /opt/build && git clone https://gerrit.oran-osc.org/r/com/log \
- && cd log/ ; ./autogen.sh ; ./configure ; make ; make install \
- && ldconfig
-
-#----------------------------------------------------------
-#
-#----------------------------------------------------------
-FROM ubuntubase as builder
-
-ARG PACKAGEURL=gerrit.oran-osc.org/r/ric-plt/appmgr
-ARG HELMVERSION=v2.13.0-rc.1
-
-#
-# helm
-#
-RUN wget https://storage.googleapis.com/kubernetes-helm/helm-${HELMVERSION}-linux-amd64.tar.gz \
- && tar -zxvf helm-${HELMVERSION}-linux-amd64.tar.gz \
- && cp linux-amd64/helm /usr/bin/helm \
- && rm -rf helm-${HELMVERSION}-linux-amd64.tar.gz \
- && rm -rf linux-amd64
-
-
-#
-# appmgr codes
-#
-RUN mkdir -p /go/src/${PACKAGEURL}
-ENV GOPATH="/go"
-
-#
-# Speed up things by generating layer with needed go packages
-#
-RUN go get github.com/gorilla/mux \
- && go get github.com/spf13/viper \
- && go get github.com/gorilla/mux \
- && go get github.com/orcaman/concurrent-map \
- && go get github.com/segmentio/ksuid \
- && go get gopkg.in/yaml.v2
-
-
-COPY . /go/src/${PACKAGEURL}
-
-
-#
-# build
-#
-RUN make -C /go/src/${PACKAGEURL} build
-
-
-#----------------------------------------------------------
-#
-#----------------------------------------------------------
-FROM builder as test_unit
-ARG PACKAGEURL=gerrit.oran-osc.org/r/ric-plt/appmgr
-WORKDIR "/go/src/${PACKAGEURL}"
-CMD ["make","test"]
-
-
-#----------------------------------------------------------
-#
-#----------------------------------------------------------
-FROM builder as test_fmt
-ARG PACKAGEURL=gerrit.oran-osc.org/r/ric-plt/appmgr
-WORKDIR "/go/src/${PACKAGEURL}"
-CMD ["make","test-fmt"]
-
-#----------------------------------------------------------
-#
-#----------------------------------------------------------
-FROM builder as test_sanity
-ARG PACKAGEURL=gerrit.oran-osc.org/r/ric-plt/appmgr
-WORKDIR "/go/src/${PACKAGEURL}"
-CMD ["jq","-s",".", "api/appmgr_rest_api.json"]
-
-
-#----------------------------------------------------------
-#
-#----------------------------------------------------------
-FROM ubuntu:16.04 as release
-ARG PACKAGEURL=gerrit.oran-osc.org/r/ric-plt/appmgr
-
-RUN apt-get update -y \
- && apt-get install -y sudo openssl ca-certificates ca-cacert \
- && apt-get clean
-
-
-#
-# libraries and helm
-#
-COPY --from=builder /usr/local/include/ /usr/local/include/
-COPY --from=builder /usr/local/lib/ /usr/local/lib/
-COPY --from=builder /usr/bin/helm /usr/bin/helm
-
-RUN ldconfig
-
-#
-# xApp
-#
-RUN mkdir -p /opt/xAppManager \
- && chmod -R 755 /opt/xAppManager
-
-COPY --from=builder /go/src/${PACKAGEURL}/build/appmgr /opt/xAppManager/appmgr
-#COPY --from=builder /go/src/${PACKAGEURL}/config/appmgr.yaml /opt/etc/xAppManager/config-file.yaml
-
-
-COPY docker/docker-entrypoint.sh /opt/xAppManager/
-
-WORKDIR /opt/xAppManager
-
-ENTRYPOINT ["/opt/xAppManager/docker-entrypoint.sh"]
+++ /dev/null
-#!/bin/bash
-
-# Copyright (c) 2019 AT&T Intellectual Property.
-# Copyright (c) 2019 Nokia.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-cp /opt/ric/config/appmgr.yaml /opt/xAppManager/config-file.yaml
-
-# Copy all certificates from mounted folder to root system
-cp /opt/ric/certificates/* /etc/ssl/certs
-
-# Start services, etc.
-/opt/xAppManager/appmgr -f /opt/xAppManager/config-file.yaml
+++ /dev/null
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright (c) 2019 AT&T Intellectual Property.
-
- Copyright (c) 2019 Nokia.
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+++ /dev/null
-package sdlgo_test
-
-import (
- "fmt"
- "strconv"
- "strings"
- "testing"
-
- "gitlabe1.ext.net.nokia.com/ric_dev/sdlgo"
-)
-
-type singleBenchmark struct {
- key string
- keySize int
- valueSize int
-}
-
-type multiBenchmark struct {
- keyBase string
- keyCount int
- keySize int
- valueSize int
-}
-
-func (bm singleBenchmark) String(oper string) string {
- return fmt.Sprintf("op = %s key=%d value=%d", oper, bm.keySize, bm.valueSize)
-}
-
-func (bm multiBenchmark) String(oper string) string {
- return fmt.Sprintf("op = %s keycnt=%d key=%d value=%d", oper, bm.keyCount, bm.keySize, bm.valueSize)
-}
-func BenchmarkSet(b *testing.B) {
- benchmarks := []singleBenchmark{
- {"a", 10, 64},
- {"b", 10, 1024},
- {"c", 10, 64 * 1024},
- {"d", 10, 1024 * 1024},
- {"e", 10, 10 * 1024 * 1024},
-
- {"f", 100, 64},
- {"g", 100, 1024},
- {"h", 100, 64 * 1024},
- {"i", 100, 1024 * 1024},
- {"j", 100, 10 * 1024 * 1024},
- }
-
- for _, bm := range benchmarks {
- b.Run(bm.String("set"), func(b *testing.B) {
- key := strings.Repeat(bm.key, bm.keySize)
- value := strings.Repeat("1", bm.valueSize)
- sdl := sdlgo.Create("namespace")
-
- b.ResetTimer()
- b.RunParallel(func(pb *testing.PB) {
- for pb.Next() {
- err := sdl.Set(key, value)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- })
- }
-}
-
-func BenchmarkGet(b *testing.B) {
- benchmarks := []singleBenchmark{
- {"a", 10, 64},
- {"b", 10, 1024},
- {"c", 10, 64 * 1024},
- {"d", 10, 1024 * 1024},
- {"e", 10, 10 * 1024 * 1024},
-
- {"f", 100, 64},
- {"g", 100, 1024},
- {"h", 100, 64 * 1024},
- {"i", 100, 1024 * 1024},
- {"j", 100, 10 * 1024 * 1024},
- }
-
- for _, bm := range benchmarks {
- b.Run(bm.String("Get"), func(b *testing.B) {
- key := strings.Repeat(bm.key, bm.keySize)
- value := strings.Repeat("1", bm.valueSize)
- sdl := sdlgo.Create("namespace")
- if err := sdl.Set(key, value); err != nil {
- b.Fatal(err)
- }
- b.ResetTimer()
- b.RunParallel(func(pb *testing.PB) {
- for pb.Next() {
- _, err := sdl.Get([]string{key})
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- })
- }
-}
-
-func BenchmarkMultiSet(b *testing.B) {
- benchmarks := []multiBenchmark{
- {"a", 2, 10, 64},
- {"b", 10, 10, 64},
- {"c", 100, 10, 64},
- {"d", 1000, 10, 64},
- {"e", 5000, 10, 64},
-
- {"f", 2, 100, 64},
- {"g", 10, 100, 64},
- {"h", 100, 100, 64},
- {"i", 1000, 100, 64},
- {"j", 5000, 100, 64},
- }
-
- for _, bm := range benchmarks {
- b.Run(bm.String("mset"), func(b *testing.B) {
- sdl := sdlgo.Create("namespace")
- value := strings.Repeat("1", bm.valueSize)
- keyVals := make([]string, 0)
- for i := 0; i < bm.keyCount; i++ {
- key := strings.Repeat(bm.keyBase+strconv.Itoa(i), bm.keySize)
- keyVals = append(keyVals, key, value)
- }
- b.ResetTimer()
- b.RunParallel(func(pb *testing.PB) {
- for pb.Next() {
- err := sdl.Set(keyVals)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- })
- }
-}
-
-func BenchmarkMultiGet(b *testing.B) {
- benchmarks := []multiBenchmark{
- {"a", 2, 10, 64},
- {"b", 10, 10, 64},
- {"c", 100, 10, 64},
- {"d", 1000, 10, 64},
- {"e", 5000, 10, 64},
-
- {"f", 2, 100, 64},
- {"g", 10, 100, 64},
- {"h", 100, 100, 64},
- {"i", 1000, 100, 64},
- {"j", 5000, 100, 64},
- }
-
- for _, bm := range benchmarks {
- b.Run(bm.String("gset"), func(b *testing.B) {
- sdl := sdlgo.Create("namespace")
- keyVals := make([]string, 0)
- for i := 0; i < bm.keyCount; i++ {
- key := strings.Repeat(bm.keyBase+strconv.Itoa(i), bm.keySize)
- keyVals = append(keyVals, key)
- }
- b.ResetTimer()
- b.RunParallel(func(pb *testing.PB) {
- for pb.Next() {
- _, err := sdl.Get(keyVals)
- if err != nil {
- b.Fatal(err)
- }
- }
- })
- })
- }
-}
+++ /dev/null
-module gerrit.oran-osc.org/r/ric-plt/sdlgo
-
-go 1.12
-
-require (
- github.com/go-redis/redis v6.15.2+incompatible
- github.com/onsi/ginkgo v1.8.0 // indirect
- github.com/onsi/gomega v1.5.0 // indirect
- github.com/stretchr/testify v1.3.0
-)
-
-replace gerrit.oran-osc.org/r/ric-plt/sdlgo/internal/sdlgoredis => ./internal/sdlgoredis
+++ /dev/null
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
-github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
-github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
-github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
-github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-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/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+++ /dev/null
-package sdlgoredis
-
-import (
- "os"
-
- "github.com/go-redis/redis"
-)
-
-type DB struct {
- client *redis.Client
-}
-
-func Create() *DB {
- hostname := os.Getenv("DBAAS_SERVICE_HOST")
- if hostname == "" {
- hostname = "localhost"
- }
- port := os.Getenv("DBAAS_SERVICE_PORT")
- if port == "" {
- port = "6379"
- }
- redisAddress := hostname + ":" + port
- client := redis.NewClient(&redis.Options{
- Addr: redisAddress,
- Password: "", // no password set
- DB: 0, // use default DB
- PoolSize: 20,
- })
-
- db := DB{
- client: client,
- }
-
- return &db
-}
-
-func (db *DB) Close() error {
- return db.Close()
-}
-
-func (db *DB) MSet(pairs ...interface{}) error {
- return db.client.MSet(pairs...).Err()
-}
-
-func (db *DB) MGet(keys []string) ([]interface{}, error) {
- val, err := db.client.MGet(keys...).Result()
- return val, err
-}
-
-func (db *DB) Del(keys []string) error {
- _, err := db.client.Del(keys...).Result()
- return err
-}
-
-func (db *DB) Keys(pattern string) ([]string, error) {
- val, err := db.client.Keys(pattern).Result()
- return val, err
-}
+++ /dev/null
-/*
- Copyright (c) 2019 AT&T Intellectual Property.
- Copyright (c) 2018-2019 Nokia.
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-package sdlgo
-
-import (
- "reflect"
- "strings"
-
- "gerrit.oran-osc.org/r/ric-plt/sdlgo/internal/sdlgoredis"
-)
-
-type Idatabase interface {
- MSet(pairs ...interface{}) error
- MGet(keys []string) ([]interface{}, error)
- Close() error
- Del(keys []string) error
- Keys(key string) ([]string, error)
-}
-
-type SdlInstance struct {
- NameSpace string
- NsPrefix string
- Idatabase
-}
-
-func Create(NameSpace string) *SdlInstance {
- db := sdlgoredis.Create()
- s := SdlInstance{
- NameSpace: NameSpace,
- NsPrefix: "{" + NameSpace + "},",
- Idatabase: db,
- }
-
- return &s
-}
-
-func (s *SdlInstance) Close() error {
- return s.Close()
-}
-
-func (s *SdlInstance) setNamespaceToKeys(pairs ...interface{}) []interface{} {
- var retVal []interface{}
- for i, v := range pairs {
- if i%2 == 0 {
- reflectType := reflect.TypeOf(v)
- switch reflectType.Kind() {
- case reflect.Slice:
- x := reflect.ValueOf(v)
- for i2 := 0; i2 < x.Len(); i2++ {
- if i2%2 == 0 {
- retVal = append(retVal, s.NsPrefix+x.Index(i2).Interface().(string))
- } else {
- retVal = append(retVal, x.Index(i2).Interface())
- }
- }
- case reflect.Array:
- x := reflect.ValueOf(v)
- for i2 := 0; i2 < x.Len(); i2++ {
- if i2%2 == 0 {
- retVal = append(retVal, s.NsPrefix+x.Index(i2).Interface().(string))
- } else {
- retVal = append(retVal, x.Index(i2).Interface())
- }
- }
- default:
- retVal = append(retVal, s.NsPrefix+v.(string))
- }
- } else {
- retVal = append(retVal, v)
- }
- }
- return retVal
-}
-
-func (s *SdlInstance) Set(pairs ...interface{}) error {
- if len(pairs) == 0 {
- return nil
- }
-
- keyAndData := s.setNamespaceToKeys(pairs...)
- err := s.MSet(keyAndData...)
- return err
-}
-
-func (s *SdlInstance) Get(keys []string) (map[string]interface{}, error) {
- m := make(map[string]interface{})
- if len(keys) == 0 {
- return m, nil
- }
-
- var keysWithNs []string
- for _, v := range keys {
- keysWithNs = append(keysWithNs, s.NsPrefix+v)
- }
- val, err := s.MGet(keysWithNs)
- if err != nil {
- return m, err
- }
- for i, v := range val {
- m[keys[i]] = v
- }
- return m, err
-}
-
-func (s *SdlInstance) SetIf(key string, oldData, newData interface{}) {
- panic("SetIf not implemented\n")
-}
-
-func (s *SdlInstance) SetIfiNotExists(key string, data interface{}) {
- panic("SetIfiNotExists not implemented\n")
-}
-
-func (s *SdlInstance) Remove(keys []string) error {
- if len(keys) == 0 {
- return nil
- }
-
- var keysWithNs []string
- for _, v := range keys {
- keysWithNs = append(keysWithNs, s.NsPrefix+v)
- }
- err := s.Del(keysWithNs)
- return err
-}
-
-func (s *SdlInstance) RemoveIf(key string, data interface{}) {
- panic("RemoveIf not implemented\n")
-}
-
-func (s *SdlInstance) GetAll() ([]string, error) {
- keys, err := s.Keys(s.NsPrefix + "*")
- var retVal []string = nil
- if err != nil {
- return retVal, err
- }
- for _, v := range keys {
- retVal = append(retVal, strings.Split(v, s.NsPrefix)[1])
- }
- return retVal, err
-}
-
-func (s *SdlInstance) RemoveAll() error {
- keys, err := s.Keys(s.NsPrefix + "*")
- if err != nil {
- return err
- }
- if keys != nil {
- err = s.Del(keys)
- }
- return err
-}
+++ /dev/null
-package sdlgo_test
-
-import (
- "errors"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "gerrit.oran-osc.org/r/ric-plt/sdlgo"
-)
-
-type mockDB struct {
- mock.Mock
-}
-
-func (m *mockDB) MSet(pairs ...interface{}) error {
- a := m.Called(pairs)
- return a.Error(0)
-}
-
-func (m *mockDB) MGet(keys []string) ([]interface{}, error) {
- a := m.Called(keys)
- return a.Get(0).([]interface{}), a.Error(1)
-}
-
-func (m *mockDB) Close() error {
- a := m.Called()
- return a.Error(0)
-}
-
-func (m *mockDB) Del(keys []string) error {
- a := m.Called(keys)
- return a.Error(0)
-}
-
-func (m *mockDB) Keys(pattern string) ([]string, error) {
- a := m.Called(pattern)
- return a.Get(0).([]string), a.Error(1)
-}
-
-func setup() (*mockDB, *sdlgo.SdlInstance) {
- m := new(mockDB)
- i := &sdlgo.SdlInstance{
- NameSpace: "namespace",
- NsPrefix: "{namespace},",
- Idatabase: m,
- }
- return m, i
-}
-
-func TestGetOneKey(t *testing.T) {
- m, i := setup()
-
- mgetExpected := []string{"{namespace},key"}
- mReturn := []interface{}{"somevalue"}
- mReturnExpected := make(map[string]interface{})
- mReturnExpected["key"] = "somevalue"
-
- m.On("MGet", mgetExpected).Return(mReturn, nil)
- retVal, err := i.Get([]string{"key"})
- assert.Nil(t, err)
- assert.Equal(t, mReturnExpected, retVal)
- m.AssertExpectations(t)
-}
-
-func TestGetSeveralKeys(t *testing.T) {
- m, i := setup()
-
- mgetExpected := []string{"{namespace},key1", "{namespace},key2", "{namespace},key3"}
- mReturn := []interface{}{"somevalue1", 2, "someothervalue"}
- mReturnExpected := make(map[string]interface{})
- mReturnExpected["key1"] = "somevalue1"
- mReturnExpected["key2"] = 2
- mReturnExpected["key3"] = "someothervalue"
-
- m.On("MGet", mgetExpected).Return(mReturn, nil)
- retVal, err := i.Get([]string{"key1", "key2", "key3"})
- assert.Nil(t, err)
- assert.Equal(t, mReturnExpected, retVal)
- m.AssertExpectations(t)
-}
-
-func TestGetSeveralKeysSomeFail(t *testing.T) {
- m, i := setup()
-
- mgetExpected := []string{"{namespace},key1", "{namespace},key2", "{namespace},key3"}
- mReturn := []interface{}{"somevalue1", nil, "someothervalue"}
- mReturnExpected := make(map[string]interface{})
- mReturnExpected["key1"] = "somevalue1"
- mReturnExpected["key2"] = nil
- mReturnExpected["key3"] = "someothervalue"
-
- m.On("MGet", mgetExpected).Return(mReturn, nil)
- retVal, err := i.Get([]string{"key1", "key2", "key3"})
- assert.Nil(t, err)
- assert.Equal(t, mReturnExpected, retVal)
- m.AssertExpectations(t)
-}
-
-func TestGetKeyReturnError(t *testing.T) {
- m, i := setup()
-
- mgetExpected := []string{"{namespace},key"}
- mReturn := []interface{}{nil}
- mReturnExpected := make(map[string]interface{})
-
- m.On("MGet", mgetExpected).Return(mReturn, errors.New("Some error"))
- retVal, err := i.Get([]string{"key"})
- assert.NotNil(t, err)
- assert.Equal(t, mReturnExpected, retVal)
- m.AssertExpectations(t)
-}
-
-func TestGetEmptyList(t *testing.T) {
- m, i := setup()
-
- mgetExpected := []string{}
-
- retval, err := i.Get([]string{})
- assert.Nil(t, err)
- assert.Len(t, retval, 0)
- m.AssertNotCalled(t, "MGet", mgetExpected)
-}
-
-func TestWriteOneKey(t *testing.T) {
- m, i := setup()
-
- msetExpected := []interface{}{"{namespace},key1", "data1"}
-
- m.On("MSet", msetExpected).Return(nil)
- err := i.Set("key1", "data1")
- assert.Nil(t, err)
- m.AssertExpectations(t)
-}
-
-func TestWriteSeveralKeysSlice(t *testing.T) {
- m, i := setup()
-
- msetExpected := []interface{}{"{namespace},key1", "data1", "{namespace},key2", 22}
-
- m.On("MSet", msetExpected).Return(nil)
- err := i.Set([]interface{}{"key1", "data1", "key2", 22})
- assert.Nil(t, err)
- m.AssertExpectations(t)
-
-}
-
-func TestWriteSeveralKeysArray(t *testing.T) {
- m, i := setup()
-
- msetExpected := []interface{}{"{namespace},key1", "data1", "{namespace},key2", "data2"}
-
- m.On("MSet", msetExpected).Return(nil)
- err := i.Set([4]string{"key1", "data1", "key2", "data2"})
- assert.Nil(t, err)
- m.AssertExpectations(t)
-}
-
-func TestWriteFail(t *testing.T) {
- m, i := setup()
-
- msetExpected := []interface{}{"{namespace},key1", "data1"}
-
- m.On("MSet", msetExpected).Return(errors.New("Some error"))
- err := i.Set("key1", "data1")
- assert.NotNil(t, err)
- m.AssertExpectations(t)
-}
-
-func TestWriteEmptyList(t *testing.T) {
- m, i := setup()
-
- msetExpected := []interface{}{}
- err := i.Set()
- assert.Nil(t, err)
- m.AssertNotCalled(t, "MSet", msetExpected)
-}
-
-func TestRemoveSuccessfully(t *testing.T) {
- m, i := setup()
-
- msetExpected := []string{"{namespace},key1", "{namespace},key2"}
- m.On("Del", msetExpected).Return(nil)
-
- err := i.Remove([]string{"key1", "key2"})
- assert.Nil(t, err)
- m.AssertExpectations(t)
-}
-
-func TestRemoveFail(t *testing.T) {
- m, i := setup()
-
- msetExpected := []string{"{namespace},key"}
- m.On("Del", msetExpected).Return(errors.New("Some error"))
-
- err := i.Remove([]string{"key"})
- assert.NotNil(t, err)
- m.AssertExpectations(t)
-}
-
-func TestRemoveEmptyList(t *testing.T) {
- m, i := setup()
-
- err := i.Remove([]string{})
- assert.Nil(t, err)
- m.AssertNotCalled(t, "Del", []string{})
-}
-
-func TestGetAllSuccessfully(t *testing.T) {
- m, i := setup()
-
- mKeysExpected := string("{namespace},*")
- mReturnExpected := []string{"{namespace},key1", "{namespace},key2"}
- expectedReturn := []string{"key1", "key2"}
- m.On("Keys", mKeysExpected).Return(mReturnExpected, nil)
- retVal, err := i.GetAll()
- assert.Nil(t, err)
- assert.Equal(t, expectedReturn, retVal)
- m.AssertExpectations(t)
-}
-
-func TestGetAllFail(t *testing.T) {
- m, i := setup()
-
- mKeysExpected := string("{namespace},*")
- mReturnExpected := []string{}
- m.On("Keys", mKeysExpected).Return(mReturnExpected, errors.New("some error"))
- retVal, err := i.GetAll()
- assert.NotNil(t, err)
- assert.Nil(t, retVal)
- assert.Equal(t, len(retVal), 0)
- m.AssertExpectations(t)
-}
-
-func TestGetAllReturnEmpty(t *testing.T) {
- m, i := setup()
-
- mKeysExpected := string("{namespace},*")
- var mReturnExpected []string = nil
- m.On("Keys", mKeysExpected).Return(mReturnExpected, nil)
- retVal, err := i.GetAll()
- assert.Nil(t, err)
- assert.Nil(t, retVal)
- assert.Equal(t, len(retVal), 0)
- m.AssertExpectations(t)
-
-}
-
-func TestRemoveAllSuccessfully(t *testing.T) {
- m, i := setup()
-
- mKeysExpected := string("{namespace},*")
- mKeysReturn := []string{"{namespace},key1", "{namespace},key2"}
- mDelExpected := mKeysReturn
- m.On("Keys", mKeysExpected).Return(mKeysReturn, nil)
- m.On("Del", mDelExpected).Return(nil)
- err := i.RemoveAll()
- assert.Nil(t, err)
- m.AssertExpectations(t)
-}
-
-func TestRemoveAllNoKeysFound(t *testing.T) {
- m, i := setup()
-
- mKeysExpected := string("{namespace},*")
- var mKeysReturn []string = nil
- m.On("Keys", mKeysExpected).Return(mKeysReturn, nil)
- m.AssertNumberOfCalls(t, "Del", 0)
- err := i.RemoveAll()
- assert.Nil(t, err)
- m.AssertExpectations(t)
-}
-
-func TestRemoveAllKeysReturnError(t *testing.T) {
- m, i := setup()
-
- mKeysExpected := string("{namespace},*")
- var mKeysReturn []string = nil
- m.On("Keys", mKeysExpected).Return(mKeysReturn, errors.New("Some error"))
- m.AssertNumberOfCalls(t, "Del", 0)
- err := i.RemoveAll()
- assert.NotNil(t, err)
- m.AssertExpectations(t)
-}
-
-func TestRemoveAllDelReturnError(t *testing.T) {
- m, i := setup()
-
- mKeysExpected := string("{namespace},*")
- mKeysReturn := []string{"{namespace},key1", "{namespace},key2"}
- mDelExpected := mKeysReturn
- m.On("Keys", mKeysExpected).Return(mKeysReturn, nil)
- m.On("Del", mDelExpected).Return(errors.New("Some Error"))
- err := i.RemoveAll()
- assert.NotNil(t, err)
- m.AssertExpectations(t)
-}
(health|heal)
cmd=health
;;
+ (config|upload)
+ cmd=config
+ ;;
(help)
usage
exit 0
if [ "x$3" != "x" ]; then
data="--data $3"
fi
+
if curl --silent --show-error --connect-timeout 20 --header "Content-Type: application/json" -X $1 -o $resultfile "http://${host}:${port}$2" $data 2> $errfile ;then
status=0
else
base_xapps=$base/xapps
base_health=$base/health
base_subs=$base/subscriptions
+base_config=$base/config
do_deploy() {
if [ "x$1" != "x" ]; then
esac
}
+do_config() {
+ local urlpath
+ urlpath=$base_config
+ case $1 in
+ (get|list)
+ if [ "x$2" != "x" ]; then
+ urlpath="$urlpath/$2"
+ fi
+ if rest GET $urlpath; then
+ json_reformat < $resultfile
+ else
+ status=1
+ fi
+ ;;
+ (add|update)
+ if rest POST $urlpath "@$2" ; then
+ cat $resultfile
+ else
+ status=1
+ fi
+ ;;
+ (del|delete|remove|rem)
+ if rest DELETE $urlpath "@$2" ; then
+ cat $resultfile
+ else
+ status=1
+ fi
+ ;;
+ (*)
+ echo "$myname: unrecognized config subcommand $1"
+ status=1
+ esac
+}
+
case $cmd in
(deploy)
do_deploy "$2"
(subscriptions)
do_subscriptions "$2" "$3" "$4" "$5" "$6" "$7"
;;
+ (config)
+ do_config "$2" "$3"
+ ;;
(health)
if rest GET $base_health ; then
echo OK
--- /dev/null
+{
+ "definitions": {},
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "http://example.com/root.json",
+ "type": "object",
+ "title": "The Root Schema",
+ "required": [
+ "local",
+ "logger"
+ ],
+ "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": "^(.*)$"
+ }
+ }
+ },
+ "logger": {
+ "$id": "#/properties/logger",
+ "type": "object",
+ "title": "The Logger Schema",
+ "required": [
+ "level"
+ ],
+ "properties": {
+ "level": {
+ "$id": "#/properties/logger/properties/level",
+ "type": "integer",
+ "title": "The Level Schema",
+ "default": 0
+ }
+ }
+ }
+ }
+}
\ No newline at end of file