Add Network and OVN4NFV Plugins 77/72177/5
authorRitu Sood <ritu.sood@intel.com>
Sat, 10 Nov 2018 03:54:15 +0000 (03:54 +0000)
committerVictor Morales <victor.morales@intel.com>
Thu, 6 Dec 2018 00:26:56 +0000 (16:26 -0800)
This patch includes support for Network Objects through a new plugin.
It also add the first sub-module plugin for OVN4NFVK8s support.

Change-Id: Ia23c42d50f75a5206e1b6a04052c34e940518428
Signed-off-by: Ritu Sood <ritu.sood@intel.com>
Signed-off-by: Victor Morales <victor.morales@intel.com>
Issue-ID: MULTICLOUD-303

16 files changed:
deployments/Dockerfile
src/k8splugin/Makefile
src/k8splugin/api/api.go
src/k8splugin/go.mod
src/k8splugin/go.sum
src/k8splugin/krd/plugins.go
src/k8splugin/krd/plugins_test.go
src/k8splugin/mock_files/mock_plugins/mocknetworkplugin.go [new file with mode: 0644]
src/k8splugin/mock_files/mock_yamls/ovn4nfvk8s.yaml [new file with mode: 0644]
src/k8splugin/plugins/deployment/plugin.go
src/k8splugin/plugins/network/plugin.go [new file with mode: 0644]
src/k8splugin/plugins/network/plugin_test.go [new file with mode: 0644]
src/k8splugin/plugins/network/v1/types.go [new file with mode: 0644]
src/k8splugin/plugins/ovn4nfvk8s-network/plugin.go [new file with mode: 0644]
src/k8splugin/plugins/ovn4nfvk8s-network/plugin_test.go [new file with mode: 0644]
src/k8splugin/plugins/service/plugin.go

index 65c44b8..770f0e8 100644 (file)
@@ -7,7 +7,7 @@
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
 
-FROM debian:jessie
+FROM ubuntu:16.04
 
 ARG HTTP_PROXY=${HTTP_PROXY}
 ARG HTTPS_PROXY=${HTTPS_PROXY}
@@ -20,9 +20,15 @@ ENV CSAR_DIR "/opt/csar"
 ENV KUBE_CONFIG_DIR "/opt/kubeconfig"
 ENV DATABASE_TYPE "consul"
 ENV DATABASE_IP "127.0.0.1"
+ENV OVN_CENTRAL_ADDRESS "127.0.0.1:6641"
 
 EXPOSE 8081
 
