New standard GO project layout 50/50/1
authorAbukar Mohamed <amohamed@nokia.com>
Thu, 11 Apr 2019 13:51:07 +0000 (13:51 +0000)
committerAbukar Mohamed <abukar.mohamed@nokia.com>
Thu, 11 Apr 2019 13:54:43 +0000 (13:54 +0000)
By Juha Hyttinen <juha.hyttinen@nokia.com>

Change-Id: I82e73e38d4ecc96a4b827047d570b4a0da35d129
Signed-off-by: Abukar Mohamed <abukar.mohamed@nokia.com>
36 files changed:
Makefile [moved from build/Makefile with 51% similarity, mode: 0644]
README.md
api/appmgr_rest_api.json [moved from rest_api/xapp_manager_rest_api.json with 98% similarity]
cmd/appmgr/api.go [new file with mode: 0755]
cmd/appmgr/api_test.go [new file with mode: 0755]
cmd/appmgr/config.go [moved from src/config.go with 56% similarity]
cmd/appmgr/db.go [new file with mode: 0755]
cmd/appmgr/helm.go [new file with mode: 0755]
cmd/appmgr/logger.go [moved from src/logger.go with 69% similarity]
cmd/appmgr/main.go [moved from src/main.go with 89% similarity]
cmd/appmgr/subscriptions.go [new file with mode: 0755]
cmd/appmgr/subscriptions_test.go [new file with mode: 0755]
cmd/appmgr/types.go [new file with mode: 0755]
config/appmgr.yaml
docker/Dockerfile [moved from build/Dockerfile with 78% similarity]
docker/docker-entrypoint.sh [moved from build/docker-entrypoint.sh with 100% similarity]
go.mod [new file with mode: 0755]
go.sum [new file with mode: 0755]
helm_chart/appmgr/templates/deployment.yaml
helm_chart/appmgr/values.yaml
internal/sdlgo/LICENSE.txt [new file with mode: 0644]
internal/sdlgo/README [new file with mode: 0644]
internal/sdlgo/bench_test.go [new file with mode: 0644]
internal/sdlgo/go.mod [new file with mode: 0644]
internal/sdlgo/go.sum [new file with mode: 0644]
internal/sdlgo/internal/sdlgoredis/sdlgoredis.go [new file with mode: 0644]
internal/sdlgo/sdl.go [new file with mode: 0644]
internal/sdlgo/sdl_test.go [new file with mode: 0644]
scripts/appmgrcli [moved from cli/appmgrcli with 98% similarity]
src/api.go [deleted file]
src/api_test.go [deleted file]
src/helm.go [deleted file]
src/helm_test.go [deleted file]
src/subscriptions.go [deleted file]
src/subscriptions_test.go [deleted file]
src/types.go [deleted file]

old mode 100755 (executable)
new mode 100644 (file)
similarity index 51%
rename from build/Makefile
rename to Makefile
index 266ac05..8061874
+++ b/Makefile
 #   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
 
index f28f1dc..d3cf791 100755 (executable)
--- 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
similarity index 98%
rename from rest_api/xapp_manager_rest_api.json
rename to api/appmgr_rest_api.json
index 951f199..c5a4c0b 100644 (file)
     }
   },
   "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",
         }
       }
     },
-    "/ric/v1/xapps/{xAppName}/instances/{xAppInstanceName}": {
+    "/xapps/{xAppName}/instances/{xAppInstanceName}": {
       "get": {
         "summary": "Returns the status of a given xapp",
         "operationId": "getXappInstanceByName",
         }
       }
     },
-    "/ric/v1/subscriptions": {
+    "/subscriptions": {
       "post": {
         "summary": "Subscribe event",
         "operationId": "addSubscription",
         }
       }
     },
-    "/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 (executable)
index 0000000..0d3fef9
--- /dev/null
@@ -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 (executable)
index 0000000..7b9691c
--- /dev/null
@@ -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
+}
similarity index 56%
rename from src/config.go
rename to cmd/appmgr/config.go
index e9ede6b..5e1975e 100755 (executable)
 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 (executable)
index 0000000..6f772c0
--- /dev/null
@@ -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 (executable)
index 0000000..453ff29
--- /dev/null
@@ -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)
+}
similarity index 69%
rename from src/logger.go
rename to cmd/appmgr/logger.go
index bc255ee..591fa47 100755 (executable)
@@ -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)
+       })
 }
similarity index 89%
rename from src/main.go
rename to cmd/appmgr/main.go
index e948cac..da50681 100755 (executable)
 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 (executable)
index 0000000..5728e5b
--- /dev/null
@@ -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 (executable)
index 0000000..8861af0
--- /dev/null
@@ -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 (executable)
index 0000000..a3599fe
--- /dev/null
@@ -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
+)
index 3753f1f..6be3be7 100755 (executable)
@@ -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"
 "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
similarity index 78%
rename from build/Dockerfile
rename to docker/Dockerfile
index 298fb67..3277037 100755 (executable)
@@ -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/go.mod b/go.mod
new file mode 100755 (executable)
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 (executable)
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=
index 0ef46d2..0a7eba4 100755 (executable)
@@ -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 }}
index 8d71678..d27d020 100755 (executable)
@@ -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 (file)
index 0000000..f886a1f
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/internal/sdlgo/bench_test.go b/internal/sdlgo/bench_test.go
new file mode 100644 (file)
index 0000000..9f70fe6
--- /dev/null
@@ -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 (file)
index 0000000..d46db31
--- /dev/null
@@ -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 (file)
index 0000000..a6e2f77
--- /dev/null
@@ -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 (file)
index 0000000..2037496
--- /dev/null
@@ -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 (file)
index 0000000..cdf50f9
--- /dev/null
@@ -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 (file)
index 0000000..8c64d05
--- /dev/null
@@ -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)
+}
similarity index 98%
rename from cli/appmgrcli
rename to scripts/appmgrcli
index e31048a..ac00485 100755 (executable)
@@ -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 (executable)
index b9c2b63..0000000
+++ /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 (executable)
index 7610f49..0000000
+++ /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 (executable)
index cfa1f18..0000000
+++ /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 (executable)
index 44882df..0000000
+++ /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  <none>       80/TCP   55m
-
-==> v1beta1/Deployment
-NAME        READY  UP-TO-DATE  AVAILABLE  AGE
-dummy-xapp  3/3    3           3          55m
-`
-
-var helListOutput = `Next: ""
-Releases:
-- AppVersion: "1.0"
-  Chart: dummy-xapp-chart-0.1.0
-  Name: dummy-xapp
-  Namespace: default
-  Revision: 1
-  Status: DEPLOYED
-  Updated: Mon Mar 11 06:55:05 2019
-- AppVersion: "2.0"
-  Chart: dummy-xapp-chart-0.1.0
-  Name: dummy-xapp2
-  Namespace: default
-  Revision: 1
-  Status: DEPLOYED
-  Updated: Mon Mar 11 06:55:05 2019
-- AppVersion: "1.0"
-  Chart: appmgr-0.0.1
-  Name: appmgr
-  Namespace: default
-  Revision: 1
-  Status: DEPLOYED
-  Updated: Sun Mar 24 07:17:00 2019`
-
-var 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 (executable)
index c8db1e6..0000000
+++ /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 (executable)
index 065eddb..0000000
+++ /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 (executable)
index 930d008..0000000
+++ /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
-)