From b175b949d5e3a9caf78e2c0c0db225d08bd73b07 Mon Sep 17 00:00:00 2001 From: Mohamed Abukar Date: Thu, 9 May 2019 16:30:58 +0300 Subject: [PATCH] Support for XApp configuration update (new retry) Change-Id: I19292b6c85f3293f70a90a717b61f9b5c6f3f1db Signed-off-by: Mohamed Abukar --- .gitignore | 2 + api/appmgr_rest_api.json | 223 +++++++++- api/appmgr_rest_api.yaml | 529 +++++++++++++++++++++++ cmd/appmgr/api.go | 40 +- cmd/appmgr/api_test.go | 44 +- cmd/appmgr/desc.go | 99 +++-- cmd/appmgr/desc_test.go | 262 +++++++++++ cmd/appmgr/helm.go | 77 ++-- cmd/appmgr/helm_test.go | 154 +++++++ cmd/appmgr/main.go | 2 +- cmd/appmgr/types.go | 36 +- docker/Dockerfile | 171 -------- docker/docker-entrypoint.sh | 24 - go.mod | 0 go.sum | 0 internal/sdlgo/LICENSE.txt | 204 --------- internal/sdlgo/README | 0 internal/sdlgo/bench_test.go | 173 -------- internal/sdlgo/go.mod | 12 - internal/sdlgo/go.sum | 37 -- internal/sdlgo/internal/sdlgoredis/sdlgoredis.go | 58 --- internal/sdlgo/sdl.go | 166 ------- internal/sdlgo/sdl_test.go | 297 ------------- scripts/appmgrcli | 42 ++ test/schema.json | 46 ++ 25 files changed, 1469 insertions(+), 1229 deletions(-) create mode 100644 .gitignore create mode 100644 api/appmgr_rest_api.yaml create mode 100755 cmd/appmgr/desc_test.go create mode 100755 cmd/appmgr/helm_test.go delete mode 100755 docker/Dockerfile delete mode 100755 docker/docker-entrypoint.sh mode change 100755 => 100644 go.mod mode change 100755 => 100644 go.sum delete mode 100644 internal/sdlgo/LICENSE.txt delete mode 100644 internal/sdlgo/README delete mode 100644 internal/sdlgo/bench_test.go delete mode 100644 internal/sdlgo/go.mod delete mode 100644 internal/sdlgo/go.sum delete mode 100644 internal/sdlgo/internal/sdlgoredis/sdlgoredis.go delete mode 100644 internal/sdlgo/sdl.go delete mode 100644 internal/sdlgo/sdl_test.go create mode 100755 test/schema.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa8d028 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/cache/* +cache/* diff --git a/api/appmgr_rest_api.json b/api/appmgr_rest_api.json index 66250ae..92cc418 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.0.11", + "version": "0.1.2", "title": "RIC appmgr", "license": { "name": "Apache 2.0", @@ -19,6 +19,9 @@ "/health": { "get": { "summary": "Health check of xApp Manager", + "tags": [ + "health" + ], "operationId": "getHealth", "responses": { "200": { @@ -30,6 +33,9 @@ "/xapps": { "post": { "summary": "Deploy a xapp", + "tags": [ + "xapp" + ], "operationId": "deployXapp", "consumes": [ "application/json" @@ -74,6 +80,9 @@ }, "get": { "summary": "Returns the status of all xapps", + "tags": [ + "xapp" + ], "operationId": "getAllXapps", "produces": [ "application/json" @@ -94,6 +103,9 @@ "/xapps/{xAppName}": { "get": { "summary": "Returns the status of a given xapp", + "tags": [ + "xapp" + ], "operationId": "getXappByName", "produces": [ "application/json" @@ -127,6 +139,9 @@ }, "delete": { "summary": "Undeploy an existing xapp", + "tags": [ + "xapp" + ], "operationId": "undeployXapp", "parameters": [ { @@ -153,6 +168,9 @@ "/xapps/{xAppName}/instances/{xAppInstanceName}": { "get": { "summary": "Returns the status of a given xapp", + "tags": [ + "xapp" + ], "operationId": "getXappInstanceByName", "produces": [ "application/json" @@ -192,9 +210,144 @@ } } }, + "/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" @@ -227,6 +380,10 @@ }, "get": { "summary": "Returns all subscriptions", + "tags": [ + "xapp", + "subscriptions" + ], "operationId": "getSubscriptions", "produces": [ "application/json" @@ -244,6 +401,10 @@ "/subscriptions/{subscriptionId}": { "get": { "summary": "Returns the information of subscription", + "tags": [ + "xapp", + "subscriptions" + ], "operationId": "getSubscriptionById", "produces": [ "application/json" @@ -274,6 +435,10 @@ }, "put": { "summary": "Modify event subscription", + "tags": [ + "xapp", + "subscriptions" + ], "operationId": "modifySubscription", "consumes": [ "application/json" @@ -313,6 +478,10 @@ }, "delete": { "summary": "Unsubscribe event", + "tags": [ + "xapp", + "subscriptions" + ], "description": "", "operationId": "deleteSubscription", "parameters": [ @@ -423,6 +592,58 @@ } } }, + "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 new file mode 100644 index 0000000..0a5cf73 --- /dev/null +++ b/api/appmgr_rest_api.yaml @@ -0,0 +1,529 @@ +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 2e7ddc7..8450d02 100755 --- a/cmd/appmgr/api.go +++ b/cmd/appmgr/api.go @@ -30,7 +30,11 @@ import ( // 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{ @@ -51,6 +55,7 @@ func (m *XappManager) Initialize(h Helmer) { {"GET", "/ric/v1/config", m.getConfig}, {"POST", "/ric/v1/config", m.createConfig}, + {"PUT", "/ric/v1/config", m.updateConfig}, {"DELETE", "/ric/v1/config", m.deleteConfig}, } @@ -67,7 +72,6 @@ func (m *XappManager) finalize(h Helmer) { m.sd = SubscriptionDispatcher{} m.sd.Initialize() - m.helm = h m.helm.Initialize() m.notifyClients() @@ -157,7 +161,7 @@ func (m *XappManager) deployXapp(w http.ResponseWriter, r *http.Request) { 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!") @@ -262,7 +266,8 @@ func (m *XappManager) notifyClients() { } 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) { @@ -271,20 +276,41 @@ 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 } diff --git a/cmd/appmgr/api_test.go b/cmd/appmgr/api_test.go index 2648dee..89e4a08 100755 --- a/cmd/appmgr/api_test.go +++ b/cmd/appmgr/api_test.go @@ -41,6 +41,9 @@ var helmError error type MockedHelmer struct { } +func (h *MockedHelmer) SetCM(cm ConfigMapper) { +} + func (sd *MockedHelmer) Initialize() { } @@ -56,7 +59,7 @@ func (h *MockedHelmer) List() (names []string, err error) { return names, helmError } -func (h *MockedHelmer) Install(m ConfigMetadata) (Xapp, error) { +func (h *MockedHelmer) Install(m XappDeploy) (Xapp, error) { return xapp, helmError } @@ -71,9 +74,10 @@ func TestMain(m *testing.M) { 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() @@ -154,6 +158,36 @@ 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") @@ -258,13 +292,15 @@ 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: []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) diff --git a/cmd/appmgr/desc.go b/cmd/appmgr/desc.go index c3a27a3..2f55460 100755 --- a/cmd/appmgr/desc.go +++ b/cmd/appmgr/desc.go @@ -58,8 +58,13 @@ type CMMetadata struct { 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 } @@ -68,12 +73,12 @@ func UploadConfig() (cfg []XAppConfig) { 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!") } @@ -83,18 +88,18 @@ func UploadConfig() (cfg []XAppConfig) { 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 } @@ -106,8 +111,8 @@ func ReadSchema(name string, c *XAppConfig) (err error) { 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 @@ -121,15 +126,15 @@ func ReadConfigMap(name string, ns string, c *interface{}) (err error) { 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 @@ -142,26 +147,37 @@ func ApplyConfigMap(r XAppConfig) (err error) { 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) @@ -169,23 +185,26 @@ func DeleteConfigMap(r XAppConfig) (cm interface{}, err error) { 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}, "") @@ -206,37 +225,37 @@ func GetNamesFromHelmRepo() (names []string) { 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) @@ -252,7 +271,7 @@ func ReadFile(name string, data interface{}) (err error) { 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) @@ -261,7 +280,7 @@ func FetchChart(name string) (err error) { 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 } diff --git a/cmd/appmgr/desc_test.go b/cmd/appmgr/desc_test.go new file mode 100755 index 0000000..36b6c72 --- /dev/null +++ b/cmd/appmgr/desc_test.go @@ -0,0 +1,262 @@ +/* +================================================================================== + 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 0f463d2..388fb5c 100755 --- a/cmd/appmgr/helm.go +++ b/cmd/appmgr/helm.go @@ -63,14 +63,18 @@ func Exec(args string) (out []byte, err error) { 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 @@ -103,7 +107,6 @@ 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 @@ -113,7 +116,6 @@ 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 { @@ -140,30 +142,40 @@ func (h *Helm) AddRepo() (out []byte, err error) { 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())) @@ -184,7 +196,6 @@ 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 { @@ -219,7 +230,6 @@ 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 { @@ -283,7 +293,7 @@ func (h *Helm) FillInstanceData(name string, out string, xapp *Xapp, msgs Messag 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 { @@ -300,21 +310,11 @@ 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) - 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 } @@ -333,7 +333,6 @@ 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") @@ -357,6 +356,30 @@ 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 new file mode 100755 index 0000000..c3ac3c5 --- /dev/null +++ b/cmd/appmgr/helm_test.go @@ -0,0 +1,154 @@ +/* +================================================================================== + 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 da50681..9b9eebb 100755 --- a/cmd/appmgr/main.go +++ b/cmd/appmgr/main.go @@ -25,7 +25,7 @@ func main() { loadConfig() m := XappManager{} - m.Initialize(&Helm{}) + m.Initialize(&Helm{}, &ConfigMap{}) m.Run() } diff --git a/cmd/appmgr/types.go b/cmd/appmgr/types.go index 243179d..2e0f547 100755 --- a/cmd/appmgr/types.go +++ b/cmd/appmgr/types.go @@ -38,12 +38,10 @@ type Resource struct { } 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 { @@ -55,17 +53,40 @@ 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) @@ -76,6 +97,7 @@ type Helm struct { host string chartPath string initDone bool + cm ConfigMapper } type SubscriptionReq struct { diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100755 index 3277037..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,171 +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. - -#---------------------------------------------------------- -# -#---------------------------------------------------------- -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 deleted file mode 100755 index 80e99df..0000000 --- a/docker/docker-entrypoint.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/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 100755 new mode 100644 diff --git a/go.sum b/go.sum old mode 100755 new mode 100644 diff --git a/internal/sdlgo/LICENSE.txt b/internal/sdlgo/LICENSE.txt deleted file mode 100644 index f886a1f..0000000 --- a/internal/sdlgo/LICENSE.txt +++ /dev/null @@ -1,204 +0,0 @@ - - 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 deleted file mode 100644 index e69de29..0000000 diff --git a/internal/sdlgo/bench_test.go b/internal/sdlgo/bench_test.go deleted file mode 100644 index 9f70fe6..0000000 --- a/internal/sdlgo/bench_test.go +++ /dev/null @@ -1,173 +0,0 @@ -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 deleted file mode 100644 index d46db31..0000000 --- a/internal/sdlgo/go.mod +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index a6e2f77..0000000 --- a/internal/sdlgo/go.sum +++ /dev/null @@ -1,37 +0,0 @@ -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 deleted file mode 100644 index 2037496..0000000 --- a/internal/sdlgo/internal/sdlgoredis/sdlgoredis.go +++ /dev/null @@ -1,58 +0,0 @@ -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 deleted file mode 100644 index cdf50f9..0000000 --- a/internal/sdlgo/sdl.go +++ /dev/null @@ -1,166 +0,0 @@ -/* - 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 deleted file mode 100644 index 8c64d05..0000000 --- a/internal/sdlgo/sdl_test.go +++ /dev/null @@ -1,297 +0,0 @@ -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 ac00485..22f0677 100755 --- a/scripts/appmgrcli +++ b/scripts/appmgrcli @@ -125,6 +125,9 @@ case $1 in (health|heal) cmd=health ;; + (config|upload) + cmd=config + ;; (help) usage exit 0 @@ -167,6 +170,7 @@ 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 @@ -188,6 +192,7 @@ 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 @@ -333,6 +338,40 @@ 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" @@ -346,6 +385,9 @@ 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 new file mode 100755 index 0000000..bc89da8 --- /dev/null +++ b/test/schema.json @@ -0,0 +1,46 @@ +{ + "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 -- 2.16.6