diff options
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 |