Skip to content

Commit 48824a7

Browse files
chews93319Shubhranshu153
authored andcommitted
Include init NetworkSettings within inspect response
Related to [#3310](#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 <[email protected]>
1 parent d97ca7d commit 48824a7

File tree

2 files changed

+336
-5
lines changed

2 files changed

+336
-5
lines changed

pkg/inspecttypes/dockercompat/dockercompat.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ type ContainerState struct {
173173
}
174174

175175
type NetworkSettings struct {
176-
Ports *nat.PortMap `json:",omitempty"`
176+
Ports *nat.PortMap
177177
DefaultNetworkSettings
178178
Networks map[string]*NetworkEndpointSettings
179179
}
@@ -337,12 +337,15 @@ func statusFromNative(x containerd.Status, labels map[string]string) string {
337337
}
338338

339339
func networkSettingsFromNative(n *native.NetNS, sp *specs.Spec) (*NetworkSettings, error) {
340-
if n == nil {
341-
return nil, nil
342-
}
343340
res := &NetworkSettings{
344341
Networks: make(map[string]*NetworkEndpointSettings),
345342
}
343+
resPortMap := make(nat.PortMap)
344+
res.Ports = &resPortMap
345+
if n == nil {
346+
return res, nil
347+
}
348+
346349
var primary *NetworkEndpointSettings
347350
for _, x := range n.Interfaces {
348351
if x.Interface.Flags&net.FlagLoopback != 0 {
@@ -386,8 +389,11 @@ func networkSettingsFromNative(n *native.NetNS, sp *specs.Spec) (*NetworkSetting
386389
if err != nil {
387390
return nil, err
388391
}
389-
res.Ports = nports
392+
for portLabel, portBindings := range *nports {
393+
resPortMap[portLabel] = portBindings
394+
}
390395
}
396+
391397
if x.Index == n.PrimaryInterface {
392398
primary = nes
393399
}
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package dockercompat
18+
19+
import (
20+
"net"
21+
"os"
22+
"path/filepath"
23+
"runtime"
24+
"testing"
25+
26+
"github.com/docker/go-connections/nat"
27+
"github.com/opencontainers/runtime-spec/specs-go"
28+
"gotest.tools/v3/assert"
29+
30+
"github.com/containerd/containerd"
31+
"github.com/containerd/containerd/containers"
32+
33+
"github.com/containerd/nerdctl/pkg/inspecttypes/native"
34+
)
35+
36+
func TestContainerFromNative(t *testing.T) {
37+
tempStateDir, err := os.MkdirTemp(t.TempDir(), "rw")
38+
if err != nil {
39+
t.Fatal(err)
40+
}
41+
os.WriteFile(filepath.Join(tempStateDir, "resolv.conf"), []byte(""), 0644)
42+
defer os.RemoveAll(tempStateDir)
43+
44+
testcase := []struct {
45+
name string
46+
n *native.Container
47+
expected *Container
48+
}{
49+
// nerdctl container, mount /mnt/foo:/mnt/foo:rw,rslave; ResolvConfPath; hostname
50+
{
51+
name: "container from nerdctl",
52+
n: &native.Container{
53+
Container: containers.Container{
54+
Labels: map[string]string{
55+
"nerdctl/mounts": "[{\"Type\":\"bind\",\"Source\":\"/mnt/foo\",\"Destination\":\"/mnt/foo\",\"Mode\":\"rshared,rw\",\"RW\":true,\"Propagation\":\"rshared\"}]",
56+
"nerdctl/state-dir": tempStateDir,
57+
"nerdctl/hostname": "host1",
58+
},
59+
},
60+
Spec: &specs.Spec{},
61+
Process: &native.Process{
62+
Pid: 10000,
63+
Status: containerd.Status{
64+
Status: "running",
65+
},
66+
},
67+
},
68+
expected: &Container{
69+
Created: "0001-01-01T00:00:00Z",
70+
Platform: runtime.GOOS,
71+
ResolvConfPath: tempStateDir + "/resolv.conf",
72+
State: &ContainerState{
73+
Status: "running",
74+
Running: true,
75+
Pid: 10000,
76+
FinishedAt: "0001-01-01T00:00:00Z",
77+
},
78+
Mounts: []MountPoint{
79+
{
80+
Type: "bind",
81+
Source: "/mnt/foo",
82+
Destination: "/mnt/foo",
83+
Mode: "rshared,rw",
84+
RW: true,
85+
Propagation: "rshared",
86+
},
87+
},
88+
Config: &Config{
89+
Labels: map[string]string{
90+
"nerdctl/mounts": "[{\"Type\":\"bind\",\"Source\":\"/mnt/foo\",\"Destination\":\"/mnt/foo\",\"Mode\":\"rshared,rw\",\"RW\":true,\"Propagation\":\"rshared\"}]",
91+
"nerdctl/state-dir": tempStateDir,
92+
"nerdctl/hostname": "host1",
93+
},
94+
Hostname: "host1",
95+
},
96+
NetworkSettings: &NetworkSettings{
97+
Ports: &nat.PortMap{},
98+
Networks: map[string]*NetworkEndpointSettings{},
99+
},
100+
},
101+
},
102+
// cri container, mount /mnt/foo:/mnt/foo:rw,rslave; mount resolv.conf and hostname; internal sysfs mount
103+
{
104+
name: "container from cri",
105+
n: &native.Container{
106+
Container: containers.Container{},
107+
Spec: &specs.Spec{
108+
Mounts: []specs.Mount{
109+
{
110+
Destination: "/etc/resolv.conf",
111+
Type: "bind",
112+
Source: "/mock-sandbox-dir/resolv.conf",
113+
Options: []string{"rbind", "rprivate", "rw"},
114+
},
115+
{
116+
Destination: "/etc/hostname",
117+
Type: "bind",
118+
Source: "/mock-sandbox-dir/hostname",
119+
Options: []string{"rbind", "rprivate", "rw"},
120+
},
121+
{
122+
Destination: "/mnt/foo",
123+
Type: "bind",
124+
Source: "/mnt/foo",
125+
Options: []string{"rbind", "rslave", "rw"},
126+
},
127+
{
128+
Destination: "/sys",
129+
Type: "sysfs",
130+
Source: "sysfs",
131+
Options: []string{"nosuid", "noexec", "nodev", "ro"},
132+
},
133+
},
134+
},
135+
Process: &native.Process{
136+
Pid: 10000,
137+
Status: containerd.Status{
138+
Status: "running",
139+
},
140+
},
141+
},
142+
expected: &Container{
143+
Created: "0001-01-01T00:00:00Z",
144+
Platform: runtime.GOOS,
145+
ResolvConfPath: "",
146+
HostnamePath: "",
147+
State: &ContainerState{
148+
Status: "running",
149+
Running: true,
150+
Pid: 10000,
151+
FinishedAt: "0001-01-01T00:00:00Z",
152+
},
153+
Config: &Config{},
154+
NetworkSettings: &NetworkSettings{
155+
Ports: &nat.PortMap{},
156+
Networks: map[string]*NetworkEndpointSettings{},
157+
},
158+
},
159+
},
160+
// ctr container, mount /mnt/foo:/mnt/foo:rw,rslave; internal sysfs mount; hostname
161+
{
162+
name: "container from ctr",
163+
n: &native.Container{
164+
Container: containers.Container{},
165+
Spec: &specs.Spec{
166+
Hostname: "",
167+
Mounts: []specs.Mount{
168+
{
169+
Destination: "/mnt/foo",
170+
Type: "bind",
171+
Source: "/mnt/foo",
172+
Options: []string{"rbind", "rslave", "rw"},
173+
},
174+
{
175+
Destination: "/sys",
176+
Type: "sysfs",
177+
Source: "sysfs",
178+
Options: []string{"nosuid", "noexec", "nodev", "ro"},
179+
},
180+
},
181+
},
182+
Process: &native.Process{
183+
Pid: 10000,
184+
Status: containerd.Status{
185+
Status: "running",
186+
},
187+
},
188+
},
189+
expected: &Container{
190+
Created: "0001-01-01T00:00:00Z",
191+
Platform: runtime.GOOS,
192+
State: &ContainerState{
193+
Status: "running",
194+
Running: true,
195+
Pid: 10000,
196+
FinishedAt: "0001-01-01T00:00:00Z",
197+
},
198+
Config: &Config{
199+
Hostname: "",
200+
},
201+
NetworkSettings: &NetworkSettings{
202+
Ports: &nat.PortMap{},
203+
Networks: map[string]*NetworkEndpointSettings{},
204+
},
205+
},
206+
},
207+
}
208+
209+
for _, tc := range testcase {
210+
t.Run(tc.name, func(tt *testing.T) {
211+
d, _ := ContainerFromNative(tc.n)
212+
assert.DeepEqual(tt, d, tc.expected)
213+
})
214+
}
215+
}
216+
217+
func TestNetworkSettingsFromNative(t *testing.T) {
218+
tempStateDir, err := os.MkdirTemp(t.TempDir(), "rw")
219+
if err != nil {
220+
t.Fatal(err)
221+
}
222+
os.WriteFile(filepath.Join(tempStateDir, "resolv.conf"), []byte(""), 0644)
223+
defer os.RemoveAll(tempStateDir)
224+
225+
testcase := []struct {
226+
name string
227+
n *native.NetNS
228+
s *specs.Spec
229+
expected *NetworkSettings
230+
}{
231+
// Given null native.NetNS, Return initialized NetworkSettings
232+
// UseCase: Inspect a Stopped Container
233+
{
234+
name: "Given Null NetNS, Return initialized NetworkSettings",
235+
n: nil,
236+
s: &specs.Spec{},
237+
expected: &NetworkSettings{
238+
Ports: &nat.PortMap{},
239+
Networks: map[string]*NetworkEndpointSettings{},
240+
},
241+
},
242+
// Given native.NetNS with single Interface with Port Annotations, Return populated NetworkSettings
243+
// UseCase: Inspect a Running Container with published ports
244+
{
245+
name: "Given NetNS with single Interface with Port Annotation, Return populated NetworkSettings",
246+
n: &native.NetNS{
247+
Interfaces: []native.NetInterface{
248+
{
249+
Interface: net.Interface{
250+
Index: 1,
251+
MTU: 1500,
252+
Name: "eth0.100",
253+
Flags: net.FlagUp,
254+
},
255+
HardwareAddr: "xx:xx:xx:xx:xx:xx",
256+
Flags: []string{},
257+
Addrs: []string{"10.0.4.30/24"},
258+
},
259+
},
260+
},
261+
s: &specs.Spec{
262+
Annotations: map[string]string{
263+
"nerdctl/ports": "[{\"HostPort\":8075,\"ContainerPort\":77,\"Protocol\":\"tcp\",\"HostIP\":\"127.0.0.1\"}]",
264+
},
265+
},
266+
expected: &NetworkSettings{
267+
Ports: &nat.PortMap{
268+
nat.Port("77/tcp"): []nat.PortBinding{
269+
{
270+
HostIP: "127.0.0.1",
271+
HostPort: "8075",
272+
},
273+
},
274+
},
275+
Networks: map[string]*NetworkEndpointSettings{
276+
"unknown-eth0.100": {
277+
IPAddress: "10.0.4.30",
278+
IPPrefixLen: 24,
279+
MacAddress: "xx:xx:xx:xx:xx:xx",
280+
},
281+
},
282+
},
283+
},
284+
// Given native.NetNS with single Interface without Port Annotations, Return valid NetworkSettings w/ empty Ports
285+
// UseCase: Inspect a Running Container without published ports
286+
{
287+
name: "Given NetNS with single Interface without Port Annotations, Return valid NetworkSettings w/ empty Ports",
288+
n: &native.NetNS{
289+
Interfaces: []native.NetInterface{
290+
{
291+
Interface: net.Interface{
292+
Index: 1,
293+
MTU: 1500,
294+
Name: "eth0.100",
295+
Flags: net.FlagUp,
296+
},
297+
HardwareAddr: "xx:xx:xx:xx:xx:xx",
298+
Flags: []string{},
299+
Addrs: []string{"10.0.4.30/24"},
300+
},
301+
},
302+
},
303+
s: &specs.Spec{
304+
Annotations: map[string]string{},
305+
},
306+
expected: &NetworkSettings{
307+
Ports: &nat.PortMap{},
308+
Networks: map[string]*NetworkEndpointSettings{
309+
"unknown-eth0.100": {
310+
IPAddress: "10.0.4.30",
311+
IPPrefixLen: 24,
312+
MacAddress: "xx:xx:xx:xx:xx:xx",
313+
},
314+
},
315+
},
316+
},
317+
}
318+
319+
for _, tc := range testcase {
320+
t.Run(tc.name, func(tt *testing.T) {
321+
d, _ := networkSettingsFromNative(tc.n, tc.s)
322+
assert.DeepEqual(tt, d, tc.expected)
323+
})
324+
}
325+
}

0 commit comments

Comments
 (0)