Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial work to support dynamic multiple CNI configurations #113

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 101 additions & 1 deletion cni.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -45,6 +49,8 @@ type CNI interface {
Status() error
// GetConfig returns a copy of the CNI plugin configurations as parsed by CNI
GetConfig() *ConfigResult
//BuildMultiNetwork returns a list of networks that match the network name in the cni cache
BuildMultiNetwork(networkNames []*NetworkInterface) ([]*Network, error)
}

type ConfigResult struct {
Expand Down Expand Up @@ -83,6 +89,11 @@ type libcni struct {
sync.RWMutex
}

type NetworkInterface struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am thinking through this type. It works in the current however give me some time to debate. If anyone likes the current state lmk

NetworkName string
InterfaceName string
}

func defaultCNIConfig() *libcni {
return &libcni{
config: config{
Expand Down Expand Up @@ -183,15 +194,77 @@ func (c *libcni) SetupSerially(ctx context.Context, id string, path string, opts
return c.createResult(result)
}

// BuildMultiNetwork build dynamic list of Networks.
func (c *libcni) BuildMultiNetwork(networkNames []*NetworkInterface) ([]*Network, error) {
var network []*Network
ifaceindex := 0

config := make(map[string]*Network)
ifs := make(map[string]*Network)

for _, v := range c.Networks() {
config[v.config.Name] = v
}

name := ""
for _, v := range networkNames {
if net, ok := config[v.NetworkName]; ok {
if v.InterfaceName == "" {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if a network has two or more interfaces?

name = getIfName(c.prefix, ifaceindex)
ifaceindex++
} else {
name = v.InterfaceName
}

network = append(network, &Network{
cni: net.cni,
config: net.config,
ifName: name,
})

if _, ok := ifs[name]; ok {
return nil, fmt.Errorf("the interface: %v already exists and must be unique", name)
}

ifs[name] = net
} else {
return nil, fmt.Errorf("the network config: %v does not exist", v.NetworkName)
}
}

return network, nil
}

// 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
}

Expand Down Expand Up @@ -258,6 +331,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 {
Expand Down
125 changes: 125 additions & 0 deletions cni_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,131 @@ 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: "cni-loopback",
InterfaceName: "lo",
},
{
NetworkName: "plugin1",
//InterfaceName: "This should be commented out"
},
{
NetworkName: "plugin2",
InterfaceName: "net1",
},
{
NetworkName: "plugin2",
InterfaceName: "net10",
},
}

networks, err := l.BuildMultiNetwork(net)

assert.NoError(t, err)

if err != nil {
t.Fail()
}

assert.Equal(t, len(networks), 4)

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)

assert.Equal(t, "net10", networks[3].ifName)
assert.Equal(t, "plugin2", networks[3].config.Name)
}

func TestBuildNetworksDuplicateIfName(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: "net10",
},
{
NetworkName: "cni-loopback",
InterfaceName: "lo",
},
{
NetworkName: "plugin1",
//InterfaceName: "This should be commented out"
},
{
NetworkName: "plugin2",
InterfaceName: "net10",
},
}

_, err = l.BuildMultiNetwork(net)

assert.Error(t, err)
assert.EqualError(t, err, "the interface: net10 already exists and must be unique")
}

func TestBuildNetworksMissingNetworkConfig(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: "thisdoesnotexist",
InterfaceName: "net10",
},
}

_, err = l.BuildMultiNetwork(net)

assert.Error(t, err)
assert.EqualError(t, err, "the network config: thisdoesnotexist does not exist")
}

type MockCNI struct {
mock.Mock
}
Expand Down