From f15864b95d7e5a116318750eb816b4399d5a123b Mon Sep 17 00:00:00 2001 From: Markus Vahlenkamp Date: Fri, 6 Oct 2023 17:55:29 +0200 Subject: [PATCH] Migrate `tools veth` to new link structs (#1523) * use new link structs in tools veth commands * cleanup * cleanup2 * cleanup3 * use clab NewNode to create fake nodes for link resolving * remove commented veth create tests * use existing iface for link impairments * added runtime exec for podman --------- Co-authored-by: Roman Dodin --- clab/config.go | 4 +- clab/netlink.go | 271 ++----------------------- clab/ovs.go | 44 ---- cmd/tools_veth.go | 212 ++++++++++++------- links/endpoint.go | 3 +- links/endpoint_raw.go | 3 +- links/link_host.go | 3 +- links/link_macvlan.go | 11 +- links/link_mgmt-net.go | 3 +- mocks/mocknodes/node.go | 12 ++ nodes/linux/linux.go | 2 +- nodes/node.go | 1 + tests/01-smoke/08-tools-cmds.robot | 16 +- tests/01-smoke/12-tools-veth.clab.yaml | 15 ++ tests/01-smoke/12-tools-veth.robot | 114 +++++++++++ 15 files changed, 312 insertions(+), 402 deletions(-) delete mode 100644 clab/ovs.go create mode 100644 tests/01-smoke/12-tools-veth.clab.yaml create mode 100644 tests/01-smoke/12-tools-veth.robot diff --git a/clab/config.go b/clab/config.go index ef3e412c1..1b5384312 100644 --- a/clab/config.go +++ b/clab/config.go @@ -29,8 +29,8 @@ const ( dockerNetName = "clab" dockerNetIPv4Addr = "172.20.20.0/24" dockerNetIPv6Addr = "2001:172:20:20::/64" - // NSPath value assigned to host interfaces. - hostNSPath = "__host" + // veth link mtu. + DefaultVethLinkMTU = 9500 // clab specific topology variables. clabDirVar = "__clabDir__" diff --git a/clab/netlink.go b/clab/netlink.go index a456a06a7..d0255de6a 100644 --- a/clab/netlink.go +++ b/clab/netlink.go @@ -6,148 +6,14 @@ package clab import ( "fmt" - "net" + "strings" - "github.com/containernetworking/plugins/pkg/ns" - "github.com/google/uuid" log "github.com/sirupsen/logrus" - "github.com/srl-labs/containerlab/nodes/srl" "github.com/srl-labs/containerlab/types" "github.com/srl-labs/containerlab/utils" "github.com/vishvananda/netlink" ) -type vEthEndpoint struct { - Link netlink.Link - LinkName string - NSName string // netns name - NSPath string // netns path - Bridge string // bridge name a veth is destined to be connected to - OvsBridge string // ovs-bridge name a veth is destined to be connected to -} - -// CreateVirtualWiring creates the virtual topology between the containers. -func (c *CLab) CreateVirtualWiring(l *types.Link) (err error) { - log.Infof("Creating virtual wire: %s:%s <--> %s:%s", l.A.Node.ShortName, l.A.EndpointName, l.B.Node.ShortName, l.B.EndpointName) - - // connect containers (or container and a bridge) using veth pair - // based on the link configuration contained within *Link struct - // veth side A - vA := vEthEndpoint{ - LinkName: l.A.EndpointName, - NSName: l.A.Node.LongName, - NSPath: l.A.Node.NSPath, - } - // veth side B - vB := vEthEndpoint{ - LinkName: l.B.EndpointName, - NSName: l.B.Node.LongName, - NSPath: l.B.Node.NSPath, - } - - // get random names for veth sides as they will be created in root netns first - ARndmName := fmt.Sprintf("clab-%s", genIfName()) - BRndmName := fmt.Sprintf("clab-%s", genIfName()) - - // set bridge name for endpoint that should be connect to linux bridge - switch { - case l.A.Node.Kind == "bridge": - - // mgmt-net is a reserved node name that means - // connect this endpoint to docker management bridged network - if l.A.Node.ShortName != "mgmt-net" { - vA.Bridge = l.A.Node.ShortName - } else { - vA.Bridge = c.Config.Mgmt.Bridge - } - // veth endpoint destined to connect to the bridge in the host netns - // will not have a random name - ARndmName = l.A.EndpointName - case l.B.Node.Kind == "bridge": - if l.B.Node.ShortName != "mgmt-net" { - vB.Bridge = l.B.Node.ShortName - } else { - vB.Bridge = c.Config.Mgmt.Bridge - } - BRndmName = l.B.EndpointName - case l.A.Node.Kind == "ovs-bridge": - vA.OvsBridge = l.A.Node.ShortName - ARndmName = l.A.EndpointName - case l.B.Node.Kind == "ovs-bridge": - vB.OvsBridge = l.B.Node.ShortName - BRndmName = l.B.EndpointName - // for host connections random names shouldn't be used - case l.A.Node.Kind == "host": - ARndmName = l.A.EndpointName - case l.B.Node.Kind == "host": - BRndmName = l.B.EndpointName - } - - // Generate MAC addresses - aMAC, err := net.ParseMAC(l.A.MAC) - if err != nil { - return err - } - bMAC, err := net.ParseMAC(l.B.MAC) - if err != nil { - return err - } - - // if one of the endpoints is a macvlan interface - // we need to create a macvlan interface in the root netns - if l.A.Node.Kind == "macvlan" || l.B.Node.Kind == "macvlan" { - // make sure the macvlan is always the B side - if l.A.Node.Kind == "macvlan" { - tmp := l.A - l.A = l.B - l.B = tmp - } - - link, err := createMACVLANInterface(ARndmName, l.B.EndpointName, l.MTU, aMAC) - if err != nil { - return err - } - - err = toNS(l.A.Node.NSPath, link, l.A.EndpointName) - if err != nil { - return err - } - - // SR Linux brings down non-veth interfaces, so we have to force them up after SR Linux is started. - for _, kn := range srl.KindNames { - if l.A.Node.Kind == kn { - l.A.Node.Exec = append([]string{fmt.Sprintf("ip l set dev %s up", l.A.EndpointName)}, l.A.Node.Exec...) - } - } - - } else { - - // create veth pair in the root netns - vA.Link, vB.Link, err = createVethIface(ARndmName, BRndmName, l.MTU, aMAC, bMAC) - if err != nil { - return err - } - - // once veth pair is created, disable tx offload for veth pair - if err := utils.EthtoolTXOff(ARndmName); err != nil { - return err - } - if err := utils.EthtoolTXOff(BRndmName); err != nil { - return err - } - - if err = vA.setVethLink(); err != nil { - _ = netlink.LinkDel(vA.Link) - return err - } - if err = vB.setVethLink(); err != nil { - _ = netlink.LinkDel(vB.Link) - } - } - - return err -} - // RemoveHostOrBridgeVeth tries to remove veths connected to the host network namespace or a linux bridge // and does nothing in case they are not found. func (c *CLab) RemoveHostOrBridgeVeth(l *types.Link) (err error) { @@ -184,134 +50,25 @@ func (c *CLab) RemoveHostOrBridgeVeth(l *types.Link) (err error) { return nil } -// createMACVLANInterface creates a macvlan interface in the root netns. -func createMACVLANInterface(ifName, parentIfName string, mtu int, MAC net.HardwareAddr) (netlink.Link, error) { - parentInterface, err := utils.LinkByNameOrAlias(parentIfName) - if err != nil { - return nil, err - } - - mvl := &netlink.Macvlan{ - LinkAttrs: netlink.LinkAttrs{ - Name: ifName, - ParentIndex: parentInterface.Attrs().Index, - HardwareAddr: MAC, - }, - Mode: netlink.MACVLAN_MODE_BRIDGE, +// GetLinksByNamePrefix returns a list of links whose name matches a prefix. +func GetLinksByNamePrefix(prefix string) ([]netlink.Link, error) { + // filtered list of interfaces + if prefix == "" { + return nil, fmt.Errorf("prefix is not specified") } + var fls []netlink.Link - err = netlink.LinkAdd(mvl) + ls, err := netlink.LinkList() if err != nil { return nil, err } - - return mvl, nil -} - -// createVethIface takes two veth endpoint structs and create a veth pair and return -// veth interface links. -func createVethIface(ifName, peerName string, mtu int, aMAC, bMAC net.HardwareAddr) (linkA, linkB netlink.Link, err error) { - linkA = &netlink.Veth{ - LinkAttrs: netlink.LinkAttrs{ - Name: ifName, - HardwareAddr: aMAC, - Flags: net.FlagUp, - MTU: mtu, - }, - PeerName: peerName, - PeerHardwareAddr: bMAC, - } - - if err := netlink.LinkAdd(linkA); err != nil { - return nil, nil, err - } - - if linkB, err = utils.LinkByNameOrAlias(peerName); err != nil { - err = fmt.Errorf("failed to lookup %q: %v", peerName, err) - } - - return -} - -// setVethLink sets the veth link endpoints to the relevant namespaces and/or connects one end to the bridge. -func (veth *vEthEndpoint) setVethLink() error { - // if veth is destined to connect to a linux bridge in the host netns - if veth.Bridge != "" { - return veth.toBridge() - } - if veth.OvsBridge != "" { - return veth.toOvsBridge() - } - // host endpoints have a special NSPath value - // the host portion of veth doesn't need to be additionally processed - if veth.NSPath == hostNSPath { - if err := netlink.LinkSetUp(veth.Link); err != nil { - return fmt.Errorf("failed to set %q up: %v", - veth.LinkName, err) + for _, l := range ls { + if strings.HasPrefix(l.Attrs().Name, prefix) { + fls = append(fls, l) } - return nil } - // otherwise it needs to be put into a netns - return veth.toNS() -} - -// toNS puts a veth endpoint to a given netns and renames its random name to a desired name. -func (veth *vEthEndpoint) toNS() error { - return toNS(veth.NSPath, veth.Link, veth.LinkName) -} - -func toNS(nsPath string, link netlink.Link, expectedName string) error { - var vethNS ns.NetNS - var err error - if vethNS, err = ns.GetNS(nsPath); err != nil { - return err - } - // move veth endpoint to namespace - if err = netlink.LinkSetNsFd(link, int(vethNS.Fd())); err != nil { - return err + if len(fls) == 0 { + return nil, fmt.Errorf("no links found by specified prefix %s", prefix) } - err = vethNS.Do(func(_ ns.NetNS) error { - if err = netlink.LinkSetName(link, expectedName); err != nil { - return fmt.Errorf( - "failed to rename link: %v", err) - } - - if err = netlink.LinkSetUp(link); err != nil { - return fmt.Errorf("failed to set %q up: %v", - expectedName, err) - } - return nil - }) - return err -} - -func (veth *vEthEndpoint) toBridge() error { - var vethNS ns.NetNS - var err error - // bridge is in the host netns, thus we need to get current netns - if vethNS, err = ns.GetCurrentNS(); err != nil { - return err - } - err = vethNS.Do(func(_ ns.NetNS) error { - br, err := utils.BridgeByName(veth.Bridge) - if err != nil { - return err - } - - // connect host veth end to the bridge - if err := netlink.LinkSetMaster(veth.Link, br); err != nil { - return fmt.Errorf("failed to connect %q to bridge %v: %v", veth.LinkName, veth.Bridge, err) - } - - if err = netlink.LinkSetUp(veth.Link); err != nil { - return fmt.Errorf("failed to set %q up: %v", veth.LinkName, err) - } - return nil - }) - return err -} - -func genIfName() string { - s, _ := uuid.New().MarshalText() // .MarshalText() always return a nil error - return string(s[:8]) + return fls, nil } diff --git a/clab/ovs.go b/clab/ovs.go deleted file mode 100644 index 7eac905f6..000000000 --- a/clab/ovs.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package clab - -import ( - "fmt" - - "github.com/containernetworking/plugins/pkg/ns" - "github.com/digitalocean/go-openvswitch/ovs" - "github.com/srl-labs/containerlab/utils" - "github.com/vishvananda/netlink" -) - -func (veth *vEthEndpoint) toOvsBridge() error { - var vethNS ns.NetNS - var err error - // bridge is in the host netns, thus we need to get current netns - if vethNS, err = ns.GetCurrentNS(); err != nil { - return err - } - err = vethNS.Do(func(_ ns.NetNS) error { - _, err := utils.LinkByNameOrAlias(veth.OvsBridge) - if err != nil { - return fmt.Errorf("could not find ovs bridge %q: %v", veth.OvsBridge, err) - } - - c := ovs.New( - // Prepend "sudo" to all commands. - ovs.Sudo(), - ) - - if err := c.VSwitch.AddPort(veth.OvsBridge, veth.LinkName); err != nil { - return err - } - - if err = netlink.LinkSetUp(veth.Link); err != nil { - return fmt.Errorf("failed to set %q up: %v", veth.LinkName, err) - } - return nil - }) - return err -} diff --git a/cmd/tools_veth.go b/cmd/tools_veth.go index 723233c4a..2b9fb6d0b 100644 --- a/cmd/tools_veth.go +++ b/cmd/tools_veth.go @@ -14,6 +14,8 @@ import ( "github.com/spf13/cobra" "github.com/srl-labs/containerlab/clab" "github.com/srl-labs/containerlab/links" + "github.com/srl-labs/containerlab/nodes" + "github.com/srl-labs/containerlab/nodes/state" "github.com/srl-labs/containerlab/runtime" "github.com/srl-labs/containerlab/types" "github.com/srl-labs/containerlab/utils" @@ -45,6 +47,17 @@ var vethCreateCmd = &cobra.Command{ Short: "Create a veth interface and attach its sides to the specified containers", RunE: func(cmd *cobra.Command, args []string) error { var err error + + parsedAEnd, err := parseVethEndpoint(AEnd) + if err != nil { + return err + } + + parsedBEnd, err := parseVethEndpoint(BEnd) + if err != nil { + return err + } + opts := []clab.ClabOption{ clab.WithTimeout(timeout), clab.WithRuntime(rt, @@ -64,106 +77,163 @@ var vethCreateCmd = &cobra.Command{ ctx, cancel := context.WithCancel(context.Background()) defer cancel() - var vethAEndpoint *vethEndpoint - var vethBEndpoint *vethEndpoint - - if vethAEndpoint, err = parseVethEndpoint(AEnd); err != nil { - return err - } - if vethBEndpoint, err = parseVethEndpoint(BEnd); err != nil { + // create fake nodes to make links resolve work + err = createNodes(ctx, c, parsedAEnd, parsedBEnd) + if err != nil { return err } - aNode := &types.NodeConfig{ - LongName: vethAEndpoint.node, - ShortName: vethAEndpoint.node, - Kind: vethAEndpoint.kind, - NSPath: "__host", // NSPath defaults to __host to make attachment to host. For attachment to containers the NSPath will be overwritten + // now create link brief as if the link was passed via topology file + linkBrief := &links.LinkBriefRaw{ + Endpoints: []string{fmt.Sprintf("%s:%s", parsedAEnd.Node, parsedAEnd.Iface), + fmt.Sprintf("%s:%s", parsedBEnd.Node, parsedBEnd.Iface)}, + LinkCommonParams: links.LinkCommonParams{ + MTU: MTU, + }, } - bNode := &types.NodeConfig{ - LongName: vethBEndpoint.node, - ShortName: vethBEndpoint.node, - Kind: vethBEndpoint.kind, - NSPath: "__host", - } - - if aNode.Kind == "container" { - aNode.NSPath, err = c.GlobalRuntime().GetNSPath(ctx, aNode.LongName) - if err != nil { - return err - } - } - if bNode.Kind == "container" { - bNode.NSPath, err = c.GlobalRuntime().GetNSPath(ctx, bNode.LongName) - if err != nil { - return err - } - } - // generate mac for endpoint A - aMac, err := utils.GenMac(links.ClabOUI) + linkRaw, err := linkBrief.ToTypeSpecificRawLink() if err != nil { return err } - endpointA := types.Endpoint{ - Node: aNode, - EndpointName: vethAEndpoint.iface, - MAC: aMac.String(), + + // we need to copy nodes.Nodes to links.Nodes since two interfaces + // are not identical, but a subset + resolveNodes := make(map[string]links.Node, len(c.Nodes)) + for k, v := range c.Nodes { + resolveNodes[k] = v } - // generate mac for endpoint B - bMac, err := utils.GenMac(links.ClabOUI) + link, err := linkRaw.Resolve(&links.ResolveParams{Nodes: resolveNodes}) if err != nil { return err } - endpointB := types.Endpoint{ - Node: bNode, - EndpointName: vethBEndpoint.iface, - MAC: bMac.String(), - } - - link := &types.Link{ - A: &endpointA, - B: &endpointB, - MTU: MTU, - } - if err := c.CreateVirtualWiring(link); err != nil { + err = link.Deploy(ctx) + if err != nil { return err } + log.Info("veth interface successfully created!") return nil }, } -func parseVethEndpoint(s string) (*vethEndpoint, error) { - supportedKinds := []string{"ovs-bridge", "bridge", "host"} - ve := &vethEndpoint{} - arr := strings.Split(s, ":") - if (len(arr) != 2) && (len(arr) != 3) { - return ve, errors.New("malformed veth endpoint reference") +// createNodes creates fake nodes in c.Nodes map to make link resolve work. +// It checks which endpoint type is set by a user and creates a node that matches the type. +func createNodes(ctx context.Context, c *clab.CLab, AEnd, BEnd parsedEndpoint) error { + for _, epDefinition := range []parsedEndpoint{AEnd, BEnd} { + switch epDefinition.Kind { + case links.LinkEndpointTypeHost: + err := createFakeNode(c, "host", &types.NodeConfig{ + ShortName: epDefinition.Node, + }) + if err != nil { + return err + } + + case links.LinkEndpointTypeBridge: + err := createFakeNode(c, "bridge", &types.NodeConfig{ + ShortName: epDefinition.Node, + }) + if err != nil { + return err + } + default: + // default endpoint type is veth + // so we create a fake linux node for it and fetch + // its namespace path. + // techinically we don't care which node this is, as long as it uses + // standard veth interface attachment process. + nspath, err := c.GlobalRuntime().GetNSPath(ctx, epDefinition.Node) + if err != nil { + return err + } + + err = createFakeNode(c, "linux", &types.NodeConfig{ + ShortName: epDefinition.Node, + NSPath: nspath, + }) + if err != nil { + return err + } + } } + + return nil +} + +// parsedEndpoint is a parsed veth endpoint definition. +type parsedEndpoint struct { + Node string + Iface string + Kind links.LinkEndpointType +} + +// parseVethEndpoint parses the veth endpoint definition as passed in the veth create command. +func parseVethEndpoint(s string) (parsedEndpoint, error) { + s = strings.TrimSpace(s) + + ep := parsedEndpoint{} + + arr := strings.Split(s, ":") + + var kind links.LinkEndpointType + switch len(arr) { case 2: - ve.kind = "container" + ep.Kind = links.LinkEndpointTypeVeth + if arr[0] == "host" { - ve.kind = "host" + ep.Kind = links.LinkEndpointTypeHost } - ve.node = arr[0] - ve.iface = arr[1] + + ep.Node = arr[0] + ep.Iface = arr[1] + case 3: - if _, ok := utils.StringInSlice(supportedKinds, arr[0]); !ok { - return nil, fmt.Errorf("node type %s is not supported, supported nodes are %q", arr[0], supportedKinds) + if _, ok := utils.StringInSlice([]string{"ovs-bridge", "bridge"}, arr[0]); !ok { + return ep, fmt.Errorf("node type %s is not supported, supported nodes are %q", arr[0], supportedKinds) } - ve.kind = arr[0] - ve.node = arr[1] - ve.iface = arr[2] + + switch arr[0] { + case "bridge", "ovs-bridge": + kind = links.LinkEndpointTypeBridge + default: + kind = links.LinkEndpointTypeVeth + } + + ep.Kind = kind + + ep.Node = arr[1] + ep.Iface = arr[2] + + default: + return ep, errors.New("malformed veth endpoint reference") } - return ve, nil + + return ep, nil } -type vethEndpoint struct { - kind string // kind of the node to attach to: ovs-bridge, bridge, host or implicitly container - node string - iface string +// createFakeNode creates a fake node in c.Nodes map using the provided node kind and its config. +func createFakeNode(c *clab.CLab, kind string, nodeCfg *types.NodeConfig) error { + name := nodeCfg.ShortName + // construct node + n, err := c.Reg.NewNodeOfKind(kind) + if err != nil { + return fmt.Errorf("error constructing node %s: %v", name, err) + } + + // Init + err = n.Init(nodeCfg, nodes.WithRuntime(c.GlobalRuntime())) + if err != nil { + return fmt.Errorf("failed to initialize node %s: %v", name, err) + } + + // fake node is always assumed to be deployed in case of tools veth command + n.SetState(state.Deployed) + + c.Nodes[name] = n + + return nil } diff --git a/links/endpoint.go b/links/endpoint.go index 0d45d7681..f4877d109 100644 --- a/links/endpoint.go +++ b/links/endpoint.go @@ -43,13 +43,14 @@ type EndpointGeneric struct { randName string } -func NewEndpointGeneric(node Node, iface string) *EndpointGeneric { +func NewEndpointGeneric(node Node, iface string, link Link) *EndpointGeneric { return &EndpointGeneric{ Node: node, IfaceName: iface, // random name is generated for the endpoint to avoid name collisions // when it is first deployed in the root namespace randName: genRandomIfName(), + Link: link, } } diff --git a/links/endpoint_raw.go b/links/endpoint_raw.go index fc6eef724..08ff1a4f4 100644 --- a/links/endpoint_raw.go +++ b/links/endpoint_raw.go @@ -36,8 +36,7 @@ func (er *EndpointRaw) Resolve(params *ResolveParams, l Link) (Endpoint, error) return nil, fmt.Errorf("unable to find node %s", er.Node) } - genericEndpoint := NewEndpointGeneric(node, er.Iface) - genericEndpoint.Link = l + genericEndpoint := NewEndpointGeneric(node, er.Iface, l) var err error if er.MAC == "" { diff --git a/links/link_host.go b/links/link_host.go index 93b9330bc..125fad7ff 100644 --- a/links/link_host.go +++ b/links/link_host.go @@ -66,9 +66,8 @@ func (r *LinkHostRaw) Resolve(params *ResolveParams) (Link, error) { return nil, err } hostEp := &EndpointHost{ - EndpointGeneric: *NewEndpointGeneric(GetHostLinkNode(), r.HostInterface), + EndpointGeneric: *NewEndpointGeneric(GetHostLinkNode(), r.HostInterface, link), } - hostEp.Link = link hostEp.MAC, err = utils.GenMac(ClabOUI) if err != nil { diff --git a/links/link_macvlan.go b/links/link_macvlan.go index faea26609..97018e4c8 100644 --- a/links/link_macvlan.go +++ b/links/link_macvlan.go @@ -63,9 +63,13 @@ func (r *LinkMacVlanRaw) Resolve(params *ResolveParams) (Link, error) { return nil, nil } + link := &LinkMacVlan{ + LinkCommonParams: r.LinkCommonParams, + } ep := &EndpointMacVlan{ - EndpointGeneric: *NewEndpointGeneric(GetHostLinkNode(), r.HostInterface), + EndpointGeneric: *NewEndpointGeneric(GetHostLinkNode(), r.HostInterface, link), } + link.HostEndpoint = ep var err error ep.MAC, err = utils.GenMac(ClabOUI) @@ -73,11 +77,6 @@ func (r *LinkMacVlanRaw) Resolve(params *ResolveParams) (Link, error) { return nil, err } - link := &LinkMacVlan{ - LinkCommonParams: r.LinkCommonParams, - HostEndpoint: ep, - } - ep.Link = link // parse the MacVlanMode mode, err := MacVlanModeParse(r.Mode) if err != nil { diff --git a/links/link_mgmt-net.go b/links/link_mgmt-net.go index 12afcfeae..7b179b3d6 100644 --- a/links/link_mgmt-net.go +++ b/links/link_mgmt-net.go @@ -46,9 +46,8 @@ func (r *LinkMgmtNetRaw) Resolve(params *ResolveParams) (Link, error) { mgmtBridgeNode := GetMgmtBrLinkNode() bridgeEp := &EndpointBridge{ - EndpointGeneric: *NewEndpointGeneric(mgmtBridgeNode, r.HostInterface), + EndpointGeneric: *NewEndpointGeneric(mgmtBridgeNode, r.HostInterface, link), } - bridgeEp.Link = link var err error bridgeEp.MAC, err = utils.GenMac(ClabOUI) diff --git a/mocks/mocknodes/node.go b/mocks/mocknodes/node.go index a8d6284b5..b9b95663a 100644 --- a/mocks/mocknodes/node.go +++ b/mocks/mocknodes/node.go @@ -381,6 +381,18 @@ func (mr *MockNodeMockRecorder) SaveConfig(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveConfig", reflect.TypeOf((*MockNode)(nil).SaveConfig), arg0) } +// SetState mocks base method. +func (m *MockNode) SetState(arg0 state.NodeState) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetState", arg0) +} + +// SetState indicates an expected call of SetState. +func (mr *MockNodeMockRecorder) SetState(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetState", reflect.TypeOf((*MockNode)(nil).SetState), arg0) +} + // UpdateConfigWithRuntimeInfo mocks base method. func (m *MockNode) UpdateConfigWithRuntimeInfo(arg0 context.Context) error { m.ctrl.T.Helper() diff --git a/nodes/linux/linux.go b/nodes/linux/linux.go index ae0509558..6ce254dd0 100644 --- a/nodes/linux/linux.go +++ b/nodes/linux/linux.go @@ -42,7 +42,7 @@ func (n *linux) Init(cfg *types.NodeConfig, opts ...nodes.NodeOption) error { // make ipv6 enabled on all linux node interfaces // but not for the nodes with host network mode, as this is not supported on gh action runners - if n.Config().NetworkMode != "host" { + if cfg.Sysctls != nil && n.Config().NetworkMode != "host" { cfg.Sysctls["net.ipv6.conf.all.disable_ipv6"] = "0" } diff --git a/nodes/node.go b/nodes/node.go index e45540ecf..c9d0a7f62 100644 --- a/nodes/node.go +++ b/nodes/node.go @@ -106,6 +106,7 @@ type Node interface { // ExecFunction executes the given function within the nodes network namespace ExecFunction(func(ns.NetNS) error) error GetState() state.NodeState + SetState(state.NodeState) } type NodeOption func(Node) diff --git a/tests/01-smoke/08-tools-cmds.robot b/tests/01-smoke/08-tools-cmds.robot index 73d7ebdcc..4e0556d15 100644 --- a/tests/01-smoke/08-tools-cmds.robot +++ b/tests/01-smoke/08-tools-cmds.robot @@ -13,6 +13,7 @@ Suite Teardown Run sudo -E ${CLAB_BIN} --runtime ${runtime} destroy -t $ *** Variables *** +${runtime} docker ${lab-name} 2-linux-nodes ${topo} ${CURDIR}/01-linux-nodes.clab.yml @@ -25,22 +26,9 @@ Deploy ${lab-name} lab Log ${output} Should Be Equal As Integers ${rc} 0 -Create new veth pair between nodes - ${rc} ${output} = Run And Return Rc And Output - ... sudo -E ${CLAB_BIN} --runtime ${runtime} tools veth create -a clab-${lab-name}-l1:eth63 -b clab-${lab-name}-l2:eth63 - Log ${output} - Should Be Equal As Integers ${rc} 0 - -Check the new interface has been created - ${rc} ${output} = Run And Return Rc And Output - ... sudo ip netns exec clab-${lab-name}-l1 ip l show dev eth63 - Log ${output} - Should Be Equal As Integers ${rc} 0 - Should Contain ${output} eth63 - Add link impairments ${rc} ${output} = Run And Return Rc And Output - ... sudo -E ${CLAB_BIN} --runtime ${runtime} tools netem set -n clab-${lab-name}-l1 -i eth63 --delay 100ms --jitter 2ms --loss 10 --rate 1000 + ... sudo -E ${CLAB_BIN} --runtime ${runtime} tools netem set -n clab-${lab-name}-l1 -i eth3 --delay 100ms --jitter 2ms --loss 10 --rate 1000 Log ${output} Should Be Equal As Integers ${rc} 0 Should Contain ${output} 100ms diff --git a/tests/01-smoke/12-tools-veth.clab.yaml b/tests/01-smoke/12-tools-veth.clab.yaml new file mode 100644 index 000000000..3d1597a95 --- /dev/null +++ b/tests/01-smoke/12-tools-veth.clab.yaml @@ -0,0 +1,15 @@ +# Copyright 2023 Nokia +# Licensed under the BSD 3-Clause License. +# SPDX-License-Identifier: BSD-3-Clause + +name: dual-node + +topology: + nodes: + n1: + kind: linux + image: alpine:latest + + n2: + kind: linux + image: alpine:latest diff --git a/tests/01-smoke/12-tools-veth.robot b/tests/01-smoke/12-tools-veth.robot new file mode 100644 index 000000000..98a3f5f54 --- /dev/null +++ b/tests/01-smoke/12-tools-veth.robot @@ -0,0 +1,114 @@ +*** Settings *** +Library OperatingSystem +Library String +Library Process +Resource ../common.robot + +Suite Setup Run Keyword Setup +Suite Teardown Run Keyword Teardown + + +*** Variables *** +${lab-file} 12-tools-veth.clab.yaml +${lab-name} dual-node +${runtime} docker +${bridge-name} clabtestbr +${bridge-n1-iface} n1eth1 +${host-n1-iface} n1hosteth1 +# runtime command to execute tasks in a container +# defaults to docker exec. Will be rewritten to containerd `ctr` if needed in "Define runtime exec" test +${runtime-cli-exec-cmd} sudo docker exec + + +*** Test Cases *** +Deploy ${lab-name} lab + Log ${CURDIR} + ${rc} ${output} = Run And Return Rc And Output + ... sudo ${CLAB_BIN} --runtime ${runtime} deploy -t ${CURDIR}/${lab-file} + Log ${output} + Should Be Equal As Integers ${rc} 0 + # save output to be used in next steps + Set Suite Variable ${deploy-output} ${output} + +Define runtime exec command + IF "${runtime}" == "podman" + Set Suite Variable ${runtime-cli-exec-cmd} sudo podman exec + END + +Verify links in node n1 pre-deploy + ${rc} ${output} = Run And Return Rc And Output + ... ${runtime-cli-exec-cmd} clab-${lab-name}-n1 ip link show eth0 + Log ${output} + Should Be Equal As Integers ${rc} 0 + Should Contain ${output} state UP + +Deploy veth between bridge and n1 + Log ${CURDIR} + ${rc} ${output} = Run And Return Rc And Output + ... sudo ${CLAB_BIN} --runtime ${runtime} tools veth create -d -a clab-${lab-name}-n1:eth1 -b bridge:${bridge-name}:${bridge-n1-iface} + Log ${output} + Should Be Equal As Integers ${rc} 0 + +Verify links in node n1 post-deploy + ${rc} ${output} = Run And Return Rc And Output + ... ${runtime-cli-exec-cmd} clab-${lab-name}-n1 ip link show eth1 + Log ${output} + Should Be Equal As Integers ${rc} 0 + Should Contain ${output} state UP + +Verify bridge link + ${rc} ${output} = Run And Return Rc And Output + ... ip l show ${bridge-n1-iface} + Log ${output} + Should Contain ${output} master ${bridge-name} state UP + +Deploy veth between n1 and n2 + Log ${CURDIR} + ${rc} ${output} = Run And Return Rc And Output + ... sudo ${CLAB_BIN} --runtime ${runtime} tools veth create -d -a clab-${lab-name}-n1:eth2 -b clab-${lab-name}-n2:eth2 + Log ${output} + Should Be Equal As Integers ${rc} 0 + +Verify links in node n1 post-deploy 2 + ${rc} ${output} = Run And Return Rc And Output + ... ${runtime-cli-exec-cmd} clab-${lab-name}-n1 ip link show eth2 + Log ${output} + Should Be Equal As Integers ${rc} 0 + Should Contain ${output} state UP + +Verify links in node n2 post-deploy + ${rc} ${output} = Run And Return Rc And Output + ... ${runtime-cli-exec-cmd} clab-${lab-name}-n2 ip link show eth2 + Log ${output} + Should Be Equal As Integers ${rc} 0 + Should Contain ${output} state UP + +Deploy veth between n2 and host + Log ${CURDIR} + ${rc} ${output} = Run And Return Rc And Output + ... sudo ${CLAB_BIN} --runtime ${runtime} tools veth create -d -a clab-${lab-name}-n2:eth3 -b host:${host-n1-iface} + Log ${output} + Should Be Equal As Integers ${rc} 0 + +Verify links in node n2 post-deploy 2 + ${rc} ${output} = Run And Return Rc And Output + ... ${runtime-cli-exec-cmd} clab-${lab-name}-n2 ip link show eth3 + Log ${output} + Should Be Equal As Integers ${rc} 0 + Should Contain ${output} state UP + +Verify links in host 2 + ${rc} ${output} = Run And Return Rc And Output + ... ip link show dev ${host-n1-iface} + Log ${output} + Should Be Equal As Integers ${rc} 0 + Should Contain ${output} state UP + + +*** Keywords *** +Setup + Run sudo ip l add dev ${bridge-name} type bridge + +Teardown + Run sudo ${CLAB_BIN} --runtime ${runtime} destroy -t ${CURDIR}/${lab-file} --cleanup + Run sudo ip l del dev ${bridge-name}