diff --git a/README.md b/README.md index 1533b4c49..cc1e2c63a 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ metadata: k8s.v1.cni.cncf.io/resourceName: ovs-cni.network.kubevirt.io/br1 spec: config: '{ - "cniVersion": "0.3.1", + "cniVersion": "0.4.0", "type": "ovs", "bridge": "br1", "vlan": 100 diff --git a/cmd/plugin/main.go b/cmd/plugin/main.go index fa8b69d67..50c9efd91 100644 --- a/cmd/plugin/main.go +++ b/cmd/plugin/main.go @@ -23,5 +23,5 @@ import ( ) func main() { - skel.PluginMain(plugin.CmdAdd, plugin.CmdCheck, plugin.CmdDel, version.All, buildversion.BuildString("OVS bridge")) + skel.PluginMain(plugin.CmdAdd, plugin.CmdCheck, plugin.CmdDel, version.PluginSupports("0.3.0", "0.3.1", "0.4.0"), buildversion.BuildString("OVS bridge")) } diff --git a/docs/cni-plugin.md b/docs/cni-plugin.md index 7adaf5687..233c87c41 100644 --- a/docs/cni-plugin.md +++ b/docs/cni-plugin.md @@ -102,7 +102,7 @@ ovs-vsctl add-br br1 # Run ADD command connecting the namespace to the bridge cat < 0 { + for i := range trunksCol { + trunks = append(trunks, uint(trunksCol[i].(float64))) + } + } + + return vlanMode, tag, trunks, nil +} + // IsBridgePresent Check if the bridge entry already exists func (ovsd *OvsDriver) IsBridgePresent(bridgeName string) (bool, error) { condition := ovsdb.NewCondition("name", ovsdb.ConditionEqual, bridgeName) diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index 698a66cde..443ee2ca3 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -32,6 +32,7 @@ import ( "github.com/containernetworking/cni/pkg/skel" cnitypes "github.com/containernetworking/cni/pkg/types" current "github.com/containernetworking/cni/pkg/types/100" + "github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/ipam" "github.com/containernetworking/plugins/pkg/ns" @@ -566,9 +567,242 @@ func CmdDel(args *skel.CmdArgs) error { return err } -// CmdCheck check handler to make sure networking is as expected. yet to be implemented. +// CmdCheck check handler to make sure networking is as expected. func CmdCheck(args *skel.CmdArgs) error { logCall("CHECK", args) - log.Print("CHECK is not yet implemented, pretending everything is fine") + + netconf, err := config.LoadConf(args.StdinData) + if err != nil { + return err + } + + // run the IPAM plugin + if netconf.NetConf.IPAM.Type != "" { + err = ipam.ExecCheck(netconf.NetConf.IPAM.Type, args.StdinData) + if err != nil { + return fmt.Errorf("failed to check with IPAM plugin type %q: %v", netconf.NetConf.IPAM.Type, err) + } + } + + // check cache + cRef := config.GetCRef(args.ContainerID, args.IfName) + cache, err := config.LoadConfFromCache(cRef) + if err != nil { + return err + } + if err := validateCache(cache, netconf); err != nil { + return err + } + + // Parse previous result. + if netconf.NetConf.RawPrevResult == nil { + return fmt.Errorf("Required prevResult missing") + } + if err := version.ParsePrevResult(&netconf.NetConf); err != nil { + return err + } + result, err := current.NewResultFromResult(netconf.NetConf.PrevResult) + if err != nil { + return err + } + + var contIntf, hostIntf current.Interface + // Find interfaces + for _, intf := range result.Interfaces { + if args.IfName == intf.Name { + if args.Netns == intf.Sandbox { + contIntf = *intf + } + } else { + // Check prevResults for ips against values found in the host + if err := validateInterface(*intf, true); err != nil { + return err + } + hostIntf = *intf + } + } + + // The namespace must be the same as what was configured + if args.Netns != contIntf.Sandbox { + return fmt.Errorf("Sandbox in prevResult %s doesn't match configured netns: %s", + contIntf.Sandbox, args.Netns) + } + + netns, err := ns.GetNS(args.Netns) + if err != nil { + return fmt.Errorf("failed to open netns %q: %v", args.Netns, err) + } + defer netns.Close() + + // Check prevResults for ips and routes against values found in the container + if err := netns.Do(func(_ ns.NetNS) error { + + // Check interface against values found in the container + err := validateInterface(contIntf, false) + if err != nil { + return err + } + + err = ip.ValidateExpectedInterfaceIPs(args.IfName, result.IPs) + if err != nil { + return err + } + + err = ip.ValidateExpectedRoute(result.Routes) + if err != nil { + return err + } + return nil + }); err != nil { + return err + } + + // ovs specific check + if err := validateOvs(args, netconf, hostIntf.Name); err != nil { + return err + } + + return nil +} + +func validateCache(cache *types.CachedNetConf, netconf *types.NetConf) error { + if cache.Netconf.BrName != netconf.BrName { + return fmt.Errorf("BrName mismatch. cache=%s,netconf=%s", + cache.Netconf.BrName, netconf.BrName) + } + + if cache.Netconf.SocketFile != netconf.SocketFile { + return fmt.Errorf("SocketFile mismatch. cache=%s,netconf=%s", + cache.Netconf.SocketFile, netconf.SocketFile) + } + + if cache.Netconf.IPAM.Type != netconf.IPAM.Type { + return fmt.Errorf("IPAM mismatch. cache=%s,netconf=%s", + cache.Netconf.IPAM.Type, netconf.IPAM.Type) + } + + if cache.Netconf.DeviceID != netconf.DeviceID { + return fmt.Errorf("DeviceID mismatch. cache=%s,netconf=%s", + cache.Netconf.DeviceID, netconf.DeviceID) + } + + return nil +} + +func validateInterface(intf current.Interface, isHost bool) error { + var link netlink.Link + var err error + var iftype string + if isHost { + iftype = "Host" + } else { + iftype = "Container" + } + + if intf.Name == "" { + return fmt.Errorf("%s interface name missing in prevResult: %v", iftype, intf.Name) + } + link, err = netlink.LinkByName(intf.Name) + if err != nil { + return fmt.Errorf("Error: %s Interface name in prevResult: %s not found", iftype, intf.Name) + } + if !isHost && intf.Sandbox == "" { + return fmt.Errorf("Error: %s interface %s should not be in host namespace", iftype, link.Attrs().Name) + } + + _, isVeth := link.(*netlink.Veth) + if !isVeth { + return fmt.Errorf("Error: %s interface %s not of type veth/p2p", iftype, link.Attrs().Name) + } + + if intf.Mac != "" && intf.Mac != link.Attrs().HardwareAddr.String() { + return fmt.Errorf("Error: Interface %s Mac %s doesn't match %s Mac: %s", intf.Name, intf.Mac, iftype, link.Attrs().HardwareAddr) + } + + return nil +} + +func validateOvs(args *skel.CmdArgs, netconf *types.NetConf, hostIfname string) error { + envArgs, err := getEnvArgs(args.Args) + if err != nil { + return err + } + var ovnPort string + if envArgs != nil { + ovnPort = string(envArgs.OvnPort) + } + + bridgeName, err := getBridgeName(netconf.BrName, ovnPort) + if err != nil { + return err + } + + ovsDriver, err := ovsdb.NewOvsBridgeDriver(bridgeName, netconf.SocketFile) + if err != nil { + return err + } + + found, err := ovsDriver.IsBridgePresent(netconf.BrName) + if err != nil { + return err + } + if !found { + return fmt.Errorf("Error: bridge %s is not found in OVS", netconf.BrName) + } + + ifaces, err := ovsDriver.FindInterfacesWithError() + if err != nil { + return err + } + if len(ifaces) > 0 { + return fmt.Errorf("Error: There are some interfaces in error state: %v", ifaces) + } + + vlanMode, tag, trunk, err := ovsDriver.GetOFPortVlanState(hostIfname) + if err != nil { + return fmt.Errorf("Error: Failed to retrieve port %s state: %v", hostIfname, err) + } + + // check vlan tag + if netconf.VlanTag == nil { + if tag != nil { + return fmt.Errorf("vlan tag mismatch. ovs=%d,netconf=nil", *tag) + } + } else { + if tag == nil { + return fmt.Errorf("vlan tag mismatch. ovs=nil,netconf=%d", *netconf.VlanTag) + } + if *tag != *netconf.VlanTag { + return fmt.Errorf("vlan tag mismatch. ovs=%d,netconf=%d", *tag, *netconf.VlanTag) + } + if vlanMode != "access" { + return fmt.Errorf("vlan mode mismatch. expected=access,real=%s", vlanMode) + } + } + + // check trunk + netconfTrunks := make([]uint, 0) + if len(netconf.Trunk) > 0 { + trunkVlanIds, err := splitVlanIds(netconf.Trunk) + if err != nil { + return err + } + netconfTrunks = append(netconfTrunks, trunkVlanIds...) + } + if len(trunk) != len(netconfTrunks) { + return fmt.Errorf("trunk mismatch. ovs=%v,netconf=%v", trunk, netconfTrunks) + } + if len(netconfTrunks) > 0 { + for i := 0; i < len(trunk); i++ { + if trunk[i] != netconfTrunks[i] { + return fmt.Errorf("trunk mismatch. ovs=%v,netconf=%v", trunk, netconfTrunks) + } + } + + if vlanMode != "trunk" { + return fmt.Errorf("vlan mode mismatch. expected=trunk,real=%s", vlanMode) + } + } + return nil } diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go index 3be725a63..dd73cde93 100644 --- a/pkg/plugin/plugin_test.go +++ b/pkg/plugin/plugin_test.go @@ -15,6 +15,7 @@ package plugin import ( + "bytes" "encoding/json" "errors" "fmt" @@ -27,7 +28,9 @@ import ( "github.com/containernetworking/cni/pkg/skel" cnitypes "github.com/containernetworking/cni/pkg/types" + types040 "github.com/containernetworking/cni/pkg/types/040" current "github.com/containernetworking/cni/pkg/types/100" + cniversion "github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/testutils" @@ -39,6 +42,50 @@ import ( "github.com/k8snetworkplumbingwg/ovs-cni/pkg/types" ) +type Range struct { + RangeStart net.IP `json:"rangeStart,omitempty"` // The first ip, inclusive + RangeEnd net.IP `json:"rangeEnd,omitempty"` // The last ip, inclusive + Subnet cnitypes.IPNet `json:"subnet"` + Gateway net.IP `json:"gateway,omitempty"` +} + +type RangeSet []Range + +type IPAMConfig struct { + *Range + Name string + Type string `json:"type"` + Routes []*cnitypes.Route `json:"routes"` + DataDir string `json:"dataDir"` + ResolvConf string `json:"resolvConf"` + Ranges []RangeSet `json:"ranges"` + IPArgs []net.IP `json:"-"` // Requested IPs from CNI_ARGS, args and capabilities +} + +type Net040 struct { + CNIVersion string `json:"cniVersion"` + Name string `json:"name"` + Type string `json:"type"` + Bridge string `json:"bridge"` + IPAM *IPAMConfig `json:"ipam"` + VlanTag *uint `json:"vlan"` + Trunk []*types.Trunk `json:"trunk,omitempty"` + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult types040.Result `json:"-"` +} + +type NetCurrent struct { + CNIVersion string `json:"cniVersion"` + Name string `json:"name"` + Type string `json:"type"` + Bridge string `json:"bridge"` + IPAM *IPAMConfig `json:"ipam"` + VlanTag *uint `json:"vlan"` + Trunk []*types.Trunk `json:"trunk,omitempty"` + RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` + PrevResult current.Result `json:"-"` +} + const bridgeName = "test-bridge" const vlanID = 100 const mtu = 1456 @@ -54,8 +101,12 @@ var _ = AfterSuite(func() { exec.Command("ovs-vsctl", "del-br", "--if-exists", bridgeName).Run() }) -var _ = Describe("CNI Plugin", func() { +var _ = Describe("CNI Plugin 0.3.0", func() { testFunc("0.3.0") }) +var _ = Describe("CNI Plugin 0.3.1", func() { testFunc("0.3.1") }) +var _ = Describe("CNI Plugin 0.4.0", func() { testFunc("0.4.0") }) +var _ = Describe("CNI Plugin 1.0.0", func() { testFunc("1.0.0") }) +var testFunc = func(version string) { BeforeEach(func() { output, err := exec.Command("ovs-vsctl", "add-br", bridgeName).CombinedOutput() Expect(err).NotTo(HaveOccurred(), "Failed to create testing OVS bridge: %v", string(output[:])) @@ -91,6 +142,70 @@ var _ = Describe("CNI Plugin", func() { } } + testCheck := func(conf string, r cnitypes.Result, targetNs ns.NetNS) { + if checkSupported, _ := cniversion.GreaterThanOrEqualTo(version, "0.4.0"); !checkSupported { + return + } + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNs.Path(), + IfName: IFNAME, + StdinData: []byte(conf), + } + + By("Calling Check command") + moreThan100, err := cniversion.GreaterThanOrEqualTo(version, "1.0.0") + Expect(err).NotTo(HaveOccurred()) + var confString []byte + if moreThan100 { + netconf := &NetCurrent{} + err = json.Unmarshal([]byte(conf), &netconf) + Expect(err).NotTo(HaveOccurred()) + result, err := current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + netconf.PrevResult = *result + + var data bytes.Buffer + err = result.PrintTo(&data) + Expect(err).NotTo(HaveOccurred()) + + var raw map[string]interface{} + err = json.Unmarshal(data.Bytes(), &raw) + Expect(err).NotTo(HaveOccurred()) + netconf.RawPrevResult = raw + + confString, err = json.Marshal(netconf) + Expect(err).NotTo(HaveOccurred()) + } else { + netconf := &Net040{} + err = json.Unmarshal([]byte(conf), &netconf) + Expect(err).NotTo(HaveOccurred()) + result, err := types040.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + netconf.PrevResult = *result + + var data bytes.Buffer + err = result.PrintTo(&data) + Expect(err).NotTo(HaveOccurred()) + + var raw map[string]interface{} + err = json.Unmarshal(data.Bytes(), &raw) + Expect(err).NotTo(HaveOccurred()) + netconf.RawPrevResult = raw + + confString, err = json.Marshal(netconf) + Expect(err).NotTo(HaveOccurred()) + } + + args.StdinData = confString + + err = cmdCheckWithArgs(args, func() error { + return CmdCheck(args) + }) + Expect(err).NotTo(HaveOccurred()) + } + testIPAM := func(conf string, isDual bool, ipPrefix, ip6Prefix string) { By("Creating temporary target namespace to simulate a container") targetNs, err := testutils.NewNS() @@ -157,6 +272,9 @@ var _ = Describe("CNI Plugin", func() { }) Expect(err).NotTo(HaveOccurred()) + By("Calling CHECK command") + testCheck(conf, r, targetNs) + By("Calling DEL command to cleanup assigned ip address") err = cmdDelWithArgs(args, func() error { return CmdDel(args) @@ -204,7 +322,7 @@ var _ = Describe("CNI Plugin", func() { Expect(len(brPorts)).To(Equal(0)) } - testAdd := func(conf string, setVlan, setMtu bool, Trunk string, targetNs ns.NetNS) string { + testAdd := func(conf string, setVlan, setMtu bool, Trunk string, targetNs ns.NetNS) (string, cnitypes.Result) { args := &skel.CmdArgs{ ContainerID: "dummy", Netns: targetNs.Path(), @@ -299,63 +417,84 @@ var _ = Describe("CNI Plugin", func() { }) Expect(err).NotTo(HaveOccurred()) - return hostIface.Name + return hostIface.Name, r } Context("connecting container to a bridge", func() { Context("with VLAN ID set on port", func() { conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", + "cniVersion": "%s", "name": "mynet", "type": "ovs", "bridge": "%s", "vlan": %d - }`, bridgeName, vlanID) - It("should successfully complete ADD and DEL commands", func() { + }`, version, bridgeName, vlanID) + It("should successfully complete ADD, CHECK and DEL commands", func() { targetNs := newNS() defer func() { closeNS(targetNs) }() - hostIfName := testAdd(conf, true, false, "", targetNs) + hostIfName, result := testAdd(conf, true, false, "", targetNs) + testCheck(conf, result, targetNs) testDel(conf, hostIfName, targetNs, true) }) }) Context("without a VLAN ID set on port", func() { conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", + "cniVersion": "%s", "name": "mynet", "type": "ovs", "bridge": "%s" - }`, bridgeName) - It("should successfully complete ADD and DEL commands", func() { + }`, version, bridgeName) + It("should successfully complete ADD, CHECK and DEL commands", func() { targetNs := newNS() defer func() { closeNS(targetNs) }() - hostIfName := testAdd(conf, false, false, "", targetNs) + hostIfName, result := testAdd(conf, false, false, "", targetNs) + testCheck(conf, result, targetNs) testDel(conf, hostIfName, targetNs, true) }) }) Context("with specific VLAN ID ranges set (via both range and id) for the port", func() { conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", + "cniVersion": "%s", "name": "mynet", "type": "ovs", "bridge": "%s", "trunk": [ {"minID": 10, "maxID": 12}, {"id": 15}, {"minID": 17, "maxID": 18} ] - }`, bridgeName) - It("should successfully complete ADD and DEL commands", func() { + }`, version, bridgeName) + It("should successfully complete ADD, CHECK and DEL commands", func() { targetNs := newNS() defer func() { closeNS(targetNs) }() - hostIfName := testAdd(conf, false, false, "[10, 11, 12, 15, 17, 18]", targetNs) + hostIfName, result := testAdd(conf, false, false, "[10, 11, 12, 15, 17, 18]", targetNs) + testCheck(conf, result, targetNs) + testDel(conf, hostIfName, targetNs, true) + }) + }) + Context("with specific VLAN ID ranges set (via both range and id) for the port (not sorted)", func() { + conf := fmt.Sprintf(`{ + "cniVersion": "%s", + "name": "mynet", + "type": "ovs", + "bridge": "%s", + "trunk": [ {"minID": 17, "maxID": 18}, {"id": 15}, {"minID": 10, "maxID": 12} ] + }`, version, bridgeName) + It("should successfully complete ADD, CHECK and DEL commands", func() { + targetNs := newNS() + defer func() { + closeNS(targetNs) + }() + hostIfName, result := testAdd(conf, false, false, "[10, 11, 12, 15, 17, 18]", targetNs) + testCheck(conf, result, targetNs) testDel(conf, hostIfName, targetNs, true) }) }) Context("with specific IPAM set for container interface", func() { conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", + "cniVersion": "%s", "name": "mynet", "type": "ovs", "bridge": "%s", @@ -364,14 +503,14 @@ var _ = Describe("CNI Plugin", func() { "ranges": [[ {"subnet": "10.1.2.0/24", "gateway": "10.1.2.1"} ]], "dataDir": "/tmp/ovs-cni/conf" } - }`, bridgeName) - It("should successfully complete ADD and DEL commands", func() { + }`, version, bridgeName) + It("should successfully complete ADD, CHECK and DEL commands", func() { testIPAM(conf, false, "10.1.2", "") }) }) Context("with dual stack ip addresses set for container interface", func() { conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", + "cniVersion": "%s", "name": "mynet", "type": "ovs", "bridge": "%s", @@ -380,43 +519,45 @@ var _ = Describe("CNI Plugin", func() { "ranges": [[ {"subnet": "10.1.2.0/24", "gateway": "10.1.2.1"} ], [{"subnet": "3ffe:ffff:0:1ff::/64", "rangeStart": "3ffe:ffff:0:1ff::10", "rangeEnd": "3ffe:ffff:0:1ff::20"}]], "dataDir": "/tmp/ovs-cni/conf" } - }`, bridgeName) - It("should successfully complete ADD and DEL commands", func() { + }`, version, bridgeName) + It("should successfully complete ADD, CHECK and DEL commands", func() { testIPAM(conf, true, "10.1.2", "3ffe:ffff:0:1ff") }) }) Context("with MTU set on port", func() { conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", + "cniVersion": "%s", "name": "mynet", "type": "ovs", "bridge": "%s", "mtu": %d - }`, bridgeName, mtu) - It("should successfully complete ADD and DEL commands", func() { + }`, version, bridgeName, mtu) + It("should successfully complete ADD, CHECK and DEL commands", func() { targetNs := newNS() defer func() { closeNS(targetNs) }() - hostIfName := testAdd(conf, false, true, "", targetNs) + hostIfName, result := testAdd(conf, false, true, "", targetNs) + testCheck(conf, result, targetNs) testDel(conf, hostIfName, targetNs, true) }) }) Context("invoke DEL action after deleting container net namespace", func() { conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", + "cniVersion": "%s", "name": "mynet", "type": "ovs", "bridge": "%s" - }`, bridgeName) - It("should successfully complete ADD and DEL commands", func() { + }`, version, bridgeName) + It("should successfully complete ADD, CHECK and DEL commands", func() { targetNs := newNS() defer func() { // clean up targetNs in case of testAdd failure targetNs.Close() testutils.UnmountNS(targetNs) }() - hostIfName := testAdd(conf, false, false, "", targetNs) + hostIfName, result := testAdd(conf, false, false, "", targetNs) + testCheck(conf, result, targetNs) Expect(targetNs.Close()).To(Succeed()) Expect(testutils.UnmountNS(targetNs)).To(Succeed()) testDel(conf, hostIfName, targetNs, false) @@ -424,17 +565,18 @@ var _ = Describe("CNI Plugin", func() { }) Context("invoke DEL action after deleting container interface", func() { conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", + "cniVersion": "%s", "name": "mynet", "type": "ovs", "bridge": "%s" - }`, bridgeName) - It("should successfully complete ADD and DEL commands", func() { + }`, version, bridgeName) + It("should successfully complete ADD, CHECK and DEL commands", func() { targetNs := newNS() defer func() { closeNS(targetNs) }() - hostIfName := testAdd(conf, false, false, "", targetNs) + hostIfName, result := testAdd(conf, false, false, "", targetNs) + testCheck(conf, result, targetNs) err := targetNs.Do(func(ns.NetNS) error { defer GinkgoRecover() return ip.DelLinkByName(IFNAME) @@ -446,12 +588,12 @@ var _ = Describe("CNI Plugin", func() { Context("random mac address on container interface", func() { It("should create eth0 on two different namespace with different mac addresses", func() { conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", + "cniVersion": "%s", "name": "mynet", "type": "ovs", "bridge": "%s", "vlan": %d - }`, bridgeName, vlanID) + }`, version, bridgeName, vlanID) By("Creating two temporary target namespace to simulate two containers") targetNsOne := newNS() @@ -476,12 +618,12 @@ var _ = Describe("CNI Plugin", func() { Context("specified mac address on container interface", func() { It("should create eth0 with the specified mac address", func() { conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", + "cniVersion": "%s", "name": "mynet", "type": "ovs", "bridge": "%s", "vlan": %d - }`, bridgeName, vlanID) + }`, version, bridgeName, vlanID) By("Creating temporary target namespace to simulate a container") targetNs := newNS() @@ -502,11 +644,11 @@ var _ = Describe("CNI Plugin", func() { const ovsOutput = "external_ids : {iface-id=test-port}" conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", + "cniVersion": "%s", "name": "mynet", "type": "ovs", "OvnPort": "test-port", - "bridge": "%s"}`, bridgeName) + "bridge": "%s"}`, version, bridgeName) targetNs := newNS() defer func() { @@ -560,11 +702,11 @@ var _ = Describe("CNI Plugin", func() { Context("purge ports with failed interfaces", func() { conf := fmt.Sprintf(`{ - "cniVersion": "0.3.1", + "cniVersion": "%s", "name": "mynet", "type": "ovs", "OvnPort": "test-port", - "bridge": "%s"}`, bridgeName) + "bridge": "%s"}`, version, bridgeName) It("DEL removes ports without network namespace", func() { firstTargetNs := newNS() @@ -625,7 +767,7 @@ var _ = Describe("CNI Plugin", func() { }) }) }) -}) +} func attach(namespace ns.NetNS, conf, ifName, mac, ovnPort string) *current.Result { extraArgs := "" @@ -677,6 +819,10 @@ func cmdAddWithArgs(args *skel.CmdArgs, f func() error) (cnitypes.Result, []byte return testutils.CmdAdd(args.Netns, args.ContainerID, args.IfName, args.StdinData, f) } +func cmdCheckWithArgs(args *skel.CmdArgs, f func() error) error { + return testutils.CmdCheck(args.Netns, args.ContainerID, args.IfName, args.StdinData, f) +} + func cmdDelWithArgs(args *skel.CmdArgs, f func() error) error { return testutils.CmdDel(args.Netns, args.ContainerID, args.IfName, f) } diff --git a/tests/ovs_test.go b/tests/ovs_test.go index 9153908d7..a0ab53c92 100644 --- a/tests/ovs_test.go +++ b/tests/ovs_test.go @@ -28,7 +28,11 @@ import ( "github.com/k8snetworkplumbingwg/ovs-cni/tests/node" ) -var _ = Describe("ovs-cni", func() { +var _ = Describe("ovs-cni 0.3.0", func() { testFunc("0.3.0") }) +var _ = Describe("ovs-cni 0.3.1", func() { testFunc("0.3.1") }) +var _ = Describe("ovs-cni 0.4.0", func() { testFunc("0.4.0") }) + +var testFunc = func(version string) { Describe("pod availability tests", func() { Context("When ovs-cni is deployed on the cluster", func() { Specify("ovs-cni pod should be up and running", func() { @@ -51,7 +55,7 @@ var _ = Describe("ovs-cni", func() { Context("and a network attachment definition is defined", func() { const nadName = "ovs-net" BeforeEach(func() { - clusterApi.CreateNetworkAttachmentDefinition(nadName, bridgeName, `{ "cniVersion": "0.3.1", "type": "ovs", "bridge": "`+bridgeName+`", "vlan": 100 }`) + clusterApi.CreateNetworkAttachmentDefinition(nadName, bridgeName, `{ "cniVersion": "`+version+`", "type": "ovs", "bridge": "`+bridgeName+`", "vlan": 100 }`) }) AfterEach(func() { clusterApi.RemoveNetworkAttachmentDefinition(nadName) @@ -86,4 +90,4 @@ var _ = Describe("ovs-cni", func() { }) }) }) -}) +}