diff --git a/cni.go b/cni.go index b10af47..003f302 100644 --- a/cni.go +++ b/cni.go @@ -135,11 +135,20 @@ func (c *libcni) Load(opts ...Opt) error { // Status returns the status of CNI initialization. func (c *libcni) Status() error { + if err := c.ready(); err != nil { + return err + } c.RLock() defer c.RUnlock() - if len(c.networks) < c.networkCount { - return ErrCNINotInitialized + // STATUS is only called for CNI Version 1.1.0 or greater. It is ignored for previous versions. + for _, v := range c.networks { + err := c.cniConfig.GetStatusNetworkList(context.Background(), v.config) + + if err != nil { + return err + } } + return nil } @@ -153,9 +162,11 @@ func (c *libcni) Networks() []*Network { // Setup setups the network in the namespace and returns a Result func (c *libcni) Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error) { - if err := c.Status(); err != nil { + if err := c.ready(); err != nil { return nil, err } + c.RLock() + defer c.RUnlock() ns, err := newNamespace(id, path, opts...) if err != nil { return nil, err @@ -169,9 +180,11 @@ func (c *libcni) Setup(ctx context.Context, id string, path string, opts ...Name // SetupSerially setups the network in the namespace and returns a Result func (c *libcni) SetupSerially(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error) { - if err := c.Status(); err != nil { + if err := c.ready(); err != nil { return nil, err } + c.RLock() + defer c.RUnlock() ns, err := newNamespace(id, path, opts...) if err != nil { return nil, err @@ -232,9 +245,11 @@ func (c *libcni) attachNetworks(ctx context.Context, ns *Namespace) ([]*types100 // Remove removes the network config from the namespace func (c *libcni) Remove(ctx context.Context, id string, path string, opts ...NamespaceOpts) error { - if err := c.Status(); err != nil { + if err := c.ready(); err != nil { return err } + c.RLock() + defer c.RUnlock() ns, err := newNamespace(id, path, opts...) if err != nil { return err @@ -260,9 +275,11 @@ func (c *libcni) Remove(ctx context.Context, id string, path string, opts ...Nam // 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 { + if err := c.ready(); err != nil { return err } + c.RLock() + defer c.RUnlock() ns, err := newNamespace(id, path, opts...) if err != nil { return err @@ -310,3 +327,13 @@ func (c *libcni) GetConfig() *ConfigResult { func (c *libcni) reset() { c.networks = nil } + +func (c *libcni) ready() error { + c.RLock() + defer c.RUnlock() + if len(c.networks) < c.networkCount { + return ErrCNINotInitialized + } + + return nil +} diff --git a/cni_test.go b/cni_test.go index ea7d1d5..2abdab2 100644 --- a/cni_test.go +++ b/cni_test.go @@ -18,6 +18,7 @@ package cni import ( "context" + "errors" "net" "testing" @@ -292,21 +293,19 @@ func TestLibCNIType120(t *testing.T) { // Get the default CNI config l := defaultCNIConfig() // Create a fake cni config directory and file - cniDir, confDir := makeFakeCNIConfig(t) + cniDir, confDir := buildFakeConfig(t) defer tearDownCNIConfig(t, cniDir) l.pluginConfDir = confDir // Set the minimum network count as 2 for this test l.networkCount = 2 - err := l.Load(WithAllConf) - assert.NoError(t, err) - - err = l.Status() + err := l.Load(WithLoNetwork, WithDefaultConf) assert.NoError(t, err) mockCNI := &MockCNI{} + l.cniConfig = mockCNI l.networks[0].cni = mockCNI l.networks[1].cni = mockCNI - ipv4, err := types.ParseCIDR("10.0.0.1/24") + ipv4, err := types.ParseCIDR("10.88.0.1/16") assert.NoError(t, err) expectedRT := &cnilibrary.RuntimeConf{ ContainerID: "container-id1", @@ -315,59 +314,65 @@ func TestLibCNIType120(t *testing.T) { Args: [][2]string(nil), CapabilityArgs: map[string]interface{}{}, } - mockCNI.On("AddNetworkList", l.networks[0].config, expectedRT).Return(&types100.Result{ - CNIVersion: "1.1.0", - Interfaces: []*types100.Interface{ + + loRT := &cnilibrary.RuntimeConf{ + ContainerID: "container-id1", + NetNS: "/proc/12345/ns/net", + IfName: "lo", + Args: [][2]string(nil), + CapabilityArgs: map[string]interface{}{}, + } + + // mock for loopback + mockCNI.On("GetStatusNetworkList", l.networks[0].config).Return(nil) + mockCNI.On("AddNetworkList", l.networks[0].config, loRT).Return(&types040.Result{ + CNIVersion: "0.3.1", + Interfaces: []*types040.Interface{ { - Name: "eth0", + Name: "lo", }, }, - IPs: []*types100.IPConfig{ + IPs: []*types040.IPConfig{ { - Interface: types100.Int(0), - Address: *ipv4, - Gateway: net.ParseIP("10.0.0.255"), + Interface: types040.Int(0), + Address: net.IPNet{ + IP: net.ParseIP("127.0.0.1"), + }, }, }, }, nil) - mockCNI.On("DelNetworkList", l.networks[0].config, expectedRT).Return(nil) - mockCNI.On("CheckNetworkList", l.networks[0].config, expectedRT).Return(nil) - ipv4, err = types.ParseCIDR("10.0.0.2/24") - assert.NoError(t, err) - l.networks[1].cni = mockCNI - expectedRT = &cnilibrary.RuntimeConf{ - ContainerID: "container-id1", - NetNS: "/proc/12345/ns/net", - IfName: "eth1", - Args: [][2]string(nil), - CapabilityArgs: map[string]interface{}{}, - } + mockCNI.On("DelNetworkList", l.networks[0].config, loRT).Return(nil) + mockCNI.On("CheckNetworkList", l.networks[0].config, loRT).Return(nil) + + // mock for primary cni + mockCNI.On("GetStatusNetworkList", l.networks[1].config).Return(nil) mockCNI.On("AddNetworkList", l.networks[1].config, expectedRT).Return(&types100.Result{ CNIVersion: "1.1.0", Interfaces: []*types100.Interface{ { - Name: "eth1", + Name: "eth0", }, }, IPs: []*types100.IPConfig{ { Interface: types100.Int(0), Address: *ipv4, - Gateway: net.ParseIP("10.0.0.2"), + Gateway: net.ParseIP("10.88.0.1"), }, }, }, nil) mockCNI.On("DelNetworkList", l.networks[1].config, expectedRT).Return(nil) mockCNI.On("CheckNetworkList", l.networks[1].config, expectedRT).Return(nil) ctx := context.Background() + + err = l.Status() + assert.NoError(t, err) + r, err := l.Setup(ctx, "container-id1", "/proc/12345/ns/net") assert.NoError(t, err) assert.Contains(t, r.Interfaces, "eth0") assert.NotNil(t, r.Interfaces["eth0"].IPConfigs) - assert.Equal(t, r.Interfaces["eth0"].IPConfigs[0].IP.String(), "10.0.0.1") - assert.Contains(t, r.Interfaces, "eth1") - assert.NotNil(t, r.Interfaces["eth1"].IPConfigs) - assert.Equal(t, r.Interfaces["eth1"].IPConfigs[0].IP.String(), "10.0.0.2") + assert.Equal(t, r.Interfaces["eth0"].IPConfigs[0].IP.String(), "10.88.0.1") err = l.Check(ctx, "container-id1", "/proc/12345/ns/net") assert.NoError(t, err) @@ -376,6 +381,30 @@ func TestLibCNIType120(t *testing.T) { assert.NoError(t, err) } +func TestLibCNIType120FailStatus(t *testing.T) { + // Get the default CNI config + l := defaultCNIConfig() + // Create a fake cni config directory and file + cniDir, confDir := buildFakeConfig(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, WithDefaultConf) + assert.NoError(t, err) + + mockCNI := &MockCNI{} + l.cniConfig = mockCNI + l.networks[0].cni = mockCNI + l.networks[1].cni = mockCNI + + mockCNI.On("GetStatusNetworkList", l.networks[0].config).Return(nil) + mockCNI.On("GetStatusNetworkList", l.networks[1].config).Return(errors.New("no ip addresses")) + l.cniConfig = mockCNI + err = l.Status() + assert.Error(t, err) +} + type MockCNI struct { mock.Mock } diff --git a/testutils.go b/testutils.go index 0807e20..c270100 100644 --- a/testutils.go +++ b/testutils.go @@ -75,3 +75,64 @@ func tearDownCNIConfig(t *testing.T, confDir string) { t.Fatalf("Failed to cleanup CNI configs: %v", err) } } + +func buildFakeConfig(t *testing.T) (string, string) { + conf := ` + { + "cniVersion": "1.1.0", + "name": "containerd-net", + "plugins": [ + { + "type": "bridge", + "bridge": "cni0", + "isGateway": true, + "ipMasq": true, + "promiscMode": true, + "ipam": { + "type": "host-ipam", + "ranges": [ + [{ + "subnet": "10.88.0.0/16" + }], + [{ + "subnet": "2001:4860:4860::/64" + }] + ], + "routes": [ + { "dst": "0.0.0.0/0" }, + { "dst": "::/0" } + ] + } + }, + { + "type": "portmap", + "capabilities": {"portMappings": true} + } + ] + }` + + cniDir, err := makeTmpDir("fakecni") + if err != nil { + t.Fatalf("Failed to create plugin config dir: %v", err) + } + + cniConfDir := path.Join(cniDir, "net.d") + err = os.MkdirAll(cniConfDir, 0777) + if err != nil { + t.Fatalf("Failed to create network config dir: %v", err) + } + + networkConfig1 := path.Join(cniConfDir, "mocknetwork1.conflist") + f1, err := os.Create(networkConfig1) + if err != nil { + t.Fatalf("Failed to create network config %v: %v", f1, err) + } + + _, err = f1.WriteString(conf) + if err != nil { + t.Fatalf("Failed to write network config file %v: %v", f1, err) + } + f1.Close() + + return cniDir, cniConfDir +}