diff options
-rwxr-xr-x | kud/hosting_providers/baremetal/aio.sh | 6 | ||||
-rw-r--r-- | kud/hosting_providers/vagrant/Vagrantfile | 2 | ||||
-rwxr-xr-x | kud/hosting_providers/vagrant/installer.sh | 43 | ||||
-rwxr-xr-x | kud/tests/_functions.sh | 23 | ||||
-rw-r--r-- | src/k8splugin/go.sum | 2 | ||||
-rw-r--r-- | src/k8splugin/internal/app/client.go | 181 | ||||
-rw-r--r-- | src/k8splugin/internal/app/client_test.go | 2 | ||||
-rw-r--r-- | src/k8splugin/internal/plugin/helpers.go | 92 | ||||
-rw-r--r-- | src/k8splugin/mock_files/mock_plugins/mockplugin.go | 31 | ||||
-rw-r--r-- | src/k8splugin/plugins/generic/plugin.go | 68 | ||||
-rw-r--r-- | src/k8splugin/plugins/namespace/plugin.go | 60 | ||||
-rw-r--r-- | src/k8splugin/plugins/namespace/plugin_test.go | 106 | ||||
-rw-r--r-- | src/k8splugin/plugins/network/plugin.go | 39 | ||||
-rw-r--r-- | src/k8splugin/plugins/network/plugin_test.go | 33 | ||||
-rw-r--r-- | src/k8splugin/plugins/service/plugin.go | 58 | ||||
-rw-r--r-- | src/k8splugin/plugins/service/plugin_test.go | 141 |
16 files changed, 515 insertions, 372 deletions
diff --git a/kud/hosting_providers/baremetal/aio.sh b/kud/hosting_providers/baremetal/aio.sh index 416a1fef..c9903cd3 100755 --- a/kud/hosting_providers/baremetal/aio.sh +++ b/kud/hosting_providers/baremetal/aio.sh @@ -15,9 +15,13 @@ set -o pipefail aio_dir=$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd) cd ${aio_dir}/../vagrant +# For aio inventory by default get ovn central ip from local host default interface. +# This variable used only in this file, but env variable defined to enable user to override it prior calling aio.sh. +OVN_CENTRAL_IP_ADDRESS=${OVN_CENTRAL_IP_ADDRESS:-$(hostname -I | cut -d ' ' -f 1)} + cat <<EOL > inventory/hosts.ini [all] -localhost +localhost ansible_ssh_host=${OVN_CENTRAL_IP_ADDRESS} ansible_ssh_port=22 [kube-master] localhost diff --git a/kud/hosting_providers/vagrant/Vagrantfile b/kud/hosting_providers/vagrant/Vagrantfile index d068b84a..2d1b5ab4 100644 --- a/kud/hosting_providers/vagrant/Vagrantfile +++ b/kud/hosting_providers/vagrant/Vagrantfile @@ -120,7 +120,7 @@ Vagrant.configure("2") do |config| installer.vm.network :private_network, :ip => "10.10.10.2", :type => :static installer.vm.synced_folder '../../../', '/home/vagrant/multicloud-k8s/', type: sync_type installer.vm.provision 'shell', privileged: false do |sh| - sh.env = {'KUD_PLUGIN_ENABLED': 'false'} + sh.env = {'KUD_PLUGIN_ENABLED': 'false', 'OVN_CENTRAL_INTERFACE': 'eth1'} sh.inline = <<-SHELL cp /vagrant/insecure_keys/key.pub /home/vagrant/.ssh/id_rsa.pub cp /vagrant/insecure_keys/key /home/vagrant/.ssh/id_rsa diff --git a/kud/hosting_providers/vagrant/installer.sh b/kud/hosting_providers/vagrant/installer.sh index c7715b59..ca14bad7 100755 --- a/kud/hosting_providers/vagrant/installer.sh +++ b/kud/hosting_providers/vagrant/installer.sh @@ -9,8 +9,13 @@ ############################################################################## set -o errexit +set -o nounset set -o pipefail +INSTALLER_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd) + +source ${INSTALLER_DIR}/../../tests/_functions.sh + # _install_go() - Install GoLang package function _install_go { version=$(grep "go_version" ${kud_playbooks}/kud-vars.yml | awk -F "'" '{print $2}') @@ -63,15 +68,15 @@ function _install_docker { sudo apt-get install -y docker-ce sudo mkdir -p /etc/systemd/system/docker.service.d - if [ $http_proxy ]; then + if [ ${http_proxy:-} ]; then echo "[Service]" | sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf echo "Environment=\"HTTP_PROXY=$http_proxy\"" | sudo tee --append /etc/systemd/system/docker.service.d/http-proxy.conf fi - if [ $https_proxy ]; then + if [ ${https_proxy:-} ]; then echo "[Service]" | sudo tee /etc/systemd/system/docker.service.d/https-proxy.conf echo "Environment=\"HTTPS_PROXY=$https_proxy\"" | sudo tee --append /etc/systemd/system/docker.service.d/https-proxy.conf fi - if [ $no_proxy ]; then + if [ ${no_proxy:-} ]; then echo "[Service]" | sudo tee /etc/systemd/system/docker.service.d/no-proxy.conf echo "Environment=\"NO_PROXY=$no_proxy\"" | sudo tee --append /etc/systemd/system/docker.service.d/no-proxy.conf fi @@ -86,13 +91,12 @@ function _install_docker { } function _set_environment_file { - ansible_ifconfig=$(ansible ovn-central[0] -i $kud_inventory -m shell -a "ifconfig eth1 |grep \"inet addr\" |awk '{print \$2}' |awk -F: '{print \$2}'") - if [[ $ansible_ifconfig != *CHANGED* ]]; then - echo "Fail to get the OVN central IP address from eth1 nic" - exit - fi - echo "export OVN_CENTRAL_ADDRESS=$(echo ${ansible_ifconfig#*>>} | tr '\n' ':')6641" | sudo tee --append /etc/environment + # By default ovn central interface is the first active network interface on localhost. If other wanted, need to export this variable in aio.sh or Vagrant file. + OVN_CENTRAL_INTERFACE=${OVN_CENTRAL_INTERFACE:-$(ip addr show | awk '/inet.*brd/{print $NF; exit}')} + echo "export OVN_CENTRAL_INTERFACE=${OVN_CENTRAL_INTERFACE}" | sudo tee --append /etc/environment + echo "export OVN_CENTRAL_ADDRESS=$(get_ovn_central_address)" | sudo tee --append /etc/environment echo "export KUBE_CONFIG_DIR=/opt/kubeconfig" | sudo tee --append /etc/environment + echo "export CSAR_DIR=/opt/csar" | sudo tee --append /etc/environment } # install_k8s() - Install Kubernetes using kubespray tool @@ -102,7 +106,7 @@ function install_k8s { version=$(grep "kubespray_version" ${kud_playbooks}/kud-vars.yml | awk -F ': ' '{print $2}') 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 + sudo apt-get install -y sshpass make unzip # install make to run mitogen target and unzip is mitogen playbook dependency _install_docker _install_ansible wget https://github.com/kubernetes-incubator/kubespray/archive/$tarball @@ -112,18 +116,21 @@ function install_k8s { sudo mkdir -p ${local_release_dir}/containers rm $tarball - sudo -E pip install -r $dest_folder/kubespray-$version/requirements.txt + pushd $dest_folder/kubespray-$version/ + sudo -E pip install -r ./requirements.txt + make mitogen + popd rm -f $kud_inventory_folder/group_vars/all.yml 2> /dev/null - if [[ -n "${verbose}" ]]; then + if [[ -n "${verbose:-}" ]]; then echo "kube_log_level: 5" | tee $kud_inventory_folder/group_vars/all.yml else echo "kube_log_level: 2" | tee $kud_inventory_folder/group_vars/all.yml fi echo "kubeadm_enabled: true" | tee --append $kud_inventory_folder/group_vars/all.yml - if [[ -n "${http_proxy}" ]]; then + if [[ -n "${http_proxy:-}" ]]; then echo "http_proxy: \"$http_proxy\"" | tee --append $kud_inventory_folder/group_vars/all.yml fi - if [[ -n "${https_proxy}" ]]; then + if [[ -n "${https_proxy:-}" ]]; then echo "https_proxy: \"$https_proxy\"" | tee --append $kud_inventory_folder/group_vars/all.yml fi ansible-playbook $verbose -i $kud_inventory $dest_folder/kubespray-$version/cluster.yml --become --become-user=root | sudo tee $log_folder/setup-kubernetes.log @@ -162,7 +169,6 @@ function install_plugin { sudo mkdir -p /opt/{kubeconfig,consul/config} sudo cp $HOME/.kube/config /opt/kubeconfig/kud - _set_environment_file source /etc/environment pushd $kud_folder/../../../deployments @@ -207,14 +213,15 @@ if ! sudo -n "true"; then exit 1 fi -if [[ -n "${KUD_DEBUG}" ]]; then +verbose="" +if [[ -n "${KUD_DEBUG:-}" ]]; then set -o xtrace verbose="-vvv" fi # Configuration values log_folder=/var/log/kud -kud_folder=$(pwd) +kud_folder=${INSTALLER_DIR} kud_infra_folder=$kud_folder/../../deployment_infra export kud_inventory_folder=$kud_folder/inventory kud_inventory=$kud_inventory_folder/hosts.ini @@ -226,7 +233,6 @@ testing_enabled=${KUD_ENABLE_TESTS:-false} sudo mkdir -p $log_folder sudo mkdir -p /opt/csar sudo chown -R $USER /opt/csar -echo "export CSAR_DIR=/opt/csar" | sudo tee --append /etc/environment # Install dependencies # Setup proxy variables @@ -237,6 +243,7 @@ fi sudo apt-get update install_k8s install_addons +_set_environment_file if ${KUD_PLUGIN_ENABLED:-false}; then install_plugin fi diff --git a/kud/tests/_functions.sh b/kud/tests/_functions.sh index 542443d6..5e6314ce 100755 --- a/kud/tests/_functions.sh +++ b/kud/tests/_functions.sh @@ -12,6 +12,8 @@ set -o errexit set -o nounset set -o pipefail +FUNCTIONS_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd) + source /etc/environment function print_msg { @@ -22,10 +24,10 @@ function print_msg { echo -e "${RED} $msg ---------------------------------------${NC}" } -function _get_ovn_central_address { - ansible_ifconfig=$(ansible ovn-central[0] -i $test_folder/../hosting_providers/vagrant/inventory/hosts.ini -m shell -a "ifconfig eth1 |grep \"inet addr\" |awk '{print \$2}' |awk -F: '{print \$2}'") +function get_ovn_central_address { + ansible_ifconfig=$(ansible ovn-central[0] -i ${FUNCTIONS_DIR}/../hosting_providers/vagrant/inventory/hosts.ini -m shell -a "ifconfig ${OVN_CENTRAL_INTERFACE} |grep \"inet addr\" |awk '{print \$2}' |awk -F: '{print \$2}'") if [[ $ansible_ifconfig != *CHANGED* ]]; then - echo "Fail to get the OVN central IP address from eth1 nic" + echo "Fail to get the OVN central IP address from ${OVN_CENTRAL_INTERFACE} nic" exit fi echo "$(echo ${ansible_ifconfig#*>>} | tr '\n' ':')6641" @@ -39,7 +41,7 @@ function init_network { name=$(cat $fname | yq '.spec.name' | xargs) subnet=$(cat $fname | yq '.spec.subnet' | xargs) gateway=$(cat $fname | yq '.spec.gateway' | xargs) - ovn_central_address=$(_get_ovn_central_address) + ovn_central_address=$(get_ovn_central_address) router_mac=$(printf '00:00:00:%02X:%02X:%02X' $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256))) ovn-nbctl --may-exist --db tcp:$ovn_central_address ls-add $name -- set logical_switch $name other-config:subnet=$subnet external-ids:gateway_ip=$gateway @@ -52,7 +54,7 @@ function cleanup_network { local fname=$1 name=$(cat $fname | yq '.spec.name' | xargs) - ovn_central_address=$(_get_ovn_central_address) + ovn_central_address=$(get_ovn_central_address) for cmd in "ls-del $name" "lrp-del rtos-$name" "lsp-del stor-$name"; do ovn-nbctl --if-exist --db tcp:$ovn_central_address $cmd @@ -111,6 +113,10 @@ function wait_deployment { # setup() - Base testing setup shared among functional tests function setup { + if ! $(kubectl version &>/dev/null); then + echo "This funtional test requires kubectl client" + exit 1 + fi for deployment_name in $@; do recreate_deployment $deployment_name done @@ -126,9 +132,4 @@ function teardown { destroy_deployment $deployment_name done } - -if ! $(kubectl version &>/dev/null); then - echo "This funtional test requires kubectl client" - exit 1 -fi -test_folder=$(pwd) +test_folder=${FUNCTIONS_DIR} diff --git a/src/k8splugin/go.sum b/src/k8splugin/go.sum index cf605d2f..0047e332 100644 --- a/src/k8splugin/go.sum +++ b/src/k8splugin/go.sum @@ -129,6 +129,7 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= @@ -147,6 +148,7 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245 h1:DNVk+NIkGS0RbLkjQOLCJb/759yfCysThkMbl7EXxyY= github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245/go.mod h1:O1c8HleITsZqzNZDjSNzirUGsMT0oGu9LhHKoJrqO+A= diff --git a/src/k8splugin/internal/app/client.go b/src/k8splugin/internal/app/client.go index 158d21de..9a5aa9e9 100644 --- a/src/k8splugin/internal/app/client.go +++ b/src/k8splugin/internal/app/client.go @@ -16,15 +16,16 @@ package app import ( "log" "os" - "strings" + "time" - utils "k8splugin/internal" "k8splugin/internal/config" "k8splugin/internal/connection" "k8splugin/internal/helm" + "k8splugin/internal/plugin" pkgerrors "github.com/pkg/errors" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" @@ -32,16 +33,12 @@ import ( "k8s.io/client-go/tools/clientcmd" ) -// PluginReference is the interface that is implemented -type PluginReference interface { - Create(yamlFilePath string, namespace string, client *KubernetesClient) (string, error) - Delete(resource helm.KubernetesResource, namespace string, client *KubernetesClient) error -} - +// KubernetesClient encapsulates the different clients' interfaces +// we need when interacting with a Kubernetes cluster type KubernetesClient struct { - clientSet *kubernetes.Clientset + clientSet kubernetes.Interface dynamicClient dynamic.Interface - discoverClient *discovery.DiscoveryClient + discoverClient discovery.CachedDiscoveryInterface restMapper meta.RESTMapper } @@ -86,40 +83,35 @@ func (k *KubernetesClient) init(cloudregion string) error { return pkgerrors.Wrap(err, "Creating dynamic client") } - k.discoverClient, err = discovery.NewDiscoveryClientForConfig(config) + k.discoverClient, err = discovery.NewCachedDiscoveryClientForConfig(config, os.TempDir(), "", 10*time.Minute) if err != nil { return pkgerrors.Wrap(err, "Creating discovery client") } + k.restMapper = restmapper.NewDeferredDiscoveryRESTMapper(k.discoverClient) return nil } func (k *KubernetesClient) ensureNamespace(namespace string) error { - namespacePlugin, ok := utils.LoadedPlugins["namespace"] - if !ok { - return pkgerrors.New("No plugin for namespace resource found") - } - symGetNamespaceFunc, err := namespacePlugin.Lookup("Get") + pluginImpl, err := plugin.GetPluginByKind("Namespace") if err != nil { - return pkgerrors.Wrap(err, "Error fetching get namespace function") + return pkgerrors.Wrap(err, "Loading Namespace Plugin") } - ns, _ := symGetNamespaceFunc.(func(string, string, kubernetes.Interface) (string, error))( - namespace, namespace, k.clientSet) + ns, err := pluginImpl.Get(helm.KubernetesResource{ + Name: namespace, + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Namespace", + }, + }, namespace, k) if ns == "" { log.Println("Creating " + namespace + " namespace") - symGetNamespaceFunc, err := namespacePlugin.Lookup("Create") - if err != nil { - return pkgerrors.Wrap(err, "Error fetching create namespace plugin") - } - namespaceResource := &utils.ResourceData{ - Namespace: namespace, - } - _, err = symGetNamespaceFunc.(func(*utils.ResourceData, kubernetes.Interface) (string, error))( - namespaceResource, k.clientSet) + _, err = pluginImpl.Create("", namespace, k) if err != nil { return pkgerrors.Wrap(err, "Error creating "+namespace+" namespace") } @@ -127,50 +119,6 @@ func (k *KubernetesClient) ensureNamespace(namespace string) error { return nil } -func (k *KubernetesClient) createGeneric(resTempl helm.KubernetesResourceTemplate, - namespace string) (helm.KubernetesResource, error) { - - log.Println("Processing Kind: " + resTempl.GVK.Kind) - - //Check if have the mapper before loading the plugin - err := k.updateMapper() - if err != nil { - return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Unable to create RESTMapper") - } - - pluginObject, ok := utils.LoadedPlugins["generic"] - if !ok { - return helm.KubernetesResource{}, pkgerrors.New("No generic plugin found") - } - - symbol, err := pluginObject.Lookup("ExportedVariable") - if err != nil { - return helm.KubernetesResource{}, pkgerrors.Wrap(err, "No ExportedVariable symbol found") - } - - //Assert if it implements the PluginReference interface - genericPlugin, ok := symbol.(PluginReference) - if !ok { - return helm.KubernetesResource{}, pkgerrors.New("ExportedVariable is not PluginReference type") - } - - if _, err := os.Stat(resTempl.FilePath); os.IsNotExist(err) { - return helm.KubernetesResource{}, pkgerrors.New("File " + resTempl.FilePath + "does not exists") - } - - log.Println("Processing file: " + resTempl.FilePath) - - name, err := genericPlugin.Create(resTempl.FilePath, namespace, k) - if err != nil { - return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error in generic plugin") - } - - return helm.KubernetesResource{ - GVK: resTempl.GVK, - Name: name, - }, nil -} - func (k *KubernetesClient) createKind(resTempl helm.KubernetesResourceTemplate, namespace string) (helm.KubernetesResource, error) { @@ -182,28 +130,16 @@ func (k *KubernetesClient) createKind(resTempl helm.KubernetesResourceTemplate, log.Println("Processing file: " + resTempl.FilePath) - //Populate the namespace from profile instead of instance body - genericKubeData := &utils.ResourceData{ - YamlFilePath: resTempl.FilePath, - Namespace: namespace, - } - - typePlugin, ok := utils.LoadedPlugins[strings.ToLower(resTempl.GVK.Kind)] - if !ok { - log.Println("No plugin for kind " + resTempl.GVK.Kind + " found. Using generic Plugin") - return k.createGeneric(resTempl, namespace) - } - - symCreateResourceFunc, err := typePlugin.Lookup("Create") + pluginImpl, err := plugin.GetPluginByKind(resTempl.GVK.Kind) if err != nil { - return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error fetching "+resTempl.GVK.Kind+" plugin") + return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error loading plugin") } - createdResourceName, err := symCreateResourceFunc.(func(*utils.ResourceData, kubernetes.Interface) (string, error))( - genericKubeData, k.clientSet) + createdResourceName, err := pluginImpl.Create(resTempl.FilePath, namespace, k) if err != nil { return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error in plugin "+resTempl.GVK.Kind+" plugin") } + log.Print(createdResourceName + " created") return helm.KubernetesResource{ GVK: resTempl.GVK, @@ -231,58 +167,18 @@ func (k *KubernetesClient) createResources(sortedTemplates []helm.KubernetesReso return createdResources, nil } -func (k *KubernetesClient) deleteGeneric(resource helm.KubernetesResource, namespace string) error { - log.Println("Deleting Kind: " + resource.GVK.Kind) - - pluginObject, ok := utils.LoadedPlugins["generic"] - if !ok { - return pkgerrors.New("No generic plugin found") - } - - //Check if have the mapper before loading the plugin - err := k.updateMapper() - if err != nil { - return pkgerrors.Wrap(err, "Unable to create RESTMapper") - } - - symbol, err := pluginObject.Lookup("ExportedVariable") - if err != nil { - return pkgerrors.Wrap(err, "No ExportedVariable symbol found") - } - - //Assert that it implements the PluginReference interface - genericPlugin, ok := symbol.(PluginReference) - if !ok { - return pkgerrors.New("ExportedVariable is not PluginReference type") - } - - err = genericPlugin.Delete(resource, namespace, k) - if err != nil { - return pkgerrors.Wrap(err, "Error in generic plugin") - } - - return nil -} - func (k *KubernetesClient) deleteKind(resource helm.KubernetesResource, namespace string) error { log.Println("Deleting Kind: " + resource.GVK.Kind) - typePlugin, ok := utils.LoadedPlugins[strings.ToLower(resource.GVK.Kind)] - if !ok { - log.Println("No plugin for kind " + resource.GVK.Kind + " found. Using generic Plugin") - return k.deleteGeneric(resource, namespace) - } - - symDeleteResourceFunc, err := typePlugin.Lookup("Delete") + pluginImpl, err := plugin.GetPluginByKind(resource.GVK.Kind) if err != nil { - return pkgerrors.Wrap(err, "Error finding Delete symbol in plugin") + return pkgerrors.Wrap(err, "Error loading plugin") } log.Println("Deleting resource: " + resource.Name) - err = symDeleteResourceFunc.(func(string, string, kubernetes.Interface) error)( - resource.Name, namespace, k.clientSet) + err = pluginImpl.Delete(resource, namespace, k) if err != nil { - return pkgerrors.Wrap(err, "Error destroying "+resource.Name) + return pkgerrors.Wrap(err, "Error deleting "+resource.Name) } return nil @@ -300,21 +196,6 @@ func (k *KubernetesClient) deleteResources(resources []helm.KubernetesResource, return nil } -func (k *KubernetesClient) updateMapper() error { - //Create restMapper if not already done - if k.restMapper != nil { - return nil - } - - groupResources, err := restmapper.GetAPIGroupResources(k.discoverClient) - if err != nil { - return pkgerrors.Wrap(err, "Get GroupResources") - } - - k.restMapper = restmapper.NewDiscoveryRESTMapper(groupResources) - return nil -} - //GetMapper returns the RESTMapper that was created for this client func (k *KubernetesClient) GetMapper() meta.RESTMapper { return k.restMapper @@ -325,3 +206,9 @@ func (k *KubernetesClient) GetMapper() meta.RESTMapper { func (k *KubernetesClient) GetDynamicClient() dynamic.Interface { return k.dynamicClient } + +// GetStandardClient returns the standard client that can be used to handle +// standard kubernetes kinds +func (k *KubernetesClient) GetStandardClient() kubernetes.Interface { + return k.clientSet +} diff --git a/src/k8splugin/internal/app/client_test.go b/src/k8splugin/internal/app/client_test.go index 4bfbcb18..e52aa7f8 100644 --- a/src/k8splugin/internal/app/client_test.go +++ b/src/k8splugin/internal/app/client_test.go @@ -42,7 +42,7 @@ func LoadMockPlugins(krdLoadedPlugins map[string]*plugin.Plugin) error { } krdLoadedPlugins["namespace"] = mockPlugin - krdLoadedPlugins["deployment"] = mockPlugin + krdLoadedPlugins["generic"] = mockPlugin krdLoadedPlugins["service"] = mockPlugin return nil diff --git a/src/k8splugin/internal/plugin/helpers.go b/src/k8splugin/internal/plugin/helpers.go new file mode 100644 index 00000000..efcd18c1 --- /dev/null +++ b/src/k8splugin/internal/plugin/helpers.go @@ -0,0 +1,92 @@ +/* + * 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 plugin + +import ( + "log" + "strings" + + utils "k8splugin/internal" + "k8splugin/internal/helm" + + pkgerrors "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" +) + +// KubernetesConnector is an interface that is expected to be implemented +// by any code that calls the plugin framework functions. +// It implements methods that are needed by the plugins to get Kubernetes +// clients and other information needed to interface with Kubernetes +type KubernetesConnector interface { + //GetMapper returns the RESTMapper that was created for this client + GetMapper() meta.RESTMapper + + //GetDynamicClient returns the dynamic client that is needed for + //unstructured REST calls to the apiserver + GetDynamicClient() dynamic.Interface + + // GetStandardClient returns the standard client that can be used to handle + // standard kubernetes kinds + GetStandardClient() kubernetes.Interface +} + +// Reference is the interface that is implemented +type Reference interface { + //Create a kubernetes resource described by the yaml in yamlFilePath + Create(yamlFilePath string, namespace string, client KubernetesConnector) (string, error) + + //Get a kubernetes resource based on the groupVersionKind and resourceName provided in resource + Get(resource helm.KubernetesResource, namespace string, client KubernetesConnector) (string, error) + + //List all resources of the specified GroupVersionKind in the given namespace + //If gvk is empty, the plugin will return all supported objects in the namespace + List(gvk schema.GroupVersionKind, namespace string, client KubernetesConnector) ([]helm.KubernetesResource, error) + + //Delete a kubernetes resource described in the provided namespace + Delete(resource helm.KubernetesResource, namespace string, client KubernetesConnector) error +} + +// GetPluginByKind returns a plugin by the kind name +// If plugin does not exist, it will return the generic plugin +// TODO: Change this once we have a plugin registration mechanism +func GetPluginByKind(kind string) (Reference, error) { + + typePlugin, ok := utils.LoadedPlugins[strings.ToLower(kind)] + if !ok { + log.Println("No plugin for kind " + kind + " found. Using generic Plugin") + typePlugin, ok = utils.LoadedPlugins["generic"] + if !ok { + return nil, pkgerrors.New("No generic plugin found") + } + } + + symbol, err := typePlugin.Lookup("ExportedVariable") + if err != nil { + return nil, pkgerrors.Wrap(err, "No ExportedVariable symbol found") + } + + //Assert if it implements the PluginReference interface + pluginImpl, ok := symbol.(Reference) + if !ok { + return nil, pkgerrors.New("ExportedVariable does not implement plugins.Reference interface type") + } + + return pluginImpl, nil +} diff --git a/src/k8splugin/mock_files/mock_plugins/mockplugin.go b/src/k8splugin/mock_files/mock_plugins/mockplugin.go index bdc2130c..0c3d246d 100644 --- a/src/k8splugin/mock_files/mock_plugins/mockplugin.go +++ b/src/k8splugin/mock_files/mock_plugins/mockplugin.go @@ -14,30 +14,43 @@ limitations under the License. package main import ( - "k8s.io/client-go/kubernetes" + "k8splugin/internal/helm" + "k8splugin/internal/plugin" - utils "k8splugin/internal" + "k8s.io/apimachinery/pkg/runtime/schema" ) -func main() {} +// ExportedVariable is what we will look for when calling the plugin +var ExportedVariable mockPlugin + +type mockPlugin struct { +} // Create object in a specific Kubernetes resource -func Create(data *utils.ResourceData, client kubernetes.Interface) (string, error) { +func (p mockPlugin) Create(yamlFilePath string, namespace string, client plugin.KubernetesConnector) (string, error) { return "resource-name", nil } // List of existing resources -func List(namespace string, client kubernetes.Interface) ([]string, error) { - returnVal := []string{"resource-name-1", "resource-name-2"} +func (p mockPlugin) List(gvk schema.GroupVersionKind, namespace string, + client plugin.KubernetesConnector) ([]helm.KubernetesResource, error) { + returnVal := []helm.KubernetesResource{ + { + Name: "resource-name-1", + }, + { + Name: "resource-name-2", + }, + } return returnVal, nil } // Delete existing resources -func Delete(name string, namespace string, client kubernetes.Interface) error { +func (p mockPlugin) Delete(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) error { return nil } // Get existing resource host -func Get(name string, namespace string, client kubernetes.Interface) (string, error) { - return name, nil +func (p mockPlugin) Get(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) (string, error) { + return resource.Name, nil } diff --git a/src/k8splugin/plugins/generic/plugin.go b/src/k8splugin/plugins/generic/plugin.go index 9ecaf68c..31c65d05 100644 --- a/src/k8splugin/plugins/generic/plugin.go +++ b/src/k8splugin/plugins/generic/plugin.go @@ -23,15 +23,18 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" utils "k8splugin/internal" - "k8splugin/internal/app" + "k8splugin/internal/plugin" "k8splugin/internal/helm" ) +// ExportedVariable is what we will look for when calling the generic plugin +var ExportedVariable genericPlugin + type genericPlugin struct { } // Create deployment object in a specific Kubernetes cluster -func (g genericPlugin) Create(yamlFilePath string, namespace string, client *app.KubernetesClient) (string, error) { +func (g genericPlugin) Create(yamlFilePath string, namespace string, client plugin.KubernetesConnector) (string, error) { if namespace == "" { namespace = "default" } @@ -72,15 +75,58 @@ func (g genericPlugin) Create(yamlFilePath string, namespace string, client *app return createdObj.GetName(), nil } -// Delete an existing deployment hosted in a specific Kubernetes cluster -func (g genericPlugin) Delete(resource helm.KubernetesResource, namespace string, client *app.KubernetesClient) error { +// Get an existing resource hosted in a specific Kubernetes cluster +func (g genericPlugin) Get(resource helm.KubernetesResource, + namespace string, client plugin.KubernetesConnector) (string, error) { if namespace == "" { namespace = "default" } - deletePolicy := metav1.DeletePropagationForeground - opts := &metav1.DeleteOptions{ - PropagationPolicy: &deletePolicy, + dynClient := client.GetDynamicClient() + mapper := client.GetMapper() + + mapping, err := mapper.RESTMapping(schema.GroupKind{ + Group: resource.GVK.Group, + Kind: resource.GVK.Kind, + }, resource.GVK.Version) + if err != nil { + return "", pkgerrors.Wrap(err, "Mapping kind to resource error") + } + + gvr := mapping.Resource + log.Printf("Using gvr: %s, %s, %s", gvr.Group, gvr.Version, gvr.Resource) + + opts := metav1.GetOptions{} + var unstruct *unstructured.Unstructured + switch mapping.Scope.Name() { + case meta.RESTScopeNameNamespace: + unstruct, err = dynClient.Resource(gvr).Namespace(namespace).Get(resource.Name, opts) + case meta.RESTScopeNameRoot: + unstruct, err = dynClient.Resource(gvr).Get(resource.Name, opts) + default: + return "", pkgerrors.New("Got an unknown RESTSCopeName for mapping: " + resource.GVK.String()) + } + + if err != nil { + return "", pkgerrors.Wrap(err, "Delete object error") + } + + return unstruct.GetName(), nil +} + +// List all existing resources of the GroupVersionKind +// TODO: Implement in seperate patch +func (g genericPlugin) List(gvk schema.GroupVersionKind, namespace string, + client plugin.KubernetesConnector) ([]helm.KubernetesResource, error) { + + var returnData []helm.KubernetesResource + return returnData, nil +} + +// Delete an existing resource hosted in a specific Kubernetes cluster +func (g genericPlugin) Delete(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) error { + if namespace == "" { + namespace = "default" } dynClient := client.GetDynamicClient() @@ -97,6 +143,11 @@ func (g genericPlugin) Delete(resource helm.KubernetesResource, namespace string gvr := mapping.Resource log.Printf("Using gvr: %s, %s, %s", gvr.Group, gvr.Version, gvr.Resource) + deletePolicy := metav1.DeletePropagationForeground + opts := &metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + } + switch mapping.Scope.Name() { case meta.RESTScopeNameNamespace: err = dynClient.Resource(gvr).Namespace(namespace).Delete(resource.Name, opts) @@ -111,6 +162,3 @@ func (g genericPlugin) Delete(resource helm.KubernetesResource, namespace string } return nil } - -// ExportedVariable is what we will look for when calling the generic plugin -var ExportedVariable genericPlugin diff --git a/src/k8splugin/plugins/namespace/plugin.go b/src/k8splugin/plugins/namespace/plugin.go index 6f823918..2d5d2ab8 100644 --- a/src/k8splugin/plugins/namespace/plugin.go +++ b/src/k8splugin/plugins/namespace/plugin.go @@ -16,39 +16,42 @@ package main import ( "log" - "k8s.io/client-go/kubernetes" - pkgerrors "github.com/pkg/errors" - coreV1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" utils "k8splugin/internal" + "k8splugin/internal/helm" + "k8splugin/internal/plugin" ) +// ExportedVariable is what we will look for when calling the plugin +var ExportedVariable namespacePlugin + +type namespacePlugin struct { +} + // Create a namespace object in a specific Kubernetes cluster -func Create(data *utils.ResourceData, client kubernetes.Interface) (string, error) { - namespace := &coreV1.Namespace{ +func (p namespacePlugin) Create(yamlFilePath string, namespace string, client plugin.KubernetesConnector) (string, error) { + namespaceObj := &coreV1.Namespace{ ObjectMeta: metaV1.ObjectMeta{ - Name: data.Namespace, + Name: namespace, }, } - _, err := client.CoreV1().Namespaces().Create(namespace) + _, err := client.GetStandardClient().CoreV1().Namespaces().Create(namespaceObj) if err != nil { return "", pkgerrors.Wrap(err, "Create Namespace error") } - log.Printf("Namespace (%s) created", data.Namespace) + log.Printf("Namespace (%s) created", namespace) - return data.Namespace, nil + return namespace, nil } // Get an existing namespace hosted in a specific Kubernetes cluster -func Get(name string, namespace string, client kubernetes.Interface) (string, error) { +func (p namespacePlugin) Get(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) (string, error) { opts := metaV1.GetOptions{} - opts.APIVersion = "apps/v1" - opts.Kind = "Deployment" - - ns, err := client.CoreV1().Namespaces().Get(name, opts) + ns, err := client.GetStandardClient().CoreV1().Namespaces().Get(resource.Name, opts) if err != nil { return "", pkgerrors.Wrap(err, "Get Namespace error") } @@ -57,14 +60,14 @@ func Get(name string, namespace string, client kubernetes.Interface) (string, er } // Delete an existing namespace hosted in a specific Kubernetes cluster -func Delete(name string, namespace string, client kubernetes.Interface) error { +func (p namespacePlugin) Delete(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) error { deletePolicy := metaV1.DeletePropagationForeground opts := &metaV1.DeleteOptions{ PropagationPolicy: &deletePolicy, } - log.Println("Deleting namespace: " + name) - if err := client.CoreV1().Namespaces().Delete(name, opts); err != nil { + log.Println("Deleting namespace: " + resource.Name) + if err := client.GetStandardClient().CoreV1().Namespaces().Delete(resource.Name, opts); err != nil { return pkgerrors.Wrap(err, "Delete namespace error") } @@ -72,23 +75,30 @@ func Delete(name string, namespace string, client kubernetes.Interface) error { } // List of existing namespaces hosted in a specific Kubernetes cluster -func List(namespace string, client kubernetes.Interface) ([]string, error) { +// This plugin ignores both gvk and namespace arguments +func (p namespacePlugin) List(gvk schema.GroupVersionKind, namespace string, client plugin.KubernetesConnector) ([]helm.KubernetesResource, error) { opts := metaV1.ListOptions{ Limit: utils.ResourcesListLimit, } - opts.APIVersion = "apps/v1" - opts.Kind = "Namespace" - list, err := client.CoreV1().Namespaces().List(opts) + list, err := client.GetStandardClient().CoreV1().Namespaces().List(opts) if err != nil { return nil, pkgerrors.Wrap(err, "Get Namespace list error") } - result := make([]string, 0, utils.ResourcesListLimit) + result := make([]helm.KubernetesResource, 0, utils.ResourcesListLimit) if list != nil { - for _, deployment := range list.Items { - log.Printf("%v", deployment.Name) - result = append(result, deployment.Name) + for _, ns := range list.Items { + log.Printf("%v", ns.Name) + result = append(result, + helm.KubernetesResource{ + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Namespace", + }, + Name: ns.Name, + }) } } diff --git a/src/k8splugin/plugins/namespace/plugin_test.go b/src/k8splugin/plugins/namespace/plugin_test.go index 0019df1c..9e57b971 100644 --- a/src/k8splugin/plugins/namespace/plugin_test.go +++ b/src/k8splugin/plugins/namespace/plugin_test.go @@ -18,36 +18,54 @@ import ( "strings" "testing" - utils "k8splugin/internal" + "k8splugin/internal/helm" coreV1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" - testclient "k8s.io/client-go/kubernetes/fake" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" ) +type TestKubernetesConnector struct { + object runtime.Object +} + +func (t TestKubernetesConnector) GetMapper() meta.RESTMapper { + return nil +} + +func (t TestKubernetesConnector) GetDynamicClient() dynamic.Interface { + return nil +} + +func (t TestKubernetesConnector) GetStandardClient() kubernetes.Interface { + return fake.NewSimpleClientset(t.object) +} + func TestCreateNamespace(t *testing.T) { - namespace := "test1" testCases := []struct { label string - input *utils.ResourceData - clientOutput *coreV1.Namespace + input string + object *coreV1.Namespace expectedResult string expectedError string }{ { - label: "Successfully create a namespace", - input: &utils.ResourceData{ - Namespace: namespace, - }, - clientOutput: &coreV1.Namespace{}, - expectedResult: namespace, + label: "Successfully create a namespace", + input: "test1", + object: &coreV1.Namespace{}, + expectedResult: "test1", }, } for _, testCase := range testCases { - client := testclient.NewSimpleClientset(testCase.clientOutput) + client := TestKubernetesConnector{testCase.object} t.Run(testCase.label, func(t *testing.T) { - result, err := Create(testCase.input, client) + result, err := namespacePlugin{}.Create("", testCase.input, client) if err != nil { if testCase.expectedError == "" { t.Fatalf("Create method return an un-expected (%s)", err) @@ -72,39 +90,47 @@ func TestCreateNamespace(t *testing.T) { } func TestListNamespace(t *testing.T) { - namespace := "test1" testCases := []struct { label string input string - clientOutput *coreV1.NamespaceList - expectedResult []string + object *coreV1.NamespaceList + expectedResult []helm.KubernetesResource }{ { label: "Sucessfully to display an empty namespace list", - input: namespace, - clientOutput: &coreV1.NamespaceList{}, - expectedResult: []string{}, + input: "", + object: &coreV1.NamespaceList{}, + expectedResult: []helm.KubernetesResource{}, }, { label: "Sucessfully to display a list of existing namespaces", - input: namespace, - clientOutput: &coreV1.NamespaceList{ + input: "test1", + object: &coreV1.NamespaceList{ Items: []coreV1.Namespace{ coreV1.Namespace{ ObjectMeta: metaV1.ObjectMeta{ - Name: namespace, + Name: "test1", }, }, }, }, - expectedResult: []string{namespace}, + expectedResult: []helm.KubernetesResource{ + { + Name: "test1", + GVK: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}, + }, + }, }, } for _, testCase := range testCases { - client := testclient.NewSimpleClientset(testCase.clientOutput) + client := TestKubernetesConnector{testCase.object} t.Run(testCase.label, func(t *testing.T) { - result, err := List(testCase.input, client) + result, err := namespacePlugin{}.List(schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Namespace", + }, testCase.input, client) if err != nil { t.Fatalf("List method returned an error (%s)", err) } else { @@ -113,7 +139,7 @@ func TestListNamespace(t *testing.T) { } if !reflect.DeepEqual(testCase.expectedResult, result) { - t.Fatalf("List method returned: \n%v\n and it was expected: \n%v", result, testCase.expectedResult) + t.Fatalf("List method returned: \n%+v\n and it was expected: \n%+v", result, testCase.expectedResult) } } }) @@ -122,14 +148,14 @@ func TestListNamespace(t *testing.T) { func TestDeleteNamespace(t *testing.T) { testCases := []struct { - label string - input map[string]string - clientOutput *coreV1.Namespace + label string + input map[string]string + object *coreV1.Namespace }{ { label: "Sucessfully to delete an existing namespace", input: map[string]string{"name": "test-name", "namespace": "test-namespace"}, - clientOutput: &coreV1.Namespace{ + object: &coreV1.Namespace{ ObjectMeta: metaV1.ObjectMeta{ Name: "test-name", }, @@ -138,9 +164,12 @@ func TestDeleteNamespace(t *testing.T) { } for _, testCase := range testCases { - client := testclient.NewSimpleClientset(testCase.clientOutput) + client := TestKubernetesConnector{testCase.object} t.Run(testCase.label, func(t *testing.T) { - err := Delete(testCase.input["name"], testCase.input["namespace"], client) + err := namespacePlugin{}.Delete(helm.KubernetesResource{ + GVK: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}, + Name: testCase.input["name"], + }, testCase.input["namespace"], client) if err != nil { t.Fatalf("Delete method returned an error (%s)", err) } @@ -152,14 +181,14 @@ func TestGetNamespace(t *testing.T) { testCases := []struct { label string input map[string]string - clientOutput *coreV1.Namespace + object *coreV1.Namespace expectedResult string expectedError string }{ { label: "Sucessfully to get an existing namespace", input: map[string]string{"name": "test-name", "namespace": "test-namespace"}, - clientOutput: &coreV1.Namespace{ + object: &coreV1.Namespace{ ObjectMeta: metaV1.ObjectMeta{ Name: "test-name", }, @@ -169,7 +198,7 @@ func TestGetNamespace(t *testing.T) { { label: "Fail to get an non-existing namespace", input: map[string]string{"name": "test-name", "namespace": "test-namespace"}, - clientOutput: &coreV1.Namespace{ + object: &coreV1.Namespace{ ObjectMeta: metaV1.ObjectMeta{ Name: "test-name2", }, @@ -179,9 +208,12 @@ func TestGetNamespace(t *testing.T) { } for _, testCase := range testCases { - client := testclient.NewSimpleClientset(testCase.clientOutput) + client := TestKubernetesConnector{testCase.object} t.Run(testCase.label, func(t *testing.T) { - result, err := Get(testCase.input["name"], testCase.input["namespace"], client) + result, err := namespacePlugin{}.Get(helm.KubernetesResource{ + GVK: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}, + Name: testCase.input["name"], + }, testCase.input["namespace"], client) if err != nil { if testCase.expectedError == "" { t.Fatalf("Get method return an un-expected (%s)", err) diff --git a/src/k8splugin/plugins/network/plugin.go b/src/k8splugin/plugins/network/plugin.go index 74ac3473..5cc57e87 100644 --- a/src/k8splugin/plugins/network/plugin.go +++ b/src/k8splugin/plugins/network/plugin.go @@ -14,31 +14,38 @@ limitations under the License. package main import ( - "k8splugin/plugins/network/v1" + v1 "k8splugin/plugins/network/v1" "regexp" utils "k8splugin/internal" + "k8splugin/internal/app" + "k8splugin/internal/helm" pkgerrors "github.com/pkg/errors" - "k8s.io/client-go/kubernetes" + "k8s.io/apimachinery/pkg/runtime/schema" ) -func extractData(data string) (vnfID, cniType, networkName string) { +// ExportedVariable is what we will look for when calling the plugin +var ExportedVariable networkPlugin + +type networkPlugin struct { +} + +func extractData(data string) (cniType, networkName string) { re := regexp.MustCompile("_") split := re.Split(data, -1) if len(split) != 3 { return } - vnfID = split[0] cniType = split[1] networkName = split[2] return } // Create an ONAP Network object -func Create(data *utils.ResourceData, client kubernetes.Interface) (string, error) { +func (p networkPlugin) Create(yamlFilePath string, namespace string, client *app.KubernetesClient) (string, error) { network := &v1.OnapNetwork{} - if _, err := utils.DecodeYAML(data.YamlFilePath, network); err != nil { + if _, err := utils.DecodeYAML(yamlFilePath, network); err != nil { return "", pkgerrors.Wrap(err, "Decode network object error") } @@ -58,17 +65,24 @@ func Create(data *utils.ResourceData, client kubernetes.Interface) (string, erro return "", pkgerrors.Wrap(err, "Error during the creation for "+cniType+" plugin") } - return data.VnfId + "_" + cniType + "_" + name, nil + return cniType + "_" + name, nil +} + +// Get a Network +func (p networkPlugin) Get(resource helm.KubernetesResource, namespace string, client *app.KubernetesClient) (string, error) { + return "", nil } // List of Networks -func List(namespace string, kubeclient kubernetes.Interface) ([]string, error) { +func (p networkPlugin) List(gvk schema.GroupVersionKind, namespace string, + client *app.KubernetesClient) ([]helm.KubernetesResource, error) { + return nil, nil } // Delete an existing Network -func Delete(name string, namespace string, kubeclient kubernetes.Interface) error { - _, cniType, networkName := extractData(name) +func (p networkPlugin) Delete(resource helm.KubernetesResource, namespace string, client *app.KubernetesClient) error { + cniType, networkName := extractData(resource.Name) typePlugin, ok := utils.LoadedPlugins[cniType+"-network"] if !ok { return pkgerrors.New("No plugin for resource " + cniType + " found") @@ -85,8 +99,3 @@ func Delete(name string, namespace string, kubeclient kubernetes.Interface) erro return nil } - -// Get an existing Network -func Get(name string, namespace string, kubeclient kubernetes.Interface) (string, error) { - return "", nil -} diff --git a/src/k8splugin/plugins/network/plugin_test.go b/src/k8splugin/plugins/network/plugin_test.go index e8e113b2..5a8ce4db 100644 --- a/src/k8splugin/plugins/network/plugin_test.go +++ b/src/k8splugin/plugins/network/plugin_test.go @@ -15,6 +15,7 @@ package main import ( utils "k8splugin/internal" + "k8splugin/internal/helm" "os" "plugin" "reflect" @@ -22,6 +23,7 @@ import ( "testing" pkgerrors "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime/schema" ) func LoadMockNetworkPlugins(krdLoadedPlugins *map[string]*plugin.Plugin, networkName, errMsg string) error { @@ -51,7 +53,6 @@ func LoadMockNetworkPlugins(krdLoadedPlugins *map[string]*plugin.Plugin, network } func TestCreateNetwork(t *testing.T) { - internalVNFID := "1" oldkrdPluginData := utils.LoadedPlugins defer func() { @@ -60,34 +61,27 @@ func TestCreateNetwork(t *testing.T) { testCases := []struct { label string - input *utils.ResourceData + input string mockError string mockOutput string expectedResult string expectedError string }{ { - label: "Fail to decode a network object", - input: &utils.ResourceData{ - YamlFilePath: "../../mock_files/mock_yamls/service.yaml", - }, + label: "Fail to decode a network object", + input: "../../mock_files/mock_yamls/service.yaml", expectedError: "No plugin for resource", }, { - label: "Fail to create a network", - input: &utils.ResourceData{ - YamlFilePath: "../../mock_files/mock_yamls/ovn4nfvk8s.yaml", - }, + label: "Fail to create a network", + input: "../../mock_files/mock_yamls/ovn4nfvk8s.yaml", mockError: "Internal error", expectedError: "Error during the creation for ovn4nfvk8s plugin: Internal error", }, { - label: "Successfully create a ovn4nfv network", - input: &utils.ResourceData{ - VnfId: internalVNFID, - YamlFilePath: "../../mock_files/mock_yamls/ovn4nfvk8s.yaml", - }, - expectedResult: internalVNFID + "_ovn4nfvk8s_myNetwork", + label: "Successfully create a ovn4nfv network", + input: "../../mock_files/mock_yamls/ovn4nfvk8s.yaml", + expectedResult: "ovn4nfvk8s_myNetwork", mockOutput: "myNetwork", }, } @@ -98,7 +92,7 @@ func TestCreateNetwork(t *testing.T) { if err != nil { t.Fatalf("TestCreateNetwork returned an error (%s)", err) } - result, err := Create(testCase.input, nil) + result, err := networkPlugin{}.Create(testCase.input, "", nil) if err != nil { if testCase.expectedError == "" { t.Fatalf("Create method return an un-expected (%s)", err) @@ -157,7 +151,10 @@ func TestDeleteNetwork(t *testing.T) { if err != nil { t.Fatalf("TestDeleteNetwork returned an error (%s)", err) } - err = Delete(testCase.input, "", nil) + err = networkPlugin{}.Delete(helm.KubernetesResource{ + GVK: schema.GroupVersionKind{Group: "", Version: "", Kind: "Network"}, + Name: testCase.input, + }, "", nil) if err != nil { if testCase.expectedError == "" { t.Fatalf("Create method return an un-expected (%s)", err) diff --git a/src/k8splugin/plugins/service/plugin.go b/src/k8splugin/plugins/service/plugin.go index ea5aecad..2957c441 100644 --- a/src/k8splugin/plugins/service/plugin.go +++ b/src/k8splugin/plugins/service/plugin.go @@ -16,23 +16,29 @@ package main import ( "log" - "k8s.io/client-go/kubernetes" - pkgerrors "github.com/pkg/errors" - coreV1 "k8s.io/api/core/v1" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" utils "k8splugin/internal" + "k8splugin/internal/helm" + "k8splugin/internal/plugin" ) +// ExportedVariable is what we will look for when calling the plugin +var ExportedVariable servicePlugin + +type servicePlugin struct { +} + // Create a service object in a specific Kubernetes cluster -func Create(data *utils.ResourceData, client kubernetes.Interface) (string, error) { - namespace := data.Namespace +func (p servicePlugin) Create(yamlFilePath string, namespace string, client plugin.KubernetesConnector) (string, error) { if namespace == "" { namespace = "default" } - obj, err := utils.DecodeYAML(data.YamlFilePath, nil) + + obj, err := utils.DecodeYAML(yamlFilePath, nil) if err != nil { return "", pkgerrors.Wrap(err, "Decode service object error") } @@ -43,7 +49,7 @@ func Create(data *utils.ResourceData, client kubernetes.Interface) (string, erro } service.Namespace = namespace - result, err := client.CoreV1().Services(namespace).Create(service) + result, err := client.GetStandardClient().CoreV1().Services(namespace).Create(service) if err != nil { return "", pkgerrors.Wrap(err, "Create Service error") } @@ -52,7 +58,8 @@ func Create(data *utils.ResourceData, client kubernetes.Interface) (string, erro } // List of existing services hosted in a specific Kubernetes cluster -func List(namespace string, kubeclient kubernetes.Interface) ([]string, error) { +// gvk parameter is not used as this plugin is specific to services only +func (p servicePlugin) List(gvk schema.GroupVersionKind, namespace string, client plugin.KubernetesConnector) ([]helm.KubernetesResource, error) { if namespace == "" { namespace = "default" } @@ -60,19 +67,25 @@ func List(namespace string, kubeclient kubernetes.Interface) ([]string, error) { opts := metaV1.ListOptions{ Limit: utils.ResourcesListLimit, } - opts.APIVersion = "apps/v1" - opts.Kind = "Service" - list, err := kubeclient.CoreV1().Services(namespace).List(opts) + list, err := client.GetStandardClient().CoreV1().Services(namespace).List(opts) if err != nil { return nil, pkgerrors.Wrap(err, "Get Service list error") } - result := make([]string, 0, utils.ResourcesListLimit) + result := make([]helm.KubernetesResource, 0, utils.ResourcesListLimit) if list != nil { - for _, deployment := range list.Items { - log.Printf("%v", deployment.Name) - result = append(result, deployment.Name) + for _, service := range list.Items { + log.Printf("%v", service.Name) + result = append(result, + helm.KubernetesResource{ + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service", + }, + Name: service.GetName(), + }) } } @@ -80,7 +93,7 @@ func List(namespace string, kubeclient kubernetes.Interface) ([]string, error) { } // Delete an existing service hosted in a specific Kubernetes cluster -func Delete(name string, namespace string, kubeclient kubernetes.Interface) error { +func (p servicePlugin) Delete(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) error { if namespace == "" { namespace = "default" } @@ -90,8 +103,8 @@ func Delete(name string, namespace string, kubeclient kubernetes.Interface) erro PropagationPolicy: &deletePolicy, } - log.Println("Deleting service: " + name) - if err := kubeclient.CoreV1().Services(namespace).Delete(name, opts); err != nil { + log.Println("Deleting service: " + resource.Name) + if err := client.GetStandardClient().CoreV1().Services(namespace).Delete(resource.Name, opts); err != nil { return pkgerrors.Wrap(err, "Delete service error") } @@ -99,18 +112,15 @@ func Delete(name string, namespace string, kubeclient kubernetes.Interface) erro } // Get an existing service hosted in a specific Kubernetes cluster -func Get(name string, namespace string, kubeclient kubernetes.Interface) (string, error) { +func (p servicePlugin) Get(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) (string, error) { if namespace == "" { namespace = "default" } opts := metaV1.GetOptions{} - opts.APIVersion = "apps/v1" - opts.Kind = "Service" - - service, err := kubeclient.CoreV1().Services(namespace).Get(name, opts) + service, err := client.GetStandardClient().CoreV1().Services(namespace).Get(resource.Name, opts) if err != nil { - return "", pkgerrors.Wrap(err, "Get Deployment error") + return "", pkgerrors.Wrap(err, "Get Service error") } return service.Name, nil diff --git a/src/k8splugin/plugins/service/plugin_test.go b/src/k8splugin/plugins/service/plugin_test.go index d3614860..66703089 100644 --- a/src/k8splugin/plugins/service/plugin_test.go +++ b/src/k8splugin/plugins/service/plugin_test.go @@ -14,54 +14,67 @@ limitations under the License. package main import ( + "k8splugin/internal/helm" "reflect" "strings" "testing" - utils "k8splugin/internal" - coreV1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" - testclient "k8s.io/client-go/kubernetes/fake" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" ) +type TestKubernetesConnector struct { + object runtime.Object +} + +func (t TestKubernetesConnector) GetMapper() meta.RESTMapper { + return nil +} + +func (t TestKubernetesConnector) GetDynamicClient() dynamic.Interface { + return nil +} + +func (t TestKubernetesConnector) GetStandardClient() kubernetes.Interface { + return fake.NewSimpleClientset(t.object) +} + func TestCreateService(t *testing.T) { - namespace := "test1" name := "mock-service" testCases := []struct { label string - input *utils.ResourceData - clientOutput *coreV1.Service + input string + namespace string + object *coreV1.Service expectedResult string expectedError string }{ { - label: "Fail to create a service with invalid type", - input: &utils.ResourceData{ - YamlFilePath: "../../mock_files/mock_yamls/deployment.yaml", - }, - clientOutput: &coreV1.Service{}, + label: "Fail to create a service with invalid type", + input: "../../mock_files/mock_yamls/deployment.yaml", + namespace: "test1", + object: &coreV1.Service{}, expectedError: "contains another resource different than Service", }, { - label: "Successfully create a service", - input: &utils.ResourceData{ - YamlFilePath: "../../mock_files/mock_yamls/service.yaml", - }, - clientOutput: &coreV1.Service{ - ObjectMeta: metaV1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - }, + label: "Successfully create a service", + input: "../../mock_files/mock_yamls/service.yaml", + namespace: "test1", + object: &coreV1.Service{}, expectedResult: name, }, } for _, testCase := range testCases { - client := testclient.NewSimpleClientset(testCase.clientOutput) + client := TestKubernetesConnector{testCase.object} t.Run(testCase.label, func(t *testing.T) { - result, err := Create(testCase.input, client) + result, err := servicePlugin{}.Create(testCase.input, testCase.namespace, client) if err != nil { if testCase.expectedError == "" { t.Fatalf("Create method return an un-expected (%s)", err) @@ -86,38 +99,42 @@ func TestCreateService(t *testing.T) { } func TestListService(t *testing.T) { - namespace := "test1" testCases := []struct { label string - input string - clientOutput *coreV1.ServiceList - expectedResult []string + namespace string + object *coreV1.ServiceList + expectedResult []helm.KubernetesResource }{ { label: "Sucessfully to display an empty service list", - input: namespace, - clientOutput: &coreV1.ServiceList{}, - expectedResult: []string{}, + namespace: "test1", + object: &coreV1.ServiceList{}, + expectedResult: []helm.KubernetesResource{}, }, { - label: "Sucessfully to display a list of existing services", - input: namespace, - clientOutput: &coreV1.ServiceList{ + label: "Sucessfully to display a list of existing services", + namespace: "test1", + object: &coreV1.ServiceList{ Items: []coreV1.Service{ coreV1.Service{ ObjectMeta: metaV1.ObjectMeta{ Name: "test", - Namespace: namespace, + Namespace: "test1", }, }, }, }, - expectedResult: []string{"test"}, + expectedResult: []helm.KubernetesResource{ + { + Name: "test", + GVK: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"}, + }, + }, }, { - label: "Sucessfully display a list of existing services in default namespace", - input: "", - clientOutput: &coreV1.ServiceList{ + label: "Sucessfully display a list of existing services in default namespace", + namespace: "", + object: &coreV1.ServiceList{ Items: []coreV1.Service{ coreV1.Service{ ObjectMeta: metaV1.ObjectMeta{ @@ -128,19 +145,27 @@ func TestListService(t *testing.T) { coreV1.Service{ ObjectMeta: metaV1.ObjectMeta{ Name: "test2", - Namespace: namespace, + Namespace: "test1", }, }, }, }, - expectedResult: []string{"test"}, + expectedResult: []helm.KubernetesResource{ + { + Name: "test", + GVK: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"}, + }, + }, }, } for _, testCase := range testCases { - client := testclient.NewSimpleClientset(testCase.clientOutput) + client := TestKubernetesConnector{testCase.object} t.Run(testCase.label, func(t *testing.T) { - result, err := List(testCase.input, client) + result, err := servicePlugin{}.List(schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, testCase.namespace, client) if err != nil { t.Fatalf("List method returned an error (%s)", err) } else { @@ -158,14 +183,14 @@ func TestListService(t *testing.T) { func TestDeleteService(t *testing.T) { testCases := []struct { - label string - input map[string]string - clientOutput *coreV1.Service + label string + input map[string]string + object *coreV1.Service }{ { label: "Sucessfully to delete an existing service", input: map[string]string{"name": "test-service", "namespace": "test-namespace"}, - clientOutput: &coreV1.Service{ + object: &coreV1.Service{ ObjectMeta: metaV1.ObjectMeta{ Name: "test-service", Namespace: "test-namespace", @@ -175,7 +200,7 @@ func TestDeleteService(t *testing.T) { { label: "Sucessfully delete an existing service in default namespace", input: map[string]string{"name": "test-service", "namespace": ""}, - clientOutput: &coreV1.Service{ + object: &coreV1.Service{ ObjectMeta: metaV1.ObjectMeta{ Name: "test-service", Namespace: "default", @@ -185,9 +210,12 @@ func TestDeleteService(t *testing.T) { } for _, testCase := range testCases { - client := testclient.NewSimpleClientset(testCase.clientOutput) + client := TestKubernetesConnector{testCase.object} t.Run(testCase.label, func(t *testing.T) { - err := Delete(testCase.input["name"], testCase.input["namespace"], client) + err := servicePlugin{}.Delete(helm.KubernetesResource{ + GVK: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"}, + Name: testCase.input["name"], + }, testCase.input["namespace"], client) if err != nil { t.Fatalf("Delete method returned an error (%s)", err) } @@ -199,14 +227,14 @@ func TestGetService(t *testing.T) { testCases := []struct { label string input map[string]string - clientOutput *coreV1.Service + object *coreV1.Service expectedResult string expectedError string }{ { label: "Sucessfully to get an existing service", input: map[string]string{"name": "test-service", "namespace": "test-namespace"}, - clientOutput: &coreV1.Service{ + object: &coreV1.Service{ ObjectMeta: metaV1.ObjectMeta{ Name: "test-service", Namespace: "test-namespace", @@ -217,7 +245,7 @@ func TestGetService(t *testing.T) { { label: "Sucessfully get an existing service from default namespaces", input: map[string]string{"name": "test-service", "namespace": ""}, - clientOutput: &coreV1.Service{ + object: &coreV1.Service{ ObjectMeta: metaV1.ObjectMeta{ Name: "test-service", Namespace: "default", @@ -228,7 +256,7 @@ func TestGetService(t *testing.T) { { label: "Fail to get an non-existing namespace", input: map[string]string{"name": "test-name", "namespace": "test-namespace"}, - clientOutput: &coreV1.Service{ + object: &coreV1.Service{ ObjectMeta: metaV1.ObjectMeta{ Name: "test-service", Namespace: "default", @@ -239,9 +267,12 @@ func TestGetService(t *testing.T) { } for _, testCase := range testCases { - client := testclient.NewSimpleClientset(testCase.clientOutput) + client := TestKubernetesConnector{testCase.object} t.Run(testCase.label, func(t *testing.T) { - result, err := Get(testCase.input["name"], testCase.input["namespace"], client) + result, err := servicePlugin{}.Get(helm.KubernetesResource{ + GVK: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"}, + Name: testCase.input["name"], + }, testCase.input["namespace"], client) if err != nil { if testCase.expectedError == "" { t.Fatalf("Get method return an un-expected (%s)", err) |