From a1abd829315d72adb258da20470eaa2445cf3e32 Mon Sep 17 00:00:00 2001 From: Ritu Sood Date: Sat, 10 Nov 2018 03:54:15 +0000 Subject: 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 Signed-off-by: Victor Morales Issue-ID: MULTICLOUD-303 --- deployments/Dockerfile | 8 +- src/k8splugin/Makefile | 3 +- src/k8splugin/api/api.go | 4 +- src/k8splugin/go.mod | 1 + src/k8splugin/go.sum | 2 + src/k8splugin/krd/plugins.go | 4 +- src/k8splugin/krd/plugins_test.go | 2 +- .../mock_files/mock_plugins/mocknetworkplugin.go | 41 +++++ .../mock_files/mock_yamls/ovn4nfvk8s.yaml | 15 ++ src/k8splugin/plugins/deployment/plugin.go | 2 +- src/k8splugin/plugins/network/plugin.go | 95 ++++++++++++ src/k8splugin/plugins/network/plugin_test.go | 172 +++++++++++++++++++++ src/k8splugin/plugins/network/v1/types.go | 67 ++++++++ src/k8splugin/plugins/ovn4nfvk8s-network/plugin.go | 157 +++++++++++++++++++ .../plugins/ovn4nfvk8s-network/plugin_test.go | 147 ++++++++++++++++++ src/k8splugin/plugins/service/plugin.go | 2 +- 16 files changed, 713 insertions(+), 9 deletions(-) create mode 100644 src/k8splugin/mock_files/mock_plugins/mocknetworkplugin.go create mode 100644 src/k8splugin/mock_files/mock_yamls/ovn4nfvk8s.yaml create mode 100644 src/k8splugin/plugins/network/plugin.go create mode 100644 src/k8splugin/plugins/network/plugin_test.go create mode 100644 src/k8splugin/plugins/network/v1/types.go create mode 100644 src/k8splugin/plugins/ovn4nfvk8s-network/plugin.go create mode 100644 src/k8splugin/plugins/ovn4nfvk8s-network/plugin_test.go 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") } -- cgit 1.2.3-korg