+RUN apt-get update && apt-get install -y -qq apt-transport-https curl \
+ && echo "deb https://packages.wand.net.nz xenial main" > /etc/apt/sources.list.d/wand.list \
+ && curl https://packages.wand.net.nz/keyring.gpg -o /etc/apt/trusted.gpg.d/wand.gpg \
+ && apt-get update && apt install -y -qq ovn-common
+
 WORKDIR /opt/multicloud/k8s
 ADD ./k8plugin ./
 ADD ./*.so ./
index 2b9f099..751cd5f 100644 (file)
@@ -37,13 +37,14 @@ unit:
 .PHONY: integration
 integration: clean
        @go build -buildmode=plugin -o ./mock_files/mock_plugins/mockplugin.so ./mock_files/mock_plugins/mockplugin.go
+       @go build -buildmode=plugin -o ./mock_files/mock_plugins/mocknetworkplugin.so ./mock_files/mock_plugins/mocknetworkplugin.go
        @go test -v -tags 'integration' ./...
 
 format:
        @go fmt ./...
 
 plugins:
-       @find plugins -type d -not -path plugins -exec sh -c "ls {}/plugin.go | xargs go build -buildmode=plugin -o $(basename {}).so" \;
+       @find plugins -maxdepth 1 -type d -not -path plugins -exec sh -c "ls {}/plugin.go | xargs go build -buildmode=plugin -o $(basename {}).so" \;
 
 clean:
        find . -name "*so" -delete
index f05fbb0..46afadd 100644 (file)
@@ -29,7 +29,8 @@ import (
 
 // CheckEnvVariables checks for required Environment variables
 func CheckEnvVariables() error {
-       envList := []string{"CSAR_DIR", "KUBE_CONFIG_DIR", "PLUGINS_DIR", "DATABASE_TYPE", "DATABASE_IP"}
+       envList := []string{"CSAR_DIR", "KUBE_CONFIG_DIR", "PLUGINS_DIR",
+               "DATABASE_TYPE", "DATABASE_IP", "OVN_CENTRAL_ADDRESS"}
        for _, env := range envList {
                if _, ok := os.LookupEnv(env); !ok {
                        return pkgerrors.New("environment variable " + env + " not set")
@@ -64,7 +65,6 @@ func LoadPlugins() error {
                                if err != nil {
                                        return pkgerrors.Cause(err)
                                }
-
                                krd.LoadedPlugins[info.Name()[:len(info.Name())-3]] = p
                        }
                        return err
index b4f4558..d0b32af 100644 (file)
@@ -34,4 +34,5 @@ require (
        k8s.io/api v0.0.0-20180607235014-72d6e4405f81
        k8s.io/apimachinery v0.0.0-20180515182440-31dade610c05
        k8s.io/client-go v7.0.0+incompatible
+       k8s.io/utils v0.0.0-20181102055113-1bd4f387aa67
 )
index 4a10051..6e46c11 100644 (file)
@@ -64,3 +64,5 @@ k8s.io/apimachinery v0.0.0-20180515182440-31dade610c05 h1:IxbzCht0hGNBVprna3ou1l
 k8s.io/apimachinery v0.0.0-20180515182440-31dade610c05/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
 k8s.io/client-go v7.0.0+incompatible h1:gokIETH5yPpln/LuXmg1TLVH5bMSaVQTVxuRizwjWwU=
 k8s.io/client-go v7.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
+k8s.io/utils v0.0.0-20181102055113-1bd4f387aa67 h1:+kBMW7D4cSYIhPz0fVs6NRp5QILMz6+65ec4kWJOoXs=
+k8s.io/utils v0.0.0-20181102055113-1bd4f387aa67/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
index 9ccb04f..1086a2b 100644 (file)
@@ -37,7 +37,7 @@ type ResourceData struct {
 }
 
 // DecodeYAML reads a YAMl file to extract the Kubernetes object definition
-var DecodeYAML = func(path string) (runtime.Object, error) {
+var DecodeYAML = func(path string, into runtime.Object) (runtime.Object, error) {
        if _, err := os.Stat(path); err != nil {
                if os.IsNotExist(err) {
                        return nil, pkgerrors.New("File " + path + " not found")
@@ -54,7 +54,7 @@ var DecodeYAML = func(path string) (runtime.Object, error) {
 
        log.Println("Decoding deployment YAML")
        decode := scheme.Codecs.UniversalDeserializer().Decode
-       obj, _, err := decode(rawBytes, nil, nil)
+       obj, _, err := decode(rawBytes, nil, into)
        if err != nil {
                return nil, pkgerrors.Wrap(err, "Deserialize YAML error")
        }
index 81d2784..46499ad 100644 (file)
@@ -70,7 +70,7 @@ func TestDecodeYAML(t *testing.T) {
 
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
-                       result, err := DecodeYAML(testCase.input)
+                       result, err := DecodeYAML(testCase.input, nil)
                        if err != nil {
                                if testCase.expectedError == "" {
                                        t.Fatalf("Decode YAML method return an un-expected (%s)", err)
diff --git a/src/k8splugin/mock_files/mock_plugins/mocknetworkplugin.go b/src/k8splugin/mock_files/mock_plugins/mocknetworkplugin.go
new file mode 100644 (file)
index 0000000..7169f3d
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+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 (
+       pkgerrors "github.com/pkg/errors"
+       "k8splugin/plugins/network/v1"
+)
+
+// Err is the error message to be sent during functional testing
+var Err string
+
+// NetworkName is the output used for functional tests
+var NetworkName string
+
+// CreateNetwork resource
+func CreateNetwork(network *v1.OnapNetwork) (string, error) {
+       if Err != "" {
+               return "", pkgerrors.New(Err)
+       }
+       return NetworkName, nil
+}
+
+// DeleteNetwork resource
+func DeleteNetwork(name string) error {
+       if Err != "" {
+               return pkgerrors.New(Err)
+       }
+       return nil
+}
diff --git a/src/k8splugin/mock_files/mock_yamls/ovn4nfvk8s.yaml b/src/k8splugin/mock_files/mock_yamls/ovn4nfvk8s.yaml
new file mode 100644 (file)
index 0000000..1a26275
--- /dev/null
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: OnapNetwork
+metadata:
+  name: ovn-priv-net
+spec:
+  config: '{
+         "cnitype": "ovn4nfvk8s",
+         "name": "mynet",
+         "subnet": "172.16.33.0/24",
+         "gateway": "172.16.33.1",
+         "routes": [{
+                 "dst": "172.16.29.1/24",
+                 "gw": "100.64.1.1"
+         }]
+  }'
\ No newline at end of file
index 97330b5..84d01a7 100644 (file)
@@ -31,7 +31,7 @@ func Create(data *krd.ResourceData, client kubernetes.Interface) (string, error)
        if namespace == "" {
                namespace = "default"
        }
-       obj, err := krd.DecodeYAML(data.YamlFilePath)
+       obj, err := krd.DecodeYAML(data.YamlFilePath, nil)
        if err != nil {
                return "", pkgerrors.Wrap(err, "Decode deployment object error")
        }
diff --git a/src/k8splugin/plugins/network/plugin.go b/src/k8splugin/plugins/network/plugin.go
new file mode 100644 (file)
index 0000000..d54fc42
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+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 (
+       pkgerrors "github.com/pkg/errors"
+       "k8s.io/client-go/kubernetes"
+       "k8splugin/krd"
+       "k8splugin/plugins/network/v1"
+       "regexp"
+)
+
+func extractData(data string) (vnfID, 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 *krd.ResourceData, client kubernetes.Interface) (string, error) {
+       network := &v1.OnapNetwork{}
+       if _, err := krd.DecodeYAML(data.YamlFilePath, network); err != nil {
+               return "", pkgerrors.Wrap(err, "Decode network object error")
+       }
+
+       config, err := network.DecodeConfig()
+       if err != nil {
+               return "", pkgerrors.Wrap(err, "Fail to decode network's configuration")
+       }
+
+       cniType := config["cnitype"].(string)
+       typePlugin, ok := krd.LoadedPlugins[cniType+"-network"]
+       if !ok {
+               return "", pkgerrors.New("No plugin for resource " + cniType + " found")
+       }
+
+       symCreateNetworkFunc, err := typePlugin.Lookup("CreateNetwork")
+       if err != nil {
+               return "", pkgerrors.Wrap(err, "Error fetching "+cniType+" plugin")
+       }
+
+       name, err := symCreateNetworkFunc.(func(*v1.OnapNetwork) (string, error))(network)
+       if err != nil {
+               return "", pkgerrors.Wrap(err, "Error during the creation for "+cniType+" plugin")
+       }
+
+       return data.VnfId + "_" + cniType + "_" + name, nil
+}
+
+// List of Networks
+func List(namespace string, kubeclient kubernetes.Interface) ([]string, error) {
+       return nil, nil
+}
+
+// Delete an existing Network
+func Delete(name string, namespace string, kubeclient kubernetes.Interface) error {
+       _, cniType, networkName := extractData(name)
+       typePlugin, ok := krd.LoadedPlugins[cniType+"-network"]
+       if !ok {
+               return pkgerrors.New("No plugin for resource " + cniType + " found")
+       }
+
+       symDeleteNetworkFunc, err := typePlugin.Lookup("DeleteNetwork")
+       if err != nil {
+               return pkgerrors.Wrap(err, "Error fetching "+cniType+" plugin")
+       }
+
+       if err := symDeleteNetworkFunc.(func(string) error)(networkName); err != nil {
+               return pkgerrors.Wrap(err, "Error during the deletion for "+cniType+" plugin")
+       }
+
+       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
new file mode 100644 (file)
index 0000000..325de31
--- /dev/null
@@ -0,0 +1,172 @@
+// +build integration
+
+/*
+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 (
+       pkgerrors "github.com/pkg/errors"
+       "k8splugin/krd"
+       "os"
+       "plugin"
+       "reflect"
+       "strings"
+       "testing"
+)
+
+func LoadMockNetworkPlugins(krdLoadedPlugins *map[string]*plugin.Plugin, networkName, errMsg string) error {
+       if _, err := os.Stat("../../mock_files/mock_plugins/mocknetworkplugin.so"); os.IsNotExist(err) {
+               return pkgerrors.New("mocknetworkplugin.so does not exist. Please compile mocknetworkplugin.go to generate")
+       }
+
+       mockNetworkPlugin, err := plugin.Open("../../mock_files/mock_plugins/mocknetworkplugin.so")
+       if err != nil {
+               return pkgerrors.Cause(err)
+       }
+
+       symErrVar, err := mockNetworkPlugin.Lookup("Err")
+       if err != nil {
+               return err
+       }
+       symNetworkNameVar, err := mockNetworkPlugin.Lookup("NetworkName")
+       if err != nil {
+               return err
+       }
+
+       *symErrVar.(*string) = errMsg
+       *symNetworkNameVar.(*string) = networkName
+       (*krdLoadedPlugins)["ovn4nfvk8s-network"] = mockNetworkPlugin
+
+       return nil
+}
+
+func TestCreateNetwork(t *testing.T) {
+       internalVNFID := "1"
+       oldkrdPluginData := krd.LoadedPlugins
+
+       defer func() {
+               krd.LoadedPlugins = oldkrdPluginData
+       }()
+
+       testCases := []struct {
+               label          string
+               input          *krd.ResourceData
+               mockError      string
+               mockOutput     string
+               expectedResult string
+               expectedError  string
+       }{
+               {
+                       label: "Fail to decode a network object",
+                       input: &krd.ResourceData{
+                               YamlFilePath: "../../mock_files/mock_yamls/service.yaml",
+                       },
+                       expectedError: "Fail to decode network's configuration: Invalid configuration value",
+               },
+               {
+                       label: "Fail to create a network",
+                       input: &krd.ResourceData{
+                               YamlFilePath: "../../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: &krd.ResourceData{
+                               VnfId:        internalVNFID,
+                               YamlFilePath: "../../mock_files/mock_yamls/ovn4nfvk8s.yaml",
+                       },
+                       expectedResult: internalVNFID + "_ovn4nfvk8s_myNetwork",
+                       mockOutput:     "myNetwork",
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       err := LoadMockNetworkPlugins(&krd.LoadedPlugins, testCase.mockOutput, testCase.mockError)
+                       if err != nil {
+                               t.Fatalf("TestCreateNetwork returned an error (%s)", err)
+                       }
+                       result, err := Create(testCase.input, nil)
+                       if err != nil {
+                               if testCase.expectedError == "" {
+                                       t.Fatalf("Create method return an un-expected (%s)", err)
+                               }
+                               if !strings.Contains(string(err.Error()), testCase.expectedError) {
+                                       t.Fatalf("Create method returned an error (%s)", err)
+                               }
+                       } else {
+                               if testCase.expectedError != "" && testCase.expectedResult == "" {
+                                       t.Fatalf("Create method was expecting \"%s\" error message", testCase.expectedError)
+                               }
+                               if !reflect.DeepEqual(testCase.expectedResult, result) {
+
+                                       t.Fatalf("Create method returned: \n%v\n and it was expected: \n%v", result, testCase.expectedResult)
+                               }
+                       }
+               })
+       }
+}
+
+func TestDeleteNetwork(t *testing.T) {
+       oldkrdPluginData := krd.LoadedPlugins
+
+       defer func() {
+               krd.LoadedPlugins = oldkrdPluginData
+       }()
+
+       testCases := []struct {
+               label          string
+               input          string
+               mockError      string
+               mockOutput     string
+               expectedResult string
+               expectedError  string
+       }{
+               {
+                       label:         "Fail to load non-existing plugin",
+                       input:         "test",
+                       expectedError: "No plugin for resource",
+               },
+               {
+                       label:         "Fail to delete a network",
+                       input:         "1_ovn4nfvk8s_test",
+                       mockError:     "Internal error",
+                       expectedError: "Error during the deletion for ovn4nfvk8s plugin: Internal error",
+               },
+               {
+                       label: "Successfully delete a ovn4nfv network",
+                       input: "1_ovn4nfvk8s_test",
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       err := LoadMockNetworkPlugins(&krd.LoadedPlugins, testCase.mockOutput, testCase.mockError)
+                       if err != nil {
+                               t.Fatalf("TestDeleteNetwork returned an error (%s)", err)
+                       }
+                       err = Delete(testCase.input, "", nil)
+                       if err != nil {
+                               if testCase.expectedError == "" {
+                                       t.Fatalf("Create method return an un-expected (%s)", err)
+                               }
+                               if !strings.Contains(string(err.Error()), testCase.expectedError) {
+                                       t.Fatalf("Create method returned an error (%s)", err)
+                               }
+                       }
+               })
+       }
+}
diff --git a/src/k8splugin/plugins/network/v1/types.go b/src/k8splugin/plugins/network/v1/types.go
new file mode 100644 (file)
index 0000000..b4efa39
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+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 v1
+
+import (
+       "encoding/json"
+
+       pkgerrors "github.com/pkg/errors"
+       metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// OnapNetwork describes an ONAP network resouce
+type OnapNetwork struct {
+       metaV1.TypeMeta   `json:",inline"`
+       metaV1.ObjectMeta `json:"metadata,omitempty"`
+       Spec              OnapNetworkSpec `json:"spec"`
+}
+
+// OnapNetworkSpec is the spec for OnapNetwork resource
+type OnapNetworkSpec struct {
+       Config string `json:"config"`
+}
+
+// DeepCopyObject returns a generically typed copy of an object
+func (in OnapNetwork) DeepCopyObject() runtime.Object {
+       out := OnapNetwork{}
+       out.TypeMeta = in.TypeMeta
+       out.ObjectMeta = in.ObjectMeta
+       out.Spec = in.Spec
+
+       return &out
+}
+
+// GetObjectKind
+func (in OnapNetwork) GetObjectKind() schema.ObjectKind {
+       return &in.TypeMeta
+}
+
+// DecodeConfig content
+func (in OnapNetwork) DecodeConfig() (map[string]interface{}, error) {
+       var raw map[string]interface{}
+
+       if in.Spec.Config == "" {
+               return nil, pkgerrors.New("Invalid configuration value")
+       }
+
+       if err := json.Unmarshal([]byte(in.Spec.Config), &raw); err != nil {
+               return nil, pkgerrors.Wrap(err, "JSON unmarshalling error")
+       }
+
+       return raw, nil
+}
diff --git a/src/k8splugin/plugins/ovn4nfvk8s-network/plugin.go b/src/k8splugin/plugins/ovn4nfvk8s-network/plugin.go
new file mode 100644 (file)
index 0000000..959586b
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+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 (
+       "bytes"
+       "fmt"
+       kexec "k8s.io/utils/exec"
+       "os"
+
+       pkgerrors "github.com/pkg/errors"
+       "k8splugin/plugins/network/v1"
+       "log"
+       "strings"
+       "unicode"
+
+       "math/rand"
+       "time"
+)
+
+const (
+       ovn4nfvRouter   = "ovn4nfv-master"
+       ovnNbctlCommand = "ovn-nbctl"
+)
+
+type OVNNbctler interface {
+       Run(args ...string) (string, string, error)
+}
+
+type OVNNbctl struct {
+       run  func(args ...string) (string, string, error)
+       exec kexec.Interface
+       path string
+}
+
+// Run a command via ovn-nbctl
+func (ctl *OVNNbctl) Run(args ...string) (string, string, error) {
+       if ctl.path == "" {
+               nbctlPath, err := ctl.exec.LookPath(ovnNbctlCommand)
+               if err != nil {
+                       return "", "", pkgerrors.Wrap(err, "Look nbctl path error")
+               }
+               ctl.path = nbctlPath
+       }
+       if ctl.exec == nil {
+               ctl.exec = kexec.New()
+       }
+
+       stdout := &bytes.Buffer{}
+       stderr := &bytes.Buffer{}
+       cmd := ctl.exec.Command(ctl.path, args...)
+       cmd.SetStdout(stdout)
+       cmd.SetStderr(stderr)
+       err := cmd.Run()
+
+       return strings.Trim(strings.TrimFunc(stdout.String(), unicode.IsSpace), "\""),
+               stderr.String(), err
+}
+
+var ovnCmd OVNNbctler
+
+func init() {
+       ovnCmd = &OVNNbctl{}
+}
+
+// CreateNetwork in OVN controller
+func CreateNetwork(network *v1.OnapNetwork) (string, error) {
+       config, err := network.DecodeConfig()
+       if err != nil {
+               return "", err
+       }
+
+       name := config["name"].(string)
+       if name == "" {
+               return "", pkgerrors.New("Empty Name value")
+       }
+
+       subnet := config["subnet"].(string)
+       if subnet == "" {
+               return "", pkgerrors.New("Empty Subnet value")
+       }
+
+       gatewayIPMask := config["gateway"].(string)
+       if gatewayIPMask == "" {
+               return "", pkgerrors.New("Empty Gateway IP Mask")
+       }
+
+       routerMac, stderr, err := ovnCmd.Run(getAuthStr(), "--if-exist", "-v", "get", "logical_router_port", "rtos-"+name, "mac")
+       if err != nil {
+               return "", pkgerrors.Wrapf(err, "Failed to get logical router port,stderr: %q, error: %v", stderr, err)
+       }
+
+       if routerMac == "" {
+               log.Print("Generate MAC address")
+               prefix := "00:00:00"
+               newRand := rand.New(rand.NewSource(time.Now().UnixNano()))
+               routerMac = fmt.Sprintf("%s:%02x:%02x:%02x", prefix, newRand.Intn(255), newRand.Intn(255), newRand.Intn(255))
+       }
+
+       _, stderr, err = ovnCmd.Run(getAuthStr(), "--may-exist", "lrp-add", ovn4nfvRouter, "rtos-"+name, routerMac, gatewayIPMask)
+       if err != nil {
+               return "", pkgerrors.Wrapf(err, "Failed to add logical port to router, stderr: %q, error: %v", stderr, err)
+       }
+
+       // Create a logical switch and set its subnet.
+       stdout, stderr, err := ovnCmd.Run(getAuthStr(), "--", "--may-exist", "ls-add", name, "--", "set", "logical_switch", name, "other-config:subnet="+subnet, "external-ids:gateway_ip="+gatewayIPMask)
+       if err != nil {
+               return "", pkgerrors.Wrapf(err, "Failed to create a logical switch %v, stdout: %q, stderr: %q, error: %v", name, stdout, stderr, err)
+       }
+
+       // Connect the switch to the router.
+       stdout, stderr, err = ovnCmd.Run(getAuthStr(), "--", "--may-exist", "lsp-add", name, "stor-"+name, "--", "set", "logical_switch_port", "stor-"+name, "type=router", "options:router-port=rtos-"+name, "addresses="+"\""+routerMac+"\"")
+       if err != nil {
+               return "", pkgerrors.Wrapf(err, "Failed to add logical port to switch, stdout: %q, stderr: %q, error: %v", stdout, stderr, err)
+       }
+
+       return name, nil
+}
+
+// DeleteNetwork in OVN controller
+func DeleteNetwork(name string) error {
+       log.Printf("Deleting Network: Ovn4nfvk8s %s", name)
+
+       stdout, stderr, err := ovnCmd.Run(getAuthStr(), "--if-exist", "ls-del", name)
+       if err != nil {
+               return pkgerrors.Wrapf(err, "Failed to delete switch %v, stdout: %q, stderr: %q, error: %v", name, stdout, stderr, err)
+       }
+
+       stdout, stderr, err = ovnCmd.Run(getAuthStr(), "--if-exist", "lrp-del", "rtos-"+name)
+       if err != nil {
+               return pkgerrors.Wrapf(err, "Failed to delete router port %v, stdout: %q, stderr: %q, error: %v", name, stdout, stderr, err)
+       }
+
+       stdout, stderr, err = ovnCmd.Run(getAuthStr(), "--if-exist", "lsp-del", "stor-"+name)
+       if err != nil {
+               return pkgerrors.Wrapf(err, "Failed to delete switch port %v, stdout: %q, stderr: %q, error: %v", name, stdout, stderr, err)
+       }
+
+       return nil
+}
+
+func getAuthStr() string {
+       //TODO: Remove hardcoding: Use ESR data passed to Initialize
+       ovnCentralAddress := os.Getenv("OVN_CENTRAL_ADDRESS")
+       return "--db=tcp:" + ovnCentralAddress
+}
diff --git a/src/k8splugin/plugins/ovn4nfvk8s-network/plugin_test.go b/src/k8splugin/plugins/ovn4nfvk8s-network/plugin_test.go
new file mode 100644 (file)
index 0000000..b6b28ea
--- /dev/null
@@ -0,0 +1,147 @@
+// +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 (
+       pkgerrors "github.com/pkg/errors"
+       metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8splugin/plugins/network/v1"
+       "reflect"
+       "strings"
+       "testing"
+)
+
+type mockOVNCmd struct {
+       StdOut string
+       StdErr string
+       Err    error
+}
+
+func (cmd *mockOVNCmd) Run(args ...string) (string, string, error) {
+       return cmd.StdOut, cmd.StdErr, cmd.Err
+}
+
+func TestCreateOVN4NFVK8SNetwork(t *testing.T) {
+       testCases := []struct {
+               label          string
+               input          *v1.OnapNetwork
+               mock           *mockOVNCmd
+               expectedResult string
+               expectedError  string
+       }{
+               {
+                       label:         "Fail to decode a network",
+                       input:         &v1.OnapNetwork{},
+                       expectedError: "Invalid configuration value",
+               },
+               {
+                       label: "Fail to create a network",
+                       input: &v1.OnapNetwork{
+                               ObjectMeta: metaV1.ObjectMeta{
+                                       Name: "test",
+                               },
+                               Spec: v1.OnapNetworkSpec{
+                                       Config: "{\"cnitype\": \"ovn4nfvk8s\",\"name\": \"mynet\",\"subnet\": \"172.16.33.0/24\",\"gateway\": \"172.16.33.1\",\"routes\": [{\"dst\": \"172.16.29.1/24\",\"gw\": \"100.64.1.1\"}]}",
+                               },
+                       },
+                       expectedError: "Failed to get logical router",
+                       mock: &mockOVNCmd{
+                               Err: pkgerrors.New("Internal error"),
+                       },
+               },
+               {
+                       label: "Successfully create a ovn4nfv network",
+                       input: &v1.OnapNetwork{
+                               ObjectMeta: metaV1.ObjectMeta{
+                                       Name: "test",
+                               },
+                               Spec: v1.OnapNetworkSpec{
+                                       Config: "{\"cnitype\": \"ovn4nfvk8s\",\"name\": \"mynet\",\"subnet\": \"172.16.33.0/24\",\"gateway\": \"172.16.33.1\",\"routes\": [{\"dst\": \"172.16.29.1/24\",\"gw\": \"100.64.1.1\"}]}",
+                               },
+                       },
+                       expectedResult: "mynet",
+                       mock:           &mockOVNCmd{},
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       if testCase.mock != nil {
+                               ovnCmd = testCase.mock
+                       }
+                       result, err := CreateNetwork(testCase.input)
+                       if err != nil {
+                               if testCase.expectedError == "" {
+                                       t.Fatalf("CreateNetwork method return an un-expected (%s)", err)
+                               }
+                               if !strings.Contains(string(err.Error()), testCase.expectedError) {
+                                       t.Fatalf("CreateNetwork method returned an error (%s)", err)
+                               }
+                       } else {
+                               if testCase.expectedError != "" && testCase.expectedResult == "" {
+                                       t.Fatalf("CreateNetwork method was expecting \"%s\" error message", testCase.expectedError)
+                               }
+                               if result == "" {
+                                       t.Fatal("CreateNetwork method returned nil result")
+                               }
+                               if !reflect.DeepEqual(testCase.expectedResult, result) {
+
+                                       t.Fatalf("CreateNetwork method returned: \n%v\n and it was expected: \n%v", result, testCase.expectedResult)
+                               }
+                       }
+               })
+       }
+}
+
+func TestDeleteOVN4NFVK8SNetwork(t *testing.T) {
+       testCases := []struct {
+               label         string
+               input         string
+               mock          *mockOVNCmd
+               expectedError string
+       }{
+               {
+                       label:         "Fail to delete a network",
+                       input:         "test",
+                       expectedError: "Failed to delete switch test",
+                       mock: &mockOVNCmd{
+                               Err: pkgerrors.New("Internal error"),
+                       },
+               },
+               {
+                       label: "Successfully delete a ovn4nfv network",
+                       input: "test",
+                       mock:  &mockOVNCmd{},
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       if testCase.mock != nil {
+                               ovnCmd = testCase.mock
+                       }
+                       err := DeleteNetwork(testCase.input)
+                       if err != nil {
+                               if testCase.expectedError == "" {
+                                       t.Fatalf("DeleteNetwork method return an un-expected (%s)", err)
+                               }
+                               if !strings.Contains(string(err.Error()), testCase.expectedError) {
+                                       t.Fatalf("DeleteNetwork method returned an error (%s)", err)
+                               }
+                       }
+               })
+       }
+}
index 61609e9..69acb34 100644 (file)
@@ -32,7 +32,7 @@ func Create(data *krd.ResourceData, client kubernetes.Interface) (string, error)
        if namespace == "" {
                namespace = "default"
        }
-       obj, err := krd.DecodeYAML(data.YamlFilePath)
+       obj, err := krd.DecodeYAML(data.YamlFilePath, nil)
        if err != nil {
                return "", pkgerrors.Wrap(err, "Decode service object error")
        }