From: Abukar Mohamed Date: Thu, 9 May 2019 14:40:34 +0000 (+0000) Subject: Revert "Support for XApp configuration update" X-Git-Tag: v0.0.4~5 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=57d2a61e41861321b263fdeff5679671e36143ae;p=ric-plt%2Fappmgr.git Revert "Support for XApp configuration update" Merged by mistake! This reverts commit 10d3c67188dd2db001e0fd196b0acbad7d9bb37d. Change-Id: I3a036d4403c5c991c5be2a6f70993c3810d28286 Signed-off-by: Mohamed Abukar --- diff --git a/.gitignore b/.gitignore deleted file mode 100644 index fa8d028..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build/cache/* -cache/* diff --git a/api/appmgr_rest_api.json b/api/appmgr_rest_api.json index 92cc418..66250ae 100644 --- a/api/appmgr_rest_api.json +++ b/api/appmgr_rest_api.json @@ -2,7 +2,7 @@ "swagger": "2.0", "info": { "description": "This is a draft API for RIC appmgr", - "version": "0.1.2", + "version": "0.0.11", "title": "RIC appmgr", "license": { "name": "Apache 2.0", @@ -19,9 +19,6 @@ "/health": { "get": { "summary": "Health check of xApp Manager", - "tags": [ - "health" - ], "operationId": "getHealth", "responses": { "200": { @@ -33,9 +30,6 @@ "/xapps": { "post": { "summary": "Deploy a xapp", - "tags": [ - "xapp" - ], "operationId": "deployXapp", "consumes": [ "application/json" @@ -80,9 +74,6 @@ }, "get": { "summary": "Returns the status of all xapps", - "tags": [ - "xapp" - ], "operationId": "getAllXapps", "produces": [ "application/json" @@ -103,9 +94,6 @@ "/xapps/{xAppName}": { "get": { "summary": "Returns the status of a given xapp", - "tags": [ - "xapp" - ], "operationId": "getXappByName", "produces": [ "application/json" @@ -139,9 +127,6 @@ }, "delete": { "summary": "Undeploy an existing xapp", - "tags": [ - "xapp" - ], "operationId": "undeployXapp", "parameters": [ { @@ -168,9 +153,6 @@ "/xapps/{xAppName}/instances/{xAppInstanceName}": { "get": { "summary": "Returns the status of a given xapp", - "tags": [ - "xapp" - ], "operationId": "getXappInstanceByName", "produces": [ "application/json" @@ -210,144 +192,9 @@ } } }, - "/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" @@ -380,10 +227,6 @@ }, "get": { "summary": "Returns all subscriptions", - "tags": [ - "xapp", - "subscriptions" - ], "operationId": "getSubscriptions", "produces": [ "application/json" @@ -401,10 +244,6 @@ "/subscriptions/{subscriptionId}": { "get": { "summary": "Returns the information of subscription", - "tags": [ - "xapp", - "subscriptions" - ], "operationId": "getSubscriptionById", "produces": [ "application/json" @@ -435,10 +274,6 @@ }, "put": { "summary": "Modify event subscription", - "tags": [ - "xapp", - "subscriptions" - ], "operationId": "modifySubscription", "consumes": [ "application/json" @@ -478,10 +313,6 @@ }, "delete": { "summary": "Unsubscribe event", - "tags": [ - "xapp", - "subscriptions" - ], "description": "", "operationId": "deleteSubscription", "parameters": [ @@ -592,58 +423,6 @@ } } }, - "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": [ diff --git a/api/appmgr_rest_api.yaml b/api/appmgr_rest_api.yaml deleted file mode 100644 index 0a5cf73..0000000 --- a/api/appmgr_rest_api.yaml +++ /dev/null @@ -1,529 +0,0 @@ -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' diff --git a/cmd/appmgr/api.go b/cmd/appmgr/api.go index 8450d02..2e7ddc7 100755 --- a/cmd/appmgr/api.go +++ b/cmd/appmgr/api.go @@ -30,11 +30,7 @@ import ( // API functions -func (m *XappManager) Initialize(h Helmer, cm ConfigMapper) { - m.cm = cm - m.helm = h - m.helm.SetCM(cm) - +func (m *XappManager) Initialize(h Helmer) { m.router = mux.NewRouter().StrictSlash(true) resources := []Resource{ @@ -55,7 +51,6 @@ func (m *XappManager) Initialize(h Helmer, cm ConfigMapper) { {"GET", "/ric/v1/config", m.getConfig}, {"POST", "/ric/v1/config", m.createConfig}, - {"PUT", "/ric/v1/config", m.updateConfig}, {"DELETE", "/ric/v1/config", m.deleteConfig}, } @@ -72,6 +67,7 @@ func (m *XappManager) finalize(h Helmer) { m.sd = SubscriptionDispatcher{} m.sd.Initialize() + m.helm = h m.helm.Initialize() m.notifyClients() @@ -161,7 +157,7 @@ func (m *XappManager) deployXapp(w http.ResponseWriter, r *http.Request) { return } - var cm XappDeploy + var cm ConfigMetadata 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!") @@ -266,8 +262,7 @@ func (m *XappManager) notifyClients() { } func (m *XappManager) getConfig(w http.ResponseWriter, r *http.Request) { - cfg := m.cm.UploadConfig() - respondWithJSON(w, http.StatusOK, cfg) + respondWithJSON(w, http.StatusOK, UploadConfig()) } func (m *XappManager) createConfig(w http.ResponseWriter, r *http.Request) { @@ -276,41 +271,20 @@ func (m *XappManager) createConfig(w http.ResponseWriter, r *http.Request) { return } - 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) - } + if err := CreateConfigMap(c); err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) 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 := m.cm.DeleteConfigMap(c); err != nil { + if _, err := DeleteConfigMap(c); err != nil { respondWithError(w, http.StatusInternalServerError, err.Error()) return } diff --git a/cmd/appmgr/api_test.go b/cmd/appmgr/api_test.go index 89e4a08..2648dee 100755 --- a/cmd/appmgr/api_test.go +++ b/cmd/appmgr/api_test.go @@ -41,9 +41,6 @@ var helmError error type MockedHelmer struct { } -func (h *MockedHelmer) SetCM(cm ConfigMapper) { -} - func (sd *MockedHelmer) Initialize() { } @@ -59,7 +56,7 @@ func (h *MockedHelmer) List() (names []string, err error) { return names, helmError } -func (h *MockedHelmer) Install(m XappDeploy) (Xapp, error) { +func (h *MockedHelmer) Install(m ConfigMetadata) (Xapp, error) { return xapp, helmError } @@ -74,10 +71,9 @@ func TestMain(m *testing.M) { xapp = Xapp{} xapps = []Xapp{} - cm := MockedConfigMapper{} h := MockedHelmer{} x = XappManager{} - x.Initialize(&h, &cm) + x.Initialize(&h) // Just run on the background (for coverage) go x.Run() @@ -158,36 +154,6 @@ func TestDeleteAppRemovesGivenXapp(t *testing.T) { 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") @@ -292,15 +258,13 @@ func generateXapp(name, status, ver, iname, istatus, ip, port string) (x Xapp) { x.Status = status x.Version = ver p, _ := strconv.Atoi(port) - var msgs MessageTypes - instance := XappInstance{ Name: iname, Status: istatus, Ip: ip, Port: p, - TxMessages: msgs.TxMessages, - RxMessages: msgs.RxMessages, + TxMessages: []string{"RIC_E2_TERMINATION_HC_REQUEST", "RIC_E2_MANAGER_HC_REQUEST"}, + RxMessages: []string{"RIC_E2_TERMINATION_HC_RESPONSE", "RIC_E2_MANAGER_HC_RESPONSE"}, } x.Instances = append(x.Instances, instance) diff --git a/cmd/appmgr/desc.go b/cmd/appmgr/desc.go index 2f55460..c3a27a3 100755 --- a/cmd/appmgr/desc.go +++ b/cmd/appmgr/desc.go @@ -58,13 +58,8 @@ type CMMetadata struct { Namespace string `json:"namespace"` } -type CMError struct { - Field string `json:"field"` - Description string `json:"description"` -} - -func (cm *ConfigMap) UploadConfig() (cfg []XAppConfig) { - for _, name := range cm.GetNamesFromHelmRepo() { +func UploadConfig() (cfg []XAppConfig) { + for _, name := range GetNamesFromHelmRepo() { if name == "appmgr" { continue } @@ -73,12 +68,12 @@ func (cm *ConfigMap) UploadConfig() (cfg []XAppConfig) { Metadata: ConfigMetadata{Name: name, Namespace: "ricxapp", ConfigName: name + "-appconfig"}, } - err := cm.ReadSchema(name, &c) + err := ReadSchema(name, &c) if err != nil { continue } - err = cm.ReadConfigMap(c.Metadata.ConfigName, "ricxapp", &c.Configuration) + err = ReadConfigMap(name, "ricxapp", &c.Configuration) if err != nil { log.Println("No active configMap found, using default!") } @@ -88,18 +83,18 @@ func (cm *ConfigMap) UploadConfig() (cfg []XAppConfig) { return } -func (cm *ConfigMap) ReadSchema(name string, c *XAppConfig) (err error) { - if err = cm.FetchChart(name); err != nil { +func ReadSchema(name string, c *XAppConfig) (err error) { + if err = FetchChart(name); err != nil { return } tarDir := viper.GetString("xapp.tarDir") - err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.schema")), &c.Descriptor) + err = ReadFile(path.Join(tarDir, name, viper.GetString("xapp.schema")), &c.Descriptor) if err != nil { return } - err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.config")), &c.Configuration) + err = ReadFile(path.Join(tarDir, name, viper.GetString("xapp.config")), &c.Configuration) if err != nil { return } @@ -111,8 +106,8 @@ func (cm *ConfigMap) ReadSchema(name string, c *XAppConfig) (err error) { return } -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) +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) configMapJson, err := KubectlExec(args) if err != nil { return @@ -126,15 +121,15 @@ func (cm *ConfigMap) ReadConfigMap(ConfigName string, ns string, c *interface{}) return } -func (cm *ConfigMap) ApplyConfigMap(r XAppConfig, action string) (err error) { - c := ConfigMap{ +func ApplyConfigMap(r XAppConfig) (err error) { + cm := ConfigMap{ Kind: "ConfigMap", ApiVersion: "v1", Metadata: CMMetadata{Name: r.Metadata.Name, Namespace: r.Metadata.Namespace}, Data: r.Configuration, } - cmJson, err := json.Marshal(c) + cmJson, err := json.Marshal(cm) if err != nil { log.Println("Config marshalling failed: ", err) return @@ -147,37 +142,26 @@ func (cm *ConfigMap) ApplyConfigMap(r XAppConfig, action string) (err error) { return } - 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) + 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) _, err = KubectlExec(args) if err != nil { return } - log.Println("Configmap changes done!") - - return -} + log.Println("Configmap changes created!") -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 (cm *ConfigMap) UpdateConfigMap(r XAppConfig) (errList []CMError, err error) { - if errList, err = cm.Validate(r); err != nil { +func CreateConfigMap(r XAppConfig) (err error) { + if err = Validate(r); err != nil { return } - - // Re-create the configmap with the new parameters - err = cm.ApplyConfigMap(r, "apply") - return + return ApplyConfigMap(r) } -func (cm *ConfigMap) DeleteConfigMap(r XAppConfig) (c interface{}, err error) { - err = cm.ReadConfigMap(r.Metadata.ConfigName, r.Metadata.Namespace, &c) +func DeleteConfigMap(r XAppConfig) (cm interface{}, err error) { + err = ReadConfigMap(r.Metadata.Name, r.Metadata.Namespace, &cm) if err == nil { args := fmt.Sprintf(" delete configmap --namespace=%s %s", r.Metadata.Namespace, r.Metadata.ConfigName) _, err = KubectlExec(args) @@ -185,26 +169,23 @@ func (cm *ConfigMap) DeleteConfigMap(r XAppConfig) (c interface{}, err error) { return } -func (cm *ConfigMap) PurgeConfigMap(m XappDeploy) (c interface{}, err error) { +func PurgeConfigMap(m ConfigMetadata) (cm interface{}, err error) { if m.ConfigName == "" { m.ConfigName = m.Name + "-appconfig" } - md := ConfigMetadata{Name: m.Name, Namespace: m.Namespace, ConfigName: m.ConfigName} - - return cm.DeleteConfigMap(XAppConfig{Metadata: md}) + return DeleteConfigMap(XAppConfig{Metadata: m}) } -func (cm *ConfigMap) RestoreConfigMap(m XappDeploy, c interface{}) (err error) { +func RestoreConfigMap(m ConfigMetadata, cm 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 cm.ApplyConfigMap(XAppConfig{Metadata: md, Configuration: c}, "create") + return ApplyConfigMap(XAppConfig{Metadata: m, Configuration: cm}) } -func (cm *ConfigMap) GetNamesFromHelmRepo() (names []string) { +func GetNamesFromHelmRepo() (names []string) { rname := viper.GetString("helm.repo-name") cmdArgs := strings.Join([]string{"search ", rname}, "") @@ -225,37 +206,37 @@ func (cm *ConfigMap) GetNamesFromHelmRepo() (names []string) { return names } -func (cm *ConfigMap) Validate(req XAppConfig) (errList []CMError, err error) { +func Validate(req XAppConfig) (err error) { c := XAppConfig{} - err = cm.ReadSchema(req.Metadata.Name, &c) + err = ReadSchema(req.Metadata.Name, &c) if err != nil { log.Printf("No schema file found for '%s', aborting ...", req.Metadata.Name) - return + return err } - return cm.doValidate(c.Descriptor, req.Configuration) -} -func (cm *ConfigMap) doValidate(schema, cfg interface{}) (errList []CMError, err error) { - schemaLoader := gojsonschema.NewGoLoader(schema) - documentLoader := gojsonschema.NewGoLoader(cfg) + schemaLoader := gojsonschema.NewGoLoader(c.Descriptor) + documentLoader := gojsonschema.NewGoLoader(req.Configuration) + 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()) - for _, desc := range result.Errors() { - errList = append(errList, CMError{Field: desc.Field(), Description: desc.Description()}) + s := make([]string, 3) + for i, desc := range result.Errors() { + s = append(s, fmt.Sprintf(" (%d): %s.\n", i, desc.String())) } - return errList, errors.New("Validation failed!") + return errors.New(strings.Join(s, " ")) } return } -func (cm *ConfigMap) ReadFile(name string, data interface{}) (err error) { +func ReadFile(name string, data interface{}) (err error) { f, err := ioutil.ReadFile(name) if err != nil { log.Printf("Reading '%s' file failed: %v", name, err) @@ -271,7 +252,7 @@ func (cm *ConfigMap) ReadFile(name string, data interface{}) (err error) { return } -func (cm *ConfigMap) FetchChart(name string) (err error) { +func 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) @@ -280,7 +261,7 @@ func (cm *ConfigMap) FetchChart(name string) (err error) { return } -func (cm *ConfigMap) GetMessages(name string) (msgs MessageTypes) { +func GetMessages(name string) (msgs MessageTypes, err error) { log.Println("Fetching tx/rx messages for: ", name) return } diff --git a/cmd/appmgr/desc_test.go b/cmd/appmgr/desc_test.go deleted file mode 100755 index 36b6c72..0000000 --- a/cmd/appmgr/desc_test.go +++ /dev/null @@ -1,262 +0,0 @@ -/* -================================================================================== - 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 diff --git a/cmd/appmgr/helm.go b/cmd/appmgr/helm.go index 388fb5c..0f463d2 100755 --- a/cmd/appmgr/helm.go +++ b/cmd/appmgr/helm.go @@ -63,18 +63,14 @@ func Exec(args string) (out []byte, err error) { return stdout.Bytes(), errors.New(stderr.String()) } -var HelmExec = func(args string) (out []byte, err error) { +func HelmExec(args string) (out []byte, err error) { return Exec(strings.Join([]string{"helm", args}, " ")) } -var KubectlExec = func(args string) (out []byte, err error) { +func KubectlExec(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 @@ -107,6 +103,7 @@ func (h *Helm) Run(args string) (out []byte, err error) { // API functions func (h *Helm) Init() (out []byte, err error) { + // Add Tiller address as environment variable if err := addTillerEnv(); err != nil { return out, err @@ -116,6 +113,7 @@ 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")) if err != nil { @@ -142,40 +140,30 @@ func (h *Helm) AddRepo() (out []byte, err error) { return HelmExec(strings.Join([]string{"repo add ", rname, " ", repo, username, pwd}, "")) } -func (h *Helm) Install(m XappDeploy) (xapp Xapp, err error) { +func (h *Helm) Install(m ConfigMetadata) (xapp Xapp, err error) { out, err := h.Run(strings.Join([]string{"repo update "}, "")) if err != nil { return } - 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)) - } - - // ConfigMap exists, try to override - out, err = h.Run(getInstallArgs(m, true)) - if err == nil { - return h.ParseStatus(m.Name, string(out)) - } + m.Namespace = getNamespace(m.Namespace) + cm, cmErr := PurgeConfigMap(m) - cm, cmErr := h.cm.PurgeConfigMap(m) - out, err = h.Run(getInstallArgs(m, false)) + 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}, "")) if err != nil { return } if cmErr == nil { - cmErr = h.cm.RestoreConfigMap(m, cm) + cmErr = 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())) @@ -196,6 +184,7 @@ func (h *Helm) StatusAll() (xapps []Xapp, 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 { @@ -230,6 +219,7 @@ func (h *Helm) Fetch(name, tarDir string) error { // 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 { @@ -293,7 +283,7 @@ func (h *Helm) FillInstanceData(name string, out string, xapp *Xapp, msgs Messag return } - re := regexp.MustCompile(name + "-(\\w+-\\w+).*") + re := regexp.MustCompile(name + "-(\\d+).*") resources := re.FindAllStringSubmatch(string(result[0]), -1) if resources != nil { for _, v := range resources { @@ -310,11 +300,21 @@ func (h *Helm) FillInstanceData(name string, out string, xapp *Xapp, msgs Messag } 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) - h.FillInstanceData(name, out, &xapp, h.cm.GetMessages(name)) + 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) return } @@ -333,6 +333,7 @@ func (h *Helm) parseAllStatus(names []string) (xapps []Xapp, err error) { } func addTillerEnv() (err error) { + service := viper.GetString("helm.tiller-service") namespace := viper.GetString("helm.tiller-namespace") port := viper.GetString("helm.tiller-port") @@ -356,30 +357,6 @@ func getNamespace(namespace string) string { 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) } diff --git a/cmd/appmgr/helm_test.go b/cmd/appmgr/helm_test.go deleted file mode 100755 index c3ac3c5..0000000 --- a/cmd/appmgr/helm_test.go +++ /dev/null @@ -1,154 +0,0 @@ -/* -================================================================================== - 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 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 -} - diff --git a/cmd/appmgr/main.go b/cmd/appmgr/main.go index 9b9eebb..da50681 100755 --- a/cmd/appmgr/main.go +++ b/cmd/appmgr/main.go @@ -25,7 +25,7 @@ func main() { loadConfig() m := XappManager{} - m.Initialize(&Helm{}, &ConfigMap{}) + m.Initialize(&Helm{}) m.Run() } diff --git a/cmd/appmgr/types.go b/cmd/appmgr/types.go index 2e0f547..243179d 100755 --- a/cmd/appmgr/types.go +++ b/cmd/appmgr/types.go @@ -38,10 +38,12 @@ type Resource struct { } type Xapp struct { - Name string `json:"name"` - Status string `json:"status"` - Version string `json:"version"` - Instances []XappInstance `json:"instances"` + 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"` } type XappInstance struct { @@ -53,40 +55,17 @@ 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 XappDeploy) (xapp Xapp, err error) + Install(m ConfigMetadata) (xapp Xapp, err error) Status(name string) (xapp Xapp, err error) StatusAll() (xapps []Xapp, err error) List() (xapps []string, err error) @@ -97,7 +76,6 @@ type Helm struct { host string chartPath string initDone bool - cm ConfigMapper } type SubscriptionReq struct { diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100755 index 0000000..3277037 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,171 @@ +# 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"] diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh new file mode 100755 index 0000000..80e99df --- /dev/null +++ b/docker/docker-entrypoint.sh @@ -0,0 +1,24 @@ +#!/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 diff --git a/go.mod b/go.mod old mode 100644 new mode 100755 diff --git a/go.sum b/go.sum old mode 100644 new mode 100755 diff --git a/internal/sdlgo/LICENSE.txt b/internal/sdlgo/LICENSE.txt new file mode 100644 index 0000000..f886a1f --- /dev/null +++ b/internal/sdlgo/LICENSE.txt @@ -0,0 +1,204 @@ + + 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. diff --git a/internal/sdlgo/README b/internal/sdlgo/README new file mode 100644 index 0000000..e69de29 diff --git a/internal/sdlgo/bench_test.go b/internal/sdlgo/bench_test.go new file mode 100644 index 0000000..9f70fe6 --- /dev/null +++ b/internal/sdlgo/bench_test.go @@ -0,0 +1,173 @@ +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) + } + } + }) + }) + } +} diff --git a/internal/sdlgo/go.mod b/internal/sdlgo/go.mod new file mode 100644 index 0000000..d46db31 --- /dev/null +++ b/internal/sdlgo/go.mod @@ -0,0 +1,12 @@ +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 diff --git a/internal/sdlgo/go.sum b/internal/sdlgo/go.sum new file mode 100644 index 0000000..a6e2f77 --- /dev/null +++ b/internal/sdlgo/go.sum @@ -0,0 +1,37 @@ +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= diff --git a/internal/sdlgo/internal/sdlgoredis/sdlgoredis.go b/internal/sdlgo/internal/sdlgoredis/sdlgoredis.go new file mode 100644 index 0000000..2037496 --- /dev/null +++ b/internal/sdlgo/internal/sdlgoredis/sdlgoredis.go @@ -0,0 +1,58 @@ +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 +} diff --git a/internal/sdlgo/sdl.go b/internal/sdlgo/sdl.go new file mode 100644 index 0000000..cdf50f9 --- /dev/null +++ b/internal/sdlgo/sdl.go @@ -0,0 +1,166 @@ +/* + 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 +} diff --git a/internal/sdlgo/sdl_test.go b/internal/sdlgo/sdl_test.go new file mode 100644 index 0000000..8c64d05 --- /dev/null +++ b/internal/sdlgo/sdl_test.go @@ -0,0 +1,297 @@ +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) +} diff --git a/scripts/appmgrcli b/scripts/appmgrcli index 22f0677..ac00485 100755 --- a/scripts/appmgrcli +++ b/scripts/appmgrcli @@ -125,9 +125,6 @@ case $1 in (health|heal) cmd=health ;; - (config|upload) - cmd=config - ;; (help) usage exit 0 @@ -170,7 +167,6 @@ rest() { 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 @@ -192,7 +188,6 @@ base=/ric/v1 base_xapps=$base/xapps base_health=$base/health base_subs=$base/subscriptions -base_config=$base/config do_deploy() { if [ "x$1" != "x" ]; then @@ -338,40 +333,6 @@ do_subscriptions() { 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" @@ -385,9 +346,6 @@ case $cmd in (subscriptions) do_subscriptions "$2" "$3" "$4" "$5" "$6" "$7" ;; - (config) - do_config "$2" "$3" - ;; (health) if rest GET $base_health ; then echo OK diff --git a/test/schema.json b/test/schema.json deleted file mode 100755 index bc89da8..0000000 --- a/test/schema.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "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