aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--deployments/helm/onap4k8s/charts/multicloud-k8s/values.yaml4
-rw-r--r--kud/build/Dockerfile4
-rw-r--r--kud/deployment_infra/images/multus-daemonset.yml2
-rw-r--r--kud/deployment_infra/images/sriov-daemonset.yml4
-rw-r--r--kud/deployment_infra/playbooks/Debian.yml2
-rw-r--r--kud/deployment_infra/playbooks/configure-onap4k8s-reset.yml56
-rw-r--r--kud/deployment_infra/playbooks/configure-onap4k8s.yml55
-rw-r--r--kud/deployment_infra/playbooks/configure-sriov.yml19
-rwxr-xr-xkud/deployment_infra/playbooks/install_iavf_drivers.sh47
-rw-r--r--kud/deployment_infra/playbooks/preconfigure-sriov.yml44
-rw-r--r--kud/deployment_infra/playbooks/sriov_hardware_check.sh4
-rw-r--r--kud/hosting_providers/containerized/README.md6
-rwxr-xr-xkud/hosting_providers/containerized/installer.sh125
-rw-r--r--kud/hosting_providers/containerized/inventory/group_vars/k8s-cluster.yml9
-rw-r--r--kud/hosting_providers/vagrant/Vagrantfile4
-rwxr-xr-xkud/hosting_providers/vagrant/installer.sh24
-rw-r--r--kud/hosting_providers/vagrant/inventory/group_vars/k8s-cluster.yml3
-rwxr-xr-xkud/tests/onap4k8s.sh40
-rwxr-xr-xkud/tests/plugin_edgex.sh14
-rwxr-xr-xkud/tests/plugin_fw.sh14
-rwxr-xr-xkud/tests/sdwan.sh25
-rw-r--r--kud/tests/sdwan/build/Dockerfile_1806_mwan3.tpl26
-rw-r--r--kud/tests/sdwan/build/Dockerfile_1806_mwan3_noproxy.tpl19
-rw-r--r--kud/tests/sdwan/build/README.md10
-rw-r--r--kud/tests/sdwan/build/build_image.sh39
-rw-r--r--kud/tests/sdwan/build/commands.lua43
-rw-r--r--kud/tests/sdwan/build/set_proxy2
-rw-r--r--kud/tests/sdwan/build/system7
-rw-r--r--kud/tests/sdwan/ovn-pod.yml40
-rw-r--r--kud/tests/sdwan/sdwan-openwrt-ovn.yml82
-rw-r--r--kud/tests/sdwan/sdwan.yml44
-rwxr-xr-xkud/tests/sdwan/test.sh120
-rwxr-xr-xkud/tests/sriov.sh4
-rw-r--r--src/Makefile4
-rw-r--r--src/k8splugin/Makefile6
-rw-r--r--src/k8splugin/api/brokerhandler.go12
-rw-r--r--src/k8splugin/api/brokerhandler_test.go41
-rw-r--r--src/k8splugin/api/instancehandler.go62
-rw-r--r--src/k8splugin/go.mod1
-rw-r--r--src/k8splugin/internal/app/client.go40
-rw-r--r--src/k8splugin/internal/logutils/logger.go28
-rw-r--r--src/orchestrator/Makefile37
-rw-r--r--src/orchestrator/api/api.go38
-rw-r--r--src/orchestrator/api/projecthandler.go105
-rw-r--r--src/orchestrator/api/projecthandler_test.go228
-rw-r--r--src/orchestrator/api/testing.go31
-rw-r--r--src/orchestrator/cmd/main.go71
-rw-r--r--src/orchestrator/go.mod34
-rw-r--r--src/orchestrator/go.sum361
-rw-r--r--src/orchestrator/internal/auth/auth.go107
-rw-r--r--src/orchestrator/internal/auth/auth_test.go47
-rw-r--r--src/orchestrator/internal/config/config.go130
-rw-r--r--src/orchestrator/internal/config/config_test.go40
-rw-r--r--src/orchestrator/internal/db/mock.go94
-rw-r--r--src/orchestrator/internal/db/mongo.go396
-rw-r--r--src/orchestrator/internal/db/mongo_test.go597
-rw-r--r--src/orchestrator/internal/db/store.go106
-rw-r--r--src/orchestrator/internal/db/store_test.go121
-rw-r--r--src/orchestrator/internal/project/project.go133
-rw-r--r--src/orchestrator/internal/project/project_test.go177
-rw-r--r--src/orchestrator/tests/certs/auth_test_certificate.pem21
-rw-r--r--src/orchestrator/tests/certs/auth_test_key.pem28
-rw-r--r--src/orchestrator/tests/configs/mock_config.json5
63 files changed, 3931 insertions, 111 deletions
diff --git a/deployments/helm/onap4k8s/charts/multicloud-k8s/values.yaml b/deployments/helm/onap4k8s/charts/multicloud-k8s/values.yaml
index 4b279e04..30d70092 100644
--- a/deployments/helm/onap4k8s/charts/multicloud-k8s/values.yaml
+++ b/deployments/helm/onap4k8s/charts/multicloud-k8s/values.yaml
@@ -26,8 +26,8 @@ global:
# Application configuration defaults.
#################################################################
# application image
-repository: nexus3.onap.org:10001
-image: onap/multicloud/k8s:0.4.0
+repository: registry.hub.docker.com
+image: onap/multicloud-k8s:0.5.0
pullPolicy: Always
# flag to enable debugging - application support required
diff --git a/kud/build/Dockerfile b/kud/build/Dockerfile
index da100bb7..38c63295 100644
--- a/kud/build/Dockerfile
+++ b/kud/build/Dockerfile
@@ -1,4 +1,8 @@
FROM ubuntu:18.04 as base
+ARG KUD_ENABLE_TESTS=false
+ARG KUD_PLUGIN_ENABLED=false
+ENV KUD_ENABLE_TESTS=$KUD_ENABLE_TESTS
+ENV KUD_PLUGIN_ENABLED=$KUD_PLUGIN_ENABLED
ADD . /usr/src/multicloud-k8s
USER root
SHELL ["/bin/bash", "-c"]
diff --git a/kud/deployment_infra/images/multus-daemonset.yml b/kud/deployment_infra/images/multus-daemonset.yml
index ff44a217..0c41a052 100644
--- a/kud/deployment_infra/images/multus-daemonset.yml
+++ b/kud/deployment_infra/images/multus-daemonset.yml
@@ -79,7 +79,7 @@ data:
"delegates": [
{
"cniVersion": "0.3.1",
- "name": "default-cni-network",
+ "name": "cni0",
"plugins": [
{
"type": "flannel",
diff --git a/kud/deployment_infra/images/sriov-daemonset.yml b/kud/deployment_infra/images/sriov-daemonset.yml
index 1edbc6c3..72f33869 100644
--- a/kud/deployment_infra/images/sriov-daemonset.yml
+++ b/kud/deployment_infra/images/sriov-daemonset.yml
@@ -13,8 +13,8 @@ data:
"resourceList": [{
"resourceName": "intel_sriov_700",
"selectors": {
- "vendors": ["8086"]
- "devices": ["37cd"]
+ "vendors": ["8086"],
+ "drivers": ["i40evf", "iavf"]
}
}]
}
diff --git a/kud/deployment_infra/playbooks/Debian.yml b/kud/deployment_infra/playbooks/Debian.yml
index 96357fe2..b9725b2d 100644
--- a/kud/deployment_infra/playbooks/Debian.yml
+++ b/kud/deployment_infra/playbooks/Debian.yml
@@ -11,8 +11,6 @@ openvswitch_service: openvswitch-switch
openvswitch_pkgs:
- openvswitch-common
- openvswitch-switch
- - libopenvswitch
- - openvswitch-datapath-dkms
ovn_central_service: ovn-central
ovn_central_pkgs:
- ovn-central # <= 2.8.1-1
diff --git a/kud/deployment_infra/playbooks/configure-onap4k8s-reset.yml b/kud/deployment_infra/playbooks/configure-onap4k8s-reset.yml
new file mode 100644
index 00000000..6adaf2ee
--- /dev/null
+++ b/kud/deployment_infra/playbooks/configure-onap4k8s-reset.yml
@@ -0,0 +1,56 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2018
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+- hosts: kube-master
+ tasks:
+ - name: Load kud variables
+ include_vars:
+ file: kud-vars.yml
+
+ - name: Change the onap4k8s directory and run helm delete
+ command: /usr/local/bin/helm delete --purge multicloud-onap8ks
+ register: helm_delete
+ args:
+ chdir: /opt/multicloud/deployments/helm/onap4k8s
+
+ - debug:
+ var: helm_delete.stdout_lines
+
+ - name: Change the onap4k8s directory and delete the ona4k8s-ns namespace
+ command: /usr/local/bin/kubectl delete ns onap4k8s-ns
+ register: delete_onap_ns
+ args:
+ chdir: /opt/multicloud/deployments/helm/onap4k8s
+
+ - debug:
+ var: delete_onap_ns.stdout_lines
+
+ - name: Change the onap4k8s directory and make clean
+ command: /usr/bin/make clean
+ register: make_clean
+ args:
+ chdir: /opt/multicloud/deployments/helm/onap4k8s
+
+ - debug:
+ var: make_clean.stdout_lines
+
+ - name: Change the onap4k8s directory and make repo-stop
+ command: /usr/bin/make repo-stop
+ register: make_repo_stop
+ args:
+ chdir: /opt/multicloud/deployments/helm/onap4k8s
+
+ - debug:
+ var: make_repo_stop.stdout_lines
+
+ - name: clean multicloud-k8s path
+ file:
+ state: absent
+ path: /opt/multicloud
diff --git a/kud/deployment_infra/playbooks/configure-onap4k8s.yml b/kud/deployment_infra/playbooks/configure-onap4k8s.yml
new file mode 100644
index 00000000..11729171
--- /dev/null
+++ b/kud/deployment_infra/playbooks/configure-onap4k8s.yml
@@ -0,0 +1,55 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2018
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+- hosts: kube-master
+ tasks:
+ - name: Load kud variables
+ include_vars:
+ file: kud-vars.yml
+
+ - name: Getting onap4k8s code in /opt folder
+ git:
+ repo: 'https://github.com/onap/multicloud-k8s.git'
+ dest: /opt/multicloud
+
+ - name: install make package for ubuntu systems
+ apt: name=make state=present update_cache=yes
+ when: ansible_distribution == "Ubuntu"
+
+ - name: install make package for centos systems
+ yum: name=make state=present update_cache=yes
+ when: ansible_distribution == "CentOS"
+
+ - name: Change the onap4k8s directory and run the command make repo
+ command: /usr/bin/make repo
+ register: make_repo
+ args:
+ chdir: /opt/multicloud/deployments/helm/onap4k8s
+
+ - debug:
+ var: make_repo.stdout_lines
+
+ - name: Change the onap4k8s directory and run the command make all
+ command: /usr/bin/make all
+ register: make_all
+ args:
+ chdir: /opt/multicloud/deployments/helm/onap4k8s
+
+ - debug:
+ var: make_all.stdout_lines
+
+ - name: Change the onap4k8s directory and run the command helm install
+ command: /usr/local/bin/helm install dist/packages/multicloud-k8s-5.0.0.tgz --name multicloud-onap8ks --namespace onap4k8s-ns --set service.type=NodePort
+ register: helm_install
+ args:
+ chdir: /opt/multicloud/deployments/helm/onap4k8s
+
+ - debug:
+ var: helm_install.stdout_lines
diff --git a/kud/deployment_infra/playbooks/configure-sriov.yml b/kud/deployment_infra/playbooks/configure-sriov.yml
index 8ba6cf48..45f276c6 100644
--- a/kud/deployment_infra/playbooks/configure-sriov.yml
+++ b/kud/deployment_infra/playbooks/configure-sriov.yml
@@ -12,25 +12,18 @@
- hosts: localhost
become: yes
- pre_tasks:
- - block:
- - name: "End play if SRIOV is False"
- debug:
- msg: "SRIOV option not available, ending play"
- - meta: end_play
- when: SRIOV_NODE == "False"
tasks:
- debug:
var: SRIOV_NODE
- name: Apply Multus
shell: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/../images/multus-daemonset.yml"
- when: SRIOV_NODE==True
+ when: SRIOV_NODE
- name: Apply SRIOV CNI
- shell: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/../images/sriov-cni.yaml"
- when: SRIOV_NODE==True
+ shell: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/../images/sriov-cni.yml"
+ when: SRIOV_NODE
- name: Apply SRIOV DaemonSet
- shell: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/../images/sriov-daemonset.yaml"
- when: SRIOV_NODE==True
+ shell: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/../images/sriov-daemonset.yml"
+ when: SRIOV_NODE
- name: Apply SRIOV Network Attachment definition
shell: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/sriov-nad.yml"
- when: SRIOV_NODE==True
+ when: SRIOV_NODE
diff --git a/kud/deployment_infra/playbooks/install_iavf_drivers.sh b/kud/deployment_infra/playbooks/install_iavf_drivers.sh
index d44483de..7a54e9f2 100755
--- a/kud/deployment_infra/playbooks/install_iavf_drivers.sh
+++ b/kud/deployment_infra/playbooks/install_iavf_drivers.sh
@@ -3,6 +3,10 @@
# Based on:
# https://gerrit.akraino.org/r/#/c/icn/+/1359/1/deploy/kud-plugin-addons/device-plugins/sriov/driver/install_iavf_drivers.sh
+nic_models=(XL710 X722)
+nic_drivers=(i40e)
+device_checkers=(is_not_used is_driver_match is_model_match)
+
function install_iavf_driver {
local ifname=$1
@@ -27,22 +31,55 @@ function install_iavf_driver {
echo '8' > /sys/class/net/$ifname/device/sriov_numvfs
}
-function is_used {
+function is_not_used {
local ifname=$1
route_info=`ip route show | grep $ifname`
if [ -z "$route_info" ]; then
- return 0
- else
return 1
+ else
+ return 0
+ fi
+}
+
+function is_driver_match {
+ local ifname=$1
+ driver=`cat /sys/class/net/$ifname/device/uevent | grep DRIVER | cut -f2 -d "="`
+ if [ ! -z "$driver" ]; then
+ for nic_driver in ${nic_drivers[@]}; do
+ if [ "$driver" = "$nic_driver" ]; then
+ return 1
+ fi
+ done
+ fi
+ return 0
+}
+
+function is_model_match {
+ local ifname=$1
+ pci_addr=`cat /sys/class/net/$ifname/device/uevent | grep PCI_SLOT_NAME | cut -f2 -d "=" | cut -f2,3 -d ":"`
+ if [ ! -z "$pci_addr" ]; then
+ for nic_model in ${nic_models[@]}; do
+ model_match=$(lspci | grep $pci_addr | grep $nic_model)
+ if [ ! -z "$model_match" ]; then
+ return 1
+ fi
+ done
fi
+ return 0
}
function get_sriov_ifname {
for net_device in /sys/class/net/*/ ; do
if [ -e $net_device/device/sriov_numvfs ] ; then
ifname=$(basename $net_device)
- is_used $ifname
- if [ "$?" = "0" ]; then
+ for device_checker in ${device_checkers[@]}; do
+ eval $device_checker $ifname
+ if [ "$?" = "0" ]; then
+ ifname=""
+ break
+ fi
+ done
+ if [ ! -z "$ifname" ]; then
echo $ifname
return
fi
diff --git a/kud/deployment_infra/playbooks/preconfigure-sriov.yml b/kud/deployment_infra/playbooks/preconfigure-sriov.yml
index c4276e1b..fd16d935 100644
--- a/kud/deployment_infra/playbooks/preconfigure-sriov.yml
+++ b/kud/deployment_infra/playbooks/preconfigure-sriov.yml
@@ -31,7 +31,7 @@
command: sriov/sriov_hardware_check.sh
register: output
- set_fact:
- SRIOV: "{{ output.stdout }}"
+ _SRIOV: "{{ output.stdout }}"
- name: Recreate the conf file for every host
file:
path: /tmp/sriov.conf
@@ -40,7 +40,7 @@
- lineinfile : >
dest=/tmp/sriov.conf
create=yes
- line='{{SRIOV}}'
+ line='{{_SRIOV}}'
delegate_to: localhost
- name: Clean the script and folder.
file:
@@ -58,32 +58,30 @@
become: yes
- set_fact:
SRIOV_NODE: "{{ installer_output.stdout }}"
- - meta: end_play
- when: SRIOV_NODE == "False"
- name: Load kud variables
include_vars:
file: kud-vars.yml
- when: SRIOV_NODE == "True"
+ when: SRIOV_NODE
tasks:
- name: Create sriov folder
file:
state: directory
path: "{{ sriov_dest }}"
- when: SRIOV_NODE == "True"
ignore_errors: yes
+ when: SRIOV_NODE
- name: Get SRIOV compatible driver
get_url: "url={{ driver_url }} dest=/tmp/{{ package }}.tar.gz"
- when: SRIOV_NODE == "True"
+ when: SRIOV_NODE
- name: Extract sriov source code
unarchive:
src: "/tmp/{{ package }}.tar.gz"
dest: "{{ sriov_dest }}"
- when: SRIOV_NODE == "True"
+ when: SRIOV_NODE
- name: Build the default target
make:
chdir: "/tmp/sriov/{{ package }}/src"
become: yes
- when: SRIOV_NODE == "True"
+ when: SRIOV_NODE
# Copy all the driver and install script into target node
- hosts: kube-node
become: yes
@@ -91,7 +89,7 @@
- name: Load kud variables
include_vars:
file: kud-vars.yml
- when: SRIOV == "True"
+ when: _SRIOV
tasks:
- name: create SRIOV driver folder in the target destination
file:
@@ -99,18 +97,22 @@
path: "{{ item }}"
with_items:
- sriov_driver
- when: SRIOV == "True"
- - name: Copy SRIOV driver to target destination
- command: "cp {{ sriov_dest }}/{{ package }}/src/iavf.ko /root/sriov_driver/"
- when: SRIOV == "True"
- - name: Copy SRIOV driver install script to target folder
- command: "cp {{ playbook_dir }}/install_iavf_drivers.sh /root/sriov_driver/install.sh"
- when: SRIOV == "True"
+ when: _SRIOV
+ - copy:
+ src: "{{ sriov_dest }}/{{ package }}/src/iavf.ko"
+ dest: sriov_driver
+ remote_src: no
+ when: _SRIOV
+ - copy:
+ src: "{{ playbook_dir }}/install_iavf_drivers.sh"
+ dest: sriov_driver/install.sh
+ remote_src: no
+ when: _SRIOV
- name: Changing perm of "install.sh", adding "+x"
- file: dest=/root/sriov_driver/install.sh mode=a+x
- when: SRIOV == "True"
+ file: dest=sriov_driver/install.sh mode=a+x
+ when: _SRIOV
- name: Run a script with arguments
shell: ./install.sh
args:
- chdir: "/root/sriov_driver"
- when: SRIOV == "True"
+ chdir: "sriov_driver"
+ when: _SRIOV
diff --git a/kud/deployment_infra/playbooks/sriov_hardware_check.sh b/kud/deployment_infra/playbooks/sriov_hardware_check.sh
index ea1b7b0c..662c28c8 100644
--- a/kud/deployment_infra/playbooks/sriov_hardware_check.sh
+++ b/kud/deployment_infra/playbooks/sriov_hardware_check.sh
@@ -12,14 +12,14 @@ set -o pipefail
source /etc/environment
-ethernet_adpator_version=$( lspci | grep "Ethernet Controller X710" | head -n 1 | cut -d " " -f 8 )
+ethernet_adpator_version=$( lspci | grep "Ethernet Controller XL710" | head -n 1 | cut -d " " -f 8 )
if [ -z "$ethernet_adpator_version" ]; then
echo "False"
exit 0
fi
SRIOV_ENABLED=${ethernet_adpator_version:-"false"}
#checking for the right hardware version of NIC on the machine
-if [ "$ethernet_adpator_version" == "X710" ]; then
+if [ "$ethernet_adpator_version" == "XL710" ]; then
echo "True"
else
echo "False"
diff --git a/kud/hosting_providers/containerized/README.md b/kud/hosting_providers/containerized/README.md
index 4119ca78..12ce1a19 100644
--- a/kud/hosting_providers/containerized/README.md
+++ b/kud/hosting_providers/containerized/README.md
@@ -27,7 +27,7 @@ Kubernetes jobs(a cluster per job) are used to install multiple clusters and log
## Quickstart Installation Guide
-Build the kud docker images as follows:
+Build the kud docker images as follows, add KUD_ENABLE_TESTS & KUD_PLUGIN_ENABLED for the testing only:
```
$ git clone https://github.com/onap/multicloud-k8s.git && cd multicloud-k8s
@@ -38,6 +38,8 @@ $ docker build --rm \
--build-arg HTTPS_PROXY=${HTTPS_PROXY} \
--build-arg no_proxy=${no_proxy} \
--build-arg NO_PROXY=${NO_PROXY} \
+ --build-arg KUD_ENABLE_TESTS=true \
+ --build-arg KUD_PLUGIN_ENABLED=true \
-t github.com/onap/multicloud-k8s:latest . -f build/Dockerfile
```
Let's create a cluster-101 and cluster-102 hosts.ini as follows
@@ -100,7 +102,7 @@ spec:
- name: secret-volume
mountPath: "/.ssh"
command: ["/bin/sh","-c"]
- args: ["cp -r /.ssh /root/; chmod -R 600 /root/.ssh; ./installer --cluster $CLUSTER_NAME"]
+ args: ["cp -r /.ssh /root/; chmod -R 600 /root/.ssh; ./installer --cluster $CLUSTER_NAME --plugins onap4k8s"]
securityContext:
privileged: true
volumes:
diff --git a/kud/hosting_providers/containerized/installer.sh b/kud/hosting_providers/containerized/installer.sh
index 426c89a0..f1b95acb 100755
--- a/kud/hosting_providers/containerized/installer.sh
+++ b/kud/hosting_providers/containerized/installer.sh
@@ -19,15 +19,15 @@ function install_prerequisites {
#install package for docker images
apt-get update
apt-get install -y curl vim wget git \
- software-properties-common python-pip
- add-apt-repository ppa:longsleep/golang-backports
+ software-properties-common python-pip sudo
+ add-apt-repository -y ppa:longsleep/golang-backports
apt-get update
apt-get install -y golang-go rsync
}
# _install_ansible() - Install and Configure Ansible program
function _install_ansible {
- local version=$(grep "ansible_version" ${kud_playbooks}/kud-vars.yml | \
+ local version=$(grep "ansible_version" ${kud_playbooks}/kud-vars.yml |
awk -F ': ' '{print $2}')
mkdir -p /etc/ansible/
pip install ansible==$version
@@ -100,6 +100,14 @@ function install_k8s {
# install_addons() - Install Kubenertes AddOns
function install_addons {
+ if [ ${1:+1} ]; then
+ local plugins_name="$1"
+ echo "additional addons plugins $1"
+ else
+ local plugins_name=""
+ echo "no additional addons pluigns"
+ fi
+
source /etc/environment
echo "Installing Kubernetes AddOns"
ansible-galaxy install $verbose -r \
@@ -108,36 +116,52 @@ function install_addons {
ansible-playbook $verbose -i \
$kud_inventory $kud_playbooks/configure-kud.yml | \
tee $cluster_log/setup-kud.log
- for addon in ${KUD_ADDONS:-virtlet ovn4nfv nfd}; do
+ for addon in ${KUD_ADDONS:-virtlet ovn4nfv nfd sriov $plugins_name}; do
echo "Deploying $addon using configure-$addon.yml playbook.."
ansible-playbook $verbose -i \
$kud_inventory $kud_playbooks/configure-${addon}.yml | \
tee $cluster_log/setup-${addon}.log
- if [[ "${testing_enabled}" == "true" ]]; then
+ done
+
+ echo "Run the test cases if testing_enabled is set to true."
+ if [[ "${testing_enabled}" == "true" ]]; then
+ for addon in ${KUD_ADDONS:-virtlet ovn4nfv nfd sriov $plugins_name}; do
pushd $kud_tests
bash ${addon}.sh
popd
- fi
- done
+ done
+ fi
+ echo "Add-ons deployment complete..."
}
# install_plugin() - Install ONAP Multicloud Kubernetes plugin
function install_plugin {
- echo "Installing multicloud/k8s plugin"
- mkdir -p /opt/{kubeconfig,consul/config}
- cp $HOME/.kube/config /opt/kubeconfig/kud
-
- pushd $kud_folder/../../../deployments
- ./build.sh
+ echo "Installing multicloud/k8s onap4k8s plugin"
if [[ "${testing_enabled}" == "true" ]]; then
- ./start.sh
pushd $kud_tests
- for functional_test in plugin plugin_edgex plugin_fw; do
- bash ${functional_test}.sh
+ echo "Test the onap4k8s installation"
+ bash onap4k8s.sh
+ echo "Test the onap4k8s plugin installation"
+ for functional_test in plugin_edgex plugin_fw; do
+ bash ${functional_test}.sh --external
done
popd
fi
- popd
+}
+
+# install_controllers() - Install ONAP Multicloud Kubernetes controllers
+function install_controllers {
+ echo "Installing multicloud/k8s onap4k8s controllers"
+ if [[ "${testing_enabled}" == "true" ]]; then
+ echo "Test controllers installation"
+ for controller_test in sdwan; do
+ pushd $kud_tests/$controller_test
+ ansible-playbook $verbose -i \
+ $kud_inventory ${controller_test}.yml | \
+ tee $cluster_log/test-${controller_test}.log
+ popd
+ done
+ fi
}
# _print_kubernetes_info() - Prints the login Kubernetes information
@@ -178,6 +202,7 @@ k8s_info_file=$kud_folder/k8s_info.log
testing_enabled=${KUD_ENABLE_TESTS:-false}
mkdir -p /opt/csar
+export CSAR_DIR=/opt/csar
function install_pkg {
# Install dependencies
@@ -188,27 +213,70 @@ function install_pkg {
function install_cluster {
install_k8s $1
- install_addons
+ if [ ${2:+1} ]; then
+ echo "install default addons and $2"
+ install_addons "$2"
+ else
+ install_addons
+ fi
+
echo "installed the addons"
if ${KUD_PLUGIN_ENABLED:-false}; then
install_plugin
echo "installed the install_plugin"
+ install_controllers
+ echo "installed controllers"
fi
_print_kubernetes_info
}
+function usage {
+ echo "installer usage:"
+ echo "./installer.sh --install_pkg - Install the required softwarepackage"
+ echo "./installer.sh --cluster <cluster name> \
+- Install k8s cluster with default plugins"
+ echo "./installer.sh --cluster <cluster name> \
+--plugins <plugin_1 plugin_2> - Install k8s cluster with default plugins \
+and additional plugins such as onap4k8s."
+}
+
+if [ $# -eq 0 ]; then
+ echo "Error: No arguments supplied"
+ usage
+ exit 1
+fi
+
+if [ -z "$1" ]; then
+ echo "Error: Null argument passed"
+ usage
+ exit 1
+fi
if [ "$1" == "--install_pkg" ]; then
export kud_inventory_folder=$kud_folder/inventory
kud_inventory=$kud_inventory_folder/hosts.ini
install_pkg
+ echo "install pkg"
exit 0
fi
if [ "$1" == "--cluster" ]; then
+ if [ -z "${2-}" ]; then
+ echo "Error: Cluster name is null"
+ usage
+ exit 1
+ fi
+
cluster_name=$2
kud_multi_cluster_path=/opt/kud/multi-cluster
cluster_path=$kud_multi_cluster_path/$cluster_name
+ echo $cluster_path
+ if [ ! -d "${cluster_path}" ]; then
+ echo "Error: cluster_path ${cluster_path} doesn't exit"
+ usage
+ exit 1
+ fi
+
cluster_log=$kud_multi_cluster_path/$cluster_name/log
export kud_inventory_folder=$kud_folder/inventory/$cluster_name
kud_inventory=$kud_inventory_folder/hosts.ini
@@ -218,6 +286,27 @@ if [ "$1" == "--cluster" ]; then
cp $kud_multi_cluster_path/$cluster_name/hosts.ini $kud_inventory_folder/
cp -rf $kud_folder/inventory/group_vars $kud_inventory_folder/
+ if [ ${3:+1} ]; then
+ if [ "$3" == "--plugins" ]; then
+ if [ -z "${4-}" ]; then
+ echo "Error: plugins arguments is null; Refer the usage"
+ usage
+ exit 1
+ fi
+ plugins_name=${@:4:$#}
+ install_cluster $cluster_name "$plugins_name"
+ exit 0
+ else
+ echo "Error: cluster argument should have plugins; \
+ Refer the usage"
+ usage
+ exit 1
+ fi
+ fi
install_cluster $cluster_name
exit 0
fi
+
+echo "Error: Refer the installer usage"
+usage
+exit 1
diff --git a/kud/hosting_providers/containerized/inventory/group_vars/k8s-cluster.yml b/kud/hosting_providers/containerized/inventory/group_vars/k8s-cluster.yml
index bc085b4e..31d0d669 100644
--- a/kud/hosting_providers/containerized/inventory/group_vars/k8s-cluster.yml
+++ b/kud/hosting_providers/containerized/inventory/group_vars/k8s-cluster.yml
@@ -71,3 +71,12 @@ local_release_dir: "/tmp/releases"
# cannot access each over via ssh or you want to use local docker
# images as a cache for multiple clusters.
download_localhost: false
+
+# Subnet for cluster IPs
+kube_service_addresses: 10.244.0.0/18
+
+# Subnet for Pod IPs
+kube_pods_subnet: 10.244.64.0/18
+
+# disable localdns cache
+enable_nodelocaldns: false
diff --git a/kud/hosting_providers/vagrant/Vagrantfile b/kud/hosting_providers/vagrant/Vagrantfile
index 2d1b5ab4..58251fe9 100644
--- a/kud/hosting_providers/vagrant/Vagrantfile
+++ b/kud/hosting_providers/vagrant/Vagrantfile
@@ -10,8 +10,8 @@
##############################################################################
box = {
- :virtualbox => { :name => 'elastic/ubuntu-16.04-x86_64', :version => '20180708.0.0' },
- :libvirt => { :name => 'elastic/ubuntu-16.04-x86_64', :version=> '20180210.0.0'}
+ :virtualbox => { :name => 'elastic/ubuntu-18.04-x86_64', :version => '20191013.0.0' },
+ :libvirt => { :name => 'peru/ubuntu-18.04-server-amd64'}
}
require 'yaml'
diff --git a/kud/hosting_providers/vagrant/installer.sh b/kud/hosting_providers/vagrant/installer.sh
index 94023524..235736e1 100755
--- a/kud/hosting_providers/vagrant/installer.sh
+++ b/kud/hosting_providers/vagrant/installer.sh
@@ -21,6 +21,11 @@ function _install_go {
version=$(grep "go_version" ${kud_playbooks}/kud-vars.yml | awk -F "'" '{print $2}')
local tarball=go$version.linux-amd64.tar.gz
+ #gcc is required for go apps compilation
+ if ! which gcc; then
+ sudo apt-get install -y gcc
+ fi
+
if $(go version &>/dev/null); then
return
fi
@@ -107,6 +112,7 @@ function install_k8s {
local_release_dir=$(grep "local_release_dir" $kud_inventory_folder/group_vars/k8s-cluster.yml | awk -F "\"" '{print $2}')
local tarball=v$version.tar.gz
sudo apt-get install -y sshpass make unzip # install make to run mitogen target and unzip is mitogen playbook dependency
+ sudo apt-get install -y gnupg2 software-properties-common
_install_docker
_install_ansible
wget https://github.com/kubernetes-incubator/kubespray/archive/$tarball
@@ -148,23 +154,19 @@ function install_addons {
echo "Installing Kubernetes AddOns"
_install_ansible
sudo ansible-galaxy install $verbose -r $kud_infra_folder/galaxy-requirements.yml --ignore-errors
-
ansible-playbook $verbose -i $kud_inventory $kud_playbooks/configure-kud.yml | sudo tee $log_folder/setup-kud.log
- for addon in ${KUD_ADDONS:-virtlet ovn4nfv nfd}; do
+ for addon in ${KUD_ADDONS:-virtlet ovn4nfv nfd sriov}; do
echo "Deploying $addon using configure-$addon.yml playbook.."
ansible-playbook $verbose -i $kud_inventory $kud_playbooks/configure-${addon}.yml | sudo tee $log_folder/setup-${addon}.log
- if [[ "${testing_enabled}" == "true" ]]; then
- pushd $kud_tests
- bash ${addon}.sh
- popd
- fi
done
- ansible-playbook $verbose -i $kud_inventory $kud_playbooks/configure-sriov.yml | sudo tee $log_folder/setup-sriov.log
- if [[ "${testing_enabled}" == "true" ]]; then
+ echo "Run the test cases if testing_enabled is set to true."
+ if [[ "${testing_enabled}" == "true" ]]; then
+ for addon in ${KUD_ADDONS:-virtlet ovn4nfv nfd sriov}; do
pushd $kud_tests
- bash sriov.sh
+ bash ${addon}.sh
popd
- fi
+ done
+ fi
echo "Add-ons deployment complete..."
}
diff --git a/kud/hosting_providers/vagrant/inventory/group_vars/k8s-cluster.yml b/kud/hosting_providers/vagrant/inventory/group_vars/k8s-cluster.yml
index 7ac7f798..fb744d0e 100644
--- a/kud/hosting_providers/vagrant/inventory/group_vars/k8s-cluster.yml
+++ b/kud/hosting_providers/vagrant/inventory/group_vars/k8s-cluster.yml
@@ -42,7 +42,8 @@ kube_network_plugin: flannel
kubeconfig_localhost: true
# Copy kubectl binary on the host that runs Ansible to inventory/artifacts
kubectl_localhost: true
-
+# Disable nodelocal dns cache
+enable_nodelocaldns: false
# Enable MountPropagation gate feature
local_volumes_enabled: true
local_volume_provisioner_enabled: true
diff --git a/kud/tests/onap4k8s.sh b/kud/tests/onap4k8s.sh
new file mode 100755
index 00000000..702bed46
--- /dev/null
+++ b/kud/tests/onap4k8s.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2018
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+set -o errexit
+set -o pipefail
+
+source _functions.sh
+set +e
+
+master_ip=$(kubectl cluster-info | grep "Kubernetes master" | \
+ awk -F ":" '{print $2}' | awk -F "//" '{print $2}')
+onap_svc_node_port=30498
+declare -i timeout=18
+declare -i interval=10
+
+base_url="http://$master_ip:$onap_svc_node_port/v1"
+
+function check_onap_svc {
+ while ((timeout > 0)); do
+ echo "try $timeout: Wait for $interval seconds to check for onap svc"
+ sleep $interval
+ call_api "$base_url/healthcheck"
+ call_api_ret=$?
+ if [[ $call_api_ret -eq 0 ]]; then
+ echo "onap svc health check is success"
+ exit 0
+ fi
+ ((timeout-=1))
+ done
+}
+
+check_onap_svc
+echo "Failed to check for onap svc"
+exit 1
diff --git a/kud/tests/plugin_edgex.sh b/kud/tests/plugin_edgex.sh
index 8eae5692..ae390add 100755
--- a/kud/tests/plugin_edgex.sh
+++ b/kud/tests/plugin_edgex.sh
@@ -17,7 +17,16 @@ source _common_test.sh
source _functions.sh
source _common.sh
-base_url="http://localhost:9015/v1"
+if [ ${1:+1} ]; then
+ if [ "$1" == "--external" ]; then
+ master_ip=$(kubectl cluster-info | grep "Kubernetes master" | \
+ awk -F ":" '{print $2}' | awk -F "//" '{print $2}')
+ onap_svc_node_port=30498
+ base_url="http://$master_ip:$onap_svc_node_port/v1"
+ fi
+fi
+
+base_url=${base_url:-"http://localhost:9015/v1"}
kubeconfig_path="$HOME/.kube/config"
csar_id=cb009bfe-bbee-11e8-9766-525400435678
rb_name="edgex"
@@ -91,6 +100,9 @@ response="$(call_api -d "${payload}" "${base_url}/instance")"
echo "$response"
vnf_id="$(jq -r '.id' <<< "${response}")"
+print_msg "Waiting for EdgeX instances"
+sleep 240
+
print_msg "Validating Kubernetes"
kubectl get --no-headers=true --namespace=${namespace} deployment edgex-core-command
kubectl get --no-headers=true --namespace=${namespace} service edgex-core-command
diff --git a/kud/tests/plugin_fw.sh b/kud/tests/plugin_fw.sh
index d7bed4fd..eec467c3 100755
--- a/kud/tests/plugin_fw.sh
+++ b/kud/tests/plugin_fw.sh
@@ -17,7 +17,16 @@ source _common_test.sh
source _functions.sh
source _common.sh
-base_url="http://localhost:9015/v1"
+if [ ${1:+1} ]; then
+ if [ "$1" == "--external" ]; then
+ master_ip=$(kubectl cluster-info | grep "Kubernetes master" | \
+ awk -F ":" '{print $2}' | awk -F "//" '{print $2}')
+ onap_svc_node_port=30498
+ base_url="http://$master_ip:$onap_svc_node_port/v1"
+ fi
+fi
+
+base_url=${base_url:-"http://localhost:9015/v1"}
kubeconfig_path="$HOME/.kube/config"
csar_id=cc009bfe-bbee-11e8-9766-525400435678
rb_name="vfw"
@@ -98,6 +107,9 @@ wait_for_pod -n "${namespace}" -l app=firewall
wait_for_pod -n "${namespace}" -l app=packetgen
# TODO: Provide some health check to verify vFW work
+print_msg "Waiting for VNF instances"
+sleep 480
+
print_msg "Retrieving VNF details"
call_api "${base_url}/instance/${vnf_id}"
diff --git a/kud/tests/sdwan.sh b/kud/tests/sdwan.sh
new file mode 100755
index 00000000..64b10f22
--- /dev/null
+++ b/kud/tests/sdwan.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2018
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+set -o errexit
+set -o nounset
+set -o pipefail
+
+echo "Create pods ..."
+kubectl apply -f sdwan/ovn-pod.yml
+kubectl apply -f sdwan/sdwan-openwrt-ovn.yml
+
+bash sdwan/test.sh
+
+echo "Clear pods ..."
+kubectl delete -f sdwan/ovn-pod.yml
+kubectl delete -f sdwan/sdwan-openwrt-ovn.yml
+
+echo "Test Completed!"
diff --git a/kud/tests/sdwan/build/Dockerfile_1806_mwan3.tpl b/kud/tests/sdwan/build/Dockerfile_1806_mwan3.tpl
new file mode 100644
index 00000000..85c7d358
--- /dev/null
+++ b/kud/tests/sdwan/build/Dockerfile_1806_mwan3.tpl
@@ -0,0 +1,26 @@
+FROM openwrt-1806-4-base
+
+#EXPOSE 80
+ENV http_proxy={docker_proxy}
+ENV https_proxy={docker_proxy}
+ENV no_proxy=localhost,120.0.0.1,192.168.*
+
+RUN mkdir /var/lock && \
+ opkg update && \
+ opkg install uhttpd-mod-lua && \
+ uci set uhttpd.main.interpreter='.lua=/usr/bin/lua' && \
+ uci commit uhttpd && \
+ opkg install mwan3 && \
+ opkg install luci-app-mwan3; exit 0
+
+COPY system /etc/config/system
+COPY commands.lua /usr/lib/lua/luci/controller/
+
+ENV http_proxy=
+ENV https_proxy=
+ENV no_proxy=
+
+USER root
+
+# using exec format so that /sbin/init is proc 1 (see procd docs)
+CMD ["/sbin/init"]
diff --git a/kud/tests/sdwan/build/Dockerfile_1806_mwan3_noproxy.tpl b/kud/tests/sdwan/build/Dockerfile_1806_mwan3_noproxy.tpl
new file mode 100644
index 00000000..8b5c57d2
--- /dev/null
+++ b/kud/tests/sdwan/build/Dockerfile_1806_mwan3_noproxy.tpl
@@ -0,0 +1,19 @@
+FROM openwrt-1806-4-base
+
+#EXPOSE 80
+
+RUN mkdir /var/lock && \
+ opkg update && \
+ opkg install uhttpd-mod-lua && \
+ uci set uhttpd.main.interpreter='.lua=/usr/bin/lua' && \
+ uci commit uhttpd && \
+ opkg install mwan3 && \
+ opkg install luci-app-mwan3; exit 0
+
+COPY system /etc/config/system
+COPY commands.lua /usr/lib/lua/luci/controller/
+
+USER root
+
+# using exec format so that /sbin/init is proc 1 (see procd docs)
+CMD ["/sbin/init"]
diff --git a/kud/tests/sdwan/build/README.md b/kud/tests/sdwan/build/README.md
new file mode 100644
index 00000000..87e21956
--- /dev/null
+++ b/kud/tests/sdwan/build/README.md
@@ -0,0 +1,10 @@
+# Introduction:
+Please refer ICN SDWAN Module Design for architecture introduction
+link:https://wiki.akraino.org/display/AK/SDWAN+Module+Design
+
+# SDWAN Docker Image build instructions:
+Use below steps to build openwrt docker image: openwrt-1806-mwan3
+(1) update set_proxy file with proxy used for docker build
+(2) execute build_image.sh
+cd build
+sudo bash build_image.sh
diff --git a/kud/tests/sdwan/build/build_image.sh b/kud/tests/sdwan/build/build_image.sh
new file mode 100644
index 00000000..7ff6e20b
--- /dev/null
+++ b/kud/tests/sdwan/build/build_image.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+# usage: build_images.sh
+
+set -ex
+base_image_tag=openwrt-1806-4-base
+docker_file=Dockerfile_1806_mwan3
+image_tag=openwrt-1806-mwan3
+package=openwrt-18.06.4-x86-64-generic-rootfs
+
+# build openwrt base docker images
+base_image=`docker images | grep $base_image_tag | awk '{print $1}'`
+if [ -z "$base_image" ]; then
+ # download driver source package
+ if [ ! -e /tmp/$package.tar.gz ]; then
+ wget -P /tmp https://downloads.openwrt.org/releases/18.06.4/targets/x86/64/$package.tar.gz
+ fi
+ cp /tmp/$package.tar.gz .
+
+ docker import $package.tar.gz $base_image_tag
+fi
+
+# generate Dockerfile
+test -f ./set_proxy && . set_proxy
+docker_proxy=${docker_proxy-""}
+if [ -z "$docker_proxy" ]; then
+ cp ${docker_file}_noproxy.tpl $docker_file
+else
+ cp $docker_file.tpl $docker_file
+ sed -i "s,{docker_proxy},$docker_proxy,g" $docker_file
+fi
+
+# build docker images for openwrt with wman3
+docker build --network=host -f $docker_file -t $image_tag .
+
+# clear
+docker image rm $base_image_tag
+rm -rf $docker_file
+rm -rf $package.tar.gz
diff --git a/kud/tests/sdwan/build/commands.lua b/kud/tests/sdwan/build/commands.lua
new file mode 100644
index 00000000..d99f4579
--- /dev/null
+++ b/kud/tests/sdwan/build/commands.lua
@@ -0,0 +1,43 @@
+-- Licensed to the public under the GNU General Public License v2.
+
+module("luci.controller.commands", package.seeall)
+
+sys = require "luci.sys"
+ut = require "luci.util"
+io = require "io"
+
+ip = "ip -4 "
+
+function index()
+ entry({"admin", "config", "command"},
+ call("execute")).dependent = false
+end
+
+function trim(s)
+ return s:match("^%s*(.-)%s*$")
+end
+
+function split_and_trim(str, sep)
+ local array = {}
+ local reg = string.format("([^%s]+)", sep)
+ for item in string.gmatch(str, reg) do
+ item_trimed = trim(item)
+ if string.len(item_trimed) > 0 then
+ table.insert(array, item_trimed)
+ end
+ end
+ return array
+end
+
+function execute()
+ local commands = luci.http.formvalue("command")
+ io.stderr:write("Execute command: %s\n" % commands)
+
+ local command_array = split_and_trim(commands, ";")
+ for index, command in ipairs(command_array) do
+ sys.exec(command)
+ end
+
+ luci.http.prepare_content("application/json")
+ luci.http.write_json("{'status':'ok'}")
+end
diff --git a/kud/tests/sdwan/build/set_proxy b/kud/tests/sdwan/build/set_proxy
new file mode 100644
index 00000000..7a195fe5
--- /dev/null
+++ b/kud/tests/sdwan/build/set_proxy
@@ -0,0 +1,2 @@
+# set docker proxy with below line, the build script will use this info
+#docker_proxy=
diff --git a/kud/tests/sdwan/build/system b/kud/tests/sdwan/build/system
new file mode 100644
index 00000000..5165430f
--- /dev/null
+++ b/kud/tests/sdwan/build/system
@@ -0,0 +1,7 @@
+config system
+ option log_file '/var/log/mylog'
+ option timezone 'UTC'
+ option ttylogin '0'
+ option log_size '64'
+ option urandom_seed '0'
+EOF
diff --git a/kud/tests/sdwan/ovn-pod.yml b/kud/tests/sdwan/ovn-pod.yml
new file mode 100644
index 00000000..0715c030
--- /dev/null
+++ b/kud/tests/sdwan/ovn-pod.yml
@@ -0,0 +1,40 @@
+# Create 2 ovn4nfv network attachment definition
+---
+apiVersion: k8s.plugin.opnfv.org/v1alpha1
+kind: Network
+metadata:
+ name: ovn-port-net
+spec:
+ cniType : ovn4nfv
+ ipv4Subnets:
+ - subnet: 172.16.33.0/24
+ name: subnet1
+ gateway: 172.16.33.1/24
+
+---
+apiVersion: k8s.plugin.opnfv.org/v1alpha1
+kind: Network
+metadata:
+ name: ovn-priv-net
+spec:
+ cniType : ovn4nfv
+ ipv4Subnets:
+ - subnet: 172.16.44.0/24
+ name: subnet1
+ gateway: 172.16.44.1/24
+
+---
+apiVersion: v1
+kind: Pod
+metadata:
+ name: ovn-pod
+ annotations:
+ k8s.v1.cni.cncf.io/networks: '[{ "name": "ovn-networkobj"}]'
+ k8s.plugin.opnfv.org/nfn-network: '{ "type": "ovn4nfv", "interface": [{ "name": "ovn-port-net", "interface": "net0" , "defaultGateway": "false"},
+ { "name": "ovn-priv-net", "interface": "net1" , "defaultGateway": "false"}]}'
+spec:
+ containers:
+ - name: ovn-pod
+ image: docker.io/centos/tools:latest
+ command:
+ - /sbin/init
diff --git a/kud/tests/sdwan/sdwan-openwrt-ovn.yml b/kud/tests/sdwan/sdwan-openwrt-ovn.yml
new file mode 100644
index 00000000..2accdc6c
--- /dev/null
+++ b/kud/tests/sdwan/sdwan-openwrt-ovn.yml
@@ -0,0 +1,82 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: sdwan-config-ovn
+data:
+ entrypoint.sh: |
+ #!/bin/bash
+ # Always exit on errors.
+ set -e
+
+ interface0=net0
+ ipaddr0=`ifconfig $interface0 | awk '/inet/{print $2}' | cut -f2 -d ":" | awk 'NR==1 {print $1}'`
+
+ interface1=net1
+ ipaddr1=`ifconfig $interface1 | awk '/inet/{print $2}' | cut -f2 -d ":" | awk 'NR==1 {print $1}'`
+
+ net_config=/etc/config/network
+ cat >> $net_config << EOF
+ config interface 'wan'
+ option ifname '$interface0'
+ option proto 'static'
+ option ipaddr '$ipaddr0'
+ option netmask '255.255.255.0'
+
+ config interface 'wanb'
+ option ifname '$interface1'
+ option proto 'static'
+ option ipaddr '$ipaddr1'
+ option netmask '255.255.255.0'
+ EOF
+
+ /sbin/procd &
+ /sbin/ubusd &
+ iptables -S
+ sleep 1
+ /etc/init.d/rpcd start
+ /etc/init.d/dnsmasq start
+ /etc/init.d/network start
+ /etc/init.d/odhcpd start
+ /etc/init.d/uhttpd start
+ /etc/init.d/log start
+ /etc/init.d/dropbear start
+ /etc/init.d/mwan3 restart
+
+ echo "Entering sleep... (success)"
+
+ # Sleep forever.
+ while true; do sleep 100; done
+
+---
+apiVersion: v1
+kind: Pod
+metadata:
+ name: sdwan-ovn-pod
+ annotations:
+ k8s.v1.cni.cncf.io/networks: '[{ "name": "ovn-networkobj"}]'
+ k8s.plugin.opnfv.org/nfn-network: '{ "type": "ovn4nfv", "interface": [{ "name": "ovn-port-net", "interface": "net0" , "defaultGateway": "false"},
+ { "name": "ovn-priv-net", "interface": "net1" , "defaultGateway": "false"}]}'
+spec:
+ containers:
+ - name: sdwan-ovn-pod
+ image: hle2/openwrt-1806-mwan3:v0.1.0
+ ports:
+ - containerPort: 22
+ - containerPort: 80
+ command:
+ - /bin/sh
+ - /init/entrypoint.sh
+ imagePullPolicy: IfNotPresent
+ securityContext:
+ privileged: true
+ volumeMounts:
+ - name: entrypoint-sh
+ mountPath: /init
+ volumes:
+ - name: entrypoint-sh
+ configMap:
+ name: sdwan-config-ovn
+ items:
+ - key: entrypoint.sh
+ path: entrypoint.sh
diff --git a/kud/tests/sdwan/sdwan.yml b/kud/tests/sdwan/sdwan.yml
new file mode 100644
index 00000000..760d8599
--- /dev/null
+++ b/kud/tests/sdwan/sdwan.yml
@@ -0,0 +1,44 @@
+---
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2018
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+- hosts: localhost
+ become: yes
+ tasks:
+ - name: create ovn network and client workload
+ command: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/ovn-pod.yml"
+
+ - name: create sdwan controller
+ command: "/usr/local/bin/kubectl apply -f {{ playbook_dir }}/sdwan-openwrt-ovn.yml"
+
+- hosts: kube-master
+ become: yes
+ tasks:
+ - name: install wget package for ubuntu systems
+ apt: name=wget state=present update_cache=yes
+ when: ansible_distribution == "Ubuntu"
+
+ - name: install wget package for centos systems
+ yum: name=wget state=present update_cache=yes
+ when: ansible_distribution == "CentOS"
+
+ - name: Execute sdwan test script in cluster master
+ script: test.sh
+ register: sdwan
+
+ - debug:
+ var: sdwan.stdout_lines
+
+- hosts: localhost
+ become: yes
+ tasks:
+ - name: delete ovn network and client workload
+ command: "/usr/local/bin/kubectl delete -f {{ playbook_dir }}/ovn-pod.yml"
+
+ - name: delete sdwan controller
+ command: "/usr/local/bin/kubectl delete -f {{ playbook_dir }}/sdwan-openwrt-ovn.yml"
diff --git a/kud/tests/sdwan/test.sh b/kud/tests/sdwan/test.sh
new file mode 100755
index 00000000..ba4b4173
--- /dev/null
+++ b/kud/tests/sdwan/test.sh
@@ -0,0 +1,120 @@
+#!/bin/bash
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2018
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+set -o errexit
+set -o nounset
+set -o pipefail
+
+sdwan_pod_name=sdwan-ovn-pod
+ovn_pod_name=ovn-pod
+wan_interface=net0
+
+function login {
+ login_url=http://$1/cgi-bin/luci/
+ echo $(wget -S --spider --post-data "luci_username=root&luci_password=" $login_url 2>&1 | grep sysauth= | sed -r 's/.*sysauth=([^;]+);.*/\1/')
+}
+
+function disable_ping {
+ command_url=http://$2/cgi-bin/luci/admin/config/command
+ command="uci set firewall.@rule[1].target='REJECT';fw3 reload"
+ echo $(wget -S --spider --header="Cookie:sysauth=$1" --post-data "command=$command" $command_url 2>&1)
+}
+
+function enable_ping {
+ command_url=http://$2/cgi-bin/luci/admin/config/command
+ command="uci set firewall.@rule[1].target='ACCEPT';fw3 reload"
+ echo $(wget -S --spider --header="Cookie:sysauth=$1" --post-data "command=$command" $command_url 2>&1)
+}
+
+function wait_for_pod {
+ status_phase=""
+ while [[ "$status_phase" != "Running" ]]; do
+ new_phase="$(kubectl get pods -o wide | grep ^$1 | awk '{print $3}')"
+ if [[ "$new_phase" != "$status_phase" ]]; then
+ status_phase="$new_phase"
+ fi
+ if [[ "$new_phase" == "Err"* ]]; then
+ exit 1
+ fi
+ sleep 2
+ done
+}
+
+function wait_for_pod_namespace {
+ status_phase=""
+ while [[ "$status_phase" != "Running" ]]; do
+ new_phase="$(kubectl get pods -o wide -n $2 | grep ^$1 | awk '{print $3}')"
+ if [[ "$new_phase" != "$status_phase" ]]; then
+ status_phase="$new_phase"
+ fi
+ if [[ "$new_phase" == "Err"* ]]; then
+ exit 1
+ fi
+ sleep 2
+ done
+}
+
+echo "Waiting for pods to be ready ..."
+wait_for_pod $ovn_pod_name
+wait_for_pod $sdwan_pod_name
+echo "* Create pods success"
+
+sdwan_pod_ip=$(kubectl get pods -o wide | grep ^$sdwan_pod_name | awk '{print $6}')
+ovn_pod_ip=$(kubectl get pods -o wide | grep ^$ovn_pod_name | awk '{print $6}')
+echo "SDWAN pod ip:"$sdwan_pod_ip
+echo "OVN pod ip:"$ovn_pod_ip
+
+echo "Login to sdwan ..."
+security_token=""
+while [[ "$security_token" == "" ]]; do
+ echo "Get Security Token ..."
+ security_token=$(login $sdwan_pod_ip)
+ sleep 2
+done
+echo "* Security Token: "$security_token
+
+kubectl exec $sdwan_pod_name ifconfig
+
+sdwan_pod_wan_ip=$(kubectl exec $sdwan_pod_name ifconfig $wan_interface | awk '/inet/{print $2}' | cut -f2 -d ":" | awk 'NR==1 {print $1}')
+echo "Verify ping is work through wan interface between $sdwan_pod_name and $ovn_pod_name"
+ping_result=$(kubectl exec $ovn_pod_name -- ping -c 3 $sdwan_pod_wan_ip)
+if [[ $ping_result == *", 0% packet loss"* ]]; then
+ echo "* Ping is work through wan interface"
+else
+ echo "* Test failed!"
+ exit 1
+fi
+
+echo "Disable ping rule of wan interface ..."
+ret=$(disable_ping $security_token $sdwan_pod_ip)
+
+echo "Verify ping is not work through wan interface after ping rule disabled"
+ping_result=$(kubectl exec $ovn_pod_name -- ping -c 3 $sdwan_pod_wan_ip 2>&1 || true)
+if [[ $ping_result == *", 100% packet loss"* ]]; then
+ echo "* Ping is disabled"
+else
+ echo "* Test failed!"
+ exit 1
+fi
+
+echo "Enable ping rule of wan interface ..."
+ret=$(enable_ping $security_token $sdwan_pod_ip)
+
+echo "Verify ping is work through wan interface after ping rule enabled"
+ping_result=$(kubectl exec $ovn_pod_name -- ping -c 3 $sdwan_pod_wan_ip)
+if [[ $ping_result == *", 0% packet loss"* ]]; then
+ echo "* Ping is enabled"
+else
+ echo "* Test failed!"
+ exit 1
+fi
+
+
+echo "Test Completed!"
diff --git a/kud/tests/sriov.sh b/kud/tests/sriov.sh
index c66f5db8..a721b722 100755
--- a/kud/tests/sriov.sh
+++ b/kud/tests/sriov.sh
@@ -10,13 +10,13 @@
set -o pipefail
-ethernet_adpator_version=$( lspci | grep "Ethernet Controller X710" | head -n 1 | cut -d " " -f 8 )
+ethernet_adpator_version=$( lspci | grep "Ethernet Controller XL710" | head -n 1 | cut -d " " -f 8 )
if [ -z "$ethernet_adpator_version" ]; then
echo " Ethernet adapator version is not set. SRIOV test case cannot run on this machine"
exit 0
fi
#checking for the right hardware version of NIC on the machine
-if [ $ethernet_adpator_version == "X710" ]; then
+if [ $ethernet_adpator_version == "XL710" ]; then
echo "NIC card specs match. SRIOV option avaiable for this version."
else
echo -e "Failed. The version supplied does not match.\nTest cannot be executed."
diff --git a/src/Makefile b/src/Makefile
index 94359287..8d856563 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1,15 +1,19 @@
build:
$(MAKE) -C monitor build
$(MAKE) -C k8splugin build
+ $(MAKE) -C orchestrator build
deploy:
$(MAKE) -C monitor deploy
$(MAKE) -C k8splugin deploy
+ $(MAKE) -C orchestrator deploy
all:
$(MAKE) -C monitor all
$(MAKE) -C k8splugin all
+ $(MAKE) -C orchestrator all
clean:
$(MAKE) -C monitor clean
$(MAKE) -C k8splugin clean
+ $(MAKE) -C orchestrator clean
diff --git a/src/k8splugin/Makefile b/src/k8splugin/Makefile
index 7d41158c..77196afa 100644
--- a/src/k8splugin/Makefile
+++ b/src/k8splugin/Makefile
@@ -25,8 +25,8 @@ deploy: build
.PHONY: test
test: clean
- @go build -buildmode=plugin -o ./mock_files/mock_plugins/mockplugin.so ./mock_files/mock_plugins/mockplugin.go
- @go test -v ./...
+ @go build -race -buildmode=plugin -o ./mock_files/mock_plugins/mockplugin.so ./mock_files/mock_plugins/mockplugin.go
+ @go test -race ./...
format:
@go fmt ./...
@@ -40,5 +40,5 @@ clean:
.PHONY: cover
cover:
- @go test ./... -coverprofile=coverage.out
+ @go test -race ./... -coverprofile=coverage.out
@go tool cover -html=coverage.out -o coverage.html
diff --git a/src/k8splugin/api/brokerhandler.go b/src/k8splugin/api/brokerhandler.go
index 669b539f..b377baf1 100644
--- a/src/k8splugin/api/brokerhandler.go
+++ b/src/k8splugin/api/brokerhandler.go
@@ -134,21 +134,21 @@ func (b brokerInstanceHandler) createHandler(w http.ResponseWriter, r *http.Requ
return
}
- rbName := req.getAttributeValue(req.UserDirectives, "definition-name")
+ rbName := req.getAttributeValue(req.SDNCDirectives, "k8s-rb-definition-name")
if rbName == "" {
- http.Error(w, "definition-name is missing from user-directives", http.StatusBadRequest)
+ http.Error(w, "k8s-rb-definition-name is missing from sdnc-directives", http.StatusBadRequest)
return
}
- rbVersion := req.getAttributeValue(req.UserDirectives, "definition-version")
+ rbVersion := req.getAttributeValue(req.SDNCDirectives, "k8s-rb-definition-version")
if rbVersion == "" {
- http.Error(w, "definition-version is missing from user-directives", http.StatusBadRequest)
+ http.Error(w, "k8s-rb-definition-version is missing from sdnc-directives", http.StatusBadRequest)
return
}
- profileName := req.getAttributeValue(req.UserDirectives, "profile-name")
+ profileName := req.getAttributeValue(req.SDNCDirectives, "k8s-rb-profile-name")
if profileName == "" {
- http.Error(w, "profile-name is missing from user-directives", http.StatusBadRequest)
+ http.Error(w, "k8s-rb-profile-name is missing from sdnc-directives", http.StatusBadRequest)
return
}
diff --git a/src/k8splugin/api/brokerhandler_test.go b/src/k8splugin/api/brokerhandler_test.go
index 8ef5e184..00ca3b7b 100644
--- a/src/k8splugin/api/brokerhandler_test.go
+++ b/src/k8splugin/api/brokerhandler_test.go
@@ -54,11 +54,11 @@ func TestBrokerCreateHandler(t *testing.T) {
"user_directives": {
"attributes": [
{
- "attribute_name": "definition-name",
+ "attribute_name": "k8s-rb-definition-name",
"attribute_value": "test-rbdef"
},
{
- "attribute_name": "definition-version",
+ "attribute_name": "k8s-rb-definition-version",
"attribute_value": "v1"
}
]
@@ -67,9 +67,9 @@ func TestBrokerCreateHandler(t *testing.T) {
expectedCode: http.StatusBadRequest,
},
{
- label: "Succesfully create an Instance",
+ label: "Deprecated parameters passed (user_directives)",
input: bytes.NewBuffer([]byte(`{
- "vf-module-model-customization-id": "84sdfkio938",
+ "vf-module-model-customization-id": "97sdfkio168",
"sdnc_directives": {
"attributes": [
{
@@ -81,15 +81,42 @@ func TestBrokerCreateHandler(t *testing.T) {
"user_directives": {
"attributes": [
{
- "attribute_name": "definition-name",
+ "attribute_name": "rb-definition-name",
+ "attribute_value": "test-rbdef"
+ },
+ {
+ "attribute_name": "rb-definition-version",
+ "attribute_value": "v1"
+ },
+ {
+ "attribute_name": "rb-profile-name",
+ "attribute_value": "profile1"
+ }
+ ]
+ }
+ }`)),
+ expectedCode: http.StatusBadRequest,
+ },
+ {
+ label: "Succesfully create an Instance",
+ input: bytes.NewBuffer([]byte(`{
+ "vf-module-model-customization-id": "84sdfkio938",
+ "sdnc_directives": {
+ "attributes": [
+ {
+ "attribute_name": "vf_module_name",
+ "attribute_value": "test-vf-module-name"
+ },
+ {
+ "attribute_name": "k8s-rb-definition-name",
"attribute_value": "test-rbdef"
},
{
- "attribute_name": "definition-version",
+ "attribute_name": "k8s-rb-definition-version",
"attribute_value": "v1"
},
{
- "attribute_name": "profile-name",
+ "attribute_name": "k8s-rb-profile-name",
"attribute_value": "profile1"
}
]
diff --git a/src/k8splugin/api/instancehandler.go b/src/k8splugin/api/instancehandler.go
index ab98e4be..b0437426 100644
--- a/src/k8splugin/api/instancehandler.go
+++ b/src/k8splugin/api/instancehandler.go
@@ -20,6 +20,7 @@ import (
"net/http"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/app"
+ log "github.com/onap/multicloud-k8s/src/k8splugin/internal/logutils"
"github.com/gorilla/mux"
pkgerrors "github.com/pkg/errors"
@@ -36,14 +37,25 @@ func (i instanceHandler) validateBody(body interface{}) error {
switch b := body.(type) {
case app.InstanceRequest:
if b.CloudRegion == "" {
+ log.Error("CreateVnfRequest Bad Request", log.Fields{
+ "cloudRegion": "Missing CloudRegion in POST request",
+ })
werr := pkgerrors.Wrap(errors.New("Invalid/Missing CloudRegion in POST request"), "CreateVnfRequest bad request")
return werr
}
if b.RBName == "" || b.RBVersion == "" {
+ log.Error("CreateVnfRequest Bad Request", log.Fields{
+ "message": "One of RBName, RBVersion is missing",
+ "RBName": b.RBName,
+ "RBVersion": b.RBVersion,
+ })
werr := pkgerrors.Wrap(errors.New("Invalid/Missing resource bundle parameters in POST request"), "CreateVnfRequest bad request")
return werr
}
if b.ProfileName == "" {
+ log.Error("CreateVnfRequest bad request", log.Fields{
+ "ProfileName": "Missing profile name in POST request",
+ })
werr := pkgerrors.Wrap(errors.New("Invalid/Missing profile name in POST request"), "CreateVnfRequest bad request")
return werr
}
@@ -57,9 +69,15 @@ func (i instanceHandler) createHandler(w http.ResponseWriter, r *http.Request) {
err := json.NewDecoder(r.Body).Decode(&resource)
switch {
case err == io.EOF:
+ log.Error("Body Empty", log.Fields{
+ "error": io.EOF,
+ })
http.Error(w, "Body empty", http.StatusBadRequest)
return
case err != nil:
+ log.Error("Error unmarshaling Body", log.Fields{
+ "error": err,
+ })
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
return
}
@@ -67,12 +85,19 @@ func (i instanceHandler) createHandler(w http.ResponseWriter, r *http.Request) {
// Check body for expected parameters
err = i.validateBody(resource)
if err != nil {
+ log.Error("Invalid Parameters in Body", log.Fields{
+ "error": err,
+ })
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
return
}
resp, err := i.client.Create(resource)
if err != nil {
+ log.Error("Error Creating Resource", log.Fields{
+ "error": err,
+ "resource": resource,
+ })
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -81,6 +106,10 @@ func (i instanceHandler) createHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
err = json.NewEncoder(w).Encode(resp)
if err != nil {
+ log.Error("Error Marshaling Response", log.Fields{
+ "error": err,
+ "response": resp,
+ })
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -93,6 +122,10 @@ func (i instanceHandler) getHandler(w http.ResponseWriter, r *http.Request) {
resp, err := i.client.Get(id)
if err != nil {
+ log.Error("Error getting Instance", log.Fields{
+ "error": err,
+ "id": id,
+ })
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -101,6 +134,10 @@ func (i instanceHandler) getHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
err = json.NewEncoder(w).Encode(resp)
if err != nil {
+ log.Error("Error Marshaling Response", log.Fields{
+ "error": err,
+ "response": resp,
+ })
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -113,6 +150,10 @@ func (i instanceHandler) statusHandler(w http.ResponseWriter, r *http.Request) {
resp, err := i.client.Status(id)
if err != nil {
+ log.Error("Error getting Status", log.Fields{
+ "error": err,
+ "id": id,
+ })
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -121,6 +162,10 @@ func (i instanceHandler) statusHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
err = json.NewEncoder(w).Encode(resp)
if err != nil {
+ log.Error("Error Marshaling Response", log.Fields{
+ "error": err,
+ "response": resp,
+ })
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -133,10 +178,16 @@ func (i instanceHandler) listHandler(w http.ResponseWriter, r *http.Request) {
//Which will list all instances
rbName := r.FormValue("rb-name")
rbVersion := r.FormValue("rb-version")
- ProfileName := r.FormValue("profile-name")
+ profileName := r.FormValue("profile-name")
- resp, err := i.client.List(rbName, rbVersion, ProfileName)
+ resp, err := i.client.List(rbName, rbVersion, profileName)
if err != nil {
+ log.Error("Error listing instances", log.Fields{
+ "error": err,
+ "rb-name": rbName,
+ "rb-version": rbVersion,
+ "profile-name": profileName,
+ })
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -145,6 +196,10 @@ func (i instanceHandler) listHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
err = json.NewEncoder(w).Encode(resp)
if err != nil {
+ log.Error("Error Marshaling Response", log.Fields{
+ "error": err,
+ "response": resp,
+ })
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -157,6 +212,9 @@ func (i instanceHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
err := i.client.Delete(id)
if err != nil {
+ log.Error("Error Deleting Instance", log.Fields{
+ "error": err,
+ })
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
diff --git a/src/k8splugin/go.mod b/src/k8splugin/go.mod
index aab4cd2f..f924828d 100644
--- a/src/k8splugin/go.mod
+++ b/src/k8splugin/go.mod
@@ -63,6 +63,7 @@ require (
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.8.1
github.com/rubenv/sql-migrate v0.0.0-20190902133344-8926f37f0bc1 // indirect
+ github.com/sirupsen/logrus v1.4.2
github.com/soheilhy/cmux v0.1.4 // indirect
github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245 // indirect
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51 // indirect
diff --git a/src/k8splugin/internal/app/client.go b/src/k8splugin/internal/app/client.go
index e52225d4..78477a82 100644
--- a/src/k8splugin/internal/app/client.go
+++ b/src/k8splugin/internal/app/client.go
@@ -14,12 +14,12 @@ limitations under the License.
package app
import (
- "log"
"os"
"time"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/connection"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
+ log "github.com/onap/multicloud-k8s/src/k8splugin/internal/logutils"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/plugin"
pkgerrors "github.com/pkg/errors"
@@ -116,11 +116,25 @@ func (k *KubernetesClient) ensureNamespace(namespace string) error {
},
}, namespace, k)
+ if err != nil {
+ log.Error("Error checking for namespace", log.Fields{
+ "error": err,
+ "namespace": namespace,
+ })
+ return pkgerrors.Wrap(err, "Error checking for namespace: "+namespace)
+ }
+
if ns == "" {
- log.Println("Creating " + namespace + " namespace")
+ log.Info("Creating Namespace", log.Fields{
+ "namespace": namespace,
+ })
_, err = pluginImpl.Create("", namespace, k)
if err != nil {
+ log.Error("Error Creating Namespace", log.Fields{
+ "error": err,
+ "namespace": namespace,
+ })
return pkgerrors.Wrap(err, "Error creating "+namespace+" namespace")
}
}
@@ -134,7 +148,9 @@ func (k *KubernetesClient) createKind(resTempl helm.KubernetesResourceTemplate,
return helm.KubernetesResource{}, pkgerrors.New("File " + resTempl.FilePath + "does not exists")
}
- log.Println("Processing file: " + resTempl.FilePath)
+ log.Info("Processing Kubernetes Resource", log.Fields{
+ "filepath": resTempl.FilePath,
+ })
pluginImpl, err := plugin.GetPluginByKind(resTempl.GVK.Kind)
if err != nil {
@@ -143,11 +159,19 @@ func (k *KubernetesClient) createKind(resTempl helm.KubernetesResourceTemplate,
createdResourceName, err := pluginImpl.Create(resTempl.FilePath, namespace, k)
if err != nil {
- log.Printf("Error: %s while creating: %s", err.Error(), resTempl.GVK.Kind)
+ log.Error("Error Creating Resource", log.Fields{
+ "error": err,
+ "gvk": resTempl.GVK,
+ "filepath": resTempl.FilePath,
+ })
return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error in plugin "+resTempl.GVK.Kind+" plugin")
}
- log.Print(createdResourceName + " created")
+ log.Info("Created Kubernetes Resource", log.Fields{
+ "resource": createdResourceName,
+ "gvk": resTempl.GVK,
+ })
+
return helm.KubernetesResource{
GVK: resTempl.GVK,
Name: createdResourceName,
@@ -175,14 +199,16 @@ func (k *KubernetesClient) createResources(sortedTemplates []helm.KubernetesReso
}
func (k *KubernetesClient) deleteKind(resource helm.KubernetesResource, namespace string) error {
- log.Println("Deleting Kind: " + resource.GVK.Kind)
+ log.Warn("Deleting Resource", log.Fields{
+ "gvk": resource.GVK,
+ "resource": resource.Name,
+ })
pluginImpl, err := plugin.GetPluginByKind(resource.GVK.Kind)
if err != nil {
return pkgerrors.Wrap(err, "Error loading plugin")
}
- log.Println("Deleting resource: " + resource.Name)
err = pluginImpl.Delete(resource, namespace, k)
if err != nil {
return pkgerrors.Wrap(err, "Error deleting "+resource.Name)
diff --git a/src/k8splugin/internal/logutils/logger.go b/src/k8splugin/internal/logutils/logger.go
new file mode 100644
index 00000000..2e8f9969
--- /dev/null
+++ b/src/k8splugin/internal/logutils/logger.go
@@ -0,0 +1,28 @@
+package logutils
+
+import (
+ log "github.com/sirupsen/logrus"
+)
+
+//Fields is type that will be used by the calling function
+type Fields map[string]interface{}
+
+func init() {
+ // Log as JSON instead of the default ASCII formatter.
+ log.SetFormatter(&log.JSONFormatter{})
+}
+
+// Error uses the fields provided and logs
+func Error(msg string, fields Fields) {
+ log.WithFields(log.Fields(fields)).Error(msg)
+}
+
+// Warn uses the fields provided and logs
+func Warn(msg string, fields Fields) {
+ log.WithFields(log.Fields(fields)).Warn(msg)
+}
+
+// Info uses the fields provided and logs
+func Info(msg string, fields Fields) {
+ log.WithFields(log.Fields(fields)).Info(msg)
+}
diff --git a/src/orchestrator/Makefile b/src/orchestrator/Makefile
new file mode 100644
index 00000000..b17485ca
--- /dev/null
+++ b/src/orchestrator/Makefile
@@ -0,0 +1,37 @@
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2019 Intel Corporation
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+export GO111MODULE=on
+
+all: clean
+ CGO_ENABLED=1 GOOS=linux GOARCH=amd64
+ @go build -tags netgo -o ./orchestrator ./cmd/main.go
+
+# The following is done this way as each patch on CI runs build and each merge runs deploy. So for build we don't need to build binary and hence
+# no need to create a static binary with additional flags. However, for generating binary, additional build flags are necessary. This if used with
+# mock plugin errors out for unit tests. So the seperation avoids the error.
+
+build: clean test cover
+deploy: build
+
+.PHONY: test
+test: clean
+ @go test -race ./...
+
+format:
+ @go fmt ./...
+
+clean:
+ @find . -name "*so" -delete
+ @rm -f orchestrator coverage.html coverage.out
+
+.PHONY: cover
+cover:
+ @go test -race ./... -coverprofile=coverage.out
+ @go tool cover -html=coverage.out -o coverage.html
diff --git a/src/orchestrator/api/api.go b/src/orchestrator/api/api.go
new file mode 100644
index 00000000..83f17bbe
--- /dev/null
+++ b/src/orchestrator/api/api.go
@@ -0,0 +1,38 @@
+/*
+Copyright 2018 Intel Corporation.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+ "github.com/onap/multicloud-k8s/src/orchestrator/internal/project"
+
+ "github.com/gorilla/mux"
+)
+
+// NewRouter creates a router that registers the various urls that are supported
+func NewRouter(projectClient project.ProjectManager) *mux.Router {
+
+ router := mux.NewRouter().PathPrefix("/v2").Subrouter()
+
+ if projectClient == nil {
+ projectClient = project.NewProjectClient()
+ }
+ projHandler := projectHandler{
+ client: projectClient,
+ }
+ router.HandleFunc("/project", projHandler.createHandler).Methods("POST")
+ router.HandleFunc("/project/{project-name}", projHandler.getHandler).Methods("GET")
+ router.HandleFunc("/project/{project-name}", projHandler.deleteHandler).Methods("DELETE")
+
+ return router
+}
diff --git a/src/orchestrator/api/projecthandler.go b/src/orchestrator/api/projecthandler.go
new file mode 100644
index 00000000..30f21de3
--- /dev/null
+++ b/src/orchestrator/api/projecthandler.go
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2019 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package api
+
+import (
+ "encoding/json"
+ "io"
+ "net/http"
+
+ "github.com/onap/multicloud-k8s/src/orchestrator/internal/project"
+
+ "github.com/gorilla/mux"
+)
+
+// Used to store backend implementations objects
+// Also simplifies mocking for unit testing purposes
+type projectHandler struct {
+ // Interface that implements Project operations
+ // We will set this variable with a mock interface for testing
+ client project.ProjectManager
+}
+
+// Create handles creation of the Project entry in the database
+func (h projectHandler) createHandler(w http.ResponseWriter, r *http.Request) {
+ var p project.Project
+
+ err := json.NewDecoder(r.Body).Decode(&p)
+ switch {
+ case err == io.EOF:
+ http.Error(w, "Empty body", http.StatusBadRequest)
+ return
+ case err != nil:
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ // Name is required.
+ if p.ProjectName == "" {
+ http.Error(w, "Missing name in POST request", http.StatusBadRequest)
+ return
+ }
+
+ ret, err := h.client.Create(p)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Get handles GET operations on a particular Project Name
+// Returns a rb.Project
+func (h projectHandler) getHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ name := vars["project-name"]
+
+ ret, err := h.client.Get(name)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Delete handles DELETE operations on a particular Project Name
+func (h projectHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ name := vars["project-name"]
+
+ err := h.client.Delete(name)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
diff --git a/src/orchestrator/api/projecthandler_test.go b/src/orchestrator/api/projecthandler_test.go
new file mode 100644
index 00000000..2699f2e3
--- /dev/null
+++ b/src/orchestrator/api/projecthandler_test.go
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package api
+
+import (
+ "bytes"
+ "encoding/json"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+
+ "github.com/onap/multicloud-k8s/src/orchestrator/internal/project"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+//Creating an embedded interface via anonymous variable
+//This allows us to make mockDB satisfy the DatabaseConnection
+//interface even if we are not implementing all the methods in it
+type mockProjectManager struct {
+ // Items and err will be used to customize each test
+ // via a localized instantiation of mockProjectManager
+ Items []project.Project
+ Err error
+}
+
+func (m *mockProjectManager) Create(inp project.Project) (project.Project, error) {
+ if m.Err != nil {
+ return project.Project{}, m.Err
+ }
+
+ return m.Items[0], nil
+}
+
+func (m *mockProjectManager) Get(name string) (project.Project, error) {
+ if m.Err != nil {
+ return project.Project{}, m.Err
+ }
+
+ return m.Items[0], nil
+}
+
+func (m *mockProjectManager) Delete(name string) error {
+ return m.Err
+}
+
+func TestProjectCreateHandler(t *testing.T) {
+ testCases := []struct {
+ label string
+ reader io.Reader
+ expected project.Project
+ expectedCode int
+ projectClient *mockProjectManager
+ }{
+ {
+ label: "Missing Body Failure",
+ expectedCode: http.StatusBadRequest,
+ projectClient: &mockProjectManager{},
+ },
+ {
+ label: "Create Project",
+ expectedCode: http.StatusCreated,
+ reader: bytes.NewBuffer([]byte(`{
+ "project-name":"testProject",
+ "description":"Test Project used for unit testing"
+ }`)),
+ expected: project.Project{
+ ProjectName: "testProject",
+ Description: "Test Project used for unit testing",
+ },
+ projectClient: &mockProjectManager{
+ //Items that will be returned by the mocked Client
+ Items: []project.Project{
+ {
+ ProjectName: "testProject",
+ Description: "Test Project used for unit testing",
+ },
+ },
+ },
+ },
+ {
+ label: "Missing Project Name in Request Body",
+ reader: bytes.NewBuffer([]byte(`{
+ "description":"test description"
+ }`)),
+ expectedCode: http.StatusBadRequest,
+ projectClient: &mockProjectManager{},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ request := httptest.NewRequest("POST", "/v2/project", testCase.reader)
+ resp := executeRequest(request, NewRouter(testCase.projectClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+
+ //Check returned body only if statusCreated
+ if resp.StatusCode == http.StatusCreated {
+ got := project.Project{}
+ json.NewDecoder(resp.Body).Decode(&got)
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("createHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestProjectGetHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ expected project.Project
+ name, version string
+ expectedCode int
+ projectClient *mockProjectManager
+ }{
+ {
+ label: "Get Project",
+ expectedCode: http.StatusOK,
+ expected: project.Project{
+ ProjectName: "testProject",
+ Description: "A Test project for unit testing",
+ },
+ name: "testProject",
+ projectClient: &mockProjectManager{
+ Items: []project.Project{
+ {
+ ProjectName: "testProject",
+ Description: "A Test project for unit testing",
+ },
+ },
+ },
+ },
+ {
+ label: "Get Non-Exiting Project",
+ expectedCode: http.StatusInternalServerError,
+ name: "nonexistingproject",
+ projectClient: &mockProjectManager{
+ Items: []project.Project{},
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ request := httptest.NewRequest("GET", "/v2/project/"+testCase.name, nil)
+ resp := executeRequest(request, NewRouter(testCase.projectClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+
+ //Check returned body only if statusOK
+ if resp.StatusCode == http.StatusOK {
+ got := project.Project{}
+ json.NewDecoder(resp.Body).Decode(&got)
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("listHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestProjectDeleteHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ name string
+ version string
+ expectedCode int
+ projectClient *mockProjectManager
+ }{
+ {
+ label: "Delete Project",
+ expectedCode: http.StatusNoContent,
+ name: "testProject",
+ projectClient: &mockProjectManager{},
+ },
+ {
+ label: "Delete Non-Exiting Project",
+ expectedCode: http.StatusInternalServerError,
+ name: "testProject",
+ projectClient: &mockProjectManager{
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ request := httptest.NewRequest("DELETE", "/v2/project/"+testCase.name, nil)
+ resp := executeRequest(request, NewRouter(testCase.projectClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+ })
+ }
+}
diff --git a/src/orchestrator/api/testing.go b/src/orchestrator/api/testing.go
new file mode 100644
index 00000000..e99ec75b
--- /dev/null
+++ b/src/orchestrator/api/testing.go
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package api
+
+import (
+ "net/http"
+ "net/http/httptest"
+
+ "github.com/gorilla/mux"
+)
+
+func executeRequest(request *http.Request, router *mux.Router) *http.Response {
+ recorder := httptest.NewRecorder()
+ router.ServeHTTP(recorder, request)
+ resp := recorder.Result()
+ return resp
+}
diff --git a/src/orchestrator/cmd/main.go b/src/orchestrator/cmd/main.go
new file mode 100644
index 00000000..657d5bf5
--- /dev/null
+++ b/src/orchestrator/cmd/main.go
@@ -0,0 +1,71 @@
+/*
+Copyright 2018 Intel Corporation.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+ "context"
+ "log"
+ "math/rand"
+ "net/http"
+ "os"
+ "os/signal"
+ "time"
+
+ "github.com/onap/multicloud-k8s/src/orchestrator/api"
+ "github.com/onap/multicloud-k8s/src/orchestrator/internal/auth"
+ "github.com/onap/multicloud-k8s/src/orchestrator/internal/config"
+ "github.com/onap/multicloud-k8s/src/orchestrator/internal/db"
+
+ "github.com/gorilla/handlers"
+)
+
+func main() {
+
+ rand.Seed(time.Now().UnixNano())
+
+ err := db.InitializeDatabaseConnection()
+ if err != nil {
+ log.Println("Unable to initialize database connection...")
+ log.Println(err)
+ log.Fatalln("Exiting...")
+ }
+
+ httpRouter := api.NewRouter(nil)
+ loggedRouter := handlers.LoggingHandler(os.Stdout, httpRouter)
+ log.Println("Starting Kubernetes Multicloud API")
+
+ httpServer := &http.Server{
+ Handler: loggedRouter,
+ Addr: ":" + config.GetConfiguration().ServicePort,
+ }
+
+ connectionsClose := make(chan struct{})
+ go func() {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+ <-c
+ httpServer.Shutdown(context.Background())
+ close(connectionsClose)
+ }()
+
+ tlsConfig, err := auth.GetTLSConfig("ca.cert", "server.cert", "server.key")
+ if err != nil {
+ log.Println("Error Getting TLS Configuration. Starting without TLS...")
+ log.Fatal(httpServer.ListenAndServe())
+ } else {
+ httpServer.TLSConfig = tlsConfig
+ // empty strings because tlsconfig already has this information
+ err = httpServer.ListenAndServeTLS("", "")
+ }
+}
diff --git a/src/orchestrator/go.mod b/src/orchestrator/go.mod
new file mode 100644
index 00000000..d6fada43
--- /dev/null
+++ b/src/orchestrator/go.mod
@@ -0,0 +1,34 @@
+module github.com/onap/multicloud-k8s/src/orchestrator
+
+require (
+ github.com/docker/engine v0.0.0-20190620014054-c513a4c6c298
+ github.com/ghodss/yaml v1.0.0
+ github.com/gogo/protobuf v1.3.1 // indirect
+ github.com/golang/snappy v0.0.1 // indirect
+ github.com/gorilla/handlers v1.3.0
+ github.com/gorilla/mux v1.6.2
+ github.com/hashicorp/consul v1.4.0
+ github.com/json-iterator/go v1.1.8 // indirect
+ github.com/onap/multicloud-k8s/src/k8splugin v0.0.0-20191115005109-f168ebb73d8d // indirect
+ github.com/pkg/errors v0.8.1
+ github.com/sirupsen/logrus v1.4.2
+ go.etcd.io/etcd v3.3.12+incompatible
+ go.mongodb.org/mongo-driver v1.0.0
+ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
+ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
+ k8s.io/api v0.0.0-20190831074750-7364b6bdad65
+ k8s.io/apimachinery v0.0.0-20190831074630-461753078381
+ k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible
+ k8s.io/helm v2.14.3+incompatible
+ k8s.io/klog v1.0.0 // indirect
+)
+
+replace (
+ k8s.io/api => k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
+ k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8
+ k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
+ k8s.io/apiserver => k8s.io/apiserver v0.0.0-20190409021813-1ec86e4da56c
+ k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20190409023024-d644b00f3b79
+ k8s.io/client-go => k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible
+ k8s.io/cloud-provider => k8s.io/cloud-provider v0.0.0-20190409023720-1bc0c81fa51d
+)
diff --git a/src/orchestrator/go.sum b/src/orchestrator/go.sum
new file mode 100644
index 00000000..dcda41d4
--- /dev/null
+++ b/src/orchestrator/go.sum
@@ -0,0 +1,361 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
+github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
+github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
+github.com/Masterminds/sprig v2.17.1+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
+github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
+github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/aokoli/goutils v1.1.0/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
+github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
+github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/etcd v3.3.12+incompatible h1:pAWNwdf7QiT1zfaWyqCtNZQWCLByQyA3JrSQyuYAqnQ=
+github.com/coreos/etcd v3.3.12+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
+github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/docker v0.7.3-0.20190912223608-ad718029b705/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/engine v0.0.0-20190620014054-c513a4c6c298/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY=
+github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
+github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
+github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
+github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
+github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
+github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
+github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
+github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
+github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
+github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
+github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
+github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
+github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
+github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
+github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
+github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
+github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
+github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/handlers v1.3.0 h1:tsg9qP3mjt1h4Roxp+M1paRjrVBfPSOpBuVclh6YluI=
+github.com/gorilla/handlers v1.3.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
+github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.11.1/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/consul v1.4.0 h1:PQTW4xCuAExEiSbhrsFsikzbW5gVBoi74BjUvYFyKHw=
+github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 h1:VBj0QYQ0u2MCJzBfeYXGexnAl17GsH1yidnoxCqqD9E=
+github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.1 h1:mYs6SMzu72+90OcPa5wr3nfznA4Dw9UyR791ZFNOIf4=
+github.com/hashicorp/serf v0.8.1/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
+github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
+github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
+github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/onap/multicloud-k8s v0.0.0-20191115005109-f168ebb73d8d h1:3uFucXVv6gqa3H1u85CjoLOvGraREfD8/NL7m/9W9tc=
+github.com/onap/multicloud-k8s/src/k8splugin v0.0.0-20191115005109-f168ebb73d8d h1:ucIEjqzNVeFPnQofeuBfUqro0OnilX//fajEFxuLsgA=
+github.com/onap/multicloud-k8s/src/k8splugin v0.0.0-20191115005109-f168ebb73d8d/go.mod h1:EnQd/vQGZR1/55IihaHxiux4ZUig/zfXZux7bfmU0S8=
+github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
+github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rubenv/sql-migrate v0.0.0-20190902133344-8926f37f0bc1/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245/go.mod h1:O1c8HleITsZqzNZDjSNzirUGsMT0oGu9LhHKoJrqO+A=
+github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
+github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
+github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
+github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
+github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
+go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/etcd v3.3.12+incompatible h1:V6PRYRGpU4k5EajJaaj/GL3hqIdzyPnBU8aPUp+35yw=
+go.etcd.io/etcd v3.3.12+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
+go.mongodb.org/mongo-driver v1.0.0 h1:KxPRDyfB2xXnDE2My8acoOWBQkfv3tz0SaWTRZjJR0c=
+go.mongodb.org/mongo-driver v1.0.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
+golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
+golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b h1:aBGgKJUM9Hk/3AE8WaZIApnTxG35kbuQba2w+SXqezo=
+k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
+k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
+k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA=
+k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
+k8s.io/apiserver v0.0.0-20190409021813-1ec86e4da56c/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w=
+k8s.io/cli-runtime v0.0.0-20190409023024-d644b00f3b79/go.mod h1:qWnH3/b8sp/l7EvlDh7ulDU3UWA4P4N1NFbEEP791tM=
+k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ=
+k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
+k8s.io/cloud-provider v0.0.0-20190409023720-1bc0c81fa51d/go.mod h1:LlIffnLBu+GG7d4ppPzC8UnA1Ex8S+ntmSRVsnr7Xy4=
+k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
+k8s.io/helm v2.14.3+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
+k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
+k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
+k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
+k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
+k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
+k8s.io/kubernetes v1.14.1/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
+k8s.io/utils v0.0.0-20190907131718-3d4f5b7dea0b/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
+sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
+sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
+sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
+sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
diff --git a/src/orchestrator/internal/auth/auth.go b/src/orchestrator/internal/auth/auth.go
new file mode 100644
index 00000000..3da8f2af
--- /dev/null
+++ b/src/orchestrator/internal/auth/auth.go
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package auth
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/pem"
+ "io/ioutil"
+ "log"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+// GetTLSConfig initializes a tlsConfig using the CA's certificate
+// This config is then used to enable the server for mutual TLS
+func GetTLSConfig(caCertFile string, certFile string, keyFile string) (*tls.Config, error) {
+
+ // Initialize tlsConfig once
+ caCert, err := ioutil.ReadFile(caCertFile)
+
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Read CA Cert file")
+ }
+
+ caCertPool := x509.NewCertPool()
+ caCertPool.AppendCertsFromPEM(caCert)
+
+ tlsConfig := &tls.Config{
+ // Change to RequireAndVerify once we have mandatory certs
+ ClientAuth: tls.VerifyClientCertIfGiven,
+ ClientCAs: caCertPool,
+ MinVersion: tls.VersionTLS12,
+ }
+
+ certPEMBlk, err := readPEMBlock(certFile)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Read Cert File")
+ }
+
+ keyPEMBlk, err := readPEMBlock(keyFile)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Read Key File")
+ }
+
+ tlsConfig.Certificates = make([]tls.Certificate, 1)
+ tlsConfig.Certificates[0], err = tls.X509KeyPair(certPEMBlk, keyPEMBlk)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Load x509 cert and key")
+ }
+
+ tlsConfig.BuildNameToCertificate()
+ return tlsConfig, nil
+}
+
+func readPEMBlock(filename string) ([]byte, error) {
+
+ pemData, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Read PEM File")
+ }
+
+ pemBlock, rest := pem.Decode(pemData)
+ if len(rest) > 0 {
+ log.Println("Pemfile has extra data")
+ }
+
+ if x509.IsEncryptedPEMBlock(pemBlock) {
+ password, err := ioutil.ReadFile(filename + ".pass")
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Read Password File")
+ }
+
+ pByte, err := base64.StdEncoding.DecodeString(string(password))
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Decode PEM Password")
+ }
+
+ pemData, err = x509.DecryptPEMBlock(pemBlock, pByte)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Decrypt PEM Data")
+ }
+ var newPEMBlock pem.Block
+ newPEMBlock.Type = pemBlock.Type
+ newPEMBlock.Bytes = pemData
+ // Converting back to PEM from DER data you get from
+ // DecryptPEMBlock
+ pemData = pem.EncodeToMemory(&newPEMBlock)
+ }
+
+ return pemData, nil
+}
diff --git a/src/orchestrator/internal/auth/auth_test.go b/src/orchestrator/internal/auth/auth_test.go
new file mode 100644
index 00000000..e41cb1ac
--- /dev/null
+++ b/src/orchestrator/internal/auth/auth_test.go
@@ -0,0 +1,47 @@
+/*
+* Copyright 2018 TechMahindra
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+
+package auth
+
+import (
+ "crypto/tls"
+ "testing"
+)
+
+//Unit test to varify GetTLSconfig func and varify the tls config min version to be 771
+//Assuming cert file name as auth_test.cert
+func TestGetTLSConfig(t *testing.T) {
+ _, err := GetTLSConfig("filedoesnotexist.cert", "filedoesnotexist.cert", "filedoesnotexist.cert")
+ if err == nil {
+ t.Errorf("Test failed, expected error but got none")
+ }
+ tlsConfig, err := GetTLSConfig("../../tests/certs/auth_test_certificate.pem",
+ "../../tests/certs/auth_test_certificate.pem",
+ "../../tests/certs/auth_test_key.pem")
+ if err != nil {
+ t.Fatal("Test Failed as GetTLSConfig returned error: " + err.Error())
+ }
+ expected := tls.VersionTLS12
+ actual := tlsConfig.MinVersion
+ if tlsConfig != nil {
+ if int(actual) != expected {
+ t.Errorf("Test Failed due to version mismatch")
+ }
+ if tlsConfig == nil {
+ t.Errorf("Test Failed due to GetTLSConfig returned nil")
+ }
+ }
+}
diff --git a/src/orchestrator/internal/config/config.go b/src/orchestrator/internal/config/config.go
new file mode 100644
index 00000000..cb4656f0
--- /dev/null
+++ b/src/orchestrator/internal/config/config.go
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2019 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package config
+
+import (
+ "encoding/json"
+ "log"
+ "os"
+ "reflect"
+)
+
+// Configuration loads up all the values that are used to configure
+// backend implementations
+type Configuration struct {
+ CAFile string `json:"ca-file"`
+ ServerCert string `json:"server-cert"`
+ ServerKey string `json:"server-key"`
+ Password string `json:"password"`
+ DatabaseIP string `json:"database-ip"`
+ DatabaseType string `json:"database-type"`
+ PluginDir string `json:"plugin-dir"`
+ EtcdIP string `json:"etcd-ip"`
+ EtcdCert string `json:"etcd-cert"`
+ EtcdKey string `json:"etcd-key"`
+ EtcdCAFile string `json:"etcd-ca-file"`
+ ServicePort string `json:"service-port"`
+ KubernetesLabelName string `json:"kubernetes-label-name"`
+}
+
+// Config is the structure that stores the configuration
+var gConfig *Configuration
+
+// readConfigFile reads the specified smsConfig file to setup some env variables
+func readConfigFile(file string) (*Configuration, error) {
+ f, err := os.Open(file)
+ if err != nil {
+ return defaultConfiguration(), err
+ }
+ defer f.Close()
+
+ // Setup some defaults here
+ // If the json file has values in it, the defaults will be overwritten
+ conf := defaultConfiguration()
+
+ // Read the configuration from json file
+ decoder := json.NewDecoder(f)
+ decoder.DisallowUnknownFields()
+ err = decoder.Decode(conf)
+ if err != nil {
+ return conf, err
+ }
+
+ return conf, nil
+}
+
+func defaultConfiguration() *Configuration {
+ cwd, err := os.Getwd()
+ if err != nil {
+ log.Println("Error getting cwd. Using .")
+ cwd = "."
+ }
+
+ return &Configuration{
+ CAFile: "ca.cert",
+ ServerCert: "server.cert",
+ ServerKey: "server.key",
+ Password: "",
+ DatabaseIP: "127.0.0.1",
+ DatabaseType: "mongo",
+ PluginDir: cwd,
+ EtcdIP: "127.0.0.1",
+ EtcdCert: "etcd.cert",
+ EtcdKey: "etcd.key",
+ EtcdCAFile: "etcd-ca.cert",
+ ServicePort: "9015",
+ KubernetesLabelName: "orchestrator.io/rb-instance-id",
+ }
+}
+
+// GetConfiguration returns the configuration for the app.
+// It will try to load it if it is not already loaded.
+func GetConfiguration() *Configuration {
+ if gConfig == nil {
+ conf, err := readConfigFile("config.json")
+ if err != nil {
+ log.Println("Error loading config file: ", err)
+ log.Println("Using defaults...")
+ }
+ gConfig = conf
+ }
+
+ return gConfig
+}
+
+// SetConfigValue sets a value in the configuration
+// This is mostly used to customize the application and
+// should be used carefully.
+func SetConfigValue(key string, value string) *Configuration {
+ c := GetConfiguration()
+ if value == "" || key == "" {
+ return c
+ }
+
+ v := reflect.ValueOf(c).Elem()
+ if v.Kind() == reflect.Struct {
+ f := v.FieldByName(key)
+ if f.IsValid() {
+ if f.CanSet() {
+ if f.Kind() == reflect.String {
+ f.SetString(value)
+ }
+ }
+ }
+ }
+ return c
+}
diff --git a/src/orchestrator/internal/config/config_test.go b/src/orchestrator/internal/config/config_test.go
new file mode 100644
index 00000000..ce7641ae
--- /dev/null
+++ b/src/orchestrator/internal/config/config_test.go
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package config
+
+import (
+ "testing"
+)
+
+func TestReadConfigurationFile(t *testing.T) {
+ t.Run("Non Existent Configuration File", func(t *testing.T) {
+ _, err := readConfigFile("filedoesnotexist.json")
+ if err == nil {
+ t.Fatal("ReadConfiguationFile: Expected Error, got nil")
+ }
+ })
+
+ t.Run("Read Configuration File", func(t *testing.T) {
+ conf, err := readConfigFile("../../tests/configs/mock_config.json")
+ if err != nil {
+ t.Fatal("ReadConfigurationFile: Error reading file: ", err)
+ }
+ if conf.DatabaseType != "mock_db_test" {
+ t.Fatal("ReadConfigurationFile: Incorrect entry read from file")
+ }
+ })
+}
diff --git a/src/orchestrator/internal/db/mock.go b/src/orchestrator/internal/db/mock.go
new file mode 100644
index 00000000..1dbca4b4
--- /dev/null
+++ b/src/orchestrator/internal/db/mock.go
@@ -0,0 +1,94 @@
+/*
+Copyright 2018 Intel Corporation.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package db
+
+import (
+ "encoding/json"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+type MockKey struct {
+ Key string
+}
+
+func (m MockKey) String() string {
+ return m.Key
+}
+
+//Creating an embedded interface via anonymous variable
+//This allows us to make mockDB satisfy the DatabaseConnection
+//interface even if we are not implementing all the methods in it
+type MockDB struct {
+ Store
+ Items map[string]map[string][]byte
+ Err error
+}
+
+func (m *MockDB) HealthCheck() error {
+ return m.Err
+}
+
+func (m *MockDB) Create(table string, key Key, tag string, data interface{}) error {
+ return m.Err
+}
+
+func (m *MockDB) Update(table string, key Key, tag string, data interface{}) error {
+ return m.Err
+}
+
+// MockDB uses simple JSON and not BSON
+func (m *MockDB) Unmarshal(inp []byte, out interface{}) error {
+ err := json.Unmarshal(inp, out)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Unmarshaling json")
+ }
+ return nil
+}
+
+func (m *MockDB) Read(table string, key Key, tag string) ([]byte, error) {
+ if m.Err != nil {
+ return nil, m.Err
+ }
+
+ for k, v := range m.Items {
+ if k == key.String() {
+ return v[tag], nil
+ }
+ }
+
+ return nil, m.Err
+}
+
+func (m *MockDB) Delete(table string, key Key, tag string) error {
+ return m.Err
+}
+
+func (m *MockDB) ReadAll(table string, tag string) (map[string][]byte, error) {
+ if m.Err != nil {
+ return nil, m.Err
+ }
+
+ ret := make(map[string][]byte)
+
+ for k, v := range m.Items {
+ for k1, v1 := range v {
+ if k1 == tag {
+ ret[k] = v1
+ }
+ }
+ }
+
+ return ret, nil
+}
diff --git a/src/orchestrator/internal/db/mongo.go b/src/orchestrator/internal/db/mongo.go
new file mode 100644
index 00000000..3720a4f2
--- /dev/null
+++ b/src/orchestrator/internal/db/mongo.go
@@ -0,0 +1,396 @@
+/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package db
+
+import (
+ "log"
+
+ "golang.org/x/net/context"
+
+ "github.com/onap/multicloud-k8s/src/orchestrator/internal/config"
+
+ pkgerrors "github.com/pkg/errors"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+)
+
+// MongoCollection defines the a subset of MongoDB operations
+// Note: This interface is defined mainly for mock testing
+type MongoCollection interface {
+ InsertOne(ctx context.Context, document interface{},
+ opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error)
+ FindOne(ctx context.Context, filter interface{},
+ opts ...*options.FindOneOptions) *mongo.SingleResult
+ FindOneAndUpdate(ctx context.Context, filter interface{},
+ update interface{}, opts ...*options.FindOneAndUpdateOptions) *mongo.SingleResult
+ DeleteOne(ctx context.Context, filter interface{},
+ opts ...*options.DeleteOptions) (*mongo.DeleteResult, error)
+ Find(ctx context.Context, filter interface{},
+ opts ...*options.FindOptions) (*mongo.Cursor, error)
+}
+
+// MongoStore is an implementation of the db.Store interface
+type MongoStore struct {
+ db *mongo.Database
+}
+
+// This exists only for allowing us to mock the collection object
+// for testing purposes
+var getCollection = func(coll string, m *MongoStore) MongoCollection {
+ return m.db.Collection(coll)
+}
+
+// This exists only for allowing us to mock the DecodeBytes function
+// Mainly because we cannot construct a SingleResult struct from our
+// tests. All fields in that struct are private.
+var decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) {
+ return sr.DecodeBytes()
+}
+
+// These exists only for allowing us to mock the cursor.Next function
+// Mainly because we cannot construct a mongo.Cursor struct from our
+// tests. All fields in that struct are private and there is no public
+// constructor method.
+var cursorNext = func(ctx context.Context, cursor *mongo.Cursor) bool {
+ return cursor.Next(ctx)
+}
+var cursorClose = func(ctx context.Context, cursor *mongo.Cursor) error {
+ return cursor.Close(ctx)
+}
+
+// NewMongoStore initializes a Mongo Database with the name provided
+// If a database with that name exists, it will be returned
+func NewMongoStore(name string, store *mongo.Database) (Store, error) {
+ if store == nil {
+ ip := "mongodb://" + config.GetConfiguration().DatabaseIP + ":27017"
+ clientOptions := options.Client()
+ clientOptions.ApplyURI(ip)
+ mongoClient, err := mongo.NewClient(clientOptions)
+ if err != nil {
+ return nil, err
+ }
+
+ err = mongoClient.Connect(context.Background())
+ if err != nil {
+ return nil, err
+ }
+ store = mongoClient.Database(name)
+ }
+
+ return &MongoStore{
+ db: store,
+ }, nil
+}
+
+// HealthCheck verifies if the database is up and running
+func (m *MongoStore) HealthCheck() error {
+
+ _, err := decodeBytes(m.db.RunCommand(context.Background(), bson.D{{"serverStatus", 1}}))
+ if err != nil {
+ return pkgerrors.Wrap(err, "Error getting server status")
+ }
+
+ return nil
+}
+
+// validateParams checks to see if any parameters are empty
+func (m *MongoStore) validateParams(args ...interface{}) bool {
+ for _, v := range args {
+ val, ok := v.(string)
+ if ok {
+ if val == "" {
+ return false
+ }
+ } else {
+ if v == nil {
+ return false
+ }
+ }
+ }
+
+ return true
+}
+
+// Create is used to create a DB entry
+func (m *MongoStore) Create(coll string, key Key, tag string, data interface{}) error {
+ if data == nil || !m.validateParams(coll, key, tag) {
+ return pkgerrors.New("No Data to store")
+ }
+
+ c := getCollection(coll, m)
+ ctx := context.Background()
+
+ //Insert the data and then add the objectID to the masterTable
+ res, err := c.InsertOne(ctx, bson.D{
+ {tag, data},
+ })
+ if err != nil {
+ return pkgerrors.Errorf("Error inserting into database: %s", err.Error())
+ }
+
+ //Add objectID of created data to masterKey document
+ //Create masterkey document if it does not exist
+ filter := bson.D{{"key", key}}
+
+ _, err = decodeBytes(
+ c.FindOneAndUpdate(
+ ctx,
+ filter,
+ bson.D{
+ {"$set", bson.D{
+ {tag, res.InsertedID},
+ }},
+ },
+ options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After)))
+
+ if err != nil {
+ return pkgerrors.Errorf("Error updating master table: %s", err.Error())
+ }
+
+ return nil
+}
+
+// Update is used to update a DB entry
+func (m *MongoStore) Update(coll string, key Key, tag string, data interface{}) error {
+ if data == nil || !m.validateParams(coll, key, tag) {
+ return pkgerrors.New("No Data to update")
+ }
+
+ c := getCollection(coll, m)
+ ctx := context.Background()
+
+ //Get the masterkey document based on given key
+ filter := bson.D{{"key", key}}
+ keydata, err := decodeBytes(c.FindOne(context.Background(), filter))
+ if err != nil {
+ return pkgerrors.Errorf("Error finding master table: %s", err.Error())
+ }
+
+ //Read the tag objectID from document
+ tagoid, ok := keydata.Lookup(tag).ObjectIDOK()
+ if !ok {
+ return pkgerrors.Errorf("Error finding objectID for tag %s", tag)
+ }
+
+ //Update the document with new data
+ filter = bson.D{{"_id", tagoid}}
+
+ _, err = decodeBytes(
+ c.FindOneAndUpdate(
+ ctx,
+ filter,
+ bson.D{
+ {"$set", bson.D{
+ {tag, data},
+ }},
+ },
+ options.FindOneAndUpdate().SetReturnDocument(options.After)))
+
+ if err != nil {
+ return pkgerrors.Errorf("Error updating record: %s", err.Error())
+ }
+
+ return nil
+}
+
+// Unmarshal implements an unmarshaler for bson data that
+// is produced from the mongo database
+func (m *MongoStore) Unmarshal(inp []byte, out interface{}) error {
+ err := bson.Unmarshal(inp, out)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Unmarshaling bson")
+ }
+ return nil
+}
+
+// Read method returns the data stored for this key and for this particular tag
+func (m *MongoStore) Read(coll string, key Key, tag string) ([]byte, error) {
+ if !m.validateParams(coll, key, tag) {
+ return nil, pkgerrors.New("Mandatory fields are missing")
+ }
+
+ c := getCollection(coll, m)
+ ctx := context.Background()
+
+ //Get the masterkey document based on given key
+ filter := bson.D{{"key", key}}
+ keydata, err := decodeBytes(c.FindOne(context.Background(), filter))
+ if err != nil {
+ return nil, pkgerrors.Errorf("Error finding master table: %s", err.Error())
+ }
+
+ //Read the tag objectID from document
+ tagoid, ok := keydata.Lookup(tag).ObjectIDOK()
+ if !ok {
+ return nil, pkgerrors.Errorf("Error finding objectID for tag %s", tag)
+ }
+
+ //Use tag objectID to read the data from store
+ filter = bson.D{{"_id", tagoid}}
+ tagdata, err := decodeBytes(c.FindOne(ctx, filter))
+ if err != nil {
+ return nil, pkgerrors.Errorf("Error reading found object: %s", err.Error())
+ }
+
+ //Return the data as a byte array
+ //Convert string data to byte array using the built-in functions
+ switch tagdata.Lookup(tag).Type {
+ case bson.TypeString:
+ return []byte(tagdata.Lookup(tag).StringValue()), nil
+ default:
+ return tagdata.Lookup(tag).Value, nil
+ }
+}
+
+// Helper function that deletes an object by its ID
+func (m *MongoStore) deleteObjectByID(coll string, objID primitive.ObjectID) error {
+
+ c := getCollection(coll, m)
+ ctx := context.Background()
+
+ _, err := c.DeleteOne(ctx, bson.D{{"_id", objID}})
+ if err != nil {
+ return pkgerrors.Errorf("Error Deleting from database: %s", err.Error())
+ }
+
+ log.Printf("Deleted Obj with ID %s", objID.String())
+ return nil
+}
+
+// Delete method removes a document from the Database that matches key
+// TODO: delete all referenced docs if tag is empty string
+func (m *MongoStore) Delete(coll string, key Key, tag string) error {
+ if !m.validateParams(coll, key, tag) {
+ return pkgerrors.New("Mandatory fields are missing")
+ }
+
+ c := getCollection(coll, m)
+ ctx := context.Background()
+
+ //Get the masterkey document based on given key
+ filter := bson.D{{"key", key}}
+ //Remove the tag ID entry from masterkey table
+ update := bson.D{
+ {
+ "$unset", bson.D{
+ {tag, ""},
+ },
+ },
+ }
+ keydata, err := decodeBytes(c.FindOneAndUpdate(ctx, filter, update,
+ options.FindOneAndUpdate().SetReturnDocument(options.Before)))
+ if err != nil {
+ //No document was found. Return nil.
+ if err == mongo.ErrNoDocuments {
+ return nil
+ }
+ //Return any other error that was found.
+ return pkgerrors.Errorf("Error decoding master table after update: %s",
+ err.Error())
+ }
+
+ //Read the tag objectID from document
+ elems, err := keydata.Elements()
+ if err != nil {
+ return pkgerrors.Errorf("Error reading elements from database: %s", err.Error())
+ }
+
+ tagoid, ok := keydata.Lookup(tag).ObjectIDOK()
+ if !ok {
+ return pkgerrors.Errorf("Error finding objectID for tag %s", tag)
+ }
+
+ //Use tag objectID to read the data from store
+ err = m.deleteObjectByID(coll, tagoid)
+ if err != nil {
+ return pkgerrors.Errorf("Error deleting from database: %s", err.Error())
+ }
+
+ //Delete master table if no more tags left
+ //_id, key and tag should be elements in before doc
+ //if master table needs to be removed too
+ if len(elems) == 3 {
+ keyid, ok := keydata.Lookup("_id").ObjectIDOK()
+ if !ok {
+ return pkgerrors.Errorf("Error finding objectID for key %s", key)
+ }
+ err = m.deleteObjectByID(coll, keyid)
+ if err != nil {
+ return pkgerrors.Errorf("Error deleting master table from database: %s", err.Error())
+ }
+ }
+
+ return nil
+}
+
+// ReadAll is used to get all documents in db of a particular tag
+func (m *MongoStore) ReadAll(coll, tag string) (map[string][]byte, error) {
+ if !m.validateParams(coll, tag) {
+ return nil, pkgerrors.New("Missing collection or tag name")
+ }
+
+ c := getCollection(coll, m)
+ ctx := context.Background()
+
+ //Get all master tables in this collection
+ filter := bson.D{
+ {"key", bson.D{
+ {"$exists", true},
+ }},
+ }
+ cursor, err := c.Find(ctx, filter)
+ if err != nil {
+ return nil, pkgerrors.Errorf("Error reading from database: %s", err.Error())
+ }
+ defer cursorClose(ctx, cursor)
+
+ //Iterate over all the master tables
+ result := make(map[string][]byte)
+ for cursorNext(ctx, cursor) {
+ d := cursor.Current
+
+ //Read key of each master table
+ key, ok := d.Lookup("key").DocumentOK()
+ if !ok {
+ //Throw error if key is not found
+ pkgerrors.New("Unable to read key from mastertable")
+ }
+
+ //Get objectID of tag document
+ tid, ok := d.Lookup(tag).ObjectIDOK()
+ if !ok {
+ log.Printf("Did not find tag: %s", tag)
+ continue
+ }
+
+ //Find tag document and unmarshal it into []byte
+ tagData, err := decodeBytes(c.FindOne(ctx, bson.D{{"_id", tid}}))
+ if err != nil {
+ log.Printf("Unable to decode tag data %s", err.Error())
+ continue
+ }
+ result[key.String()] = tagData.Lookup(tag).Value
+ }
+
+ if len(result) == 0 {
+ return result, pkgerrors.Errorf("Did not find any objects with tag: %s", tag)
+ }
+
+ return result, nil
+}
diff --git a/src/orchestrator/internal/db/mongo_test.go b/src/orchestrator/internal/db/mongo_test.go
new file mode 100644
index 00000000..171c908f
--- /dev/null
+++ b/src/orchestrator/internal/db/mongo_test.go
@@ -0,0 +1,597 @@
+/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package db
+
+import (
+ "bytes"
+ "context"
+ "reflect"
+ "strings"
+ "testing"
+
+ pkgerrors "github.com/pkg/errors"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+)
+
+//Implements the functions used currently in mongo.go
+type mockCollection struct {
+ Err error
+ mCursor *mongo.Cursor
+ mCursorCount int
+}
+
+func (c *mockCollection) InsertOne(ctx context.Context, document interface{},
+ opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error) {
+
+ if c.Err != nil {
+ return nil, c.Err
+ }
+
+ return &mongo.InsertOneResult{InsertedID: "_id1234"}, nil
+}
+
+func (c *mockCollection) FindOne(ctx context.Context, filter interface{},
+ opts ...*options.FindOneOptions) *mongo.SingleResult {
+
+ return &mongo.SingleResult{}
+}
+
+func (c *mockCollection) FindOneAndUpdate(ctx context.Context, filter interface{},
+ update interface{}, opts ...*options.FindOneAndUpdateOptions) *mongo.SingleResult {
+
+ return &mongo.SingleResult{}
+}
+
+func (c *mockCollection) DeleteOne(ctx context.Context, filter interface{},
+ opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) {
+
+ return nil, c.Err
+}
+
+func (c *mockCollection) Find(ctx context.Context, filter interface{},
+ opts ...*options.FindOptions) (*mongo.Cursor, error) {
+
+ return c.mCursor, c.Err
+}
+
+func TestCreate(t *testing.T) {
+ testCases := []struct {
+ label string
+ input map[string]interface{}
+ mockColl *mockCollection
+ bson bson.Raw
+ expectedError string
+ }{
+ {
+ label: "Successfull creation of entry",
+ input: map[string]interface{}{
+ "coll": "collname",
+ "key": MockKey{Key: "keyvalue"},
+ "tag": "tagName",
+ "data": "Data In String Format",
+ },
+ bson: bson.Raw{'\x08', '\x00', '\x00', '\x00', '\x0A', 'x', '\x00', '\x00'},
+ mockColl: &mockCollection{},
+ },
+ {
+ label: "UnSuccessfull creation of entry",
+ input: map[string]interface{}{
+ "coll": "collname",
+ "key": MockKey{Key: "keyvalue"},
+ "tag": "tagName",
+ "data": "Data In String Format",
+ },
+ mockColl: &mockCollection{
+ Err: pkgerrors.New("DB Error"),
+ },
+ expectedError: "DB Error",
+ },
+ {
+ label: "Missing input fields",
+ input: map[string]interface{}{
+ "coll": "",
+ "key": MockKey{Key: ""},
+ "tag": "",
+ "data": "",
+ },
+ expectedError: "No Data to store",
+ mockColl: &mockCollection{},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ m, _ := NewMongoStore("name", &mongo.Database{})
+ // Override the getCollection function with our mocked version
+ getCollection = func(coll string, m *MongoStore) MongoCollection {
+ return testCase.mockColl
+ }
+
+ decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) {
+ return testCase.bson, testCase.mockColl.Err
+ }
+
+ err := m.Create(testCase.input["coll"].(string), testCase.input["key"].(Key),
+ testCase.input["tag"].(string), testCase.input["data"])
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Create method returned an un-expected (%s)", err)
+ }
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Create method returned an error (%s)", err)
+ }
+ }
+ })
+ }
+}
+
+func TestUpdate(t *testing.T) {
+ testCases := []struct {
+ label string
+ input map[string]interface{}
+ mockColl *mockCollection
+ bson bson.Raw
+ expectedError string
+ }{
+ {
+ label: "Successfull update of entry",
+ input: map[string]interface{}{
+ "coll": "collname",
+ "key": MockKey{Key: "keyvalue"},
+ "tag": "metadata",
+ "data": "Data In String Format",
+ },
+ // Binary form of
+ // {
+ // "_id" : ObjectId("5c115156777ff85654248ae1"),
+ // "key" : bson.D{{"name","testdef"},{"version","v1"}},
+ // "metadata" : ObjectId("5c115156c9755047e318bbfd")
+ // }
+ bson: bson.Raw{
+ '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79',
+ '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61',
+ '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74',
+ '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02',
+ '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00',
+ '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00',
+ '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74',
+ '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f',
+ '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f',
+ '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77',
+ '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00',
+ },
+ mockColl: &mockCollection{},
+ },
+ {
+ label: "Entry does not exist",
+ input: map[string]interface{}{
+ "coll": "collname",
+ "key": MockKey{Key: "keyvalue"},
+ "tag": "tagName",
+ "data": "Data In String Format",
+ },
+ mockColl: &mockCollection{
+ Err: pkgerrors.New("DB Error"),
+ },
+ expectedError: "DB Error",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ m, _ := NewMongoStore("name", &mongo.Database{})
+ // Override the getCollection function with our mocked version
+ getCollection = func(coll string, m *MongoStore) MongoCollection {
+ return testCase.mockColl
+ }
+
+ decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) {
+ return testCase.bson, testCase.mockColl.Err
+ }
+
+ err := m.Update(testCase.input["coll"].(string), testCase.input["key"].(Key),
+ testCase.input["tag"].(string), testCase.input["data"])
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Create method returned an un-expected (%s)", err)
+ }
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Create method returned an error (%s)", err)
+ }
+ }
+ })
+ }
+}
+
+func TestRead(t *testing.T) {
+ testCases := []struct {
+ label string
+ input map[string]interface{}
+ mockColl *mockCollection
+ bson bson.Raw
+ expectedError string
+ expected []byte
+ }{
+ {
+ label: "Successfull Read of entry",
+ input: map[string]interface{}{
+ "coll": "collname",
+ "key": MockKey{Key: "keyvalue"},
+ "tag": "metadata",
+ },
+ // Binary form of
+ // {
+ // "_id" : ObjectId("5c115156777ff85654248ae1"),
+ // "key" : bson.D{{"name","testdef"},{"version","v1"}},
+ // "metadata" : ObjectId("5c115156c9755047e318bbfd")
+ // }
+ bson: bson.Raw{
+ '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79',
+ '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61',
+ '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74',
+ '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02',
+ '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00',
+ '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00',
+ '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74',
+ '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f',
+ '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f',
+ '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77',
+ '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00',
+ },
+ mockColl: &mockCollection{},
+ // This is not the document because we are mocking decodeBytes
+ expected: []byte{92, 17, 81, 86, 119, 127, 248, 86, 84, 36, 138, 225},
+ },
+ {
+ label: "UnSuccessfull Read of entry: object not found",
+ input: map[string]interface{}{
+ "coll": "collname",
+ "key": MockKey{Key: "keyvalue"},
+ "tag": "badtag",
+ },
+ // Binary form of
+ // {
+ // "_id" : ObjectId("5c115156777ff85654248ae1"),
+ // "key" : bson.D{{"name","testdef"},{"version","v1"}},
+ // "metadata" : ObjectId("5c115156c9755047e318bbfd")
+ // }
+ bson: bson.Raw{
+ '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79',
+ '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61',
+ '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74',
+ '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02',
+ '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00',
+ '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00',
+ '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74',
+ '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f',
+ '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f',
+ '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77',
+ '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00',
+ },
+ mockColl: &mockCollection{},
+ expectedError: "Error finding objectID",
+ },
+ {
+ label: "UnSuccessfull Read of entry",
+ input: map[string]interface{}{
+ "coll": "collname",
+ "key": MockKey{Key: "keyvalue"},
+ "tag": "tagName",
+ },
+ mockColl: &mockCollection{
+ Err: pkgerrors.New("DB Error"),
+ },
+ expectedError: "DB Error",
+ },
+ {
+ label: "Missing input fields",
+ input: map[string]interface{}{
+ "coll": "",
+ "key": MockKey{Key: ""},
+ "tag": "",
+ },
+ expectedError: "Mandatory fields are missing",
+ mockColl: &mockCollection{},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ m, _ := NewMongoStore("name", &mongo.Database{})
+ // Override the getCollection function with our mocked version
+ getCollection = func(coll string, m *MongoStore) MongoCollection {
+ return testCase.mockColl
+ }
+
+ decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) {
+ return testCase.bson, testCase.mockColl.Err
+ }
+ got, err := m.Read(testCase.input["coll"].(string), testCase.input["key"].(Key),
+ testCase.input["tag"].(string))
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Read method returned an un-expected (%s)", err)
+ }
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Read method returned an error (%s)", err)
+ }
+ } else {
+ if bytes.Compare(got, testCase.expected) != 0 {
+ t.Fatalf("Read returned unexpected data: %v, expected: %v",
+ string(got), testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestDelete(t *testing.T) {
+ testCases := []struct {
+ label string
+ input map[string]interface{}
+ mockColl *mockCollection
+ bson bson.Raw
+ expectedError string
+ }{
+ {
+ label: "Successfull Delete of entry",
+ input: map[string]interface{}{
+ "coll": "collname",
+ "key": MockKey{Key: "keyvalue"},
+ "tag": "metadata",
+ },
+ // Binary form of
+ // {
+ // "_id" : ObjectId("5c115156777ff85654248ae1"),
+ // "key" : bson.D{{"name","testdef"},{"version","v1"}},
+ // "metadata" : ObjectId("5c115156c9755047e318bbfd")
+ // }
+ bson: bson.Raw{
+ '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79',
+ '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61',
+ '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74',
+ '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02',
+ '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00',
+ '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00',
+ '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74',
+ '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f',
+ '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f',
+ '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77',
+ '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00',
+ },
+ mockColl: &mockCollection{},
+ },
+ {
+ label: "UnSuccessfull Delete of entry",
+ input: map[string]interface{}{
+ "coll": "collname",
+ "key": MockKey{Key: "keyvalue"},
+ "tag": "tagName",
+ },
+ mockColl: &mockCollection{
+ Err: pkgerrors.New("DB Error"),
+ },
+ expectedError: "DB Error",
+ },
+ {
+ label: "UnSuccessfull Delete, key not found",
+ input: map[string]interface{}{
+ "coll": "collname",
+ "key": MockKey{Key: "keyvalue"},
+ "tag": "tagName",
+ },
+ // Binary form of
+ // {
+ // "_id" : ObjectId("5c115156777ff85654248ae1"),
+ // "key" : bson.D{{"name","testdef"},{"version","v1"}},
+ // "metadata" : ObjectId("5c115156c9755047e318bbfd")
+ // }
+ bson: bson.Raw{
+ '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79',
+ '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61',
+ '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74',
+ '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02',
+ '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00',
+ '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00',
+ '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74',
+ '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f',
+ '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f',
+ '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77',
+ '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00',
+ },
+ mockColl: &mockCollection{},
+ expectedError: "Error finding objectID",
+ },
+ {
+ label: "Missing input fields",
+ input: map[string]interface{}{
+ "coll": "",
+ "key": MockKey{Key: ""},
+ "tag": "",
+ },
+ expectedError: "Mandatory fields are missing",
+ mockColl: &mockCollection{},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ m, _ := NewMongoStore("name", &mongo.Database{})
+ // Override the getCollection function with our mocked version
+ getCollection = func(coll string, m *MongoStore) MongoCollection {
+ return testCase.mockColl
+ }
+
+ decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) {
+ return testCase.bson, testCase.mockColl.Err
+ }
+ err := m.Delete(testCase.input["coll"].(string), testCase.input["key"].(Key),
+ testCase.input["tag"].(string))
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Delete method returned an un-expected (%s)", err)
+ }
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Delete method returned an error (%s)", err)
+ }
+ }
+ })
+ }
+}
+
+func TestReadAll(t *testing.T) {
+ testCases := []struct {
+ label string
+ input map[string]interface{}
+ mockColl *mockCollection
+ bson bson.Raw
+ expectedError string
+ expected map[string][]byte
+ }{
+ {
+ label: "Successfully Read all entries",
+ input: map[string]interface{}{
+ "coll": "collname",
+ "tag": "metadata",
+ },
+ mockColl: &mockCollection{
+ mCursor: &mongo.Cursor{
+ // Binary form of
+ // {
+ // "_id" : ObjectId("5c115156777ff85654248ae1"),
+ // "key" : bson.D{{"name","testdef"},{"version","v1"}},
+ // "metadata" : ObjectId("5c115156c9755047e318bbfd")
+ // }
+
+ Current: bson.Raw{
+ '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79',
+ '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61',
+ '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74',
+ '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02',
+ '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00',
+ '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00',
+ '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74',
+ '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f',
+ '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f',
+ '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77',
+ '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00',
+ },
+ },
+ mCursorCount: 1,
+ },
+ expected: map[string][]byte{
+ `{"name": "testdef","version": "v1"}`: []byte{
+ 92, 17, 81, 86, 119, 127, 248, 86, 84, 36, 138, 225},
+ },
+ },
+ {
+ label: "UnSuccessfully Read of all entries",
+ input: map[string]interface{}{
+ "coll": "collname",
+ "tag": "tagName",
+ },
+ mockColl: &mockCollection{
+ Err: pkgerrors.New("DB Error"),
+ },
+ expectedError: "DB Error",
+ },
+ {
+ label: "UnSuccessfull Readall, tag not found",
+ input: map[string]interface{}{
+ "coll": "collname",
+ "tag": "tagName",
+ },
+ mockColl: &mockCollection{
+ mCursor: &mongo.Cursor{
+ // Binary form of
+ // {
+ // "_id" : ObjectId("5c115156777ff85654248ae1"),
+ // "key" : bson.D{{"name","testdef"},{"version","v1"}},
+ // "metadata" : ObjectId("5c115156c9755047e318bbfd")
+ // }
+ Current: bson.Raw{
+ '\x58', '\x00', '\x00', '\x00', '\x03', '\x6b', '\x65', '\x79',
+ '\x00', '\x27', '\x00', '\x00', '\x00', '\x02', '\x6e', '\x61',
+ '\x6d', '\x65', '\x00', '\x08', '\x00', '\x00', '\x00', '\x74',
+ '\x65', '\x73', '\x74', '\x64', '\x65', '\x66', '\x00', '\x02',
+ '\x76', '\x65', '\x72', '\x73', '\x69', '\x6f', '\x6e', '\x00',
+ '\x03', '\x00', '\x00', '\x00', '\x76', '\x31', '\x00', '\x00',
+ '\x07', '\x6d', '\x65', '\x74', '\x61', '\x64', '\x61', '\x74',
+ '\x61', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f',
+ '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x07', '\x5f',
+ '\x69', '\x64', '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77',
+ '\x7f', '\xf8', '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x00',
+ },
+ },
+ mCursorCount: 1,
+ },
+ expectedError: "Did not find any objects with tag",
+ },
+ {
+ label: "Missing input fields",
+ input: map[string]interface{}{
+ "coll": "",
+ "tag": "",
+ },
+ expectedError: "Missing collection or tag name",
+ mockColl: &mockCollection{},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ m, _ := NewMongoStore("name", &mongo.Database{})
+ // Override the getCollection function with our mocked version
+ getCollection = func(coll string, m *MongoStore) MongoCollection {
+ return testCase.mockColl
+ }
+
+ decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) {
+ return testCase.mockColl.mCursor.Current, testCase.mockColl.Err
+ }
+
+ cursorNext = func(ctx context.Context, cursor *mongo.Cursor) bool {
+ if testCase.mockColl.mCursorCount > 0 {
+ testCase.mockColl.mCursorCount -= 1
+ return true
+ }
+ return false
+ }
+
+ cursorClose = func(ctx context.Context, cursor *mongo.Cursor) error {
+ return nil
+ }
+
+ got, err := m.ReadAll(testCase.input["coll"].(string), testCase.input["tag"].(string))
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Readall method returned an un-expected (%s)", err)
+ }
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Readall method returned an error (%s)", err)
+ }
+ } else {
+ if reflect.DeepEqual(got, testCase.expected) == false {
+ t.Fatalf("Readall returned unexpected data: %v, expected: %v",
+ got, testCase.expected)
+ }
+ }
+ })
+ }
+}
diff --git a/src/orchestrator/internal/db/store.go b/src/orchestrator/internal/db/store.go
new file mode 100644
index 00000000..ed394205
--- /dev/null
+++ b/src/orchestrator/internal/db/store.go
@@ -0,0 +1,106 @@
+/*
+Copyright 2018 Intel Corporation.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package db
+
+import (
+ "encoding/json"
+ "reflect"
+
+ "github.com/onap/multicloud-k8s/src/orchestrator/internal/config"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+// DBconn interface used to talk a concrete Database connection
+var DBconn Store
+
+// Key is an interface that will be implemented by anypackage
+// that wants to use the Store interface. This allows various
+// db backends and key types.
+type Key interface {
+ String() string
+}
+
+// Store is an interface for accessing the database
+type Store interface {
+ // Returns nil if db health is good
+ HealthCheck() error
+
+ // Unmarshal implements any unmarshaling needed for the database
+ Unmarshal(inp []byte, out interface{}) error
+
+ // Creates a new master document with key and links data with tag and
+ // creates a pointer(row) to the newly added data in the master table
+ Create(table string, key Key, tag string, data interface{}) error
+
+ // Reads data for a particular key with specific tag.
+ Read(table string, key Key, tag string) ([]byte, error)
+
+ // Update data for particular key with specific tag
+ Update(table string, key Key, tag string, data interface{}) error
+
+ // Deletes a specific tag data for key.
+ // TODO: If tag is empty, it will delete all tags under key.
+ Delete(table string, key Key, tag string) error
+
+ // Reads all master tables and data from the specified tag in table
+ ReadAll(table string, tag string) (map[string][]byte, error)
+}
+
+// CreateDBClient creates the DB client
+func createDBClient(dbType string) error {
+ var err error
+ switch dbType {
+ case "mongo":
+ // create a mongodb database with orchestrator as the name
+ DBconn, err = NewMongoStore("orchestrator", nil)
+ default:
+ return pkgerrors.New(dbType + "DB not supported")
+ }
+ return err
+}
+
+// Serialize converts given data into a JSON string
+func Serialize(v interface{}) (string, error) {
+ out, err := json.Marshal(v)
+ if err != nil {
+ return "", pkgerrors.Wrap(err, "Error serializing "+reflect.TypeOf(v).String())
+ }
+ return string(out), nil
+}
+
+// DeSerialize converts string to a json object specified by type
+func DeSerialize(str string, v interface{}) error {
+ err := json.Unmarshal([]byte(str), &v)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Error deSerializing "+str)
+ }
+ return nil
+}
+
+// InitializeDatabaseConnection sets up the connection to the
+// configured database to allow the application to talk to it.
+func InitializeDatabaseConnection() error {
+ err := createDBClient(config.GetConfiguration().DatabaseType)
+ if err != nil {
+ return pkgerrors.Cause(err)
+ }
+
+ err = DBconn.HealthCheck()
+ if err != nil {
+ return pkgerrors.Cause(err)
+ }
+
+ return nil
+}
diff --git a/src/orchestrator/internal/db/store_test.go b/src/orchestrator/internal/db/store_test.go
new file mode 100644
index 00000000..42a41787
--- /dev/null
+++ b/src/orchestrator/internal/db/store_test.go
@@ -0,0 +1,121 @@
+/*
+Copyright 2018 Intel Corporation.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package db
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestCreateDBClient(t *testing.T) {
+ t.Run("Successfully create DB client", func(t *testing.T) {
+ expected := &MongoStore{}
+
+ err := createDBClient("mongo")
+ if err != nil {
+ t.Fatalf("CreateDBClient returned an error (%s)", err)
+ }
+ if reflect.TypeOf(DBconn) != reflect.TypeOf(expected) {
+ t.Fatalf("CreateDBClient set DBconn as:\n result=%T\n expected=%T", DBconn, expected)
+ }
+ })
+ t.Run("Fail to create client for unsupported DB", func(t *testing.T) {
+ err := createDBClient("fakeDB")
+ if err == nil {
+ t.Fatal("CreateDBClient didn't return an error")
+ }
+ if !strings.Contains(string(err.Error()), "DB not supported") {
+ t.Fatalf("CreateDBClient method returned an error (%s)", err)
+ }
+ })
+}
+
+func TestSerialize(t *testing.T) {
+
+ inp := map[string]interface{}{
+ "UUID": "123e4567-e89b-12d3-a456-426655440000",
+ "Data": "sdaijsdiodalkfjsdlagf",
+ "Number": 23,
+ "Float": 34.4,
+ "Map": map[string]interface{}{
+ "m1": "m1",
+ "m2": 2,
+ "m3": 3.0,
+ },
+ }
+
+ got, err := Serialize(inp)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ expected := "{\"Data\":\"sdaijsdiodalkfjsdlagf\"," +
+ "\"Float\":34.4,\"Map\":{\"m1\":\"m1\",\"m2\":2,\"m3\":3}," +
+ "\"Number\":23,\"UUID\":\"123e4567-e89b-12d3-a456-426655440000\"}"
+
+ if expected != got {
+ t.Errorf("Serialize returned unexpected string: %s;"+
+ " expected %sv", got, expected)
+ }
+}
+
+func TestDeSerialize(t *testing.T) {
+ testCases := []struct {
+ label string
+ input string
+ expected map[string]interface{}
+ errMsg string
+ }{
+ {
+ label: "Sucessful deserialize entry",
+ input: "{\"Data\":\"sdaijsdiodalkfjsdlagf\"," +
+ "\"Float\":34.4,\"Map\":{\"m1\":\"m1\",\"m3\":3}," +
+ "\"UUID\":\"123e4567-e89b-12d3-a456-426655440000\"}",
+ expected: map[string]interface{}{
+ "UUID": "123e4567-e89b-12d3-a456-426655440000",
+ "Data": "sdaijsdiodalkfjsdlagf",
+ "Float": 34.4,
+ "Map": map[string]interface{}{
+ "m1": "m1",
+ "m3": 3.0,
+ },
+ },
+ },
+ {
+ label: "Fail to deserialize invalid entry",
+ input: "{invalid}",
+ errMsg: "Error deSerializing {invalid}: invalid character 'i' looking for beginning of object key string",
+ },
+ }
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ got := make(map[string]interface{})
+ err := DeSerialize(testCase.input, &got)
+ if err != nil {
+ if testCase.errMsg == "" {
+ t.Fatalf("DeSerialize method return an un-expected (%s)", err)
+ }
+ if !strings.Contains(string(err.Error()), testCase.errMsg) {
+ t.Fatalf("DeSerialize method returned an error (%s)", err)
+ }
+ } else {
+ if !reflect.DeepEqual(testCase.expected, got) {
+ t.Errorf("Serialize returned unexpected : %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
diff --git a/src/orchestrator/internal/project/project.go b/src/orchestrator/internal/project/project.go
new file mode 100644
index 00000000..f0c50065
--- /dev/null
+++ b/src/orchestrator/internal/project/project.go
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2019 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package project
+
+import (
+ "encoding/json"
+
+ "github.com/onap/multicloud-k8s/src/orchestrator/internal/db"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+// Project contains the parameters needed for Projects
+// It implements the interface for managing the Projects
+type Project struct {
+ ProjectName string `json:"project-name"`
+ Description string `json:"description"`
+}
+
+// ProjectKey is the key structure that is used in the database
+type ProjectKey struct {
+ ProjectName string `json:"rb-name"`
+}
+
+// We will use json marshalling to convert to string to
+// preserve the underlying structure.
+func (pk ProjectKey) String() string {
+ out, err := json.Marshal(pk)
+ if err != nil {
+ return ""
+ }
+
+ return string(out)
+}
+
+// ProjectManager is an interface exposes the Project functionality
+type ProjectManager interface {
+ Create(pr Project) (Project, error)
+ Get(name string) (Project, error)
+ Delete(name string) error
+}
+
+// ProjectClient implements the ProjectManager
+// It will also be used to maintain some localized state
+type ProjectClient struct {
+ storeName string
+ tagMeta, tagContent string
+}
+
+// NewProjectClient returns an instance of the ProjectClient
+// which implements the ProjectManager
+func NewProjectClient() *ProjectClient {
+ return &ProjectClient{
+ tagMeta: "projectmetadata",
+ }
+}
+
+// Create a new collection based on the project
+func (v *ProjectClient) Create(p Project) (Project, error) {
+
+ //Construct the composite key to select the entry
+ key := ProjectKey{
+ ProjectName: p.ProjectName,
+ }
+
+ //Check if this Project already exists
+ _, err := v.Get(p.ProjectName)
+ if err == nil {
+ return Project{}, pkgerrors.New("Project already exists")
+ }
+
+ err = db.DBconn.Create(p.ProjectName, key, v.tagMeta, p)
+ if err != nil {
+ return Project{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+
+ return p, nil
+}
+
+// Get returns the Project for corresponding name
+func (v *ProjectClient) Get(name string) (Project, error) {
+
+ //Construct the composite key to select the entry
+ key := ProjectKey{
+ ProjectName: name,
+ }
+ value, err := db.DBconn.Read(name, key, v.tagMeta)
+ if err != nil {
+ return Project{}, pkgerrors.Wrap(err, "Get Project")
+ }
+
+ //value is a byte array
+ if value != nil {
+ proj := Project{}
+ err = db.DBconn.Unmarshal(value, &proj)
+ if err != nil {
+ return Project{}, pkgerrors.Wrap(err, "Unmarshaling Value")
+ }
+ return proj, nil
+ }
+
+ return Project{}, pkgerrors.New("Error getting Project")
+}
+
+// Delete the Project from database
+func (v *ProjectClient) Delete(name string) error {
+
+ //Construct the composite key to select the entry
+ key := ProjectKey{
+ ProjectName: name,
+ }
+ err := db.DBconn.Delete(name, key, v.tagMeta)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete Project Entry;")
+ }
+
+ //TODO: Delete the collection when the project is deleted
+ return nil
+}
diff --git a/src/orchestrator/internal/project/project_test.go b/src/orchestrator/internal/project/project_test.go
new file mode 100644
index 00000000..cc691e33
--- /dev/null
+++ b/src/orchestrator/internal/project/project_test.go
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package project
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+
+ "github.com/onap/multicloud-k8s/src/orchestrator/internal/db"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+func TestCreateProject(t *testing.T) {
+ testCases := []struct {
+ label string
+ inp Project
+ expectedError string
+ mockdb *db.MockDB
+ expected Project
+ }{
+ {
+ label: "Create Project",
+ inp: Project{
+ ProjectName: "testProject",
+ Description: "A sample Project used for unit testing",
+ },
+ expected: Project{
+ ProjectName: "testProject",
+ Description: "A sample Project used for unit testing",
+ },
+ expectedError: "",
+ mockdb: &db.MockDB{},
+ },
+ {
+ label: "Failed Create Project",
+ expectedError: "Error Creating Project",
+ mockdb: &db.MockDB{
+ Err: pkgerrors.New("Error Creating Project"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ impl := NewProjectClient()
+ got, err := impl.Create(testCase.inp)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Create returned an unexpected error %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Create returned an unexpected error %s", err)
+ }
+ } else {
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("Create returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestGetProject(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ name string
+ expectedError string
+ mockdb *db.MockDB
+ inp string
+ expected Project
+ }{
+ {
+ label: "Get Project",
+ name: "testProject",
+ expected: Project{
+ ProjectName: "testProject",
+ Description: "Test project for unit testing",
+ },
+ expectedError: "",
+ mockdb: &db.MockDB{
+ Items: map[string]map[string][]byte{
+ ProjectKey{ProjectName: "testProject"}.String(): {
+ "projectmetadata": []byte(
+ "{\"project-name\":\"testProject\"," +
+ "\"description\":\"Test project for unit testing\"}"),
+ },
+ },
+ },
+ },
+ {
+ label: "Get Error",
+ expectedError: "DB Error",
+ mockdb: &db.MockDB{
+ Err: pkgerrors.New("DB Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ impl := NewProjectClient()
+ got, err := impl.Get(testCase.name)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Get returned an unexpected error: %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Get returned an unexpected error: %s", err)
+ }
+ } else {
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("Get returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestDeleteProject(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ name string
+ expectedError string
+ mockdb *db.MockDB
+ }{
+ {
+ label: "Delete Project",
+ name: "testProject",
+ mockdb: &db.MockDB{},
+ },
+ {
+ label: "Delete Error",
+ expectedError: "DB Error",
+ mockdb: &db.MockDB{
+ Err: pkgerrors.New("DB Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ impl := NewProjectClient()
+ err := impl.Delete(testCase.name)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Delete returned an unexpected error %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Delete returned an unexpected error %s", err)
+ }
+ }
+ })
+ }
+}
diff --git a/src/orchestrator/tests/certs/auth_test_certificate.pem b/src/orchestrator/tests/certs/auth_test_certificate.pem
new file mode 100644
index 00000000..42e77491
--- /dev/null
+++ b/src/orchestrator/tests/certs/auth_test_certificate.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAKAHJi8eUs73MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTgwNTE1MDQ0MDQwWhcNMTkwNTE1MDQ0MDQwWjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA5PHDk+RRFh5o3Xe2nZuLn0Vo+5BjnHp/ul2NNYSG00Slc8F86gW4xcNA
+wm6xC8tYCSangV2lFG3E8H2L7SCEVaM5VDV2GCOpOoMihc+2Qenk/YbHwuYenwjo
+OgTK4aCItqjcAJ2DB1KC7AxARxHBDu9Kif+M/pc49so+G9ObQuS8k2vmTTaRYkMK
+ZvbTJcWsc0vbNnPhxcG5PVj4unlaREM+yQDm18/glUkkBNnYKMHABRvPnBrDktTT
+BQWsqkbQTw7ZuLOrl9rCzVTstZX9wEXarrstIdQJj3KZjbFOp2opND8bjNIjcdVt
+iRFnP1nHQYr7EgRqcx/YMJZ+gmCy3wIDAQABo1AwTjAdBgNVHQ4EFgQU9qPNwwIu
+kZh41sJqFtnMC2blSYMwHwYDVR0jBBgwFoAU9qPNwwIukZh41sJqFtnMC2blSYMw
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA4+daLY1wE10IMPaOKurG
+QBvfYeO/rgNXGeg0TisTIKAfx/We9Hmwo/37Bd2Nk5gxfy/DIJ4lMbrzXjgWITlm
+XOrS5QfluwvaEcREtHFtPFa3NZqn2VzKNDFTR+rJj7I5o600NKdcPrGeQ1i/vny2
+K0g68ogw2jfufcuePvZTYGst8RclomPr7ZXxI24kIjcE1MbiViy68sQueTXBEr5s
+Th6RsvPfVnLxjR/m/V6VJl31nn4T6hbmKzXCHo/X7aC3I8Isui4bQGKgfAxyBkhE
+0T7tP+GgymiEKQ6qJ/1c4HFFSuFRUQjLnK7uJu9jM/HMKoLCPayx6birHZRIMF94
+pg==
+-----END CERTIFICATE-----
diff --git a/src/orchestrator/tests/certs/auth_test_key.pem b/src/orchestrator/tests/certs/auth_test_key.pem
new file mode 100644
index 00000000..5f01f572
--- /dev/null
+++ b/src/orchestrator/tests/certs/auth_test_key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDk8cOT5FEWHmjd
+d7adm4ufRWj7kGOcen+6XY01hIbTRKVzwXzqBbjFw0DCbrELy1gJJqeBXaUUbcTw
+fYvtIIRVozlUNXYYI6k6gyKFz7ZB6eT9hsfC5h6fCOg6BMrhoIi2qNwAnYMHUoLs
+DEBHEcEO70qJ/4z+lzj2yj4b05tC5LyTa+ZNNpFiQwpm9tMlxaxzS9s2c+HFwbk9
+WPi6eVpEQz7JAObXz+CVSSQE2dgowcAFG8+cGsOS1NMFBayqRtBPDtm4s6uX2sLN
+VOy1lf3ARdquuy0h1AmPcpmNsU6naik0PxuM0iNx1W2JEWc/WcdBivsSBGpzH9gw
+ln6CYLLfAgMBAAECggEAYB3H2D0QddLKf8AUoNJ+qZ1AV+zkhPtAyIMiF4fN+sBl
+HdXrlWxViGFSvM4v8h2qlhzuUfd4qLz042ox5pmyNSnTlbDkJXpDP9dyFO+BOubx
+Ribhksd9r5LTvBfq/RKikt0NkAyQx/AyGtuB2NRxUs3PY2QwU2o1dhauQIx0MH5/
+6D8PgQf6+5njKQaKa4e8Kp4kB+KjnALvt6JgYuNJUHWap+nnDbuuVy5dl1bKkAZ+
+qa7CITKWO4kE2EqaCb2asFc2w3538+w72UJZtwQCmOaxtKpRSl9fQXu54N8jIGoZ
+1FvEj5x3X6QkglE+iVJYaX3RmiJ3uzZ2LICDr89vEQKBgQD7fquIw4p1idSxz3Cm
+5o3Y5kD0CKm61ZaRJWKd+tNlSsxubmV9HROYW6vj2xEPSDvyp1na00pDXxMJQLLc
+O5Awd1SaU+d45jnoi70fsEY8X0WH1rDTYfnU+zQBmpbGqX5qTIfpy4yoADiUD1CQ
+EBdaSBWiKPx2jFSct58TwDP9YwKBgQDpC64TScZYz7uQq4gAbDso/7TjNwgt/Bw8
+JgLSdx1UdUclh81smTujsouyCFwJSvRjKik8e/Qt0f5patukFbFRINxUGUDhOKbA
+7zqeNQbeYaP7Rvw+3z01CU2BTBmB/EWa2xWDam8B9xQvjiHSOrubqkt3sIQJb045
+hzuigdV7VQKBgQD7Gnd0nyCwyMSIIMGuswYv6X4y6i9lr3qdQ4GakOTe/vbsz+cf
+K5f0CJuwbnszEgFg/zzVIx/D8rqUA3hSMlp+ObdMO7gi22Q4TsWvTRZjkxBeV7rH
+48xJneNIMqyWgIcK5YzSn3y6BTZ4hm3+2UInz09iUJ/6UZTtwNzhIIgIVwKBgQCT
+LxRHDE4gIzrT+PHRSonmr/DfnA8nc9WlS2B26lH02IkRs/5Su0iGb6p4y3zNRbCp
+vKQElki2c60ZiSqlLCosEfP1jWmDlRMEQVMlPlpTMxmtBr0jPDzc9T4lDhoCFYEk
+d3/T2vG3LQRrsHm92+hHPTuioTIS/2BJRxar4RIibQKBgQC8zoayoQ7zfEYxy3Ax
+OSao8g85hj0EAJk/VKQP2POgz6KoPay3JE9D7P7OvkebTyv/pijAuTPby4XipCNI
+K0JbFC2Kn7RW/ZV23UdnoO9crh2omOh+/52prStWXKoc+/pJe70Af+4rU7FUiI7F
+y1mIE9krIoVis6iYsyFEmkP7iw==
+-----END PRIVATE KEY-----
diff --git a/src/orchestrator/tests/configs/mock_config.json b/src/orchestrator/tests/configs/mock_config.json
new file mode 100644
index 00000000..47a6b627
--- /dev/null
+++ b/src/orchestrator/tests/configs/mock_config.json
@@ -0,0 +1,5 @@
+{
+ "database-type": "mock_db_test",
+ "database-ip": "127.0.0.1",
+ "plugin-dir": "."
+} \ No newline at end of file