Add UTs to plugins 31/67531/1
authorVictor Morales <victor.morales@intel.com>
Wed, 19 Sep 2018 00:19:00 +0000 (17:19 -0700)
committerVictor Morales <victor.morales@intel.com>
Wed, 19 Sep 2018 00:19:00 +0000 (17:19 -0700)
Deployment, service and namespace are plugins which offers CRUD
operations to manage their resources. They haven't implemented
Unit Tests which makes fragile to change/refactor the source code.
This change adds their corresponding Unit Tests and defines a
standard interface.

Change-Id: I1e1eb40f1a18ba33c74069a117462c8df17767ac
Signed-off-by: Victor Morales <victor.morales@intel.com>
Issue-ID: MULTICLOUD-301

13 files changed:
src/k8splugin/Gopkg.lock
src/k8splugin/Makefile
src/k8splugin/csar/parser.go
src/k8splugin/krd/plugins.go
src/k8splugin/mock_files/mock_plugins/mockplugin.go
src/k8splugin/mock_files/mock_yamls/deployment.yaml
src/k8splugin/mock_files/mock_yamls/service.yaml
src/k8splugin/plugins/deployment/plugin.go
src/k8splugin/plugins/deployment/plugin_test.go [new file with mode: 0644]
src/k8splugin/plugins/namespace/plugin.go
src/k8splugin/plugins/namespace/plugin_test.go [new file with mode: 0644]
src/k8splugin/plugins/service/plugin.go
src/k8splugin/plugins/service/plugin_test.go [new file with mode: 0644]

index 0fc9885..4ccf69e 100644 (file)
   version = "kubernetes-1.10.3"
 
 [[projects]]
-  digest = "1:0a4e3d4f41939942aa81b5900cd8332def6f529f2f286e521cc54d6d7874dbb8"
+  digest = "1:490b16761d2ded2729bf4e262eedc2d0df8b57d6f7d4f60a2893bb24be108331"
   name = "k8s.io/client-go"
   packages = [
     "discovery",
+    "discovery/fake",
     "kubernetes",
+    "kubernetes/fake",
     "kubernetes/scheme",
     "kubernetes/typed/admissionregistration/v1alpha1",
+    "kubernetes/typed/admissionregistration/v1alpha1/fake",
     "kubernetes/typed/admissionregistration/v1beta1",
+    "kubernetes/typed/admissionregistration/v1beta1/fake",
     "kubernetes/typed/apps/v1",
+    "kubernetes/typed/apps/v1/fake",
     "kubernetes/typed/apps/v1beta1",
+    "kubernetes/typed/apps/v1beta1/fake",
     "kubernetes/typed/apps/v1beta2",
+    "kubernetes/typed/apps/v1beta2/fake",
     "kubernetes/typed/authentication/v1",
+    "kubernetes/typed/authentication/v1/fake",
     "kubernetes/typed/authentication/v1beta1",
+    "kubernetes/typed/authentication/v1beta1/fake",
     "kubernetes/typed/authorization/v1",
+    "kubernetes/typed/authorization/v1/fake",
     "kubernetes/typed/authorization/v1beta1",
+    "kubernetes/typed/authorization/v1beta1/fake",
     "kubernetes/typed/autoscaling/v1",
+    "kubernetes/typed/autoscaling/v1/fake",
     "kubernetes/typed/autoscaling/v2beta1",
+    "kubernetes/typed/autoscaling/v2beta1/fake",
     "kubernetes/typed/batch/v1",
+    "kubernetes/typed/batch/v1/fake",
     "kubernetes/typed/batch/v1beta1",
+    "kubernetes/typed/batch/v1beta1/fake",
     "kubernetes/typed/batch/v2alpha1",
+    "kubernetes/typed/batch/v2alpha1/fake",
     "kubernetes/typed/certificates/v1beta1",
+    "kubernetes/typed/certificates/v1beta1/fake",
     "kubernetes/typed/core/v1",
+    "kubernetes/typed/core/v1/fake",
     "kubernetes/typed/events/v1beta1",
+    "kubernetes/typed/events/v1beta1/fake",
     "kubernetes/typed/extensions/v1beta1",
+    "kubernetes/typed/extensions/v1beta1/fake",
     "kubernetes/typed/networking/v1",
+    "kubernetes/typed/networking/v1/fake",
     "kubernetes/typed/policy/v1beta1",
+    "kubernetes/typed/policy/v1beta1/fake",
     "kubernetes/typed/rbac/v1",
+    "kubernetes/typed/rbac/v1/fake",
     "kubernetes/typed/rbac/v1alpha1",
+    "kubernetes/typed/rbac/v1alpha1/fake",
     "kubernetes/typed/rbac/v1beta1",
+    "kubernetes/typed/rbac/v1beta1/fake",
     "kubernetes/typed/scheduling/v1alpha1",
+    "kubernetes/typed/scheduling/v1alpha1/fake",
     "kubernetes/typed/settings/v1alpha1",
+    "kubernetes/typed/settings/v1alpha1/fake",
     "kubernetes/typed/storage/v1",
+    "kubernetes/typed/storage/v1/fake",
     "kubernetes/typed/storage/v1alpha1",
+    "kubernetes/typed/storage/v1alpha1/fake",
     "kubernetes/typed/storage/v1beta1",
+    "kubernetes/typed/storage/v1beta1/fake",
     "pkg/apis/clientauthentication",
     "pkg/apis/clientauthentication/v1alpha1",
     "pkg/version",
     "plugin/pkg/client/auth/exec",
     "rest",
     "rest/watch",
+    "testing",
     "tools/auth",
     "tools/clientcmd",
     "tools/clientcmd/api",
     "k8s.io/api/core/v1",
     "k8s.io/apimachinery/pkg/apis/meta/v1",
     "k8s.io/client-go/kubernetes",
+    "k8s.io/client-go/kubernetes/fake",
     "k8s.io/client-go/kubernetes/scheme",
+    "k8s.io/client-go/kubernetes/typed/apps/v1",
+    "k8s.io/client-go/kubernetes/typed/core/v1",
     "k8s.io/client-go/tools/clientcmd",
     "k8s.io/client-go/util/homedir",
   ]
