Skip to content

Commit

Permalink
New link structs from raw (#1475)
Browse files Browse the repository at this point in the history
* fix reconfigure on exited nodes

* update

* update

* update

* update

* update

* update

* fix

* update

* update

* update

* update

* CheckInterfaceName to use NWEndpoints

* update

* update

* update

* update

* MacvlanMtuOnSRL

* update

* resolve link-mgmt-net-raw to linkveth

* refactor veth bridge attachment processing

* link validation

* cumulate validation errors

* fix

* fix mgmt bridge

* fix test

* fix

* on destroy also resolve links

* fix iptablesrules destroy cleanup

* removed yaml type error during unmarshalling since the error was not present

* rename pointer receiver

* LinkConfig -> LinkBrief

and moved it to its own file from the topology.go

* added make target to create a bin suitable for debug

* added Resolve to LinkBrief

* added link common param to a test

* put mtu on the right level

* function rename to avoid meanings clash

* move links to their own package

* renamed LinkInterf to Link and added comments

* rename Endpt -> Endpoint

* rename endpoint structs

* give LinkNodeResolver name to the interface

* more renaming and comments

* renamed NWEndpoints to Endpoints

* added comments for Link interface methods

* renamed linkNodeResolver to Node

* delay initilaisation of the mgmt-net bridge name

* removed endpoints from endpoin's verify as it is not used

* sort unexported fields

* renamed endpoint uniqueness check

* added same link case

* splice endpoints to different files

* remove node filtering test cases that operated on links

* set iface name for xrd ifEnvVar

* added comment for runtime.Node interface

* renamed SetupNetworking to DeployLinks

* changed error msg

* do not use endpoints to deploy links

* add links to only "real" nodes

* added comment

* move net creation after redeploy

* set link deployment state

* added node state

* remove commented methods and fields

* add linkcommonparams to initial LinkDefinition struct

* set deployment state upon node deployment finish

* remove unused link deployment state

* switch string based statuses to enum

* catch unmarshall type error extend tests to extended link definition format

* choose different interface for tools command test

* added links to genericlinknode

* strict unmarshal error bypass

* add unittest

* add tests, fix state race

* fix

* move GenericLinkNode to own file, resolve raw host link to veth link

* remove commented out code

* implement vethCleanup

* fix podman

* deepsource fix

* fix unittest

* Revert "deepsource fix"

This reverts commit 4ed320b.

* deepsource skip line

* fix json export

* remove mocked runtime

* fix graph

* make generate use brief link format

* reintroduce log message for link creation

* update

* remove mutex from veth link

* run e2e tests with race

* added cgo for debug bin

* bump scrapligo

* Update links/link_veth.go

Co-authored-by: Roman Dodin <dodin.roman@gmail.com>

* Update links/link_macvlan.go

Co-authored-by: Roman Dodin <dodin.roman@gmail.com>

* DefaultNode Nodestate mutex to RWMutex

* logrus -> log

* added endpoint mutex

* Revert "added endpoint mutex"

This reverts commit 935b5f5.

* Introduce NewGenericEndpoint() to generate randifnames on construction

* please deepsource

* adjust link creation log message

* explicit string method is not required

* implicit break implied

* added comments for veth endpoint deployment routine

* move host and mgmt-net nodes to appropriate files

* reintroduce state link mutex

* renamed AddLinkToContainer

* exclude fake name from host and mgmt-net link nodes

* regen mocks

* removed unused custom marshaller for vethraw

* renamed ToLinkBriefRaw to match the return type

* renamed linkVEthRawFromLinkBriefRaw

* added some bad test for veth resolve

* fix import

* removed unused test topology

* verifyRootNetNSLinks

* removed unused receivers and args

---------

Co-authored-by: Roman Dodin <dodin.roman@gmail.com>
  • Loading branch information
steiler and hellt committed Aug 14, 2023
1 parent ba86eca commit 0b023dc
Show file tree
Hide file tree
Showing 87 changed files with 2,811 additions and 972 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ jobs:
${{ runner.os }}-go-
- name: Build containerlab
run: make build-with-podman BINARY=containerlab
run: make build-with-podman-debug BINARY=containerlab
# store clab binary as artifact
- uses: actions/upload-artifact@v3
with:
Expand Down
15 changes: 12 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,30 @@ build-with-cover:
mkdir -p $(BIN_DIR)
go build -cover -o $(BINARY) -ldflags="$(LDFLAGS)" main.go

build-debug:
mkdir -p $(BIN_DIR)
go build -o $(BINARY) -gcflags=all="-N -l" -race main.go


build-with-podman:
mkdir -p $(BIN_DIR)
CGO_ENABLED=0 go build -o $(BINARY) -ldflags="$(LDFLAGS)" -trimpath -tags "podman exclude_graphdriver_btrfs btrfs_noversion exclude_graphdriver_devicemapper exclude_graphdriver_overlay containers_image_openpgp" main.go

build-with-podman-debug:
mkdir -p $(BIN_DIR)
CGO_ENABLED=1 go build -o $(BINARY) -gcflags=all="-N -l" -race -trimpath -tags "podman exclude_graphdriver_btrfs btrfs_noversion exclude_graphdriver_devicemapper exclude_graphdriver_overlay containers_image_openpgp" main.go

test:
go test -race ./... -v

MOCKDIR = ./mocks
.PHONY: mocks-gen
mocks-gen: mocks-rm ## Generate mocks for all the defined interfaces.
go install github.com/golang/mock/mockgen@v1.6.0
mockgen -package=mocks -source=nodes/node.go -destination=$(MOCKDIR)/node.go
mockgen -package=mocknodes -source=nodes/node.go -destination=$(MOCKDIR)/mocknodes/node.go
mockgen -package=mocks -source=clab/dependency_manager/dependency_manager.go -destination=$(MOCKDIR)/dependency_manager.go
mockgen -package=mocks -source=runtime/runtime.go -destination=$(MOCKDIR)/runtime.go
mockgen -package=mocks -source=nodes/default_node.go -destination=$(MOCKDIR)/default_node.go
mockgen -package=mockruntime -source=runtime/runtime.go -destination=$(MOCKDIR)/mockruntime/runtime.go
mockgen -package=mocknodes -source=nodes/default_node.go -destination=$(MOCKDIR)/mocknodes/default_node.go
mockgen -package=mocks -source=clab/exec/exec.go -destination=$(MOCKDIR)/exec.go

.PHONY: mocks-rm
Expand Down
12 changes: 6 additions & 6 deletions border0_api/border0_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

"github.com/golang/mock/gomock"
"github.com/h2non/gock"
"github.com/srl-labs/containerlab/mocks"
"github.com/srl-labs/containerlab/mocks/mocknodes"
"github.com/srl-labs/containerlab/nodes"
"github.com/srl-labs/containerlab/types"
)
Expand Down Expand Up @@ -211,7 +211,7 @@ func Test_createBorder0Config(t *testing.T) {
// getNodeMap return a map of nodes for testing purpose.
func getNodeMap(mockCtrl *gomock.Controller) map[string]nodes.Node {
// instantiate Mock Node 1
mockNode1 := mocks.NewMockNode(mockCtrl)
mockNode1 := mocknodes.NewMockNode(mockCtrl)
mockNode1.EXPECT().Config().Return(
&types.NodeConfig{
Image: "alpine:3",
Expand All @@ -221,7 +221,7 @@ func getNodeMap(mockCtrl *gomock.Controller) map[string]nodes.Node {
).AnyTimes()

// instantiate Mock Node 2
mockNode2 := mocks.NewMockNode(mockCtrl)
mockNode2 := mocknodes.NewMockNode(mockCtrl)
mockNode2.EXPECT().Config().Return(
&types.NodeConfig{
Image: "alpine:3",
Expand All @@ -236,7 +236,7 @@ func getNodeMap(mockCtrl *gomock.Controller) map[string]nodes.Node {
).AnyTimes()

// instantiate Mock Node 3
mockNode3 := mocks.NewMockNode(mockCtrl)
mockNode3 := mocknodes.NewMockNode(mockCtrl)
mockNode3.EXPECT().Config().Return(
&types.NodeConfig{
Image: "alpine:3",
Expand All @@ -248,7 +248,7 @@ func getNodeMap(mockCtrl *gomock.Controller) map[string]nodes.Node {
).AnyTimes()

// instantiate Mock Node 4
mockNode4 := mocks.NewMockNode(mockCtrl)
mockNode4 := mocknodes.NewMockNode(mockCtrl)
mockNode4.EXPECT().Config().Return(
&types.NodeConfig{
Image: "alpine:3",
Expand All @@ -259,7 +259,7 @@ func getNodeMap(mockCtrl *gomock.Controller) map[string]nodes.Node {
).AnyTimes()

// instantiate Mock Node 5
mockNode5 := mocks.NewMockNode(mockCtrl)
mockNode5 := mocknodes.NewMockNode(mockCtrl)
mockNode5.EXPECT().Config().Return(
&types.NodeConfig{
Image: "alpine:3",
Expand Down
167 changes: 100 additions & 67 deletions clab/clab.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/srl-labs/containerlab/cert"
"github.com/srl-labs/containerlab/clab/dependency_manager"
errs "github.com/srl-labs/containerlab/errors"
"github.com/srl-labs/containerlab/links"
"github.com/srl-labs/containerlab/nodes"
"github.com/srl-labs/containerlab/runtime"
_ "github.com/srl-labs/containerlab/runtime/all"
Expand All @@ -25,17 +26,15 @@ import (
"github.com/srl-labs/containerlab/types"
"golang.org/x/crypto/ssh"
"golang.org/x/exp/slices"
"golang.org/x/sync/semaphore"
)

type CLab struct {
Config *Config `json:"config,omitempty"`
TopoPaths *types.TopoPaths
m *sync.RWMutex
Nodes map[string]nodes.Node `json:"nodes,omitempty"`
Links map[int]*types.Link `json:"links,omitempty"`
Runtimes map[string]runtime.ContainerRuntime `json:"runtimes,omitempty"`
globalRuntime string
Config *Config `json:"config,omitempty"`
TopoPaths *types.TopoPaths
Nodes map[string]nodes.Node `json:"nodes,omitempty"`
Links map[int]links.Link `json:"links,omitempty"`
Endpoints []links.Endpoint
Runtimes map[string]runtime.ContainerRuntime `json:"runtimes,omitempty"`
// reg is a registry of node kinds
Reg *nodes.NodeRegistry
Cert *cert.Cert
Expand All @@ -44,7 +43,9 @@ type CLab struct {
// The keys are used to enable key-based SSH access for the nodes.
SSHPubKeys []ssh.PublicKey

timeout time.Duration
m *sync.RWMutex
timeout time.Duration
globalRuntime string
}

type ClabOption func(c *CLab) error
Expand Down Expand Up @@ -165,16 +166,6 @@ func filterClabNodes(c *CLab, nodeFilter []string) error {
}
}

// filter links
for id, l := range c.Links {
for _, nodeName := range []string{l.A.Node.ShortName, l.B.Node.ShortName} {
// if both endpoints of a link belong to the node filter, keep the link
if !slices.Contains(nodeFilter, nodeName) {
delete(c.Links, id)
break
}
}
}
return nil
}

Expand All @@ -187,7 +178,7 @@ func NewContainerLab(opts ...ClabOption) (*CLab, error) {
},
m: new(sync.RWMutex),
Nodes: make(map[string]nodes.Node),
Links: make(map[int]*types.Link),
Links: make(map[int]links.Link),
Runtimes: make(map[string]runtime.ContainerRuntime),
Cert: &cert.Cert{},
}
Expand Down Expand Up @@ -423,6 +414,12 @@ func (c *CLab) scheduleNodes(ctx context.Context, maxWorkers int,
continue
}

err = node.DeployLinks(ctx)
if err != nil {
log.Errorf("failed deploy links for node %q: %v", node.Config().ShortName, err)
continue
}

// signal to dependency manager that this node is done with creation
dm.SignalDone(node.Config().ShortName, dependency_manager.NodeStateCreated)

Expand Down Expand Up @@ -506,49 +503,6 @@ func (c *CLab) WaitForExternalNodeDependencies(ctx context.Context, nodeName str
runtime.WaitForContainerRunning(ctx, c.Runtimes[c.globalRuntime], contName, nodeName)
}

// CreateLinks creates links using the specified number of workers.
func (c *CLab) CreateLinks(ctx context.Context, workers uint, dm dependency_manager.DependencyManager) {
wg := new(sync.WaitGroup)
sem := semaphore.NewWeighted(int64(workers))

for _, link := range c.Links {
wg.Add(1)
go func(li *types.Link) {
defer wg.Done()

var waitNodes []string
for _, n := range []*types.NodeConfig{li.A.Node, li.B.Node} {
// we should not wait for "host", "mgmt-net" and "macvlan" fake nodes
// as they are never managed by dependency manager (never really get created)
if n.Kind == "host" || n.ShortName == "mgmt-net" || n.Kind == "macvlan" {
continue
}
waitNodes = append(waitNodes, n.ShortName)
}

err := dm.WaitForNodes(waitNodes, dependency_manager.NodeStateCreated)
if err != nil {
log.Error(err)
}

// acquire Sem
err = sem.Acquire(ctx, 1)
if err != nil {
log.Error(err)
}
defer sem.Release(1)
// create the wiring
err = c.CreateVirtualWiring(li)
if err != nil {
log.Error(err)
}
}(link)
}

// wait for all workers to finish
wg.Wait()
}

func (c *CLab) DeleteNodes(ctx context.Context, workers uint, serialNodes map[string]struct{}) {
wg := new(sync.WaitGroup)

Expand Down Expand Up @@ -632,6 +586,21 @@ func (c *CLab) ListNodesContainers(ctx context.Context) ([]runtime.GenericContai
return containers, nil
}

// ListNodesContainersIgnoreNotFound lists all containers based on the nodes stored in clab instance, ignoring errors for non found containers
func (c *CLab) ListNodesContainersIgnoreNotFound(ctx context.Context) ([]runtime.GenericContainer, error) {
var containers []runtime.GenericContainer

for _, n := range c.Nodes {
cts, err := n.GetContainers(ctx)
if err != nil {
continue
}
containers = append(containers, cts...)
}

return containers, nil
}

func (c *CLab) GetNodeRuntime(contName string) (runtime.ContainerRuntime, error) {
shortName, err := getShortName(c.Config.Name, c.Config.Prefix, contName)
if err != nil {
Expand All @@ -648,12 +617,76 @@ func (c *CLab) GetNodeRuntime(contName string) (runtime.ContainerRuntime, error)
// VethCleanup iterates over links found in clab topology to initiate removal of dangling veths
// in host networking namespace or attached to linux bridge.
// See https://github.com/srl-labs/containerlab/issues/842 for the reference.
func (c *CLab) VethCleanup(_ context.Context) error {
for _, link := range c.Links {
err := c.RemoveHostOrBridgeVeth(link)
func (c *CLab) VethCleanup(ctx context.Context) error {
hostBasedEndpoints := []links.Endpoint{}

// collect the endpoints of regular nodes
for _, n := range c.Nodes {
if n.Config().IsRootNamespaceBased || n.Config().NetworkMode == "host" {
hostBasedEndpoints = append(hostBasedEndpoints, n.GetEndpoints()...)
}
}

// collect the endpoints of the fake nodes
hostBasedEndpoints = append(hostBasedEndpoints, links.GetHostLinkNode().GetEndpoints()...)
hostBasedEndpoints = append(hostBasedEndpoints, links.GetMgmtBrLinkNode().GetEndpoints()...)

var joinedErr error
for _, ep := range hostBasedEndpoints {
// finally remove all the collected endpoints
log.Debugf("removing endpoint %s", ep.String())
err := ep.Remove()
if err != nil {
joinedErr = errors.Join(joinedErr, err)
}
}

return joinedErr
}

// ResolveLinks resolves raw links to the actual link types and stores them in the CLab.Links map.
func (c *CLab) ResolveLinks() error {
// resolveNodes is a map of all nodes in the topology
// that is artificially created to combat circular dependencies.
// If no circ deps were in place we could've used c.Nodes map instead.
// The map is used to resolve links between the nodes by passing it in the ResolveParams struct.
resolveNodes := make(map[string]links.Node, len(c.Nodes))
for k, v := range c.Nodes {
resolveNodes[k] = v
}

// add the virtual host and mgmt-bridge nodes to the resolve nodes
specialNodes := map[string]links.Node{
"host": links.GetHostLinkNode(),
"mgmt-net": links.GetMgmtBrLinkNode(),
}
for _, n := range specialNodes {
resolveNodes[n.GetShortName()] = n
}

resolveParams := &links.ResolveParams{
Nodes: resolveNodes,
MgmtBridgeName: c.Config.Mgmt.Bridge,
}

for i, l := range c.Config.Topology.Links {
l, err := l.Link.Resolve(resolveParams)
if err != nil {
log.Infof("Error during veth cleanup: %v", err)
return err
}

c.Endpoints = append(c.Endpoints, l.GetEndpoints()...)
c.Links[i] = l

// add the link to the nodes connected with it
for _, ep := range l.GetEndpoints() {
// check if node is in the list of c.Nodes
// this will skip fake endpoints like host and mgmt-net
if n, ok := c.Nodes[ep.GetNode().GetShortName()]; ok {
n.AddLink(l)
}
}
}

return nil
}
Loading

0 comments on commit 0b023dc

Please sign in to comment.