From 288e86701cdeb2454ff71764e5868ccea155d112 Mon Sep 17 00:00:00 2001 From: Sam Chew Date: Fri, 16 Aug 2024 09:54:46 -0700 Subject: [PATCH] Include init NetworkSettings within inspect response Related to [containerd#3310](https://github.com/containerd/nerdctl/issues/3310) New behavior will always initialize a NetworkSettings entity for the inspect response, including Ports child member. If inspecting a running container with published ports, then all information will be correctly returned. If inspecting a running container without published ports, then NetworkSettings will include an initialized Ports member. If inspecting a stopped/exited container, then an entirely initialized, "empty-value" NetworkSettings is returned. Signed-off-by: Sam Chew --- pkg/inspecttypes/dockercompat/dockercompat.go | 16 ++- .../dockercompat/dockercompat_test.go | 125 ++++++++++++++++++ 2 files changed, 136 insertions(+), 5 deletions(-) diff --git a/pkg/inspecttypes/dockercompat/dockercompat.go b/pkg/inspecttypes/dockercompat/dockercompat.go index 16c1a91a282..f1704f1781a 100644 --- a/pkg/inspecttypes/dockercompat/dockercompat.go +++ b/pkg/inspecttypes/dockercompat/dockercompat.go @@ -186,7 +186,7 @@ type ContainerState struct { } type NetworkSettings struct { - Ports *nat.PortMap `json:",omitempty"` + Ports *nat.PortMap DefaultNetworkSettings Networks map[string]*NetworkEndpointSettings } @@ -398,12 +398,15 @@ func statusFromNative(x containerd.Status, labels map[string]string) string { } func networkSettingsFromNative(n *native.NetNS, sp *specs.Spec) (*NetworkSettings, error) { - if n == nil { - return nil, nil - } res := &NetworkSettings{ Networks: make(map[string]*NetworkEndpointSettings), } + resPortMap := make(nat.PortMap) + res.Ports = &resPortMap + if n == nil { + return res, nil + } + var primary *NetworkEndpointSettings for _, x := range n.Interfaces { if x.Interface.Flags&net.FlagLoopback != 0 { @@ -447,8 +450,11 @@ func networkSettingsFromNative(n *native.NetNS, sp *specs.Spec) (*NetworkSetting if err != nil { return nil, err } - res.Ports = nports + for portLabel, portBindings := range *nports { + resPortMap[portLabel] = portBindings + } } + if x.Index == n.PrimaryInterface { primary = nes } diff --git a/pkg/inspecttypes/dockercompat/dockercompat_test.go b/pkg/inspecttypes/dockercompat/dockercompat_test.go index efca61c087e..38ddfa3447d 100644 --- a/pkg/inspecttypes/dockercompat/dockercompat_test.go +++ b/pkg/inspecttypes/dockercompat/dockercompat_test.go @@ -17,11 +17,14 @@ package dockercompat import ( + "fmt" + "net" "os" "path/filepath" "runtime" "testing" + "github.com/docker/go-connections/nat" "github.com/opencontainers/runtime-spec/specs-go" "gotest.tools/v3/assert" @@ -91,6 +94,10 @@ func TestContainerFromNative(t *testing.T) { }, Hostname: "host1", }, + NetworkSettings: &NetworkSettings{ + Ports: &nat.PortMap{}, + Networks: map[string]*NetworkEndpointSettings{}, + }, }, }, // cri container, mount /mnt/foo:/mnt/foo:rw,rslave; mount resolv.conf and hostname; internal sysfs mount @@ -172,6 +179,10 @@ func TestContainerFromNative(t *testing.T) { // ignore sysfs mountpoint }, Config: &Config{}, + NetworkSettings: &NetworkSettings{ + Ports: &nat.PortMap{}, + Networks: map[string]*NetworkEndpointSettings{}, + }, }, }, // ctr container, mount /mnt/foo:/mnt/foo:rw,rslave; internal sysfs mount; hostname @@ -226,12 +237,126 @@ func TestContainerFromNative(t *testing.T) { Config: &Config{ Hostname: "host1", }, + NetworkSettings: &NetworkSettings{ + Ports: &nat.PortMap{}, + Networks: map[string]*NetworkEndpointSettings{}, + }, }, }, } for _, tc := range testcase { + fmt.Fprintln(os.Stderr, tc.name) d, _ := ContainerFromNative(tc.n) assert.DeepEqual(t, d, tc.expected) } } + +func TestNetworkSettingsFromNative(t *testing.T) { + tempStateDir, err := os.MkdirTemp(t.TempDir(), "rw") + if err != nil { + t.Fatal(err) + } + os.WriteFile(filepath.Join(tempStateDir, "resolv.conf"), []byte(""), 0644) + defer os.RemoveAll(tempStateDir) + + testcase := []struct { + name string + n *native.NetNS + s *specs.Spec + expected *NetworkSettings + }{ + // Given null native.NetNS, Return initialized NetworkSettings + // UseCase: Inspect a Stopped Container + { + name: "Given Null NetNS, Return initialized NetworkSettings", + n: nil, + s: &specs.Spec{}, + expected: &NetworkSettings{ + Ports: &nat.PortMap{}, + Networks: map[string]*NetworkEndpointSettings{}, + }, + }, + // Given native.NetNS with single Interface with Port Annotations, Return populated NetworkSettings + // UseCase: Inspect a Running Container with published ports + { + name: "Given NetNS with single Interface with Port Annotation, Return populated NetworkSettings", + n: &native.NetNS{ + Interfaces: []native.NetInterface{ + { + Interface: net.Interface{ + Index: 1, + MTU: 1500, + Name: "eth0.100", + Flags: net.FlagUp, + }, + HardwareAddr: "xx:xx:xx:xx:xx:xx", + Flags: []string{}, + Addrs: []string{"10.0.4.30/24"}, + }, + }, + }, + s: &specs.Spec{ + Annotations: map[string]string{ + "nerdctl/ports": "[{\"HostPort\":8075,\"ContainerPort\":77,\"Protocol\":\"tcp\",\"HostIP\":\"127.0.0.1\"}]", + }, + }, + expected: &NetworkSettings{ + Ports: &nat.PortMap{ + nat.Port("77/tcp"): []nat.PortBinding{ + { + HostIP: "127.0.0.1", + HostPort: "8075", + }, + }, + }, + Networks: map[string]*NetworkEndpointSettings{ + "unknown-eth0.100": { + IPAddress: "10.0.4.30", + IPPrefixLen: 24, + MacAddress: "xx:xx:xx:xx:xx:xx", + }, + }, + }, + }, + // Given native.NetNS with single Interface without Port Annotations, Return populated NetworkSettings + // UseCase: Inspect a Running Container without published ports + { + name: "Given NetNS with single Interface without Port Annotations, Return populated NetworkSettings", + n: &native.NetNS{ + Interfaces: []native.NetInterface{ + { + Interface: net.Interface{ + Index: 1, + MTU: 1500, + Name: "eth0.100", + Flags: net.FlagUp, + }, + HardwareAddr: "xx:xx:xx:xx:xx:xx", + Flags: []string{}, + Addrs: []string{"10.0.4.30/24"}, + }, + }, + }, + s: &specs.Spec{ + Annotations: map[string]string{}, + }, + expected: &NetworkSettings{ + Ports: &nat.PortMap{}, + Networks: map[string]*NetworkEndpointSettings{ + "unknown-eth0.100": { + IPAddress: "10.0.4.30", + IPPrefixLen: 24, + MacAddress: "xx:xx:xx:xx:xx:xx", + }, + }, + }, + }, + } + + for _, tc := range testcase { + fmt.Fprintln(os.Stderr, tc.name) + d, _ := networkSettingsFromNative(tc.n, tc.s) + assert.DeepEqual(t, d, tc.expected) + } +}