summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRitu Sood <ritu.sood@intel.com>2018-11-10 03:54:15 +0000
committerVictor Morales <victor.morales@intel.com>2018-12-05 16:26:56 -0800
commita1abd829315d72adb258da20470eaa2445cf3e32 (patch)
treea1c2ae3e61490241b0a4c93f49acffd6d319a7a4
parentdaf3a00798ee77e469cd89cb16ade818c50968f9 (diff)
Add Network and OVN4NFV Plugins
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
-rw-r--r--deployments/Dockerfile8
-rw-r--r--src/k8splugin/Makefile3
-rw-r--r--src/k8splugin/api/api.go4
-rw-r--r--src/k8splugin/go.mod1
-rw-r--r--src/k8splugin/go.sum2
-rw-r--r--src/k8splugin/krd/plugins.go4
-rw-r--r--src/k8splugin/krd/plugins_test.go2
-rw-r--r--src/k8splugin/mock_files/mock_plugins/mocknetworkplugin.go41
-rw-r--r--src/k8splugin/mock_files/mock_yamls/ovn4nfvk8s.yaml15
-rw-r--r--src/k8splugin/plugins/deployment/plugin.go2
-rw-r--r--src/k8splugin/plugins/network/plugin.go95
-rw-r--r--src/k8splugin/plugins/network/plugin_test.go172
-rw-r--r--src/k8splugin/plugins/network/v1/types.go67
-rw-r--r--src/k8splugin/plugins/ovn4nfvk8s-network/plugin.go157
-rw-r--r--src/k8splugin/plugins/ovn4nfvk8s-network/plugin_test.go147
-rw-r--r--src/k8splugin/plugins/service/plugin.go2
16 files changed, 713 insertions, 9 deletions
diff --git a/deployments/Dockerfile b/deployments/Dockerfile
index 65c44b8c..770f0e8d 100644
--- a/deployments/Dockerfile
+++ b/deployments/Dockerfile
@@ -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 ./
diff --git a/src/k8splugin/Makefile b/src/k8splugin/Makefile
index 2b9f0994..751cd5f4 100644
--- a/src/k8splugin/Makefile
+++ b/src/k8splugin/Makefile
@@ -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
diff --git a/src/k8splugin/api/api.go b/src/k8splugin/api/api.go
index f05fbb0b..46afadd6 100644
--- a/src/k8splugin/api/api.go
+++ b/src/k8splugin/api/api.go
@@ -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
diff --git a/src/k8splugin/go.mod b/src/k8splugin/go.mod
index b4f4558b..d0b32af4 100644
--- a/src/k8splugin/go.mod
+++ b/src/k8splugin/go.mod
@@ -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
)
diff --git a/src/k8splugin/go.sum b/src/k8splugin/go.sum
index 4a10051a..6e46c11c 100644
--- a/src/k8splugin/go.sum
+++ b/src/k8splugin/go.sum
@@ -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=
diff --git a/src/k8splugin/krd/plugins.go b/src/k8splugin/krd/plugins.go
index 9ccb04fa..1086a2bb 100644
--- a/src/k8splugin/krd/plugins.go
+++ b/src/k8splugin/krd/plugins.go
@@ -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")
}
diff --git a/src/k8splugin/krd/plugins_test.go b/src/k8splugin/krd/plugins_test.go
index 81d2784e..46499adb 100644
--- a/src/k8splugin/krd/plugins_test.go
+++ b/src/k8splugin/krd/plugins_test.go
@@ -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
index 00000000..7169f3d6
--- /dev/null
+++ b/src/k8splugin/mock_files/mock_plugins/mocknetworkplugin.go
@@ -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
index 00000000..1a262753
--- /dev/null
+++ b/src/k8splugin/mock_files/mock_yamls/ovn4nfvk8s.yaml
@@ -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
diff --git a/src/k8splugin/plugins/deployment/plugin.go b/src/k8splugin/plugins/deployment/plugin.go
index 97330b5b..84d01a7d 100644
--- a/src/k8splugin/plugins/deployment/plugin.go
+++ b/src/k8splugin/plugins/deployment/plugin.go
@@ -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
index 00000000..d54fc429
--- /dev/null
+++ b/src/k8splugin/plugins/network/plugin.go
@@ -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
index 00000000..325de31f
--- /dev/null
+++ b/src/k8splugin/plugins/network/plugin_test.go
@@ -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
index 00000000..b4efa39a
--- /dev/null
+++ b/src/k8splugin/plugins/network/v1/types.go
@@ -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
index 00000000..959586bc
--- /dev/null
+++ b/src/k8splugin/plugins/ovn4nfvk8s-network/plugin.go
@@ -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
index 00000000..b6b28ea1
--- /dev/null
+++ b/src/k8splugin/plugins/ovn4nfvk8s-network/plugin_test.go
@@ -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)
+ }
+ }
+ })
+ }
+}
diff --git a/src/k8splugin/plugins/service/plugin.go b/src/k8splugin/plugins/service/plugin.go
index 61609e98..69acb348 100644
--- a/src/k8splugin/plugins/service/plugin.go
+++ b/src/k8splugin/plugins/service/plugin.go
@@ -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")
}