Skip to content

Commit

Permalink
Add support for CHECK command (#212)
Browse files Browse the repository at this point in the history
1. Add support for CHECK command
2. Bump ovs-cni version to 0.4.0.
3. Add tests for 0.3.0, 0.4.0.
4. Restrict supported versions to 0.3.0, 0.3.1, 0.4.0.

Signed-off-by: Masashi Honma <[email protected]>
  • Loading branch information
masap authored Nov 2, 2021
1 parent 0f25421 commit 3973253
Show file tree
Hide file tree
Showing 10 changed files with 494 additions and 56 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion cmd/plugin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
4 changes: 2 additions & 2 deletions docs/cni-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ ovs-vsctl add-br br1
# Run ADD command connecting the namespace to the bridge
cat <<EOF | CNI_COMMAND=ADD CNI_CONTAINERID=ns1 CNI_NETNS=/var/run/netns/ns1 CNI_IFNAME=eth2 CNI_PATH=`pwd` ./cmd/plugin/plugin
{
"cniVersion": "0.3.1",
"cniVersion": "0.4.0",
"name": "mynet",
"type": "ovs",
"bridge": "br1",
Expand All @@ -119,7 +119,7 @@ ovs-vsctl show
# Run DEL command removing the veth pair and OVS port
cat <<EOF | CNI_COMMAND=DEL CNI_CONTAINERID=ns1 CNI_NETNS=/var/run/netns/ns1 CNI_IFNAME=eth2 CNI_PATH=/opt/cni/bin ./cmd/plugin/plugin
{
"cniVersion": "0.3.1",
"cniVersion": "0.4.0",
"name": "mynet",
"type": "ovs",
"bridge": "br1",
Expand Down
6 changes: 3 additions & 3 deletions docs/demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,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"
}'
Expand All @@ -116,7 +116,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
Expand Down Expand Up @@ -167,7 +167,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,
Expand Down
2 changes: 1 addition & 1 deletion docs/ovs-offload.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ metadata:
k8s.v1.cni.cncf.io/resourceName: intel.com/mellanox_snic0
spec:
config: '{
"cniVersion": "0.3.1",
"cniVersion": "0.4.0",
"type": "ovs",
"bridge": "br-snic0",
"trunk": [ {"minID": 1050, "maxID": 1059} ]
Expand Down
2 changes: 1 addition & 1 deletion examples/ovs-net-vlan100.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ metadata:
name: ovs-net-vlan100
spec:
config: '{
"cniVersion": "0.3.1",
"cniVersion": "0.4.0",
"type": "ovs",
"bridge": "br1",
"vlan": 100
Expand Down
54 changes: 54 additions & 0 deletions pkg/ovsdb/ovsdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,60 @@ func (ovsd *OvsDriver) GetOFPortOpState(portName string) (string, error) {
return fmt.Sprintf("%v", operationResult.Rows[0]["link_state"]), nil
}

// GetOFPortVlanState retrieves port vlan state of the OF port
func (ovsd *OvsDriver) GetOFPortVlanState(portName string) (string, *uint, []uint, error) {
condition := ovsdb.NewCondition("name", ovsdb.ConditionEqual, portName)
selectOp := []ovsdb.Operation{{
Op: "select",
Table: "Port",
Columns: []string{"vlan_mode", "tag", "trunks"},
Where: []ovsdb.Condition{condition},
}}
var vlanMode = ""
var tag *uint = nil
var trunks []uint

transactionResult, err := ovsd.ovsdbTransact(selectOp)
if err != nil {
return vlanMode, tag, trunks, err
}

if len(transactionResult) != 1 {
return vlanMode, tag, trunks, fmt.Errorf("transactionResult length is not one")
}

operationResult := transactionResult[0]
if operationResult.Error != "" {
return vlanMode, tag, trunks, fmt.Errorf("%s - %s", operationResult.Error, operationResult.Details)
}

if len(operationResult.Rows) != 1 {
return vlanMode, tag, trunks, fmt.Errorf("operationResult.Rows length is not one")
}

vlanModeCol := operationResult.Rows[0]["vlan_mode"]
switch vlanModeCol.(type) {
case string:
vlanMode = operationResult.Rows[0]["vlan_mode"].(string)
}

tagCol := operationResult.Rows[0]["tag"]
switch tagCol.(type) {
case float64:
tagValue := uint(operationResult.Rows[0]["tag"].(float64))
tag = &tagValue
}

trunksCol := operationResult.Rows[0]["trunks"].(ovsdb.OvsSet).GoSet
if len(trunksCol) > 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)
Expand Down
238 changes: 236 additions & 2 deletions pkg/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit 3973253

Please sign in to comment.