From 193caf9d7e08b84a0b9c1f0352924a7efd77e77c Mon Sep 17 00:00:00 2001 From: Abukar Mohamed Date: Thu, 11 Apr 2019 13:51:07 +0000 Subject: [PATCH] New standard GO project layout By Juha Hyttinen Change-Id: I82e73e38d4ecc96a4b827047d570b4a0da35d129 Signed-off-by: Abukar Mohamed --- build/Makefile => Makefile | 110 +++--- README.md | 11 +- .../appmgr_rest_api.json | 14 +- cmd/appmgr/api.go | 286 +++++++++++++++ cmd/appmgr/api_test.go | 272 ++++++++++++++ {src => cmd/appmgr}/config.go | 40 +-- cmd/appmgr/db.go | 91 +++++ cmd/appmgr/helm.go | 389 +++++++++++++++++++++ {src => cmd/appmgr}/logger.go | 27 +- {src => cmd/appmgr}/main.go | 10 +- cmd/appmgr/subscriptions.go | 149 ++++++++ cmd/appmgr/subscriptions_test.go | 198 +++++++++++ cmd/appmgr/types.go | 130 +++++++ config/appmgr.yaml | 5 +- {build => docker}/Dockerfile | 44 ++- {build => docker}/docker-entrypoint.sh | 0 go.mod | 16 + go.sum | 63 ++++ helm_chart/appmgr/templates/deployment.yaml | 10 +- helm_chart/appmgr/values.yaml | 10 +- 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 ++++++++++++++++ {cli => scripts}/appmgrcli | 4 +- src/api.go | 251 ------------- src/api_test.go | 265 -------------- src/helm.go | 359 ------------------- src/helm_test.go | 277 --------------- src/subscriptions.go | 131 ------- src/subscriptions_test.go | 191 ---------- src/types.go | 126 ------- 36 files changed, 2709 insertions(+), 1717 deletions(-) rename build/Makefile => Makefile (51%) mode change 100755 => 100644 rename rest_api/xapp_manager_rest_api.json => api/appmgr_rest_api.json (98%) create mode 100755 cmd/appmgr/api.go create mode 100755 cmd/appmgr/api_test.go rename {src => cmd/appmgr}/config.go (56%) create mode 100755 cmd/appmgr/db.go create mode 100755 cmd/appmgr/helm.go rename {src => cmd/appmgr}/logger.go (69%) rename {src => cmd/appmgr}/main.go (89%) create mode 100755 cmd/appmgr/subscriptions.go create mode 100755 cmd/appmgr/subscriptions_test.go create mode 100755 cmd/appmgr/types.go rename {build => docker}/Dockerfile (78%) rename {build => docker}/docker-entrypoint.sh (100%) create mode 100755 go.mod create mode 100755 go.sum create mode 100644 internal/sdlgo/LICENSE.txt create mode 100644 internal/sdlgo/README create mode 100644 internal/sdlgo/bench_test.go create mode 100644 internal/sdlgo/go.mod create mode 100644 internal/sdlgo/go.sum create mode 100644 internal/sdlgo/internal/sdlgoredis/sdlgoredis.go create mode 100644 internal/sdlgo/sdl.go create mode 100644 internal/sdlgo/sdl_test.go rename {cli => scripts}/appmgrcli (98%) delete mode 100755 src/api.go delete mode 100755 src/api_test.go delete mode 100755 src/helm.go delete mode 100755 src/helm_test.go delete mode 100755 src/subscriptions.go delete mode 100755 src/subscriptions_test.go delete mode 100755 src/types.go diff --git a/build/Makefile b/Makefile old mode 100755 new mode 100644 similarity index 51% rename from build/Makefile rename to Makefile index 266ac05..8061874 --- a/build/Makefile +++ b/Makefile @@ -13,20 +13,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -BUILD_DIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST)))) -ROOT_DIR:=$(abspath $(BUILD_DIR)/..) - -BUILD_PREFIX?="${USER}-" +#------------------------------------------------------------------------------ +# +#-------------------------------------------------------------------- ---------- +ROOT_DIR:=$(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +BUILD_DIR:=$(abspath $(ROOT_DIR)/build) -XAPP_MGR:=appmgr -XAPP_MGR_DOCKER:=${BUILD_PREFIX}appmgr +PACKAGEURL:="gerrit.oran-osc.org/r/ric-plt/appmgr" +HELMVERSION:=v2.13.0-rc.1 -GOSRC := $(abspath $(BUILD_DIR)/../src) -GOFILES := $(GOSRC)/*.go +#------------------------------------------------------------------------------ +# +#-------------------------------------------------------------------- ---------- COVEROUT := $(abspath $(BUILD_DIR)/cover.out) COVERHTML := $(abspath $(BUILD_DIR)/cover.html) +GOOS=$(shell go env GOOS) GOCMD=go GOBUILD=$(GOCMD) build -a -installsuffix cgo GORUN=$(GOCMD) run -a -installsuffix cgo @@ -34,56 +37,68 @@ GOCLEAN=$(GOCMD) clean GOTEST=$(GOCMD) test -v -coverprofile $(COVEROUT) GOGET=$(GOCMD) get -HELMVERSION:=v2.13.0-rc.1 +GOFILES := $(shell find $(ROOT_DIR) -name '*.go' -not -name '*_test.go') go.mod go.sum +GOFILES_NO_VENDOR := $(shell find $(ROOT_DIR) -path ./vendor -prune -o -name "*.go" -not -name '*_test.go' -print) + +CMDS:=$(BUILD_DIR)/appmgr #------------------------------------------------------------------------------ # #-------------------------------------------------------------------- ---------- -.PHONY: FORCE build deps run unit-test test-pkg test clean docker-base-build docker-base-clean docker-build docker-run docker-clean docker-test-build docker-test-run-unittest docker-test-run-sanity docker-test-run docker-test-clean - -.DEFAULT: build + .DEFAULT: build default: build +.PHONY: FORCE + FORCE: #------------------------------------------------------------------------------ # #------------------------------------------------------------------------------ -XAPP_MGR_DOCKER:=$(shell echo $(XAPP_MGR_DOCKER) | tr '[:upper:]' '[:lower:]') +$(CMDS): $(GOFILES) + GO111MODULE=on GO_ENABLED=0 GOOS=linux $(GOBUILD) -o $@ ./cmd/$(shell basename "$@") -#XAPP_MGR_DOCKER:=$(subst /,_,${XAPP_MGR_DOCKER}) -#------------------------------------------------------------------------------ -# -#------------------------------------------------------------------------------ +$(addsuffix _test,$(CMDS)): $(GOFILES) + GO111MODULE=on GO_ENABLED=0 GOOS=linux $(GOTEST) -c -o $@ ./cmd/$(patsubst %_test,%, $(shell basename "$@")) + timeout -s KILL 5s $@ -test.coverprofile $(COVEROUT) + go tool cover -html=$(COVEROUT) -o $(COVERHTML) -$(BUILD_DIR)$(XAPP_MGR): deps ${wildcard $(GOFILES)} - GO_ENABLED=0 GOOS=linux $(GOBUILD) -o $(BUILD_DIR)$(XAPP_MGR) $(GOFILES) -build: $(BUILD_DIR)$(XAPP_MGR) +build: $(CMDS) -deps: ${wildcard $(GOFILES)} - cd $(GOSRC) && $(GOGET) -run: $(BUILD_DIR)$(XAPP_MGR) - $(BUILD_DIR)$(XAPP_MGR) -host-addr="localhost:8080" -helm-host="localhost:31807" -helm-chart="./" +test: $(addsuffix _test,$(CMDS)) + + +test-fmt: $(GOFILES_NO_VENDOR) + @(RESULT="$$(gofmt -l $^)"; test -z "$${RESULT}" || (echo -e "gofmt failed:\n$${RESULT}" && false) ) + + +fmt: $(GOFILES_NO_VENDOR) + gofmt -w -s $^ -unit-test: - cd $(GOSRC) && $(GOTEST) - go tool cover -html=$(COVEROUT) -o $(COVERHTML) clean: @echo " > Cleaning build cache" - @-rm -rf $(XAPP_MGR) 2> /dev/null + @-rm -rf $(CMDS)* 2> /dev/null go clean 2> /dev/null #------------------------------------------------------------------------------ # #------------------------------------------------------------------------------ -DCKR_BUILD_OPTS:=${DCKR_BUILD_OPTS} --network=host --build-arg HELMVERSION=${HELMVERSION} +BUILD_PREFIX?="${USER}-" + +DCKR_FILE:=docker/Dockerfile + +DCKR_NAME:=${BUILD_PREFIX}appmgr +DCKR_NAME:=$(shell echo $(DCKR_NAME) | tr '[:upper:]' '[:lower:]') +DCKR_NAME:=$(subst /,_,${DCKR_NAME}) + +DCKR_BUILD_OPTS:=${DCKR_BUILD_OPTS} --network=host --build-arg HELMVERSION=${HELMVERSION} --build-arg PACKAGEURL=${PACKAGEURL} DCKR_RUN_OPTS:=${DCKR_RUN_OPTS} --rm -i DCKR_RUN_OPTS:=${DCKR_RUN_OPTS}$(shell test -t 0 && echo ' -t') @@ -95,16 +110,16 @@ DCKR_RUN_OPTS:=${DCKR_RUN_OPTS}$(shell test -e /var/run/docker.sock && echo ' -v # #------------------------------------------------------------------------------ docker-name: - @echo $(XAPP_MGR_DOCKER) + @echo $(DCKR_NAME) docker-build: - docker build --target release ${DCKR_BUILD_OPTS} -t $(XAPP_MGR_DOCKER) -f Dockerfile ../. + docker build --target release ${DCKR_BUILD_OPTS} -t $(DCKR_NAME) -f $(DCKR_FILE) . docker-run: - docker run ${DCKR_RUN_OPTS} -v /opt/ric:/opt/ric -p 8080:8080 $(XAPP_MGR_DOCKER) + docker run ${DCKR_RUN_OPTS} -v /opt/ric:/opt/ric -p 8080:8080 $(DCKR_NAME) docker-clean: - docker rmi $(XAPP_MGR_DOCKER) + docker rmi $(DCKR_NAME) #------------------------------------------------------------------------------ @@ -112,18 +127,31 @@ docker-clean: #------------------------------------------------------------------------------ docker-test-build: - docker build --target test_unit ${DCKR_BUILD_OPTS} -t ${XAPP_MGR_DOCKER}-test_unit -f Dockerfile ../. - docker build --target test_sanity ${DCKR_BUILD_OPTS} -t ${XAPP_MGR_DOCKER}-test_sanity -f Dockerfile ../. + docker build --target test_unit ${DCKR_BUILD_OPTS} -t ${DCKR_NAME}-test_unit -f $(DCKR_FILE) . + docker build --target test_sanity ${DCKR_BUILD_OPTS} -t ${DCKR_NAME}-test_sanity -f $(DCKR_FILE) . + docker build --target test_fmt ${DCKR_BUILD_OPTS} -t ${DCKR_NAME}-test_fmt -f $(DCKR_FILE) . docker-test-run-unit: - docker run ${DCKR_RUN_OPTS} ${XAPP_MGR_DOCKER}-test_unit + @( \ + RETVAL=0;\ + docker network create --driver bridge ${DCKR_NAME}-test_unit_network;\ + docker run ${DCKR_RUN_OPTS} -d --name ${DCKR_NAME}-test_unit_redis --network ${DCKR_NAME}-test_unit_network redis;\ + docker run ${DCKR_RUN_OPTS} --name ${DCKR_NAME}-test_unit_run --network ${DCKR_NAME}-test_unit_network -e DBAAS_SERVICE_HOST=${DCKR_NAME}-test_unit_redis ${DCKR_NAME}-test_unit;\ + RETVAL=$$?;\ + docker stop ${DCKR_NAME}-test_unit_redis;\ + docker network rm ${DCKR_NAME}-test_unit_network;\ + exit $${RETVAL};\ + ) -docker-test-run-sanity: - docker run ${DCKR_RUN_OPTS} ${XAPP_MGR_DOCKER}-test_sanity -docker-test-run: docker-test-run-sanity docker-test-run-unit +docker-test-run-fmt: + docker run ${DCKR_RUN_OPTS} ${DCKR_NAME}-test_fmt + +docker-test-run-sanity: + docker run ${DCKR_RUN_OPTS} ${DCKR_NAME}-test_sanity docker-test-clean: - docker rmi -f ${XAPP_MGR_DOCKER}-test_unit - docker rmi -f ${XAPP_MGR_DOCKER}-test_sanity + docker rmi -f ${DCKR_NAME}-test_unit + docker rmi -f ${DCKR_NAME}-test_sanity + docker rmi -f ${DCKR_NAME}-test_fmt diff --git a/README.md b/README.md index f28f1dc..d3cf791 100755 --- a/README.md +++ b/README.md @@ -37,28 +37,29 @@ TBD later ## Prerequisites Make sure that following tools are properly installed and configured * GO (golang) development and runtime tools +* mdclog (com/log) * Docker * Kubernates and related tools (kubectl and helm) * Xapp Docker repo (either local or remote) * Xapp Helm charts -* com/log +* ... ## Building go binary and docker container for xApp Manager ```sh -# Change to build-folder and run following command +# Run following command. Make sure that mdclog is installed and found in the standard library path make docker-build ``` ## Running xApp Manager unit tests ```sh -# Change to build-folder and run following command +# Run following command make test ``` ## Running xApp Manager locally ```sh # Now run the xApp manager -./xapp_mgr -f ../config/appmgr.yaml +build/appmgr -f config/appmgr.yaml ``` # Running Docker container of xApp manager @@ -387,4 +388,4 @@ $ appmgrcli subs list # Additional info ```sh Todo -``` +``` \ No newline at end of file diff --git a/rest_api/xapp_manager_rest_api.json b/api/appmgr_rest_api.json similarity index 98% rename from rest_api/xapp_manager_rest_api.json rename to api/appmgr_rest_api.json index 951f199..c5a4c0b 100644 --- a/rest_api/xapp_manager_rest_api.json +++ b/api/appmgr_rest_api.json @@ -10,13 +10,13 @@ } }, "host": "hostname", - "basePath": "/ric/v1/xapps", + "basePath": "/ric/v1", "schemes": [ "https", "http" ], "paths": { - "/ric/v1/health": { + "/health": { "get": { "summary": "Health check of xApp Manager", "operationId": "getHealth", @@ -27,7 +27,7 @@ } } }, - "/ric/v1/xapps": { + "/xapps": { "post": { "summary": "Deploy a xapp", "operationId": "deployXapp", @@ -91,7 +91,7 @@ } } }, - "/ric/v1/xapps/{xAppName}": { + "/xapps/{xAppName}": { "get": { "summary": "Returns the status of a given xapp", "operationId": "getXappByName", @@ -150,7 +150,7 @@ } } }, - "/ric/v1/xapps/{xAppName}/instances/{xAppInstanceName}": { + "/xapps/{xAppName}/instances/{xAppInstanceName}": { "get": { "summary": "Returns the status of a given xapp", "operationId": "getXappInstanceByName", @@ -192,7 +192,7 @@ } } }, - "/ric/v1/subscriptions": { + "/subscriptions": { "post": { "summary": "Subscribe event", "operationId": "addSubscription", @@ -241,7 +241,7 @@ } } }, - "/ric/v1/subscriptions/{subscriptionId}": { + "/subscriptions/{subscriptionId}": { "get": { "summary": "Returns the information of subscription", "operationId": "getSubscriptionById", diff --git a/cmd/appmgr/api.go b/cmd/appmgr/api.go new file mode 100755 index 0000000..0d3fef9 --- /dev/null +++ b/cmd/appmgr/api.go @@ -0,0 +1,286 @@ +/* +================================================================================== + 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 ( + "encoding/json" + "github.com/gorilla/mux" + "github.com/spf13/viper" + "log" + "net/http" +) + +// API functions + +func (m *XappManager) Initialize(h Helmer) { + /* + m.sd = SubscriptionDispatcher{} + m.sd.Initialize() + m.helm = h + m.helm.Initialize() + */ + m.router = mux.NewRouter().StrictSlash(true) + + resources := []Resource{ + {"GET", "/ric/v1/health/alive", m.getHealthStatus}, + {"GET", "/ric/v1/health/ready", m.getHealthStatus}, + + {"GET", "/ric/v1/xapps", m.getAllXapps}, + {"GET", "/ric/v1/xapps/{name}", m.getXappByName}, + {"GET", "/ric/v1/xapps/{name}/instances/{id}", m.getXappInstanceByName}, + {"POST", "/ric/v1/xapps", m.deployXapp}, + {"DELETE", "/ric/v1/xapps/{name}", m.undeployXapp}, + + {"GET", "/ric/v1/subscriptions", m.getSubscriptions}, + {"POST", "/ric/v1/subscriptions", m.addSubscription}, + {"GET", "/ric/v1/subscriptions/{id}", m.getSubscription}, + {"DELETE", "/ric/v1/subscriptions/{id}", m.deleteSubscription}, + {"PUT", "/ric/v1/subscriptions/{id}", m.updateSubscription}, + } + + for _, resource := range resources { + handler := Logger(resource.HandlerFunc) + //handler = m.serviceChecker(handler) + m.router.Methods(resource.Method).Path(resource.Url).Handler(handler) + } + + go m.finalize(h) +} + +func (m *XappManager) finalize(h Helmer) { + m.sd = SubscriptionDispatcher{} + m.sd.Initialize() + + m.helm = h + m.helm.Initialize() + + m.notifyClients() + m.ready = true +} + +func (m *XappManager) serviceChecker(inner http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.RequestURI() == "/ric/v1/health/alive" || m.ready == true { + inner.ServeHTTP(w, r) + } else { + respondWithJSON(w, http.StatusServiceUnavailable, nil) + } + }) +} + +// API: XAPP handlers +func (m *XappManager) getHealthStatus(w http.ResponseWriter, r *http.Request) { + respondWithJSON(w, http.StatusOK, nil) +} + +func (m *XappManager) Run() { + host := viper.GetString("local.host") + if host == "" { + host = ":8080" + } + log.Printf("Xapp manager started ... serving on %s\n", host) + + log.Fatal(http.ListenAndServe(host, m.router)) +} + +func (m *XappManager) getXappByName(w http.ResponseWriter, r *http.Request) { + xappName, ok := getResourceId(r, w, "name") + if ok != true { + return + } + + if xapp, err := m.helm.Status(xappName); err == nil { + respondWithJSON(w, http.StatusOK, xapp) + } else { + respondWithError(w, http.StatusNotFound, err.Error()) + } +} + +func (m *XappManager) getXappInstanceByName(w http.ResponseWriter, r *http.Request) { + xappName, ok := getResourceId(r, w, "name") + if ok != true { + return + } + + xapp, err := m.helm.Status(xappName) + if err != nil { + respondWithError(w, http.StatusNotFound, err.Error()) + return + } + + xappInstanceName, ok := getResourceId(r, w, "id") + if ok != true { + return + } + + for _, v := range xapp.Instances { + if v.Name == xappInstanceName { + respondWithJSON(w, http.StatusOK, v) + return + } + } + mdclog(MdclogErr, "Xapp instance not found - url="+r.URL.RequestURI()) + + respondWithError(w, http.StatusNotFound, "Xapp instance not found") +} + +func (m *XappManager) getAllXapps(w http.ResponseWriter, r *http.Request) { + xapps, err := m.helm.StatusAll() + if err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusOK, xapps) +} + +func (m *XappManager) deployXapp(w http.ResponseWriter, r *http.Request) { + if r.Body == nil { + mdclog(MdclogErr, "No xapp data found in request body - url="+r.URL.RequestURI()) + respondWithError(w, http.StatusMethodNotAllowed, "No xapp data!") + return + } + + var xapp Xapp + if err := json.NewDecoder(r.Body).Decode(&xapp); err != nil { + mdclog(MdclogErr, "Invalid xapp data in request body - url="+r.URL.RequestURI()) + respondWithError(w, http.StatusMethodNotAllowed, "Invalid xapp data!") + return + } + defer r.Body.Close() + + xapp, err := m.helm.Install(xapp.Name) + if err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusCreated, xapp) + + m.sd.Publish(xapp, EventType("created")) +} + +func (m *XappManager) undeployXapp(w http.ResponseWriter, r *http.Request) { + xappName, ok := getResourceId(r, w, "name") + if ok != true { + return + } + + xapp, err := m.helm.Delete(xappName) + if err != nil { + respondWithError(w, http.StatusInternalServerError, err.Error()) + return + } + + respondWithJSON(w, http.StatusNoContent, nil) + + m.sd.Publish(xapp, EventType("deleted")) +} + +// API: resthook handlers +func (m *XappManager) getSubscriptions(w http.ResponseWriter, r *http.Request) { + respondWithJSON(w, http.StatusOK, m.sd.GetAll()) +} + +func (m *XappManager) getSubscription(w http.ResponseWriter, r *http.Request) { + if id, ok := getResourceId(r, w, "id"); ok == true { + if s, ok := m.sd.Get(id); ok { + respondWithJSON(w, http.StatusOK, s) + } else { + mdclog(MdclogErr, "Subscription not found - url="+r.URL.RequestURI()) + respondWithError(w, http.StatusNotFound, "Subscription not found") + } + } +} + +func (m *XappManager) deleteSubscription(w http.ResponseWriter, r *http.Request) { + if id, ok := getResourceId(r, w, "id"); ok == true { + if _, ok := m.sd.Delete(id); ok { + respondWithJSON(w, http.StatusNoContent, nil) + } else { + mdclog(MdclogErr, "Subscription not found - url="+r.URL.RequestURI()) + respondWithError(w, http.StatusNotFound, "Subscription not found") + } + } +} + +func (m *XappManager) addSubscription(w http.ResponseWriter, r *http.Request) { + var req SubscriptionReq + if r.Body == nil || json.NewDecoder(r.Body).Decode(&req) != nil { + mdclog(MdclogErr, "Invalid request payload - url="+r.URL.RequestURI()) + respondWithError(w, http.StatusMethodNotAllowed, "Invalid request payload") + return + } + defer r.Body.Close() + + respondWithJSON(w, http.StatusCreated, m.sd.Add(req)) +} + +func (m *XappManager) updateSubscription(w http.ResponseWriter, r *http.Request) { + if id, ok := getResourceId(r, w, "id"); ok == true { + var req SubscriptionReq + if r.Body == nil || json.NewDecoder(r.Body).Decode(&req) != nil { + mdclog(MdclogErr, "Invalid request payload - url="+r.URL.RequestURI()) + respondWithError(w, http.StatusMethodNotAllowed, "Invalid request payload") + return + } + defer r.Body.Close() + + if s, ok := m.sd.Update(id, req); ok { + respondWithJSON(w, http.StatusOK, s) + } else { + mdclog(MdclogErr, "Subscription not found - url="+r.URL.RequestURI()) + respondWithError(w, http.StatusNotFound, "Subscription not found") + } + } +} + +func (m *XappManager) notifyClients() { + xapps, err := m.helm.StatusAll() + if err != nil { + mdclog(MdclogInfo, "Couldn't fetch xapps status information"+err.Error()) + return + } + + m.sd.notifyClients(xapps, "updated") +} + +// Helper functions +func respondWithError(w http.ResponseWriter, code int, message string) { + respondWithJSON(w, code, map[string]string{"error": message}) +} + +func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + if payload != nil { + response, _ := json.Marshal(payload) + w.Write(response) + } +} + +func getResourceId(r *http.Request, w http.ResponseWriter, pattern string) (id string, ok bool) { + if id, ok = mux.Vars(r)[pattern]; ok != true { + mdclog(MdclogErr, "Couldn't resolve name/id from the request URL") + respondWithError(w, http.StatusMethodNotAllowed, "Couldn't resolve name/id from the request URL") + return + } + return +} diff --git a/cmd/appmgr/api_test.go b/cmd/appmgr/api_test.go new file mode 100755 index 0000000..7b9691c --- /dev/null +++ b/cmd/appmgr/api_test.go @@ -0,0 +1,272 @@ +/* +================================================================================== + 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 ( + "bytes" + "encoding/json" + "errors" + "github.com/gorilla/mux" + "net/http" + "net/http/httptest" + "os" + "reflect" + "strconv" + "testing" + "time" +) + +var x XappManager +var xapp Xapp +var xapps []Xapp +var helmError error + +type MockedHelmer struct { +} + +func (sd *MockedHelmer) Initialize() { +} + +func (h *MockedHelmer) Status(name string) (Xapp, error) { + return xapp, helmError +} + +func (h *MockedHelmer) StatusAll() ([]Xapp, error) { + return xapps, helmError +} + +func (h *MockedHelmer) List() (names []string, err error) { + return names, helmError +} + +func (h *MockedHelmer) Install(name string) (Xapp, error) { + return xapp, helmError +} + +func (h *MockedHelmer) Delete(name string) (Xapp, error) { + return xapp, helmError +} + +// Test cases +func TestMain(m *testing.M) { + loadConfig() + + xapp = Xapp{} + xapps = []Xapp{} + + h := MockedHelmer{} + x = XappManager{} + x.Initialize(&h) + + // Just run on the background (for coverage) + go x.Run() + x.ready = true + + time.Sleep(time.Duration(2 * time.Second)) + + code := m.Run() + os.Exit(code) +} + +func TestGetHealthCheck(t *testing.T) { + req, _ := http.NewRequest("GET", "/ric/v1/health/ready", nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) +} + +func TestGetAppsReturnsEmpty(t *testing.T) { + req, _ := http.NewRequest("GET", "/ric/v1/xapps", nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) + if body := response.Body.String(); body != "[]" { + t.Errorf("handler returned unexpected body: got %v want []", body) + } +} + +func TestCreateXApp(t *testing.T) { + xapp = generateXapp("dummy-xapp", "started", "1.0", "dummy-xapp-1234-5678", "running", "127.0.0.1", "9999") + + payload := []byte(`{"name":"dummy-xapp"}`) + req, _ := http.NewRequest("POST", "/ric/v1/xapps", bytes.NewBuffer(payload)) + response := executeRequest(req) + + checkResponseData(t, response, http.StatusCreated, false) +} + +func TestGetAppsReturnsListOfXapps(t *testing.T) { + xapps = append(xapps, xapp) + req, _ := http.NewRequest("GET", "/ric/v1/xapps", nil) + response := executeRequest(req) + + checkResponseData(t, response, http.StatusOK, true) +} + +func TestGetAppByIdReturnsGivenXapp(t *testing.T) { + req, _ := http.NewRequest("GET", "/ric/v1/xapps/"+xapp.Name, nil) + response := executeRequest(req) + + checkResponseData(t, response, http.StatusOK, false) +} + +func TestGetAppInstanceByIdReturnsGivenXapp(t *testing.T) { + req, _ := http.NewRequest("GET", "/ric/v1/xapps/"+xapp.Name+"/instances/dummy-xapp-1234-5678", nil) + response := executeRequest(req) + + var ins XappInstance + checkResponseCode(t, http.StatusOK, response.Code) + json.NewDecoder(response.Body).Decode(&ins) + + if !reflect.DeepEqual(ins, xapp.Instances[0]) { + t.Errorf("handler returned unexpected body: got: %v, expected: %v", ins, xapp.Instances[0]) + } +} + +func TestDeleteAppRemovesGivenXapp(t *testing.T) { + req, _ := http.NewRequest("DELETE", "/ric/v1/xapps/"+xapp.Name, nil) + response := executeRequest(req) + + checkResponseData(t, response, http.StatusNoContent, false) + + // Xapp not found from the Redis DB + helmError = errors.New("Not found") + + req, _ = http.NewRequest("GET", "/ric/v1/xapps/"+xapp.Name, nil) + response = executeRequest(req) + checkResponseCode(t, http.StatusNotFound, response.Code) +} + +// Error handling +func TestGetXappReturnsError(t *testing.T) { + helmError = errors.New("Not found") + + req, _ := http.NewRequest("GET", "/ric/v1/xapps/invalidXappName", nil) + response := executeRequest(req) + checkResponseCode(t, http.StatusNotFound, response.Code) +} + +func TestGetXappInstanceReturnsError(t *testing.T) { + helmError = errors.New("Some error") + + req, _ := http.NewRequest("GET", "/ric/v1/xapps/"+xapp.Name+"/instances/invalidXappName", nil) + response := executeRequest(req) + checkResponseCode(t, http.StatusNotFound, response.Code) +} + +func TestGetXappListReturnsError(t *testing.T) { + helmError = errors.New("Internal error") + + req, _ := http.NewRequest("GET", "/ric/v1/xapps", nil) + response := executeRequest(req) + checkResponseCode(t, http.StatusInternalServerError, response.Code) +} + +func TestCreateXAppWithoutXappData(t *testing.T) { + req, _ := http.NewRequest("POST", "/ric/v1/xapps", nil) + response := executeRequest(req) + checkResponseData(t, response, http.StatusMethodNotAllowed, false) +} + +func TestCreateXAppWithInvalidXappData(t *testing.T) { + body := []byte("Invalid JSON data ...") + + req, _ := http.NewRequest("POST", "/ric/v1/xapps", bytes.NewBuffer(body)) + response := executeRequest(req) + checkResponseData(t, response, http.StatusMethodNotAllowed, false) +} + +func TestCreateXAppReturnsError(t *testing.T) { + helmError = errors.New("Not found") + + payload := []byte(`{"name":"dummy-xapp"}`) + req, _ := http.NewRequest("POST", "/ric/v1/xapps", bytes.NewBuffer(payload)) + response := executeRequest(req) + + checkResponseData(t, response, http.StatusInternalServerError, false) +} + +func TestDeleteXappListReturnsError(t *testing.T) { + helmError = errors.New("Internal error") + + req, _ := http.NewRequest("DELETE", "/ric/v1/xapps/invalidXappName", nil) + response := executeRequest(req) + checkResponseCode(t, http.StatusInternalServerError, response.Code) +} + +// Helper functions +type fn func(w http.ResponseWriter, r *http.Request) + +func executeRequest(req *http.Request) *httptest.ResponseRecorder { + rr := httptest.NewRecorder() + + vars := map[string]string{ + "id": "1", + } + req = mux.SetURLVars(req, vars) + + x.router.ServeHTTP(rr, req) + + return rr +} + +func checkResponseCode(t *testing.T, expected, actual int) { + if expected != actual { + t.Errorf("Expected response code %d. Got %d\n", expected, actual) + } +} + +func checkResponseData(t *testing.T, response *httptest.ResponseRecorder, expectedHttpStatus int, isList bool) { + expectedData := xapp + + checkResponseCode(t, expectedHttpStatus, response.Code) + if isList == true { + jsonResp := []Xapp{} + json.NewDecoder(response.Body).Decode(&jsonResp) + + if !reflect.DeepEqual(jsonResp[0], expectedData) { + t.Errorf("handler returned unexpected body: %v", jsonResp) + } + } else { + json.NewDecoder(response.Body).Decode(&xapp) + + if !reflect.DeepEqual(xapp, expectedData) { + t.Errorf("handler returned unexpected body: got: %v, expected: %v", xapp, expectedData) + } + } +} + +func generateXapp(name, status, ver, iname, istatus, ip, port string) (x Xapp) { + x.Name = name + x.Status = status + x.Version = ver + p, _ := strconv.Atoi(port) + 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"}, + } + x.Instances = append(x.Instances, instance) + + return +} diff --git a/src/config.go b/cmd/appmgr/config.go similarity index 56% rename from src/config.go rename to cmd/appmgr/config.go index e9ede6b..5e1975e 100755 --- a/src/config.go +++ b/cmd/appmgr/config.go @@ -20,37 +20,37 @@ package main import ( - "flag" - "log" - "github.com/spf13/viper" - "github.com/fsnotify/fsnotify" + "flag" + "github.com/fsnotify/fsnotify" + "github.com/spf13/viper" + "log" ) -const DEFAULT_CONFIG_FILE = "../config/appmgr.yaml" +const DEFAULT_CONFIG_FILE = "config/appmgr.yaml" func parseCmd() string { - var fileName *string - fileName = flag.String("f", DEFAULT_CONFIG_FILE, "Specify the configuration file.") - flag.Parse() + var fileName *string + fileName = flag.String("f", DEFAULT_CONFIG_FILE, "Specify the configuration file.") + flag.Parse() - return *fileName + return *fileName } func loadConfig() { - viper.SetConfigFile(parseCmd()) + viper.SetConfigFile(parseCmd()) - if err := viper.ReadInConfig(); err != nil { - log.Fatalf("Error reading config file, %s", err) - } - log.Printf("Using config file: %s\n", viper.ConfigFileUsed()) + if err := viper.ReadInConfig(); err != nil { + log.Fatalf("Error reading config file, %s", err) + } + log.Printf("Using config file: %s\n", viper.ConfigFileUsed()) - // Watch for config file changes and re-read data ... - watch() + // Watch for config file changes and re-read data ... + watch() } func watch() { - viper.WatchConfig() - viper.OnConfigChange(func(e fsnotify.Event) { - log.Println("config file changed ", e.Name) - }) + viper.WatchConfig() + viper.OnConfigChange(func(e fsnotify.Event) { + log.Println("config file changed ", e.Name) + }) } diff --git a/cmd/appmgr/db.go b/cmd/appmgr/db.go new file mode 100755 index 0000000..6f772c0 --- /dev/null +++ b/cmd/appmgr/db.go @@ -0,0 +1,91 @@ +/* +================================================================================== + 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 ( + "encoding/json" + sdl "gerrit.oran-osc.org/r/ric-plt/sdlgo" + cmap "github.com/orcaman/concurrent-map" + "github.com/spf13/viper" + "time" +) + +type DB struct { + session *sdl.SdlInstance +} + +func (d *DB) Create() { + ns := viper.GetString("db.sessionNamespace") + d.session = sdl.Create(ns) + + // Test DB connection, and wait until ready! + for { + if _, err := d.session.GetAll(); err == nil { + return + } + mdclog(MdclogErr, "Database connection not ready, waiting ...") + time.Sleep(time.Duration(5 * time.Second)) + } +} + +func (d *DB) StoreSubscriptions(m cmap.ConcurrentMap) { + for v := range m.Iter() { + s := v.Val.(Subscription) + + data, err := json.Marshal(s.req) + if err != nil { + mdclog(MdclogErr, "json.marshal failed: "+err.Error()) + return + } + + if err := d.session.Set(s.req.Id, data); err != nil { + mdclog(MdclogErr, "DB.session.Set failed: "+err.Error()) + } + } +} + +func (d *DB) RestoreSubscriptions() (m cmap.ConcurrentMap) { + m = cmap.New() + + keys, err := d.session.GetAll() + if err != nil { + mdclog(MdclogErr, "DB.session.GetAll failed: "+err.Error()) + return + } + + for _, key := range keys { + value, err := d.session.Get([]string{key}) + if err != nil { + mdclog(MdclogErr, "DB.session.Get failed: "+err.Error()) + return + } + + var item SubscriptionReq + if err = json.Unmarshal([]byte(value[key].(string)), &item); err != nil { + mdclog(MdclogErr, "json.Unmarshal failed: "+err.Error()) + return + } + + resp := SubscriptionResp{key, 0, item.EventType} + m.Set(key, Subscription{item, resp}) + } + + return m +} diff --git a/cmd/appmgr/helm.go b/cmd/appmgr/helm.go new file mode 100755 index 0000000..453ff29 --- /dev/null +++ b/cmd/appmgr/helm.go @@ -0,0 +1,389 @@ +/* +================================================================================== + 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 ( + "bytes" + "errors" + "fmt" + "github.com/spf13/viper" + "gopkg.in/yaml.v2" + "io/ioutil" + "log" + "os" + "os/exec" + "path" + "regexp" + "strconv" + "strings" + "time" +) + +var execCommand = exec.Command + +func Exec(args string) (out []byte, err error) { + cmd := execCommand("/bin/sh", "-c", strings.Join([]string{"helm", args}, " ")) + + if !strings.HasSuffix(os.Args[0], ".test") { + out, err = cmd.CombinedOutput() + if err != nil { + mdclog(MdclogErr, formatLog("Command failed", args, err.Error())) + } + return out, err + } + + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + log.Printf("Running command: %v", cmd) + for i := 0; i < 3; i++ { + err = cmd.Run() + if err != nil { + mdclog(MdclogErr, formatLog("Command failed, retrying", args, err.Error()+stderr.String())) + time.Sleep(time.Duration(5) * time.Second) + continue + } + break + } + + if err == nil && !strings.HasSuffix(os.Args[0], ".test") { + mdclog(MdclogDebug, formatLog("command success", stdout.String(), "")) + return stdout.Bytes(), nil + } + + return stdout.Bytes(), errors.New(stderr.String()) +} + +func (h *Helm) Initialize() { + if h.initDone == true { + return + } + + for { + if _, err := h.Init(); err == nil { + mdclog(MdclogDebug, formatLog("Helm init done successfully!", "", "")) + break + } + mdclog(MdclogErr, formatLog("helm init failed, retyring ...", "", "")) + time.Sleep(time.Duration(10) * time.Second) + } + + for { + if _, err := h.AddRepo(); err == nil { + mdclog(MdclogDebug, formatLog("Helm repo added successfully", "", "")) + break + } + mdclog(MdclogErr, formatLog("Helm repo addition failed, retyring ...", "", "")) + time.Sleep(time.Duration(10) * time.Second) + } + h.initDone = true +} + +func (h *Helm) Run(args string) (out []byte, err error) { + return Exec(args) +} + +// API functions +func (h *Helm) Init() (out []byte, err error) { + + // Add Tiller address as environment variable + if err := addTillerEnv(); err != nil { + return out, err + } + + return Exec(strings.Join([]string{"init -c"}, "")) +} + +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 { + mdclog(MdclogErr, formatLog("helm_repo_username ReadFile failed", "", err.Error())) + return + } + + username := " --username " + string(credFile) + + credFile, err = ioutil.ReadFile(viper.GetString("helm.helm-password-file")) + if err != nil { + mdclog(MdclogErr, formatLog("helm_repo_password ReadFile failed", "", err.Error())) + return + } + + pwd := " --password " + string(credFile) + + // Get internal helm repo name + rname := viper.GetString("helm.repo-name") + + // Get helm repo address from values.yaml + repo := viper.GetString("helm.repo") + + return Exec(strings.Join([]string{"repo add ", rname, " ", repo, username, pwd}, "")) +} + +func (h *Helm) Install(name string) (xapp Xapp, err error) { + out, err := h.Run(strings.Join([]string{"repo update "}, "")) + if err != nil { + return + } + + rname := viper.GetString("helm.repo-name") + + ns := getNamespaceArgs() + out, err = h.Run(strings.Join([]string{"install ", rname, "/", name, " --name ", name, ns}, "")) + if err != nil { + return + } + + return h.ParseStatus(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())) + return + } + + return h.ParseStatus(name, string(out)) +} + +func (h *Helm) StatusAll() (xapps []Xapp, err error) { + xappNameList, err := h.List() + if err != nil { + mdclog(MdclogErr, formatLog("Helm list failed", "", err.Error())) + return + } + + return h.parseAllStatus(xappNameList) +} + +func (h *Helm) List() (names []string, err error) { + + ns := getNamespaceArgs() + out, err := h.Run(strings.Join([]string{"list --all --output yaml ", ns}, "")) + if err != nil { + mdclog(MdclogErr, formatLog("Listing deployed xapps failed", "", err.Error())) + return + } + + return h.GetNames(string(out)) +} + +func (h *Helm) Delete(name string) (xapp Xapp, err error) { + xapp, err = h.Status(name) + if err != nil { + mdclog(MdclogErr, formatLog("Fetching xapp status failed", "", err.Error())) + return + } + + _, err = h.Run(strings.Join([]string{"del --purge ", name}, "")) + return xapp, err +} + +func (h *Helm) Fetch(name, tarDir string) error { + if strings.HasSuffix(os.Args[0], ".test") { + return nil + } + + rname := viper.GetString("helm.repo-name") + "/" + + _, err := h.Run(strings.Join([]string{"fetch --untar --untardir ", tarDir, " ", rname, name}, "")) + return err +} + +// Helper functions +func (h *Helm) GetMessages(name string) (msgs MessageTypes, err error) { + tarDir := viper.GetString("xapp.tarDir") + if tarDir == "" { + tarDir = "/tmp" + } + + if h.Fetch(name, tarDir); err != nil { + mdclog(MdclogWarn, formatLog("Fetch chart failed", "", err.Error())) + return + } + + return h.ParseMessages(name, tarDir, viper.GetString("xapp.msg_type_file")) + +} + +func (h *Helm) ParseMessages(name string, chartDir, msgFile string) (msgs MessageTypes, err error) { + yamlFile, err := ioutil.ReadFile(path.Join(chartDir, name, msgFile)) + if err != nil { + mdclog(MdclogWarn, formatLog("ReadFile failed", "", err.Error())) + return + } + + err = yaml.Unmarshal(yamlFile, &msgs) + if err != nil { + mdclog(MdclogWarn, formatLog("Unmarshal failed", "", err.Error())) + return + } + + if err = os.RemoveAll(path.Join(chartDir, name)); err != nil { + mdclog(MdclogWarn, formatLog("RemoveAll failed", "", err.Error())) + } + + return +} + +func (h *Helm) GetVersion(name string) (version string) { + + ns := getNamespaceArgs() + out, err := h.Run(strings.Join([]string{"list --output yaml ", name, ns}, "")) + if err != nil { + return + } + + var re = regexp.MustCompile(`AppVersion: .*`) + ver := re.FindStringSubmatch(string(out)) + if ver != nil { + version = strings.Split(ver[0], ": ")[1] + version, _ = strconv.Unquote(version) + } + + return +} + +func (h *Helm) GetState(out string) (status string) { + re := regexp.MustCompile(`STATUS: .*`) + result := re.FindStringSubmatch(string(out)) + if result != nil { + status = strings.ToLower(strings.Split(result[0], ": ")[1]) + } + + return +} + +func (h *Helm) GetAddress(out string) (ip, port string) { + var tmp string + re := regexp.MustCompile(`ClusterIP.*`) + addr := re.FindStringSubmatch(string(out)) + if addr != nil { + fmt.Sscanf(addr[0], "%s %s %s %s", &tmp, &ip, &tmp, &port) + } + + return +} + +func (h *Helm) GetNames(out string) (names []string, err error) { + re := regexp.MustCompile(`Name: .*`) + result := re.FindAllStringSubmatch(out, -1) + if result == nil { + return + } + + for _, v := range result { + xappName := strings.Split(v[0], ": ")[1] + if strings.Contains(xappName, "appmgr") == false { + names = append(names, xappName) + } + } + return names, nil +} + +func (h *Helm) FillInstanceData(name string, out string, xapp *Xapp, msgs MessageTypes) { + ip, port := h.GetAddress(out) + + var tmp string + r := regexp.MustCompile(`(?s)\/Pod.*?\/Service`) + result := r.FindStringSubmatch(string(out)) + if result == nil { + return + } + + re := regexp.MustCompile(name + "-(\\d+).*") + resources := re.FindAllStringSubmatch(string(result[0]), -1) + if resources != nil { + for _, v := range resources { + var x XappInstance + fmt.Sscanf(v[0], "%s %s %s", &x.Name, &tmp, &x.Status) + x.Status = strings.ToLower(x.Status) + x.Ip = ip + x.Port, _ = strconv.Atoi(strings.Split(port, "/")[0]) + x.TxMessages = msgs.TxMessages + x.RxMessages = msgs.RxMessages + xapp.Instances = append(xapp.Instances, x) + } + } +} + +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 := h.GetMessages(name) + if err != nil { + // xAPP can still be deployed if the msg_type file is missing. + mdclog(MdclogWarn, formatLog("method 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 +} + +func (h *Helm) parseAllStatus(names []string) (xapps []Xapp, err error) { + xapps = []Xapp{} + + for _, name := range names { + x, err := h.Status(name) + if err == nil { + xapps = append(xapps, x) + } + } + + return +} + +func addTillerEnv() (err error) { + + service := viper.GetString("helm.tiller-service") + namespace := viper.GetString("helm.tiller-namespace") + port := viper.GetString("helm.tiller-port") + + if err = os.Setenv("HELM_HOST", service+"."+namespace+":"+port); err != nil { + mdclog(MdclogErr, formatLog("Tiller Env Setting Failed", "", err.Error())) + } + + return err +} + +func getNamespaceArgs() string { + ns := viper.GetString("xapp.namespace") + if ns == "" { + ns = "ricxapp" + } + return " --namespace=" + ns +} + +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/src/logger.go b/cmd/appmgr/logger.go similarity index 69% rename from src/logger.go rename to cmd/appmgr/logger.go index bc255ee..591fa47 100755 --- a/src/logger.go +++ b/cmd/appmgr/logger.go @@ -18,6 +18,7 @@ */ package main + /* #cgo CFLAGS: -I/usr/local/include #cgo LDFLAGS: -lmdclog @@ -30,22 +31,26 @@ void xAppMgr_mdclog_write(mdclog_severity_t severity, const char *msg) { import "C" import ( - "net/http" - "time" - "fmt" + "fmt" + "net/http" + "time" ) func mdclog(severity C.mdclog_severity_t, msg string) { - msg = fmt.Sprintf("%s:: %s ", time.Now().Format("2019-01-02 15:04:05"), msg) + msg = fmt.Sprintf("%s:: %s ", time.Now().Format("2019-01-02 15:04:05"), msg) + + C.mdclog_mdc_add(C.CString("XM"), C.CString("1.0.0")) + C.xAppMgr_mdclog_write(severity, C.CString(msg)) +} - C.mdclog_mdc_add(C.CString("XM"), C.CString("1.0.0")) - C.xAppMgr_mdclog_write(severity, C.CString(msg)) +func mdclogSetLevel(severity C.mdclog_severity_t) { + C.mdclog_level_set(severity) } func Logger(inner http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - inner.ServeHTTP(w, r) - s := fmt.Sprintf("Logger: method=%s url=%s", r.Method, r.URL.RequestURI()) - mdclog(C.MDCLOG_DEBUG, s) - }) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + inner.ServeHTTP(w, r) + s := fmt.Sprintf("Logger: method=%s url=%s", r.Method, r.URL.RequestURI()) + mdclog(C.MDCLOG_DEBUG, s) + }) } diff --git a/src/main.go b/cmd/appmgr/main.go similarity index 89% rename from src/main.go rename to cmd/appmgr/main.go index e948cac..da50681 100755 --- a/src/main.go +++ b/cmd/appmgr/main.go @@ -20,10 +20,12 @@ package main func main() { - loadConfig() + mdclogSetLevel(MdclogDebug) - m := XappManager{} - m.Initialize(&Helm{}) + loadConfig() - m.Run() + m := XappManager{} + m.Initialize(&Helm{}) + + m.Run() } diff --git a/cmd/appmgr/subscriptions.go b/cmd/appmgr/subscriptions.go new file mode 100755 index 0000000..5728e5b --- /dev/null +++ b/cmd/appmgr/subscriptions.go @@ -0,0 +1,149 @@ +/* +================================================================================== + 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 ( + "bytes" + "encoding/json" + "fmt" + "github.com/segmentio/ksuid" + "net/http" + "time" +) + +func (sd *SubscriptionDispatcher) Initialize() { + sd.client = &http.Client{} + + sd.db = &DB{} + sd.db.Create() + sd.subscriptions = sd.db.RestoreSubscriptions() +} + +func (sd *SubscriptionDispatcher) Add(sr SubscriptionReq) (resp SubscriptionResp) { + key := ksuid.New().String() + resp = SubscriptionResp{key, 0, sr.EventType} + sr.Id = key + + sd.subscriptions.Set(key, Subscription{sr, resp}) + sd.db.StoreSubscriptions(sd.subscriptions) + + mdclog(MdclogInfo, fmt.Sprintf("Sub: New subscription added: key=%s value=%v", key, sr)) + return +} + +func (sd *SubscriptionDispatcher) GetAll() (hooks []SubscriptionReq) { + hooks = []SubscriptionReq{} + for v := range sd.subscriptions.IterBuffered() { + hooks = append(hooks, v.Val.(Subscription).req) + } + + return hooks +} + +func (sd *SubscriptionDispatcher) Get(id string) (SubscriptionReq, bool) { + if v, found := sd.subscriptions.Get(id); found { + mdclog(MdclogInfo, fmt.Sprintf("Subscription id=%s found: %v", id, v.(Subscription).req)) + + return v.(Subscription).req, found + } + return SubscriptionReq{}, false +} + +func (sd *SubscriptionDispatcher) Delete(id string) (SubscriptionReq, bool) { + if v, found := sd.subscriptions.Get(id); found { + mdclog(MdclogInfo, fmt.Sprintf("Subscription id=%s found: %v ... deleting", id, v.(Subscription).req)) + + sd.subscriptions.Remove(id) + sd.db.StoreSubscriptions(sd.subscriptions) + + return v.(Subscription).req, found + } + return SubscriptionReq{}, false +} + +func (sd *SubscriptionDispatcher) Update(id string, sr SubscriptionReq) (SubscriptionReq, bool) { + if s, found := sd.subscriptions.Get(id); found { + mdclog(MdclogInfo, fmt.Sprintf("Subscription id=%s found: %v ... updating", id, s.(Subscription).req)) + + sr.Id = id + sd.subscriptions.Set(id, Subscription{sr, s.(Subscription).resp}) + sd.db.StoreSubscriptions(sd.subscriptions) + + return sr, found + } + return SubscriptionReq{}, false +} + +func (sd *SubscriptionDispatcher) Publish(x Xapp, et EventType) { + sd.notifyClients([]Xapp{x}, et) +} + +func (sd *SubscriptionDispatcher) notifyClients(xapps []Xapp, et EventType) { + if len(xapps) == 0 || len(sd.subscriptions) == 0 { + mdclog(MdclogInfo, fmt.Sprintf("Nothing to publish [%d:%d]", len(xapps), len(sd.subscriptions))) + return + } + + sd.Seq = sd.Seq + 1 + for v := range sd.subscriptions.Iter() { + go sd.notify(xapps, et, v.Val.(Subscription), sd.Seq) + } +} + +func (sd *SubscriptionDispatcher) notify(xapps []Xapp, et EventType, s Subscription, seq int) error { + notif := []SubscriptionNotif{} + notif = append(notif, SubscriptionNotif{Id: s.req.Id, Version: seq, EventType: string(et), XappData: xapps}) + + jsonData, err := json.Marshal(notif) + if err != nil { + mdclog(MdclogInfo, fmt.Sprintf("json.Marshal failed: %v", err)) + return err + } + + // Execute the request with retry policy + return sd.retry(s, func() error { + resp, err := http.Post(s.req.TargetUrl, "application/json", bytes.NewBuffer(jsonData)) + if err != nil { + mdclog(MdclogInfo, fmt.Sprintf("Posting to subscription failed: %v", err)) + return err + } + + if resp.StatusCode != http.StatusOK { + mdclog(MdclogInfo, fmt.Sprintf("Client returned error code: %d", resp.StatusCode)) + return err + } + + mdclog(MdclogInfo, fmt.Sprintf("subscription to '%s' dispatched, response code: %d \n", s.req.TargetUrl, resp.StatusCode)) + return nil + }) +} + +func (sd *SubscriptionDispatcher) retry(s Subscription, fn func() error) error { + if err := fn(); err != nil { + // Todo: use exponential backoff, or similar mechanism + if s.req.MaxRetries--; s.req.MaxRetries > 0 { + time.Sleep(time.Duration(s.req.RetryTimer) * time.Second) + return sd.retry(s, fn) + } + sd.subscriptions.Remove(s.req.Id) + return err + } + return nil +} diff --git a/cmd/appmgr/subscriptions_test.go b/cmd/appmgr/subscriptions_test.go new file mode 100755 index 0000000..8861af0 --- /dev/null +++ b/cmd/appmgr/subscriptions_test.go @@ -0,0 +1,198 @@ +/* +================================================================================== + 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 ( + "bytes" + "encoding/json" + "fmt" + sdl "gerrit.oran-osc.org/r/ric-plt/sdlgo" + "github.com/spf13/viper" + "log" + "net" + "net/http" + "net/http/httptest" + "testing" +) + +var resp SubscriptionResp + +// Test cases +func TestNoSubscriptionsFound(t *testing.T) { + req, _ := http.NewRequest("GET", "/ric/v1/subscriptions", nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) + if body := response.Body.String(); body != "[]" { + t.Errorf("handler returned unexpected body: got %v want []", body) + } +} + +func TestAddNewSubscription(t *testing.T) { + payload := []byte(`{"maxRetries": 3, "retryTimer": 5, "eventType":"Created", "targetUrl": "http://localhost:8087/xapps_handler"}`) + req, _ := http.NewRequest("POST", "/ric/v1/subscriptions", bytes.NewBuffer(payload)) + response := executeRequest(req) + + checkResponseCode(t, http.StatusCreated, response.Code) + + json.NewDecoder(response.Body).Decode(&resp) + if resp.Version != 0 { + t.Errorf("Creating new subscription failed: %v", resp) + } +} + +func TestGettAllSubscriptions(t *testing.T) { + req, _ := http.NewRequest("GET", "/ric/v1/subscriptions", nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) + + var subscriptions []SubscriptionReq + json.NewDecoder(response.Body).Decode(&subscriptions) + + verifySubscription(t, subscriptions[0], "http://localhost:8087/xapps_handler", 3, 5, "Created") +} + +func TestGetSingleSubscription(t *testing.T) { + req, _ := http.NewRequest("GET", "/ric/v1/subscriptions/"+resp.Id, nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) + + var subscription SubscriptionReq + json.NewDecoder(response.Body).Decode(&subscription) + + verifySubscription(t, subscription, "http://localhost:8087/xapps_handler", 3, 5, "Created") +} + +func TestUpdateSingleSubscription(t *testing.T) { + payload := []byte(`{"maxRetries": 11, "retryTimer": 22, "eventType":"Deleted", "targetUrl": "http://localhost:8088/xapps_handler"}`) + + req, _ := http.NewRequest("PUT", "/ric/v1/subscriptions/"+resp.Id, bytes.NewBuffer(payload)) + response := executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) + + var res SubscriptionResp + json.NewDecoder(response.Body).Decode(&res) + if res.Version != 0 { + t.Errorf("handler returned unexpected data: %v", resp) + } + + // Check that the subscription is updated properly + req, _ = http.NewRequest("GET", "/ric/v1/subscriptions/"+resp.Id, nil) + response = executeRequest(req) + checkResponseCode(t, http.StatusOK, response.Code) + + var subscription SubscriptionReq + json.NewDecoder(response.Body).Decode(&subscription) + + verifySubscription(t, subscription, "http://localhost:8088/xapps_handler", 11, 22, "Deleted") +} + +func TestDeleteSingleSubscription(t *testing.T) { + req, _ := http.NewRequest("DELETE", "/ric/v1/subscriptions/"+resp.Id, nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusNoContent, response.Code) + + // Check that the subscription is removed properly + req, _ = http.NewRequest("GET", "/ric/v1/subscriptions/"+resp.Id, nil) + response = executeRequest(req) + checkResponseCode(t, http.StatusNotFound, response.Code) +} + +func TestDeleteSingleSubscriptionFails(t *testing.T) { + req, _ := http.NewRequest("DELETE", "/ric/v1/subscriptions/invalidSubscriptionId", nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusNotFound, response.Code) +} + +func TestAddSingleSubscriptionFailsBodyEmpty(t *testing.T) { + req, _ := http.NewRequest("POST", "/ric/v1/subscriptions/"+resp.Id, nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusMethodNotAllowed, response.Code) +} + +func TestUpdateeSingleSubscriptionFailsBodyEmpty(t *testing.T) { + req, _ := http.NewRequest("PUT", "/ric/v1/subscriptions/"+resp.Id, nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusMethodNotAllowed, response.Code) +} + +func TestUpdateeSingleSubscriptionFailsInvalidId(t *testing.T) { + payload := []byte(`{"maxRetries": 11, "retryTimer": 22, "eventType":"Deleted", "targetUrl": "http://localhost:8088/xapps_handler"}`) + + req, _ := http.NewRequest("PUT", "/ric/v1/subscriptions/invalidSubscriptionId"+resp.Id, bytes.NewBuffer(payload)) + response := executeRequest(req) + + checkResponseCode(t, http.StatusNotFound, response.Code) +} + +func TestPublishXappAction(t *testing.T) { + payload := []byte(`{"maxRetries": 3, "retryTimer": 5, "eventType":"Created", "targetUrl": "http://127.0.0.1:8888"}`) + req, _ := http.NewRequest("POST", "/ric/v1/subscriptions", bytes.NewBuffer(payload)) + response := executeRequest(req) + + checkResponseCode(t, http.StatusCreated, response.Code) + + // Create a RestApi server (simulating RM) + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hello, XM!") + })) + + l, err := net.Listen("tcp", "127.0.0.1:8888") + if err != nil { + log.Fatal(err) + } + ts.Listener.Close() + ts.Listener = l + ts.Start() + + defer ts.Close() + + x.sd.Publish(xapp, EventType("created")) +} + +func TestTeardown(t *testing.T) { + db := sdl.Create(viper.GetString("db.sessionNamespace")) + db.RemoveAll() +} + +func verifySubscription(t *testing.T, subscription SubscriptionReq, url string, retries int, timer int, event string) { + if subscription.TargetUrl != url { + t.Errorf("Unexpected url: got=%s expected=%s", subscription.TargetUrl, url) + } + + if subscription.MaxRetries != retries { + t.Errorf("Unexpected retries: got=%d expected=%d", subscription.MaxRetries, retries) + } + + if subscription.RetryTimer != timer { + t.Errorf("Unexpected timer: got=%d expected=%d", subscription.RetryTimer, timer) + } + + if subscription.EventType != event { + t.Errorf("Unexpected event type: got=%s expected=%s", subscription.EventType, event) + } +} diff --git a/cmd/appmgr/types.go b/cmd/appmgr/types.go new file mode 100755 index 0000000..a3599fe --- /dev/null +++ b/cmd/appmgr/types.go @@ -0,0 +1,130 @@ +/* +================================================================================== + 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 ( + "github.com/gorilla/mux" + cmap "github.com/orcaman/concurrent-map" + "net/http" +) + +type CmdOptions struct { + hostAddr *string + helmHost *string + helmChartPath *string +} + +type Resource struct { + Method string + Url string + HandlerFunc http.HandlerFunc +} + +type Xapp struct { + Name string `json:"name"` + Status string `json:"status"` + Version string `json:"version"` + Instances []XappInstance `json:"instances"` +} + +type XappInstance struct { + Name string `json:"name"` + Status string `json:"status"` + Ip string `json:"ip"` + Port int `json:"port"` + TxMessages []string `json:"txMessages"` + RxMessages []string `json:"rxMessages"` +} + +type XappManager struct { + router *mux.Router + helm Helmer + sd SubscriptionDispatcher + opts CmdOptions + ready bool +} + +type Helmer interface { + Initialize() + Install(name string) (xapp Xapp, err error) + Status(name string) (xapp Xapp, err error) + StatusAll() (xapps []Xapp, err error) + List() (xapps []string, err error) + Delete(name string) (xapp Xapp, err error) +} + +type Helm struct { + host string + chartPath string + initDone bool +} + +type SubscriptionReq struct { + Id string `json:"id"` + TargetUrl string `json:"targetUrl"` + EventType string `json:"eventType"` + MaxRetries int `json:"maxRetries"` + RetryTimer int `json:"retryTimer"` +} + +type SubscriptionResp struct { + Id string `json:"id"` + Version int `json:"version"` + EventType string `json:"eventType"` +} + +type SubscriptionNotif struct { + Id string `json:"id"` + Version int `json:"version"` + EventType string `json:"eventType"` + XappData []Xapp `json:"xapp"` +} + +type Subscription struct { + req SubscriptionReq + resp SubscriptionResp +} + +type SubscriptionDispatcher struct { + client *http.Client + subscriptions cmap.ConcurrentMap + db *DB + Seq int +} + +type MessageTypes struct { + TxMessages []string `yaml:"txMessages"` + RxMessages []string `yaml:"rxMessages"` +} + +type EventType string + +const ( + Created EventType = "created" + Updated EventType = "updated" + Deleted EventType = "deleted" +) + +const ( + MdclogErr = 1 //! Error level log entry + MdclogWarn = 2 //! Warning level log entry + MdclogInfo = 3 //! Info level log entry + MdclogDebug = 4 //! Debug level log entry +) diff --git a/config/appmgr.yaml b/config/appmgr.yaml index 3753f1f..6be3be7 100755 --- a/config/appmgr.yaml +++ b/config/appmgr.yaml @@ -18,6 +18,7 @@ "helm": "host": "192.168.0.12:31807" "repo": "/opt/ric/dummy-xapp-chart" + "repo-name": "dummy" "secrets": "username": "admin" "password": "ric" @@ -26,8 +27,10 @@ "xapp": "namespace": "ricxapp" "tarDir": "/tmp" + "msg_type_file": "msg_type.yaml" "db": + "sessionNamespace": "XMSession" "host": ":6379" "prot": "tcp" "maxIdle": 80 - "maxActive": 12000 \ No newline at end of file + "maxActive": 12000 diff --git a/build/Dockerfile b/docker/Dockerfile similarity index 78% rename from build/Dockerfile rename to docker/Dockerfile index 298fb67..3277037 100755 --- a/build/Dockerfile +++ b/docker/Dockerfile @@ -21,10 +21,6 @@ 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,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list -#RUN sed -i -e 's,http://security.ubuntu.com/ubuntu,mirror://mirrors.ubuntu.com/mirrors.txt,' /etc/apt/sources.list -#RUN sed -i -e 's,http://archive.ubuntu.com/ubuntu,http://mirrors.nic.funet.fi/ubuntu,' /etc/apt/sources.list -#RUN sed -i -e 's,http://security.ubuntu.com/ubuntu,http://mirrors.nic.funet.fi/ubuntu,' /etc/apt/sources.list 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 @@ -68,13 +64,13 @@ RUN mkdir -p /opt/build \ && cd log/ ; ./autogen.sh ; ./configure ; make ; make install \ && ldconfig - #---------------------------------------------------------- # #---------------------------------------------------------- FROM ubuntubase as builder -ARG HELMVERSION +ARG PACKAGEURL=gerrit.oran-osc.org/r/ric-plt/appmgr +ARG HELMVERSION=v2.13.0-rc.1 # # helm @@ -87,9 +83,9 @@ RUN wget https://storage.googleapis.com/kubernetes-helm/helm-${HELMVERSION}-linu # -# xapp_manager codes +# appmgr codes # -RUN mkdir -p /go/src/appmgr +RUN mkdir -p /go/src/${PACKAGEURL} ENV GOPATH="/go" # @@ -103,36 +99,46 @@ RUN go get github.com/gorilla/mux \ && go get gopkg.in/yaml.v2 -COPY . /go/src/appmgr +COPY . /go/src/${PACKAGEURL} # # build # -RUN make -C /go/src/appmgr/build deps - -RUN make -C /go/src/appmgr/build build +RUN make -C /go/src/${PACKAGEURL} build #---------------------------------------------------------- # #---------------------------------------------------------- FROM builder as test_unit -WORKDIR "/go/src/appmgr" -CMD ["make","-C","build", "unit-test"] +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 -WORKDIR "/go/src/appmgr" -CMD ["jq","-s",".", "rest_api/xapp_manager_rest_api.json"] +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 \ @@ -154,11 +160,11 @@ RUN ldconfig RUN mkdir -p /opt/xAppManager \ && chmod -R 755 /opt/xAppManager -COPY --from=builder /go/src/appmgr/build/appmgr /opt/xAppManager/appmgr -#COPY --from=builder /go/src/appmgr/config/appmgr.yaml /opt/etc/xAppManager/config-file.yaml +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 build/docker-entrypoint.sh /opt/xAppManager/ +COPY docker/docker-entrypoint.sh /opt/xAppManager/ WORKDIR /opt/xAppManager diff --git a/build/docker-entrypoint.sh b/docker/docker-entrypoint.sh similarity index 100% rename from build/docker-entrypoint.sh rename to docker/docker-entrypoint.sh diff --git a/go.mod b/go.mod new file mode 100755 index 0000000..43c6a6f --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module gerrit.oran-osc.org/r/ric-plt/appmgr + +go 1.12 + +require ( + gerrit.oran-osc.org/r/ric-plt/sdlgo v0.0.0 + github.com/fsnotify/fsnotify v1.4.7 + github.com/gorilla/mux v1.7.1 + github.com/mitchellh/mapstructure v1.1.2 + github.com/orcaman/concurrent-map v0.0.0-20190314100340-2693aad1ed75 + github.com/segmentio/ksuid v1.0.2 + github.com/spf13/viper v1.3.2 + gopkg.in/yaml.v2 v2.2.2 +) + +replace gerrit.oran-osc.org/r/ric-plt/sdlgo => ./internal/sdlgo diff --git a/go.sum b/go.sum new file mode 100755 index 0000000..1661c23 --- /dev/null +++ b/go.sum @@ -0,0 +1,63 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gorilla/mux v1.7.1 h1:Dw4jY2nghMMRsh1ol8dv1axHkDwMQK2DHerMNJsIpJU= +github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/orcaman/concurrent-map v0.0.0-20190314100340-2693aad1ed75 h1:IV56VwUb9Ludyr7s53CMuEh4DdTnnQtEPLEgLyJ0kHI= +github.com/orcaman/concurrent-map v0.0.0-20190314100340-2693aad1ed75/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +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/segmentio/ksuid v1.0.2 h1:9yBfKyw4ECGTdALaF09Snw3sLJmYIX6AbPJrAy6MrDc= +github.com/segmentio/ksuid v1.0.2/go.mod h1:BXuJDr2byAiHuQaQtSKoXh1J0YmUDurywOXgB2w+OSU= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/helm_chart/appmgr/templates/deployment.yaml b/helm_chart/appmgr/templates/deployment.yaml index 0ef46d2..0a7eba4 100755 --- a/helm_chart/appmgr/templates/deployment.yaml +++ b/helm_chart/appmgr/templates/deployment.yaml @@ -54,16 +54,18 @@ spec: name: {{ .Release.Name }}-appenv livenessProbe: httpGet: - path: {{ .Values.service.health_check_endpoint }} + path: {{ .Values.service.health_alive_check_endpoint }} port: 8080 initialDelaySeconds: 5 - periodSeconds: 15 + periodSeconds: 5 + failureThreshold: 3 readinessProbe: httpGet: - path: {{ .Values.service.health_check_endpoint }} + path: {{ .Values.service.health_ready_check_endpoint }} port: 8080 initialDelaySeconds: 5 - periodSeconds: 15 + periodSeconds: 5 + failureThreshold: 3 restartPolicy: Always resources: {{- toYaml .Values.resources | nindent 12 }} diff --git a/helm_chart/appmgr/values.yaml b/helm_chart/appmgr/values.yaml index 8d71678..d27d020 100755 --- a/helm_chart/appmgr/values.yaml +++ b/helm_chart/appmgr/values.yaml @@ -24,7 +24,7 @@ image: #repositoryCred: # user: docker # password: docker - pullPolicy: IfNotPresent + pullPolicy: Always # This section describes xAppManager replicaCount: 1 @@ -43,7 +43,8 @@ service: port: 8080 nodePort: 30218 name: appmgr-service - health_check_endpoint: ric/v1/health + health_alive_check_endpoint: ric/v1/health/alive + health_ready_check_endpoint: ric/v1/health/ready # config # Path referred in appmgr for retrieving configuration details @@ -72,7 +73,10 @@ appconfig: "helm-password-file": "/opt/ric/secret/helm_repo_password" "xapp": #Namespace to install xAPPs - "namespace": ricxapp + "namespace": "ricxapp" + + #File containing xAPP message types + "msg_type_file": "msg_type.yaml" # To be provided as env variables appenv: 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/cli/appmgrcli b/scripts/appmgrcli similarity index 98% rename from cli/appmgrcli rename to scripts/appmgrcli index e31048a..ac00485 100755 --- a/cli/appmgrcli +++ b/scripts/appmgrcli @@ -207,7 +207,7 @@ do_undeploy() { if [ "x$1" != "x" ]; then urlpath="$urlpath/$1" if rest DELETE $urlpath; then - # Currently xapp_manager returns an empty result if + # Currently appmgr returns an empty result if # undeploy is succesfull. Don't reformat file if empty. if [ -s $resultfile ]; then json_reformat < $resultfile @@ -297,7 +297,7 @@ do_subscriptions() { fi if [ $status = 0 ]; then if rest DELETE $urlpath; then - # Currently xapp_manager returns an empty result if + # Currently appmgr returns an empty result if # delete is succesfull. Don't reformat file if empty. if [ -s $resultfile ]; then json_reformat < $resultfile diff --git a/src/api.go b/src/api.go deleted file mode 100755 index b9c2b63..0000000 --- a/src/api.go +++ /dev/null @@ -1,251 +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 ( - "encoding/json" - "github.com/gorilla/mux" - "github.com/spf13/viper" - "log" - "net/http" -) - -// API functions - -func (m *XappManager) Initialize(h Helmer) *mux.Router { - m.sd = SubscriptionDispatcher{} - m.sd.Initialize() - m.helm = h - m.router = mux.NewRouter().StrictSlash(true) - - resources := []Resource{ - Resource{"GET", "/ric/v1/health", m.getHealthStatus}, - - Resource{"GET", "/ric/v1/xapps", m.getAllXapps}, - Resource{"GET", "/ric/v1/xapps/{name}", m.getXappByName}, - Resource{"GET", "/ric/v1/xapps/{name}/instances/{id}", m.getXappInstanceByName}, - Resource{"POST", "/ric/v1/xapps", m.deployXapp}, - Resource{"DELETE", "/ric/v1/xapps/{name}", m.undeployXapp}, - - Resource{"GET", "/ric/v1/subscriptions", m.getSubscriptions}, - Resource{"POST", "/ric/v1/subscriptions", m.addSubscription}, - Resource{"GET", "/ric/v1/subscriptions/{id}", m.getSubscription}, - Resource{"DELETE", "/ric/v1/subscriptions/{id}", m.deleteSubscription}, - Resource{"PUT", "/ric/v1/subscriptions/{id}", m.updateSubscription}, - } - - for _, resource := range resources { - handler := Logger(resource.HandlerFunc) - m.router.Methods(resource.Method).Path(resource.Url).Handler(handler) - } - - return m.router -} - -// Health monitoring -func (m *XappManager) getHealthStatus(w http.ResponseWriter, r *http.Request) { - respondWithJSON(w, http.StatusOK, nil) -} - -// API: XAPP handlers -func (m *XappManager) Run() { - host := viper.GetString("local.host") - if host == "" { - host = ":8080" - } - log.Printf("Xapp manager started ... serving on %s\n", host) - - log.Fatal(http.ListenAndServe(host, m.router)) -} - -func (m *XappManager) getXappByName(w http.ResponseWriter, r *http.Request) { - xappName, ok := getResourceId(r, w, "name") - if ok != true { - return - } - - if xapp, err := m.helm.Status(xappName); err == nil { - respondWithJSON(w, http.StatusOK, xapp) - } else { - respondWithError(w, http.StatusNotFound, err.Error()) - } -} - -func (m *XappManager) getXappInstanceByName(w http.ResponseWriter, r *http.Request) { - xappName, ok := getResourceId(r, w, "name") - if ok != true { - return - } - - xapp, err := m.helm.Status(xappName) - if err != nil { - respondWithError(w, http.StatusNotFound, err.Error()) - return - } - - xappInstanceName, ok := getResourceId(r, w, "id") - if ok != true { - return - } - - for _, v := range xapp.Instances { - if v.Name == xappInstanceName { - respondWithJSON(w, http.StatusOK, v) - return - } - } - mdclog(Mdclog_err, "Xapp instance not found - url=" + r.URL.RequestURI()) - - respondWithError(w, http.StatusNotFound, "Xapp instance not found") -} - -func (m *XappManager) getAllXapps(w http.ResponseWriter, r *http.Request) { - xapps, err := m.helm.StatusAll() - if err != nil { - respondWithError(w, http.StatusInternalServerError, err.Error()) - return - } - - respondWithJSON(w, http.StatusOK, xapps) -} - -func (m *XappManager) deployXapp(w http.ResponseWriter, r *http.Request) { - if r.Body == nil { - mdclog(Mdclog_err, "No xapp data found in request body - url=" + r.URL.RequestURI()) - respondWithError(w, http.StatusMethodNotAllowed, "No xapp data!") - return - } - - var xapp Xapp - if err := json.NewDecoder(r.Body).Decode(&xapp); err != nil { - mdclog(Mdclog_err, "Invalid xapp data in request body - url=" + r.URL.RequestURI()) - respondWithError(w, http.StatusMethodNotAllowed, "Invalid xapp data!") - return - } - defer r.Body.Close() - - xapp, err := m.helm.Install(xapp.Name) - if err != nil { - respondWithError(w, http.StatusInternalServerError, err.Error()) - return - } - - respondWithJSON(w, http.StatusCreated, xapp) - - m.sd.Publish(xapp, EventType("created")) -} - -func (m *XappManager) undeployXapp(w http.ResponseWriter, r *http.Request) { - xappName, ok := getResourceId(r, w, "name") - if ok != true { - return - } - - xapp, err := m.helm.Delete(xappName) - if err != nil { - respondWithError(w, http.StatusInternalServerError, err.Error()) - return - } - - respondWithJSON(w, http.StatusNoContent, nil) - - m.sd.Publish(xapp, EventType("deleted")) -} - -// API: resthook handlers -func (m *XappManager) getSubscriptions(w http.ResponseWriter, r *http.Request) { - respondWithJSON(w, http.StatusOK, m.sd.GetAll()) -} - -func (m *XappManager) getSubscription(w http.ResponseWriter, r *http.Request) { - if id, ok := getResourceId(r, w, "id"); ok == true { - if s, ok := m.sd.Get(id); ok { - respondWithJSON(w, http.StatusOK, s) - } else { - mdclog(Mdclog_err, "Subscription not found - url=" + r.URL.RequestURI()) - respondWithError(w, http.StatusNotFound, "Subscription not found") - } - } -} - -func (m *XappManager) deleteSubscription(w http.ResponseWriter, r *http.Request) { - if id, ok := getResourceId(r, w, "id"); ok == true { - if _, ok := m.sd.Delete(id); ok { - respondWithJSON(w, http.StatusNoContent, nil) - } else { - mdclog(Mdclog_err, "Subscription not found - url=" + r.URL.RequestURI()) - respondWithError(w, http.StatusNotFound, "Subscription not found") - } - } -} - -func (m *XappManager) addSubscription(w http.ResponseWriter, r *http.Request) { - var req SubscriptionReq - if r.Body == nil || json.NewDecoder(r.Body).Decode(&req) != nil { - mdclog(Mdclog_err, "Invalid request payload - url=" + r.URL.RequestURI()) - respondWithError(w, http.StatusMethodNotAllowed, "Invalid request payload") - return - } - defer r.Body.Close() - - respondWithJSON(w, http.StatusCreated, m.sd.Add(req)) -} - -func (m *XappManager) updateSubscription(w http.ResponseWriter, r *http.Request) { - if id, ok := getResourceId(r, w, "id"); ok == true { - var req SubscriptionReq - if r.Body == nil || json.NewDecoder(r.Body).Decode(&req) != nil { - mdclog(Mdclog_err, "Invalid request payload - url=" + r.URL.RequestURI()) - respondWithError(w, http.StatusMethodNotAllowed, "Invalid request payload") - return - } - defer r.Body.Close() - - if s, ok := m.sd.Update(id, req); ok { - respondWithJSON(w, http.StatusOK, s) - } else { - mdclog(Mdclog_err, "Subscription not found - url=" + r.URL.RequestURI()) - respondWithError(w, http.StatusNotFound, "Subscription not found") - } - } -} - -// Helper functions -func respondWithError(w http.ResponseWriter, code int, message string) { - respondWithJSON(w, code, map[string]string{"error": message}) -} - -func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - if payload != nil { - response, _ := json.Marshal(payload) - w.Write(response) - } -} - -func getResourceId(r *http.Request, w http.ResponseWriter, pattern string) (id string, ok bool) { - if id, ok = mux.Vars(r)[pattern]; ok != true { - mdclog(Mdclog_err, "Couldn't resolve name/id from the request URL") - respondWithError(w, http.StatusMethodNotAllowed, "Couldn't resolve name/id from the request URL") - return - } - return -} diff --git a/src/api_test.go b/src/api_test.go deleted file mode 100755 index 7610f49..0000000 --- a/src/api_test.go +++ /dev/null @@ -1,265 +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 ( - "net/http" - "net/http/httptest" - "testing" - "reflect" - "strconv" - "os" - "bytes" - "errors" - "encoding/json" - "github.com/gorilla/mux" -) - -var x XappManager -var xapp Xapp -var xapps []Xapp -var helmError error - -type MockedHelmer struct { -} - -func (h *MockedHelmer) Status(name string) (Xapp, error) { - return xapp, helmError -} - -func (h *MockedHelmer) StatusAll() ([]Xapp, error) { - return xapps, helmError -} - -func (h *MockedHelmer) List() (names []string, err error) { - return names, helmError -} - -func (h *MockedHelmer) Install(name string) (Xapp, error) { - return xapp, helmError -} - -func (h *MockedHelmer) Delete(name string) (Xapp, error) { - return xapp, helmError -} - -// Test cases -func TestMain(m *testing.M) { - loadConfig(); - - xapp = Xapp{} - xapps = []Xapp{} - - h := MockedHelmer{} - x = XappManager{} - x.Initialize(&h) - - // Just run on the background (for coverage) - go x.Run() - - code := m.Run() - os.Exit(code) -} - -func TestGetHealthCheck(t *testing.T) { - req, _ := http.NewRequest("GET", "/ric/v1/health", nil) - response := executeRequest(req) - - checkResponseCode(t, http.StatusOK, response.Code) -} - -func TestGetAppsReturnsEmpty(t *testing.T) { - req, _ := http.NewRequest("GET", "/ric/v1/xapps", nil) - response := executeRequest(req) - - checkResponseCode(t, http.StatusOK, response.Code) - if body := response.Body.String(); body != "[]" { - t.Errorf("handler returned unexpected body: got %v want []", body) - } -} - -func TestCreateXApp(t *testing.T) { - xapp = generateXapp("dummy-xapp", "started", "1.0", "dummy-xapp-1234-5678", "running", "127.0.0.1", "9999") - - payload := []byte(`{"name":"dummy-xapp"}`) - req, _ := http.NewRequest("POST", "/ric/v1/xapps", bytes.NewBuffer(payload)) - response := executeRequest(req) - - checkResponseData(t, response, http.StatusCreated, false) -} - -func TestGetAppsReturnsListOfXapps(t *testing.T) { - xapps = append(xapps, xapp) - req, _ := http.NewRequest("GET", "/ric/v1/xapps", nil) - response := executeRequest(req) - - checkResponseData(t, response, http.StatusOK, true) -} - -func TestGetAppByIdReturnsGivenXapp(t *testing.T) { - req, _ := http.NewRequest("GET", "/ric/v1/xapps/" + xapp.Name, nil) - response := executeRequest(req) - - checkResponseData(t, response, http.StatusOK, false) -} - -func TestGetAppInstanceByIdReturnsGivenXapp(t *testing.T) { - req, _ := http.NewRequest("GET", "/ric/v1/xapps/" + xapp.Name + "/instances/dummy-xapp-1234-5678", nil) - response := executeRequest(req) - - var ins XappInstance - checkResponseCode(t, http.StatusOK, response.Code) - json.NewDecoder(response.Body).Decode(&ins) - - if !reflect.DeepEqual(ins, xapp.Instances[0]) { - t.Errorf("handler returned unexpected body: got: %v, expected: %v", ins, xapp.Instances[0]) - } -} - -func TestDeleteAppRemovesGivenXapp(t *testing.T) { - req, _ := http.NewRequest("DELETE", "/ric/v1/xapps/" + xapp.Name, nil) - response := executeRequest(req) - - checkResponseData(t, response, http.StatusNoContent, false) - - // Xapp not found from the Redis DB - helmError = errors.New("Not found") - - req, _ = http.NewRequest("GET", "/ric/v1/xapps/" + xapp.Name, nil) - response = executeRequest(req) - checkResponseCode(t, http.StatusNotFound, response.Code) -} - -// Error handling -func TestGetXappReturnsError(t *testing.T) { - helmError = errors.New("Not found") - - req, _ := http.NewRequest("GET", "/ric/v1/xapps/invalidXappName", nil) - response := executeRequest(req) - checkResponseCode(t, http.StatusNotFound, response.Code) -} - -func TestGetXappInstanceReturnsError(t *testing.T) { - helmError = errors.New("Some error") - - req, _ := http.NewRequest("GET", "/ric/v1/xapps/" + xapp.Name + "/instances/invalidXappName", nil) - response := executeRequest(req) - checkResponseCode(t, http.StatusNotFound, response.Code) -} - -func TestGetXappListReturnsError(t *testing.T) { - helmError = errors.New("Internal error") - - req, _ := http.NewRequest("GET", "/ric/v1/xapps", nil) - response := executeRequest(req) - checkResponseCode(t, http.StatusInternalServerError, response.Code) -} - -func TestCreateXAppWithoutXappData(t *testing.T) { - req, _ := http.NewRequest("POST", "/ric/v1/xapps", nil) - response := executeRequest(req) - checkResponseData(t, response, http.StatusMethodNotAllowed, false) -} - -func TestCreateXAppWithInvalidXappData(t *testing.T) { - body := []byte("Invalid JSON data ...") - - req, _ := http.NewRequest("POST", "/ric/v1/xapps", bytes.NewBuffer(body)) - response := executeRequest(req) - checkResponseData(t, response, http.StatusMethodNotAllowed, false) -} - -func TestCreateXAppReturnsError(t *testing.T) { - helmError = errors.New("Not found") - - payload := []byte(`{"name":"dummy-xapp"}`) - req, _ := http.NewRequest("POST", "/ric/v1/xapps", bytes.NewBuffer(payload)) - response := executeRequest(req) - - checkResponseData(t, response, http.StatusInternalServerError, false) -} - -func TestDeleteXappListReturnsError(t *testing.T) { - helmError = errors.New("Internal error") - - req, _ := http.NewRequest("DELETE", "/ric/v1/xapps/invalidXappName", nil) - response := executeRequest(req) - checkResponseCode(t, http.StatusInternalServerError, response.Code) -} - -// Helper functions -type fn func(w http.ResponseWriter, r *http.Request) - -func executeRequest(req *http.Request) *httptest.ResponseRecorder { - rr := httptest.NewRecorder() - - vars := map[string]string{ - "id": "1", - } - req = mux.SetURLVars(req, vars) - - x.router.ServeHTTP(rr, req) - - return rr -} - -func checkResponseCode(t *testing.T, expected, actual int) { - if expected != actual { - t.Errorf("Expected response code %d. Got %d\n", expected, actual) - } -} - -func checkResponseData(t *testing.T, response *httptest.ResponseRecorder, expectedHttpStatus int, isList bool) { - expectedData := xapp - - checkResponseCode(t, expectedHttpStatus, response.Code) - if isList == true { - jsonResp := []Xapp{} - json.NewDecoder(response.Body).Decode(&jsonResp) - - if !reflect.DeepEqual(jsonResp[0], expectedData) { - t.Errorf("handler returned unexpected body: %v", jsonResp) - } - } else { - json.NewDecoder(response.Body).Decode(&xapp) - - if !reflect.DeepEqual(xapp, expectedData) { - t.Errorf("handler returned unexpected body: got: %v, expected: %v", xapp, expectedData) - } - } -} - -func generateXapp(name, status, ver, iname, istatus, ip, port string) (x Xapp) { - x.Name = name - x.Status = status - x.Version = ver - p , _ := strconv.Atoi(port) - 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"}, - } - x.Instances = append(x.Instances, instance) - - return -} diff --git a/src/helm.go b/src/helm.go deleted file mode 100755 index cfa1f18..0000000 --- a/src/helm.go +++ /dev/null @@ -1,359 +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 ( - "fmt" - "log" - "os" - "os/exec" - "regexp" - "strconv" - "strings" - "github.com/spf13/viper" - "gopkg.in/yaml.v2" - "io/ioutil" - "path" -) - -var execCommand = exec.Command - -func Exec(args string) (out []byte, err error) { - cmd := execCommand("/bin/sh", "-c", strings.Join([]string{"helm", args}, " ")) - - // In testing environment, don't print command traces ... - if !strings.HasSuffix(os.Args[0], ".test") { - log.Printf("Running command: %v", cmd) - } - - out, err = cmd.CombinedOutput() - if err != nil { - mdclog(Mdclog_err, formatLog("Command failed", args, err.Error())) - return out, err - } - - if !strings.HasSuffix(os.Args[0], ".test") { - mdclog(Mdclog_debug, formatLog("command success", string(out), "")) - } - - return out, err -} - -func (h *Helm) Run(args string) (out []byte, err error) { - if h.initDone == false { - if _, err := h.Init(); err != nil { - mdclog(Mdclog_err, formatLog("helm init failed", args, err.Error())) - return out, err - } - mdclog(Mdclog_debug, formatLog("Helm init done successfully!", args, "")) - - // Add helm repo - if _, err := h.AddRepo(); err != nil { - mdclog(Mdclog_err, formatLog("Helm repo addition failed", args, err.Error())) - return out, err - } - - mdclog(Mdclog_debug, formatLog("Helm repo added successfully", string(out), "")) - h.initDone = true - } - - return Exec(args) -} - -// API functions -func (h *Helm) Init() (out []byte, err error) { - - // Add Tiller address as environment variable - if err := addTillerEnv(); err != nil { - return out, err - } - - return Exec(strings.Join([]string{"init -c"}, "")) -} - -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 { - mdclog(Mdclog_err, formatLog("helm_repo_username ReadFile failed", "", err.Error())) - return - } - - username := " --username " + string(credFile) - - credFile, err = ioutil.ReadFile(viper.GetString("helm.helm-password-file")) - if err != nil { - mdclog(Mdclog_err, formatLog("helm_repo_password ReadFile failed", "", err.Error())) - return - } - - pwd := " --password " + string(credFile) - - // Get internal helm repo name - rname := viper.GetString("helm.repo-name") - - // Get helm repo address from values.yaml - repo := viper.GetString("helm.repo") - - return Exec(strings.Join([]string{"repo add ", rname, " ", repo, username, pwd}, "")) -} - -func (h *Helm) Install(name string) (xapp Xapp, err error) { - out, err := h.Run(strings.Join([]string{"repo update "}, "")) - if err != nil { - return - } - - rname := viper.GetString("helm.repo-name") - - ns := getNamespaceArgs() - out, err = h.Run(strings.Join([]string{"install ", rname, "/", name, " --name ", name, ns}, "")) - if err != nil { - return - } - - return h.ParseStatus(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(Mdclog_err, formatLog("Getting xapps status", "", err.Error())) - return - } - - return h.ParseStatus(name, string(out)) -} - -func (h *Helm) StatusAll() (xapps []Xapp, err error) { - xappNameList, err := h.List() - if err != nil { - mdclog(Mdclog_err, formatLog("Helm list failed", "", err.Error())) - return - } - - return h.parseAllStatus(xappNameList) -} - -func (h *Helm) List() (names []string, err error) { - - ns := getNamespaceArgs() - out, err := h.Run(strings.Join([]string{"list --all --output yaml ", ns}, "")) - if err != nil { - mdclog(Mdclog_err, formatLog("Listing deployed xapps failed", "", err.Error())) - return - } - - return h.GetNames(string(out)) -} - -func (h *Helm) Delete(name string) (xapp Xapp, err error) { - xapp, err = h.Status(name) - if err != nil { - mdclog(Mdclog_err, formatLog("Fetching xapp status failed", "", err.Error())) - return - } - - _, err = h.Run(strings.Join([]string{"del --purge ", name}, "")) - return xapp, err -} - -func (h *Helm) Fetch(name , tarDir string) (error) { - if strings.HasSuffix(os.Args[0], ".test") { - return nil - } - - rname := viper.GetString("helm.repo-name") + "/" - - _, err := h.Run(strings.Join([]string{"fetch --untar --untardir ", tarDir, " ", rname, name}, "")) - return err -} - -// Helper functions -func (h *Helm) GetMessages(name string) (msgs MessageTypes, err error) { - tarDir := viper.GetString("xapp.tarDir") - if tarDir == "" { - tarDir = "/tmp" - } - - if h.Fetch(name, tarDir); err != nil { - mdclog(Mdclog_err, formatLog("Fetch chart failed", "", err.Error())) - return - } - - return h.ParseMessages(name, tarDir, "msg_type.yaml") - -} - -func (h *Helm) ParseMessages(name string, chartDir, msgFile string) (msgs MessageTypes, err error) { - yamlFile, err := ioutil.ReadFile(path.Join(chartDir, name, msgFile)) - if err != nil { - mdclog(Mdclog_err, formatLog("ReadFile failed", "", err.Error())) - return - } - - err = yaml.Unmarshal(yamlFile, &msgs) - if err != nil { - mdclog(Mdclog_err, formatLog("Unmarshal failed", "", err.Error())) - return - } - - if err = os.RemoveAll(path.Join(chartDir, name)); err != nil { - mdclog(Mdclog_err, formatLog("RemoveAll failed", "", err.Error())) - } - - return -} - -func (h *Helm) GetVersion(name string) (version string) { - - ns := getNamespaceArgs() - out, err := h.Run(strings.Join([]string{"list --output yaml ", name, ns}, "")) - if err != nil { - return - } - - var re = regexp.MustCompile(`AppVersion: .*`) - ver := re.FindStringSubmatch(string(out)) - if ver != nil { - version = strings.Split(ver[0], ": ")[1] - version, _ = strconv.Unquote(version) - } - - return -} - -func (h *Helm) GetState(out string) (status string) { - re := regexp.MustCompile(`STATUS: .*`) - result := re.FindStringSubmatch(string(out)) - if result != nil { - status = strings.ToLower(strings.Split(result[0], ": ")[1]) - } - - return -} - -func (h *Helm) GetAddress(out string) (ip, port string) { - var tmp string - re := regexp.MustCompile(`ClusterIP.*`) - addr := re.FindStringSubmatch(string(out)) - if addr != nil { - fmt.Sscanf(addr[0], "%s %s %s %s", &tmp, &ip, &tmp, &port) - } - - return -} - -func (h *Helm) GetNames(out string) (names []string, err error) { - re := regexp.MustCompile(`Name: .*`) - result := re.FindAllStringSubmatch(out, -1) - if result == nil { - return - } - - for _, v := range result { - xappName := strings.Split(v[0], ": ")[1] - if strings.Contains(xappName, "appmgr") == false { - names = append(names, xappName) - } - } - return names, nil -} - -func (h *Helm) FillInstanceData(name string, out string, xapp *Xapp, msgs MessageTypes) { - ip, port := h.GetAddress(out) - - var tmp string - r := regexp.MustCompile(`(?s)\/Pod.*?\/Service`) - result := r.FindStringSubmatch(string(out)) - if result == nil { - return - } - - re := regexp.MustCompile(name + "-(\\d+).*") - resources := re.FindAllStringSubmatch(string(result[0]), -1) - if resources != nil { - for _, v := range resources { - var x XappInstance - fmt.Sscanf(v[0], "%s %s %s", &x.Name, &tmp, &x.Status) - x.Status = strings.ToLower(x.Status) - x.Ip = ip - x.Port, _ = strconv.Atoi(strings.Split(port, "/")[0]) - x.TxMessages = msgs.TxMessages - x.RxMessages = msgs.RxMessages - xapp.Instances = append(xapp.Instances, x) - } - } -} - -func (h *Helm) ParseStatus(name string, out string) (xapp Xapp, err error) { - types, err := h.GetMessages(name) - if (err != nil) { - return - } - - xapp.Name = name - xapp.Version = h.GetVersion(name) - xapp.Status = h.GetState(out) - h.FillInstanceData(name, out, &xapp, types) - - return -} - -func (h *Helm) parseAllStatus(names []string) (xapps []Xapp, err error) { - xapps = []Xapp{} - - for _, name := range names { - x, err := h.Status(name) - if err == nil { - xapps = append(xapps, x) - } - } - - return -} - -func addTillerEnv() (err error) { - - service := viper.GetString("helm.tiller-service") - namespace := viper.GetString("helm.tiller-namespace") - port := viper.GetString("helm.tiller-port") - - if err = os.Setenv("HELM_HOST", service + "." + namespace + ":" + port); err != nil { - mdclog(Mdclog_err, formatLog("Tiller Env Setting Failed", "", err.Error())) - } - - return err -} - -func getNamespaceArgs() (string) { - ns := viper.GetString("xapp.namespace") - if ns == "" { - ns = "ricxapp" - } - return " --namespace=" + ns -} - -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/src/helm_test.go b/src/helm_test.go deleted file mode 100755 index 44882df..0000000 --- a/src/helm_test.go +++ /dev/null @@ -1,277 +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 ( - "fmt" - "os" - "os/exec" - "strconv" - "testing" - "reflect" - "github.com/spf13/viper" - "io/ioutil" - "path" -) - -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 mockedExitStatus = 0 -var mockedStdout string -var h = Helm{} - -func fakeExecCommand(command string, args ...string) *exec.Cmd { - cs := []string{"-test.run=TestExecCommandHelper", "--", command} - cs = append(cs, args...) - - cmd := exec.Command(os.Args[0], cs...) - es := strconv.Itoa(mockedExitStatus) - cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1", "STDOUT=" + mockedStdout, "EXIT_STATUS=" + es} - - return cmd -} - -func TestExecCommandHelper(t *testing.T) { - if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { - return - } - - fmt.Fprintf(os.Stdout, os.Getenv("STDOUT")) - i, _ := strconv.Atoi(os.Getenv("EXIT_STATUS")) - os.Exit(i) -} - -func writeTestCreds() (err error) { - - // Write test entries to helm username and password files - f, err := os.Create(viper.GetString("helm.helm-username-file")) - if err != nil { - return err - } - - _, err = f.WriteString(viper.GetString("helm.secrets.username")) - if err != nil { - f.Close() - return (err) - } - f.Close() - - f, err = os.Create(viper.GetString("helm.helm-password-file")) - if err != nil { - return err - } - - _, err = f.WriteString(viper.GetString("helm.secrets.password")) - if err != nil { - f.Close() - return (err) - } - f.Close() - return -} - -func TestHelmInit(t *testing.T) { - mockedExitStatus = 0 - execCommand = fakeExecCommand - defer func() { execCommand = exec.Command }() - - if err := writeTestCreds(); err != nil { - t.Errorf("Writing test entries failed: %s", err) - return - } - - out, err := h.Init() - if err != nil { - t.Errorf("Helm init failed: %s %s", err, string(out)) - } -} - -func TestHelmInstall(t *testing.T) { - copyFile(t) - mockedExitStatus = 0 - execCommand = fakeExecCommand - mockedStdout = helmStatusOutput - defer func() { execCommand = exec.Command }() - - xapp, err := h.Install("dummy-xapp") - if err != nil { - t.Errorf("Helm install failed: %v", err) - } - - x := getXappData() - xapp.Version = "1.0" - - if !reflect.DeepEqual(xapp, x) { - t.Errorf("%v \n%v", xapp, x) - } -} - -func TestHelmStatus(t *testing.T) { - copyFile(t) - mockedExitStatus = 0 - mockedStdout = helmStatusOutput - execCommand = fakeExecCommand - defer func() { execCommand = exec.Command }() - - xapp, err := h.Status("dummy-xapp") - if err != nil { - t.Errorf("Helm status failed: %v", err) - } - - x := getXappData() - xapp.Version = "1.0" - - if !reflect.DeepEqual(xapp, x) { - t.Errorf("%v \n%v", xapp, x) - } -} - -func TestHelmStatusAll(t *testing.T) { - copyFile(t) - mockedExitStatus = 0 - mockedStdout = helListOutput - execCommand = fakeExecCommand - defer func() { execCommand = exec.Command }() - - xapp, err := h.StatusAll() - if err != nil { - t.Errorf("Helm StatusAll failed: %v - %v", err, xapp) - } - - // Todo: check the content -} - -func TestHelmParseAllStatus(t *testing.T) { - copyFile(t) - mockedExitStatus = 0 - mockedStdout = helListOutput - execCommand = fakeExecCommand - defer func() { execCommand = exec.Command }() - - xapp, err := h.parseAllStatus([]string{"dummy-xapp", "dummy-xapp2"}) - if err != nil { - t.Errorf("Helm parseAllStatus failed: %v - %v", err, xapp) - } - - // Todo: check the content -} - -func TestHelmDelete(t *testing.T) { - copyFile(t) - mockedExitStatus = 0 - mockedStdout = helListOutput - execCommand = fakeExecCommand - defer func() { execCommand = exec.Command }() - - xapp, err := h.Delete("dummy-xapp") - if err != nil { - t.Errorf("Helm delete failed: %v - %v", err, xapp) - } - - // Todo: check the content -} - -func TestHelmLists(t *testing.T) { - mockedExitStatus = 0 - mockedStdout = helListOutput - execCommand = fakeExecCommand - defer func() { execCommand = exec.Command }() - - names, err := h.List() - 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 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 -} - - -func copyFile(t *testing.T) { - tarDir := path.Join(viper.GetString("xapp.tarDir"), "dummy-xapp") - err := os.MkdirAll(tarDir, 0777) - if err != nil { - t.Errorf("%v", err) - } - - data, err := ioutil.ReadFile("../config/msg_type.yaml") - if err != nil { - t.Errorf("%v", err) - } - - _ = ioutil.WriteFile(path.Join(tarDir, "msg_type.yaml"), data, 0644) - if err != nil { - t.Errorf("%v", err) - } -} \ No newline at end of file diff --git a/src/subscriptions.go b/src/subscriptions.go deleted file mode 100755 index c8db1e6..0000000 --- a/src/subscriptions.go +++ /dev/null @@ -1,131 +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 ( - "bytes" - "log" - "net/http" - "encoding/json" - "time" - "github.com/segmentio/ksuid" - cmap "github.com/orcaman/concurrent-map" -) - -func (sd *SubscriptionDispatcher) Initialize() { - sd.client = &http.Client{} - sd.subscriptions = cmap.New() -} - -func (sd *SubscriptionDispatcher) Add(sr SubscriptionReq) (resp SubscriptionResp) { - key := ksuid.New().String() - resp = SubscriptionResp{key, 0, sr.EventType} - sr.Id = key - - sd.subscriptions.Set(key, Subscription{sr, resp}) - - log.Printf("New subscription added: key=%s value=%v", key, sr) - return -} - -func (sd *SubscriptionDispatcher) GetAll() (hooks []SubscriptionReq) { - hooks = []SubscriptionReq{} - for v := range sd.subscriptions.IterBuffered() { - hooks = append(hooks, v.Val.(Subscription).req) - } - - return hooks -} - -func (sd *SubscriptionDispatcher) Get(id string) (SubscriptionReq, bool) { - if v, found := sd.subscriptions.Get(id); found { - log.Printf("Subscription id=%s found: %v", id, v.(Subscription).req) - - return v.(Subscription).req, found - } - return SubscriptionReq{}, false -} - -func (sd *SubscriptionDispatcher) Delete(id string) (SubscriptionReq, bool) { - if v, found := sd.subscriptions.Get(id); found { - log.Printf("Subscription id=%s found: %v ... deleting", id, v.(Subscription).req) - - sd.subscriptions.Remove(id) - return v.(Subscription).req, found - } - return SubscriptionReq{}, false -} - -func (sd *SubscriptionDispatcher) Update(id string, sr SubscriptionReq) (SubscriptionReq, bool) { - if s, found := sd.subscriptions.Get(id); found { - log.Printf("Subscription id=%s found: %v ... updating", id, s.(Subscription).req) - - sr.Id = id - sd.subscriptions.Set(id, Subscription{sr, s.(Subscription).resp}); - return sr, found - } - return SubscriptionReq{}, false -} - -func (sd *SubscriptionDispatcher) Publish(x Xapp, et EventType) { - for v := range sd.subscriptions.Iter() { - go sd.notify(x, et, v.Val.(Subscription)) - } -} - -func (sd *SubscriptionDispatcher) notify(x Xapp, et EventType, s Subscription) error { - notif := []SubscriptionNotif{} - notif = append(notif, SubscriptionNotif{Id: s.req.Id, Version: s.resp.Version, EventType: string(et), XappData: x}) - - jsonData, err := json.Marshal(notif) - if err != nil { - log.Panic(err) - } - - // Execute the request with retry policy - return sd.retry(s, func() error { - resp, err := http.Post(s.req.TargetUrl, "application/json", bytes.NewBuffer(jsonData)) - if err != nil { - log.Printf("Posting to subscription failed: %v", err) - return err - } - - if resp.StatusCode != http.StatusOK { - log.Printf("Client returned error code: %d", resp.StatusCode) - return err - } - - log.Printf("subscription to '%s' dispatched, response code: %d \n", s.req.TargetUrl, resp.StatusCode) - return nil - }) -} - -func (sd *SubscriptionDispatcher) retry(s Subscription, fn func() error) error { - if err := fn(); err != nil { - // Todo: use exponential backoff, or similar mechanism - if s.req.MaxRetries--; s.req.MaxRetries > 0 { - time.Sleep(time.Duration(s.req.RetryTimer) * time.Second) - return sd.retry(s, fn) - } - sd.subscriptions.Remove(s.req.Id); - return err - } - return nil -} diff --git a/src/subscriptions_test.go b/src/subscriptions_test.go deleted file mode 100755 index 065eddb..0000000 --- a/src/subscriptions_test.go +++ /dev/null @@ -1,191 +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 ( - "net/http" - "testing" - "bytes" - "encoding/json" - "net/http/httptest" - "net" - "log" - "fmt" -) - -var resp SubscriptionResp - -// Test cases -func TestNoSubscriptionsFound(t *testing.T) { - req, _ := http.NewRequest("GET", "/ric/v1/subscriptions", nil) - response := executeRequest(req) - - checkResponseCode(t, http.StatusOK, response.Code) - if body := response.Body.String(); body != "[]" { - t.Errorf("handler returned unexpected body: got %v want []", body) - } -} - -func TestAddNewSubscription(t *testing.T) { - payload := []byte(`{"maxRetries": 3, "retryTimer": 5, "eventType":"Created", "targetUrl": "http://localhost:8087/xapps_handler"}`) - req, _ := http.NewRequest("POST", "/ric/v1/subscriptions", bytes.NewBuffer(payload)) - response := executeRequest(req) - - checkResponseCode(t, http.StatusCreated, response.Code) - - json.NewDecoder(response.Body).Decode(&resp) - if resp.Version != 0 { - t.Errorf("Creating new subscription failed: %v", resp) - } -} - -func TestGettAllSubscriptions(t *testing.T) { - req, _ := http.NewRequest("GET", "/ric/v1/subscriptions", nil) - response := executeRequest(req) - - checkResponseCode(t, http.StatusOK, response.Code) - - var subscriptions []SubscriptionReq - json.NewDecoder(response.Body).Decode(&subscriptions) - - verifySubscription(t, subscriptions[0], "http://localhost:8087/xapps_handler", 3, 5, "Created") -} - -func TestGetSingleSubscription(t *testing.T) { - req, _ := http.NewRequest("GET", "/ric/v1/subscriptions/" + resp.Id, nil) - response := executeRequest(req) - - checkResponseCode(t, http.StatusOK, response.Code) - - var subscription SubscriptionReq - json.NewDecoder(response.Body).Decode(&subscription) - - verifySubscription(t, subscription, "http://localhost:8087/xapps_handler", 3, 5, "Created") -} - -func TestUpdateSingleSubscription(t *testing.T) { - payload := []byte(`{"maxRetries": 11, "retryTimer": 22, "eventType":"Deleted", "targetUrl": "http://localhost:8088/xapps_handler"}`) - - req, _ := http.NewRequest("PUT", "/ric/v1/subscriptions/" + resp.Id, bytes.NewBuffer(payload)) - response := executeRequest(req) - - checkResponseCode(t, http.StatusOK, response.Code) - - var res SubscriptionResp - json.NewDecoder(response.Body).Decode(&res) - if res.Version != 0 { - t.Errorf("handler returned unexpected data: %v", resp) - } - - // Check that the subscription is updated properly - req, _ = http.NewRequest("GET", "/ric/v1/subscriptions/" + resp.Id, nil) - response = executeRequest(req) - checkResponseCode(t, http.StatusOK, response.Code) - - var subscription SubscriptionReq - json.NewDecoder(response.Body).Decode(&subscription) - - verifySubscription(t, subscription, "http://localhost:8088/xapps_handler", 11, 22, "Deleted") -} - -func TestDeleteSingleSubscription(t *testing.T) { - req, _ := http.NewRequest("DELETE", "/ric/v1/subscriptions/" + resp.Id, nil) - response := executeRequest(req) - - checkResponseCode(t, http.StatusNoContent, response.Code) - - // Check that the subscription is removed properly - req, _ = http.NewRequest("GET", "/ric/v1/subscriptions/" + resp.Id, nil) - response = executeRequest(req) - checkResponseCode(t, http.StatusNotFound, response.Code) -} - -func TestDeleteSingleSubscriptionFails(t *testing.T) { - req, _ := http.NewRequest("DELETE", "/ric/v1/subscriptions/invalidSubscriptionId" , nil) - response := executeRequest(req) - - checkResponseCode(t, http.StatusNotFound, response.Code) -} - -func TestAddSingleSubscriptionFailsBodyEmpty(t *testing.T) { - req, _ := http.NewRequest("POST", "/ric/v1/subscriptions/" + resp.Id , nil) - response := executeRequest(req) - - checkResponseCode(t, http.StatusMethodNotAllowed, response.Code) -} - -func TestUpdateeSingleSubscriptionFailsBodyEmpty(t *testing.T) { - req, _ := http.NewRequest("PUT", "/ric/v1/subscriptions/" + resp.Id , nil) - response := executeRequest(req) - - checkResponseCode(t, http.StatusMethodNotAllowed, response.Code) -} - -func TestUpdateeSingleSubscriptionFailsInvalidId(t *testing.T) { - payload := []byte(`{"maxRetries": 11, "retryTimer": 22, "eventType":"Deleted", "targetUrl": "http://localhost:8088/xapps_handler"}`) - - req, _ := http.NewRequest("PUT", "/ric/v1/subscriptions/invalidSubscriptionId" + resp.Id, bytes.NewBuffer(payload)) - response := executeRequest(req) - - checkResponseCode(t, http.StatusNotFound, response.Code) -} - -func TestPublishXappAction(t *testing.T) { - payload := []byte(`{"maxRetries": 3, "retryTimer": 5, "eventType":"Created", "targetUrl": "http://127.0.0.1:8888"}`) - req, _ := http.NewRequest("POST", "/ric/v1/subscriptions", bytes.NewBuffer(payload)) - response := executeRequest(req) - - checkResponseCode(t, http.StatusCreated, response.Code) - - // Create a RestApi server (simulating RM) - ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Hello, XM!") - })) - - l, err := net.Listen("tcp", "127.0.0.1:8888") - if err != nil { - log.Fatal(err) - } - ts.Listener.Close() - ts.Listener = l - ts.Start() - - defer ts.Close() - - x.sd.Publish(xapp, EventType("created")) -} - -func verifySubscription(t *testing.T, subscription SubscriptionReq, url string, retries int, timer int, event string) { - if subscription.TargetUrl != url { - t.Errorf("Unexpected url: got=%s expected=%s", subscription.TargetUrl, url) - } - - if subscription.MaxRetries != retries { - t.Errorf("Unexpected retries: got=%d expected=%d", subscription.MaxRetries, retries) - } - - if subscription.RetryTimer != timer { - t.Errorf("Unexpected timer: got=%d expected=%d", subscription.RetryTimer, timer) - } - - if subscription.EventType != event { - t.Errorf("Unexpected event type: got=%s expected=%s", subscription.EventType, event) - } -} \ No newline at end of file diff --git a/src/types.go b/src/types.go deleted file mode 100755 index 930d008..0000000 --- a/src/types.go +++ /dev/null @@ -1,126 +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 ( - "github.com/gorilla/mux" - cmap "github.com/orcaman/concurrent-map" - "net/http" -) - -type CmdOptions struct { - hostAddr *string - helmHost *string - helmChartPath *string -} - -type Resource struct { - Method string - Url string - HandlerFunc http.HandlerFunc -} - -type Xapp struct { - Name string `json:"name"` - Status string `json:"status"` - Version string `json:"version"` - Instances []XappInstance `json:"instances"` -} - -type XappInstance struct { - Name string `json:"name"` - Status string `json:"status"` - Ip string `json:"ip"` - Port int `json:"port"` - TxMessages []string `json:"txMessages"` - RxMessages []string `json:"rxMessages"` -} - -type XappManager struct { - router *mux.Router - helm Helmer - sd SubscriptionDispatcher - opts CmdOptions -} - -type Helmer interface { - Install(name string) (xapp Xapp, err error) - Status(name string) (xapp Xapp, err error) - StatusAll() (xapps []Xapp, err error) - List() (xapps []string, err error) - Delete(name string) (xapp Xapp, err error) -} - -type Helm struct { - host string - chartPath string - initDone bool -} - -type SubscriptionReq struct { - Id string `json:"id"` - TargetUrl string `json:"targetUrl"` - EventType string `json:"eventType"` - MaxRetries int `json:"maxRetries"` - RetryTimer int `json:"retryTimer"` -} - -type SubscriptionResp struct { - Id string `json:"id"` - Version int `json:"version"` - EventType string `json:"eventType"` -} - -type SubscriptionNotif struct { - Id string `json:"id"` - Version int `json:"version"` - EventType string `json:"eventType"` - XappData Xapp `json:"xapp"` -} - -type Subscription struct { - req SubscriptionReq - resp SubscriptionResp -} - -type SubscriptionDispatcher struct { - client *http.Client - subscriptions cmap.ConcurrentMap -} - -type MessageTypes struct { - TxMessages []string `yaml:"txMessages"` - RxMessages []string `yaml:"rxMessages"` -} - -type EventType string - -const ( - Created EventType = "created" - Updated EventType = "updated" - Deleted EventType = "deleted" -) - -const ( - Mdclog_err = 1 //! Error level log entry - Mdclog_warn = 2 //! Warning level log entry - Mdclog_info = 3 //! Info level log entry - Mdclog_debug = 4 //! Debug level log entry -) -- 2.16.6