First commit for xapp_onboarder that onboards xApp 19/2719/22
authorZhe Huang <zhehuang@research.att.com>
Mon, 9 Mar 2020 18:51:27 +0000 (14:51 -0400)
committerZhe <zhehuang@research.att.com>
Mon, 23 Mar 2020 16:40:56 +0000 (12:40 -0400)
Signed-off-by: Zhe Huang <zhehuang@research.att.com>
Change-Id: If824324a77d646c4742793eb8c48c7e033dc1cbe

56 files changed:
.gitignore [new file with mode: 0644]
LICENSES.txt
bin/onboard-xapps [deleted file]
bin/prepull-xapp-charts [deleted file]
ci/xapp_onboarder/Dockerfile [new file with mode: 0644]
ci/xapp_onboarder/container-tag.yaml [new file with mode: 0644]
ric-xapps/bin/install [deleted file]
ric-xapps/helm/xapp-std/.helmignore [deleted file]
ric-xapps/helm/xapp-std/templates/appconfig.yaml [deleted file]
ric-xapps/helm/xapp-std/values.yaml [deleted file]
xapp_onboarder/MANIFEST.in [new file with mode: 0644]
xapp_onboarder/README.md [new file with mode: 0644]
xapp_onboarder/requirements.txt [new file with mode: 0644]
xapp_onboarder/setup.cfg [new file with mode: 0644]
xapp_onboarder/setup.py [new file with mode: 0644]
xapp_onboarder/tests/__init__.py [new file with mode: 0644]
xapp_onboarder/tests/conftest.py [new file with mode: 0644]
xapp_onboarder/tests/constants.py [new file with mode: 0644]
xapp_onboarder/tests/mock_helm_repo/__init__.py [new file with mode: 0644]
xapp_onboarder/tests/mock_helm_repo/mock_helm_repo.py [new file with mode: 0755]
xapp_onboarder/tests/test_api.py [new file with mode: 0644]
xapp_onboarder/tests/test_helm_controller.py [new file with mode: 0644]
xapp_onboarder/tests/test_trim_thread.py [new file with mode: 0644]
xapp_onboarder/tox.ini [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/__init__.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/api/__init__.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/api/api_reference.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/api/charts.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/api/endpoints/__init__.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/api/endpoints/charts_ep.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/api/endpoints/health_check_ep.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/api/endpoints/onboard_ep.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/api/models/__init__.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/api/models/request_models.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/api/models/response_models.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/api/onboard.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/cli [new file with mode: 0755]
xapp_onboarder/xapp_onboarder/helm_controller/__init__.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/helm_controller/artifacts_manager.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/helm_controller/xApp_builder.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/logging.conf [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/repo_manager/__init__.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/repo_manager/repo_manager.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/resources/xapp-std/Chart.yaml [moved from ric-xapps/helm/xapp-std/Chart.yaml with 100% similarity]
xapp_onboarder/xapp_onboarder/resources/xapp-std/templates/_helpers.tpl [moved from ric-xapps/helm/xapp-std/templates/_helpers.tpl with 89% similarity]
xapp_onboarder/xapp_onboarder/resources/xapp-std/templates/appconfig.yaml [moved from ric-xapps/etc/xapp.conf with 90% similarity]
xapp_onboarder/xapp_onboarder/resources/xapp-std/templates/appenv.yaml [moved from ric-xapps/helm/xapp-std/templates/appenv.yaml with 79% similarity]
xapp_onboarder/xapp_onboarder/resources/xapp-std/templates/deployment.yaml [moved from ric-xapps/helm/xapp-std/templates/deployment.yaml with 69% similarity]
xapp_onboarder/xapp_onboarder/resources/xapp-std/templates/service-http.yaml [moved from ric-xapps/helm/xapp-std/templates/service-http.yaml with 89% similarity]
xapp_onboarder/xapp_onboarder/resources/xapp-std/templates/service-rmr.yaml [moved from ric-xapps/helm/xapp-std/templates/service-rmr.yaml with 82% similarity]
xapp_onboarder/xapp_onboarder/resources/xapp-std/values.yaml [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/server/__init__.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/server/cli.py [new file with mode: 0755]
xapp_onboarder/xapp_onboarder/server/server.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/server/settings.py [new file with mode: 0644]
xapp_onboarder/xapp_onboarder/xapp_onboarder [new file with mode: 0755]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..e68fbe1
--- /dev/null
@@ -0,0 +1,147 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# Pycharm project config
+.idea/
+
+# VIM swap file
+*.swp
+
+# Mac OS
+.DS_Store
index 2a3dd5c..a9db3d0 100644 (file)
@@ -29,4 +29,3 @@ distributed under the Documentation License is distributed on an "AS IS"
 BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 implied. See the Documentation License for the specific language governing
 permissions and limitations under the Documentation License.
-
diff --git a/bin/onboard-xapps b/bin/onboard-xapps
deleted file mode 100755 (executable)
index b38699f..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-#!/bin/bash
-##############################################################################
-#
-#   Copyright (c) 2019 AT&T Intellectual Property.
-#
-#   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.
-#
-##############################################################################
-
-# Installs well-known RIC charts then verifies specified helm chart
-# Requires chart tgz archives in /tmp
-
-
-while [ -n "$1" ]; do # while loop starts
-
-    case "$1" in
-
-    -d) DOCKER_REGISTRY=$2
-        shift
-        ;;
-
-    -p) CHART_DIRECTORY_PATH=$2
-       shift
-       ;;
-
-    -h) HELM_REPO=$2
-       shift
-       ;;
-
-    *) echo "Option $1 not recognized. Please specify the docker registry path with the -d option and specify the xApp directory path with the -p option."
-       ;; # In case you typed a different option other than -d or -p
-
-    esac
-
-    shift
-
-done
-
-if [ -z "$DOCKER_REGISTRY" ]; then
-   echo "Please specify the docker registry path with the -d option."
-   exit 1
-fi
-
-if [ -z "$CHART_DIRECTORY_PATH" ]; then
-   echo "Please specify the xApp directory path with the -p option."
-   exit 1
-fi
-
-if [ -z "$HELM_REPO" ]; then
-   echo "Please specify the helm repo uploading URL with the -h option."
-   exit 1
-fi
-
-
-echo "************************************************************"
-
-for image in $CHART_DIRECTORY_PATH/docker_images/*/*; do
-       echo "Loading image $image"
-       OUTPUT=$(docker load -i $image)
-       IMAGE_PATH_ORIGINAL=$(echo $OUTPUT | grep image: | awk '{print $3}' )
-       IMAGENAME=$(echo $IMAGE_PATH_ORIGINAL | awk '{ n=split($0, a, "/"); print a[n] }')
-       echo "Pushing image $DOCKER_REGISTRY/$IMAGENAME"
-        docker tag $IMAGE_PATH_ORIGINAL $DOCKER_REGISTRY/$IMAGENAME
-       docker push $DOCKER_REGISTRY/$IMAGENAME || { echo "Failed to push the docker image." ; exit 1; }
-
-done
-
-rm -rf $CHART_DIRECTORY_PATH/chart_packages
-mkdir -p $CHART_DIRECTORY_PATH/chart_packages
-
-echo "************************************************************"
-for chart in $CHART_DIRECTORY_PATH/helm_charts/*; do
-        echo "Onboard Helm Charts"
-
-       sed -i "s/^    repository: .*/    repository: $DOCKER_REGISTRY/" $chart/values.yaml
-        helm package $chart -d $CHART_DIRECTORY_PATH/chart_packages
-
-done
-
-
-
-echo "************************************************************"
-for tarball in $CHART_DIRECTORY_PATH/chart_packages/*; do
-       TARBALL_FILE_NAME=$(echo $tarball | awk -F '/' '{print $NF}')
-       CHART_NAME=$(echo $TARBALL_FILE_NAME |  awk -F '-' '{gsub($NF, ""); print substr($0,1,length($0)-1)}')
-       CHART_VERSION=$(echo $TARBALL_FILE_NAME |  awk -F '-' '{print substr ($NF,1,length($NF)-4)}')
-
-       DELETE_MESSAGE=$(curl -X DELETE --connect-timeout 2 $HELM_REPO/api/charts/$CHART_NAME/$CHART_VERSION 2>/dev/null)
-       LOCATE_CHART=$(curl --connect-timeout 2 $HELM_REPO/index.yaml |& grep $TARBALL_FILE_NAME)
-
-       if [ ! -z "$LOCATE_CHART" ]; then
-           echo "ERROR: Helm chart delete fail."
-           echo $DELETE_MESSAGE
-           exit 1
-        fi
-
-       UPLOAD_MESSAGE=$(curl --data-binary "@$tarball" --connect-timeout 2 $HELM_REPO/api/charts  2>/dev/null)
-       LOCATE_CHART=$(curl --connect-timeout 2 $HELM_REPO/index.yaml |& grep $TARBALL_FILE_NAME )
-
-       if [ -z "$LOCATE_CHART" ]; then
-           echo "ERROR: Helm chart upload failed."
-           echo $UPLOAD_MESSAGE
-           exit 1
-        fi
-
-       echo "Helm chart $CHART_NAME version $CHART_VERSION uploaded successfully."
-
-done
diff --git a/bin/prepull-xapp-charts b/bin/prepull-xapp-charts
deleted file mode 100755 (executable)
index 1ad05ff..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-#!/bin/bash
-##############################################################################
-#
-#   Copyright (c) 2019 AT&T Intellectual Property.
-#
-#   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.
-#
-##############################################################################
-
-# Installs well-known RIC charts then verifies specified helm chart
-# Requires chart tgz archives in /tmp
-
-
-while [ -n "$1" ]; do # while loop starts
-
-    case "$1" in
-
-    -f) XAPPLISTFILE=$2
-        shift
-        ;; 
-
-    -d) CHART_DIRECTORY_PATH=$2
-        shift
-        ;;
-
-
-    *) echo "Option $1 not recognized. Please use -f to specify the recipe path." ;; # In case you typed a different option other than a,b,c
-
-    esac
-
-    shift
-
-done
-
-if [ -z "$XAPPLISTFILE" ]; then
-   echo "xApp list file is missing. Please use -f to specify the path."
-   exit 1
-fi
-
-if [ -z "$CHART_DIRECTORY_PATH" ]; then
-    CHART_DIRECTORY_PATH=/tmp/xapp_charts
-fi
-
-
-rm -rf $CHART_DIRECTORY_PATH
-mkdir -p $CHART_DIRECTORY_PATH/helm_charts
-mkdir -p $CHART_DIRECTORY_PATH/docker_images
-
-
-while IFS= read -r chart
-do
-    CHARTNAME=$(echo $chart | awk '{ n=split($0, a, "/"); split(a[n],b,":"); print b[1] }')
-    CHARTVERSION=$(echo $chart | awk '{ n=split($0, a, "/"); split(a[n],b,":"); print b[2] }')
-    HELM_REPO=$(echo $chart | awk -F'/' '{gsub($NF,""); print substr($0,1,length($0)-1)}')
-     
-    echo "Fetching helm charts $CHARTNAME version $CHARTVERSION from repo $HELM_REPO"
-    helm repo add temp $HELM_REPO > /dev/null
-    helm fetch temp/$CHARTNAME --version $CHARTVERSION -d $CHART_DIRECTORY_PATH/helm_charts --untar
-    helm repo remove temp > /dev/null
-
-
-done < "$XAPPLISTFILE"
-
-echo "************************************************************"
-for chart in $CHART_DIRECTORY_PATH/helm_charts/*; do
-
-
-    CHART_NAME=$(echo $chart | awk -F '/' '{print $NF}')
-    mkdir -p $CHART_DIRECTORY_PATH/docker_images/$CHART_NAME
-
-    IMAGE_ARRAY=$(helm template $chart | grep "image:" | awk '{ gsub(/.*image: /, "", $0); gsub(/"/, "", $0); print $0}' )
-
-    while read -r image; do
-
-        IMAGENAME=$(echo $image | awk '{ n=split($0, a, "/"); print a[n] }')
-        echo "Pulling image $image"
-        RESULT=$(docker pull $image |& grep "no basic auth credentials" )
-        if [ ! -z "$RESULT" ]; then
-            echo "You are not logined to docker registry. Please login by running \"docker login DOCKER_REGISTRY\""
-            exit 1
-        fi
-    
-        echo "Saving image $image"
-        docker save $image -o $CHART_DIRECTORY_PATH/docker_images/$CHART_NAME/$IMAGENAME
-
-
-
-
-    done <<< "$IMAGE_ARRAY"
-
-
-done
-
-
-
-
-echo "************************************************************"
-echo "xApp helm charts are downloaded to: $CHART_DIRECTORY_PATH"
diff --git a/ci/xapp_onboarder/Dockerfile b/ci/xapp_onboarder/Dockerfile
new file mode 100644 (file)
index 0000000..d9c9d3b
--- /dev/null
@@ -0,0 +1,27 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   Licensed under the Apache License, Version 2.0 (the "License");            #
+#   you may not use this file except in compliance with the License.           #
+#   You may obtain a copy of the License at                                    #
+#                                                                              #
+#       http://www.apache.org/licenses/LICENSE-2.0                             #
+#                                                                              #
+#   Unless required by applicable law or agreed to in writing, software        #
+#   distributed under the License is distributed on an "AS IS" BASIS,          #
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
+#   See the License for the specific language governing permissions and        #
+#   limitations under the License.                                             #
+################################################################################
+
+FROM python:3.7-alpine
+
+ADD ./xapp_onboarder /opt/xapp_onboarder
+
+RUN cd /opt/xapp_onboarder && pip3 install . && ln -s $(pip show xapp_onboarder | grep Location | awk '{printf  $2 "/xapp_onboarder/xapp_onboarder"}') /usr/local/bin/xapp_onboarder && ln -s $(pip show xapp_onboarder | grep Location | awk '{printf  $2 "/xapp_onboarder/cli"}') /usr/local/bin/cli && rm -r /opt/xapp_onboarder
+
+ARG HELM_VERSION="2.12.3"
+
+RUN wget https://storage.googleapis.com/kubernetes-helm/helm-v${HELM_VERSION}-linux-amd64.tar.gz && tar -xvf helm-v${HELM_VERSION}-linux-amd64.tar.gz && mv linux-amd64/helm /usr/local/bin/helm && rm -r linux-amd64 && rm helm-v${HELM_VERSION}-linux-amd64.tar.gz
+
+ENTRYPOINT ["xapp_onboarder"]
diff --git a/ci/xapp_onboarder/container-tag.yaml b/ci/xapp_onboarder/container-tag.yaml
new file mode 100644 (file)
index 0000000..b7b4825
--- /dev/null
@@ -0,0 +1 @@
+tag: 1.0.0
diff --git a/ric-xapps/bin/install b/ric-xapps/bin/install
deleted file mode 100755 (executable)
index cfb81d7..0000000
+++ /dev/null
@@ -1,169 +0,0 @@
-#!/bin/bash
-################################################################################
-#   Copyright (c) 2019 AT&T Intellectual Property.                             #
-#                                                                              #
-#   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.                                             #
-################################################################################
-
-
-while [ -n "$1" ]; do # while loop starts
-
-    case "$1" in
-
-    -n) 
-       CHART_NAME=$2
-       shift
-       ;; 
-
-    -v) CHART_VERSION=$2
-       shift
-               ;; # Message for -b option
-
-    -f) OVERRIDEYAML=$2
-       shift
-       ;; # Message for -c option
-    
-    -i) FULLIMAGE=$2
-       shift
-       ;;
-
-    -d) DESCRIPTOR_PATH=$2
-       shift
-       ;;
-
-    -c) CONFIG_JSON_PATH=$2
-       shift
-       ;;
-
-    -h) HELM_REPO_USERNAME=$2
-        shift
-        ;;
-    
-    -p) HELM_REPO_PASSWORD=$2
-        shift
-        ;;
-
-    *) echo "Option $1 not recognized" ;; # In case you typed a different option other than a,b,c
-
-    esac
-
-    shift
-
-done
-
-
-
-
-if [ -z $CHART_NAME ]; then
-  echo "Please specify chart name using -n option."
-  exit 1
-fi
-if [ -z $CHART_VERSION ]; then
-  echo "Please specify chart version using -v option."
-  exit 1
-fi
-if [ -z $FULLIMAGE ]; then
-  echo "Please specify image using -i option."
-  exit 1
-fi
-if [ -z $DESCRIPTOR_PATH ]; then
-  echo "Please specify descriptor file using -d option."
-  exit 1
-fi
-if [ -z $CONFIG_JSON_PATH ]; then
-  echo "Please specify config json file using -c option."
-  exit 1
-fi
-
-
-if [ ! -f $DESCRIPTOR_PATH ]; then
-  echo "Descriptor file cannot be founded at $DESCRIPTOR_PATH"
-  exit 1
-fi
-if [ ! -f $CONFIG_JSON_PATH ]; then
-  echo "Config json file cannot be founded at $CONFIG_JSON_PATH"
-  exit 1
-fi
-
-
-DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
-
-
-source $DIR/../etc/xapp.conf
-
-if [ -z $OVERRIDEYAML ]; then
-  HELM_REPO=$default_helm_repo
-  DOCKER_REGISTRY=$default_docker_registry
-else
-  helm_repo_override=$(grep "^ *helmRepository:" $OVERRIDEYAML |  awk '{gsub(/ /,""); gsub(/\"/,""); split($0, b, "tory:");split(b[2],c,"#"); print c[1]}')
-  docker_reg_override=$(grep "^ *repository:" $OVERRIDEYAML |  awk '{ gsub(/ /,""); gsub(/\"/,""); split($0, b, "tory:");split(b[2],c,"#"); print c[1]}')
-  if [ -z $helm_repo_override ]; then
-    HELM_REPO=$default_helm_repo
-  else
-    HELM_REPO=$helm_repo_override
-  fi
-
-  if [ -z $docker_reg_override ]; then
-    DOCKER_REGISTRY=$default_docker_registry
-  else
-    DOCKER_REGISTRY=$docker_reg_override
-  fi
-fi
-
-
-
-
-rm -rf /tmp/$CHART_NAME
-
-cp -r $DIR/../helm/xapp-std/ /tmp/$CHART_NAME
-
-
-
-sed -i "s/^name: xapp-std/name: $CHART_NAME/" /tmp/$CHART_NAME/Chart.yaml
-sed -i "s/^version: 0.0.1/version: $CHART_VERSION/" /tmp/$CHART_NAME/Chart.yaml
-
-
-registry_path=$(echo $FULLIMAGE | awk '{n=split($0, a, "/"); if(n>1) print a[1]}')
-
-
-
-tag=$(echo $FULLIMAGE | awk '{n=split($0, a, "/"); split(a[n], b, ":"); print b[2]}')
-
-image=$(echo $FULLIMAGE | awk -v head="$registry_path/" -v tail=":$tag" '{gsub (head, ""); gsub(tail,""); gsub(/\//,"\\/"); print $0}') 
-
-
-sed -i "s/^  name: xapp-std/  name: $CHART_NAME/" /tmp/$CHART_NAME/values.yaml
-sed -i "s/^    name: xapp-std/    name: $image/" /tmp/$CHART_NAME/values.yaml
-sed -i "s/^    tag: latest/    tag: $tag/" /tmp/$CHART_NAME/values.yaml
-
-
-if [ -z $registry_path ]; then
-  sed -i "s/^    repository: xapp-std-reg/    repository: $DOCKER_REGISTRY/" /tmp/$CHART_NAME/values.yaml
-else
-  sed -i "s/^    repository: xapp-std-reg/    repository: $registry_path/" /tmp/$CHART_NAME/values.yaml
-fi
-
-
-mkdir /tmp/$CHART_NAME/config/
-mkdir /tmp/$CHART_NAME/descriptors/
-
-cp $CONFIG_JSON_PATH /tmp/$CHART_NAME/config/
-cp $DESCRIPTOR_PATH /tmp/$CHART_NAME/descriptors/
-
-
-helm package -d /tmp /tmp/$CHART_NAME
-
-
-echo $HELM_REPO
-#curl -k -u $HELM_REPO_USERNAME:$HELM_REPO_PASSWORD $HELM_REPO --upload-file /tmp/$CHART_NAME-$CHART_VERSION.tgz -v
-curl -Lk -u $HELM_REPO_USERNAME:$HELM_REPO_PASSWORD "$HELM_REPO"/api/charts --data-binary "@/tmp/$CHART_NAME-$CHART_VERSION.tgz"
diff --git a/ric-xapps/helm/xapp-std/.helmignore b/ric-xapps/helm/xapp-std/.helmignore
deleted file mode 100644 (file)
index 50af031..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-# Patterns to ignore when building packages.
-# This supports shell glob matching, relative path matching, and
-# negation (prefixed with !). Only one pattern per line.
-.DS_Store
-# Common VCS dirs
-.git/
-.gitignore
-.bzr/
-.bzrignore
-.hg/
-.hgignore
-.svn/
-# Common backup files
-*.swp
-*.bak
-*.tmp
-*~
-# Various IDEs
-.project
-.idea/
-*.tmproj
-.vscode/
diff --git a/ric-xapps/helm/xapp-std/templates/appconfig.yaml b/ric-xapps/helm/xapp-std/templates/appconfig.yaml
deleted file mode 100644 (file)
index 5e5463c..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-################################################################################
-#   Copyright (c) 2019 AT&T Intellectual Property.                             #
-#                                                                              #
-#   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.                                             #
-################################################################################'
-
-apiVersion: v1
-kind: ConfigMap
-metadata:
-  name: {{ include "ricxapp.configmapname" . }}-appconfig
-data:
-{{- $dbaasservice := .Values.ricplt.dbaasService | quote -}}
-{{- $pltingressurl := .Values.ricplt.pltIngressUrl | quote -}}
-{{- $xappingressurl := .Values.ricplt.xappIngressUrl | quote -}}
-{{- $appmgrrmrservice := .Values.ricplt.appmgrRMRService | quote -}}
-{{- $e2mgrrmrservice := .Values.ricplt.e2mgrRMRService | quote -}}
-{{- $e2termrmrservice := .Values.ricplt.e2termRMRService | quote -}}
-{{- $rtmgrrmrservice := .Values.ricplt.rtmgrRMRService | quote -}}
-{{- $a1mediatorrmrservice := .Values.ricplt.a1mediatorRMRService | quote -}}
-
-{{- (.Files.Glob "config/*").AsConfig | replace "__DBAAS_SERVICE__" $dbaasservice | replace "__PLT_INGRESS_URL__" $pltingressurl | replace "__XAPP_INGRESS_URL__" $xappingressurl | replace "__APPMGR_RMR_SERVICE__" $appmgrrmrservice | replace "__E2MGR_RMR_SERVICE__" $e2mgrrmrservice | replace "__E2TERM_RMR_SERVICE__" $e2termrmrservice | replace "__RTMGR_RMR_SERVICE__" $rtmgrrmrservice | replace "__A1MEDIATOR_RMR_SERVICE__" $a1mediatorrmrservice | nindent 2 }}
diff --git a/ric-xapps/helm/xapp-std/values.yaml b/ric-xapps/helm/xapp-std/values.yaml
deleted file mode 100644 (file)
index 1689a6a..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-################################################################################
-#   Copyright (c) 2019 AT&T Intellectual Property.                             #
-#                                                                              #
-#   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.                                             #
-################################################################################'
-# This is a YAML-formatted file.
-# Declare variables to be passed into your templates.
-ricplt:
-  # This section is reserved for values imported from RIC Platform charts
-  dbaasService: "service-ricplt-dbaas-tcp.ricplt.svc.cluster.local"
-  pltIngressUrl: "ricplt-entry"
-  xappIngressUrl: "ricxapp-entry"
-
-  appmgrRMRService: "service-ricplt-appmgr-rmr.ricplt.svc.cluster.local"
-  e2mgrRMRService: "service-ricplt-e2mgr-rmr.ricplt.svc.cluster.local"
-  e2termRMRService: "service-ricplt-e2term-rmr.ricplt.svc.cluster.local"
-  rtmgrRMRService: "service-ricplt-rtmgr-rmr.ricplt.svc.cluster.local"
-  a1mediatorRMRService: "service-ricplt-a1mediator-rmr.ricplt.svc.cluster.local"
-
-  
-
-ricxapp:
-  # This section is for xapp. Templates to be resolved from xApp descriptor
-  replicaCount: 1
-  name: xapp-std
-  # Your can specify the chart fullname by using the following option
-  #fullname: xapp-std
-
-  image:
-    pullPolicy: IfNotPresent
-    repository: xapp-std-reg
-    name: xapp-std
-    tag: latest
-
-  service:
-    http:
-      port: 8080
-      containerPort: 8080
-    rmr:
-      route:
-        port: 4561
-      data:
-        port: 4560
-
-  livenessProbe: |-
-    httpGet:
-      path: ric/v1/health/alive
-      port: 8080
-    initialDelaySeconds: 5
-    periodSeconds: 15
-
-  readinessProbe: |-
-    httpGet:
-      path: ric/v1/health/ready
-      port: 8080
-    initialDelaySeconds: 5
-    periodSeconds: 15
-
-
-  appconfig:
-    path: /opt/ric/config
-
-  appenv:
diff --git a/xapp_onboarder/MANIFEST.in b/xapp_onboarder/MANIFEST.in
new file mode 100644 (file)
index 0000000..0547a50
--- /dev/null
@@ -0,0 +1,10 @@
+recursive-include * *.py
+recursive-include * *.yaml
+recursive-include * *.tpl
+recursive-include * *.conf
+recursive-include * *.pickle
+include xapp_onboarder/cli
+include xapp_onboarder/xapp_onboarder
+include README.md
+include LICENSES.txt
+include requirements.txt
diff --git a/xapp_onboarder/README.md b/xapp_onboarder/README.md
new file mode 100644 (file)
index 0000000..c6fefad
--- /dev/null
@@ -0,0 +1,56 @@
+xApp Onboarder
+==============
+
+xApp onboarder onboards xApp to the near-rt RIC platform. The operators provides the xApp descriptors and their schemas, the xApp onboarder generates the xApp helm charts dynamically.
+
+## Install xapp_onboarder
+
+Run  [pip](https://pip.pypa.io/en/stable/) to install xapp_onboarder.
+
+```bash
+pip install xapp_onboarder
+```
+
+## Prerequisite Requirements
+A helm chart repo is needed to store the xApp helm charts. You can use [chartmuseum](https://github.com/helm/chartmuseum) for this purpose.
+Environment variables:
+* **FLASK_SERVER_NAME**: Address that the xapp_onboarder is listening on. Default http://0.0.0.0:8888
+* **CHART_REPO_URL**: helm chart repo URL. Default http://0.0.0.0:8080
+* **DBAAS_SERVICE_HOST**: DBAAS service host URL that will be injected into the xApp config
+* **DBAAS_MASTER_NAME**: DBAAS_HA sentinel master URL that will be injected into the xApp config
+* **DBAAS_SERVICE_SENTINEL_PORT**: DBAAS_HA sentinel port that will be injected into the xApp config
+* **DBAAS_SERVICE_PORT**: DBAAS service port that will be injected into the xApp config
+
+## Configurations
+Environment variables:
+* **CHART_WORKSPACE_PATH**: Temporary directory that will store the xApp helm chart artifacts. Default /tmp/xapp_onboarder
+* **CHART_WORKSPACE_SIZE**: Size limit of the temporary directory. Default 500MB
+* **ALLOW_REDEPLOY**: Enable or disable redeploying of xApp helm charts. Default True
+* **HTTP_TIME_OUT**: Timeout of all http requests. Default 10 
+* **HTTP_RETRY**: Number of retry xapp_onboarder will use for the http requests. Default 3
+
+## Run the API server
+```bash
+python3 -m xapp_onboarder.server.server
+```
+Or we recommend you can set up the symbolic link in your PATH
+```bash
+ln -s $(pip show xapp_onboarder | grep Location | awk '{printf  $2 "/xapp_onboarder/xapp_onboarder"}') /usr/local/bin/xapp_onboarder
+```
+Then you can run the server
+```bash
+xapp_onboarder
+```
+## Run the CLI tool
+```bash
+python3 -m xapp_onboarder.server.cli
+```
+Or we recommend you can set up the symbolic link in your PATH
+```bash
+ln -s $(pip show xapp_onboarder | grep Location | awk '{printf  $2 "/xapp_onboarder/cli"}') /usr/local/bin/cli
+```
+Then you can run the server
+```bash
+cli
+```
diff --git a/xapp_onboarder/requirements.txt b/xapp_onboarder/requirements.txt
new file mode 100644 (file)
index 0000000..bd8c85a
--- /dev/null
@@ -0,0 +1,23 @@
+aniso8601==8.0.0
+attrs==19.3.0
+certifi==2019.11.28
+chardet==3.0.4
+Click==7.0
+fire==0.2.1
+Flask==1.1.1
+flask-restplus==0.13.0
+idna==2.9
+importlib-metadata==1.5.0
+itsdangerous==1.1.0
+Jinja2==2.11.1
+jsonschema==3.2.0
+MarkupSafe==1.1.1
+pyrsistent==0.15.7
+pytz==2019.3
+PyYAML==5.3
+requests==2.23.0
+six==1.14.0
+termcolor==1.1.0
+urllib3==1.25.8
+Werkzeug==0.16.1
+zipp==3.0.0
diff --git a/xapp_onboarder/setup.cfg b/xapp_onboarder/setup.cfg
new file mode 100644 (file)
index 0000000..e1053b8
--- /dev/null
@@ -0,0 +1,19 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+[flake8]
+max-line-length = 160
+max-complexity = 10
diff --git a/xapp_onboarder/setup.py b/xapp_onboarder/setup.py
new file mode 100644 (file)
index 0000000..b6c9578
--- /dev/null
@@ -0,0 +1,44 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   Licensed under the Apache License, Version 2.0 (the "License");            #
+#   you may not use this file except in compliance with the License.           #
+#   You may obtain a copy of the License at                                    #
+#                                                                              #
+#       http://www.apache.org/licenses/LICENSE-2.0                             #
+#                                                                              #
+#   Unless required by applicable law or agreed to in writing, software        #
+#   distributed under the License is distributed on an "AS IS" BASIS,          #
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
+#   See the License for the specific language governing permissions and        #
+#   limitations under the License.                                             #
+################################################################################
+
+from setuptools import setup, find_packages
+
+with open("README.md", "r") as fh:
+    long_description = fh.read()
+
+with open('requirements.txt') as f:
+    requirements = f.read().splitlines()
+
+
+setup(
+    name='xapp_onboarder',
+    version='1.0.0',
+    description='RIC xApp onboarder',
+    long_description=long_description,
+    long_description_content_type="text/markdown",
+    url='https://gerrit.o-ran-sc.org/r/admin/repos/it/dev',
+    author='Zhe Huang',
+    author_email='zhehuang@research.att.com',
+    include_package_data=True,
+    packages=find_packages(),
+    package_data={'': ['*.yaml', '*.tpl', '*.conf', 'xapp_onboarder', 'cli']},
+    classifiers=[
+        "Programming Language :: Python :: 3",
+        "Operating System :: OS Independent",
+    ],
+    python_requires='>=3.6',
+    install_requires=requirements,
+)
diff --git a/xapp_onboarder/tests/__init__.py b/xapp_onboarder/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/xapp_onboarder/tests/conftest.py b/xapp_onboarder/tests/conftest.py
new file mode 100644 (file)
index 0000000..96b9877
--- /dev/null
@@ -0,0 +1,29 @@
+# tests/conftest.py
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+import pytest
+from flask import Flask
+from xapp_onboarder.server.server import server
+
+@pytest.fixture
+def app() -> Flask:
+    """ Provides an instance of xapp onboarder """
+    server_instance = server()
+    assert isinstance(server_instance.app, Flask)
+    return server_instance.app
+
+
diff --git a/xapp_onboarder/tests/constants.py b/xapp_onboarder/tests/constants.py
new file mode 100644 (file)
index 0000000..0a52b6a
--- /dev/null
@@ -0,0 +1,656 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+schema_file = {
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "$id": "http://example.com/root.json",
+    "type": "object",
+    "title": "The Root Schema",
+    "required": [
+        "local",
+        "logger",
+        "rmr",
+        "db",
+        "controls",
+        "metrics"
+    ],
+    "properties": {
+        "local": {
+            "$id": "#/properties/local",
+            "type": "object",
+            "title": "The Local Schema",
+            "required": [
+                "host"
+            ],
+            "properties": {
+                "host": {
+                    "$id": "#/properties/local/properties/host",
+                    "type": "string",
+                    "title": "The Host Schema",
+                    "default": "",
+                    "examples": [
+                        ":8080"
+                    ],
+                    "pattern": "^(.*)$"
+                }
+            }
+        },
+        "logger": {
+            "$id": "#/properties/logger",
+            "type": "object",
+            "title": "The Logger Schema",
+            "required": [
+                "level"
+            ],
+            "properties": {
+                "level": {
+                    "$id": "#/properties/logger/properties/level",
+                    "type": "integer",
+                    "title": "The Level Schema",
+                    "default": 0,
+                    "examples": [
+                        3
+                    ]
+                }
+            }
+        },
+        "rmr": {
+            "$id": "#/properties/rmr",
+            "type": "object",
+            "title": "The Rmr Schema",
+            "required": [
+                "protPort",
+                "maxSize",
+                "numWorkers",
+                "rxMessages",
+                "txMessages"
+            ],
+            "properties": {
+                "protPort": {
+                    "$id": "#/properties/rmr/properties/protPort",
+                    "type": "string",
+                    "title": "The Protport Schema",
+                    "default": "",
+                    "examples": [
+                        "tcp:4560"
+                    ],
+                    "pattern": "^(.*)$"
+                },
+                "maxSize": {
+                    "$id": "#/properties/rmr/properties/maxSize",
+                    "type": "integer",
+                    "title": "The Maxsize Schema",
+                    "default": 0,
+                    "examples": [
+                        2072
+                    ]
+                },
+                "numWorkers": {
+                    "$id": "#/properties/rmr/properties/numWorkers",
+                    "type": "integer",
+                    "title": "The Numworkers Schema",
+                    "default": 0,
+                    "examples": [
+                        1
+                    ]
+                },
+                "rxMessages": {
+                    "$id": "#/properties/rmr/properties/rxMessages",
+                    "type": "array",
+                    "title": "The Rxmessages Schema",
+                    "items": {
+                        "$id": "#/properties/rmr/properties/rxMessages/items",
+                        "type": "string",
+                        "title": "The Items Schema",
+                        "default": "",
+                        "examples": [
+                            "RIC_SUB_RESP",
+                            "RIC_SUB_FAILURE",
+                            "RIC_SUB_DEL_RESP",
+                            "RIC_SUB_DEL_FAILURE",
+                            "RIC_INDICATION"
+                        ],
+                        "pattern": "^(.*)$"
+                    }
+                },
+                "txMessages": {
+                    "$id": "#/properties/rmr/properties/txMessages",
+                    "type": "array",
+                    "title": "The Txmessages Schema",
+                    "items": {
+                        "$id": "#/properties/rmr/properties/txMessages/items",
+                        "type": "string",
+                        "title": "The Items Schema",
+                        "default": "",
+                        "examples": [
+                            "RIC_SUB_REQ",
+                            "RIC_SUB_DEL_REQ",
+                            "RIC_SGNB_ADDITION_REQ",
+                            "RIC_SGNB_ADDITION_ACK",
+                            "RIC_SGNB_ADDITION_REJECT",
+                            "RIC_SGNB_MOD_REQUEST",
+                            "RIC_SGNB_MOD_REQUEST_ACK",
+                            "RIC_SGNB_MOD_REQUEST_REJECT",
+                            "RIC_SGNB_MOD_REQUIRED",
+                            "RIC_SGNB_MOD_CONFIRM",
+                            "RIC_SGNB_MOD_REFUSE",
+                            "RIC_SGNB_RECONF_COMPLETE",
+                            "RIC_SGNB_RELEASE_REQUEST",
+                            "RIC_SGNB_RELEASE_CONFIRM",
+                            "RIC_SGNB_RELEASE_REQUIRED",
+                            "RIC_SGNB_RELEASE_REQUEST_ACK",
+                            "RIC_SECONDARY_RAT_DATA_USAGE_REPORT",
+                            "RIC_SN_STATUS_TRANSFER",
+                            "RIC_RRC_TRANSFER",
+                            "RIC_UE_CONTEXT_RELEASE"
+                        ],
+                        "pattern": "^(.*)$"
+                    }
+                }
+            }
+        },
+        "db": {
+            "$id": "#/properties/db",
+            "type": "object",
+            "title": "The Db Schema",
+            "required": [
+                "host",
+                "port",
+                "namespaces"
+            ],
+            "properties": {
+                "host": {
+                    "$id": "#/properties/db/properties/host",
+                    "type": "string",
+                    "title": "The Host Schema",
+                    "default": "",
+                    "examples": [
+                        "localhost"
+                    ],
+                    "pattern": "^(.*)$"
+                },
+                "port": {
+                    "$id": "#/properties/db/properties/port",
+                    "type": "integer",
+                    "title": "The Port Schema",
+                    "default": 0,
+                    "examples": [
+                        6379
+                    ]
+                },
+                "namespaces": {
+                    "$id": "#/properties/db/properties/namespaces",
+                    "type": "array",
+                    "title": "The Namespaces Schema",
+                    "items": {
+                        "$id": "#/properties/db/properties/namespaces/items",
+                        "type": "string",
+                        "title": "The Items Schema",
+                        "default": "",
+                        "examples": [
+                            "sdl",
+                            "rnib"
+                        ],
+                        "pattern": "^(.*)$"
+                    }
+                }
+            }
+        },
+        "controls": {
+            "$id": "#/properties/controls",
+            "type": "object",
+            "title": "The Controls Schema",
+            "required": [
+                "active",
+                "requestorId",
+                "ranFunctionId",
+                "ricActionId",
+                "interfaceId"
+            ],
+            "properties": {
+                "active": {
+                    "$id": "#/properties/controls/properties/active",
+                    "type": "boolean",
+                    "title": "The Active Schema",
+                    "default": False,
+                    "examples": [
+                        True
+                    ]
+                },
+                "requestorId": {
+                    "$id": "#/properties/controls/properties/requestorId",
+                    "type": "integer",
+                    "title": "The Requestorid Schema",
+                    "default": 0,
+                    "examples": [
+                        66
+                    ]
+                },
+                "ranFunctionId": {
+                    "$id": "#/properties/controls/properties/ranFunctionId",
+                    "type": "integer",
+                    "title": "The Ranfunctionid Schema",
+                    "default": 0,
+                    "examples": [
+                        1
+                    ]
+                },
+                "ricActionId": {
+                    "$id": "#/properties/controls/properties/ricActionId",
+                    "type": "integer",
+                    "title": "The Ricactionid Schema",
+                    "default": 0,
+                    "examples": [
+                        0
+                    ]
+                },
+                "interfaceId": {
+                    "$id": "#/properties/controls/properties/interfaceId",
+                    "type": "object",
+                    "title": "The Interfaceid Schema",
+                    "required": [
+                        "globalENBId"
+                    ],
+                    "properties": {
+                        "globalENBId": {
+                            "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId",
+                            "type": "object",
+                            "title": "The Globalenbid Schema",
+                            "required": [
+                                "plmnId",
+                                "eNBId"
+                            ],
+                            "properties": {
+                                "plmnId": {
+                                    "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/plmnId",
+                                    "type": "string",
+                                    "title": "The Plmnid Schema",
+                                    "default": "",
+                                    "examples": [
+                                        "310150"
+                                    ],
+                                    "pattern": "^(.*)$"
+                                },
+                                "eNBId": {
+                                    "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/eNBId",
+                                    "type": "integer",
+                                    "title": "The Enbid Schema",
+                                    "default": 0,
+                                    "examples": [
+                                        202251
+                                    ]
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        },
+        "metrics": {
+            "$id": "#/properties/metrics",
+            "type": "array",
+            "title": "The Metrics Schema",
+            "items": {
+                "$id": "#/properties/metrics/items",
+                "type": "object",
+                "title": "The Items Schema",
+                "required": [
+                    "objectName",
+                    "objectInstance",
+                    "name",
+                    "type",
+                    "description"
+                ],
+                "properties": {
+                    "objectName": {
+                        "$id": "#/properties/metrics/items/properties/objectName",
+                        "type": "string",
+                        "title": "The Objectname Schema",
+                        "default": "",
+                        "examples": [
+                            "UEEventStreamingCounters"
+                        ],
+                        "pattern": "^(.*)$"
+                    },
+                    "objectInstance": {
+                        "$id": "#/properties/metrics/items/properties/objectInstance",
+                        "type": "string",
+                        "title": "The Objectinstance Schema",
+                        "default": "",
+                        "examples": [
+                            "SgNBAdditionRequest"
+                        ],
+                        "pattern": "^(.*)$"
+                    },
+                    "name": {
+                        "$id": "#/properties/metrics/items/properties/name",
+                        "type": "string",
+                        "title": "The Name Schema",
+                        "default": "",
+                        "examples": [
+                            "SgNBAdditionRequest"
+                        ],
+                        "pattern": "^(.*)$"
+                    },
+                    "type": {
+                        "$id": "#/properties/metrics/items/properties/type",
+                        "type": "string",
+                        "title": "The Type Schema",
+                        "default": "",
+                        "examples": [
+                            "counter"
+                        ],
+                        "pattern": "^(.*)$"
+                    },
+                    "description": {
+                        "$id": "#/properties/metrics/items/properties/description",
+                        "type": "string",
+                        "title": "The Description Schema",
+                        "default": "",
+                        "examples": [
+                            "The total number of SG addition request events processed"
+                        ],
+                        "pattern": "^(.*)$"
+                    }
+                }
+            }
+        }
+    }
+}
+
+config_file = {
+    "xapp_name": "test_xapp",
+    "version": "1.0.0",
+    "containers": [{
+        "name": "test1",
+        "image": {
+            "registry": "test_repo",
+            "name": "test_name",
+            "tag": "test_tag"
+        },
+        "command": "test command"
+    },
+        {
+            "name": "test2",
+            "image": {
+                "registry": "test2_repo",
+                "name": "test2_name",
+                "tag": "test2:_tag"
+            },
+            "command": "test2 command"
+        }],
+    "local": {
+        "host": ":8080"
+    },
+    "logger": {
+        "level": 3
+    },
+    "db": {
+        "host": "localhost",
+        "port": 6379,
+        "namespaces": ["sdl", "rnib"]
+    },
+    "controls": {
+        "active": True,
+        "requestorId": 66,
+        "ranFunctionId": 1,
+        "ricActionId": 0,
+        "interfaceId": {
+            "globalENBId": {
+                "plmnId": "310150",
+                "eNBId": 202251
+            }
+        }
+    },
+    "rmr": {
+        "protPort": "tcp:4560",
+        "maxSize": 10000,
+        "numWorkers": 1,
+        "rxMessages": [
+            "RIC_SUB_RESP",
+            "RIC_SUB_FAILURE",
+            "RIC_SUB_DEL_RESP",
+            "RIC_SUB_DEL_FAILURE",
+            "RIC_INDICATION"
+        ],
+        "txMessages": [
+            "RIC_SUB_REQ",
+            "RIC_SUB_DEL_REQ",
+            "RIC_SGNB_ADDITION_REQ",
+            "RIC_SGNB_ADDITION_ACK",
+            "RIC_SGNB_ADDITION_REJECT",
+            "RIC_SGNB_MOD_REQUEST",
+            "RIC_SGNB_MOD_REQUEST_ACK",
+            "RIC_SGNB_MOD_REQUEST_REJECT",
+            "RIC_SGNB_MOD_REQUIRED",
+            "RIC_SGNB_MOD_CONFIRM",
+            "RIC_SGNB_MOD_REFUSE",
+            "RIC_SGNB_RELEASE_REQUEST",
+            "RIC_SGNB_RELEASE_CONFIRM",
+            "RIC_SGNB_RELEASE_REQUIRED",
+            "RIC_SGNB_RELEASE_REQUEST_ACK",
+            "RIC_SGNB_RECONF_COMPLETE",
+            "RIC_UE_CONTEXT_RELEASE",
+            "RIC_RRC_TRANSFER",
+            "RIC_SECONDARY_RAT_DATA_USAGE_REPORT",
+            "RIC_SN_STATUS_TRANSFER"
+        ]
+    },
+    "metrics": [
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBAdditionRequest",
+            "name": "SgNBAdditionRequest",
+            "type": "counter",
+            "description": "The total number of SG addition request events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBAdditionRequestAcknowledge",
+            "name": "SgNBAdditionRequestAcknowledge",
+            "type": "counter",
+            "description": "The total number of SG addition request acknowledge events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBAdditionRequestReject",
+            "name": "SgNBAdditionRequestReject",
+            "type": "counter",
+            "description": "The total number of SG addition request reject events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBModificationRequest",
+            "name": "SgNBModificationRequest",
+            "type": "counter",
+            "description": "The total number of SG modification request events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBModificationRequestAcknowledge",
+            "name": "SgNBModificationRequestAcknowledge",
+            "type": "counter",
+            "description": "The total number of SG modification request acknowledge events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBModificationRequestReject",
+            "name": "SgNBModificationRequestReject",
+            "type": "counter",
+            "description": "The total number of SG modification request reject events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBModificationRequired",
+            "name": "SgNBModificationRequired",
+            "type": "counter",
+            "description": "The total number of SG modification required events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBModificationConfirm",
+            "name": "SgNBModificationConfirm",
+            "type": "counter",
+            "description": "The total number of SG modification confirm events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBModificationRefuse",
+            "name": "SgNBModificationRefuse",
+            "type": "counter",
+            "description": "The total number of SG modification refuse events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBReleaseRequest",
+            "name": "SgNBReleaseRequest",
+            "type": "counter",
+            "description": "The total number of SG release request events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBReleaseRequestAcknowledge",
+            "name": "SgNBReleaseRequestAcknowledge",
+            "type": "counter",
+            "description": "The total number of SG release request acknowledge events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBReleaseRequestReject",
+            "name": "SgNBReleaseRequestReject",
+            "type": "counter",
+            "description": "The total number of SG release request reject events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBReleaseRequired",
+            "name": "SgNBReleaseRequired",
+            "type": "counter",
+            "description": "The total number of SG release required events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBReleasenConfirm",
+            "name": "SgNBReleasenConfirm",
+            "type": "counter",
+            "description": "The total number of SG release confirm events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SgNBReconfigurationComplete",
+            "name": "SgNBReconfigurationComplete",
+            "type": "counter",
+            "description": "The total number of SG reconfiguration complete events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "UEContextRelease",
+            "name": "UEContextRelease",
+            "type": "counter",
+            "description": "The total number of SG UE context release events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "RRCTransfer",
+            "name": "RRCTransfer",
+            "type": "counter",
+            "description": "The total number of SG RRC transfers events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SNStatusTransfer",
+            "name": "SNStatusTransfer",
+            "type": "counter",
+            "description": "The total number of SG SN status transfers events processed"
+        },
+        {
+            "objectName": "UEEventStreamingCounters",
+            "objectInstance": "SecondaryRATDataUsageReport",
+            "name": "SecondaryRATDataUsageReport",
+            "type": "counter",
+            "description": "The total number of SG secondary RAT data usage reports events processed"
+        },
+        {
+            "objectName": "RMRCounters",
+            "objectInstance": "Transmitted",
+            "name": "Transmitted",
+            "type": "counter",
+            "description": "The total number of RMR messages transmited"
+        },
+        {
+            "objectName": "RMRCounters",
+            "objectInstance": "Received",
+            "name": "Received",
+            "type": "counter",
+            "description": "The total number of RMR messages received"
+        },
+        {
+            "objectName": "RMRCounters",
+            "objectInstance": "TransmitError",
+            "name": "TransmitError",
+            "type": "counter",
+            "description": "The total number of RMR messages transmission errors"
+        },
+        {
+            "objectName": "RMRCounters",
+            "objectInstance": "ReceiveError",
+            "name": "ReceiveError",
+            "type": "counter",
+            "description": "The total number of RMR messages receive errors"
+        },
+        {
+            "objectName": "SDLounters",
+            "objectInstance": "Stored",
+            "name": "Stored",
+            "type": "counter",
+            "description": "The total number of stored SDL transactions"
+        },
+        {
+            "objectName": "SDLounters",
+            "objectInstance": "StoreError",
+            "name": "StoreError",
+            "type": "counter",
+            "description": "The total number of SDL store errors"
+        }
+    ]
+}
+
+mock_json_body_url = {
+    'config-file.json_url': 'http://0.0.0.0:8080/config-file.json',
+    'schema.json_url': 'http://0.0.0.0:8080/schema.json'
+}
+
+mock_json_body = {
+    "config-file.json": config_file,
+    "schema.json": schema_file
+}
+
+helm_repo_index_response={'apiVersion': 'v1',
+                'entries':{
+                    'test_xapp':[{
+                        'apiVersion': 'v1',
+                        'appVersion': '1.0',
+                        'created': '2020-03-12T19:10:17.178396719Z',
+                        'description': 'test xApp Helm Chart',
+                        'digest': 'd77dfb3f008e5174e90d79bfe982ef85b5dc5930141f6a1bd9995b2fa35',
+                        'name': 'test_xapp',
+                        'urls':['charts/test-1.0.0.tgz'],
+                        'version': '1.0.0'
+                    }]
+                },
+                'generated': '2020-03-16T16:54:44Z',
+                'serverInfo':{}
+                }
diff --git a/xapp_onboarder/tests/mock_helm_repo/__init__.py b/xapp_onboarder/tests/mock_helm_repo/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/xapp_onboarder/tests/mock_helm_repo/mock_helm_repo.py b/xapp_onboarder/tests/mock_helm_repo/mock_helm_repo.py
new file mode 100755 (executable)
index 0000000..0e0e450
--- /dev/null
@@ -0,0 +1,102 @@
+#!/usr/bin/env python3
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+import pkg_resources
+import logging.config
+import yaml
+import json
+import os
+import tarfile
+from flask import Flask, jsonify, send_file
+from tests.constants import config_file, schema_file, helm_repo_index_response
+from xapp_onboarder.server import settings
+
+
+logger_config = pkg_resources.resource_filename("xapp_onboarder", 'logging.conf')
+logging.config.fileConfig(logger_config)
+log = logging.getLogger(__name__)
+
+listen_address = '0.0.0.0:8080'
+app = Flask(__name__)
+app.config['SERVER_NAME'] = listen_address
+
+
+
+
+@app.route('/')
+def root_dir():
+    return {'health': 'OK'}
+
+@app.route('/index.yaml')
+def get_index():
+    return yaml.dump(helm_repo_index_response)
+
+@app.route('/api/charts', methods=['POST'])
+def upload_chart():
+    return {"saved": True}, 201
+
+@app.route('/api/charts/test_xapp/1.0.0', methods=['DELETE'])
+def delete_chart():
+    return {"deleted":True}, 200
+
+@app.route('/api/charts/test_xapp', methods=['GET'])
+def get_all_xapp_version():
+    test_xapp = helm_repo_index_response['entries']['test_xapp']
+    return jsonify(test_xapp), 200
+
+@app.route('/api/charts', methods=['GET'])
+def get_all_xapp():
+    return jsonify(helm_repo_index_response['entries']), 200
+
+@app.route('/charts/test_xapp-1.0.0.tgz', methods=['GET'])
+def download_xapp_helm_package():
+    if not os.path.exists(settings.MOCK_TEST_HELM_REPO_TEMP_DIR):
+        os.makedirs(settings.MOCK_TEST_HELM_REPO_TEMP_DIR)
+    with open(settings.MOCK_TEST_HELM_REPO_TEMP_DIR + '/values.yaml', 'w') as file:
+        file.write(json.dumps(helm_repo_index_response))
+
+    with tarfile.open(settings.MOCK_TEST_HELM_REPO_TEMP_DIR + '/test_xapp-1.0.0.tgz', "w:gz") as tar:
+        tar.addfile(tarfile.TarInfo("test_xapp/values.yaml"), open(settings.MOCK_TEST_HELM_REPO_TEMP_DIR + '/values.yaml'))
+
+    return send_file(settings.MOCK_TEST_HELM_REPO_TEMP_DIR + '/test_xapp-1.0.0.tgz', mimetype='application/zip')
+
+
+
+@app.route('/schema.json', methods=['GET'])
+def get_schema():
+
+
+    return schema_file, 200
+
+@app.route('/config-file.json', methods=['GET'])
+def get_config_file():
+
+
+    return config_file, 200
+
+def run():
+    log.info('>>>>> Starting mock helm_repo at http://{}<<<<<'.format(listen_address))
+    app.run(debug=True)
+
+if __name__ == '__main__':
+    run()
+
+
+
+
+
+
diff --git a/xapp_onboarder/tests/test_api.py b/xapp_onboarder/tests/test_api.py
new file mode 100644 (file)
index 0000000..b50709b
--- /dev/null
@@ -0,0 +1,60 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   Licensed under the Apache License, Version 2.0 (the "License");            #
+#   you may not use this file except in compliance with the License.           #
+#   You may obtain a copy of the License at                                    #
+#                                                                              #
+#       http://www.apache.org/licenses/LICENSE-2.0                             #
+#                                                                              #
+#   Unless required by applicable law or agreed to in writing, software        #
+#   distributed under the License is distributed on an "AS IS" BASIS,          #
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
+#   See the License for the specific language governing permissions and        #
+#   limitations under the License.                                             #
+################################################################################
+
+from http import HTTPStatus
+from tests.constants import mock_json_body, mock_json_body_url,  helm_repo_index_response
+
+
+def test_health(client):
+    response = client.get('/api/v1/health')
+    assert response.status_code == HTTPStatus.OK, 'Wrong status code'
+    assert response.json == {'status': 'OK'}, 'Improper response'
+
+def test_get_charts(client):
+    response = client.get('/api/v1/charts')
+    assert response.status_code == HTTPStatus.OK, 'Wrong status code'
+    assert response.content_type == 'application/json', 'Content type error'
+    assert sorted([repr(x) for x in response.json]) == sorted([repr(x) for x in helm_repo_index_response['entries']])
+
+def test_get_test_xapp_charts(client):
+    response = client.get('/api/v1/charts/xapp/test_xapp')
+    assert response.status_code == HTTPStatus.OK, 'Wrong status code'
+    assert response.content_type == 'application/json', 'Content type error'
+    assert sorted([repr(x) for x in response.json]) == sorted([repr(x) for x in helm_repo_index_response['entries']['test_xapp']])
+
+def test_get_test_xapp_charts_package(client):
+    response = client.get('/api/v1/charts/xapp/test_xapp/ver/1.0.0')
+    assert response.status_code == HTTPStatus.OK, 'Wrong status code'
+    assert response.content_type == 'application/gzip', 'Content type error'
+
+def test_get_test_xapp_charts_values_yaml(client):
+    response = client.get('/api/v1/charts/xapp/test_xapp/ver/1.0.0/values.yaml')
+    assert response.status_code == HTTPStatus.OK, 'Wrong status code'
+    assert response.content_type == 'text/x-yaml', 'Content type error'
+
+def test_onboard_post(client):
+    url = '/api/v1/onboard'
+    response = client.post(url, json=mock_json_body)
+    assert response.status_code == HTTPStatus.CREATED, 'Wrong status code'
+    assert response.content_type == 'application/json', 'Content type error'
+    assert response.json == {'status': 'Created'}, 'Onboard failed'
+
+def test_onboard_download_post(client):
+    url = '/api/v1/onboard/download'
+    response = client.post(url, json=mock_json_body_url)
+    assert response.status_code == HTTPStatus.CREATED, 'Wrong status code'
+    assert response.content_type == 'application/json', 'Content type error'
+    assert response.json == {'status': 'Created'}, 'Onboard failed'
\ No newline at end of file
diff --git a/xapp_onboarder/tests/test_helm_controller.py b/xapp_onboarder/tests/test_helm_controller.py
new file mode 100644 (file)
index 0000000..094cf9f
--- /dev/null
@@ -0,0 +1,32 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+import os
+import shutil
+from xapp_onboarder.helm_controller.xApp_builder import xApp, xAppError
+from tests.constants import config_file, schema_file
+from xapp_onboarder.server import settings
+
+
+def test_packaging_xapp(client):
+        chart_workspace_path = settings.CHART_WORKSPACE_PATH + '/test_xapp-1.0.0'
+        if os.path.exists(chart_workspace_path):
+                shutil.rmtree(chart_workspace_path)
+
+        xapp = xApp(config_file, schema_file)
+        xapp.package_chart()
+        assert os.path.isfile(chart_workspace_path + '/test_xapp-1.0.0.tgz'), 'xApp packaging error'
+
+
diff --git a/xapp_onboarder/tests/test_trim_thread.py b/xapp_onboarder/tests/test_trim_thread.py
new file mode 100644 (file)
index 0000000..f4ca4e7
--- /dev/null
@@ -0,0 +1,41 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+import time
+import os
+import shutil
+from xapp_onboarder.server import settings
+from xapp_onboarder.helm_controller.artifacts_manager import format_artifact_dir_size, get_dir_size, trim_artifact_dir
+def test_trim_thread():
+    path = settings.CHART_WORKSPACE_PATH
+    if os.path.exists(path):
+        shutil.rmtree(path)
+    os.makedirs(path)
+
+    size_limit = format_artifact_dir_size()
+    f = open(path+'/mock_size_file',"wb")
+    f.seek(int(size_limit)+100000000)
+    f.write(b"\0")
+    f.close()
+
+    artifact_dir_size = get_dir_size(start_path=path)
+    assert size_limit < artifact_dir_size, 'Fail to write large files'
+
+    trim_artifact_dir()
+    artifact_dir_size = get_dir_size(start_path=path)
+
+    assert size_limit > artifact_dir_size, 'Fail to trim'
+
+
diff --git a/xapp_onboarder/tox.ini b/xapp_onboarder/tox.ini
new file mode 100644 (file)
index 0000000..5621690
--- /dev/null
@@ -0,0 +1,48 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+[tox]
+envlist = py36-{local,lf}
+
+
+[testenv]
+deps=
+    pytest
+    pytest-flask
+    coverage
+    pytest-cov
+setenv =
+    PYTHONUNBUFFERED = 1
+    CHART_REPO_URL = http://0.0.0.0:8080
+    DBAAS_SERVICE_HOST = test_dbaas_host
+    DBAAS_MASTER_NAME = test_master_name
+    DBAAS_SERVICE_SENTINEL_PORT = 29701
+    DBAAS_SERVICE_PORT = 3710
+    MOCK_TEST_MODE = True
+    lf: CHART_WORKSPACE_PATH = {toxworkdir}/.tmp/xapp_onboarder
+    lf: MOCK_TEST_HELM_REPO_TEMP_DIR = {toxworkdir}/.tmp/helm_repo
+
+commands_pre=
+    bash -c 'nohup python -m tests.mock_helm_repo.mock_helm_repo >/dev/null 2>&1 &'
+
+commands=
+    pytest --verbose --cov xapp_onboarder --cov-report xml --cov-report term-missing --cov-report html
+    coverage xml -i
+
+commands_post=
+    local: bash -c 'kill -9 $(ps -x | grep "tests.mock_helm_repo.mock_helm_repo" | grep -v grep | cut -d" " -f1)'
+
+whitelist_externals = bash
diff --git a/xapp_onboarder/xapp_onboarder/__init__.py b/xapp_onboarder/xapp_onboarder/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/xapp_onboarder/xapp_onboarder/api/__init__.py b/xapp_onboarder/xapp_onboarder/api/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/xapp_onboarder/xapp_onboarder/api/api_reference.py b/xapp_onboarder/xapp_onboarder/api/api_reference.py
new file mode 100644 (file)
index 0000000..1f288e0
--- /dev/null
@@ -0,0 +1,34 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+import logging
+
+from flask_restplus import Api
+from xapp_onboarder.server import settings
+
+log = logging.getLogger(__name__)
+
+api = Api(version='1.0', title='RIC xApp onboarder API',
+          description='APIs to manage the xApp helm charts')
+
+
+@api.errorhandler
+def default_error_handler(e):
+    message = 'An unhandled exception occurred.'
+    log.exception(message)
+
+    if not settings.FLASK_DEBUG:
+        return {'message': message}, 500
diff --git a/xapp_onboarder/xapp_onboarder/api/charts.py b/xapp_onboarder/xapp_onboarder/api/charts.py
new file mode 100644 (file)
index 0000000..223a5be
--- /dev/null
@@ -0,0 +1,81 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+import logging
+import io
+import tarfile
+from xapp_onboarder.repo_manager.repo_manager import repo_manager, RepoManagerError
+from xapp_onboarder.api.models.response_models import error_message_model, response
+
+log = logging.getLogger(__name__)
+
+
+def get_charts_list(xapp_chart_name=None):
+
+    if not repo_manager.is_repo_ready():
+        response_message = response(model=error_message_model, status_code=500,
+                                    error_source="xapp_onboarder",
+                                    error_message="Cannot connect to local helm repo.",
+                                    status="Service not ready.")
+        return response_message.get_return()
+
+    try:
+        content = repo_manager.get_xapp_list(xapp_chart_name=xapp_chart_name)
+    except RepoManagerError as err:
+        log.error(str(err))
+        response_message = response(model=error_message_model, status_code=err.status_code,
+                                    error_source="charts_fetcher",
+                                    error_message=str(err),
+                                    status="Fetching helm chart list failed")
+        return response_message.get_return()
+    return content, 200
+
+
+def download_chart_package(xapp_chart_name, version):
+
+    if not repo_manager.is_repo_ready():
+        response_message = response(model=error_message_model, status_code=500,
+                                    error_source="xapp_onboarder",
+                                    error_message="Cannot connect to local helm repo.",
+                                    status="Service not ready.")
+        return response_message.get_return()
+    try:
+        content = repo_manager.download_xapp_chart(xapp_chart_name=xapp_chart_name, version=version)
+    except RepoManagerError as err:
+        log.error(str(err))
+        response_message = response(model=error_message_model, status_code=err.status_code,
+                                    error_source="charts_fetcher",
+                                    error_message=str(err),
+                                    status="Downloading helm chart package failed")
+        return response_message.get_return()
+    return content, 200
+
+
+def download_values_yaml(xapp_chart_name, version):
+
+    content, status = download_chart_package(xapp_chart_name=xapp_chart_name, version=version)
+
+    if status != 200:
+        return content, status
+
+    file_stream = io.BytesIO(content)
+
+    with tarfile.open(fileobj=file_stream) as tar:
+        values_yaml_file = tar.extractfile(xapp_chart_name + '/values.yaml')
+        return_response = values_yaml_file.read()
+
+    return return_response, 200
+
diff --git a/xapp_onboarder/xapp_onboarder/api/endpoints/__init__.py b/xapp_onboarder/xapp_onboarder/api/endpoints/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/xapp_onboarder/xapp_onboarder/api/endpoints/charts_ep.py b/xapp_onboarder/xapp_onboarder/api/endpoints/charts_ep.py
new file mode 100644 (file)
index 0000000..dc2c994
--- /dev/null
@@ -0,0 +1,96 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+import logging
+from flask import make_response
+from flask_restplus import Resource
+from xapp_onboarder.api.api_reference import api
+from xapp_onboarder.api.charts import get_charts_list, download_chart_package, download_values_yaml
+from xapp_onboarder.api.models.response_models import error_message_model
+
+log = logging.getLogger(__name__)
+ns = api.namespace('charts', description='Managing helm charts')
+
+
+@ns.route('')
+class ChartsList(Resource):
+
+    @api.response(200, 'Get helm chart list OK')
+    @api.response(500, 'Get helm chart list failed', error_message_model)
+    def get(self):
+        """
+        Returns the list of charts that have been onboarded.
+        """
+
+        return get_charts_list()
+
+
+@ns.route('/xapp/<string:xapp_chart_name>')
+class VersionList(Resource):
+
+    @api.response(200, 'Get helm chart list OK')
+    @api.response(500, 'Get helm chart list failed', error_message_model)
+    def get(self, xapp_chart_name):
+        """
+        Returns the list of charts that have been onboarded.
+        """
+        return get_charts_list(xapp_chart_name=xapp_chart_name)
+
+
+@ns.route('/xapp/<string:xapp_chart_name>/ver/<string:version>')
+class ChartsFetcher(Resource):
+
+    @api.response(200, 'Get helm chart package OK')
+    @api.response(500, 'Get helm chart package failed', error_message_model)
+    @api.produces(['application/gzip'])
+    def get(self, xapp_chart_name, version):
+        """
+        Returns the helm charts that have been onboarded.
+        """
+
+        content, status = download_chart_package(xapp_chart_name=xapp_chart_name, version=version)
+
+        if status != 200:
+            return content, status
+
+        response = make_response(content)
+        response.headers.set("Content-Type", "application/gzip")
+        response.headers.set("Content-Disposition",
+                             "attachment; filename=\"" + xapp_chart_name + "-" + version + ".tgz\"")
+        return response
+
+
+@ns.route('/xapp/<string:xapp_chart_name>/ver/<string:version>/values.yaml')
+class ValuesYamlFetcher(Resource):
+
+    @api.response(200, 'Get helm chart values.yaml OK')
+    @api.response(500, 'Get helm chart values.yaml failed', error_message_model)
+    @api.produces(['text/x-yaml'])
+    def get(self, xapp_chart_name, version):
+        """
+        Returns the values.yaml file of the xApp
+        """
+
+        content, status = download_values_yaml(xapp_chart_name=xapp_chart_name, version=version)
+
+        if status != 200:
+            return content, status
+
+        response = make_response(content)
+        response.headers.set("Content-Type", "text/x-yaml")
+        response.headers.set("Content-Disposition", "attachment; filename=\"values.yaml\"")
+
+        return response
diff --git a/xapp_onboarder/xapp_onboarder/api/endpoints/health_check_ep.py b/xapp_onboarder/xapp_onboarder/api/endpoints/health_check_ep.py
new file mode 100644 (file)
index 0000000..d076667
--- /dev/null
@@ -0,0 +1,44 @@
+###############################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+import logging
+
+from flask_restplus import Resource
+from xapp_onboarder.api.api_reference import api
+from xapp_onboarder.api.models.response_models import status_message_model, error_message_model, response
+from xapp_onboarder.repo_manager.repo_manager import repo_manager
+
+log = logging.getLogger(__name__)
+ns = api.namespace('health', description='health check')
+
+
+@ns.route('')
+class HealthCheck(Resource):
+
+    @api.response(200, 'Health check OK', status_message_model)
+    @api.response(500, 'xApp onboarder is not ready', error_message_model)
+    def get(self):
+        """
+        Returns the health condition of the xApp onboarder
+        """
+        if not repo_manager.is_repo_ready():
+            response_message = response( model= error_message_model, status_code = 500,
+                                         error_source = "xapp_onboarder",
+                                         error_message = "Cannot connect to local helm repo.",
+                                         status = "Service not ready.")
+            return response_message.get_return()
+        return response(model = status_message_model, status_code = 200, status= "OK").get_return()
+
diff --git a/xapp_onboarder/xapp_onboarder/api/endpoints/onboard_ep.py b/xapp_onboarder/xapp_onboarder/api/endpoints/onboard_ep.py
new file mode 100644 (file)
index 0000000..d287f11
--- /dev/null
@@ -0,0 +1,71 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+import logging
+from flask import request
+from flask_restplus import Resource
+from xapp_onboarder.api.models import request_models
+from xapp_onboarder.api.api_reference import api
+from xapp_onboarder.api.models.response_models import status_message_model, error_message_model
+from xapp_onboarder.api.onboard import onboard, download_config_and_schema_and_onboard
+
+log = logging.getLogger(__name__)
+ns = api.namespace('onboard', description='onboard xApps')
+
+
+@ns.route('')
+class OnboardxApps(Resource):
+
+    # @api.response(200, 'Everything is fine')
+    # @api.response(500, 'temp is not ready')
+    # def get(self):
+    #     """
+    #     Return a list of xApp that have been onboarded and their versions.
+    #     """
+    #     if not repo_manager.is_repo_ready():
+    #         return {'status': 'not ready'}, 500
+    #     return {'status': 'OK'}, 200
+
+    @api.response(201, 'xApp onboard successfully.', status_message_model)
+    @api.response(400, 'xApp descriptor format error', error_message_model)
+    @api.response(500, 'xApp onboarder is not ready', error_message_model)
+    @api.expect(request_models.xapp_descriptor_post, validate=True)
+    def post(self):
+        """
+        Onboard xApp with the xApp descriptor and its scehma included in the Json body
+        """
+        config_file = request.json.get('config-file.json')
+        schema_file = request.json.get('schema.json')
+
+        return onboard(config_file, schema_file)
+
+
+@ns.route('/download')
+class OnboardxAppsDownload(Resource):
+
+    @api.response(201, 'xApp onboard successfully.', status_message_model)
+    @api.response(400, 'xApp descriptor format error', error_message_model)
+    @api.response(500, 'xApp onboarder is not ready', error_message_model)
+    @api.expect(request_models.xapp_descriptor_download_post, validate=True)
+    def post(self):
+        """
+        Onboard xApp with xApp descriptor and schema downloading URLs
+        """
+        config_file_url = request.json.get('config-file.json_url')
+        schema_url = request.json.get('schema.json_url')
+
+        return download_config_and_schema_and_onboard(config_file_url, schema_url)
+
diff --git a/xapp_onboarder/xapp_onboarder/api/models/__init__.py b/xapp_onboarder/xapp_onboarder/api/models/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/xapp_onboarder/xapp_onboarder/api/models/request_models.py b/xapp_onboarder/xapp_onboarder/api/models/request_models.py
new file mode 100644 (file)
index 0000000..d49a711
--- /dev/null
@@ -0,0 +1,35 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   Licensed under the Apache License, Version 2.0 (the "License");            #
+#   you may not use this file except in compliance with the License.           #
+#   You may obtain a copy of the License at                                    #
+#                                                                              #
+#       http://www.apache.org/licenses/LICENSE-2.0                             #
+#                                                                              #
+#   Unless required by applicable law or agreed to in writing, software        #
+#   distributed under the License is distributed on an "AS IS" BASIS,          #
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
+#   See the License for the specific language governing permissions and        #
+#   limitations under the License.                                             #
+################################################################################
+
+from flask_restplus import fields
+from xapp_onboarder.api.api_reference import api
+
+xapp_descriptor_post = api.model('descriptor', {
+    'config-file.json': fields.Nested(
+        api.model('config', {
+            'xapp_name': fields.String(description='Name of the xApp chart', required=True),
+            'version': fields.String(description='Version of the xApp chart', required=True,
+                                     pattern='^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$'),
+        }), required=True),
+    'schema.json': fields.Raw(description='Schema file body', required=True),
+})
+
+
+xapp_descriptor_download_post = api.model('descriptor_remote', {
+    'config-file.json_url': fields.Url(description='URL to download the config-file.json file',  absolute=True, required=True),
+    'schema.json_url': fields.Url(description='URL to download the schema.json file',  absolute=True, required=True),
+})
+
diff --git a/xapp_onboarder/xapp_onboarder/api/models/response_models.py b/xapp_onboarder/xapp_onboarder/api/models/response_models.py
new file mode 100644 (file)
index 0000000..156b120
--- /dev/null
@@ -0,0 +1,41 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   Licensed under the Apache License, Version 2.0 (the "License");            #
+#   you may not use this file except in compliance with the License.           #
+#   You may obtain a copy of the License at                                    #
+#                                                                              #
+#       http://www.apache.org/licenses/LICENSE-2.0                             #
+#                                                                              #
+#   Unless required by applicable law or agreed to in writing, software        #
+#   distributed under the License is distributed on an "AS IS" BASIS,          #
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
+#   See the License for the specific language governing permissions and        #
+#   limitations under the License.                                             #
+################################################################################
+
+from flask_restplus import fields, marshal
+from xapp_onboarder.api.api_reference import api
+
+error_message_model = api.model('error_message', {
+    'error_source': fields.String(description='source of the error', required=True),
+    'error_message': fields.String(description='source of the error', required=True),
+    'status': fields.String(description='http response message', required=True),
+})
+
+status_message_model = api.model('status', {
+    'status': fields.String(description='status of the service', required=True)
+})
+
+
+class response():
+
+    def __init__(self, model, status_code, status = "" , error_source = "", error_message = ""):
+        self.model = model
+        self.status = status
+        self.status_code = status_code
+        self.error_source = error_source
+        self.error_message = error_message
+
+    def get_return(self):
+        return marshal(self, self.model), self.status_code
\ No newline at end of file
diff --git a/xapp_onboarder/xapp_onboarder/api/onboard.py b/xapp_onboarder/xapp_onboarder/api/onboard.py
new file mode 100644 (file)
index 0000000..bdc80eb
--- /dev/null
@@ -0,0 +1,119 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+import logging
+import json
+from jsonschema import ValidationError, SchemaError
+from jsonschema import validate, Draft7Validator
+from xapp_onboarder.helm_controller.xApp_builder import xApp, xAppError
+from xapp_onboarder.server import settings
+from xapp_onboarder.repo_manager.repo_manager import requests_retry_session, repo_manager
+from xapp_onboarder.api.models.response_models import error_message_model, response, status_message_model
+
+
+log = logging.getLogger(__name__)
+
+
+def onboard(config_file, schema_file):
+    if not repo_manager.is_repo_ready():
+        response_message = response(model=error_message_model, status_code=500,
+                                    error_source="xapp_onboarder",
+                                    error_message="Cannot connect to local helm repo.",
+                                    status="Service not ready.")
+        return response_message.get_return()
+
+    try:
+        Draft7Validator.check_schema(schema_file)
+        validate(config_file, schema_file)
+    except ValidationError as err:
+        log.debug(err.message)
+        response_message = response(model=error_message_model, status_code=400,
+                                    error_source="config-file.json",
+                                    error_message=err.message,
+                                    status="Input payload validation failed")
+        return response_message.get_return()
+    except SchemaError as err:
+        log.debug(err.message)
+        response_message = response(model=error_message_model, status_code=400,
+                                    error_source="schema.json",
+                                    error_message=err.message,
+                                    status="Input payload validation failed")
+        return response_message.get_return()
+
+    try:
+        xapp = xApp(config_file, schema_file)
+        xapp.package_chart()
+        xapp.distribute_chart()
+    except xAppError as err:
+        log.error(str(err))
+        response_message = response(model=error_message_model, status_code=err.status_code,
+                                    error_source="xApp_builder",
+                                    error_message=str(err),
+                                    status="xApp onboarding failed")
+        return response_message.get_return()
+    return response(model=status_message_model, status_code=201, status="Created").get_return()
+
+
+def download_config_and_schema_and_onboard(config_file_url, schema_url):
+    if not repo_manager.is_repo_ready():
+        response_message = response(model=error_message_model, status_code=500,
+                                    error_source="xapp_onboarder",
+                                    error_message="Cannot connect to local helm repo.",
+                                    status="Service not ready.")
+        return response_message.get_return()
+
+    session = requests_retry_session()
+    try:
+        response_content = session.get(config_file_url, timeout=settings.HTTP_TIME_OUT)
+    except Exception as err:
+        log.error(err.message)
+        response_message = response(model=error_message_model, status_code=500,
+                                    error_source="config-file.json",
+                                    error_message=err.message,
+                                    status="Downloading config-file.json failed")
+        return response_message.get_return()
+    else:
+        if response_content.status_code != 200:
+            error_message = "Wrong response code: {}, {}".format(response_content.status_code, response_content.content.decode("utf-8"))
+            log.error(error_message)
+            response_message = response(model=error_message_model, status_code=500,
+                                        error_source="config-file.json",
+                                        error_message=error_message,
+                                        status="Downloading config-file.json failed")
+            return response_message.get_return()
+        config_file = json.loads(response_content.content)
+
+    try:
+        response_content = session.get(schema_url, timeout=settings.HTTP_TIME_OUT)
+    except Exception as err:
+        log.error(err.message)
+        response_message = response(model=error_message_model, status_code=500,
+                                    error_source="schema.json",
+                                    error_message=err.message,
+                                    status="Downloading schema.json failed")
+        return response_message.get_return()
+    else:
+        if response_content.status_code != 200:
+            error_message = "Wrong response code. {}, {}".format(response_content.status_code, response_content.content.decode("utf-8"))
+            log.error(error_message)
+            response_message = response(model=error_message_model, status_code=500,
+                                        error_source="schema.json",
+                                        error_message=error_message,
+                                        status="Downloading schema.json failed")
+            return response_message.get_return()
+        schema_file = json.loads(response_content.content)
+
+    return onboard(config_file, schema_file)
diff --git a/xapp_onboarder/xapp_onboarder/cli b/xapp_onboarder/xapp_onboarder/cli
new file mode 100755 (executable)
index 0000000..8e1c62b
--- /dev/null
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+import logging.config
+import pkg_resources
+from xapp_onboarder.server.cli import run
+
+if __name__ == "__main__":
+
+    logger_config = pkg_resources.resource_filename("xapp_onboarder", 'logging.conf')
+    logging.config.fileConfig(logger_config)
+    run()
diff --git a/xapp_onboarder/xapp_onboarder/helm_controller/__init__.py b/xapp_onboarder/xapp_onboarder/helm_controller/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/xapp_onboarder/xapp_onboarder/helm_controller/artifacts_manager.py b/xapp_onboarder/xapp_onboarder/helm_controller/artifacts_manager.py
new file mode 100644 (file)
index 0000000..4369884
--- /dev/null
@@ -0,0 +1,100 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+import os
+import logging
+import re
+import glob
+import threading
+import shutil
+from xapp_onboarder.server import settings
+
+log = logging.getLogger(__name__)
+
+
+def get_dir_size(start_path='.'):
+    total_size = 0
+
+    if os.path.isfile(start_path):
+        total_size += os.path.getsize(start_path)
+
+    if os.path.isdir(start_path):
+        for dirpath, dirnames, filenames in os.walk(start_path):
+            for f in filenames:
+                fp = os.path.join(dirpath, f)
+                # skip if it is symbolic link
+                if not os.path.islink(fp):
+                    total_size += os.path.getsize(fp)
+
+    return total_size
+
+
+def format_artifact_dir_size():
+    """ Converts integers to common size units used in computing """
+    artifact_dir_size_string = settings.CHART_WORKSPACE_SIZE
+    size_unit = re.sub('[0-9\s\.]', '', artifact_dir_size_string)
+    size_limit = re.sub('[A-Za-z\s\.]', '', artifact_dir_size_string)
+
+    bit_shift = {"B": 0,
+                 "kb": 7,
+                 "KB": 10,
+                 "mb": 17,
+                 "MB": 20,
+                 "gb": 27,
+                 "GB": 30,
+                 "TB": 40, }
+    return float(size_limit) * float(1 << bit_shift[size_unit])
+
+def trim_artifact_dir():
+    artifact_dir_size = get_dir_size(start_path=settings.CHART_WORKSPACE_PATH)
+    dir_limit = format_artifact_dir_size()
+
+    if artifact_dir_size > dir_limit:
+        dirs = sorted(glob.glob(settings.CHART_WORKSPACE_PATH + '/*'), key=os.path.getctime)
+        trim_dir = list()
+        remain_size = artifact_dir_size
+        for dir in dirs:
+            remain_size = remain_size - get_dir_size(start_path=dir)
+            trim_dir.append(dir)
+            if remain_size < dir_limit:
+                break
+        log.info("Trimming artifact directories: " + str(trim_dir))
+        for dir in trim_dir:
+            if os.path.isfile(dir):
+                os.remove(dir)
+            else:
+                shutil.rmtree(dir)
+
+
+class artifacts_manager():
+
+    def __init__(self):
+        self.timer_thread = threading.Timer(60.0, trim_artifact_dir)
+
+    def start_trim_thread(self):
+        if not settings.MOCK_TEST_MODE:
+            log.info("Artifact directory trimming thread started.")
+            self.timer_thread.start()
+
+
+    def cancel_trim_thread(self):
+        log.info("Artifact directory trimming thread stopped.")
+        self.timer_thread.cancel()
+
+    def start(self):
+        self.start_trim_thread()
+
+
diff --git a/xapp_onboarder/xapp_onboarder/helm_controller/xApp_builder.py b/xapp_onboarder/xapp_onboarder/helm_controller/xApp_builder.py
new file mode 100644 (file)
index 0000000..9506a46
--- /dev/null
@@ -0,0 +1,241 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+import logging
+import yaml
+import json
+import os
+import io
+import subprocess
+import shutil
+import re
+import copy
+import platform
+import tarfile
+import stat
+from xapp_onboarder.server import settings
+from xapp_onboarder.repo_manager.repo_manager import repo_manager, RepoManagerError
+from pkg_resources import resource_filename
+from subprocess import PIPE
+from xapp_onboarder.repo_manager.repo_manager import requests_retry_session
+log = logging.getLogger(__name__)
+
+
+def indent(text, amount, ch=' '):
+    padding = amount * ch
+    return ''.join(padding + line for line in text.splitlines(True))
+
+
+class xAppError(Exception):
+    def __init__(self, message, status_code):
+        # Call the base class constructor with the parameters it needs
+        super().__init__(message)
+        self.status_code = status_code
+
+
+class xApp():
+    def __init__(self, config_file, schema_file):
+        self.config_file = config_file
+        self.schema_file = schema_file
+
+        if 'xapp_name' not in self.config_file:
+            raise xAppError(
+                "xApp chart name not found. (Caused by: config-file.json does not contain xapp_name attribute.)", 500)
+
+        if 'version' not in self.config_file:
+            raise xAppError(
+                "xApp chart version not found. (Caused by: config-file.json does not contain version attribute.)", 500)
+
+        self.chart_name = self.config_file['xapp_name']
+        self.chart_version = self.config_file['version']
+        self.configmap_config_json_file = copy.deepcopy(self.config_file)
+        self.chart_workspace_path = settings.CHART_WORKSPACE_PATH + '/' + self.chart_name + '-' + self.chart_version
+        if os.path.exists(self.chart_workspace_path):
+            shutil.rmtree(self.chart_workspace_path)
+        os.makedirs(self.chart_workspace_path)
+        shutil.copytree(resource_filename( 'xapp_onboarder', 'resources/xapp-std'), self.chart_workspace_path + '/' + self.chart_name)
+        self.helm_client_path = settings.CHART_WORKSPACE_PATH + '/helm'
+        self.setup_helm()
+
+    def setup_helm(self):
+        if not os.path.isfile(settings.CHART_WORKSPACE_PATH + '/helm'):
+            log.info("Helm client missing. Trying to download it.")
+            helm_file_name = "helm-v{}-{}-amd64.tar.gz".format(settings.HELM_VERSION, platform.system().lower())
+            helm_download_link = "https://get.helm.sh/" + helm_file_name
+
+
+            try:
+                response = requests_retry_session().get(helm_download_link, timeout=settings.HTTP_TIME_OUT)
+            except Exception as err:
+                error_message = "Download helm client failed. (Caused by: " + str(err)+")"
+                log.error(error_message)
+                raise xAppError(error_message, 500)
+            else:
+                if response.status_code != 200:
+                    error_message = "Download helm chart failed. Helm repo return status code: "+ str(response.status_code)  +" "+ response.content.decode("utf-8")
+                    log.error(error_message)
+                    raise xAppError(error_message, 500)
+
+                file_stream = io.BytesIO(response.content)
+
+                with tarfile.open(fileobj=file_stream) as tar:
+                    helm_client = tar.extractfile(platform.system().lower() + "-amd64/helm")
+                    with open(settings.CHART_WORKSPACE_PATH+'/helm', 'wb') as file:
+                        file.write(helm_client.read())
+                st = os.stat(settings.CHART_WORKSPACE_PATH+'/helm')
+                os.chmod(settings.CHART_WORKSPACE_PATH+'/helm', st.st_mode | stat.S_IEXEC)
+
+
+
+
+    def recursive_convert_config_file(self, node_list=list()):
+        current_node = self.configmap_config_json_file
+        helm_value_path = '.Values'
+        for node in node_list:
+            current_node = current_node.get(node)
+            helm_value_path = helm_value_path + '.' + node
+
+        if type(current_node) is not dict:
+            raise TypeError("Recursive write was called on a leaf node.")
+
+        for item in current_node.keys():
+            if type(current_node.get(item)) is not dict:
+                current_node[item] = '{{ '+ helm_value_path +'.'+ item + ' | toJson }}'
+            else:
+                new_node_list = node_list.copy()
+                new_node_list.append(item)
+                self.recursive_convert_config_file(new_node_list)
+
+
+
+    def append_config_to_config_map(self):
+        with open(self.chart_workspace_path + '/' + self.chart_name + '/templates/appconfig.yaml', 'a') as outputfile:
+            self.recursive_convert_config_file()
+            config_file_json_text = json.dumps(self.configmap_config_json_file, indent=4)
+            indented_config_text = indent(config_file_json_text, 4)
+            indented_config_text = re.sub(r"\"{{", '{{', indented_config_text)
+            indented_config_text = re.sub(r"}}\"", '}}', indented_config_text)
+            outputfile.write("  config-file.json: |\n")
+            outputfile.write(indented_config_text)
+            outputfile.write("\n  schema.json: |\n")
+            schema_json = json.dumps(self.schema_file, indent=4)
+            indented_schema_text = indent(schema_json, 4)
+            outputfile.write(indented_schema_text)
+
+
+    def add_probes_to_deployment(self):
+        with open(self.chart_workspace_path + '/' + self.chart_name + '/templates/deployment.yaml', 'a') as outputfile:
+
+            for probes in ['readinessProbe', 'livenessProbe']:
+                if self.configmap_config_json_file.get(probes):
+                    probe_definition = self.configmap_config_json_file.get(probes)
+                    probe_definition_yaml = yaml.dump(probe_definition)
+                    indented_probe_definition_yaml = indent(probe_definition_yaml, 12)
+                    indented_probe_definition_yaml = re.sub(r" \| toJson", '', indented_probe_definition_yaml)
+                    indented_probe_definition_yaml = re.sub(r"'", '', indented_probe_definition_yaml)
+                    outputfile.write("          "+probes+":\n")
+                    outputfile.write(indented_probe_definition_yaml)
+
+
+
+    def append_config_to_values_yaml(self):
+        with open(self.chart_workspace_path + '/' + self.chart_name + '/values.yaml', 'a') as outputfile:
+            yaml.dump(self.config_file, outputfile, default_flow_style=False)
+
+
+    def append_env_to_config_map(self):
+        with open(self.chart_workspace_path + '/' + self.chart_name + '/templates/appenv.yaml', 'a') as outputfile:
+            append = {}
+            if settings.DBAAS_MASTER_NAME:
+                master_name = settings.DBAAS_MASTER_NAME
+                service_host = settings.DBAAS_SERVICE_HOST
+                sentinel_port = settings.DBAAS_SERVICE_SENTINEL_PORT
+                if not service_host:
+                    raise xAppError(
+                        "Internal failure. Cannot find environment variable 'DBAAS_SERVICE_HOST'. (Caused by: Misconfiguration of temp deployment)", 500)
+                if not sentinel_port:
+                    raise xAppError(
+                        "Internal failure. Cannot find environment variable 'DBAAS_SERVICE_SENTINEL_PORT'. (Caused by: Misconfiguration of temp deployment)", 500)
+
+                append['DBAAS_MASTER_NAME'] = master_name
+                append['DBAAS_SERVICE_HOST'] = service_host
+                append['DBAAS_SERVICE_SENTINEL_PORT'] = sentinel_port
+            elif settings.DBAAS_SERVICE_HOST:
+                service_host = settings.DBAAS_SERVICE_HOST
+                service_port = settings.DBAAS_SERVICE_PORT
+                if not service_port:
+                    raise xAppError(
+                        "Internal failure. Cannot find environment variable 'DBAAS_SERVICE_PORT'. (Caused by: Misconfiguration of temp deployment)", 500)
+                append['DBAAS_SERVICE_HOST'] = service_host
+                append['DBAAS_SERVICE_PORT'] = service_port
+            else:
+                raise xAppError(
+                    "Internal failure. Cannot find environment variable 'DBAAS_SERVICE_HOST' or 'DBAAS_MASTER_NAME'. (Caused by: Misconfiguration of temp deployment)",
+                    500)
+            output_yaml = yaml.dump(append)
+            indented_output_yaml = indent(output_yaml, 2)
+            outputfile.write(indented_output_yaml)
+
+
+    def change_chart_name_version(self):
+        with open(self.chart_workspace_path + '/' + self.chart_name + '/Chart.yaml', 'r') as inputfile:
+            self.chart_yaml = yaml.load(inputfile, Loader=yaml.FullLoader)
+            self.chart_yaml['version'] = self.chart_version
+            self.chart_yaml['name'] = self.chart_name
+
+        with open(self.chart_workspace_path + '/' + self.chart_name + '/Chart.yaml', 'w') as outputfile:
+            yaml.dump(self.chart_yaml, outputfile, default_flow_style=False)
+
+
+    def helm_lint(self):
+        try:
+            process = subprocess.run([self.helm_client_path, "lint", self.chart_workspace_path + "/" + self.chart_name], stdout=PIPE, stderr=PIPE, check=True)
+
+        except OSError as err:
+            raise xAppError(
+                "xApp " + self.chart_name + '-' + self.chart_version + " helm lint failed. (Caused by: " + str(
+                    err) + ")", 500)
+        except subprocess.CalledProcessError as err:
+            raise xAppError(
+                "xApp " + self.chart_name + '-' + self.chart_version + " helm lint failed. (Caused by: " +
+                err.stderr.decode("utf-8") +  "\n" + err.stdout.decode("utf-8") + ")", 400)
+
+    def package_chart(self):
+        self.append_config_to_config_map()
+        self.append_config_to_values_yaml()
+        self.append_env_to_config_map()
+        self.add_probes_to_deployment()
+        self.change_chart_name_version()
+        self.helm_lint()
+        try:
+            process = subprocess.run([self.helm_client_path, "package", self.chart_workspace_path + "/" + self.chart_name, "-d"
+                               ,self.chart_workspace_path,"--save=false"], stdout=PIPE, stderr=PIPE, check=True)
+
+        except OSError as err:
+                raise xAppError("xApp "+ self.chart_name+'-'+self.chart_version +" packaging failed. (Caused by: "+str(err) +")", 500)
+        except subprocess.CalledProcessError as err:
+            raise xAppError(
+                "xApp " + self.chart_name + '-' + self.chart_version + " packaging failed. (Caused by: " +
+                    err.stderr.decode("utf-8") + ")", 500)
+
+
+
+    def distribute_chart(self):
+        try:
+            repo_manager.upload_chart(self)
+        except RepoManagerError as err:
+            raise xAppError( "xApp " + self.chart_name + '-' + self.chart_version + " distribution failed. (Caused by: " + str(err) + ")" , err.status_code)
+
diff --git a/xapp_onboarder/xapp_onboarder/logging.conf b/xapp_onboarder/xapp_onboarder/logging.conf
new file mode 100644 (file)
index 0000000..76ed704
--- /dev/null
@@ -0,0 +1,28 @@
+[loggers]
+keys=root, xapp_onboarder
+
+[handlers]
+keys=console
+
+[formatters]
+keys=simple
+
+[logger_root]
+level=DEBUG
+handlers=console
+
+[logger_xapp_onboarder]
+level=DEBUG
+handlers=console
+qualname=xapp_onboarder
+propagate=0
+
+[handler_console]
+class=StreamHandler
+level=DEBUG
+formatter=simple
+args=(sys.stdout,)
+
+[formatter_simple]
+format=%(asctime)s - %(levelname)s - %(message)s - %(name)s
+datefmt=
\ No newline at end of file
diff --git a/xapp_onboarder/xapp_onboarder/repo_manager/__init__.py b/xapp_onboarder/xapp_onboarder/repo_manager/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/xapp_onboarder/xapp_onboarder/repo_manager/repo_manager.py b/xapp_onboarder/xapp_onboarder/repo_manager/repo_manager.py
new file mode 100644 (file)
index 0000000..ff0617e
--- /dev/null
@@ -0,0 +1,157 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+import yaml
+import json
+from xapp_onboarder.server import settings
+import logging
+import requests
+import time
+from requests.adapters import HTTPAdapter
+from requests.packages.urllib3.util.retry import Retry
+
+log = logging.getLogger(__name__)
+
+def requests_retry_session(retries=3, backoff_factor=0.3, status_forcelist=(500, 502, 504, 400, 401, 409), session=None,):
+    session = session or requests.Session()
+    retry = Retry(
+        total=retries,
+        read=retries,
+        connect=retries,
+        backoff_factor=backoff_factor,
+        status_forcelist=status_forcelist,
+    )
+    adapter = HTTPAdapter(max_retries=retry)
+    session.mount('http://', adapter)
+    session.mount('https://', adapter)
+    return session
+
+
+
+class RepoManagerError(Exception):
+    def __init__(self, message, status_code):
+        # Call the base class constructor with the parameters it needs
+        super().__init__(message)
+        self.status_code = status_code
+
+
+class repoManager():
+    def __init__(self, repo_url):
+        self.repo_url = repo_url
+        self.__is_repo_ready__ = False
+        log.debug("Initialize connection to helm chart repo at "+self.repo_url)
+        t0 = time.time()
+        self.retry_session = requests_retry_session()
+        try:
+            response = self.retry_session.get(self.repo_url, timeout=settings.HTTP_TIME_OUT)
+        except Exception as err:
+            t1 = time.time()
+            log.error('Failed to connect to helm chart repo ' + self.repo_url + ' after ' + str(
+                settings.HTTP_RETRY) + ' retries and ' + str(t1 - t0) + ' seconds. (Caused by: ' + err.__class__.__name__ + ')')
+        else:
+            self.__is_repo_ready__ = True
+
+
+
+    def is_repo_ready(self):
+        return self.__is_repo_ready__
+
+    def get_index(self):
+        try:
+            response = self.retry_session.get(self.repo_url +'/index.yaml', timeout=settings.HTTP_TIME_OUT)
+        except Exception as err:
+            raise RepoManagerError("Get helm repo index failed. (Caused by: " + str(err)+")", 500)
+        else:
+            if response.status_code != 200:
+                raise RepoManagerError("Get helm repo index failed. Helm repo return status code: {}, {}".format(response.status_code, response.content.decode("utf-8")))
+            return yaml.load(response.content, Loader=yaml.FullLoader)
+
+    def upload_chart(self, xapp):
+
+        xapp_chart_index = self.get_index()
+        found_xapp = False
+        for chart in xapp_chart_index.get('entries', {}).get(xapp.chart_name, []):
+            if chart['version'] == xapp.chart_version:
+                found_xapp = True
+
+        if found_xapp:
+            if settings.ALLOW_REDEPLOY:
+                self.delete_chart(xapp)
+            else:
+                raise RepoManagerError("Upload helm chart failed. Redeploy xApp helm chart is not allowed.", 400)
+
+        headers = {'Content-Type': 'application/json'}
+        chart_package_path = xapp.chart_workspace_path + '/' + xapp.chart_name + '-' + xapp.chart_version + '.tgz'
+        with open(chart_package_path, mode='rb') as filereader:
+            fileContent = filereader.read()
+
+        try:
+            response = self.retry_session.post(self.repo_url +'/api/charts', headers=headers, data=fileContent, timeout=settings.HTTP_TIME_OUT)
+        except Exception as err:
+            raise RepoManagerError("Upload helm chart failed. (Caused by: " + str(err) + ")", 500)
+        else:
+            if response.status_code != 201:
+                raise RepoManagerError("Upload helm chart failed. Helm repo return status code: "+ str(response.status_code)  +" "+ response.content.decode("utf-8"), response.status_code)
+
+
+    def delete_chart(self, xapp):
+
+        headers = {'Content-Type': 'application/json'}
+
+        try:
+            response = self.retry_session.delete(self.repo_url +'/api/charts/' + xapp.chart_name
+                                                 + '/' + xapp.chart_version, headers=headers, timeout=settings.HTTP_TIME_OUT)
+        except Exception as err:
+            raise RepoManagerError("Delete helm chart failed." + str(err), 500)
+        else:
+            if response.status_code != 200:
+                response_dict = json.loads(response.content)
+                if xapp.chart_name+'-'+xapp.chart_version+'.tgz' not in response_dict["error"]:
+                    raise RepoManagerError("Delete helm chart failed. Helm repo return status code:" + str(response.status_code)  +" "+ response.content.decode("utf-8"),response.status_code)
+
+
+    def get_xapp_list(self, xapp_chart_name=None):
+
+        request_path = self.repo_url+'/api/charts'
+        if xapp_chart_name:
+            request_path = request_path +'/' + xapp_chart_name
+
+        try:
+            response = self.retry_session.get(request_path, timeout=settings.HTTP_TIME_OUT)
+        except Exception as err:
+            raise RepoManagerError("Get xApp charts list failed. (Caused by: " + str(err)+")", 500)
+        else:
+            if response.status_code != 200:
+                raise RepoManagerError("Get xApp charts list failed. Helm repo return status code: "+ str(response.status_code)  +" "+ response.content.decode("utf-8"), response.status_code)
+            return json.loads(response.content)
+
+
+    def download_xapp_chart(self, xapp_chart_name, version):
+
+        request_path = self.repo_url+'/charts/'+xapp_chart_name+'-'+version+'.tgz'
+        try:
+            response = self.retry_session.get(request_path, timeout=settings.HTTP_TIME_OUT)
+        except Exception as err:
+            raise RepoManagerError("Download helm chart failed. (Caused by: " + str(err)+")", 500)
+        else:
+            if response.status_code != 200:
+                raise RepoManagerError("Download helm chart failed. Helm repo return status code: "+ str(response.status_code)  +" "+ response.content.decode("utf-8"), response.status_code)
+            return response.content
+
+
+
+
+repo_manager = repoManager(settings.CHART_REPO_URL)
@@ -18,7 +18,7 @@
 Expand the name of the chart.
 */}}
 {{- define "ricxapp.name" -}}
