Swagger-baser server REST API interface
Change-Id: Ic2057a93bd9dfbb19c3b00cc497a4243120c6662
Signed-off-by: Mohamed Abukar <abukar.mohamed@nokia.com>
diff --git a/Dockerfile b/Dockerfile
old mode 100644
new mode 100755
index adc6a04..027e372
--- a/Dockerfile
+++ b/Dockerfile
@@ -32,23 +32,33 @@
# Install kubectl from Docker Hub
COPY --from=lachlanevenson/k8s-kubectl:v1.10.3 /usr/local/bin/kubectl /usr/local/bin/kubectl
-RUN mkdir -p /ws
-WORKDIR "/ws"
ENV GOPATH="/go"
+# Swagger
+RUN mkdir -p /go/bin
+RUN cd /go/bin \
+ && wget --quiet https://github.com/go-swagger/go-swagger/releases/download/v0.19.0/swagger_linux_amd64 \
+ && mv swagger_linux_amd64 swagger \
+ && chmod +x swagger
+
+RUN mkdir -p /go/src/ws
+WORKDIR "/go/src/ws"
+
# Module prepare (if go.mod/go.sum updated)
-COPY go.mod /ws
-COPY go.sum /ws
+COPY go.mod /go/src/ws
+COPY go.sum /go/src/ws
RUN GO111MODULE=on go mod download
# build and test
-COPY . /ws
+COPY . /go/src/ws
-RUN make -C /ws go-build
+RUN /go/bin/swagger generate server -f api/appmgr_rest_api.yaml --name AppManager -t pkg/ --exclude-main
-RUN make -C /ws go-test-fmt
+COPY . /go/src/ws
-#RUN make -C /ws go-test
+RUN make -C /go/src/ws go-build
+
+RUN make -C /go/src/ws go-test-fmt
CMD ["/bin/bash"]
@@ -70,12 +80,12 @@
RUN ldconfig
#
-# xApp
+# xApp Manager
#
RUN mkdir -p /opt/xAppManager \
&& chmod -R 755 /opt/xAppManager
-COPY --from=appmgr-build /ws/cache/go/cmd/appmgr /opt/xAppManager/appmgr
+COPY --from=appmgr-build /go/src/ws/cache/go/cmd/appmgr /opt/xAppManager/appmgr
WORKDIR /opt/xAppManager
diff --git a/api/appmgr_rest_api.json b/api/appmgr_rest_api.json
deleted file mode 100644
index 2b08195..0000000
--- a/api/appmgr_rest_api.json
+++ /dev/null
@@ -1,840 +0,0 @@
-{
- "swagger": "2.0",
- "info": {
- "description": "This is a draft API for RIC appmgr",
- "version": "0.1.7",
- "title": "RIC appmgr",
- "license": {
- "name": "Apache 2.0",
- "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
- }
- },
- "host": "hostname",
- "basePath": "/ric/v1",
- "schemes": [
- "http"
- ],
- "paths": {
- "/health/alive": {
- "get": {
- "summary": "Health check of xApp Manager - Liveness probe",
- "tags": [
- "health"
- ],
- "operationId": "getHealthAlive",
- "responses": {
- "200": {
- "description": "Status of xApp Manager is ok"
- }
- }
- }
- },
- "/health/ready": {
- "get": {
- "summary": "Readiness check of xApp Manager - Readiness probe",
- "tags": [
- "health"
- ],
- "operationId": "getHealthReady",
- "responses": {
- "200": {
- "description": "xApp Manager is ready for service"
- },
- "503": {
- "description": "xApp Manager is not ready for service"
- }
- }
- }
- },
- "/xapps": {
- "post": {
- "summary": "Deploy a xapp",
- "tags": [
- "xapp"
- ],
- "operationId": "deployXapp",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "xAppInfo",
- "in": "body",
- "description": "xApp information",
- "schema": {
- "type": "object",
- "required": [
- "name"
- ],
- "properties": {
- "name": {
- "type": "string",
- "description": "Name of the xApp.",
- "example": "xapp-dummy"
- },
- "configName": {
- "type": "string",
- "description": "Name of the xApp configmap. Overrides the value given in Helm chart value file.",
- "example": "xapp-dummy-configmap"
- },
- "namespace": {
- "type": "string",
- "description": "Name of the namespace to which xApp is deployed. Overrides the value given in Helm chart value file.",
- "example": "ricxapps"
- },
- "serviceName": {
- "type": "string",
- "description": "Name of the service xApp is providing. Overrides the value given in Helm chart value file.",
- "example": "xapp-dummy-service"
- },
- "imageRepo": {
- "type": "string",
- "description": "Name of the docker repository xApp is located. Overrides the value given in Helm chart value file.",
- "example": "xapprepo"
- },
- "hostname": {
- "type": "string",
- "description": "Hostname for the pod. Used by messaging library. Overrides the value given in Helm chart value file.",
- "example": "xapp-dummy"
- }
- }
- }
- }
- ],
- "responses": {
- "201": {
- "description": "xApp successfully created",
- "schema": {
- "$ref": "#/definitions/Xapp"
- }
- },
- "400": {
- "description": "Invalid input"
- },
- "500": {
- "description": "Internal error"
- }
- }
- },
- "get": {
- "summary": "Returns the status of all xapps",
- "tags": [
- "xapp"
- ],
- "operationId": "getAllXapps",
- "produces": [
- "application/json"
- ],
- "responses": {
- "200": {
- "description": "successful query of xApps",
- "schema": {
- "$ref": "#/definitions/AllDeployedXapps"
- }
- },
- "500": {
- "description": "Internal error"
- }
- }
- }
- },
- "/xapps/search": {
- "get": {
- "summary": "Returns the list of all deployable xapps",
- "tags": [
- "xapp"
- ],
- "operationId": "listAllDeployableXapps",
- "produces": [
- "application/json"
- ],
- "responses": {
- "200": {
- "description": "successful list of deployable xApps",
- "schema": {
- "$ref": "#/definitions/AllDeployableXapps"
- }
- },
- "500": {
- "description": "Internal error"
- }
- }
- }
- },
- "/xapps/{xAppName}": {
- "get": {
- "summary": "Returns the status of a given xapp",
- "tags": [
- "xapp"
- ],
- "operationId": "getXappByName",
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "xAppName",
- "in": "path",
- "description": "Name of xApp",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "200": {
- "description": "successful operation",
- "schema": {
- "$ref": "#/definitions/Xapp"
- }
- },
- "400": {
- "description": "Invalid ID supplied"
- },
- "404": {
- "description": "Xapp not found"
- },
- "500": {
- "description": "Internal error"
- }
- }
- },
- "delete": {
- "summary": "Undeploy an existing xapp",
- "tags": [
- "xapp"
- ],
- "operationId": "undeployXapp",
- "parameters": [
- {
- "name": "xAppName",
- "in": "path",
- "description": "Xapp to be undeployed",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "204": {
- "description": "Successful deletion of xApp"
- },
- "400": {
- "description": "Invalid xApp name supplied"
- },
- "500": {
- "description": "Internal error"
- }
- }
- }
- },
- "/xapps/{xAppName}/instances/{xAppInstanceName}": {
- "get": {
- "summary": "Returns the status of a given xapp",
- "tags": [
- "xapp"
- ],
- "operationId": "getXappInstanceByName",
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "xAppName",
- "in": "path",
- "description": "Name of xApp",
- "required": true,
- "type": "string"
- },
- {
- "name": "xAppInstanceName",
- "in": "path",
- "description": "Name of xApp instance to get information",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "200": {
- "description": "successful operation",
- "schema": {
- "$ref": "#/definitions/XappInstance"
- }
- },
- "400": {
- "description": "Invalid name supplied"
- },
- "404": {
- "description": "Xapp not found"
- },
- "500": {
- "description": "Internal error"
- }
- }
- }
- },
- "/config": {
- "post": {
- "summary": "Create xApp config",
- "tags": [
- "xapp"
- ],
- "operationId": "createXappConfig",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "XAppConfig",
- "in": "body",
- "description": "xApp config",
- "schema": {
- "$ref": "#/definitions/XAppConfig"
- }
- }
- ],
- "responses": {
- "201": {
- "description": "xApp config successfully created",
- "schema": {
- "$ref": "#/definitions/XAppConfig"
- }
- },
- "400": {
- "description": "Invalid input"
- },
- "422": {
- "description": "Validation of configuration failed"
- },
- "500": {
- "description": "Internal error"
- }
- }
- },
- "put": {
- "summary": "Modify xApp config",
- "tags": [
- "xapp"
- ],
- "operationId": "ModifyXappConfig",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "XAppConfig",
- "in": "body",
- "description": "xApp config",
- "schema": {
- "$ref": "#/definitions/XAppConfig"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "xApp config successfully modified",
- "schema": {
- "$ref": "#/definitions/XAppConfig"
- }
- },
- "400": {
- "description": "Invalid input"
- },
- "422": {
- "description": "Validation of configuration failed"
- },
- "500": {
- "description": "Internal error"
- }
- }
- },
- "get": {
- "summary": "Returns the configuration of all xapps",
- "tags": [
- "xapp"
- ],
- "operationId": "getAllXappConfig",
- "produces": [
- "application/json"
- ],
- "responses": {
- "200": {
- "description": "successful query of xApp config",
- "schema": {
- "$ref": "#/definitions/AllXappConfig"
- }
- },
- "500": {
- "description": "Internal error"
- }
- }
- },
- "delete": {
- "summary": "Delete xApp configuration",
- "tags": [
- "xapp"
- ],
- "operationId": "deleteXappConfig",
- "parameters": [
- {
- "name": "ConfigMetadata",
- "in": "body",
- "description": "xApp configuration information",
- "schema": {
- "$ref": "#/definitions/ConfigMetadata"
- }
- }
- ],
- "responses": {
- "204": {
- "description": "Successful deletion of xApp config"
- },
- "400": {
- "description": "Invalid parameters supplied"
- },
- "500": {
- "description": "Internal error"
- }
- }
- }
- },
- "/subscriptions": {
- "post": {
- "summary": "Subscribe event",
- "tags": [
- "xapp",
- "subscriptions"
- ],
- "operationId": "addSubscription",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "subscriptionRequest",
- "in": "body",
- "description": "New subscription",
- "required": true,
- "schema": {
- "$ref": "#/definitions/subscriptionRequest"
- }
- }
- ],
- "responses": {
- "201": {
- "description": "Subscription successfully created",
- "schema": {
- "$ref": "#/definitions/subscriptionResponse"
- }
- },
- "400": {
- "description": "Invalid input"
- }
- }
- },
- "get": {
- "summary": "Returns all subscriptions",
- "tags": [
- "xapp",
- "subscriptions"
- ],
- "operationId": "getSubscriptions",
- "produces": [
- "application/json"
- ],
- "responses": {
- "200": {
- "description": "successful query of subscriptions",
- "schema": {
- "$ref": "#/definitions/allSubscriptions"
- }
- }
- }
- }
- },
- "/subscriptions/{subscriptionId}": {
- "get": {
- "summary": "Returns the information of subscription",
- "tags": [
- "xapp",
- "subscriptions"
- ],
- "operationId": "getSubscriptionById",
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "subscriptionId",
- "in": "path",
- "description": "ID of subscription",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "200": {
- "description": "successful operation",
- "schema": {
- "$ref": "#/definitions/subscription"
- }
- },
- "400": {
- "description": "Invalid ID supplied"
- },
- "404": {
- "description": "Subscription not found"
- }
- }
- },
- "put": {
- "summary": "Modify event subscription",
- "tags": [
- "xapp",
- "subscriptions"
- ],
- "operationId": "modifySubscription",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [
- {
- "name": "subscriptionId",
- "in": "path",
- "description": "ID of subscription",
- "required": true,
- "type": "string"
- },
- {
- "in": "body",
- "name": "subscriptionRequest",
- "description": "Modified subscription",
- "required": true,
- "schema": {
- "$ref": "#/definitions/subscriptionRequest"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "Subscription modification successful",
- "schema": {
- "$ref": "#/definitions/subscriptionResponse"
- }
- },
- "400": {
- "description": "Invalid input"
- }
- }
- },
- "delete": {
- "summary": "Unsubscribe event",
- "tags": [
- "xapp",
- "subscriptions"
- ],
- "description": "",
- "operationId": "deleteSubscription",
- "parameters": [
- {
- "name": "subscriptionId",
- "in": "path",
- "description": "ID of subscription",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "204": {
- "description": "Successful deletion of subscription"
- },
- "400": {
- "description": "Invalid subscription supplied"
- }
- }
- }
- }
- },
- "definitions": {
- "AllDeployableXapps": {
- "type": "array",
- "items": {
- "type": "string",
- "example": "xapp-dummy"
- }
- },
- "AllDeployedXapps": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/Xapp"
- }
- },
- "Xapp": {
- "type": "object",
- "required": [
- "name"
- ],
- "properties": {
- "name": {
- "type": "string",
- "example": "xapp-dummy"
- },
- "status": {
- "type": "string",
- "description": "xapp status in the RIC",
- "enum": [
- "unknown",
- "deployed",
- "deleted",
- "superseded",
- "failed",
- "deleting"
- ]
- },
- "version": {
- "type": "string",
- "example": "1.2.3"
- },
- "instances": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/XappInstance"
- }
- }
- }
- },
- "XappInstance": {
- "type": "object",
- "required": [
- "name"
- ],
- "properties": {
- "name": {
- "type": "string",
- "example": "xapp-dummy-6cd577d9-4v255"
- },
- "status": {
- "type": "string",
- "description": "xapp instance status",
- "enum": [
- "pending",
- "running",
- "succeeded",
- "failed",
- "unknown",
- "completed",
- "crashLoopBackOff"
- ]
- },
- "ip": {
- "type": "string",
- "example": "192.168.0.1"
- },
- "port": {
- "type": "integer",
- "example": 32300
- },
- "txMessages": {
- "type": "array",
- "items": {
- "type": "string",
- "example": "ControlIndication"
- }
- },
- "rxMessages": {
- "type": "array",
- "items": {
- "type": "string",
- "example": "LoadIndication"
- }
- }
- }
- },
- "ConfigMetadata": {
- "type": "object",
- "required": [
- "name",
- "configName",
- "namespace"
- ],
- "properties": {
- "name": {
- "type": "string",
- "description": "Name of the xApp",
- "example": "xapp-dummy"
- },
- "configName": {
- "type": "string",
- "description": "Name of the config map",
- "example": "xapp-dummy-config-map"
- },
- "namespace": {
- "type": "string",
- "description": "Name of the namespace",
- "example": "ricxapp"
- }
- }
- },
- "XAppConfig": {
- "type": "object",
- "required": [
- "metadata",
- "descriptor",
- "config"
- ],
- "properties": {
- "metadata": {
- "$ref": "#/definitions/ConfigMetadata"
- },
- "descriptor": {
- "type": "object",
- "description": "Schema of configuration in JSON format"
- },
- "config": {
- "type": "object",
- "description": "Configuration in JSON format"
- }
- }
- },
- "AllXappConfig": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/XAppConfig"
- }
- },
- "subscriptionRequest": {
- "type": "object",
- "required": [
- "targetUrl",
- "eventType",
- "maxRetries",
- "retryTimer"
- ],
- "properties": {
- "targetUrl": {
- "type": "string",
- "example": "http://localhost:11111/apps/webhook/"
- },
- "eventType": {
- "type": "string",
- "description": "Event which is subscribed",
- "enum": [
- "created",
- "deleted",
- "all"
- ]
- },
- "maxRetries": {
- "type": "integer",
- "description": "Maximum number of retries",
- "example": 11
- },
- "retryTimer": {
- "type": "integer",
- "description": "Time in seconds to wait before next retry",
- "example": 22
- }
- }
- },
- "subscriptionResponse": {
- "type": "object",
- "properties": {
- "id": {
- "type": "string",
- "example": "1ILBltYYzEGzWRrVPZKmuUmhwcc"
- },
- "version": {
- "type": "integer",
- "example": 2
- },
- "eventType": {
- "type": "string",
- "description": "Event which is subscribed",
- "enum": [
- "created",
- "deleted",
- "updated",
- "all"
- ]
- }
- }
- },
- "allSubscriptions": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/subscription"
- }
- },
- "subscription": {
- "type": "object",
- "properties": {
- "id": {
- "type": "string",
- "example": "1ILBltYYzEGzWRrVPZKmuUmhwcc"
- },
- "targetUrl": {
- "type": "string",
- "example": "http://localhost:11111/apps/webhook/"
- },
- "eventType": {
- "type": "string",
- "description": "Event which is subscribed",
- "enum": [
- "created",
- "deleted",
- "updated",
- "all"
- ]
- },
- "maxRetries": {
- "type": "integer",
- "description": "Maximum number of retries",
- "example": 11
- },
- "retryTimer": {
- "type": "integer",
- "description": "Time in seconds to wait before next retry",
- "example": 22
- }
- }
- },
- "subscriptionNotification": {
- "type": "object",
- "properties": {
- "id": {
- "type": "string",
- "example": "1ILBltYYzEGzWRrVPZKmuUmhwcc"
- },
- "version": {
- "type": "integer",
- "example": 2
- },
- "eventType": {
- "type": "string",
- "description": "Event to be notified",
- "enum": [
- "created",
- "deleted",
- "updated"
- ]
- },
- "xApps": {
- "$ref": "#/definitions/AllDeployedXapps"
- }
- }
- }
- }
-}
diff --git a/api/appmgr_rest_api.yaml b/api/appmgr_rest_api.yaml
index 9e7c15b..04f4f0a 100644
--- a/api/appmgr_rest_api.yaml
+++ b/api/appmgr_rest_api.yaml
@@ -1,7 +1,7 @@
swagger: '2.0'
info:
description: This is a draft API for RIC appmgr
- version: 0.1.7
+ version: 0.2.0
title: RIC appmgr
license:
name: Apache 2.0
@@ -42,38 +42,11 @@
produces:
- application/json
parameters:
- - name: xAppInfo
+ - name: XappDescriptor
in: body
- description: xApp information
+ description: xApp deployment info
schema:
- type: object
- required:
- - name
- properties:
- name:
- type: string
- description: Name of the xApp.
- example: xapp-dummy
- configName:
- type: string
- description: Name of the xApp configmap. Overrides the value given in Helm chart value file.
- example: xapp-dummy-configmap
- namespace:
- type: string
- description: Name of the namespace to which xApp is deployed. Overrides the value given in Helm chart value file.
- example: ricxapps
- serviceName:
- type: string
- description: Name of the service xApp is providing. Overrides the value given in Helm chart value file.
- example: xapp-dummy-service
- imageRepo:
- type: string
- description: Name of the docker repository xApp is located. Overrides the value given in Helm chart value file.
- example: xapprepo
- hostname:
- type: string
- description: Hostname for the pod. Used by messaging library. Overrides the value given in Helm chart value file.
- example: xapp-dummy
+ $ref: '#/definitions/XappDescriptor'
responses:
'201':
description: xApp successfully created
@@ -97,12 +70,12 @@
$ref: '#/definitions/AllDeployedXapps'
'500':
description: Internal error
- '/xapps/search':
+ /xapps/list:
get:
summary: Returns the list of all deployable xapps
tags:
- xapp
- operationId: listAllDeployableXapps
+ operationId: listAllXapps
produces:
- application/json
responses:
@@ -112,7 +85,7 @@
$ref: '#/definitions/AllDeployableXapps'
'500':
description: Internal error
- '/xapps/{xAppName}':
+ /xapps/{xAppName}:
get:
summary: Returns the status of a given xapp
tags:
@@ -155,7 +128,7 @@
description: Invalid xApp name supplied
'500':
description: Internal error
- '/xapps/{xAppName}/instances/{xAppInstanceName}':
+ /xapps/{xAppName}/instances/{xAppInstanceName}:
get:
summary: Returns the status of a given xapp
tags:
@@ -185,6 +158,62 @@
description: Xapp not found
'500':
description: Internal error
+ /xapps/{xAppName}/instances/{xAppInstanceName}/start:
+ put:
+ summary: Start given xapp instance
+ tags:
+ - xapp
+ operationId: startXappInstanceByName
+ produces:
+ - application/json
+ parameters:
+ - name: xAppName
+ in: path
+ description: Name of xApp
+ required: true
+ type: string
+ - name: xAppInstanceName
+ in: path
+ description: Name of xApp instance to get information
+ required: true
+ type: string
+ responses:
+ '200':
+ description: successful operation
+ '400':
+ description: Invalid name supplied
+ '404':
+ description: Xapp not found
+ '500':
+ description: Internal error
+ /xapps/{xAppName}/instances/{xAppInstanceName}/stop:
+ put:
+ summary: Stop given xapp instance
+ tags:
+ - xapp
+ operationId: stopXappInstanceByName
+ produces:
+ - application/json
+ parameters:
+ - name: xAppName
+ in: path
+ description: Name of xApp
+ required: true
+ type: string
+ - name: xAppInstanceName
+ in: path
+ description: Name of xApp instance to get information
+ required: true
+ type: string
+ responses:
+ '200':
+ description: successful operation
+ '400':
+ description: Invalid name supplied
+ '404':
+ description: Xapp not found
+ '500':
+ description: Internal error
/config:
post:
summary: Create xApp config
@@ -205,7 +234,7 @@
'201':
description: xApp config successfully created
schema:
- $ref: '#/definitions/XAppConfig'
+ $ref: '#/definitions/ConfigValidationErrors'
'400':
description: Invalid input
'422':
@@ -231,7 +260,7 @@
'200':
description: xApp config successfully modified
schema:
- $ref: '#/definitions/XAppConfig'
+ $ref: '#/definitions/ConfigValidationErrors'
'400':
description: Invalid input
'422':
@@ -270,6 +299,27 @@
description: Invalid parameters supplied
'500':
description: Internal error
+ /config/{configName}:
+ get:
+ summary: Returns the configuration of a single xapp
+ tags:
+ - xapp
+ operationId: getXappConfig
+ produces:
+ - application/json
+ parameters:
+ - name: configName
+ in: path
+ description: Name of xApp
+ required: true
+ type: string
+ responses:
+ '200':
+ description: successful query of xApp config
+ schema:
+ $ref: '#/definitions/XAppConfig'
+ '500':
+ description: Internal error
/subscriptions:
post:
summary: Subscribe event
@@ -308,7 +358,7 @@
description: successful query of subscriptions
schema:
$ref: '#/definitions/allSubscriptions'
- '/subscriptions/{subscriptionId}':
+ /subscriptions/{subscriptionId}:
get:
summary: Returns the information of subscription
tags:
@@ -384,7 +434,6 @@
type: array
items:
type: string
- example: "xapp-dummy"
AllDeployedXapps:
type: array
items:
@@ -396,7 +445,6 @@
properties:
name:
type: string
- example: xapp-dummy
status:
type: string
description: xapp status in the RIC
@@ -409,7 +457,6 @@
- deleting
version:
type: string
- example: 1.2.3
instances:
type: array
items:
@@ -421,7 +468,6 @@
properties:
name:
type: string
- example: xapp-dummy-6cd577d9-4v255
status:
type: string
description: xapp instance status
@@ -435,39 +481,70 @@
- crashLoopBackOff
ip:
type: string
- example: 192.168.0.1
port:
type: integer
- example: 32300
txMessages:
type: array
items:
type: string
- example: ControlIndication
rxMessages:
type: array
items:
type: string
- example: LoadIndication
+ XappDescriptor:
+ type: object
+ required:
+ - xappName
+ properties:
+ xappName:
+ type: string
+ description: Name of the xApp in helm chart
+ helmVersion:
+ type: string
+ description: The exact xapp helm chart version to install
+ releaseName:
+ type: string
+ description: Name of the xapp to be visible in Kubernetes
+ namespace:
+ type: string
+ description: Name of the namespace to which xApp is deployed. Overrides the value given in Helm chart value file.
+ overrideFile:
+ type: object
+ description: JSON string of override file for 'helm install' command
+ XappDescriptorList:
+ type: array
+ items:
+ $ref: '#/definitions/XappDescriptor'
ConfigMetadata:
type: object
required:
- name
- - configName
- - namespace
properties:
name:
type: string
description: Name of the xApp
- example: xapp-dummy
configName:
type: string
description: Name of the config map
- example: xapp-dummy-config-map
namespace:
type: string
description: Name of the namespace
- example: ricxapp
+ ConfigValidationError:
+ type: object
+ required:
+ - field
+ - error
+ properties:
+ field:
+ type: string
+ description: Name of the parameter
+ error:
+ type: string
+ description: Description of validation error
+ ConfigValidationErrors:
+ type: array
+ items:
+ $ref: '#/definitions/ConfigValidationError'
XAppConfig:
type: object
required:
@@ -487,7 +564,18 @@
type: array
items:
$ref: '#/definitions/XAppConfig'
- subscriptionRequest:
+ EventType:
+ type: string
+ description: Event which is subscribed
+ enum:
+ - deployed
+ - undeployed
+ - created
+ - modified
+ - deleted
+ - restarted
+ - all
+ SubscriptionData:
type: object
required:
- targetUrl
@@ -499,37 +587,29 @@
type: string
example: 'http://localhost:11111/apps/webhook/'
eventType:
- type: string
- description: Event which is subscribed
- enum:
- - created
- - deleted
- - all
+ $ref: '#/definitions/EventType'
maxRetries:
type: integer
description: Maximum number of retries
- example: 11
retryTimer:
type: integer
description: Time in seconds to wait before next retry
- example: 22
+ subscriptionRequest:
+ type: object
+ required:
+ - data
+ properties:
+ data:
+ $ref: '#/definitions/SubscriptionData'
subscriptionResponse:
type: object
properties:
id:
type: string
- example: 1ILBltYYzEGzWRrVPZKmuUmhwcc
version:
type: integer
- example: 2
eventType:
- type: string
- description: Event which is subscribed
- enum:
- - created
- - deleted
- - updated
- - all
+ $ref: '#/definitions/EventType'
allSubscriptions:
type: array
items:
@@ -539,41 +619,16 @@
properties:
id:
type: string
- example: 1ILBltYYzEGzWRrVPZKmuUmhwcc
- targetUrl:
- type: string
- example: 'http://localhost:11111/apps/webhook/'
- eventType:
- type: string
- description: Event which is subscribed
- enum:
- - created
- - deleted
- - updated
- - all
- maxRetries:
- type: integer
- description: Maximum number of retries
- example: 11
- retryTimer:
- type: integer
- description: Time in seconds to wait before next retry
- example: 22
+ data:
+ $ref: '#/definitions/SubscriptionData'
subscriptionNotification:
type: object
properties:
id:
type: string
- example: 1ILBltYYzEGzWRrVPZKmuUmhwcc
version:
type: integer
- example: 2
eventType:
- type: string
- description: Event to be notified
- enum:
- - created
- - deleted
- - updated
+ $ref: '#/definitions/EventType'
xApps:
$ref: '#/definitions/AllDeployedXapps'
diff --git a/build/make.go.mk b/build/make.go.mk
old mode 100644
new mode 100755
index 286d9b1..2208487
--- a/build/make.go.mk
+++ b/build/make.go.mk
@@ -58,13 +58,13 @@
.SECONDEXPANSION:
$(GO_CACHE_DIR)/%: $(GOFILES) $(GOMODFILES) $$(BUILDDEPS)
@echo "Building:\t$*"
- GO111MODULE=on GO_ENABLED=0 GOOS=linux $(GOBUILD) -o $@ ./$*
+ GO111MODULE=on GO_ENABLED=0 GOOS=linux $(GOBUILD) -o $@ cmd/appmgr.go
.SECONDEXPANSION:
$(GO_CACHE_DIR)/%_test: $(GOALLFILES) $(GOMODFILES) $$(BUILDDEPS) FORCE
@echo "Testing:\t$*"
- GO111MODULE=on GO_ENABLED=0 GOOS=linux $(GOTEST) -coverprofile $(COVEROUT) -c -o $@ ./$*
+ GO111MODULE=on GO_ENABLED=0 GOOS=linux $(GOTEST) -coverprofile $(COVEROUT) ./pkg/resthooks/ ./pkg/helm/ ./pkg/cm/
test -e $@ && (eval $(TESTENV) $@ -test.coverprofile $(COVEROUT) || false) || true
test -e $@ && (go tool cover -html=$(COVEROUT) -o $(COVERHTML) || false) || true
diff --git a/cmd/appmgr/main.go b/cmd/appmgr.go
similarity index 82%
rename from cmd/appmgr/main.go
rename to cmd/appmgr.go
index 8788405..b658367 100755
--- a/cmd/appmgr/main.go
+++ b/cmd/appmgr.go
@@ -19,15 +19,12 @@
package main
-var Logger *Log
+import (
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/restful"
+)
func main() {
- Logger = NewLogger("xapp-manager")
- Logger.SetMdc("appmgr", "0.1.9")
- loadConfig()
-
- m := XappManager{}
- m.Initialize(&Helm{}, &ConfigMap{})
-
- m.Run()
+ appmgr.Init()
+ restful.NewRestful().Run()
}
diff --git a/cmd/appmgr/api_test.go b/cmd/appmgr/api_test.go
deleted file mode 100755
index 248ffee..0000000
--- a/cmd/appmgr/api_test.go
+++ /dev/null
@@ -1,313 +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"
- "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 (h *MockedHelmer) SetCM(cm ConfigMapper) {
-}
-
-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) SearchAll() (s []string) {
- return s
-}
-
-func (h *MockedHelmer) List() (names []string, err error) {
- return names, helmError
-}
-
-func (h *MockedHelmer) Install(m XappDeploy) (Xapp, error) {
- return xapp, helmError
-}
-
-func (h *MockedHelmer) Delete(name string) (Xapp, error) {
- return xapp, helmError
-}
-
-// Test cases
-func TestMain(m *testing.M) {
- Logger = NewLogger("xapp-manager")
- loadConfig()
-
- xapp = Xapp{}
- xapps = []Xapp{}
-
- cm := MockedConfigMapper{}
- h := MockedHelmer{}
- x = XappManager{}
- x.Initialize(&h, &cm)
-
- // 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)
-}
-
-func TestGetConfigReturnsEmpty(t *testing.T) {
- req, _ := http.NewRequest("GET", "/ric/v1/config", nil)
- response := executeRequest(req)
-
- checkResponseCode(t, http.StatusOK, response.Code)
-}
-
-func TestCreateConfigFailsWithMethodNotAllowed(t *testing.T) {
- req, _ := http.NewRequest("POST", "/ric/v1/config", nil)
- response := executeRequest(req)
-
- checkResponseCode(t, http.StatusMethodNotAllowed, response.Code)
-}
-
-func TestCreateConfigOk(t *testing.T) {
- payload := []byte(`{"name":"dummy-xapp"}`)
- req, _ := http.NewRequest("POST", "/ric/v1/config", bytes.NewBuffer(payload))
- response := executeRequest(req)
-
- checkResponseCode(t, http.StatusCreated, response.Code)
-}
-
-func TestDeleteConfigOk(t *testing.T) {
- payload := []byte(`{"name":"dummy-xapp"}`)
- req, _ := http.NewRequest("DELETE", "/ric/v1/config", bytes.NewBuffer(payload))
- response := executeRequest(req)
-
- checkResponseCode(t, http.StatusNoContent, 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)
- var msgs MessageTypes
-
- instance := XappInstance{
- Name: iname,
- Status: istatus,
- Ip: ip,
- Port: p,
- TxMessages: msgs.TxMessages,
- RxMessages: msgs.RxMessages,
- }
- x.Instances = append(x.Instances, instance)
-
- return
-}
diff --git a/cmd/appmgr/db.go b/cmd/appmgr/db.go
deleted file mode 100755
index 97865c2..0000000
--- a/cmd/appmgr/db.go
+++ /dev/null
@@ -1,91 +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"
- 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.NewSdlInstance(ns, sdl.NewDatabase())
-
- // Test DB connection, and wait until ready!
- for {
- if _, err := d.session.GetAll(); err == nil {
- return
- }
- Logger.Error("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 {
- Logger.Error("json.marshal failed: %v ", err.Error())
- return
- }
-
- if err := d.session.Set(s.req.Id, data); err != nil {
- Logger.Error("DB.session.Set failed: %v ", err.Error())
- }
- }
-}
-
-func (d *DB) RestoreSubscriptions() (m cmap.ConcurrentMap) {
- m = cmap.New()
-
- keys, err := d.session.GetAll()
- if err != nil {
- Logger.Error("DB.session.GetAll failed: %v ", err.Error())
- return
- }
-
- for _, key := range keys {
- value, err := d.session.Get([]string{key})
- if err != nil {
- Logger.Error("DB.session.Get failed: %v ", err.Error())
- return
- }
-
- var item SubscriptionReq
- if err = json.Unmarshal([]byte(value[key].(string)), &item); err != nil {
- Logger.Error("json.Unmarshal failed: %v ", err.Error())
- return
- }
-
- resp := SubscriptionResp{key, 0, item.EventType}
- m.Set(key, Subscription{item, resp})
- }
-
- return m
-}
diff --git a/cmd/appmgr/desc.go b/cmd/appmgr/desc.go
deleted file mode 100755
index 8cc39a5..0000000
--- a/cmd/appmgr/desc.go
+++ /dev/null
@@ -1,333 +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"
- "errors"
- "fmt"
- "github.com/spf13/viper"
- "github.com/valyala/fastjson"
- "github.com/xeipuuv/gojsonschema"
- "io/ioutil"
- "log"
- "os"
- "path"
- "regexp"
- "strings"
- "time"
-)
-
-type ConfigMetadata struct {
- Name string `json:"name"`
- ConfigName string `json:"configName, omitempty"`
- Namespace string `json:"namespace, omitempty"`
-}
-
-type XAppConfig struct {
- Metadata ConfigMetadata `json:"metadata"`
- Descriptor interface{} `json:"descriptor, omitempty"`
- Configuration interface{} `json:"config, omitempty"`
-}
-
-type ConfigMap struct {
- Kind string `json:"kind"`
- ApiVersion string `json:"apiVersion"`
- Data interface{} `json:"data"`
- Metadata CMMetadata `json:"metadata"`
-}
-
-type CMMetadata struct {
- Name string `json:"name"`
- Namespace string `json:"namespace"`
-}
-
-type CMError struct {
- Field string `json:"field"`
- Description string `json:"description"`
-}
-
-func (cm *ConfigMap) UploadConfig() (cfg []XAppConfig) {
- ns := cm.GetNamespace("")
- for _, name := range cm.GetNamesFromHelmRepo() {
- if name == "appmgr" {
- continue
- }
-
- c := XAppConfig{
- Metadata: ConfigMetadata{Name: name, Namespace: ns, ConfigName: cm.GetConfigMapName(name, ns)},
- }
-
- err := cm.ReadSchema(name, &c)
- if err != nil {
- continue
- }
-
- err = cm.ReadConfigMap(c.Metadata.ConfigName, ns, &c.Configuration)
- if err != nil {
- log.Println("No active configMap found, using default!")
- }
-
- cfg = append(cfg, c)
- }
- return
-}
-
-func (cm *ConfigMap) ReadSchema(name string, c *XAppConfig) (err error) {
- if err = cm.FetchChart(name); err != nil {
- return
- }
-
- tarDir := viper.GetString("xapp.tarDir")
- err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.schema")), &c.Descriptor)
- if err != nil {
- return
- }
-
- err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.config")), &c.Configuration)
- if err != nil {
- return
- }
-
- if err = os.RemoveAll(path.Join(tarDir, name)); err != nil {
- log.Println("RemoveAll failed", err)
- }
-
- return
-}
-
-func (cm *ConfigMap) ReadConfigMap(ConfigName string, ns string, c *interface{}) (err error) {
- args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, ConfigName)
- configMapJson, err := KubectlExec(args)
- if err != nil {
- return
- }
-
- err = json.Unmarshal([]byte(configMapJson), &c)
- if err != nil {
- return
- }
-
- return
-}
-
-func (cm *ConfigMap) ApplyConfigMap(r XAppConfig, action string) (err error) {
- c := ConfigMap{
- Kind: "ConfigMap",
- ApiVersion: "v1",
- Metadata: CMMetadata{Name: r.Metadata.Name, Namespace: r.Metadata.Namespace},
- Data: r.Configuration,
- }
-
- cmJson, err := json.Marshal(c.Data)
- if err != nil {
- log.Println("Config marshalling failed: ", err)
- return
- }
-
- cmFile := viper.GetString("xapp.tmpConfig")
- err = ioutil.WriteFile(cmFile, cmJson, 0644)
- if err != nil {
- log.Println("WriteFile failed: ", err)
- return
- }
-
- cmd := " create configmap -n %s %s --from-file=%s -o json --dry-run | kubectl %s -f -"
- args := fmt.Sprintf(cmd, r.Metadata.Namespace, r.Metadata.ConfigName, cmFile, action)
- _, err = KubectlExec(args)
- if err != nil {
- return
- }
- log.Println("Configmap changes done!")
-
- return
-}
-
-func (cm *ConfigMap) GetConfigMap(m XappDeploy, c *interface{}) (err error) {
- if m.ConfigName == "" {
- m.ConfigName = cm.GetConfigMapName(m.Name, m.Namespace)
- }
- return cm.ReadConfigMap(m.ConfigName, m.Namespace, c)
-}
-
-func (cm *ConfigMap) CreateConfigMap(r XAppConfig) (errList []CMError, err error) {
- if errList, err = cm.Validate(r); err != nil {
- return
- }
- err = cm.ApplyConfigMap(r, "create")
- return
-}
-
-func (cm *ConfigMap) UpdateConfigMap(r XAppConfig) (errList []CMError, err error) {
- if errList, err = cm.Validate(r); err != nil {
- return
- }
-
- // Re-create the configmap with the new parameters
- err = cm.ApplyConfigMap(r, "apply")
- return
-}
-
-func (cm *ConfigMap) DeleteConfigMap(r XAppConfig) (c interface{}, err error) {
- err = cm.ReadConfigMap(r.Metadata.ConfigName, r.Metadata.Namespace, &c)
- if err == nil {
- args := fmt.Sprintf(" delete configmap --namespace=%s %s", r.Metadata.Namespace, r.Metadata.ConfigName)
- _, err = KubectlExec(args)
- }
- return
-}
-
-func (cm *ConfigMap) PurgeConfigMap(m XappDeploy) (c interface{}, err error) {
- if m.ConfigName == "" {
- m.ConfigName = cm.GetConfigMapName(m.Name, m.Namespace)
- }
- md := ConfigMetadata{Name: m.Name, Namespace: m.Namespace, ConfigName: m.ConfigName}
-
- return cm.DeleteConfigMap(XAppConfig{Metadata: md})
-}
-
-func (cm *ConfigMap) RestoreConfigMap(m XappDeploy, c interface{}) (err error) {
- if m.ConfigName == "" {
- m.ConfigName = cm.GetConfigMapName(m.Name, m.Namespace)
- }
- md := ConfigMetadata{Name: m.Name, Namespace: m.Namespace, ConfigName: m.ConfigName}
- time.Sleep(time.Duration(10 * time.Second))
-
- return cm.ApplyConfigMap(XAppConfig{Metadata: md, Configuration: c}, "create")
-}
-
-func (cm *ConfigMap) GetNamesFromHelmRepo() (names []string) {
- rname := viper.GetString("helm.repo-name")
-
- cmdArgs := strings.Join([]string{"search ", rname}, "")
- out, err := HelmExec(cmdArgs)
- if err != nil {
- return
- }
-
- re := regexp.MustCompile(rname + `/.*`)
- result := re.FindAllStringSubmatch(string(out), -1)
- if result != nil {
- var tmp string
- for _, v := range result {
- fmt.Sscanf(v[0], "%s", &tmp)
- names = append(names, strings.Split(tmp, "/")[1])
- }
- }
- return names
-}
-
-func (cm *ConfigMap) Validate(req XAppConfig) (errList []CMError, err error) {
- c := XAppConfig{}
- err = cm.ReadSchema(req.Metadata.Name, &c)
- if err != nil {
- log.Printf("No schema file found for '%s', aborting ...", req.Metadata.Name)
- return
- }
- return cm.doValidate(c.Descriptor, req.Configuration)
-}
-
-func (cm *ConfigMap) doValidate(schema, cfg interface{}) (errList []CMError, err error) {
- schemaLoader := gojsonschema.NewGoLoader(schema)
- documentLoader := gojsonschema.NewGoLoader(cfg)
-
- result, err := gojsonschema.Validate(schemaLoader, documentLoader)
- if err != nil {
- log.Println("Validation failed: ", err)
- return
- }
-
- if result.Valid() == false {
- log.Println("The document is not valid, Errors: ", result.Errors())
- for _, desc := range result.Errors() {
- errList = append(errList, CMError{Field: desc.Field(), Description: desc.Description()})
- }
- return errList, errors.New("Validation failed!")
- }
- return
-}
-
-func (cm *ConfigMap) ReadFile(name string, data interface{}) (err error) {
- f, err := ioutil.ReadFile(name)
- if err != nil {
- log.Printf("Reading '%s' file failed: %v", name, err)
- return
- }
-
- err = json.Unmarshal(f, &data)
- if err != nil {
- log.Printf("Unmarshalling '%s' file failed: %v", name, err)
- return
- }
-
- return
-}
-
-func (cm *ConfigMap) FetchChart(name string) (err error) {
- tarDir := viper.GetString("xapp.tarDir")
- repo := viper.GetString("helm.repo-name")
- fetchArgs := fmt.Sprintf("--untar --untardir %s %s/%s", tarDir, repo, name)
-
- _, err = HelmExec(strings.Join([]string{"fetch ", fetchArgs}, ""))
- return
-}
-
-func (cm *ConfigMap) GetMessages(name string) (msgs MessageTypes) {
- log.Println("Fetching tx/rx messages for: ", name)
-
- ns := cm.GetNamespace("")
- args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, cm.GetConfigMapName(name, ns))
- out, err := KubectlExec(args)
- if err != nil {
- return
- }
-
- var p fastjson.Parser
- v, err := p.Parse(string(out))
- if err != nil {
- log.Printf("fastjson.Parser for '%s' failed: %v", name, err)
- return
- }
-
- for _, m := range v.GetArray("rmr", "txMessages") {
- msgs.TxMessages = append(msgs.TxMessages, strings.Trim(m.String(), `"`))
- }
- for _, m := range v.GetArray("rmr", "rxMessages") {
- msgs.RxMessages = append(msgs.RxMessages, strings.Trim(m.String(), `"`))
- }
-
- return
-}
-
-func (cm *ConfigMap) GetConfigMapName(xappName, namespace string) string {
- return " configmap-" + namespace + "-" + xappName + "-appconfig"
-}
-
-func (cm *ConfigMap) GetNamespace(ns string) string {
- if ns != "" {
- return ns
- }
-
- ns = viper.GetString("xapp.namespace")
- if ns == "" {
- ns = "ricxapp"
- }
- return ns
-}
diff --git a/cmd/appmgr/desc_test.go b/cmd/appmgr/desc_test.go
deleted file mode 100755
index 8f005a1..0000000
--- a/cmd/appmgr/desc_test.go
+++ /dev/null
@@ -1,322 +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"
- "errors"
- "log"
- "reflect"
- "testing"
-)
-
-var helmSearchOutput = `
-helm-repo/anr 0.0.1 1.0 Helm Chart for Nokia ANR (Automatic Neighbour Relation) xAPP
-helm-repo/appmgr 0.0.2 1.0 Helm Chart for xAppManager
-helm-repo/dualco 0.0.1 1.0 Helm Chart for Nokia dualco xAPP
-helm-repo/reporter 0.0.1 1.0 Helm Chart for Reporting xAPP
-helm-repo/uemgr 0.0.1 1.0 Helm Chart for Nokia uemgr xAPP
-`
-
-var kubectlConfigmapOutput = `
-{
- "local": {
- "host": ":8080"
- },
- "logger": {
- "level": 3
- },
- "rmr": {
- "protPort": "tcp:4560",
- "maxSize": 2072,
- "numWorkers": 1,
- "txMessages": ["RIC_X2_LOAD_INFORMATION"],
- "rxMessages": ["RIC_X2_LOAD_INFORMATION"]
- },
- "db": {
- "namespace": "ricxapp",
- "host": "dbaas",
- "port": 6379
- }
-}
-`
-
-type ConfigSample struct {
- Level int
- Host string
-}
-
-type MockedConfigMapper struct {
-}
-
-func (cm *MockedConfigMapper) ReadSchema(name string, c *XAppConfig) (err error) {
- return
-}
-
-func (cm *MockedConfigMapper) UploadConfig() (cfg []XAppConfig) {
- return
-}
-
-func (cm *MockedConfigMapper) CreateConfigMap(r XAppConfig) (errList []CMError, err error) {
- return
-}
-
-func (cm *MockedConfigMapper) GetConfigMap(m XappDeploy, c *interface{}) (err error) {
- return
-}
-
-func (cm *MockedConfigMapper) UpdateConfigMap(r XAppConfig) (errList []CMError, err error) {
- return
-}
-
-func (cm *MockedConfigMapper) DeleteConfigMap(r XAppConfig) (c interface{}, err error) {
- return
-}
-
-func (cm *MockedConfigMapper) PurgeConfigMap(m XappDeploy) (c interface{}, err error) {
- return
-}
-
-func (cm *MockedConfigMapper) RestoreConfigMap(m XappDeploy, c interface{}) (err error) {
- return
-}
-
-func (cm *MockedConfigMapper) ReadConfigMap(name string, ns string, c *interface{}) (err error) {
- return
-}
-
-func (cm *MockedConfigMapper) ApplyConfigMap(r XAppConfig, action string) (err error) {
- return
-}
-
-func (cm *MockedConfigMapper) FetchChart(name string) (err error) {
- return
-}
-
-func (cm *MockedConfigMapper) GetMessages(name string) (msgs MessageTypes) {
- return
-}
-
-func (cm *MockedConfigMapper) GetNamespace(ns string) (n string) {
- return
-}
-
-func (cm *MockedConfigMapper) GetNamesFromHelmRepo() (names []string) {
- return
-}
-
-// Test cases
-func TestGetMessages(t *testing.T) {
- cm := ConfigMap{}
- expectedMsgs := MessageTypes{
- TxMessages: []string{"RIC_X2_LOAD_INFORMATION"},
- RxMessages: []string{"RIC_X2_LOAD_INFORMATION"},
- }
-
- KubectlExec = func(args string) (out []byte, err error) {
- return []byte(kubectlConfigmapOutput), nil
- }
-
- result := cm.GetMessages("dummy-xapp")
- if !reflect.DeepEqual(result, expectedMsgs) {
- t.Errorf("TestGetMessages failed: expected: %v, got: %v", expectedMsgs, result)
- }
-}
-
-func TestHelmNamespace(t *testing.T) {
- cm := ConfigMap{}
-
- if cm.GetNamespace("pltxapp") != "pltxapp" {
- t.Errorf("getNamespace failed!")
- }
-
- if cm.GetNamespace("") != "default" {
- t.Errorf("getNamespace failed!")
- }
-}
-
-func TestFetchChartFails(t *testing.T) {
- cm := ConfigMap{}
-
- if cm.FetchChart("dummy-xapp") == nil {
- t.Errorf("TestFetchChart failed!")
- }
-}
-
-func TestFetchChartSuccess(t *testing.T) {
- cm := ConfigMap{}
-
- HelmExec = func(args string) (out []byte, err error) {
- return
- }
-
- if cm.FetchChart("dummy-xapp") != nil {
- t.Errorf("TestFetchChart failed!")
- }
-}
-
-func TestGetNamesFromHelmRepoSuccess(t *testing.T) {
- cm := ConfigMap{}
- expectedResult := []string{"anr", "appmgr", "dualco", "reporter", "uemgr"}
- HelmExec = func(args string) (out []byte, err error) {
- return []byte(helmSearchOutput), nil
- }
-
- names := cm.GetNamesFromHelmRepo()
- if !reflect.DeepEqual(names, expectedResult) {
- t.Errorf("GetNamesFromHelmRepo failed: expected %v, got %v", expectedResult, names)
- }
-}
-
-func TestGetNamesFromHelmRepoFailure(t *testing.T) {
- cm := ConfigMap{}
- expectedResult := []string{}
- HelmExec = func(args string) (out []byte, err error) {
- return []byte(helmSearchOutput), errors.New("Command failed!")
- }
-
- names := cm.GetNamesFromHelmRepo()
- if names != nil {
- t.Errorf("GetNamesFromHelmRepo failed: expected %v, got %v", expectedResult, names)
- }
-}
-
-func TestApplyConfigMapSuccess(t *testing.T) {
- cm := ConfigMap{}
- m := ConfigMetadata{Name: "dummy-xapp", Namespace: "ricxapp"}
- s := ConfigSample{5, "localhost"}
-
- KubectlExec = func(args string) (out []byte, err error) {
- log.Println("TestApplyConfigMapSuccess: ", args)
- return []byte(`{"logger": {"level": 2}}`), nil
- }
-
- err := cm.ApplyConfigMap(XAppConfig{Metadata: m, Configuration: s}, "create")
- if err != nil {
- t.Errorf("ApplyConfigMap failed: %v", err)
- }
-}
-
-func TestRestoreConfigMapSuccess(t *testing.T) {
- cm := ConfigMap{}
- m := XappDeploy{Name: "dummy-xapp", Namespace: "ricxapp"}
- s := ConfigSample{5, "localhost"}
-
- KubectlExec = func(args string) (out []byte, err error) {
- log.Println("TestRestoreConfigMapSuccess: ", args)
- return []byte(`{"logger": {"level": 2}}`), nil
- }
-
- err := cm.RestoreConfigMap(m, s)
- if err != nil {
- t.Errorf("RestoreConfigMap failed: %v", err)
- }
-}
-
-func TestDeleteConfigMapSuccess(t *testing.T) {
- cm := ConfigMap{}
-
- HelmExec = func(args string) (out []byte, err error) {
- return []byte("ok"), nil
- }
-
- KubectlExec = func(args string) (out []byte, err error) {
- log.Println("TestDeleteConfigMapSuccess: ", args)
- return []byte(`{"logger": {"level": 2}}`), nil
- }
-
- c, err := cm.DeleteConfigMap(XAppConfig{})
- if err != nil {
- t.Errorf("DeleteConfigMap failed: %v -> %v", err, c)
- }
-}
-
-func TestPurgeConfigMapSuccess(t *testing.T) {
- cm := ConfigMap{}
-
- HelmExec = func(args string) (out []byte, err error) {
- return []byte("ok"), nil
- }
-
- KubectlExec = func(args string) (out []byte, err error) {
- return []byte(`{"logger": {"level": 2}}`), nil
- }
-
- c, err := cm.PurgeConfigMap(XappDeploy{})
- if err != nil {
- t.Errorf("PurgeConfigMap failed: %v -> %v", err, c)
- }
-}
-
-func TestCreateConfigMapFails(t *testing.T) {
- cm := ConfigMap{}
-
- c, err := cm.CreateConfigMap(XAppConfig{})
- if err == nil {
- t.Errorf("CreateConfigMap failed: %v -> %v", err, c)
- }
-}
-
-func TestUpdateConfigMapFails(t *testing.T) {
- cm := ConfigMap{}
-
- c, err := cm.UpdateConfigMap(XAppConfig{})
- if err == nil {
- t.Errorf("CreateConfigMap failed: %v -> %v", err, c)
- }
-}
-
-func TestValidationSuccess(t *testing.T) {
- cm := ConfigMap{}
- var d interface{}
- var cfg map[string]interface{}
-
- err := json.Unmarshal([]byte(`{"local": {"host": ":8080"}, "logger": {"level": 3}}`), &cfg)
-
- err = cm.ReadFile("./test/schema.json", &d)
- if err != nil {
- t.Errorf("ReadFile failed: %v -> %v", err, d)
- }
-
- feedback, err := cm.doValidate(d, cfg)
- if err != nil {
- t.Errorf("doValidate failed: %v -> %v", err, feedback)
- }
-}
-
-func TestValidationFails(t *testing.T) {
- cm := ConfigMap{}
- var d interface{}
- var cfg map[string]interface{}
-
- err := json.Unmarshal([]byte(`{"local": {"host": ":8080"}, "logger": {"level": "INVALID"}}`), &cfg)
-
- err = cm.ReadFile("./test/schema.json", &d)
- if err != nil {
- t.Errorf("ConfigMetadata failed: %v -> %v", err, d)
- }
-
- feedback, err := cm.doValidate(d, cfg)
- if err == nil {
- t.Errorf("doValidate should faile but didn't: %v -> %v", err, feedback)
- }
-
- log.Println("Feedbacks: ", feedback)
-}
diff --git a/cmd/appmgr/helm.go b/cmd/appmgr/helm.go
deleted file mode 100755
index 8a8154a..0000000
--- a/cmd/appmgr/helm.go
+++ /dev/null
@@ -1,396 +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"
- "errors"
- "fmt"
- "github.com/spf13/viper"
- "io/ioutil"
- "log"
- "os"
- "os/exec"
- "regexp"
- "strconv"
- "strings"
- "time"
-)
-
-var execCommand = exec.Command
-
-func Exec(args string) (out []byte, err error) {
- cmd := execCommand("/bin/sh", "-c", args)
-
- var stdout bytes.Buffer
- var stderr bytes.Buffer
- cmd.Stdout = &stdout
- cmd.Stderr = &stderr
-
- log.Println("Running command: ", cmd)
- for i := 0; i < viper.GetInt("helm.retry"); i++ {
- err = cmd.Run()
- if err != nil {
- Logger.Error("Command '%s' failed with error: %v, retrying", args, err.Error()+stderr.String())
- time.Sleep(time.Duration(2) * time.Second)
- continue
- }
- break
- }
-
- if err == nil && !strings.HasSuffix(os.Args[0], ".test") {
- Logger.Info("command success: %s", stdout.String())
- return stdout.Bytes(), nil
- }
-
- return stdout.Bytes(), errors.New(stderr.String())
-}
-
-var HelmExec = func(args string) (out []byte, err error) {
- return Exec(strings.Join([]string{"helm", args}, " "))
-}
-
-var KubectlExec = func(args string) (out []byte, err error) {
- return Exec(strings.Join([]string{"kubectl", args}, " "))
-}
-
-func (h *Helm) SetCM(cm ConfigMapper) {
- h.cm = cm
-}
-
-func (h *Helm) Initialize() {
- if h.initDone == true {
- return
- }
-
- for {
- if _, err := h.Init(); err == nil {
- Logger.Info("Helm init done successfully!")
- break
- }
- Logger.Error("helm init failed, retyring ...")
- time.Sleep(time.Duration(10) * time.Second)
- }
-
- for {
- if _, err := h.AddRepo(); err == nil {
- Logger.Info("Helm repo added successfully")
- break
- }
- Logger.Error("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 HelmExec(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 HelmExec(strings.Join([]string{"init -c --skip-refresh"}, ""))
-}
-
-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 {
- Logger.Error("helm_repo_username ReadFile failed: %v", err.Error())
- return
- }
-
- username := " --username " + string(credFile)
-
- credFile, err = ioutil.ReadFile(viper.GetString("helm.helm-password-file"))
- if err != nil {
- Logger.Error("helm_repo_password ReadFile failed: %v", 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 HelmExec(strings.Join([]string{"repo add ", rname, " ", repo, username, pwd}, ""))
-}
-
-func (h *Helm) Install(m XappDeploy) (xapp Xapp, err error) {
- out, err := h.Run(strings.Join([]string{"repo update "}, ""))
- if err != nil {
- return
- }
-
- var cm interface{}
- m.Namespace = h.cm.GetNamespace(m.Namespace)
-
- if err = h.cm.GetConfigMap(m, &cm); err != nil {
- out, err = h.Run(getInstallArgs(m, false))
- if err != nil {
- return
- }
- return h.ParseStatus(m.Name, string(out))
- }
-
- // ConfigMap exists, try to override
- out, err = h.Run(getInstallArgs(m, true))
- if err == nil {
- return h.ParseStatus(m.Name, string(out))
- }
-
- cm, cmErr := h.cm.PurgeConfigMap(m)
- out, err = h.Run(getInstallArgs(m, false))
- if err != nil {
- return
- }
-
- if cmErr == nil {
- cmErr = h.cm.RestoreConfigMap(m, cm)
- }
- return h.ParseStatus(m.Name, string(out))
-}
-
-func (h *Helm) Status(name string) (xapp Xapp, err error) {
- out, err := h.Run(strings.Join([]string{"status ", name}, ""))
- if err != nil {
- Logger.Error("Getting xapps status: %v", err.Error())
- return
- }
-
- return h.ParseStatus(name, string(out))
-}
-
-func (h *Helm) StatusAll() (xapps []Xapp, err error) {
- xappNameList, err := h.List()
- if err != nil {
- Logger.Error("Helm list failed: %v", err.Error())
- return
- }
-
- return h.parseAllStatus(xappNameList)
-}
-
-func (h *Helm) List() (names []string, err error) {
- ns := h.cm.GetNamespace("")
- out, err := h.Run(strings.Join([]string{"list --all --output yaml --namespace=", ns}, ""))
- if err != nil {
- Logger.Error("Listing deployed xapps failed: %v", err.Error())
- return
- }
-
- return h.GetNames(string(out))
-}
-
-func (h *Helm) SearchAll() (names []string) {
- return h.cm.GetNamesFromHelmRepo()
-}
-
-func (h *Helm) Delete(name string) (xapp Xapp, err error) {
- xapp, err = h.Status(name)
- if err != nil {
- Logger.Error("Fetching xapp status failed: %v", 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) GetVersion(name string) (version string) {
- ns := h.cm.GetNamespace("")
- out, err := h.Run(strings.Join([]string{"list --output yaml --namespace=", ns, " ", name}, ""))
- 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) GetEndpointInfo(name string) (ip string, port int) {
- ns := h.cm.GetNamespace("")
- args := fmt.Sprintf(" get endpoints -o=jsonpath='{.subsets[*].addresses[*].ip}' service-%s-%s-rmr -n %s", ns, name, ns)
- out, err := KubectlExec(args)
- if err != nil {
- return
- }
- Logger.Info("Endpoint IP address of %s: %s", name, string(out))
-
- // "service-<namespace>-<chartname>-rmr.<namespace>"
- return "service-" + ns + "-" + name + "-rmr." + ns, 4560
-}
-
-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.GetEndpointInfo(name)
- if ip == "" {
- Logger.Info("Endpoint IP address not found, using CluserIP")
- ip, _ = h.GetAddress(out)
- }
-
- var tmp string
- r := regexp.MustCompile(`.*(?s)(Running|Pending|Succeeded|Failed|Unknown).*?\r?\n\r?\n`)
- result := r.FindStringSubmatch(string(out))
- if result == nil {
- return
- }
-
- re := regexp.MustCompile(name + "-(\\w+-\\w+).*")
- 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 = port
- 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)
-
- h.FillInstanceData(name, out, &xapp, h.cm.GetMessages(name))
-
- return
-}
-
-func (h *Helm) parseAllStatus(names []string) (xapps []Xapp, err error) {
- xapps = []Xapp{}
-
- for _, name := range names {
- err := h.cm.ReadSchema(name, &XAppConfig{})
- if err != nil {
- continue
- }
-
- 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 {
- Logger.Error("Tiller Env Setting Failed: %v", err.Error())
- }
-
- return err
-}
-
-func getInstallArgs(x XappDeploy, cmOverride bool) (args string) {
- args = args + " --namespace=" + x.Namespace
-
- if x.ImageRepo != "" {
- args = args + " --set global.repository=" + x.ImageRepo
- }
-
- if x.ServiceName != "" {
- args = args + " --set ricapp.service.name=" + x.ServiceName
- }
-
- if x.Hostname != "" {
- args = args + " --set ricapp.hostname=" + x.Hostname
- }
-
- if cmOverride == true {
- args = args + " --set ricapp.appconfig.override=" + x.Name + "-appconfig"
- }
-
- rname := viper.GetString("helm.repo-name")
- return fmt.Sprintf("install %s/%s --name=%s %s", rname, x.Name, x.Name, args)
-}
diff --git a/cmd/appmgr/helm_test.go b/cmd/appmgr/helm_test.go
deleted file mode 100755
index 6ad10c6..0000000
--- a/cmd/appmgr/helm_test.go
+++ /dev/null
@@ -1,144 +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 (
- "reflect"
- "testing"
-)
-
-var helmStatusOutput = `
-LAST DEPLOYED: Sat Mar 9 06:50:45 2019
-NAMESPACE: default
-STATUS: DEPLOYED
-
-RESOURCES:
-==> v1/Pod(related)
-NAME READY STATUS RESTARTS AGE
-dummy-xapp-8984fc9fd-bkcbp 1/1 Running 0 55m
-dummy-xapp-8984fc9fd-l6xch 1/1 Running 0 55m
-dummy-xapp-8984fc9fd-pp4hg 1/1 Running 0 55m
-
-==> v1/Service
-NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
-dummy-xapp-dummy-xapp-chart ClusterIP 10.102.184.212 <none> 80/TCP 55m
-
-==> v1beta1/Deployment
-NAME READY UP-TO-DATE AVAILABLE AGE
-dummy-xapp 3/3 3 3 55m
-`
-
-var helListOutput = `Next: ""
-Releases:
-- AppVersion: "1.0"
- Chart: dummy-xapp-chart-0.1.0
- Name: dummy-xapp
- Namespace: default
- Revision: 1
- Status: DEPLOYED
- Updated: Mon Mar 11 06:55:05 2019
-- AppVersion: "2.0"
- Chart: dummy-xapp-chart-0.1.0
- Name: dummy-xapp2
- Namespace: default
- Revision: 1
- Status: DEPLOYED
- Updated: Mon Mar 11 06:55:05 2019
-- AppVersion: "1.0"
- Chart: appmgr-0.0.1
- Name: appmgr
- Namespace: default
- Revision: 1
- Status: DEPLOYED
- Updated: Sun Mar 24 07:17:00 2019`
-
-var h = Helm{}
-
-func TestHelmStatus(t *testing.T) {
- h.SetCM(&ConfigMap{})
- KubectlExec = func(args string) (out []byte, err error) {
- return []byte("10.102.184.212"), nil
- }
- xapp, err := h.ParseStatus("dummy-xapp", helmStatusOutput)
- if err != nil {
- t.Errorf("Helm install failed: %v", err)
- }
-
- x := getXappData()
- xapp.Version = "1.0"
-
- if !reflect.DeepEqual(xapp, x) {
- t.Errorf("\n%v \n%v", xapp, x)
- }
-}
-
-func TestHelmLists(t *testing.T) {
- names, err := h.GetNames(helListOutput)
- if err != nil {
- t.Errorf("Helm status failed: %v", err)
- }
-
- if !reflect.DeepEqual(names, []string{"dummy-xapp", "dummy-xapp2"}) {
- t.Errorf("Helm status failed: %v", err)
- }
-}
-
-func TestAddTillerEnv(t *testing.T) {
- if addTillerEnv() != nil {
- t.Errorf("TestAddTillerEnv failed!")
- }
-}
-
-func TestGetInstallArgs(t *testing.T) {
- x := XappDeploy{Name: "dummy-xapp", Namespace: "ricxapp"}
-
- expectedArgs := "install helm-repo/dummy-xapp --name=dummy-xapp --namespace=ricxapp"
- if args := getInstallArgs(x, false); args != expectedArgs {
- t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
- }
-
- x.ImageRepo = "localhost:5000"
- expectedArgs = expectedArgs + " --set global.repository=" + "localhost:5000"
- if args := getInstallArgs(x, false); args != expectedArgs {
- t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
- }
-
- x.ServiceName = "xapp"
- expectedArgs = expectedArgs + " --set ricapp.service.name=" + "xapp"
- if args := getInstallArgs(x, false); args != expectedArgs {
- t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
- }
-
- x.ServiceName = "xapp"
- expectedArgs = expectedArgs + " --set ricapp.appconfig.override=dummy-xapp-appconfig"
- if args := getInstallArgs(x, true); args != expectedArgs {
- t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
- }
-}
-
-func getXappData() (x Xapp) {
- x = generateXapp("dummy-xapp", "deployed", "1.0", "dummy-xapp-8984fc9fd-bkcbp", "running", "10.102.184.212", "4560")
- x.Instances = append(x.Instances, x.Instances[0])
- x.Instances = append(x.Instances, x.Instances[0])
- x.Instances[1].Name = "dummy-xapp-8984fc9fd-l6xch"
- x.Instances[2].Name = "dummy-xapp-8984fc9fd-pp4hg"
-
- return x
-}
diff --git a/cmd/appmgr/subscriptions.go b/cmd/appmgr/subscriptions.go
deleted file mode 100755
index 53bc212..0000000
--- a/cmd/appmgr/subscriptions.go
+++ /dev/null
@@ -1,162 +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"
- "encoding/json"
- "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) {
- // Skip duplicates
- for v := range sd.subscriptions.IterBuffered() {
- r := v.Val.(Subscription).req
- if r.TargetUrl == sr.TargetUrl && r.EventType == sr.EventType {
- Logger.Info("Similar subscription already exists!")
- return
- }
- }
-
- 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)
-
- Logger.Info("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 {
- Logger.Info("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 {
- Logger.Info("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 {
- Logger.Info("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 {
- Logger.Info("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 {
- xappData, err := json.Marshal(xapps)
- if err != nil {
- Logger.Info("json.Marshal failed: %v", err)
- return err
- }
-
- notif := SubscriptionNotif{Id: s.req.Id, Version: seq, EventType: string(et), XApps: string(xappData)}
- jsonData, err := json.Marshal(notif)
- if err != nil {
- Logger.Info("json.Marshal failed: %v", err)
- return err
- }
-
- // Execute the request with retry policy
- return sd.retry(s, func() error {
- Logger.Info("Posting notification to targetUrl=%s: %v", s.req.TargetUrl, notif)
- resp, err := http.Post(s.req.TargetUrl, "application/json", bytes.NewBuffer(jsonData))
- if err != nil {
- Logger.Info("Posting to subscription failed: %v", err)
- return err
- }
-
- if resp.StatusCode != http.StatusOK {
- Logger.Info("Client returned error code: %d", resp.StatusCode)
- return err
- }
-
- Logger.Info("subscription to '%s' dispatched, response code: %d", 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
deleted file mode 100755
index 5d79d00..0000000
--- a/cmd/appmgr/subscriptions_test.go
+++ /dev/null
@@ -1,198 +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"
- "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.NewSdlInstance(viper.GetString("db.sessionNamespace"), sdl.NewDatabase())
- 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
deleted file mode 100755
index 1c9556d..0000000
--- a/cmd/appmgr/types.go
+++ /dev/null
@@ -1,159 +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 XappDeploy struct {
- Name string `json:"name"`
- ConfigName string `json:"configName, omitempty"`
- Namespace string `json:"namespace, omitempty"`
- ServiceName string `json:"serviceName, omitempty"`
- ImageRepo string `json:"imageRepo, omitempty"`
- Hostname string `json:"hostname, omitempty"`
-}
-
-type XappManager struct {
- router *mux.Router
- helm Helmer
- cm ConfigMapper
- sd SubscriptionDispatcher
- opts CmdOptions
- ready bool
-}
-
-type ConfigMapper interface {
- UploadConfig() (cfg []XAppConfig)
- GetConfigMap(m XappDeploy, c *interface{}) (err error)
- CreateConfigMap(r XAppConfig) (errList []CMError, err error)
- UpdateConfigMap(r XAppConfig) (errList []CMError, err error)
- DeleteConfigMap(r XAppConfig) (cm interface{}, err error)
- ReadSchema(name string, c *XAppConfig) (err error)
- PurgeConfigMap(m XappDeploy) (cm interface{}, err error)
- RestoreConfigMap(m XappDeploy, cm interface{}) (err error)
- ReadConfigMap(name string, ns string, c *interface{}) (err error)
- ApplyConfigMap(r XAppConfig, action string) (err error)
- GetMessages(name string) (msgs MessageTypes)
- GetNamespace(ns string) string
- GetNamesFromHelmRepo() (names []string)
-}
-
-type Helmer interface {
- SetCM(ConfigMapper)
- Initialize()
- Install(m XappDeploy) (xapp Xapp, err error)
- Status(name string) (xapp Xapp, err error)
- StatusAll() (xapps []Xapp, err error)
- SearchAll() (xapps []string)
- List() (xapps []string, err error)
- Delete(name string) (xapp Xapp, err error)
-}
-
-type Helm struct {
- host string
- chartPath string
- initDone bool
- cm ConfigMapper
-}
-
-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"`
- XApps string `json:"xApps"`
-}
-
-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 `json:"txMessages"`
- RxMessages []string `json:"rxMessages"`
-}
-
-type EventType string
-
-const (
- Created EventType = "created"
- Updated EventType = "updated"
- Deleted EventType = "deleted"
-)
-
-const (
- MdclogErr = 1 //! Error level log entry
- MdclogWarn = 2 //! Warning level log entry
- MdclogInfo = 3 //! Info level log entry
- MdclogDebug = 4 //! Debug level log entry
-)
diff --git a/container-tag.yaml b/container-tag.yaml
old mode 100644
new mode 100755
index a704888..3724441
--- a/container-tag.yaml
+++ b/container-tag.yaml
@@ -1,4 +1,4 @@
# The Jenkins job uses this string for the tag in the image name
# for example nexus3.o-ran-sc.org:10004/my-image-name:my-tag
---
-tag: '0.1.10'
+tag: '0.2.0'
diff --git a/go.mod b/go.mod
index 6a22825..253e4a2 100644
--- a/go.mod
+++ b/go.mod
@@ -7,14 +7,25 @@
gerrit.oran-osc.org/r/ric-plt/sdlgo v0.0.0
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/fsnotify/fsnotify v1.4.7
+ github.com/ghodss/yaml v1.0.0
+ github.com/go-openapi/errors v0.19.2
+ github.com/go-openapi/loads v0.19.4
+ github.com/go-openapi/runtime v0.19.7
+ github.com/go-openapi/spec v0.19.4
+ github.com/go-openapi/strfmt v0.19.3
+ github.com/go-openapi/swag v0.19.5
+ github.com/go-openapi/validate v0.19.4
github.com/gorilla/mux v1.7.1
+ github.com/jessevdk/go-flags v1.4.0
github.com/orcaman/concurrent-map v0.0.0-20190314100340-2693aad1ed75
github.com/segmentio/ksuid v1.0.2
github.com/spf13/viper v1.3.2
+ github.com/stretchr/testify v1.4.0
github.com/valyala/fastjson v1.4.1
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.1.0
+ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
)
replace gerrit.oran-osc.org/r/ric-plt/sdlgo => gerrit.oran-osc.org/r/ric-plt/sdlgo.git v0.0.1
diff --git a/go.sum b/go.sum
index e0539a4..3ffdc4b 100644
--- a/go.sum
+++ b/go.sum
@@ -5,27 +5,112 @@
gerrit.oran-osc.org/r/ric-plt/sdlgo.git v0.0.1/go.mod h1:LVhkNS82IofJTBK/VYPKiYed9MG/3OFwvWC6MGSDw1w=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
+github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
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/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
+github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
+github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
+github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
+github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
+github.com/go-openapi/analysis v0.19.5 h1:8b2ZgKfKIUTVQpTb77MoRDIMEIwvDVw40o3aOXdfYzI=
+github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
+github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
+github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
+github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY=
+github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
+github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
+github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
+github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
+github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
+github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI=
+github.com/go-openapi/loads v0.19.4 h1:5I4CCSqoWzT+82bBkNIvmLc0UOsoKKQ4Fz+3VxOB7SY=
+github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
+github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
+github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
+github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
+github.com/go-openapi/runtime v0.19.7 h1:b2zcE9GCjDVtguugU7+S95vkHjwQEjz/lB+8LOuA9Nw=
+github.com/go-openapi/runtime v0.19.7/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
+github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
+github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
+github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
+github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
+github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
+github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
+github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
+github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
+github.com/go-openapi/strfmt v0.19.3 h1:eRfyY5SkaNJCAwmmMcADjY31ow9+N7MCLW7oRkbsINA=
+github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
+github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
+github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
+github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo=
+github.com/go-openapi/validate v0.19.4 h1:LGjO87VyXY3bIKjlYpXSFuLRG2mTeuYlZyeNwFFWpyM=
+github.com/go-openapi/validate v0.19.4/go.mod h1:BkJ0ZmXui7yB0bJXWSXgLPNTmbLVeX/3D1xn/N9mMUM=
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/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
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/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
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=
@@ -35,6 +120,7 @@
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/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
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=
@@ -53,9 +139,15 @@
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
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/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/valyala/fastjson v1.4.1 h1:hrltpHpIpkaxll8QltMU8c3QZ5+qIiCL8yKqPFJI/yE=
github.com/valyala/fastjson v1.4.1/go.mod h1:nV6MsjxL2IMJQUoHDIrjEI7oLyeqK6aBD7EFWPsvP8o=
@@ -66,18 +158,44 @@
github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg=
github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.mongodb.org/mongo-driver v1.1.1 h1:Sq1fR+0c58RME5EoqKdjkiQAmPjmfHlZOoRI6fTUOcs=
+go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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/sync v0.0.0-20190423024810-112230192c58/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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
+golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
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/check.v1 v1.0.0-20180628173108-788fd7840127/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=
@@ -85,3 +203,5 @@
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=
+gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/cmd/appmgr/config.go b/pkg/appmgr/appmgr.go
similarity index 74%
rename from cmd/appmgr/config.go
rename to pkg/appmgr/appmgr.go
index 5e1975e..28e98f9 100755
--- a/cmd/appmgr/config.go
+++ b/pkg/appmgr/appmgr.go
@@ -17,16 +17,27 @@
==================================================================================
*/
-package main
+package appmgr
import (
"flag"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/logger"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"log"
+ "net/http"
)
-const DEFAULT_CONFIG_FILE = "config/appmgr.yaml"
+const DEFAULT_CONFIG_FILE = "../../config/appmgr.yaml"
+
+var Logger *logger.Log
+
+func LogRestRequests(inner http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ inner.ServeHTTP(w, r)
+ Logger.Info("Logger: method=%s url=%s", r.Method, r.URL.RequestURI())
+ })
+}
func parseCmd() string {
var fileName *string
@@ -36,9 +47,15 @@
return *fileName
}
+func watch() {
+ viper.WatchConfig()
+ viper.OnConfigChange(func(e fsnotify.Event) {
+ log.Println("config file changed ", e.Name)
+ })
+}
+
func loadConfig() {
viper.SetConfigFile(parseCmd())
-
if err := viper.ReadInConfig(); err != nil {
log.Fatalf("Error reading config file, %s", err)
}
@@ -48,9 +65,8 @@
watch()
}
-func watch() {
- viper.WatchConfig()
- viper.OnConfigChange(func(e fsnotify.Event) {
- log.Println("config file changed ", e.Name)
- })
+func Init() {
+ loadConfig()
+ Logger = logger.NewLogger("appmgr")
+ Logger.SetMdc("xm", "0.2.0")
}
diff --git a/pkg/appmgr/types.go b/pkg/appmgr/types.go
new file mode 100755
index 0000000..afdb0b4
--- /dev/null
+++ b/pkg/appmgr/types.go
@@ -0,0 +1,90 @@
+/*
+==================================================================================
+ 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 appmgr
+
+import (
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+)
+
+type ConfigMap struct {
+ Kind string `json:"kind"`
+ ApiVersion string `json:"apiVersion"`
+ Data interface{} `json:"data"`
+ Metadata CMMetadata `json:"metadata"`
+}
+
+type CMMetadata struct {
+ Name string `json:"name"`
+ Namespace string `json:"namespace"`
+}
+
+type ConfigMapper interface {
+ UploadConfig() (cfg []models.XAppConfig)
+ GetConfigMap(m models.XappDescriptor, c *interface{}) (err error)
+ CreateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error)
+ UpdateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error)
+ DeleteConfigMap(r models.XAppConfig) (cm interface{}, err error)
+ ReadSchema(name string, c *models.XAppConfig) (err error)
+ PurgeConfigMap(m models.XappDescriptor) (cm interface{}, err error)
+ RestoreConfigMap(m models.XappDescriptor, cm interface{}) (err error)
+ ReadConfigMap(name string, ns string, c *interface{}) (err error)
+ ApplyConfigMap(r models.XAppConfig, action string) (err error)
+ GetMessages(name string) (msgs MessageTypes)
+ GetNamespace(ns string) string
+ GetNamesFromHelmRepo() (names []string)
+}
+
+type Helmer interface {
+ SetCM(ConfigMapper)
+ Initialize()
+ Install(m models.XappDescriptor) (xapp models.Xapp, err error)
+ Status(name string) (xapp models.Xapp, err error)
+ StatusAll() (xapps models.AllDeployedXapps, err error)
+ SearchAll() (xapps []string)
+ List() (xapps []string, err error)
+ Delete(name string) (xapp models.Xapp, err error)
+}
+
+type Helm struct {
+ host string
+ chartPath string
+ initDone bool
+ cm ConfigMapper
+}
+
+type MessageTypes struct {
+ TxMessages []string `json:"txMessages"`
+ RxMessages []string `json:"rxMessages"`
+}
+
+type EventType string
+
+const (
+ Created EventType = "created"
+ Updated EventType = "updated"
+ Deleted EventType = "deleted"
+)
+
+const (
+ MdclogErr = 1 //! Error level log entry
+ MdclogWarn = 2 //! Warning level log entry
+ MdclogInfo = 3 //! Info level log entry
+ MdclogDebug = 4 //! Debug level log entry
+)
diff --git a/pkg/cm/cm.go b/pkg/cm/cm.go
new file mode 100755
index 0000000..af5372d
--- /dev/null
+++ b/pkg/cm/cm.go
@@ -0,0 +1,306 @@
+/*
+==================================================================================
+ 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 cm
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "github.com/spf13/viper"
+ "github.com/valyala/fastjson"
+ "github.com/xeipuuv/gojsonschema"
+ "io/ioutil"
+ "os"
+ "path"
+ "regexp"
+ "strings"
+ "time"
+
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/util"
+)
+
+type CM struct{}
+
+func NewCM() *CM {
+ return &CM{}
+}
+
+func (cm *CM) UploadConfig() (cfg models.AllXappConfig) {
+ ns := cm.GetNamespace("")
+ for _, name := range cm.GetNamesFromHelmRepo() {
+ if name == "appmgr" {
+ continue
+ }
+
+ c := models.XAppConfig{
+ Metadata: &models.ConfigMetadata{Name: &name, Namespace: ns, ConfigName: cm.GetConfigMapName(name, ns)},
+ }
+
+ err := cm.ReadSchema(name, &c)
+ if err != nil {
+ continue
+ }
+
+ err = cm.ReadConfigMap(c.Metadata.ConfigName, ns, &c.Config)
+ if err != nil {
+ appmgr.Logger.Info("No active configMap found, using default!")
+ }
+
+ cfg = append(cfg, &c)
+ }
+ return
+}
+
+func (cm *CM) ReadSchema(name string, c *models.XAppConfig) (err error) {
+ if err = cm.FetchChart(name); err != nil {
+ return
+ }
+
+ tarDir := viper.GetString("xapp.tarDir")
+ err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.schema")), &c.Descriptor)
+ if err != nil {
+ return
+ }
+
+ err = cm.ReadFile(path.Join(tarDir, name, viper.GetString("xapp.config")), &c.Config)
+ if err != nil {
+ return
+ }
+
+ if err = os.RemoveAll(path.Join(tarDir, name)); err != nil {
+ appmgr.Logger.Info("RemoveAll failed: %v", err)
+ }
+
+ return
+}
+
+func (cm *CM) ReadConfigMap(ConfigName string, ns string, c *interface{}) (err error) {
+ args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, ConfigName)
+ configMapJson, err := util.KubectlExec(args)
+ if err != nil {
+ return
+ }
+
+ err = json.Unmarshal([]byte(configMapJson), &c)
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func (cm *CM) ApplyConfigMap(r models.XAppConfig, action string) (err error) {
+ c := appmgr.ConfigMap{
+ Kind: "ConfigMap",
+ ApiVersion: "v1",
+ Metadata: appmgr.CMMetadata{Name: *r.Metadata.Name, Namespace: r.Metadata.Namespace},
+ Data: r.Config,
+ }
+
+ cmJson, err := json.Marshal(c.Data)
+ if err != nil {
+ appmgr.Logger.Info("Config marshalling failed: %v", err)
+ return
+ }
+
+ cmFile := viper.GetString("xapp.tmpConfig")
+ err = ioutil.WriteFile(cmFile, cmJson, 0644)
+ if err != nil {
+ appmgr.Logger.Info("WriteFile failed: %v", err)
+ return
+ }
+
+ cmd := " create configmap -n %s %s --from-file=%s -o json --dry-run | kubectl %s -f -"
+ args := fmt.Sprintf(cmd, r.Metadata.Namespace, r.Metadata.ConfigName, cmFile, action)
+ _, err = util.KubectlExec(args)
+ if err != nil {
+ return
+ }
+ appmgr.Logger.Info("Configmap changes done!")
+
+ return
+}
+
+func (cm *CM) GetConfigMap(m models.XappDescriptor, c *interface{}) (err error) {
+ return cm.ReadConfigMap(cm.GetConfigMapName(*m.XappName, m.Namespace), m.Namespace, c)
+}
+
+func (cm *CM) CreateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
+ if errList, err = cm.Validate(r); err != nil {
+ return
+ }
+ err = cm.ApplyConfigMap(r, "create")
+ return
+}
+
+func (cm *CM) UpdateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
+ if errList, err = cm.Validate(r); err != nil {
+ return
+ }
+
+ // Re-create the configmap with the new parameters
+ err = cm.ApplyConfigMap(r, "apply")
+ return
+}
+
+func (cm *CM) DeleteConfigMap(r models.ConfigMetadata) (c interface{}, err error) {
+ err = cm.ReadConfigMap(r.ConfigName, r.Namespace, &c)
+ if err == nil {
+ args := fmt.Sprintf(" delete configmap --namespace=%s %s", r.Namespace, r.ConfigName)
+ _, err = util.KubectlExec(args)
+ }
+ return
+}
+
+func (cm *CM) PurgeConfigMap(m models.XappDescriptor) (c interface{}, err error) {
+ md := models.ConfigMetadata{Name: m.XappName, Namespace: m.Namespace, ConfigName: cm.GetConfigMapName(*m.XappName, m.Namespace)}
+
+ return cm.DeleteConfigMap(md)
+}
+
+func (cm *CM) RestoreConfigMap(m models.XappDescriptor, c interface{}) (err error) {
+ md := &models.ConfigMetadata{Name: m.XappName, Namespace: m.Namespace, ConfigName: cm.GetConfigMapName(*m.XappName, m.Namespace)}
+ time.Sleep(time.Duration(10 * time.Second))
+
+ return cm.ApplyConfigMap(models.XAppConfig{Metadata: md, Config: c}, "create")
+}
+
+func (cm *CM) GetNamesFromHelmRepo() (names []string) {
+ rname := viper.GetString("helm.repo-name")
+
+ cmdArgs := strings.Join([]string{"search ", rname}, "")
+ out, err := util.HelmExec(cmdArgs)
+ if err != nil {
+ return
+ }
+
+ re := regexp.MustCompile(rname + `/.*`)
+ result := re.FindAllStringSubmatch(string(out), -1)
+ if result != nil {
+ var tmp string
+ for _, v := range result {
+ fmt.Sscanf(v[0], "%s", &tmp)
+ names = append(names, strings.Split(tmp, "/")[1])
+ }
+ }
+ return names
+}
+
+func (cm *CM) Validate(req models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
+ c := models.XAppConfig{}
+ err = cm.ReadSchema(*req.Metadata.Name, &c)
+ if err != nil {
+ appmgr.Logger.Info("No schema file found for '%s', aborting ...", *req.Metadata.Name)
+ return
+ }
+ return cm.doValidate(c.Descriptor, req.Config)
+}
+
+func (cm *CM) doValidate(schema, cfg interface{}) (errList models.ConfigValidationErrors, err error) {
+ schemaLoader := gojsonschema.NewGoLoader(schema)
+ documentLoader := gojsonschema.NewGoLoader(cfg)
+
+ result, err := gojsonschema.Validate(schemaLoader, documentLoader)
+ if err != nil {
+ appmgr.Logger.Info("Validation failed: %v", err)
+ return
+ }
+
+ if result.Valid() == false {
+ appmgr.Logger.Info("The document is not valid, Errors: %v", result.Errors())
+ for _, desc := range result.Errors() {
+ field := desc.Field()
+ validationError := desc.Description()
+ errList = append(errList, &models.ConfigValidationError{Field: &field, Error: &validationError})
+ }
+ return errList, errors.New("Validation failed!")
+ }
+ return
+}
+
+func (cm *CM) ReadFile(name string, data interface{}) (err error) {
+ f, err := ioutil.ReadFile(name)
+ if err != nil {
+ appmgr.Logger.Info("Reading '%s' file failed: %v", name, err)
+ return
+ }
+
+ err = json.Unmarshal(f, &data)
+ if err != nil {
+ appmgr.Logger.Info("Unmarshalling '%s' file failed: %v", name, err)
+ return
+ }
+
+ return
+}
+
+func (cm *CM) FetchChart(name string) (err error) {
+ tarDir := viper.GetString("xapp.tarDir")
+ repo := viper.GetString("helm.repo-name")
+ fetchArgs := fmt.Sprintf("--untar --untardir %s %s/%s", tarDir, repo, name)
+
+ _, err = util.HelmExec(strings.Join([]string{"fetch ", fetchArgs}, ""))
+ return
+}
+
+func (cm *CM) GetMessages(name string) (msgs appmgr.MessageTypes) {
+ appmgr.Logger.Info("Fetching tx/rx messages for: %s", name)
+
+ ns := cm.GetNamespace("")
+ args := fmt.Sprintf("get configmap -o jsonpath='{.data.config-file\\.json}' -n %s %s", ns, cm.GetConfigMapName(name, ns))
+ out, err := util.KubectlExec(args)
+ if err != nil {
+ return
+ }
+
+ var p fastjson.Parser
+ v, err := p.Parse(string(out))
+ if err != nil {
+ appmgr.Logger.Info("fastjson.Parser for '%s' failed: %v", name, err)
+ return
+ }
+
+ for _, m := range v.GetArray("rmr", "txMessages") {
+ msgs.TxMessages = append(msgs.TxMessages, strings.Trim(m.String(), `"`))
+ }
+ for _, m := range v.GetArray("rmr", "rxMessages") {
+ msgs.RxMessages = append(msgs.RxMessages, strings.Trim(m.String(), `"`))
+ }
+
+ return
+}
+
+func (cm *CM) GetConfigMapName(xappName, namespace string) string {
+ return " configmap-" + namespace + "-" + xappName + "-appconfig"
+}
+
+func (cm *CM) GetNamespace(ns string) string {
+ if ns != "" {
+ return ns
+ }
+
+ ns = viper.GetString("xapp.namespace")
+ if ns == "" {
+ ns = "ricxapp"
+ }
+ return ns
+}
diff --git a/pkg/cm/cm_test.go b/pkg/cm/cm_test.go
new file mode 100755
index 0000000..be600bb
--- /dev/null
+++ b/pkg/cm/cm_test.go
@@ -0,0 +1,312 @@
+/*
+==================================================================================
+ 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 cm
+
+import (
+ "encoding/json"
+ "errors"
+ "os"
+ "reflect"
+ "testing"
+
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/util"
+)
+
+var helmSearchOutput = `
+helm-repo/anr 0.0.1 1.0 Helm Chart for Nokia ANR (Automatic Neighbour Relation) xAPP
+helm-repo/appmgr 0.0.2 1.0 Helm Chart for xAppManager
+helm-repo/dualco 0.0.1 1.0 Helm Chart for Nokia dualco xAPP
+helm-repo/reporter 0.0.1 1.0 Helm Chart for Reporting xAPP
+helm-repo/uemgr 0.0.1 1.0 Helm Chart for Nokia uemgr xAPP
+`
+
+var kubectlConfigmapOutput = `
+{
+ "local": {
+ "host": ":8080"
+ },
+ "logger": {
+ "level": 3
+ },
+ "rmr": {
+ "protPort": "tcp:4560",
+ "maxSize": 2072,
+ "numWorkers": 1,
+ "txMessages": ["RIC_X2_LOAD_INFORMATION"],
+ "rxMessages": ["RIC_X2_LOAD_INFORMATION"]
+ },
+ "db": {
+ "namespace": "ricxapp",
+ "host": "dbaas",
+ "port": 6379
+ }
+}
+`
+
+type ConfigSample struct {
+ Level int
+ Host string
+}
+
+type MockedConfigMapper struct {
+}
+
+func (cm *MockedConfigMapper) ReadSchema(name string, c *models.XAppConfig) (err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) UploadConfig() (cfg []models.XAppConfig) {
+ return
+}
+
+func (cm *MockedConfigMapper) CreateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) GetConfigMap(m models.XappDescriptor, c *interface{}) (err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) UpdateConfigMap(r models.XAppConfig) (errList models.ConfigValidationErrors, err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) DeleteConfigMap(r models.XAppConfig) (c interface{}, err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) PurgeConfigMap(m models.XappDescriptor) (c interface{}, err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) RestoreConfigMap(m models.XappDescriptor, c interface{}) (err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) ReadConfigMap(name string, ns string, c *interface{}) (err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) ApplyConfigMap(r models.XAppConfig, action string) (err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) FetchChart(name string) (err error) {
+ return
+}
+
+func (cm *MockedConfigMapper) GetMessages(name string) (msgs appmgr.MessageTypes) {
+ return
+}
+
+func (cm *MockedConfigMapper) GetNamespace(ns string) (n string) {
+ return
+}
+
+func (cm *MockedConfigMapper) GetNamesFromHelmRepo() (names []string) {
+ return
+}
+
+// Test cases
+func TestMain(m *testing.M) {
+ appmgr.Init()
+ appmgr.Logger.SetLevel(0)
+
+ code := m.Run()
+ os.Exit(code)
+}
+
+func TestGetMessages(t *testing.T) {
+ expectedMsgs := appmgr.MessageTypes{
+ TxMessages: []string{"RIC_X2_LOAD_INFORMATION"},
+ RxMessages: []string{"RIC_X2_LOAD_INFORMATION"},
+ }
+
+ util.KubectlExec = func(args string) (out []byte, err error) {
+ return []byte(kubectlConfigmapOutput), nil
+ }
+
+ result := NewCM().GetMessages("dummy-xapp")
+ if !reflect.DeepEqual(result, expectedMsgs) {
+ t.Errorf("TestGetMessages failed: expected: %v, got: %v", expectedMsgs, result)
+ }
+}
+
+func TestHelmNamespace(t *testing.T) {
+ if NewCM().GetNamespace("pltxapp") != "pltxapp" {
+ t.Errorf("getNamespace failed!")
+ }
+
+ if NewCM().GetNamespace("") != "ricxapp" {
+ t.Errorf("getNamespace failed!")
+ }
+}
+
+func TestFetchChartFails(t *testing.T) {
+ if NewCM().FetchChart("dummy-xapp") == nil {
+ t.Errorf("TestFetchChart failed!")
+ }
+}
+
+func TestFetchChartSuccess(t *testing.T) {
+ util.HelmExec = func(args string) (out []byte, err error) {
+ return
+ }
+
+ if NewCM().FetchChart("dummy-xapp") != nil {
+ t.Errorf("TestFetchChart failed!")
+ }
+}
+
+func TestGetNamesFromHelmRepoSuccess(t *testing.T) {
+ expectedResult := []string{"anr", "appmgr", "dualco", "reporter", "uemgr"}
+ util.HelmExec = func(args string) (out []byte, err error) {
+ return []byte(helmSearchOutput), nil
+ }
+
+ names := NewCM().GetNamesFromHelmRepo()
+ if !reflect.DeepEqual(names, expectedResult) {
+ t.Errorf("GetNamesFromHelmRepo failed: expected %v, got %v", expectedResult, names)
+ }
+}
+
+func TestGetNamesFromHelmRepoFailure(t *testing.T) {
+ expectedResult := []string{}
+ util.HelmExec = func(args string) (out []byte, err error) {
+ return []byte(helmSearchOutput), errors.New("Command failed!")
+ }
+
+ names := NewCM().GetNamesFromHelmRepo()
+ if names != nil {
+ t.Errorf("GetNamesFromHelmRepo failed: expected %v, got %v", expectedResult, names)
+ }
+}
+
+func TestApplyConfigMapSuccess(t *testing.T) {
+ name := "dummy-xapp"
+ m := models.ConfigMetadata{Name: &name, Namespace: "ricxapp"}
+ s := ConfigSample{5, "localhost"}
+
+ util.KubectlExec = func(args string) (out []byte, err error) {
+ return []byte(`{"logger": {"level": 2}}`), nil
+ }
+
+ err := NewCM().ApplyConfigMap(models.XAppConfig{Metadata: &m, Config: s}, "create")
+ if err != nil {
+ t.Errorf("ApplyConfigMap failed: %v", err)
+ }
+}
+
+func TestRestoreConfigMapSuccess(t *testing.T) {
+ name := "dummy-xapp"
+ m := models.XappDescriptor{XappName: &name, Namespace: "ricxapp"}
+ s := ConfigSample{5, "localhost"}
+
+ util.KubectlExec = func(args string) (out []byte, err error) {
+ return []byte(`{"logger": {"level": 2}}`), nil
+ }
+
+ err := NewCM().RestoreConfigMap(m, s)
+ if err != nil {
+ t.Errorf("RestoreConfigMap failed: %v", err)
+ }
+}
+
+func TestDeleteConfigMapSuccess(t *testing.T) {
+ util.HelmExec = func(args string) (out []byte, err error) {
+ return []byte("ok"), nil
+ }
+
+ util.KubectlExec = func(args string) (out []byte, err error) {
+ return []byte(`{"logger": {"level": 2}}`), nil
+ }
+
+ validationErrors, err := NewCM().DeleteConfigMap(models.ConfigMetadata{})
+ if err != nil {
+ t.Errorf("DeleteConfigMap failed: %v -> %v", err, validationErrors)
+ }
+}
+
+func TestPurgeConfigMapSuccess(t *testing.T) {
+ util.HelmExec = func(args string) (out []byte, err error) {
+ return []byte("ok"), nil
+ }
+
+ util.KubectlExec = func(args string) (out []byte, err error) {
+ return []byte(`{"logger": {"level": 2}}`), nil
+ }
+
+ name := "dummy-xapp"
+ validationErrors, err := NewCM().PurgeConfigMap(models.XappDescriptor{XappName: &name})
+ if err != nil {
+ t.Errorf("PurgeConfigMap failed: %v -> %v", err, validationErrors)
+ }
+}
+
+func TestCreateConfigMapFails(t *testing.T) {
+ name := "dummy-xapp"
+ validationErrors, err := NewCM().CreateConfigMap(models.XAppConfig{Metadata: &models.ConfigMetadata{Name: &name}})
+ if err == nil {
+ t.Errorf("CreateConfigMap failed: %v -> %v", err, validationErrors)
+ }
+}
+
+func TestUpdateConfigMapFails(t *testing.T) {
+ name := "dummy-xapp"
+ validationErrors, err := NewCM().UpdateConfigMap(models.XAppConfig{Metadata: &models.ConfigMetadata{Name: &name}})
+ if err == nil {
+ t.Errorf("CreateConfigMap failed: %v -> %v", err, validationErrors)
+ }
+}
+
+func TestValidationSuccess(t *testing.T) {
+ var d interface{}
+ var cfg map[string]interface{}
+ err := json.Unmarshal([]byte(`{"local": {"host": ":8080"}, "logger": {"level": 3}}`), &cfg)
+
+ err = NewCM().ReadFile("../../test/schema.json", &d)
+ if err != nil {
+ t.Errorf("ReadFile failed: %v -> %v", err, d)
+ }
+
+ feedback, err := NewCM().doValidate(d, cfg)
+ if err != nil {
+ t.Errorf("doValidate failed: %v -> %v", err, feedback)
+ }
+}
+
+func TestValidationFails(t *testing.T) {
+ var d interface{}
+ var cfg map[string]interface{}
+ err := json.Unmarshal([]byte(`{"local": {"host": ":8080"}, "logger": {"level": "INVALID"}}`), &cfg)
+
+ err = NewCM().ReadFile("../../test/schema.json", &d)
+ if err != nil {
+ t.Errorf("ConfigMetadata failed: %v -> %v", err, d)
+ }
+
+ feedback, err := NewCM().doValidate(d, cfg)
+ if err == nil {
+ t.Errorf("doValidate should faile but didn't: %v -> %v", err, feedback)
+ }
+ appmgr.Logger.Debug("Feedbacks: %v", feedback)
+}
diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go
new file mode 100755
index 0000000..a3d7b18
--- /dev/null
+++ b/pkg/helm/helm.go
@@ -0,0 +1,368 @@
+/*
+==================================================================================
+ 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 helm
+
+import (
+ "fmt"
+ "github.com/ghodss/yaml"
+ "github.com/spf13/viper"
+ "io/ioutil"
+ "os"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/cm"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/util"
+)
+
+type Helm struct {
+ initDone bool
+ cm *cm.CM
+}
+
+func NewHelm() *Helm {
+ return &Helm{initDone: false, cm: cm.NewCM()}
+}
+
+func (h *Helm) Initialize() {
+ if h.initDone == true {
+ return
+ }
+
+ for {
+ if _, err := h.Init(); err == nil {
+ appmgr.Logger.Info("Helm init done successfully!")
+ break
+ }
+ appmgr.Logger.Info("helm init failed, retyring ...")
+ time.Sleep(time.Duration(10) * time.Second)
+ }
+
+ for {
+ if _, err := h.AddRepo(); err == nil {
+ appmgr.Logger.Info("Helm repo added successfully")
+ break
+ }
+ appmgr.Logger.Info("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 util.HelmExec(args)
+}
+
+// API functions
+func (h *Helm) Init() (out []byte, err error) {
+ if err := h.AddTillerEnv(); err != nil {
+ return out, err
+ }
+
+ return util.HelmExec(strings.Join([]string{"init -c --skip-refresh"}, ""))
+}
+
+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 {
+ appmgr.Logger.Info("helm_repo_username ReadFile failed: %v", err.Error())
+ return
+ }
+ username := " --username " + string(credFile)
+
+ credFile, err = ioutil.ReadFile(viper.GetString("helm.helm-password-file"))
+ if err != nil {
+ appmgr.Logger.Info("helm_repo_password ReadFile failed: %v", err.Error())
+ return
+ }
+ pwd := " --password " + string(credFile)
+
+ rname := viper.GetString("helm.repo-name")
+ repo := viper.GetString("helm.repo")
+
+ return util.HelmExec(strings.Join([]string{"repo add ", rname, " ", repo, username, pwd}, ""))
+}
+
+func (h *Helm) Install(m models.XappDescriptor) (xapp models.Xapp, err error) {
+ var c interface{}
+ m.Namespace = h.cm.GetNamespace(m.Namespace)
+
+ out, err := h.Run(strings.Join([]string{"repo update "}, ""))
+ if err != nil {
+ return
+ }
+
+ if err = h.cm.GetConfigMap(m, &c); err != nil {
+ out, err = h.Run(h.GetInstallArgs(m, false))
+ if err != nil {
+ return
+ }
+ return h.ParseStatus(*m.XappName, string(out))
+ }
+
+ // ConfigMap exists, try to override
+ out, err = h.Run(h.GetInstallArgs(m, true))
+ if err == nil {
+ return h.ParseStatus(*m.XappName, string(out))
+ }
+
+ c, cmErr := h.cm.PurgeConfigMap(m)
+ out, err = h.Run(h.GetInstallArgs(m, false))
+ if err != nil {
+ return
+ }
+
+ if cmErr == nil {
+ cmErr = h.cm.RestoreConfigMap(m, c)
+ }
+ return h.ParseStatus(*m.XappName, string(out))
+}
+
+func (h *Helm) Status(name string) (xapp models.Xapp, err error) {
+ out, err := h.Run(strings.Join([]string{"status ", name}, ""))
+ if err != nil {
+ appmgr.Logger.Info("Getting xapps status: %v", err.Error())
+ return
+ }
+
+ return h.ParseStatus(name, string(out))
+}
+
+func (h *Helm) StatusAll() (xapps models.AllDeployedXapps, err error) {
+ xappNameList, err := h.List()
+ if err != nil {
+ appmgr.Logger.Info("Helm list failed: %v", err.Error())
+ return
+ }
+
+ return h.parseAllStatus(xappNameList)
+}
+
+func (h *Helm) List() (names []string, err error) {
+ ns := h.cm.GetNamespace("")
+ out, err := h.Run(strings.Join([]string{"list --all --deployed --output yaml --namespace=", ns}, ""))
+ if err != nil {
+ appmgr.Logger.Info("Listing deployed xapps failed: %v", err.Error())
+ return
+ }
+
+ return h.GetNames(string(out))
+}
+
+func (h *Helm) SearchAll() models.AllDeployableXapps {
+ return h.cm.GetNamesFromHelmRepo()
+}
+
+func (h *Helm) Delete(name string) (xapp models.Xapp, err error) {
+ xapp, err = h.Status(name)
+ if err != nil {
+ appmgr.Logger.Info("Fetching xapp status failed: %v", 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) GetVersion(name string) (version string) {
+ ns := h.cm.GetNamespace("")
+ out, err := h.Run(strings.Join([]string{"list --deployed --output yaml --namespace=", ns, " ", name}, ""))
+ 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) GetEndpointInfo(name string) (ip string, port int) {
+ ns := h.cm.GetNamespace("")
+ args := fmt.Sprintf(" get endpoints -o=jsonpath='{.subsets[*].addresses[*].ip}' service-%s-%s-rmr -n %s", ns, name, ns)
+ out, err := util.KubectlExec(args)
+ if err != nil {
+ return
+ }
+ appmgr.Logger.Info("Endpoint IP address of %s: %s", name, string(out))
+ return fmt.Sprintf("service-%s-%s-rmr.%s", ns, name, ns), 4560
+}
+
+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 *models.Xapp, msgs appmgr.MessageTypes) {
+ ip, port := h.GetEndpointInfo(name)
+ if ip == "" {
+ appmgr.Logger.Info("Endpoint IP address not found, using CluserIP")
+ ip, _ = h.GetAddress(out)
+ }
+
+ var tmp string
+ r := regexp.MustCompile(`.*(?s)(Running|Pending|Succeeded|Failed|Unknown).*?\r?\n\r?\n`)
+ result := r.FindStringSubmatch(string(out))
+ if result == nil {
+ return
+ }
+
+ re := regexp.MustCompile(name + "-(\\w+-\\w+).*")
+ resources := re.FindAllStringSubmatch(string(result[0]), -1)
+ if resources != nil {
+ for _, v := range resources {
+ var x models.XappInstance
+ var name string
+ fmt.Sscanf(v[0], "%s %s %s", &name, &tmp, &x.Status)
+ x.Name = &name
+ x.Status = strings.ToLower(x.Status)
+ x.IP = ip
+ x.Port = int64(port)
+ x.TxMessages = msgs.TxMessages
+ x.RxMessages = msgs.RxMessages
+ xapp.Instances = append(xapp.Instances, &x)
+ }
+ }
+}
+
+func (h *Helm) ParseStatus(name string, out string) (xapp models.Xapp, err error) {
+ xapp.Name = &name
+ xapp.Version = h.GetVersion(name)
+ xapp.Status = h.GetState(out)
+
+ h.FillInstanceData(name, out, &xapp, h.cm.GetMessages(name))
+ return
+}
+
+func (h *Helm) parseAllStatus(names []string) (xapps models.AllDeployedXapps, err error) {
+ xapps = models.AllDeployedXapps{}
+ for _, name := range names {
+ err := h.cm.ReadSchema(name, &models.XAppConfig{})
+ if err != nil {
+ continue
+ }
+
+ x, err := h.Status(name)
+ if err == nil {
+ xapps = append(xapps, &x)
+ }
+ }
+ return
+}
+
+func (h *Helm) 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 {
+ appmgr.Logger.Info("Tiller Env Setting Failed: %v", err.Error())
+ }
+ return err
+}
+
+func (h *Helm) GetInstallArgs(x models.XappDescriptor, cmOverride bool) (args string) {
+ args = args + " --namespace=" + x.Namespace
+ if x.HelmVersion != "" {
+ args = args + " --version=" + x.HelmVersion
+ }
+
+ if x.ReleaseName != "" {
+ args = args + " --name=" + x.ReleaseName
+ } else {
+ args = args + " --name=" + *x.XappName
+ }
+
+ if cmOverride == true {
+ args = args + " --set ricapp.appconfig.override=" + *x.XappName + "-appconfig"
+ }
+
+ if x.OverrideFile != nil {
+ if overrideYaml, err := yaml.JSONToYAML([]byte(x.OverrideFile.(string))); err == nil {
+ err = ioutil.WriteFile("/tmp/appmgr_override.yaml", overrideYaml, 0644)
+ if err != nil {
+ appmgr.Logger.Info("ioutil.WriteFile(/tmp/appmgr_override.yaml) failed: %v", err)
+ } else {
+ args = args + " -f=/tmp/appmgr_override.yaml"
+ }
+ } else {
+ appmgr.Logger.Info("yaml.JSONToYAML failed: %v", err)
+ }
+ }
+
+ repoName := viper.GetString("helm.repo-name")
+ if repoName == "" {
+ repoName = "helm-repo"
+ }
+ return fmt.Sprintf("install %s/%s %s", repoName, *x.XappName, args)
+}
diff --git a/pkg/helm/helm_test.go b/pkg/helm/helm_test.go
new file mode 100755
index 0000000..fb72358
--- /dev/null
+++ b/pkg/helm/helm_test.go
@@ -0,0 +1,181 @@
+/*
+==================================================================================
+ 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 helm
+
+import (
+ "os"
+ "reflect"
+ "strconv"
+ "testing"
+
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/util"
+)
+
+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`
+
+// Test cases
+func TestMain(m *testing.M) {
+ appmgr.Init()
+ appmgr.Logger.SetLevel(0)
+
+ code := m.Run()
+ os.Exit(code)
+}
+
+func TestHelmStatus(t *testing.T) {
+ //NewHelm().SetCM(&ConfigMap{})
+ util.KubectlExec = func(args string) (out []byte, err error) {
+ return []byte("10.102.184.212"), nil
+ }
+ xapp, err := NewHelm().ParseStatus("dummy-xapp", helmStatusOutput)
+ if err != nil {
+ t.Errorf("Helm install failed: %v", err)
+ }
+ x := getXappData()
+ xapp.Version = "1.0"
+
+ if *x.Name != *xapp.Name || x.Status != xapp.Status || x.Version != xapp.Version {
+ t.Errorf("\n%v \n%v", *xapp.Name, *x.Name)
+ }
+
+ if *x.Instances[0].Name != *xapp.Instances[0].Name || x.Instances[0].Status != xapp.Instances[0].Status {
+ t.Errorf("\n1:%v 2:%v", *x.Instances[0].Name, *xapp.Instances[0].Name)
+ }
+
+ if x.Instances[0].IP != xapp.Instances[0].IP || x.Instances[0].Port != xapp.Instances[0].Port {
+ t.Errorf("\n1:%v 2:%v", x.Instances[0].IP, xapp.Instances[0].IP)
+ }
+}
+
+func TestHelmLists(t *testing.T) {
+ names, err := NewHelm().GetNames(helListOutput)
+ if err != nil {
+ t.Errorf("Helm status failed: %v", err)
+ }
+
+ if !reflect.DeepEqual(names, []string{"dummy-xapp", "dummy-xapp2"}) {
+ t.Errorf("Helm status failed: %v", err)
+ }
+}
+
+func TestAddTillerEnv(t *testing.T) {
+ if NewHelm().AddTillerEnv() != nil {
+ t.Errorf("TestAddTillerEnv failed!")
+ }
+}
+
+func TestGetInstallArgs(t *testing.T) {
+ name := "dummy-xapp"
+ x := models.XappDescriptor{XappName: &name, Namespace: "ricxapp"}
+
+ expectedArgs := "install helm-repo/dummy-xapp --namespace=ricxapp --name=dummy-xapp"
+ if args := NewHelm().GetInstallArgs(x, false); args != expectedArgs {
+ t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+ }
+
+ x.HelmVersion = "1.2.3"
+ expectedArgs = "install helm-repo/dummy-xapp --namespace=ricxapp --version=1.2.3 --name=dummy-xapp"
+ if args := NewHelm().GetInstallArgs(x, false); args != expectedArgs {
+ t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+ }
+
+ x.ReleaseName = "ueec-xapp"
+ expectedArgs = "install helm-repo/dummy-xapp --namespace=ricxapp --version=1.2.3 --name=ueec-xapp"
+ if args := NewHelm().GetInstallArgs(x, false); args != expectedArgs {
+ t.Errorf("TestGetInstallArgs failed: expected %v, got %v", expectedArgs, args)
+ }
+}
+
+func getXappData() (x models.Xapp) {
+ //name1 := "dummy-xapp-8984fc9fd-l6xch"
+ //name2 := "dummy-xapp-8984fc9fd-pp4hg"
+ x = generateXapp("dummy-xapp", "deployed", "1.0", "dummy-xapp-8984fc9fd-bkcbp", "running", "service-ricxapp-dummy-xapp-rmr.ricxapp", "4560")
+ //x.Instances = append(x.Instances, x.Instances[0])
+ //x.Instances = append(x.Instances, x.Instances[0])
+ //x.Instances[1].Name = &name1
+ //x.Instances[2].Name = &name2
+
+ return x
+}
+
+func generateXapp(name, status, ver, iname, istatus, ip, port string) (x models.Xapp) {
+ x.Name = &name
+ x.Status = status
+ x.Version = ver
+ p, _ := strconv.Atoi(port)
+ var msgs appmgr.MessageTypes
+
+ instance := &models.XappInstance{
+ Name: &iname,
+ Status: istatus,
+ IP: ip,
+ Port: int64(p),
+ TxMessages: msgs.TxMessages,
+ RxMessages: msgs.RxMessages,
+ }
+ x.Instances = append(x.Instances, instance)
+
+ return
+}
diff --git a/cmd/appmgr/logger.go b/pkg/logger/logger.go
similarity index 86%
rename from cmd/appmgr/logger.go
rename to pkg/logger/logger.go
index d1bcf15..251e90f 100755
--- a/cmd/appmgr/logger.go
+++ b/pkg/logger/logger.go
@@ -17,11 +17,10 @@
==================================================================================
*/
-package main
+package logger
import (
mdclog "gerrit.o-ran-sc.org/r/com/golog"
- "net/http"
"time"
)
@@ -63,10 +62,3 @@
l.SetMdc("time", time.Now().Format(time.RFC3339))
l.logger.Debug(pattern, args...)
}
-
-func LogRestRequests(inner http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- inner.ServeHTTP(w, r)
- Logger.Info("Logger: method=%s url=%s", r.Method, r.URL.RequestURI())
- })
-}
diff --git a/pkg/restful/restful.go b/pkg/restful/restful.go
new file mode 100755
index 0000000..c0ef580
--- /dev/null
+++ b/pkg/restful/restful.go
@@ -0,0 +1,245 @@
+/*
+ ==================================================================================
+ 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 restful
+
+import (
+ //"github.com/spf13/viper"
+ "log"
+ "os"
+ "time"
+
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/restapi"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/restapi/operations"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/restapi/operations/health"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/restapi/operations/xapp"
+ "github.com/go-openapi/loads"
+ "github.com/go-openapi/runtime/middleware"
+
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/cm"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/helm"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/resthooks"
+)
+
+func NewRestful() *Restful {
+ r := &Restful{
+ helm: helm.NewHelm(),
+ cm: cm.NewCM(),
+ rh: resthooks.NewResthook(),
+ ready: false,
+ }
+ r.api = r.SetupHandler()
+ return r
+}
+
+func (r *Restful) Run() {
+ server := restapi.NewServer(r.api)
+ defer server.Shutdown()
+ server.Port = 8080 //viper.GetInt("local.port")
+ server.Host = "0.0.0.0" //viper.GetString("local.host")
+
+ appmgr.Logger.Info("Xapp manager started ... serving on %s:%d\n", server.Host, server.Port)
+
+ go r.NotifyClients()
+ if err := server.Serve(); err != nil {
+ log.Fatal(err.Error())
+ }
+}
+
+func (r *Restful) SetupHandler() *operations.AppManagerAPI {
+ swaggerSpec, err := loads.Embedded(restapi.SwaggerJSON, restapi.FlatSwaggerJSON)
+ if err != nil {
+ appmgr.Logger.Error(err.Error())
+ os.Exit(1)
+ }
+ api := operations.NewAppManagerAPI(swaggerSpec)
+
+ // URL: /ric/v1/health
+ api.HealthGetHealthAliveHandler = health.GetHealthAliveHandlerFunc(
+ func(params health.GetHealthAliveParams) middleware.Responder {
+ return health.NewGetHealthAliveOK()
+ })
+ api.HealthGetHealthReadyHandler = health.GetHealthReadyHandlerFunc(
+ func(params health.GetHealthReadyParams) middleware.Responder {
+ return health.NewGetHealthReadyOK()
+ })
+
+ // URL: /ric/v1/subscriptions
+ api.GetSubscriptionsHandler = operations.GetSubscriptionsHandlerFunc(
+ func(params operations.GetSubscriptionsParams) middleware.Responder {
+ return operations.NewGetSubscriptionsOK().WithPayload(r.rh.GetAllSubscriptions())
+ })
+ api.GetSubscriptionByIDHandler = operations.GetSubscriptionByIDHandlerFunc(
+ func(params operations.GetSubscriptionByIDParams) middleware.Responder {
+ if result, found := r.rh.GetSubscriptionById(params.SubscriptionID); found {
+ return operations.NewGetSubscriptionByIDOK().WithPayload(&result)
+ }
+ return operations.NewGetSubscriptionByIDNotFound()
+ })
+ api.AddSubscriptionHandler = operations.AddSubscriptionHandlerFunc(
+ func(params operations.AddSubscriptionParams) middleware.Responder {
+ return operations.NewAddSubscriptionCreated().WithPayload(r.rh.AddSubscription(*params.SubscriptionRequest))
+ })
+ api.ModifySubscriptionHandler = operations.ModifySubscriptionHandlerFunc(
+ func(params operations.ModifySubscriptionParams) middleware.Responder {
+ if _, ok := r.rh.ModifySubscription(params.SubscriptionID, *params.SubscriptionRequest); ok {
+ return operations.NewModifySubscriptionOK()
+ }
+ return operations.NewModifySubscriptionBadRequest()
+ })
+ api.DeleteSubscriptionHandler = operations.DeleteSubscriptionHandlerFunc(
+ func(params operations.DeleteSubscriptionParams) middleware.Responder {
+ if _, ok := r.rh.DeleteSubscription(params.SubscriptionID); ok {
+ return operations.NewDeleteSubscriptionNoContent()
+ }
+ return operations.NewDeleteSubscriptionBadRequest()
+ })
+
+ // URL: /ric/v1/xapp
+ api.XappGetAllXappsHandler = xapp.GetAllXappsHandlerFunc(
+ func(params xapp.GetAllXappsParams) middleware.Responder {
+ if result, err := r.helm.StatusAll(); err == nil {
+ return xapp.NewGetAllXappsOK().WithPayload(result)
+ }
+ return xapp.NewGetAllXappsInternalServerError()
+ })
+ api.XappListAllXappsHandler = xapp.ListAllXappsHandlerFunc(
+ func(params xapp.ListAllXappsParams) middleware.Responder {
+ if result := r.helm.SearchAll(); err == nil {
+ return xapp.NewListAllXappsOK().WithPayload(result)
+ }
+ return xapp.NewListAllXappsInternalServerError()
+ })
+ api.XappGetXappByNameHandler = xapp.GetXappByNameHandlerFunc(
+ func(params xapp.GetXappByNameParams) middleware.Responder {
+ if result, err := r.helm.Status(params.XAppName); err == nil {
+ return xapp.NewGetXappByNameOK().WithPayload(&result)
+ }
+ return xapp.NewGetXappByNameNotFound()
+ })
+ api.XappGetXappInstanceByNameHandler = xapp.GetXappInstanceByNameHandlerFunc(
+ func(params xapp.GetXappInstanceByNameParams) middleware.Responder {
+ if result, err := r.helm.Status(params.XAppName); err == nil {
+ for _, v := range result.Instances {
+ if *v.Name == params.XAppInstanceName {
+ return xapp.NewGetXappInstanceByNameOK().WithPayload(v)
+ }
+ }
+ }
+ return xapp.NewGetXappInstanceByNameNotFound()
+ })
+ api.XappDeployXappHandler = xapp.DeployXappHandlerFunc(
+ func(params xapp.DeployXappParams) middleware.Responder {
+ if result, err := r.helm.Install(*params.XappDescriptor); err == nil {
+ go r.PublishXappCreateEvent(params)
+ return xapp.NewDeployXappCreated().WithPayload(&result)
+ }
+ return xapp.NewUndeployXappInternalServerError()
+ })
+ api.XappUndeployXappHandler = xapp.UndeployXappHandlerFunc(
+ func(params xapp.UndeployXappParams) middleware.Responder {
+ if result, err := r.helm.Delete(params.XAppName); err == nil {
+ go r.PublishXappDeleteEvent(result)
+ return xapp.NewUndeployXappNoContent()
+ }
+ return xapp.NewUndeployXappInternalServerError()
+ })
+
+ // URL: /ric/v1/config
+ api.XappGetAllXappConfigHandler = xapp.GetAllXappConfigHandlerFunc(
+ func(params xapp.GetAllXappConfigParams) middleware.Responder {
+ return xapp.NewGetAllXappConfigOK().WithPayload(r.cm.UploadConfig())
+ })
+ api.XappCreateXappConfigHandler = xapp.CreateXappConfigHandlerFunc(
+ func(params xapp.CreateXappConfigParams) middleware.Responder {
+ result, err := r.cm.CreateConfigMap(*params.XAppConfig)
+ if err == nil {
+ if err.Error() != "Validation failed!" {
+ return xapp.NewCreateXappConfigInternalServerError()
+ } else {
+ return xapp.NewCreateXappConfigUnprocessableEntity()
+ }
+ }
+ r.rh.PublishSubscription(models.Xapp{}, models.EventTypeCreated)
+ return xapp.NewCreateXappConfigCreated().WithPayload(result)
+ })
+ api.XappModifyXappConfigHandler = xapp.ModifyXappConfigHandlerFunc(
+ func(params xapp.ModifyXappConfigParams) middleware.Responder {
+ result, err := r.cm.UpdateConfigMap(*params.XAppConfig)
+ if err == nil {
+ if err.Error() != "Validation failed!" {
+ return xapp.NewModifyXappConfigInternalServerError()
+ } else {
+ return xapp.NewModifyXappConfigUnprocessableEntity()
+ }
+ }
+ r.rh.PublishSubscription(models.Xapp{}, models.EventTypeModified)
+ return xapp.NewModifyXappConfigOK().WithPayload(result)
+ })
+ api.XappDeleteXappConfigHandler = xapp.DeleteXappConfigHandlerFunc(
+ func(params xapp.DeleteXappConfigParams) middleware.Responder {
+ _, err := r.cm.DeleteConfigMap(*params.ConfigMetadata)
+ if err == nil {
+ return xapp.NewDeleteXappConfigInternalServerError()
+ }
+ r.rh.PublishSubscription(models.Xapp{}, models.EventTypeDeleted)
+ return xapp.NewDeleteXappConfigNoContent()
+ })
+
+ // LCM: /xapps/{xAppName}/instances/{xAppInstanceName}/stop/start
+ api.XappStartXappInstanceByNameHandler = xapp.StartXappInstanceByNameHandlerFunc(
+ func(params xapp.StartXappInstanceByNameParams) middleware.Responder {
+ return xapp.NewStartXappInstanceByNameOK()
+ })
+ api.XappStopXappInstanceByNameHandler = xapp.StopXappInstanceByNameHandlerFunc(
+ func(params xapp.StopXappInstanceByNameParams) middleware.Responder {
+ return xapp.NewStopXappInstanceByNameOK()
+ })
+
+ return api
+}
+
+func (r *Restful) NotifyClients() {
+ r.helm.Initialize()
+ if xapps, err := r.helm.StatusAll(); err == nil {
+ r.rh.NotifyClients(xapps, models.EventTypeRestarted)
+ r.ready = true
+ }
+}
+
+func (r *Restful) PublishXappCreateEvent(params xapp.DeployXappParams) {
+ name := *params.XappDescriptor.XappName
+ if params.XappDescriptor.ReleaseName != "" {
+ name = params.XappDescriptor.ReleaseName
+ }
+
+ for i := 0; i < 5; i++ {
+ if result, _ := r.helm.Status(name); result.Instances != nil {
+ r.rh.PublishSubscription(result, models.EventTypeDeployed)
+ break
+ }
+ time.Sleep(time.Duration(5) * time.Second)
+ }
+}
+
+func (r *Restful) PublishXappDeleteEvent(xapp models.Xapp) {
+ r.rh.PublishSubscription(xapp, models.EventTypeUndeployed)
+}
diff --git a/pkg/restful/types.go b/pkg/restful/types.go
new file mode 100755
index 0000000..189962d
--- /dev/null
+++ b/pkg/restful/types.go
@@ -0,0 +1,49 @@
+/*
+==================================================================================
+ 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 restful
+
+import (
+ "net/http"
+
+ cfgmap "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/cm"
+ helmer "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/helm"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/restapi/operations"
+ resthook "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/resthooks"
+)
+
+type CmdOptions struct {
+ hostAddr *string
+ helmHost *string
+ helmChartPath *string
+}
+
+type Resource struct {
+ Method string
+ Url string
+ HandlerFunc http.HandlerFunc
+}
+
+type Restful struct {
+ api *operations.AppManagerAPI
+ helm *helmer.Helm
+ cm *cfgmap.CM
+ rh *resthook.Resthook
+ ready bool
+}
diff --git a/pkg/resthooks/resthooks.go b/pkg/resthooks/resthooks.go
new file mode 100755
index 0000000..5076b06
--- /dev/null
+++ b/pkg/resthooks/resthooks.go
@@ -0,0 +1,224 @@
+/*
+==================================================================================
+ 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 resthooks
+
+import (
+ "bytes"
+ "encoding/json"
+ sdl "gerrit.oran-osc.org/r/ric-plt/sdlgo"
+ cmap "github.com/orcaman/concurrent-map"
+ "github.com/segmentio/ksuid"
+ "net/http"
+ "time"
+
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+)
+
+func NewResthook() *Resthook {
+ rh := &Resthook{
+ client: &http.Client{},
+ db: sdl.NewSdlInstance("appmgr", sdl.NewDatabase()),
+ }
+
+ rh.subscriptions = rh.RestoreSubscriptions()
+ return rh
+}
+
+func (rh *Resthook) AddSubscription(sr models.SubscriptionRequest) *models.SubscriptionResponse {
+ for v := range rh.subscriptions.IterBuffered() {
+ r := v.Val.(SubscriptionInfo).req
+ if *r.Data.TargetURL == *sr.Data.TargetURL && r.Data.EventType == sr.Data.EventType {
+ appmgr.Logger.Info("Similar subscription already exists!")
+ return &models.SubscriptionResponse{}
+ }
+ }
+
+ key := ksuid.New().String()
+ resp := models.SubscriptionResponse{ID: key, Version: 0, EventType: sr.Data.EventType}
+ rh.subscriptions.Set(key, SubscriptionInfo{key, sr, resp})
+ rh.StoreSubscriptions(rh.subscriptions)
+
+ appmgr.Logger.Info("Sub: New subscription added: key=%s targetUl=%s eventType=%s", key, *sr.Data.TargetURL, sr.Data.EventType)
+ return &resp
+}
+
+func (rh *Resthook) DeleteSubscription(id string) (*models.SubscriptionResponse, bool) {
+ if v, found := rh.subscriptions.Get(id); found {
+ appmgr.Logger.Info("Subscription id=%s found: %v ... deleting", id, v.(SubscriptionInfo).req)
+
+ rh.subscriptions.Remove(id)
+ rh.StoreSubscriptions(rh.subscriptions)
+ resp := v.(SubscriptionInfo).resp
+ return &resp, found
+ }
+ return &models.SubscriptionResponse{}, false
+}
+
+func (rh *Resthook) ModifySubscription(id string, req models.SubscriptionRequest) (*models.SubscriptionResponse, bool) {
+ if s, found := rh.subscriptions.Get(id); found {
+ appmgr.Logger.Info("Subscription id=%s found: %v ... updating", id, s.(SubscriptionInfo).req)
+
+ resp := models.SubscriptionResponse{ID: id, Version: 0, EventType: req.Data.EventType}
+ rh.subscriptions.Set(id, SubscriptionInfo{id, req, resp})
+ rh.StoreSubscriptions(rh.subscriptions)
+
+ return &resp, found
+ }
+ return &models.SubscriptionResponse{}, false
+}
+
+func (rh *Resthook) GetAllSubscriptions() (hooks models.AllSubscriptions) {
+ hooks = models.AllSubscriptions{}
+ for v := range rh.subscriptions.IterBuffered() {
+ s := v.Val.(SubscriptionInfo)
+ r := v.Val.(SubscriptionInfo).req
+ hooks = append(hooks, &models.Subscription{&models.SubscriptionData{r.Data.EventType, r.Data.MaxRetries, r.Data.RetryTimer, r.Data.TargetURL}, s.Id})
+ }
+
+ return hooks
+}
+
+func (rh *Resthook) GetSubscriptionById(id string) (models.Subscription, bool) {
+ if v, found := rh.subscriptions.Get(id); found {
+ appmgr.Logger.Info("Subscription id=%s found: %v", id, v.(SubscriptionInfo).req)
+ r := v.(SubscriptionInfo).req
+ return models.Subscription{&models.SubscriptionData{r.Data.EventType, r.Data.MaxRetries, r.Data.RetryTimer, r.Data.TargetURL}, id}, found
+ }
+ return models.Subscription{}, false
+}
+
+func (rh *Resthook) PublishSubscription(x models.Xapp, et models.EventType) {
+ rh.NotifyClients(models.AllDeployedXapps{&x}, et)
+}
+
+func (rh *Resthook) NotifyClients(xapps models.AllDeployedXapps, et models.EventType) {
+ if len(xapps) == 0 || len(rh.subscriptions) == 0 {
+ appmgr.Logger.Info("Nothing to publish [%d:%d]", len(xapps), len(rh.subscriptions))
+ return
+ }
+
+ rh.Seq = rh.Seq + 1
+ for v := range rh.subscriptions.Iter() {
+ go rh.notify(xapps, et, v.Val.(SubscriptionInfo), rh.Seq)
+ }
+}
+
+func (rh *Resthook) notify(xapps models.AllDeployedXapps, et models.EventType, s SubscriptionInfo, seq int64) error {
+ notif := models.SubscriptionNotification{ID: s.Id, Version: seq, EventType: et, XApps: xapps}
+ jsonData, err := json.Marshal(notif)
+ if err != nil {
+ appmgr.Logger.Info("json.Marshal failed: %v", err)
+ return err
+ }
+
+ // Execute the request with retry policy
+ return rh.retry(s, func() error {
+ appmgr.Logger.Info("Posting notification to TargetURL=%s: %v", *s.req.Data.TargetURL, notif)
+ resp, err := http.Post(*s.req.Data.TargetURL, "application/json", bytes.NewBuffer(jsonData))
+ if err != nil {
+ appmgr.Logger.Info("Posting to subscription failed: %v", err)
+ return err
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ appmgr.Logger.Info("Client returned error code: %d", resp.StatusCode)
+ return err
+ }
+
+ appmgr.Logger.Info("subscription to '%s' dispatched, response code: %d", *s.req.Data.TargetURL, resp.StatusCode)
+ return nil
+ })
+}
+
+func (rh *Resthook) retry(s SubscriptionInfo, fn func() error) error {
+ if err := fn(); err != nil {
+ // Todo: use exponential backoff, or similar mechanism
+ if *s.req.Data.MaxRetries--; *s.req.Data.MaxRetries > 0 {
+ time.Sleep(time.Duration(*s.req.Data.RetryTimer) * time.Second)
+ return rh.retry(s, fn)
+ }
+ rh.subscriptions.Remove(s.Id)
+ return err
+ }
+ return nil
+}
+
+func (rh *Resthook) StoreSubscriptions(m cmap.ConcurrentMap) {
+ for v := range m.Iter() {
+ s := v.Val.(SubscriptionInfo)
+
+ data, err := json.Marshal(s.req)
+ if err != nil {
+ appmgr.Logger.Error("json.marshal failed: %v ", err.Error())
+ return
+ }
+
+ if err := rh.db.Set(s.Id, data); err != nil {
+ appmgr.Logger.Error("DB.session.Set failed: %v ", err.Error())
+ }
+ }
+}
+
+func (rh *Resthook) RestoreSubscriptions() (m cmap.ConcurrentMap) {
+ rh.VerifyDBConnection()
+
+ m = cmap.New()
+ keys, err := rh.db.GetAll()
+ if err != nil {
+ appmgr.Logger.Error("DB.session.GetAll failed: %v ", err.Error())
+ return
+ }
+
+ for _, key := range keys {
+ value, err := rh.db.Get([]string{key})
+ if err != nil {
+ appmgr.Logger.Error("DB.session.Get failed: %v ", err.Error())
+ return
+ }
+
+ var item models.SubscriptionRequest
+ if err = json.Unmarshal([]byte(value[key].(string)), &item); err != nil {
+ appmgr.Logger.Error("json.Unmarshal failed: %v ", err.Error())
+ return
+ }
+
+ resp := models.SubscriptionResponse{ID: key, Version: 0, EventType: item.Data.EventType}
+ m.Set(key, SubscriptionInfo{key, item, resp})
+ }
+
+ return m
+}
+
+func (rh *Resthook) VerifyDBConnection() {
+ // Test DB connection, and wait until ready!
+ for {
+ if _, err := rh.db.GetAll(); err == nil {
+ return
+ }
+ appmgr.Logger.Error("Database connection not ready, waiting ...")
+ time.Sleep(time.Duration(5 * time.Second))
+ }
+}
+
+func (rh *Resthook) FlushSubscriptions() {
+ rh.db.RemoveAll()
+ rh.subscriptions = cmap.New()
+}
diff --git a/pkg/resthooks/resthooks_test.go b/pkg/resthooks/resthooks_test.go
new file mode 100755
index 0000000..018dee8
--- /dev/null
+++ b/pkg/resthooks/resthooks_test.go
@@ -0,0 +1,124 @@
+/*
+==================================================================================
+ 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 resthooks
+
+import (
+ "github.com/stretchr/testify/assert"
+ "os"
+ "testing"
+
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+)
+
+var resp models.SubscriptionResponse
+
+// Test cases
+func TestMain(m *testing.M) {
+ appmgr.Init()
+ appmgr.Logger.SetLevel(0)
+
+ code := m.Run()
+ os.Exit(code)
+}
+
+func TestAddSubscriptionSuccess(t *testing.T) {
+ resp := NewResthook().AddSubscription(CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
+ assert.Equal(t, resp.Version, int64(0))
+ assert.Equal(t, resp.EventType, models.EventTypeCreated)
+}
+
+func TestAddSubscriptionExists(t *testing.T) {
+ resp := NewResthook().AddSubscription(CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
+ assert.Equal(t, resp.Version, int64(0))
+ assert.Equal(t, resp.EventType, models.EventType(""))
+}
+
+func TestDeletesubscriptionSuccess(t *testing.T) {
+ resp := NewResthook().AddSubscription(CreateSubscription(models.EventTypeDeleted, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
+ assert.Equal(t, resp.Version, int64(0))
+ assert.Equal(t, resp.EventType, models.EventTypeDeleted)
+
+ resp, ok := NewResthook().DeleteSubscription(resp.ID)
+ assert.Equal(t, ok, true)
+ assert.Equal(t, resp.Version, int64(0))
+ assert.Equal(t, resp.EventType, models.EventTypeDeleted)
+}
+
+func TestDeletesubscriptionInvalid(t *testing.T) {
+ resp, ok := NewResthook().DeleteSubscription("Non-existent-ID")
+ assert.Equal(t, ok, false)
+ assert.Equal(t, resp.Version, int64(0))
+ assert.Equal(t, resp.EventType, models.EventType(""))
+}
+
+func TestModifySubscriptionSuccess(t *testing.T) {
+ resp := NewResthook().AddSubscription(CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
+ assert.Equal(t, resp.Version, int64(0))
+ assert.Equal(t, resp.EventType, models.EventTypeCreated)
+
+ resp, ok := NewResthook().ModifySubscription(resp.ID, CreateSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
+ assert.Equal(t, ok, true)
+ assert.Equal(t, resp.Version, int64(0))
+ assert.Equal(t, resp.EventType, models.EventTypeModified)
+}
+
+func TestModifysubscriptionInvalid(t *testing.T) {
+ resp, ok := NewResthook().DeleteSubscription("Non-existent-ID")
+ assert.Equal(t, ok, false)
+ assert.Equal(t, resp.Version, int64(0))
+ assert.Equal(t, resp.EventType, models.EventType(""))
+}
+
+func TestGetAllSubscriptionSuccess(t *testing.T) {
+ NewResthook().FlushSubscriptions()
+ subscriptions := NewResthook().GetAllSubscriptions()
+ assert.Equal(t, len(subscriptions), 0)
+
+ NewResthook().AddSubscription(CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook"))
+ NewResthook().AddSubscription(CreateSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2"))
+
+ subscriptions = NewResthook().GetAllSubscriptions()
+ assert.Equal(t, len(subscriptions), 2)
+}
+
+func TestGetSubscriptionByIdSuccess(t *testing.T) {
+ NewResthook().FlushSubscriptions()
+ sub1 := CreateSubscription(models.EventTypeCreated, int64(5), int64(10), "http://localhost:8087/xapps_hook")
+ sub2 := CreateSubscription(models.EventTypeModified, int64(5), int64(10), "http://localhost:8087/xapps_hook2")
+ r1 := NewResthook().AddSubscription(sub1)
+ r2 := NewResthook().AddSubscription(sub2)
+
+ resp1, ok := NewResthook().GetSubscriptionById(r1.ID)
+ assert.Equal(t, ok, true)
+ assert.Equal(t, resp1.Data, sub1.Data)
+
+ resp2, ok := NewResthook().GetSubscriptionById(r2.ID)
+ assert.Equal(t, ok, true)
+ assert.Equal(t, resp2.Data, sub2.Data)
+}
+
+func TestTeardown(t *testing.T) {
+ NewResthook().FlushSubscriptions()
+}
+
+func CreateSubscription(et models.EventType, maxRetries, retryTimer int64, targetUrl string) models.SubscriptionRequest {
+ return models.SubscriptionRequest{&models.SubscriptionData{et, &maxRetries, &retryTimer, &targetUrl}}
+}
diff --git a/cmd/appmgr/main.go b/pkg/resthooks/types.go
similarity index 64%
copy from cmd/appmgr/main.go
copy to pkg/resthooks/types.go
index 8788405..830576b 100755
--- a/cmd/appmgr/main.go
+++ b/pkg/resthooks/types.go
@@ -17,17 +17,25 @@
==================================================================================
*/
-package main
+package resthooks
-var Logger *Log
+import (
+ sdl "gerrit.oran-osc.org/r/ric-plt/sdlgo"
+ cmap "github.com/orcaman/concurrent-map"
+ "net/http"
-func main() {
- Logger = NewLogger("xapp-manager")
- Logger.SetMdc("appmgr", "0.1.9")
- loadConfig()
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/models"
+)
- m := XappManager{}
- m.Initialize(&Helm{}, &ConfigMap{})
+type SubscriptionInfo struct {
+ Id string
+ req models.SubscriptionRequest
+ resp models.SubscriptionResponse
+}
- m.Run()
+type Resthook struct {
+ client *http.Client
+ subscriptions cmap.ConcurrentMap
+ db *sdl.SdlInstance
+ Seq int64
}
diff --git a/pkg/util/util.go b/pkg/util/util.go
new file mode 100755
index 0000000..8ea4d40
--- /dev/null
+++ b/pkg/util/util.go
@@ -0,0 +1,68 @@
+/*
+==================================================================================
+ 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 util
+
+import (
+ "bytes"
+ "errors"
+ "github.com/spf13/viper"
+ "os"
+ "os/exec"
+ "strings"
+ "time"
+
+ "gerrit.oran-osc.org/r/ric-plt/appmgr/pkg/appmgr"
+)
+
+var execCommand = exec.Command
+
+func Exec(args string) (out []byte, err error) {
+ cmd := execCommand("/bin/sh", "-c", args)
+
+ var stdout bytes.Buffer
+ var stderr bytes.Buffer
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+
+ appmgr.Logger.Info("Running command: %s ", cmd.Args)
+ for i := 0; i < viper.GetInt("helm.retry"); i++ {
+ if err = cmd.Run(); err != nil {
+ appmgr.Logger.Error("Command failed: %v - %s, retrying", err.Error(), stderr.String())
+ time.Sleep(time.Duration(2) * time.Second)
+ continue
+ }
+ break
+ }
+
+ if err == nil && !strings.HasSuffix(os.Args[0], ".test") {
+ appmgr.Logger.Info("command success: %s", stdout.String())
+ return stdout.Bytes(), nil
+ }
+
+ return stdout.Bytes(), errors.New(stderr.String())
+}
+
+var HelmExec = func(args string) (out []byte, err error) {
+ return Exec(strings.Join([]string{"helm", args}, " "))
+}
+
+var KubectlExec = func(args string) (out []byte, err error) {
+ return Exec(strings.Join([]string{"kubectl", args}, " "))
+}
diff --git a/scripts/appmgrcli b/scripts/appmgrcli
index 22f0677..4b9637d 100755
--- a/scripts/appmgrcli
+++ b/scripts/appmgrcli
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/usr/bin/perl -w
#
# Copyright (c) 2019 AT&T Intellectual Property.
# Copyright (c) 2019 Nokia.
@@ -19,26 +19,34 @@
#############################
# Simple cli for xapp manager
#
-# In addition to standard shell tools, requires packages "curl" and
+# In addition to standard shell tools, requires basic Perl installation
+# (Ubuntu package "perl-base", installed by default), packages "curl" and
# "yajl-tools" (the second provides json_reformat on Ubuntu; on Red Hat-style
# distributions install "yajl" instead).
#
-myname=appmgrcli
+use strict;
+use Getopt::Long;
+use Fcntl;
-usage() {
- cat <<EOF1
-usage: $myname [-h host] [-p port] [-v] command params...
-- command is one of deploy, undeploy, status, subscriptions, health, help
+my $myname="appmgrcli";
+
+sub usage {
+ print <<"EOF1";
+usage: $myname [-h host] [-p port] [-v] [-c curlprog] command params...
+- command is deploy, undeploy, status, subscriptions, health, config, help
- (abbreviations dep, undep, stat, subs, heal allowed)
- Parameters of the commands that may have parameters:
-- deploy: name of the xapp to deploy
+---- Deployment parameters come from the Help chart but the following can be
+---- overridden by option with the same name (for example --configName=cname):
+----- helmVersion ReleaseName namespace podHost (host of pods).
-- undeploy: name of the xapp to undeploy
-- status:
---- No parameters: Lists information about all deployed xapps
---- xapp name as parameter: Prints information about the given xapp
---- xapp name and instance: Lists information about the given instance only
-- subscriptions is followed by sub-command list, add, delete, or modify
----(abbreviations del and mod for delete and modify are allowed):
+--- (abbreviations del and mod for delete and modify are allowed):
---- list without parameters lists all subscriptions
---- list with subscription id prints that subscription
---- add URL eventType maxRetry retryTimer
@@ -50,356 +58,997 @@
--------the rest of the parameters are like in add
---- delete id
------- id is the subscription id to delete (find out with the list command)
+-- config is followed by sub-command list, add, delete, or modify
+--- (abbreviations del and mod for delete and modify are allowed):
+---- list (no pars)
+------ lists the configuration of all xapps
+---- add jsonfile
+------ Creates xapp configuration. the jsonfile must contain all data (see API)
+---- add name configName namespace configSchemaFile configDataFile
+------ Creates xapp configuration, but unlike in the 1-parameter form,
+------ Xapp name, config map name and namespace are separate parameters.
+------ The other data come from JSON files.
+---- modify
+------ Modifies existing configuration. Same parameters (1 or 5) as in add.
+---- delete name configName namespace
+------ Deletes the configuration identified by the parameters.
+--------------------------------------------------------------
- Default values for host and port can be set in environment
- variables APPMGR_HOST and APPMGR_PORT
- Option -v sets verbose mode.
+- Option -c overrides the used curl program name ("curl" by default).
+- Exit code is 0 for success, 1 for any kind of failure.
EOF1
+ exit 0;
+}
+
+sub helphint {
+ print "run $myname help (or --help) for instructions\n";
}
# Defaults
-host=localhost
-port=8080
-verbose=0
+my $host="localhost";
+my $port=8080;
+my $verbose=0;
+my $showhelp = 0;
+
+# API URLs
+
+my $base="/ric/v1";
+my $base_xapps="$base/xapps";
+my $base_health="$base/health";
+my $base_subs="$base/subscriptions";
+my $base_config="$base/config";
# Check for environment override
-if [ "x$APPMGR_HOST" != "x" ]; then
- host="$APPMGR_HOST"
-fi
-if [ "x$APPMGR_PORT" != "x" ]; then
- port="$APPMGR_PORT"
-fi
+if (exists $ENV{"APPMGR_HOST"}) {
+ $host=$ENV{"APPMGR_HOST"};
+}
+if (exists $ENV{"APPMGR_PORT"}) {
+ $port=$ENV{"APPMGR_PORT"};
+}
-# Proper shell option parsing:
-while getopts "h:p:v" flag
-do
- # Curiously, getopts does not guard against an argument-requiring option
- # eating the next option. It also does not handle the -- convention.
- # Here is how to fix that.
- if [ "X$OPTARG" = 'X--' ]; then
- break # Explicit end of options
- fi
- if expr -- "$OPTARG" : '-.*' > /dev/null ; then
- echo $myname: Option -$flag has no required value, or value begins with -,
- echo - which is disallowed.
- usage
- exit 1
- fi
- case $flag in
- (h) host="$OPTARG"
- ;;
- (p) port="$OPTARG"
- ;;
- (v) verbose=1
- ;;
- (*)
- echo $myname: Bad option letter or required option argument missing.
- usage
- exit 1
- ;;
- esac
-done
-# Get rid of the option part
-shift $((OPTIND-1))
+# Overrides for some deploy parameters
-if [ $verbose = 1 ]; then
- echo "host = $host"
- echo "port = $port"
-fi
+my $configName = "";
+my $namespace = "ricxapp";
+my $releaseName = "";
+my $helmVersion = "0.0.1";
+my $overrideFile = "";
+my $podHost = "";
-# Verify command
+# The curl command can be overridden for testing with a dummy.
-case $1 in
- (deploy|dep)
- cmd=deploy
- ;;
- (undeploy|undep)
- cmd=undeploy
- ;;
- (status|stat)
- cmd=status
- ;;
- (subscriptions|subs)
- cmd=subscriptions
- ;;
- (health|heal)
- cmd=health
- ;;
- (config|upload)
- cmd=config
- ;;
- (help)
- usage
- exit 0
- ;;
- (*)
- if [ "x$1" = "x" ]; then
- echo "$myname: Missing command"
- else
- echo "$myname: Unrecognized command $1"
- fi
- usage
- exit 1
- ;;
-esac
+my $curl = "curl";
-if [ $verbose = 1 ]; then
- echo "Command $cmd params=$2"
-fi
+Getopt::Long::Configure("no_auto_abbrev", "permute");
+if (! GetOptions("h=s" => \$host,
+ "p=i" => \$port,
+ "c=s" => \$curl,
+ "ConfigName=s" => \$configName,
+ "Namespace=s" => \$namespace,
+ "ReleaseName=s" => \$releaseName,
+ "HelmVersion=s" => \$helmVersion,
+ "OverrideFile=s" => \$overrideFile,
+ "podHost=s" => \$podHost,
+ "help" => \$showhelp,
+ "v" => \$verbose)) {
+ print "$myname: Error in options\n";
+ helphint();
+ exit 1;
+}
-errfile=`mktemp /tmp/appmgr_e.XXXXXXXXXX`
-resultfile=`mktemp /tmp/appmgr_r.XXXXXXXXXX`
+if ($showhelp) {
+ usage();
+ exit 0;
+}
+
+if ($verbose) {
+ print "host = $host\n";
+ print "port = $port\n";
+ print "ConfigName = $configName\n";
+ print "Namespace = $namespace\n";
+ print "ReleaseName = $releaseName\n";
+ print "HelmVersion = $helmVersion\n";
+ print "OverrideFile = $overrideFile\n";
+ print "podHost = $podHost\n";
+ for (my $idx = 0; $idx <= $#ARGV; ++$idx) {
+ print "\$ARGV[$idx] = $ARGV[$idx]\n";
+ }
+}
+
+# Verify command and call handler function
+
+my %commands = (
+ "deploy" => \&do_deploy,
+ "dep" => \&do_deploy,
+ "undeploy" => \&do_undeploy,
+ "undep" => \&do_undeploy,
+ "status" => \&do_status,
+ "stat" => \&do_status,
+ "subscriptions" => \&do_subscriptions,
+ "subs" => \&do_subscriptions,
+ "health" => \&do_health,
+ "heal" => \&do_health,
+ "config" => \&do_config,
+ "help" => \&usage
+);
+
+if ($#ARGV < 0) {
+ print "$myname: Missing command\n";
+ helphint();
+ exit 1;
+}
+
# Variable status used for the return value of the whole script.
-status=0
+my $status = 0;
+
+my $command = $ARGV[0];
+shift;
+if (exists $commands{$command}) {
+ # Call the handler function with the rest of the command line
+ $commands{$command}(@ARGV);
+ exit $status; # Default exit. A handler can exit also if more convenient
+}
+print "$myname: Unrecognised command $command\n";
+helphint();
+exit 1;
+
+my $errfile;
+my $resultfile;
+
+
+sub make_temp_name($) {
+ my $tmpsuffix = "${$}${^T}";
+ return "$_[0].$tmpsuffix";
+}
+
+sub make_temps {
+ $errfile = make_temp_name("/tmp/appmgr_e");
+ $resultfile = make_temp_name("/tmp/appmgr_r");
+}
+
+sub remove_temps {
+ unlink ($errfile, $resultfile);
+}
+
+sub print_file($$) {
+ my $outputhandle = $_[0];
+ my $filename = $_[1];
+ my $buffer;
+ my $inhandle;
+ if (!open($inhandle, "<", $filename)) {
+ print $outputhandle "$myname print_file: cannot open $filename: $!\n";
+ return;
+ }
+ while (read($inhandle, $buffer, 4000) > 0) {
+ print $outputhandle $buffer;
+ }
+ close($inhandle);
+}
+
+# The HTTP protocol result code, filled in by rest().
+
+my $http_code = "";
+
+# Helper: Given a curl output file, extract the number from ##code line.
+# return ERROR if file cannot be opened, or "" if no code found.
+
+sub find_http_code($) {
+ my ($fh, $line, $code);
+ open($fh, "<", $_[0]) or return "ERROR";
+ while ($line = <$fh>) {
+ if ($line =~ /^##([0-9]+)/) {
+ return $1;
+ }
+ }
+ return "";
+}
# Helper for command execution:
# Do a rest call with "curl": $1 = method, $2 = path (without host and port
# which come from variables), $3 data to POST if needed
-# returns 0 if OK, and any returned data is in $resultfile
-# else 1, and error message from curl is in $errfile, which is printed
-# before returning the 1.
-# Also sets $status to the return value.
+# returns true (1) if OK, and any returned data is in $resultfile
+# else 0, and error message from curl is in $errfile, which is printed
+# before returning the 0.
#
# On curl options: --silent --show-error disables progress bar, but allows
# error messages. --connect-timeout 20 limits waiting for connection to
# 20 seconds. In practice connection will succeed almost immediately,
# or in the case of wrong address not at all.
+# To get the http code, using -w with format. The result comes at the end
+# of the output, so "decorating" it for easier filtering.
+# The code is put to global $http_code.
#
-rest() {
- local data
- if [ "x$3" != "x" ]; then
- data="--data $3"
- fi
+sub rest($$_) {
+ my $method = $_[0];
+ my $path = $_[1];
+ my $data = $_[2] || "";
+ my $retval = 1;
+ my $http_status_file = make_temp_name("/tmp/appmgr_h");
- if curl --silent --show-error --connect-timeout 20 --header "Content-Type: application/json" -X $1 -o $resultfile "http://${host}:${port}$2" $data 2> $errfile ;then
- status=0
- else
- cat $errfile
- status=1
- fi
- return $status
+ # This redirects stderr (fd 2) to $errfile, but saving normal stderr
+ # so that if can be restored.
+ open(OLDERR, ">&", \*STDERR) or die "Can't dup STDERR: $!";
+ open(ERRFILE, ">", $errfile) or die "open errorfile failed";
+ open(STDERR, ">&", \*ERRFILE) or die "Can't dup ERRFILE: $!";
+
+ # This redirects stdout (fd 1) to $http_status_file, but saving original
+ # so that if can be restored.
+ open(OLDSTDOUT, ">&", \*STDOUT) or die "Can't dup STDOUT: $!";
+ open(HTTP_STATUS_FILE, ">", $http_status_file) or die "open http status file failed";
+ open(STDOUT, ">&", \*HTTP_STATUS_FILE) or die "Can't dup HTTP_STATUS_FILE: $!";
+
+ my @args = ($curl, "--silent", "--show-error", "--connect-timeout", "20",
+ "--header", "Content-Type: application/json", "-X", $method,
+ "-o", $resultfile, "-w", '\n##%{http_code}\n',
+ "http://${host}:${port}${path}");
+ if ($data ne "") {
+ push(@args, "--data");
+ push(@args, $data);
+ }
+ if ($verbose) {
+ print OLDSTDOUT "Running: " . join(" ", @args) . "\n";
+ }
+ if (system(@args) == -1) {
+ print OLDSTDOUT "$myname: failed to execute @args\n";
+ $retval = 0;
+ }
+ elsif ($? & 127) {
+ printf OLDSTDOUT "$myname: child died with signal %d, %s coredump\n",
+ ($? & 127), ($? & 128) ? 'with' : 'without';
+ $retval = 0;
+ }
+ else {
+ my $curl_exit_code = $? >> 8;
+ if ($curl_exit_code == 0) {
+ seek HTTP_STATUS_FILE, 0, 0; # Ensures flushing
+ $http_code = find_http_code($http_status_file);
+ if ($http_code eq "ERROR") {
+ print OLDSTDOUT "$myname: failed to open temp file $http_status_file\n";
+ $retval = 0;
+ }
+ elsif ($http_code eq "") {
+ print OLDSTDOUT "$myname: curl failed to provide HTTP code\n";
+ $retval = 0;
+ }
+ else {
+ if ($verbose) {
+ print OLDSTDOUT "HTTP status code = $http_code\n";
+ }
+ $retval = 1; # Interaction OK from REST point of view
+ }
+ }
+ else {
+ print_file(\*OLDSTDOUT, $errfile);
+ $retval = 0;
+ }
+ }
+ open(STDOUT, ">&", \*OLDSTDOUT) or die "Can't dup OLDSTDOUT: $!";
+ open(STDERR, ">&", \*OLDERR) or die "Can't dup OLDERR: $!";
+ unlink($http_status_file);
+ return $retval;
}
-remove_temps () {
- rm -f $errfile $resultfile
+# Pretty-print a JSON file to stdout.
+# (currently uses json_reformat command)
+# Skips the ##httpcode line we make "curl"
+# add in order to get access to the HTTP status.
+
+sub print_json($) {
+ my $filename = $_[0];
+ my ($line, $inhandle, $outhandle);
+ if (!open($inhandle, "<", $filename)) {
+ print "$myname print_json: cannot open $filename: $!\n";
+ return;
+ }
+ if (!open($outhandle, "|json_reformat")) {
+ print "$myname print_json: cannot pipe to json_reformat: $!\n";
+ return;
+ }
+ while ($line = <$inhandle>) {
+ if (! ($line =~ /^##[0-9]+/)) {
+ print $outhandle $line;
+ }
+ }
+ close($outhandle);
+ close($inhandle);
}
-# Execute command ($cmd guaranteed to be valid)
+# Append an entry like ","name":"value" to the first parameter, if "name"
+# names a variable with non-empty value.
+# Else returns the unmodified first parameter.
+
+sub append_option($$) {
+ my $result = $_[0];
+ my $var = $_[1];
+ my $val = eval("\$$var");
+ if ($val ne "") {
+ $result = "$result,\"$var\":\"$val\"";
+ }
+ return $result;
+}
+
+# Command handlers
# Assumes the API currently implemented.
-# Functions for each command below (except health which is so simple).
+# Functions for each command below
-base=/ric/v1
-base_xapps=$base/xapps
-base_health=$base/health
-base_subs=$base/subscriptions
-base_config=$base/config
+# Deploy:
+# The deploy command has one mandatory parameter "name" in the API,
+# and several optional ones. Used mainly internally for testing, because
+# they all override Helm chart values:
+# "helmVersion": Helm chart version to be used
+# "releaseName": The releas name of xApp visible in K8s
+# "namespace": Name of the namespace to which xApp is deployed.
+# "overrideFile": The file content used to override values.yaml file
+# this host from the host the xapp manager is running in, we use the term
+# and variable name "podHost" here.
+# The options come from options (see GetOptions() call).
-do_deploy() {
- if [ "x$1" != "x" ]; then
- if rest POST $base_xapps \{\"name\":\"$1\"\} ; then
- json_reformat < $resultfile
- fi
- else
- echo Error: expected the name of xapp to deploy
- status=1
- fi
+sub do_deploy(@) {
+ my $name = $_[0] || "";
+ if ($name ne "") {
+ my $data = "{\"XappName\":\"$name\"";
+ $data = append_option($data, "helmVersion");
+ $data = append_option($data, "releaseName");
+ $data = append_option($data, "namespace");
+ $data = append_option($data, "overrideFile");
+ $data = $data . "}";
+ make_temps();
+ if (rest("POST", $base_xapps, $data)) {
+ if ($http_code eq "201") {
+ print_json $resultfile;
+ $status = 0;
+ }
+ else {
+ my $error;
+ if ($http_code eq "400") {
+ $error = "INVALID PARAMETERS SUPPLIED";
+ }
+ elsif ($http_code eq "500") {
+ $error = "INTERNAL ERROR";
+ }
+ else {
+ $error = "UNKNOWN STATUS $http_code";
+ }
+ print "$error\n";
+ $status = 1;
+ }
+ }
+ else {
+ $status=1;
+ }
+ remove_temps();
+ }
+ else {
+ print "$myname: Error: expected the name of xapp to deploy\n";
+ $status = 1;
+ }
}
-do_undeploy() {
- local urlpath
-
- urlpath=$base_xapps
- if [ "x$1" != "x" ]; then
- urlpath="$urlpath/$1"
- if rest DELETE $urlpath; then
- # Currently appmgr returns an empty result if
- # undeploy is succesfull. Don't reformat file if empty.
- if [ -s $resultfile ]; then
- json_reformat < $resultfile
- else
- echo "$1 undeployed"
- fi
- fi
- else
- echo Error: expected the name of xapp to undeploy
- status=1
- fi
+sub do_undeploy(@) {
+ my $name = $_[0] || "";
+ my $urlpath = $base_xapps;
+ if ($name ne "") {
+ make_temps();
+ $urlpath = "$urlpath/$name";
+ if (rest("DELETE", $urlpath)) {
+ if ($http_code eq "204") {
+ print "SUCCESSFUL DELETION\n";
+ $status = 0;
+ }
+ else {
+ my $error;
+ if ($http_code eq "400") {
+ $error = "INVALID XAPP NAME SUPPLIED";
+ }
+ elsif ($http_code eq "500") {
+ $error = "INTERNAL ERROR";
+ }
+ else {
+ $error = "UNKNOWN STATUS $http_code";
+ }
+ print "$error\n";
+ $status = 1;
+ }
+ }
+ else {
+ $status = 1;
+ }
+ remove_temps();
+ }
+ else {
+ print "$myname: Error: expected the name of xapp to undeploy\n";
+ $status = 1;
+ }
}
-do_status() {
- local urlpath
+sub do_status(@) {
+ my $name = $_[0] || "";
+ my $instance = $_[1] || "";
+ my $urlpath = $base_xapps;
- urlpath=$base_xapps
- if [ "x$1" != "x" ]; then
- urlpath="$urlpath/$1"
- fi
- if [ "x$2" != "x" ]; then
- urlpath="$urlpath/instances/$2"
- fi
- if rest GET $urlpath; then
- json_reformat < $resultfile
- fi
+ if ($name ne "") {
+ $urlpath = "$urlpath/$name";
+ }
+ if ($instance ne "") {
+ $urlpath = "$urlpath/instances/$instance"
+ }
+ make_temps();
+ if (rest("GET", $urlpath)) {
+ if ($http_code eq "200") {
+ print_json $resultfile;
+ $status = 0;
+ }
+ else {
+ my $error;
+ if ($http_code eq "400") {
+ $error = "INVALID XAPP NAME SUPPLIED";
+ }
+ if ($http_code eq "404") {
+ $error = "XAPP NOT FOUND";
+ }
+ elsif ($http_code eq "500") {
+ $error = "INTERNAL ERROR";
+ }
+ else {
+ $error = "UNKNOWN STATUS $http_code";
+ }
+ print "$error\n";
+ $status = 1;
+ }
+ }
+ else {
+ $status = 1;
+ }
+ remove_temps();
}
-# This is a bit more complex. $1 is sub-command: list, add, delete, modify
-
+# Helpers for subscription:
# Validate the subscription data that follows a subscription add or modify
# subcommand. $1=URL, $2=eventType, $3=maxRetries, $4=retryTimer
# URL must look like URL, event type must be one of created deleted all,
# maxRetries and retryTimer must be non-negative numbers.
-# If errors, sets variable status=1 and prints errors, else leaves
-# status unchanged.
+# If errors, returns false (0) and prints errors, else returns 1.
#
-validate_subscription() {
- if ! expr "$1" : "^http://.*" \| "$1" : "^https://.*" >/dev/null; then
- echo "$myname: bad URL $1"
- status=1
- fi
- if ! [ "$2" = created -o "$2" = deleted -o "$2" = all ]; then
- echo "$myname: unrecognized event $2"
- status=1
- fi
- if ! expr "$3" : "^[0-9][0-9]*$" >/dev/null; then
- echo "$myname: invalid maximum retries count $3"
- status=1
- fi
- if ! expr "$4" : "^[0-9][0-9]*$" >/dev/null; then
- echo "$myname: invalid retry time $4"
- status=1
- fi
+sub validate_subscription(@) {
+ # Using the API parameter names
+ my $targetUrl = $_[0] || "";
+ my $eventType = $_[1] || "";
+ my $maxRetries = $_[2] || "";
+ my $retryTimer = $_[3] || "";
+ my $retval = 1;
+
+ if (! ($targetUrl =~ /^http:\/\/.*/ or $targetUrl =~ /^https:\/\/.*/)) {
+ print "$myname: bad URL $targetUrl\n";
+ $retval = 0;
+ }
+ if ($eventType ne "created" and $eventType ne "deleted" and
+ $eventType ne "all") {
+ print "$myname: unrecognized event $eventType\n";
+ $retval = 0;
+ }
+ if (! ($maxRetries =~ /^[0-9]+$/)) {
+ print "$myname: invalid maximum retries count $maxRetries\n";
+ $retval = 0;
+ }
+ if (! ($retryTimer =~ /^[0-9]+$/)) {
+ print "$myname: invalid retry time $retryTimer\n";
+ $retval = 0;
+ }
+ return $retval;
}
-do_subscriptions() {
- local urlpath
- urlpath=$base_subs
- case $1 in
- (list)
- if [ "x$2" != "x" ]; then
- urlpath="$urlpath/$2"
- fi
- if rest GET $urlpath; then
- json_reformat < $resultfile
- else
- status=1
- fi
- ;;
- (add)
- validate_subscription "$2" "$3" "$4" "$5"
- if [ $status = 0 ]; then
- if rest POST $urlpath \{\"targetUrl\":\"$2\",\"eventType\":\"$3\",\"maxRetries\":$4,\"retryTimer\":$5\} ; then
- json_reformat < $resultfile
- else
- status=1
- fi
- fi
- ;;
- (delete|del)
- if [ "x$2" != "x" ]; then
- urlpath="$urlpath/$2"
- else
- echo "$myname: Subscription id required"
- status=1
- fi
- if [ $status = 0 ]; then
- if rest DELETE $urlpath; then
- # Currently appmgr returns an empty result if
- # delete is succesfull. Don't reformat file if empty.
- if [ -s $resultfile ]; then
- json_reformat < $resultfile
- else
- echo "Subscription $2 deleted"
- fi
- else
- status=1
- fi
- fi
- ;;
- (modify|mod)
- if [ "x$2" != "x" ]; then
- urlpath="$urlpath/$2"
- else
- echo "$myname: Subscription id required"
- status=1
- fi
- if [ $status = 0 ]; then
- validate_subscription "$3" "$4" "$5" "$6"
- if [ $status = 0 ]; then
- if rest PUT $urlpath \{\"targetUrl\":\"$3\",\"eventType\":\"$4\",\"maxRetries\":$5,\"retryTimer\":$6\} ; then
- json_reformat < $resultfile
- else
- status=1
- fi
- fi
- fi
- ;;
- (*)
- echo "$myname: unrecognized subscriptions subcommand $1"
- status=1
- esac
+# Format a subscriptionRequest JSON object
+
+sub make_subscriptionRequest(@) {
+ my $targetUrl = $_[0];
+ my $eventType = $_[1];
+ my $maxRetries = $_[2];
+ my $retryTimer = $_[3];
+ return "{\"Data\": {\"TargetUrl\":\"$targetUrl\",\"EventType\":\"$eventType\",\"MaxRetries\":$maxRetries,\"RetryTimer\":$retryTimer}}";
}
-do_config() {
- local urlpath
- urlpath=$base_config
- case $1 in
- (get|list)
- if [ "x$2" != "x" ]; then
- urlpath="$urlpath/$2"
- fi
- if rest GET $urlpath; then
- json_reformat < $resultfile
- else
- status=1
- fi
- ;;
- (add|update)
- if rest POST $urlpath "@$2" ; then
- cat $resultfile
- else
- status=1
- fi
- ;;
- (del|delete|remove|rem)
- if rest DELETE $urlpath "@$2" ; then
- cat $resultfile
- else
- status=1
- fi
- ;;
- (*)
- echo "$myname: unrecognized config subcommand $1"
- status=1
- esac
+# Subscriptions:
+# $1 is sub-command: list, add, delete, modify
+
+sub do_subscriptions(@) {
+ my $subcommand = $_[0] || "";
+ shift;
+
+ my %subcommands = (
+ "list" => \&do_subscription_list,
+ "add" => \&do_subscription_add,
+ "delete" => \&do_subscription_delete,
+ "del" => \&do_subscription_delete,
+ "modify" => \&do_subscription_modify,
+ "mod" => \&do_subscription_modify
+ );
+ if (exists $subcommands{$subcommand}) {
+ $subcommands{$subcommand}(@_);
+ }
+ else {
+ print "$myname: unrecognized subscriptions subcommand $subcommand\n";
+ helphint();
+ $status=1
+ }
}
-case $cmd in
- (deploy)
- do_deploy "$2"
- ;;
- (undeploy)
- do_undeploy "$2"
- ;;
- (status)
- do_status "$2" "$3"
- ;;
- (subscriptions)
- do_subscriptions "$2" "$3" "$4" "$5" "$6" "$7"
- ;;
- (config)
- do_config "$2" "$3"
- ;;
- (health)
- if rest GET $base_health ; then
- echo OK
- else
- echo NOT OK
- fi
- ;;
-esac
-remove_temps
-exit $status
+# list: With empty parameter, list all, else the parameter is
+# a subscriptionId
-# An Emacs hack to set the indentation style of this file
-# Local Variables:
-# sh-indentation:2
-# End:
+sub do_subscription_list(@) {
+ my $urlpath=$base_subs;
+ my $subscriptionId = $_[0] || "";
+ if ($subscriptionId ne "") {
+ $urlpath = "$urlpath/$subscriptionId";
+ }
+ make_temps();
+ if (rest("GET", $urlpath)) {
+ if ($http_code eq "200") {
+ print_json $resultfile;
+ $status = 0;
+ }
+ else {
+ my $error;
+ if ($http_code eq "400") {
+ $error = "INVALID SUBSCRIPTION ID $subscriptionId";
+ }
+ elsif ($http_code eq "404") {
+ $error = "SUBSCRIPTION $subscriptionId NOT FOUND";
+ }
+ elsif ($http_code eq "500") {
+ $error = "INTERNAL ERROR";
+ }
+ else {
+ $error = "UNKNOWN STATUS $http_code";
+ }
+ print "$error\n";
+ $status = 1;
+ }
+ }
+ else {
+ $status=1;
+ }
+ remove_temps();
+}
+
+sub do_subscription_add(@) {
+ my $urlpath=$base_subs;
+
+ if (validate_subscription(@_)) {
+ make_temps();
+ if (rest("POST", $urlpath, make_subscriptionRequest(@_))) {
+ if ($http_code eq "201") {
+ print_json $resultfile;
+ $status = 0;
+ }
+ else {
+ my $error;
+ if ($http_code eq "400") {
+ $error = "INVALID INPUT";
+ }
+ elsif ($http_code eq "500") {
+ $error = "INTERNAL ERROR";
+ }
+ else {
+ $error = "UNKNOWN STATUS $http_code";
+ }
+ print "$error\n";
+ $status = 1;
+ }
+ }
+ else {
+ $status=1;
+ }
+ remove_temps();
+ }
+ else {
+ $status = 1;
+ }
+}
+
+sub do_subscription_delete(@) {
+ my $urlpath=$base_subs;
+ my $subscriptionId = $_[0] || "";
+ if ($subscriptionId ne "") {
+ $urlpath = "$urlpath/$subscriptionId";
+ }
+ else {
+ print "$myname: delete: Subscription id required\n";
+ $status=1;
+ return;
+ }
+ make_temps();
+ if (rest("DELETE", $urlpath)) {
+ if ($http_code eq "204") {
+ print "SUBSCRIPTION $subscriptionId DELETED\n";
+ $status = 0;
+ }
+ else {
+ my $error;
+ if ($http_code eq "400") {
+ $error = "INVALID SUBSCRIPTION ID $subscriptionId";
+ }
+ elsif ($http_code eq "500") {
+ $error = "INTERNAL ERROR";
+ }
+ else {
+ $error = "UNKNOWN STATUS $http_code";
+ }
+ print "$error\n";
+ $status = 1;
+ }
+ }
+ else {
+ $status = 1;
+ }
+ remove_temps();
+}
+
+sub do_subscription_modify(@) {
+ my $urlpath=$base_subs;
+ if (defined $_[0]) {
+ $urlpath = "$urlpath/$_[0]";
+ }
+ else {
+ print "$myname: modify: Subscription id required\n";
+ $status=1;
+ return;
+ }
+ shift;
+ if (validate_subscription(@_)) {
+ make_temps();
+ if (rest("PUT", $urlpath, make_subscriptionRequest(@_))) {
+ if ($http_code eq "200") {
+ print_json $resultfile;
+ $status = 0;
+ }
+ else {
+ my $error;
+ if ($http_code eq "400") {
+ $error = "INVALID INPUT";
+ }
+ elsif ($http_code eq "500") {
+ $error = "INTERNAL ERROR";
+ }
+ else {
+ $error = "UNKNOWN STATUS $http_code";
+ }
+ print "$error\n";
+ $status = 1;
+ }
+ }
+ else {
+ $status=1;
+ }
+ remove_temps();
+ }
+ else {
+ $status = 1;
+ }
+}
+
+sub do_health(@) {
+ my $urlpath=$base_health;
+ my $check = $_[0] || "";
+ # API now defines two types of checks, either of
+ # which must be specified.
+ if ($check ne "alive" and $check ne "ready") {
+ print "$myname: health check type required (alive or ready)\n";
+ $status=1;
+ return;
+ }
+ $urlpath = "$urlpath/$check";
+ make_temps();
+ if (rest("GET", $urlpath)) {
+ my $res;
+ if ($check eq "alive") {
+ # If GET succeeds at all, the xapp manager is alive, no
+ # need to check the HTTP code.
+ $res = "ALIVE";
+ }
+ else {
+ if ($http_code eq "200") {
+ $res = "READY";
+ }
+ elsif ($http_code eq "503") {
+ $res = "NOT READY";
+ }
+ elsif ($http_code eq "500") {
+ $res = "INTERNAL ERROR";
+ }
+ else {
+ $res = "UNKNOWN STATUS $http_code";
+ }
+ }
+ print "$res\n";
+ }
+ else {
+ $status = 1;
+ print "$myname: health check failed to contact appmgr\n";
+ }
+ remove_temps();
+}
+
+sub do_config(@) {
+ my $subcommand = $_[0] || "";
+ shift;
+
+ my %subcommands = (
+ "list" => \&do_config_list,
+ "add" => \&do_config_add,
+ "delete" => \&do_config_delete,
+ "del" => \&do_config_delete,
+ "modify" => \&do_config_modify,
+ "mod" => \&do_config_modify
+ );
+ if (exists $subcommands{$subcommand}) {
+ $subcommands{$subcommand}(@_);
+ }
+ else {
+ print "$myname: unrecognized config subcommand $subcommand\n";
+ helphint();
+ $status=1
+ }
+}
+
+sub do_config_list(@) {
+ if (defined $_[0]) {
+ print "$myname: \"config list\" has no parameters\n";
+ $status = 1;
+ return;
+ }
+ make_temps();
+ if (rest("GET", $base_config)) {
+ if ($http_code eq "200") {
+ print_json $resultfile;
+ $status = 0;
+ }
+ else {
+ my $error;
+ if ($http_code eq "500") {
+ $error = "INTERNAL ERROR";
+ }
+ else {
+ $error = "UNKNOWN STATUS $http_code";
+ }
+ print "$error\n";
+ $status = 1;
+ }
+ }
+ else {
+ $status=1;
+ }
+ remove_temps();
+}
+
+# validate_config() checks configuration commmand line.
+# "config add" and "config modify" expect either single parameter which
+# must be a JSON file that contains the whole thing to send (see API),
+# or 5 parameters, where the first three are
+# $_[0] = name
+# $_[1] = configName (name of the configMap)
+# $_[2] = namespace
+# Followed by two file names:
+# $_[3] = file containing configSchema
+# $_[4] = file containing data for configMap
+# Giving the last two literally on the command line does not make much sense,
+# since they are arbitrary JSON data.
+# On success, returns parameter count (1 or 5), depending on which kind of
+# command line found.
+# 0 if errors.
+
+# Check only the 3 names at the beginning of config add/modify/delete
+sub validate_config_names(@) {
+ my $retval = 1;
+ # Names in the Kubernetes world consist of lowercase alphanumerics
+ # and - and . as specified in
+ # https://kubernetes.io/docs/concepts/overview/working-with-objects/name
+ for (my $idx = 0; $idx <= 2; ++$idx) {
+ if (! ($_[$idx] =~ /^[a-z][-a-z0-9.]*$/)) {
+ print "$myname: invalid characters in name $_[$idx]\n";
+ $retval = 0;
+ }
+ }
+ return $retval;
+}
+
+sub validate_config(@) {
+ my $retval = 1;
+ print "validate_config args @_\n";
+ if ($#_ == 0) {
+ if (! -r $_[0]) {
+ print "$myname: config file $_[0] cannot be read: $!\n";
+ $retval = 0;
+ }
+ }
+ elsif ($#_ == 4) {
+ $retval = 5;
+ if (! validate_config_names(@_)) {
+ $retval = 0;
+ }
+ for (my $idx = 3; $idx <= 4; ++$idx) {
+ if (! -r $_[$idx]) {
+ print "$myname: cannot read file $_[$idx]\n";
+ $retval = 0;
+ }
+ }
+ }
+ else {
+ print "$myname: config add: 1 or 5 parameter expected\n";
+ $retval = 0;
+ }
+ return $retval;
+}
+
+# Generate JSON for the xAppConfig element (see API).
+
+sub make_xAppConfigInfo($$$) {
+ return "{\"xAppName\":\"$_[0]\",\"configMapName\":\"$_[1]\",\"namespace\":\"$_[2]\"}";
+}
+
+sub make_xAppConfig(@) {
+ my $retval = "{\"xAppConfigInfo\":" . make_xAppConfigInfo($_[0],$_[1],$_[2]);
+ my $fh;
+ open($fh, "<", $_[3]) or die "failed to open $_[3]";
+ my @obj = <$fh>;
+ close($fh);
+ $retval = $retval . ",\"configSchema\":" . join("", @obj);
+ open($fh, "<", $_[4]) or die "failed to open $_[4]";
+ @obj = <$fh>;
+ close($fh);
+ $retval = $retval . ",\"configMap\":" . join("", @obj) . "}";
+}
+
+sub do_config_add(@) {
+ my $paramCount;
+
+ $paramCount = validate_config(@_);
+ if ($paramCount > 0) {
+ my $xAppConfig;
+ if ($paramCount == 1) {
+ $xAppConfig = "\@$_[0]";
+ }
+ else {
+ $xAppConfig = make_xAppConfig(@_);
+ }
+ make_temps();
+ if (rest("POST", $base_config, $xAppConfig)) {
+ if ($http_code eq "201") {
+ print_json $resultfile;
+ $status = 0;
+ }
+ elsif ($http_code eq "422") { # Validation failed, details in result
+ print_json $resultfile;
+ $status = 1;
+ }
+ else {
+ my $error;
+ if ($http_code eq "400") {
+ $error = "INVALID INPUT";
+ }
+ elsif ($http_code eq "500") {
+ $error = "INTERNAL ERROR";
+ }
+ else {
+ $error = "UNKNOWN STATUS $http_code";
+ }
+ print "$error\n";
+ $status = 1;
+ }
+ }
+ else {
+ $status=1;
+ }
+ remove_temps();
+ }
+ else {
+ $status = 1;
+ }
+}
+
+sub do_config_modify(@) {
+ my $paramCount;
+
+ $paramCount = validate_config(@_);
+ if ($paramCount > 0) {
+ my $xAppConfig;
+ if ($paramCount == 1) {
+ $xAppConfig = "\@$_[0]";
+ }
+ else {
+ $xAppConfig = make_xAppConfig(@_);
+ }
+ make_temps();
+ if (rest("PUT", $base_config, $xAppConfig)) {
+ if ($http_code eq "200") {
+ print_json $resultfile;
+ $status = 0;
+ }
+ elsif ($http_code eq "422") { # Validation failed, details in result
+ print_json $resultfile;
+ $status = 1;
+ }
+ else {
+ my $error;
+ if ($http_code eq "400") {
+ $error = "INVALID INPUT";
+ }
+ elsif ($http_code eq "500") {
+ $error = "INTERNAL ERROR";
+ }
+ else {
+ $error = "UNKNOWN STATUS $http_code";
+ }
+ print "$error\n";
+ $status = 1;
+ }
+ }
+ else {
+ $status=1;
+ }
+ remove_temps();
+ }
+ else {
+ $status = 1;
+ }
+}
+
+# In config delete, allow either 1 parameter naming a file that contains
+# a JSON xAppConfigInfo object, or 3 parameters giving the
+# components (xAppName, configMapName, namespace), same as
+# in add and modify operations.
+
+sub do_config_delete(@) {
+ my $xAppConfigInfo = "";
+
+ if ($#_ != 0 and $#_ != 2) {
+ print "$myname: wrong number of parameters for config delete\n";
+ $status = 1;
+ }
+ elsif ($#_ == 0) {
+ if (-r $_[0]) {
+ $xAppConfigInfo = "\@$_[0]";
+ }
+ else {
+ print "$myname: config file $_[0] cannot be read: $!\n";
+ $status = 1;
+ }
+ }
+ elsif (($#_ == 2) && validate_config_names(@_)) {
+ $xAppConfigInfo = make_xAppConfigInfo($_[0],$_[1],$_[2]);
+ }
+ else {
+ print "$myname: bad parameters for config delete\n";
+ $status = 1;
+ }
+ if ($xAppConfigInfo ne "") {
+ make_temps();
+ if (rest("DELETE", $base_config, $xAppConfigInfo)) {
+ if ($http_code eq "204") {
+ print "SUCCESFUL DELETION OF CONFIG\n";
+ $status = 0;
+ }
+ else {
+ my $error;
+ if ($http_code eq "400") {
+ $error = "INVALID PARAMETERS SUPPLIED";
+ }
+ elsif ($http_code eq "500") {
+ $error = "INTERNAL ERROR";
+ }
+ else {
+ $error = "UNKNOWN STATUS $http_code";
+ }
+ print "$error\n";
+ $status = 1;
+ }
+ }
+ else {
+ $status=1;
+ }
+ remove_temps();
+ }
+}