From 86d1bf0be43eb0d636ebc0e356cb423579231112 Mon Sep 17 00:00:00 2001 From: Henry Wang Date: Thu, 2 Oct 2025 21:40:27 +0000 Subject: [PATCH] Support GC verb introduced in CNI spec 1.1 Signed-off-by: Henry Wang --- cni.go | 18 ++++++++++++++++++ cni_test.go | 15 +++++++++++++++ namespace.go | 12 ++++++++++++ testutils.go | 1 + 4 files changed, 46 insertions(+) diff --git a/cni.go b/cni.go index 70ca382..5abad3a 100644 --- a/cni.go +++ b/cni.go @@ -18,6 +18,7 @@ package cni import ( "context" + "errors" "fmt" "os" "strings" @@ -45,6 +46,8 @@ type CNI interface { Status() error // GetConfig returns a copy of the CNI plugin configurations as parsed by CNI GetConfig() *ConfigResult + // GC cleans up stale resources provided a list of known valid attachments + GC(ctx context.Context, ids []string) error } type ConfigResult struct { @@ -338,3 +341,18 @@ func (c *libcni) ready() error { return nil } + +func (c *libcni) GC(ctx context.Context, ids []string) error { + c.Lock() + defer c.Unlock() + var err error + for _, network := range c.networks { + if network.config.DisableGC { + continue + } + if gcerr := network.GC(ctx, ids); gcerr != nil { + err = errors.Join(err, gcerr) + } + } + return err +} diff --git a/cni_test.go b/cni_test.go index b31c55a..4e8db14 100644 --- a/cni_test.go +++ b/cni_test.go @@ -327,6 +327,17 @@ func TestLibCNIType120(t *testing.T) { CapabilityArgs: map[string]interface{}{}, } + gcArgs := func(c *cnilibrary.RuntimeConf) *cnilibrary.GCArgs { + return &cnilibrary.GCArgs{ + ValidAttachments: []types.GCAttachment{ + { + ContainerID: c.ContainerID, + IfName: c.IfName, + }, + }, + } + } + // mock for loopback mockCNI.On("GetStatusNetworkList", l.networks[0].config).Return(nil) mockCNI.On("AddNetworkList", l.networks[0].config, loRT).Return(&types040.Result{ @@ -347,6 +358,7 @@ func TestLibCNIType120(t *testing.T) { }, nil) mockCNI.On("DelNetworkList", l.networks[0].config, loRT).Return(nil) mockCNI.On("CheckNetworkList", l.networks[0].config, loRT).Return(nil) + mockCNI.On("GCNetworkList", l.networks[0].config, gcArgs(loRT)).Return(nil) // mock for primary cni mockCNI.On("GetStatusNetworkList", l.networks[1].config).Return(nil) @@ -383,6 +395,9 @@ func TestLibCNIType120(t *testing.T) { err = l.Remove(ctx, "container-id1", "/proc/12345/ns/net") assert.NoError(t, err) + + err = l.GC(ctx, []string{"container-id1"}) + assert.NoError(t, err) } func TestLibCNIType120FailStatus(t *testing.T) { diff --git a/namespace.go b/namespace.go index 319182b..1f59de2 100644 --- a/namespace.go +++ b/namespace.go @@ -20,6 +20,7 @@ import ( "context" cnilibrary "github.com/containernetworking/cni/libcni" + "github.com/containernetworking/cni/pkg/types" types100 "github.com/containernetworking/cni/pkg/types/100" ) @@ -45,6 +46,17 @@ func (n *Network) Check(ctx context.Context, ns *Namespace) error { return n.cni.CheckNetworkList(ctx, n.config, ns.config(n.ifName)) } +func (n *Network) GC(ctx context.Context, ids []string) error { + var args cnilibrary.GCArgs + for _, id := range ids { + args.ValidAttachments = append(args.ValidAttachments, types.GCAttachment{ + ContainerID: id, + IfName: n.ifName, + }) + } + return n.cni.GCNetworkList(ctx, n.config, &args) +} + type Namespace struct { id string path string diff --git a/testutils.go b/testutils.go index d195fea..a9abc3e 100644 --- a/testutils.go +++ b/testutils.go @@ -63,6 +63,7 @@ func buildFakeConfig(t *testing.T) (string, string) { { "cniVersion": "1.1.0", "name": "containerd-net", + "disableGC": true, "plugins": [ { "type": "bridge",