From 87621fef22b2aa5d8ec9255405b6872e84cdbcaa Mon Sep 17 00:00:00 2001 From: Michael Zappa Date: Tue, 1 Aug 2023 21:17:35 -0600 Subject: [PATCH] initial work to support dynamic cni configurations Signed-off-by: Michael Zappa --- cni.go | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++- cni_test.go | 42 ++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) diff --git a/cni.go b/cni.go index b10af47..82a929c 100644 --- a/cni.go +++ b/cni.go @@ -35,8 +35,12 @@ type CNI interface { Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error) // SetupSerially sets up each of the network interfaces for the namespace in serial SetupSerially(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error) + //SetupNetworks sets up a list of networks + SetupNetworks(ctx context.Context, id string, path string, networks []*Network, opts ...NamespaceOpts) (*Result, error) // Remove tears down the network of the namespace. Remove(ctx context.Context, id string, path string, opts ...NamespaceOpts) error + // RemoveNetworks tears down a list of provided networks + RemoveNetworks(ctx context.Context, id string, path string, networks []*Network, opts ...NamespaceOpts) error // Check checks if the network is still in desired state Check(ctx context.Context, id string, path string, opts ...NamespaceOpts) error // Load loads the cni network config @@ -45,6 +49,8 @@ type CNI interface { Status() error // GetConfig returns a copy of the CNI plugin configurations as parsed by CNI GetConfig() *ConfigResult + //BuildCNIMultiNetwork returns a list of networks that match the network name in the cni cache + BuildCNIMultiNetwork(networkNames []*NetworkInterface) []*Network } type ConfigResult struct { @@ -83,6 +89,11 @@ type libcni struct { sync.RWMutex } +type NetworkInterface struct { + NetworkName string + InterfaceName string +} + func defaultCNIConfig() *libcni { return &libcni{ config: config{ @@ -183,15 +194,62 @@ func (c *libcni) SetupSerially(ctx context.Context, id string, path string, opts return c.createResult(result) } +// BuildCNIMultiNetwork build dynamic list of Networks. Order Matters here! +func (c *libcni) BuildCNIMultiNetwork(networkNames []*NetworkInterface) []*Network { + var network []*Network + ifaceindex := 0 + for _, net := range c.Networks() { + for _, x := range networkNames { + if net.config.Name == x.NetworkName { + if x.InterfaceName == "" { + net.ifName = getIfName(c.prefix, ifaceindex) + } else { + net.ifName = x.InterfaceName + } + //Doing this to ensure first selected cni conf ifname is eth0 + if net.ifName != "lo" { + ifaceindex++ + } + + //TODO - Add logic to make sure interface collisions don't happen. However that could be an implementation detail. + network = append(network, net) + } + } + } + + return network +} + +// SetupSerially setups specific networks in the namespace and returns a Result +func (c *libcni) SetupNetworks(ctx context.Context, id string, path string, networks []*Network, opts ...NamespaceOpts) (*Result, error) { + if err := c.Status(); err != nil { + return nil, err + } + ns, err := newNamespace(id, path, opts...) + if err != nil { + return nil, err + } + result, err := c.attachWithMultipleNetworksSerially(ctx, ns, networks) + if err != nil { + return nil, err + } + return c.createResult(result) +} + func (c *libcni) attachNetworksSerially(ctx context.Context, ns *Namespace) ([]*types100.Result, error) { + return c.attachWithMultipleNetworksSerially(ctx, ns, c.Networks()) +} + +func (c *libcni) attachWithMultipleNetworksSerially(ctx context.Context, ns *Namespace, networks []*Network) ([]*types100.Result, error) { var results []*types100.Result - for _, network := range c.Networks() { + for _, network := range networks { r, err := network.Attach(ctx, ns) if err != nil { return nil, err } results = append(results, r) } + return results, nil } @@ -258,6 +316,33 @@ func (c *libcni) Remove(ctx context.Context, id string, path string, opts ...Nam return nil } +func (c *libcni) RemoveNetworks(ctx context.Context, id string, path string, networks []*Network, opts ...NamespaceOpts) error { + if err := c.Status(); err != nil { + return err + } + ns, err := newNamespace(id, path, opts...) + if err != nil { + return err + } + for i := len(networks) - 1; i >= 0; i-- { + if err := networks[i].Remove(ctx, ns); err != nil { + // Based on CNI spec v0.7.0, empty network namespace is allowed to + // do best effort cleanup. However, it is not handled consistently + // right now: + // https://github.com/containernetworking/plugins/issues/210 + // TODO(random-liu): Remove the error handling when the issue is + // fixed and the CNI spec v0.6.0 support is deprecated. + // NOTE(claudiub): Some CNIs could return a "not found" error, which could mean that + // it was already deleted. + if (path == "" && strings.Contains(err.Error(), "no such file or directory")) || strings.Contains(err.Error(), "not found") { + continue + } + return err + } + } + return nil +} + // Check checks if the network is still in desired state func (c *libcni) Check(ctx context.Context, id string, path string, opts ...NamespaceOpts) error { if err := c.Status(); err != nil { diff --git a/cni_test.go b/cni_test.go index c2b0b2f..9fb73d6 100644 --- a/cni_test.go +++ b/cni_test.go @@ -285,6 +285,48 @@ func TestLibCNIType100(t *testing.T) { assert.NoError(t, err) } +func TestBuildNetworksWithAnnotation(t *testing.T) { + // Get the default CNI config + l := defaultCNIConfig() + // Create a fake cni config directory and file + cniDir, confDir := makeFakeCNIConfig(t) + defer tearDownCNIConfig(t, cniDir) + l.pluginConfDir = confDir + // Set the minimum network count as 2 for this test + l.networkCount = 2 + err := l.Load(WithLoNetwork, WithAllConf) + assert.NoError(t, err) + + err = l.Status() + assert.NoError(t, err) + + net := []*NetworkInterface{ + { + NetworkName: "plugin2", + InterfaceName: "net1", + }, + { + NetworkName: "cni-loopback", + InterfaceName: "lo", + }, + { + NetworkName: "plugin1", + //InterfaceName: "This should be commented out" + }, + } + + networks := l.BuildCNIMultiNetwork(net) + + assert.Equal(t, len(networks), 3) + + assert.Equal(t, "lo", networks[0].ifName) + assert.Equal(t, "cni-loopback", networks[0].config.Name) + assert.Equal(t, "eth0", networks[1].ifName) + assert.Equal(t, "plugin1", networks[1].config.Name) + assert.Equal(t, "net1", networks[2].ifName) + assert.Equal(t, "plugin2", networks[2].config.Name) +} + type MockCNI struct { mock.Mock }