-  {{- default .Chart.Name .Values.ricxapp.name | trunc 63 | trimSuffix "-" -}}
+  {{- default .Chart.Name .Values.name | trunc 63 | trimSuffix "-" -}}
 {{- end -}}
 
 {{/*
@@ -29,7 +29,7 @@ If release name contains chart name it will be used as a full name.
 {{- define "ricxapp.fullname" -}}
   {{- $name := ( include "ricxapp.name" . ) -}}
   {{- $fullname := ( printf "%s-%s" .Release.Namespace $name ) -}}
-  {{- default $fullname .Values.ricxapp.fullname | trunc 63 | trimSuffix "-" -}}
+  {{- default $fullname .Values.fullname | trunc 63 | trimSuffix "-" -}}
 {{- end -}}
 
 {{/*
@@ -71,6 +71,9 @@ Create chart name and version as used by the chart label.
   {{- printf "container-%s" $name | trunc 63 | trimSuffix "-" -}}
 {{- end -}}
 
+
 {{- define "ricxapp.imagepullsecret" -}}
-  {{- printf "docker-reg-cred" -}}
-{{- end -}}
+  {{- $reponame := .repo -}}
+  {{- $postfix := $reponame | replace "." "-" | replace ":" "-" | replace "/" "-" | trunc 63 | trimSuffix "-" -}}
+  {{- printf "secret-%s" $postfix -}}
+{{- end -}}
\ No newline at end of file
 #   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.                                             #
-################################################################################
-
-default_helm_repo=https://$(hostname):32080/helm
-
-default_docker_registry=docker-entry
-
+################################################################################'
 
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ include "ricxapp.configmapname" . }}-appconfig
+data:
@@ -18,11 +18,8 @@ kind: ConfigMap
 metadata:
   name: {{ include "ricxapp.configmapname" . }}-appenv
 data:
-  {{- if .Values.ricxapp.appenv }}
-    {{- toYaml .Values.ricxapp.appenv | nindent 2 }}
+  {{- if .Values.appenv }}
+    {{- toYaml .Values.appenv | nindent 2 }}
   {{- end }}
-  DBAAS_SERVICE_HOST: "{{ .Values.ricplt.dbaasService }}"
-  DBAAS_SERVICE_PORT: "6379"
-  DBAAS_PORT_6379_TCP_ADDR: "{{ .Values.ricplt.dbaasService }}"
-  DBAAS_PORT_6379_TCP_PORT: "6379"
-  RMR_RTG_SVC: "{{ .Values.ricxapp.service.rmr.route.port }}"
+  RMR_RTG_SVC: "4561"
+  XAPP_DESCRIPTOR_PATH: {{ .Values.appconfig.path }}
@@ -23,7 +23,7 @@ metadata:
     release: {{ .Release.Name }}
     heritage: {{ .Release.Service }}
 spec:
-  replicas: {{ .Values.ricxapp.replicaCount }}
+  replicas: {{ .Values.replicaCount }}
   selector:
     matchLabels:
       app: {{ include "ricxapp.namespace" . }}-{{ include "ricxapp.name" . }}
@@ -36,37 +36,44 @@ spec:
     spec:
       hostname: {{ include "ricxapp.name" . }}
       imagePullSecrets:
-        - name: {{ include "ricxapp.imagepullsecret" . }}
+{{- range .Values.containers }}
+        - name: {{ .image.registry | replace "." "-" | replace ":" "-" | replace "/" "-" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+      volumes:
+        - name: config-volume
+          configMap:
+            name: {{ include "ricxapp.configmapname" . }}-appconfig
       containers:
-        - name: {{ include "ricxapp.containername" . }}
-          image: "{{ .Values.ricxapp.image.repository }}/{{ .Values.ricxapp.image.name }}:{{ .Values.ricxapp.image.tag }}"
-          imagePullPolicy: {{ .Values.ricxapp.image.pullPolicy }}
+{{- range .Values.containers }}
+        - name: {{ .name }}
+          image: "{{ .image.registry }}/{{ .image.name }}:{{ .image.tag }}"
+          {{- if .command }}
+          command: [{{ .command }}]
+          {{- end}}
+          imagePullPolicy: {{ $.Values.image_pull_policy }}
+          {{- if .ports }}
           ports:
-            - name: http
-              containerPort: {{ .Values.ricxapp.service.http.containerPort }}
+            {{- if .ports.http }}
+            - name: http_{{ .name }}
+              containerPort: {{ .ports.http }}
               protocol: TCP
-            - name: rmrroute
-              containerPort: {{ .Values.ricxapp.service.rmr.route.port }}
+            {{- end }}
+            {{- if .ports.rmr_data }}
+            - name: rmrdata_{{ .name }}
+              containerPort: {{ .ports.rmr_data }}
               protocol: TCP
-            - name: rmrdata
-              containerPort: {{ .Values.ricxapp.service.rmr.data.port }}
+            {{- end }}
+            {{- if .ports.rmr_route }}
+            - name: rmrroute_{{ .name }}
+              containerPort: {{ .ports.rmr_route }}
               protocol: TCP
+            {{- end }}
+          {{- end }}
+{{- end }}
           volumeMounts:
             - name: config-volume
-              mountPath: {{ .Values.ricxapp.appconfig.path }}
+              mountPath: {{ .Values.appconfig.path }}
           envFrom:
             - configMapRef:
                 name: {{ include "ricxapp.configmapname" . }}-appenv
-          {{- if .Values.ricxapp.livenessProbe }}
-          livenessProbe:
-            {{- .Values.ricxapp.livenessProbe | nindent 12 -}}
-          {{ end }}
-          {{- if .Values.ricxapp.readinessProbe }}
-          readinessProbe:
-            {{- .Values.ricxapp.readinessProbe | nindent 12 -}}
-          {{ end }}
           restartPolicy: Always
-      volumes:
-        - name: config-volume
-          configMap:
-            name: {{ include "ricxapp.configmapname" . }}-appconfig
@@ -27,10 +27,16 @@ metadata:
 spec:
   type: ClusterIP
   ports:
-    - port: {{ .Values.ricxapp.service.http.port }}
-      targetPort: http
+  {{- range .Values.containers }}
+  {{- if .ports }}
+    {{- if .ports.http }}
+    - port: {{  .ports.http  }}
+      targetPort: http_{{ .name }}
       protocol: TCP
-      name: http
+      name: http_{{ .name }}
+    {{- end }}
+  {{- end }}
+  {{- end }}
   selector:
     app: {{ include "ricxapp.namespace" . }}-{{ include "ricxapp.name" . }}
     release: {{ .Release.Name }}
@@ -27,14 +27,22 @@ metadata:
 spec:
   type: ClusterIP
   ports:
-    - port: {{ .Values.ricxapp.service.rmr.data.port }}
-      targetPort: rmrdata
+  {{- range .Values.containers }}
+  {{- if .ports }}
+    {{- if .ports.rmr_data }}
+    - port: {{ .ports.rmr_data }}
+      targetPort: rmrdata_{{ .name }}
       protocol: TCP
-      name: rmrdata
-    - port: {{ .Values.ricxapp.service.rmr.route.port }}
-      targetPort: rmrroute
+      name: rmrdata_{{ .name }}
+    {{- end }}
+    {{- if .ports.rmr_route }}
+    - port: {{ .ports.rmr_route }}
+      targetPort: rmrroute_{{ .name }}
       protocol: TCP
-      name: rmrroute
+      name: rmrroute_{{ .name }}
+    {{- end }}
+  {{- end }}
+  {{- end }}
   selector:
     app: {{ include "ricxapp.namespace" . }}-{{ include "ricxapp.name" . }}
     release: {{ .Release.Name }}
diff --git a/xapp_onboarder/xapp_onboarder/resources/xapp-std/values.yaml b/xapp_onboarder/xapp_onboarder/resources/xapp-std/values.yaml
new file mode 100644 (file)
index 0000000..0579c01
--- /dev/null
@@ -0,0 +1,47 @@
+################################################################################
+#   Copyright (c) 2019 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################'
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+
+# Location of the xApp config files injected from the xApp descriptor
+appconfig:
+    path: /opt/ric/config
+
+# Number of replica
+replicaCount: 1
+
+# Image pulling policy
+image_pull_policy: IfNotPresent
+
+# Environment variables that will be injected
+appenv: {}
+
+# Liveness probe definition. If empty, liveness probe will be disabled
+livenessProbe: {}
+
+# Readiness probe definition. If empty, readiness probe will be disabled
+readinessProbe: {}
+
+# Instance name. If empty, chart name will be used
+name: {}
+
+# Full instance name. If empty, full name will be constructed from name
+fullname: {}
+###############    The following are from the xApp descriptor     ###############
+
+
+
diff --git a/xapp_onboarder/xapp_onboarder/server/__init__.py b/xapp_onboarder/xapp_onboarder/server/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/xapp_onboarder/xapp_onboarder/server/cli.py b/xapp_onboarder/xapp_onboarder/server/cli.py
new file mode 100755 (executable)
index 0000000..344541d
--- /dev/null
@@ -0,0 +1,120 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+
+import fire
+import json
+import os
+import pkg_resources
+import logging.config
+
+from xapp_onboarder.api.charts import get_charts_list, download_chart_package, download_values_yaml
+from xapp_onboarder.repo_manager.repo_manager import repo_manager
+from xapp_onboarder.api.onboard import onboard, download_config_and_schema_and_onboard
+
+log = logging.getLogger(__name__)
+
+
+class cli():
+    """This is the cli tool for xapp_onboarder."""
+
+    def get_charts_list(self, xapp_chart_name=''):
+        """Get the list of all onboarded xApps. To show all version of an xApp, use --xapp_chart_name to
+        specify the name."""
+        message, status = get_charts_list(xapp_chart_name=xapp_chart_name)
+
+        return json.dumps(message, indent=4, sort_keys=True)
+
+    def download_helm_chart(self, xapp_chart_name, version, output_path="."):
+        """Download the helm chart package of an xApp. Specify xApp name with --xapp_chart_name, version with
+        --version. Optionally use --output_path to specify the path to save the file."""
+        content, status = download_chart_package(xapp_chart_name=xapp_chart_name, version=version)
+
+        if status != 200:
+            return json.dumps(content, indent=4, sort_keys=True)
+
+        try:
+            if os.path.isabs(output_path):
+                path = output_path + '/' + xapp_chart_name + '-' + version + '.tgz'
+            else:
+                path = os.getcwd() + '/' + output_path + '/' + xapp_chart_name + '-' + version + '.tgz'
+
+            if not os.path.exists(os.path.dirname(path)):
+                os.makedirs(os.path.dirname(path))
+
+            with open(path, 'wb') as outputfile:
+                outputfile.write(content)
+        except Exception as err:
+            return err
+        return {"status": "OK"}
+
+    def download_values_yaml(self, xapp_chart_name, version, output_path="."):
+        """Download the values.yaml file that can be used to override parameters at runtime. Specify xApp name with
+        --xapp_chart_name, version with --version. Optionally use --output_path to specify the path to save the file.
+        """
+        content, status = download_values_yaml(xapp_chart_name=xapp_chart_name, version=version)
+
+        if status != 200:
+            return json.dumps(content, indent=4, sort_keys=True)
+
+        try:
+            if os.path.isabs(output_path):
+                path = output_path + '/values.yaml'
+            else:
+                path = os.getcwd() + '/' + output_path + '/values.yaml'
+
+            if not os.path.exists(os.path.dirname(path)):
+                os.makedirs(os.path.dirname(path))
+
+            with open(path, 'wb') as outputfile:
+                outputfile.write(content)
+        except Exception as err:
+            return err
+        return {"status": "OK"}
+
+    def health(self):
+        """Health check. If xapp onboarder is not ready, it return false."""
+        return repo_manager.is_repo_ready()
+
+    def onboard(self, config_file_path, shcema_file_path):
+        """Onboard an xApp with local descriptor files. Use --config_file_path to specify the path to
+        config-file.json file, --shcema_file_path to specify the path to schema.json file. """
+        try:
+            with open(config_file_path, 'r') as inputfile:
+                config_file = json.load(inputfile)
+
+            with open(shcema_file_path, 'r') as inputfile:
+                schema_file = json.load(inputfile)
+
+        except Exception as err:
+            return err
+
+        message, status = onboard(config_file, schema_file)
+        return json.dumps(message, indent=4, sort_keys=True)
+
+    def download_and_onboard(self, config_file_url, schema_file_url):
+        """Onboard an xApp with URLs pointing to the xApp descriptor files. Use --config_file_url to specify the
+        config-file.json URL, --schema_file_url to specify the schema.json URL. """
+        message, status = download_config_and_schema_and_onboard(config_file_url, schema_file_url)
+        return json.dumps(message, indent=4, sort_keys=True)
+
+def run():
+    fire.Fire(cli(), name='xapp_onboarder')
+
+if __name__ == "__main__":
+    logger_config = pkg_resources.resource_filename("xapp_onboarder", 'logging.conf')
+    logging.config.fileConfig(logger_config)
+    run()
diff --git a/xapp_onboarder/xapp_onboarder/server/server.py b/xapp_onboarder/xapp_onboarder/server/server.py
new file mode 100644 (file)
index 0000000..a07a7ba
--- /dev/null
@@ -0,0 +1,54 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+import pkg_resources
+import logging.config
+from xapp_onboarder.server import settings
+import logging
+from flask import Flask, Blueprint
+from xapp_onboarder.api.api_reference import api
+from xapp_onboarder.api.endpoints.onboard_ep import ns as onboard_ns
+from xapp_onboarder.api.endpoints.health_check_ep import ns as health_check_ns
+from xapp_onboarder.api.endpoints.charts_ep import ns as charts_ns
+from xapp_onboarder.helm_controller.artifacts_manager import artifacts_manager
+
+log = logging.getLogger(__name__)
+
+class server(object):
+    def __init__(self):
+        self.app = Flask(__name__)
+        self.app.config['SERVER_NAME'] = settings.FLASK_SERVER_NAME
+        self.app.config['SWAGGER_UI_DOC_EXPANSION'] = settings.RESTPLUS_SWAGGER_UI_DOC_EXPANSION
+        self.app.config['RESTPLUS_VALIDATE'] = settings.RESTPLUS_VALIDATE
+        self.app.config['RESTPLUS_MASK_SWAGGER'] = settings.RESTPLUS_MASK_SWAGGER
+        self.app.config['ERROR_404_HELP'] = settings.RESTPLUS_ERROR_404_HELP
+        self.api = api
+        self.api_bp = Blueprint('api', __name__, url_prefix='/api/v1')
+        self.api.init_app(self.api_bp)
+        self.api.add_namespace(onboard_ns)
+        self.api.add_namespace(health_check_ns)
+        self.api.add_namespace(charts_ns)
+        self.app.register_blueprint(self.api_bp)
+        self.artifacts_manager = artifacts_manager()
+        self.artifacts_manager.start()
+
+    def run(self):
+        log.info('>>>>> Starting development xapp_onboarder at http://{}/api/v1/ <<<<<'.format(self.app.config['SERVER_NAME']))
+        self.app.run(debug=settings.FLASK_DEBUG)
+
+if __name__ == "__main__":
+    logger_config = pkg_resources.resource_filename("xapp_onboarder", 'logging.conf')
+    logging.config.fileConfig(logger_config)
+    server().run()
diff --git a/xapp_onboarder/xapp_onboarder/server/settings.py b/xapp_onboarder/xapp_onboarder/server/settings.py
new file mode 100644 (file)
index 0000000..58c7392
--- /dev/null
@@ -0,0 +1,45 @@
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+
+import os
+
+# Flask settings
+FLASK_SERVER_NAME = os.environ.get('FLASK_SERVER_NAME') or '0.0.0.0:8888'
+FLASK_DEBUG = os.environ.get('FLASK_DEBUG') or True  # Do not use debug mode in production
+
+# Flask-Restplus settings
+RESTPLUS_SWAGGER_UI_DOC_EXPANSION = os.environ.get('RESTPLUS_SWAGGER_UI_DOC_EXPANSION') or 'list'
+RESTPLUS_VALIDATE = os.environ.get('RESTPLUS_VALIDATE') or True
+RESTPLUS_MASK_SWAGGER = os.environ.get('RESTPLUS_MASK_SWAGGER') or False
+RESTPLUS_ERROR_404_HELP = os.environ.get('RESTPLUS_ERROR_404_HELP') or False
+
+# xapp_onboarder settings
+CHART_WORKSPACE_PATH = os.environ.get('CHART_WORKSPACE_PATH') or '/tmp/xapp_onboarder'
+CHART_REPO_URL = os.environ.get('CHART_REPO_URL') or 'http://0.0.0.0:8080'
+HTTP_TIME_OUT = os.environ.get('HTTP_TIME_OUT') or 10
+HELM_VERSION = os.environ.get('HELM_VERSION') or '2.12.3'
+HTTP_RETRY = os.environ.get('HTTP_RETRY') or 3
+ALLOW_REDEPLOY = os.environ.get('ALLOW_REDEPLOY') or True
+CHART_WORKSPACE_SIZE = os.environ.get('CHART_WORKSPACE_SIZE') or '500 MB'
+MOCK_TEST_MODE = os.environ.get('MOCK_TEST_MODE') or False
+MOCK_TEST_HELM_REPO_TEMP_DIR = os.environ.get('MOCK_TEST_HELM_REPO_TEMP_DIR') or '/tmp/mock_helm_repo'
+
+
+# Environment variables that will be passed into xApp
+DBAAS_MASTER_NAME = os.environ.get('DBAAS_MASTER_NAME')
+DBAAS_SERVICE_HOST = os.environ.get('DBAAS_SERVICE_HOST')
+DBAAS_SERVICE_SENTINEL_PORT = os.environ.get('DBAAS_SERVICE_SENTINEL_PORT')
+DBAAS_SERVICE_PORT = os.environ.get('DBAAS_SERVICE_PORT')
diff --git a/xapp_onboarder/xapp_onboarder/xapp_onboarder b/xapp_onboarder/xapp_onboarder/xapp_onboarder
new file mode 100755 (executable)
index 0000000..a0efd4f
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+################################################################################
+#   Copyright (c) 2020 AT&T Intellectual Property.                             #
+#                                                                              #
+#   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.                                             #
+################################################################################
+import logging.config
+import pkg_resources
+from xapp_onboarder.server.server import server
+
+if __name__ == "__main__":
+
+    logger_config = pkg_resources.resource_filename("xapp_onboarder", 'logging.conf')
+    logging.config.fileConfig(logger_config)
+    server().run()
+