index 34b5198..510ac16 100644 (file)
@@ -39,20 +39,16 @@ integration: clean
        @go build -buildmode=plugin -o ./mock_files/mock_plugins/mockplugin.so ./mock_files/mock_plugins/mockplugin.go
        @go test -v -tags 'integration' ./...
 
-.PHONY: format
 format:
        @go fmt ./...
 
-.PHONY: plugins
 plugins:
        @find plugins -type d -not -path plugins -exec sh -c "ls {}/plugin.go | xargs go build -buildmode=plugin -o $(basename {}).so" \;
 
-.PHONY: dep
 dep:
        @go get -u $(DEPENDENCIES)
        $(GOPATH)/bin/dep ensure
 
-.PHONY: clean
 clean:
        find . -name "*so" -delete
        @rm -f k8plugin
index af4546c..6cb07fc 100644 (file)
@@ -34,31 +34,31 @@ func generateExternalVNFID() string {
        return hex.EncodeToString(b)
 }
 
-func ensuresNamespace(namespace string, kubeclient *kubernetes.Clientset) error {
+func ensuresNamespace(namespace string, kubeclient kubernetes.Interface) error {
        namespacePlugin, ok := krd.LoadedPlugins["namespace"]
        if !ok {
                return pkgerrors.New("No plugin for namespace resource found")
        }
 
-       symGetNamespaceFunc, err := namespacePlugin.Lookup("GetResource")
+       symGetNamespaceFunc, err := namespacePlugin.Lookup("Get")
        if err != nil {
                return pkgerrors.Wrap(err, "Error fetching get namespace function")
        }
 
-       exists, err := symGetNamespaceFunc.(func(string, *kubernetes.Clientset) (bool, error))(
-               namespace, kubeclient)
+       ns, err := symGetNamespaceFunc.(func(string, string, kubernetes.Interface) (string, error))(
+               namespace, "", kubeclient)
        if err != nil {
                return pkgerrors.Wrap(err, "An error ocurred during the get namespace execution")
        }
 
-       if !exists {
+       if ns == "" {
                log.Println("Creating " + namespace + " namespace")
-               symGetNamespaceFunc, err := namespacePlugin.Lookup("CreateResource")
+               symGetNamespaceFunc, err := namespacePlugin.Lookup("Create")
                if err != nil {
                        return pkgerrors.Wrap(err, "Error fetching create namespace plugin")
                }
 
-               err = symGetNamespaceFunc.(func(string, *kubernetes.Clientset) error)(
+               err = symGetNamespaceFunc.(func(string, kubernetes.Interface) error)(
                        namespace, kubeclient)
                if err != nil {
                        return pkgerrors.Wrap(err, "Error creating "+namespace+" namespace")
@@ -99,10 +99,10 @@ var CreateVNF = func(csarID string, cloudRegionID string, namespace string, kube
                        }
                        log.Println("Processing file: " + path)
 
-                       genericKubeData := &krd.GenericKubeResourceData{
-                               YamlFilePath:  path,
-                               Namespace:     namespace,
-                               InternalVNFID: internalVNFID,
+                       genericKubeData := &krd.ResourceData{
+                               YamlFilePath: path,
+                               Namespace:    namespace,
+                               VnfId:        internalVNFID,
                        }
 
                        typePlugin, ok := krd.LoadedPlugins[resource]
@@ -110,12 +110,12 @@ var CreateVNF = func(csarID string, cloudRegionID string, namespace string, kube
                                return "", nil, pkgerrors.New("No plugin for resource " + resource + " found")
                        }
 
-                       symCreateResourceFunc, err := typePlugin.Lookup("CreateResource")
+                       symCreateResourceFunc, err := typePlugin.Lookup("Create")
                        if err != nil {
                                return "", nil, pkgerrors.Wrap(err, "Error fetching "+resource+" plugin")
                        }
 
-                       internalResourceName, err := symCreateResourceFunc.(func(*krd.GenericKubeResourceData, *kubernetes.Clientset) (string, error))(
+                       internalResourceName, err := symCreateResourceFunc.(func(*krd.ResourceData, kubernetes.Interface) (string, error))(
                                genericKubeData, kubeclient)
                        if err != nil {
                                return "", nil, pkgerrors.Wrap(err, "Error in plugin "+resource+" plugin")
@@ -144,7 +144,7 @@ var DestroyVNF = func(data map[string][]string, namespace string, kubeclient *ku
                        return pkgerrors.New("No plugin for resource " + resourceName + " found")
                }
 
-               symDeleteResourceFunc, err := typePlugin.Lookup("DeleteResource")
+               symDeleteResourceFunc, err := typePlugin.Lookup("Delete")
                if err != nil {
                        return pkgerrors.Wrap(err, "Error fetching "+resourceName+" plugin")
                }
@@ -153,7 +153,7 @@ var DestroyVNF = func(data map[string][]string, namespace string, kubeclient *ku
 
                        log.Println("Deleting resource: " + resourceName)
 
-                       err = symDeleteResourceFunc.(func(string, string, *kubernetes.Clientset) error)(
+                       err = symDeleteResourceFunc.(func(string, string, kubernetes.Interface) error)(
                                resourceName, namespace, kubeclient)
                        if err != nil {
                                return pkgerrors.Wrap(err, "Error destroying "+resourceName)
index 612e3f6..41b8322 100644 (file)
@@ -14,31 +14,46 @@ limitations under the License.
 package krd
 
 import (
+       "io/ioutil"
+       "log"
+       "os"
        "plugin"
 
-       appsV1 "k8s.io/api/apps/v1"
-       coreV1 "k8s.io/api/core/v1"
-       "k8s.io/client-go/kubernetes"
+       pkgerrors "github.com/pkg/errors"
+       "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/client-go/kubernetes/scheme"
 )
 
 // LoadedPlugins stores references to the stored plugins
 var LoadedPlugins = map[string]*plugin.Plugin{}
 
-// KubeResourceClient has the signature methods to create Kubernetes reources
-type KubeResourceClient interface {
-       CreateResource(GenericKubeResourceData, *kubernetes.Clientset) (string, error)
-       ListResources(string, string) (*[]string, error)
-       DeleteResource(string, string, *kubernetes.Clientset) error
-       GetResource(string, string, *kubernetes.Clientset) (string, error)
+const ResourcesListLimit = 10
+
+// ResourceData stores all supported Kubernetes plugin types
+type ResourceData struct {
+       YamlFilePath string
+       Namespace    string
+       VnfId        string
 }
 
-// GenericKubeResourceData stores all supported Kubernetes plugin types
-type GenericKubeResourceData struct {
-       YamlFilePath  string
-       Namespace     string
-       InternalVNFID string
+// DecodeYAML reads a YAMl file to extract the Kubernetes object definition
+var DecodeYAML = func(path string) (runtime.Object, error) {
+       if _, err := os.Stat(path); os.IsNotExist(err) {
+               return nil, pkgerrors.New("File " + path + " not found")
+       }
+
+       log.Println("Reading deployment YAML")
+       rawBytes, err := ioutil.ReadFile(path)
+       if err != nil {
+               return nil, pkgerrors.Wrap(err, "Deployment YAML file read error")
+       }
+
+       log.Println("Decoding deployment YAML")
+       decode := scheme.Codecs.UniversalDeserializer().Decode
+       obj, _, err := decode(rawBytes, nil, nil)
+       if err != nil {
+               return nil, pkgerrors.Wrap(err, "Deserialize deployment error")
+       }
 
-       // Add additional Kubernetes plugins below kinds
-       DeploymentData *appsV1.Deployment
-       ServiceData    *coreV1.Service
+       return obj, nil
 }
index 9ceec34..c31e4fe 100644 (file)
@@ -21,23 +21,23 @@ import (
 
 func main() {}
 
-// CreateResource object in a specific Kubernetes resource
-func CreateResource(kubedata *krd.GenericKubeResourceData, kubeclient *kubernetes.Clientset) (string, error) {
+// Create object in a specific Kubernetes resource
+func Create(data *krd.ResourceData, client kubernetes.Interface) (string, error) {
        return "externalUUID", nil
 }
 
-// ListResources of existing resources
-func ListResources(limit int64, namespace string, kubeclient *kubernetes.Clientset) (*[]string, error) {
+// List of existing resources
+func List(namespace string, client kubernetes.Interface) ([]string, error) {
        returnVal := []string{"cloud1-default-uuid1", "cloud1-default-uuid2"}
-       return &returnVal, nil
+       return returnVal, nil
 }
 
-// DeleteResource existing resources
-func DeleteResource(name string, namespace string, kubeclient *kubernetes.Clientset) error {
+// Delete existing resources
+func Delete(name string, namespace string, client kubernetes.Interface) error {
        return nil
 }
 
-// GetResource existing resource host
-func GetResource(namespace string, client *kubernetes.Clientset) (bool, error) {
-       return true, nil
+// Get existing resource host
+func Get(name string, namespace string, client kubernetes.Interface) (string, error) {
+       return name, nil
 }
index eff2fc5..49a30ef 100644 (file)
@@ -12,7 +12,7 @@
 apiVersion: apps/v1
 kind: Deployment
 metadata:
-  name: sise-deploy
+  name: mock-deployment
 spec:
   template:
     metadata:
index 297ab1b..7193894 100644 (file)
@@ -12,7 +12,7 @@
 apiVersion: v1
 kind: Service
 metadata:
-  name: sise-svc
+  name: mock-service
 spec:
   ports:
   - port: 80
index 2b4c7cb..97330b5 100644 (file)
@@ -14,55 +14,36 @@ limitations under the License.
 package main
 
 import (
-       "io/ioutil"
        "log"
-       "os"
-
-       "k8s.io/client-go/kubernetes"
 
        pkgerrors "github.com/pkg/errors"
 
        appsV1 "k8s.io/api/apps/v1"
        metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-       "k8s.io/client-go/kubernetes/scheme"
+       "k8s.io/client-go/kubernetes"
 
        "k8splugin/krd"
 )
 
-// CreateResource object in a specific Kubernetes Deployment
-func CreateResource(kubedata *krd.GenericKubeResourceData, kubeclient *kubernetes.Clientset) (string, error) {
-       if kubedata.Namespace == "" {
-               kubedata.Namespace = "default"
-       }
-
-       if _, err := os.Stat(kubedata.YamlFilePath); err != nil {
-               return "", pkgerrors.New("File " + kubedata.YamlFilePath + " not found")
-       }
-
-       log.Println("Reading deployment YAML")
-       rawBytes, err := ioutil.ReadFile(kubedata.YamlFilePath)
-       if err != nil {
-               return "", pkgerrors.Wrap(err, "Deployment YAML file read error")
+// Create deployment object in a specific Kubernetes cluster
+func Create(data *krd.ResourceData, client kubernetes.Interface) (string, error) {
+       namespace := data.Namespace
+       if namespace == "" {
+               namespace = "default"
        }
-
-       log.Println("Decoding deployment YAML")
-       decode := scheme.Codecs.UniversalDeserializer().Decode
-       obj, _, err := decode(rawBytes, nil, nil)
+       obj, err := krd.DecodeYAML(data.YamlFilePath)
        if err != nil {
-               return "", pkgerrors.Wrap(err, "Deserialize deployment error")
+               return "", pkgerrors.Wrap(err, "Decode deployment object error")
        }
 
-       switch o := obj.(type) {
-       case *appsV1.Deployment:
-               kubedata.DeploymentData = o
-       default:
-               return "", pkgerrors.New(kubedata.YamlFilePath + " contains another resource different than Deployment")
+       deployment, ok := obj.(*appsV1.Deployment)
+       if !ok {
+               return "", pkgerrors.New("Decoded object contains another resource different than Deployment")
        }
+       deployment.Namespace = namespace
+       deployment.Name = data.VnfId + "-" + deployment.Name
 
-       kubedata.DeploymentData.Namespace = kubedata.Namespace
-       kubedata.DeploymentData.Name = kubedata.InternalVNFID + "-" + kubedata.DeploymentData.Name
-
-       result, err := kubeclient.AppsV1().Deployments(kubedata.Namespace).Create(kubedata.DeploymentData)
+       result, err := client.AppsV1().Deployments(namespace).Create(deployment)
        if err != nil {
                return "", pkgerrors.Wrap(err, "Create Deployment error")
        }
@@ -70,14 +51,14 @@ func CreateResource(kubedata *krd.GenericKubeResourceData, kubeclient *kubernete
        return result.GetObjectMeta().GetName(), nil
 }
 
-// ListResources of existing deployments hosted in a specific Kubernetes Deployment
-func ListResources(limit int64, namespace string, kubeclient *kubernetes.Clientset) (*[]string, error) {
+// List of existing deployments hosted in a specific Kubernetes cluster
+func List(namespace string, kubeclient kubernetes.Interface) ([]string, error) {
        if namespace == "" {
                namespace = "default"
        }
 
        opts := metaV1.ListOptions{
-               Limit: limit,
+               Limit: krd.ResourcesListLimit,
        }
        opts.APIVersion = "apps/v1"
        opts.Kind = "Deployment"
@@ -87,38 +68,38 @@ func ListResources(limit int64, namespace string, kubeclient *kubernetes.Clients
                return nil, pkgerrors.Wrap(err, "Get Deployment list error")
        }
 
-       result := make([]string, 0, limit)
+       result := make([]string, 0, krd.ResourcesListLimit)
        if list != nil {
                for _, deployment := range list.Items {
+                       log.Printf("%v", deployment.Name)
                        result = append(result, deployment.Name)
                }
        }
 
-       return &result, nil
+       return result, nil
 }
 
-// DeleteResource existing deployments hosting in a specific Kubernetes Deployment
-func DeleteResource(name string, namespace string, kubeclient *kubernetes.Clientset) error {
+// Delete an existing deployment hosted in a specific Kubernetes cluster
+func Delete(name string, namespace string, kubeclient kubernetes.Interface) error {
        if namespace == "" {
                namespace = "default"
        }
 
-       log.Println("Deleting deployment: " + name)
-
        deletePolicy := metaV1.DeletePropagationForeground
-       err := kubeclient.AppsV1().Deployments(namespace).Delete(name, &metaV1.DeleteOptions{
+       opts := &metaV1.DeleteOptions{
                PropagationPolicy: &deletePolicy,
-       })
+       }
 
-       if err != nil {
+       log.Println("Deleting deployment: " + name)
+       if err := kubeclient.AppsV1().Deployments(namespace).Delete(name, opts); err != nil {
                return pkgerrors.Wrap(err, "Delete Deployment error")
        }
 
        return nil
 }
 
-// GetResource existing deployment hosting in a specific Kubernetes Deployment
-func GetResource(name string, namespace string, kubeclient *kubernetes.Clientset) (string, error) {
+// Get an existing deployment hosted in a specific Kubernetes cluster
+func Get(name string, namespace string, kubeclient kubernetes.Interface) (string, error) {
        if namespace == "" {
                namespace = "default"
        }
diff --git a/src/k8splugin/plugins/deployment/plugin_test.go b/src/k8splugin/plugins/deployment/plugin_test.go
new file mode 100644 (file)
index 0000000..636629a
--- /dev/null
@@ -0,0 +1,199 @@
+// +build unit
+
+/*
+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 (
+       "reflect"
+       "strings"
+       "testing"
+
+       "k8splugin/krd"
+
+       appsV1 "k8s.io/api/apps/v1"
+       metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       testclient "k8s.io/client-go/kubernetes/fake"
+)
+
+func TestCreateDeployment(t *testing.T) {
+       namespace := "test1"
+       name := "mock-deployment"
+       internalVNFID := "1"
+       testCases := []struct {
+               label          string
+               input          *krd.ResourceData
+               clientOutput   *appsV1.Deployment
+               expectedResult string
+               expectedError  string
+       }{
+               {
+                       label: "Fail to create a deployment with non-existing file",
+                       input: &krd.ResourceData{
+                               YamlFilePath: "non-existing_test_file.yaml",
+                       },
+                       clientOutput:  &appsV1.Deployment{},
+                       expectedError: "not found",
+               },
+               {
+                       label: "Fail to create a deployment with invalid type",
+                       input: &krd.ResourceData{
+                               YamlFilePath: "../../mock_files/mock_yamls/service.yaml",
+                       },
+                       clientOutput:  &appsV1.Deployment{},
+                       expectedError: "contains another resource different than Deployment",
+               },
+               {
+                       label: "Successfully create a deployment",
+                       input: &krd.ResourceData{
+                               VnfId:        internalVNFID,
+                               YamlFilePath: "../../mock_files/mock_yamls/deployment.yaml",
+                       },
+                       clientOutput: &appsV1.Deployment{
+                               ObjectMeta: metaV1.ObjectMeta{
+                                       Name:      name,
+                                       Namespace: namespace,
+                               },
+                       },
+                       expectedResult: internalVNFID + "-" + name,
+               },
+       }
+
+       for _, testCase := range testCases {
+               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               t.Run(testCase.label, func(t *testing.T) {
+                       result, err := Create(testCase.input, client)
+                       if err != nil {
+                               if !strings.Contains(string(err.Error()), testCase.expectedError) {
+                                       t.Fatalf("Create method returned an error (%s)", err)
+                               }
+                       }
+                       if !reflect.DeepEqual(testCase.expectedResult, result) {
+                               t.Fatalf("Create method returned %v and it was expected (%v)", result, testCase.expectedResult)
+                       }
+               })
+       }
+}
+
+func TestListDeployment(t *testing.T) {
+       namespace := "test1"
+       testCases := []struct {
+               label          string
+               input          string
+               clientOutput   *appsV1.DeploymentList
+               expectedResult []string
+       }{
+               {
+                       label:          "Sucessfully display an empty deployment list",
+                       input:          namespace,
+                       clientOutput:   &appsV1.DeploymentList{},
+                       expectedResult: []string{},
+               },
+               {
+                       label: "Sucessfully display a list of existing deployments",
+                       input: namespace,
+                       clientOutput: &appsV1.DeploymentList{
+                               Items: []appsV1.Deployment{
+                                       appsV1.Deployment{
+                                               ObjectMeta: metaV1.ObjectMeta{
+                                                       Name:      "test",
+                                                       Namespace: namespace,
+                                               },
+                                       },
+                               },
+                       },
+                       expectedResult: []string{"test"},
+               },
+       }
+
+       for _, testCase := range testCases {
+               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               t.Run(testCase.label, func(t *testing.T) {
+                       result, err := List(testCase.input, client)
+                       if err != nil {
+                               t.Fatalf("List method returned an error (%s)", err)
+                       }
+                       if !reflect.DeepEqual(testCase.expectedResult, result) {
+                               t.Fatalf("List method returned %v and it was expected (%v)", result, testCase.expectedResult)
+                       }
+               })
+       }
+}
+
+func TestDeleteDeployment(t *testing.T) {
+       namespace := "test1"
+       name := "mock-deployment"
+       testCases := []struct {
+               label        string
+               input        string
+               clientOutput *appsV1.Deployment
+       }{
+               {
+                       label: "Sucessfully delete an existing deployment",
+                       input: name,
+                       clientOutput: &appsV1.Deployment{
+                               ObjectMeta: metaV1.ObjectMeta{
+                                       Name:      name,
+                                       Namespace: namespace,
+                               },
+                       },
+               },
+       }
+
+       for _, testCase := range testCases {
+               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               t.Run(testCase.label, func(t *testing.T) {
+                       err := Delete(testCase.input, namespace, client)
+                       if err != nil {
+                               t.Fatalf("Delete method returned an error (%s)", err)
+                       }
+               })
+       }
+}
+
+func TestGetDeployment(t *testing.T) {
+       namespace := "test1"
+       name := "mock-deployment"
+       testCases := []struct {
+               label          string
+               input          string
+               clientOutput   *appsV1.Deployment
+               expectedResult string
+       }{
+               {
+                       label: "Sucessfully get an existing deployment",
+                       input: name,
+                       clientOutput: &appsV1.Deployment{
+                               ObjectMeta: metaV1.ObjectMeta{
+                                       Name:      name,
+                                       Namespace: namespace,
+                               },
+                       },
+                       expectedResult: name,
+               },
+       }
+
+       for _, testCase := range testCases {
+               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               t.Run(testCase.label, func(t *testing.T) {
+                       result, err := Get(testCase.input, namespace, client)
+                       if err != nil {
+                               t.Fatalf("Get method returned an error (%s)", err)
+                       }
+                       if !reflect.DeepEqual(testCase.expectedResult, result) {
+                               t.Fatalf("Get method returned %v and it was expected (%v)", result, testCase.expectedResult)
+                       }
+               })
+       }
+}
index 986de86..e29ff43 100644 (file)
@@ -14,55 +14,85 @@ limitations under the License.
 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/client-go/kubernetes"
+
+       "k8splugin/krd"
 )
 
-// CreateResource is used to create a new Namespace
-func CreateResource(namespace string, client *kubernetes.Clientset) error {
-       namespaceStruct := &coreV1.Namespace{
+// Create a namespace object in a specific Kubernetes cluster
+func Create(data *krd.ResourceData, client kubernetes.Interface) (string, error) {
+       namespace := &coreV1.Namespace{
                ObjectMeta: metaV1.ObjectMeta{
-                       Name: namespace,
+                       Name: data.Namespace,
                },
        }
-       _, err := client.CoreV1().Namespaces().Create(namespaceStruct)
+       _, err := client.CoreV1().Namespaces().Create(namespace)
        if err != nil {
-               return pkgerrors.Wrap(err, "Create Namespace error")
+               return "", pkgerrors.Wrap(err, "Create Namespace error")
        }
-       return nil
+       return data.Namespace, nil
 }
 
-// GetResource is used to check if a given namespace actually exists in Kubernetes
-func GetResource(namespace string, client *kubernetes.Clientset) (bool, error) {
+// Get an existing namespace hosted in a specific Kubernetes cluster
+func Get(name string, namespace string, client kubernetes.Interface) (string, error) {
        opts := metaV1.ListOptions{}
 
-       namespaceList, err := client.CoreV1().Namespaces().List(opts)
+       list, err := client.CoreV1().Namespaces().List(opts)
        if err != nil {
-               return false, pkgerrors.Wrap(err, "Get Namespace list error")
+               return "", pkgerrors.Wrap(err, "Get Namespace list error")
        }
 
-       for _, ns := range namespaceList.Items {
+       for _, ns := range list.Items {
                if namespace == ns.Name {
-                       return true, nil
+                       return ns.Name, nil
                }
        }
 
-       return false, nil
+       return "", nil
 }
 
-// DeleteResource is used to delete a namespace
-func DeleteResource(namespace string, client *kubernetes.Clientset) error {
+// Delete an existing namespace hosted in a specific Kubernetes cluster
+func Delete(name string, namespace string, client kubernetes.Interface) error {
        deletePolicy := metaV1.DeletePropagationForeground
-
-       err := client.CoreV1().Namespaces().Delete(namespace, &metaV1.DeleteOptions{
+       opts := &metaV1.DeleteOptions{
                PropagationPolicy: &deletePolicy,
-       })
+       }
 
-       if err != nil {
-               return pkgerrors.Wrap(err, "Delete Namespace error")
+       log.Println("Deleting namespace: " + name)
+       if err := client.CoreV1().Namespaces().Delete(name, opts); err != nil {
+               return pkgerrors.Wrap(err, "Delete namespace error")
        }
+
        return nil
 }
+
+// List of existing namespaces hosted in a specific Kubernetes cluster
+func List(namespace string, client kubernetes.Interface) ([]string, error) {
+       opts := metaV1.ListOptions{
+               Limit: krd.ResourcesListLimit,
+       }
+       opts.APIVersion = "apps/v1"
+       opts.Kind = "Namespace"
+
+       list, err := client.CoreV1().Namespaces().List(opts)
+       if err != nil {
+               return nil, pkgerrors.Wrap(err, "Get Namespace list error")
+       }
+
+       result := make([]string, 0, krd.ResourcesListLimit)
+       if list != nil {
+               for _, deployment := range list.Items {
+                       log.Printf("%v", deployment.Name)
+                       result = append(result, deployment.Name)
+               }
+       }
+
+       return result, nil
+}
diff --git a/src/k8splugin/plugins/namespace/plugin_test.go b/src/k8splugin/plugins/namespace/plugin_test.go
new file mode 100644 (file)
index 0000000..fe60404
--- /dev/null
@@ -0,0 +1,170 @@
+// +build unit
+
+/*
+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 (
+       "reflect"
+       "strings"
+       "testing"
+
+       "k8splugin/krd"
+
+       coreV1 "k8s.io/api/core/v1"
+       metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       testclient "k8s.io/client-go/kubernetes/fake"
+)
+
+func TestCreateNamespace(t *testing.T) {
+       namespace := "test1"
+       testCases := []struct {
+               label          string
+               input          *krd.ResourceData
+               clientOutput   *coreV1.Namespace
+               expectedResult string
+               expectedError  string
+       }{
+               {
+                       label: "Successfully create a namespace",
+                       input: &krd.ResourceData{
+                               Namespace: namespace,
+                       },
+                       clientOutput:   &coreV1.Namespace{},
+                       expectedResult: namespace,
+               },
+       }
+
+       for _, testCase := range testCases {
+               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               t.Run(testCase.label, func(t *testing.T) {
+                       result, err := Create(testCase.input, client)
+                       if err != nil {
+                               if !strings.Contains(string(err.Error()), testCase.expectedError) {
+                                       t.Fatalf("Create method returned an error (%s)", err)
+                               }
+                       }
+                       if !reflect.DeepEqual(testCase.expectedResult, result) {
+                               t.Fatalf("Create method returned %v and it was expected (%v)", result, testCase.expectedResult)
+                       }
+               })
+       }
+}
+
+func TestListNamespace(t *testing.T) {
+       namespace := "test1"
+       testCases := []struct {
+               label          string
+               input          string
+               clientOutput   *coreV1.NamespaceList
+               expectedResult []string
+       }{
+               {
+                       label:          "Sucessfully to display an empty namespace list",
+                       input:          namespace,
+                       clientOutput:   &coreV1.NamespaceList{},
+                       expectedResult: []string{},
+               },
+               {
+                       label: "Sucessfully to display a list of existing namespaces",
+                       input: namespace,
+                       clientOutput: &coreV1.NamespaceList{
+                               Items: []coreV1.Namespace{
+                                       coreV1.Namespace{
+                                               ObjectMeta: metaV1.ObjectMeta{
+                                                       Name: namespace,
+                                               },
+                                       },
+                               },
+                       },
+                       expectedResult: []string{namespace},
+               },
+       }
+
+       for _, testCase := range testCases {
+               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               t.Run(testCase.label, func(t *testing.T) {
+                       result, err := List(testCase.input, client)
+                       if err != nil {
+                               t.Fatalf("List method returned an error (%s)", err)
+                       }
+                       if !reflect.DeepEqual(testCase.expectedResult, result) {
+                               t.Fatalf("List method returned %v and it was expected (%v)", result, testCase.expectedResult)
+                       }
+               })
+       }
+}
+
+func TestDeleteNamespace(t *testing.T) {
+       namespace := "test1"
+       testCases := []struct {
+               label        string
+               input        string
+               clientOutput *coreV1.Namespace
+       }{
+               {
+                       label: "Sucessfully to delete an existing namespace",
+                       input: namespace,
+                       clientOutput: &coreV1.Namespace{
+                               ObjectMeta: metaV1.ObjectMeta{
+                                       Name: namespace,
+                               },
+                       },
+               },
+       }
+
+       for _, testCase := range testCases {
+               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               t.Run(testCase.label, func(t *testing.T) {
+                       err := Delete(testCase.input, namespace, client)
+                       if err != nil {
+                               t.Fatalf("Delete method returned an error (%s)", err)
+                       }
+               })
+       }
+}
+
+func TestGetNamespace(t *testing.T) {
+       namespace := "test1"
+       testCases := []struct {
+               label          string
+               input          string
+               clientOutput   *coreV1.Namespace
+               expectedResult string
+       }{
+               {
+                       label: "Sucessfully to get an existing namespace",
+                       input: namespace,
+                       clientOutput: &coreV1.Namespace{
+                               ObjectMeta: metaV1.ObjectMeta{
+                                       Name: namespace,
+                               },
+                       },
+                       expectedResult: namespace,
+               },
+       }
+
+       for _, testCase := range testCases {
+               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               t.Run(testCase.label, func(t *testing.T) {
+                       result, err := Get(testCase.input, namespace, client)
+                       if err != nil {
+                               t.Fatalf("Get method returned an error (%s)", err)
+                       }
+                       if !reflect.DeepEqual(testCase.expectedResult, result) {
+                               t.Fatalf("Get method returned %v and it was expected (%v)", result, testCase.expectedResult)
+                       }
+               })
+       }
+}
index 36ef24f..61609e9 100644 (file)
@@ -14,9 +14,7 @@ limitations under the License.
 package main
 
 import (
-       "io/ioutil"
        "log"
-       "os"
 
        "k8s.io/client-go/kubernetes"
 
@@ -24,58 +22,44 @@ import (
 
        coreV1 "k8s.io/api/core/v1"
        metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-       "k8s.io/client-go/kubernetes/scheme"
 
        "k8splugin/krd"
 )
 
-// CreateResource object in a specific Kubernetes Deployment
-func CreateResource(kubedata *krd.GenericKubeResourceData, kubeclient *kubernetes.Clientset) (string, error) {
-       if kubedata.Namespace == "" {
-               kubedata.Namespace = "default"
-       }
-
-       if _, err := os.Stat(kubedata.YamlFilePath); err != nil {
-               return "", pkgerrors.New("File " + kubedata.YamlFilePath + " not found")
-       }
-
-       log.Println("Reading service YAML")
-       rawBytes, err := ioutil.ReadFile(kubedata.YamlFilePath)
-       if err != nil {
-               return "", pkgerrors.Wrap(err, "Service YAML file read error")
+// Create a service object in a specific Kubernetes cluster
+func Create(data *krd.ResourceData, client kubernetes.Interface) (string, error) {
+       namespace := data.Namespace
+       if namespace == "" {
+               namespace = "default"
        }
-
-       log.Println("Decoding service YAML")
-       decode := scheme.Codecs.UniversalDeserializer().Decode
-       obj, _, err := decode(rawBytes, nil, nil)
+       obj, err := krd.DecodeYAML(data.YamlFilePath)
        if err != nil {
-               return "", pkgerrors.Wrap(err, "Deserialize service error")
+               return "", pkgerrors.Wrap(err, "Decode service object error")
        }
 
-       switch o := obj.(type) {
-       case *coreV1.Service:
-               kubedata.ServiceData = o
-       default:
-               return "", pkgerrors.New(kubedata.YamlFilePath + " contains another resource different than Service")
+       service, ok := obj.(*coreV1.Service)
+       if !ok {
+               return "", pkgerrors.New("Decoded object contains another resource different than Service")
        }
+       service.Namespace = namespace
+       service.Name = data.VnfId + "-" + service.Name
 
-       kubedata.ServiceData.Namespace = kubedata.Namespace
-       kubedata.ServiceData.Name = kubedata.InternalVNFID + "-" + kubedata.ServiceData.Name
-
-       result, err := kubeclient.CoreV1().Services(kubedata.Namespace).Create(kubedata.ServiceData)
+       result, err := client.CoreV1().Services(namespace).Create(service)
        if err != nil {
                return "", pkgerrors.Wrap(err, "Create Service error")
        }
+
        return result.GetObjectMeta().GetName(), nil
 }
 
-// ListResources of existing deployments hosted in a specific Kubernetes Deployment
-func ListResources(limit int64, namespace string, kubeclient *kubernetes.Clientset) (*[]string, error) {
+// List of existing services hosted in a specific Kubernetes cluster
+func List(namespace string, kubeclient kubernetes.Interface) ([]string, error) {
        if namespace == "" {
                namespace = "default"
        }
+
        opts := metaV1.ListOptions{
-               Limit: limit,
+               Limit: krd.ResourcesListLimit,
        }
        opts.APIVersion = "apps/v1"
        opts.Kind = "Service"
@@ -84,36 +68,39 @@ func ListResources(limit int64, namespace string, kubeclient *kubernetes.Clients
        if err != nil {
                return nil, pkgerrors.Wrap(err, "Get Service list error")
        }
-       result := make([]string, 0, limit)
+
+       result := make([]string, 0, krd.ResourcesListLimit)
        if list != nil {
-               for _, service := range list.Items {
-                       result = append(result, service.Name)
+               for _, deployment := range list.Items {
+                       log.Printf("%v", deployment.Name)
+                       result = append(result, deployment.Name)
                }
        }
-       return &result, nil
+
+       return result, nil
 }
 
-// DeleteResource deletes an existing Kubernetes service
-func DeleteResource(name string, namespace string, kubeclient *kubernetes.Clientset) error {
+// Delete an existing service hosted in a specific Kubernetes cluster
+func Delete(name string, namespace string, kubeclient kubernetes.Interface) error {
        if namespace == "" {
                namespace = "default"
        }
 
-       log.Println("Deleting service: " + name)
-
        deletePolicy := metaV1.DeletePropagationForeground
-       err := kubeclient.CoreV1().Services(namespace).Delete(name, &metaV1.DeleteOptions{
+       opts := &metaV1.DeleteOptions{
                PropagationPolicy: &deletePolicy,
-       })
-       if err != nil {
-               return pkgerrors.Wrap(err, "Delete Service error")
+       }
+
+       log.Println("Deleting service: " + name)
+       if err := kubeclient.CoreV1().Services(namespace).Delete(name, opts); err != nil {
+               return pkgerrors.Wrap(err, "Delete service error")
        }
 
        return nil
 }
 
-// GetResource existing service hosting in a specific Kubernetes Service
-func GetResource(name string, namespace string, kubeclient *kubernetes.Clientset) (string, error) {
+// Get an existing service hosted in a specific Kubernetes cluster
+func Get(name string, namespace string, kubeclient kubernetes.Interface) (string, error) {
        if namespace == "" {
                namespace = "default"
        }
diff --git a/src/k8splugin/plugins/service/plugin_test.go b/src/k8splugin/plugins/service/plugin_test.go
new file mode 100644 (file)
index 0000000..001467d
--- /dev/null
@@ -0,0 +1,199 @@
+// +build unit
+
+/*
+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 (
+       "reflect"
+       "strings"
+       "testing"
+
+       "k8splugin/krd"
+
+       coreV1 "k8s.io/api/core/v1"
+       metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       testclient "k8s.io/client-go/kubernetes/fake"
+)
+
+func TestCreateService(t *testing.T) {
+       namespace := "test1"
+       name := "mock-service"
+       internalVNFID := "1"
+       testCases := []struct {
+               label          string
+               input          *krd.ResourceData
+               clientOutput   *coreV1.Service
+               expectedResult string
+               expectedError  string
+       }{
+               {
+                       label: "Fail to create a service with non-existing file",
+                       input: &krd.ResourceData{
+                               YamlFilePath: "non-existing_test_file.yaml",
+                       },
+                       clientOutput:  &coreV1.Service{},
+                       expectedError: "not found",
+               },
+               {
+                       label: "Fail to create a service with invalid type",
+                       input: &krd.ResourceData{
+                               YamlFilePath: "../../mock_files/mock_yamls/deployment.yaml",
+                       },
+                       clientOutput:  &coreV1.Service{},
+                       expectedError: "contains another resource different than Service",
+               },
+               {
+                       label: "Successfully create a service",
+                       input: &krd.ResourceData{
+                               VnfId:        internalVNFID,
+                               YamlFilePath: "../../mock_files/mock_yamls/service.yaml",
+                       },
+                       clientOutput: &coreV1.Service{
+                               ObjectMeta: metaV1.ObjectMeta{
+                                       Name:      name,
+                                       Namespace: namespace,
+                               },
+                       },
+                       expectedResult: internalVNFID + "-" + name,
+               },
+       }
+
+       for _, testCase := range testCases {
+               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               t.Run(testCase.label, func(t *testing.T) {
+                       result, err := Create(testCase.input, client)
+                       if err != nil {
+                               if !strings.Contains(string(err.Error()), testCase.expectedError) {
+                                       t.Fatalf("Create method returned an error (%s)", err)
+                               }
+                       }
+                       if !reflect.DeepEqual(testCase.expectedResult, result) {
+                               t.Fatalf("Create method returned %v and it was expected (%v)", result, testCase.expectedResult)
+                       }
+               })
+       }
+}
+
+func TestListService(t *testing.T) {
+       namespace := "test1"
+       testCases := []struct {
+               label          string
+               input          string
+               clientOutput   *coreV1.ServiceList
+               expectedResult []string
+       }{
+               {
+                       label:          "Sucessfully to display an empty service list",
+                       input:          namespace,
+                       clientOutput:   &coreV1.ServiceList{},
+                       expectedResult: []string{},
+               },
+               {
+                       label: "Sucessfully to display a list of existing services",
+                       input: namespace,
+                       clientOutput: &coreV1.ServiceList{
+                               Items: []coreV1.Service{
+                                       coreV1.Service{
+                                               ObjectMeta: metaV1.ObjectMeta{
+                                                       Name:      "test",
+                                                       Namespace: namespace,
+                                               },
+                                       },
+                               },
+                       },
+                       expectedResult: []string{"test"},
+               },
+       }
+
+       for _, testCase := range testCases {
+               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               t.Run(testCase.label, func(t *testing.T) {
+                       result, err := List(testCase.input, client)
+                       if err != nil {
+                               t.Fatalf("List method returned an error (%s)", err)
+                       }
+                       if !reflect.DeepEqual(testCase.expectedResult, result) {
+                               t.Fatalf("List method returned %v and it was expected (%v)", result, testCase.expectedResult)
+                       }
+               })
+       }
+}
+
+func TestDeleteService(t *testing.T) {
+       namespace := "test1"
+       name := "mock-service"
+       testCases := []struct {
+               label        string
+               input        string
+               clientOutput *coreV1.Service
+       }{
+               {
+                       label: "Sucessfully to delete an existing service",
+                       input: name,
+                       clientOutput: &coreV1.Service{
+                               ObjectMeta: metaV1.ObjectMeta{
+                                       Name:      name,
+                                       Namespace: namespace,
+                               },
+                       },
+               },
+       }
+
+       for _, testCase := range testCases {
+               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               t.Run(testCase.label, func(t *testing.T) {
+                       err := Delete(testCase.input, namespace, client)
+                       if err != nil {
+                               t.Fatalf("Delete method returned an error (%s)", err)
+                       }
+               })
+       }
+}
+
+func TestGetService(t *testing.T) {
+       namespace := "test1"
+       name := "mock-service"
+       testCases := []struct {
+               label          string
+               input          string
+               clientOutput   *coreV1.Service
+               expectedResult string
+       }{
+               {
+                       label: "Sucessfully to get an existing service",
+                       input: name,
+                       clientOutput: &coreV1.Service{
+                               ObjectMeta: metaV1.ObjectMeta{
+                                       Name:      name,
+                                       Namespace: namespace,
+                               },
+                       },
+                       expectedResult: name,
+               },
+       }
+
+       for _, testCase := range testCases {
+               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               t.Run(testCase.label, func(t *testing.T) {
+                       result, err := Get(testCase.input, namespace, client)
+                       if err != nil {
+                               t.Fatalf("Get method returned an error (%s)", err)
+                       }
+                       if !reflect.DeepEqual(testCase.expectedResult, result) {
+                               t.Fatalf("Get method returned %v and it was expected (%v)", result, testCase.expectedResult)
+                       }
+               })
+       }
+}