Repository: containernetworking/cni
Pull request #963 (2023-02-17 00:03:50)

Porting over getCachedNetworkInfo routine from cri-o
Port over `getCachedNetworkInfo` routine from cri-o/ocicni to CNI library.

`getCachedNetworkInfo` routine is currently implemented in [cri-o/ocicni](https://github.com/cri-o/ocicni/blob/master/pkg/ocicni/ocicni.go#L576-L642).

I think it would be useful to have this routine in CNI library and shared with other consumers. One use case is the network GC feature that I am working on for containerd, this routine can be used to provide a list of all network attachments known to CNI during GC.

henry118: @dcbw PTAL if this makes sense. thanks!
coveralls: 
[![Coverage Status](https://coveralls.io/builds/57058735/badge)](https://coveralls.io/builds/57058735)

Coverage: 72.451% (+0.2%) from 72.252% when pulling **036b7039973853493e58133db8ecf182f2339f3d on henry118:cachelist** into **d42556829e9338a2a7111d1b2713d3e007d07b23 on containernetworking:main**.


Patch:
diff --git a/libcni/api.go b/libcni/api.go
index 4296f3a..935cfa1 100644
--- a/libcni/api.go
+++ b/libcni/api.go
@@ -26,6 +26,7 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"sort"
 	"strings"
 
 	"github.com/containernetworking/cni/pkg/invoke"
@@ -78,6 +79,16 @@ type NetworkConfigList struct {
 	Bytes        []byte
 }
 
+type NetworkAttachment struct {
+	ContainerID    string
+	Network        string
+	IfName         string
+	Config         []byte
+	NetNS          string
+	CniArgs        [][2]string
+	CapabilityArgs map[string]interface{}
+}
+
 type CNI interface {
 	AddNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) (types.Result, error)
 	CheckNetworkList(ctx context.Context, net *NetworkConfigList, rt *RuntimeConf) error
@@ -93,6 +104,8 @@ type CNI interface {
 
 	ValidateNetworkList(ctx context.Context, net *NetworkConfigList) ([]string, error)
 	ValidateNetwork(ctx context.Context, net *NetworkConfig) ([]string, error)
+
+	GetCachedAttachments(containerID string) ([]*NetworkAttachment, error)
 }
 
 type CNIConfig struct {
@@ -196,6 +209,7 @@ type cachedInfo struct {
 	Config         []byte                 `json:"config"`
 	IfName         string                 `json:"ifName"`
 	NetworkName    string                 `json:"networkName"`
+	NetNS          string                 `json:"netns,omitempty"`
 	CniArgs        [][2]string            `json:"cniArgs,omitempty"`
 	CapabilityArgs map[string]interface{} `json:"capabilityArgs,omitempty"`
 	RawResult      map[string]interface{} `json:"result,omitempty"`
@@ -230,6 +244,7 @@ func (c *CNIConfig) cacheAdd(result types.Result, config []byte, netName string,
 		Config:         config,
 		IfName:         rt.IfName,
 		NetworkName:    netName,
+		NetNS:          rt.NetNS,
 		CniArgs:        rt.Args,
 		CapabilityArgs: rt.CapabilityArgs,
 	}
@@ -391,6 +406,65 @@ func (c *CNIConfig) GetNetworkCachedConfig(net *NetworkConfig, rt *RuntimeConf)
 	return c.getCachedConfig(net.Network.Name, rt)
 }
 
+// GetCachedAttachments returns a list of network attachments from the cache.
+// The returned list will be filtered by the containerID if the value is not empty.
+func (c *CNIConfig) GetCachedAttachments(containerID string) ([]*NetworkAttachment, error) {
+	dirPath := filepath.Join(c.getCacheDir(&RuntimeConf{}), "results")
+	entries, err := os.ReadDir(dirPath)
+	if err != nil {
+		return nil, err
+	}
+
+	fileNames := make([]string, 0, len(entries))
+	for _, e := range entries {
+		fileNames = append(fileNames, e.Name())
+	}
+	sort.Strings(fileNames)
+
+	attachments := []*NetworkAttachment{}
+	for _, fname := range fileNames {
+		if len(containerID) > 0 {
+			part := fmt.Sprintf("-%s-", containerID)
+			pos := strings.Index(fname, part)
+			if pos <= 0 || pos+len(part) >= len(fname) {
+				continue
+			}
+		}
+
+		cacheFile := filepath.Join(dirPath, fname)
+		bytes, err := os.ReadFile(cacheFile)
+		if err != nil {
+			continue
+		}
+
+		cachedInfo := cachedInfo{}
+
+		if err := json.Unmarshal(bytes, &cachedInfo); err != nil {
+			continue
+		}
+		if cachedInfo.Kind != CNICacheV1 {
+			continue
+		}
+		if len(containerID) > 0 && cachedInfo.ContainerID != containerID {
+			continue
+		}
+		if cachedInfo.IfName == "" || cachedInfo.NetworkName == "" {
+			continue
+		}
+
+		attachments = append(attachments, &NetworkAttachment{
+			ContainerID:    cachedInfo.ContainerID,
+			Network:        cachedInfo.NetworkName,
+			IfName:         cachedInfo.IfName,
+			Config:         cachedInfo.Config,
+			NetNS:          cachedInfo.NetNS,
+			CniArgs:        cachedInfo.CniArgs,
+			CapabilityArgs: cachedInfo.CapabilityArgs,
+		})
+	}
+	return attachments, nil
+}
+
 func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {
 	c.ensureExec()
 	pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path)


Test patch:
diff --git a/libcni/api_test.go b/libcni/api_test.go
index 408b591..11f4ea6 100644
--- a/libcni/api_test.go
+++ b/libcni/api_test.go
@@ -409,6 +409,27 @@ var _ = Describe("Invoking plugins", func() {
 				returnedJson, err := json.Marshal(result)
 				Expect(err).NotTo(HaveOccurred())
 				Expect(cachedJson).To(MatchJSON(returnedJson))
+
+				// Ensure the cached attachments matches requested one
+				for _, containerID := range []string{"", runtimeConfig.ContainerID} {
+					expected, err := json.Marshal(libcni.NetworkAttachment{
+						ContainerID:    runtimeConfig.ContainerID,
+						Network:        netConfig.Network.Name,
+						NetNS:          runtimeConfig.NetNS,
+						IfName:         runtimeConfig.IfName,
+						Config:         netConfig.Bytes,
+						CniArgs:        runtimeConfig.Args,
+						CapabilityArgs: runtimeConfig.CapabilityArgs,
+					})
+					Expect(err).NotTo(HaveOccurred())
+					attachments, err := cniConfig.GetCachedAttachments(containerID)
+					Expect(err).NotTo(HaveOccurred())
+					if Expect(len(attachments)).To(Equal(1)) {
+						json, err := json.Marshal(attachments[0])
+						Expect(err).NotTo(HaveOccurred())
+						Expect(json).To(MatchJSON(expected))
+					}
+				}
 			})
 
 			Context("when finding the plugin fails", func() {
@@ -686,6 +707,13 @@ var _ = Describe("Invoking plugins", func() {
 				Expect(err).NotTo(HaveOccurred())
 				Expect(cachedConfig).To(BeNil())
 				Expect(newRt).To(BeNil())
+
+				// Ensure the cached attachments no longer exist
+				for _, containerID := range []string{"", runtimeConfig.ContainerID} {
+					attachments, err := cniConfig.GetCachedAttachments(containerID)
+					Expect(err).NotTo(HaveOccurred())
+					Expect(len(attachments)).To(Equal(0))
+				}
 			})
 
 			Context("when finding the plugin fails", func() {