Skip to content

Commit 7148cd8

Browse files
feat: add HostConfig to container inspect response (runfinch#324)
* feat: add `HostConfig` to container inspect response Signed-off-by: Swapnanil-Gupta <swpnlg@amazon.com> * Changes: - Add devices to hostconfig - Add e2e tests for hostconfig Signed-off-by: Swapnanil-Gupta <swpnlg@amazon.com> --------- Signed-off-by: Swapnanil-Gupta <swpnlg@amazon.com>
1 parent ef5d71d commit 7148cd8

File tree

5 files changed

+223
-7
lines changed

5 files changed

+223
-7
lines changed

api/types/container_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ type Container struct {
178178
// TODO: ProcessLabel string
179179
AppArmorProfile string
180180
// TODO: ExecIDs []string
181-
// TODO: HostConfig *container.HostConfig
181+
HostConfig *ContainerHostConfig
182182
// TODO: GraphDriver GraphDriverData
183183
SizeRw *int64 `json:",omitempty"`
184184
SizeRootFs *int64 `json:",omitempty"`

e2e/e2e_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func runContainerTests(opt *option.Option, pOpt func([]string, ...option.Modifie
136136
tests.ContainerAttach(opt)
137137
tests.ContainerLogs(opt)
138138
tests.ContainerKill(opt)
139-
tests.ContainerInspect(opt)
139+
tests.ContainerInspect(opt, pOpt)
140140
tests.ContainerWait(opt)
141141
tests.ContainerPause(opt)
142142
}

e2e/tests/container_inspect.go

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,19 @@ import (
99
"io"
1010
"net/http"
1111

12+
"github.com/docker/go-connections/nat"
1213
. "github.com/onsi/ginkgo/v2"
1314
. "github.com/onsi/gomega"
1415
"github.com/runfinch/common-tests/command"
1516
"github.com/runfinch/common-tests/option"
1617

1718
"github.com/runfinch/finch-daemon/api/types"
1819
"github.com/runfinch/finch-daemon/e2e/client"
20+
"github.com/runfinch/finch-daemon/e2e/util"
1921
)
2022

2123
// ContainerInspect tests the `GET containers/{id}/json` API.
22-
func ContainerInspect(opt *option.Option) {
24+
func ContainerInspect(opt *option.Option, pOpt util.NewOpt) {
2325
Describe("inspect container", func() {
2426
var (
2527
uClient *http.Client
@@ -74,6 +76,96 @@ func ContainerInspect(opt *option.Option) {
7476
Expect(got.SizeRootFs).ShouldNot(BeNil())
7577
})
7678

79+
It("should return hostconfig with proper values", func() {
80+
// setup cpu/mem options
81+
createOptions := types.ContainerCreateRequest{}
82+
createOptions.Image = defaultImage
83+
createOptions.Cmd = []string{"sleep", "Infinity"}
84+
createOptions.HostConfig.ShmSize = 134217728
85+
createOptions.HostConfig.CPUSetCPUs = "0,1"
86+
createOptions.HostConfig.CPUSetMems = "0"
87+
createOptions.HostConfig.CPUShares = 2048
88+
createOptions.HostConfig.CPUPeriod = 100000
89+
createOptions.HostConfig.Memory = 134217728
90+
createOptions.HostConfig.MemorySwap = 514288000
91+
92+
// setup port mappings
93+
hostPort := "8001"
94+
ctrPort := "8000"
95+
hostPort2 := "9001"
96+
ctrPort2 := "9000"
97+
tcpPort := nat.Port(fmt.Sprintf("%s/tcp", ctrPort))
98+
tcpPortBinding := nat.PortBinding{HostIP: "0.0.0.0", HostPort: hostPort}
99+
udpPort := nat.Port(fmt.Sprintf("%s/udp", ctrPort2))
100+
udpPortBinding := nat.PortBinding{HostIP: "0.0.0.0", HostPort: hostPort2}
101+
createOptions.HostConfig.PortBindings = nat.PortMap{
102+
tcpPort: []nat.PortBinding{tcpPortBinding},
103+
udpPort: []nat.PortBinding{udpPortBinding},
104+
}
105+
106+
// setup devices
107+
tmpFileOpt, _ := pOpt([]string{"touch", "/tmp/loopdev"})
108+
command.Run(tmpFileOpt)
109+
defer func() {
110+
rmOpt, _ := pOpt([]string{"rm", "-f", "/tmp/loopdev"})
111+
command.Run(rmOpt)
112+
}()
113+
ddOpt, _ := pOpt([]string{"dd", "if=/dev/zero", "of=/tmp/loopdev", "bs=4096", "count=1"})
114+
command.Run(ddOpt)
115+
loopDevOpt, _ := pOpt([]string{"losetup", "-f", "--show", "/tmp/loopdev"})
116+
loopDev := command.StdoutStr(loopDevOpt)
117+
Expect(loopDev).ShouldNot(BeEmpty())
118+
defer func() {
119+
detachOpt, _ := pOpt([]string{"losetup", "-d", loopDev})
120+
command.Run(detachOpt)
121+
}()
122+
createOptions.HostConfig.Devices = []types.DeviceMapping{
123+
{
124+
PathOnHost: loopDev,
125+
PathInContainer: loopDev,
126+
CgroupPermissions: "rwm",
127+
},
128+
}
129+
130+
// create and start container with options
131+
containerCreateUrl := client.ConvertToFinchUrl(version, "/containers/create")
132+
statusCode, ctr := createContainer(uClient, containerCreateUrl, testContainerName2, createOptions)
133+
Expect(statusCode).Should(Equal(http.StatusCreated))
134+
Expect(ctr.ID).ShouldNot(BeEmpty())
135+
command.Run(opt, "start", testContainerName2)
136+
137+
// inspect container
138+
res, err := uClient.Get(client.ConvertToFinchUrl(version, fmt.Sprintf("/containers/%s/json", ctr.ID)))
139+
Expect(err).Should(BeNil())
140+
Expect(res.StatusCode).Should(Equal(http.StatusOK))
141+
var got types.Container
142+
err = json.NewDecoder(res.Body).Decode(&got)
143+
Expect(err).Should(BeNil())
144+
145+
// verify hostconfig
146+
Expect(got.HostConfig).ShouldNot(BeNil())
147+
Expect(got.HostConfig.ContainerIDFile).Should(BeEmpty())
148+
Expect(got.HostConfig.PidMode).Should(BeEmpty())
149+
Expect(got.HostConfig.IpcMode).Should(Equal("private"))
150+
Expect(got.HostConfig.ReadonlyRootfs).Should(BeFalse())
151+
Expect(got.HostConfig.Sysctls).Should(BeEmpty())
152+
Expect(got.HostConfig.Devices).Should(HaveLen(1))
153+
Expect(got.HostConfig.Devices[0]).Should(Equal(createOptions.HostConfig.Devices[0]))
154+
Expect(got.HostConfig.ShmSize).Should(Equal(createOptions.HostConfig.ShmSize))
155+
Expect(got.HostConfig.CPUSetCPUs).Should(Equal(createOptions.HostConfig.CPUSetCPUs))
156+
Expect(got.HostConfig.CPUSetMems).Should(Equal(createOptions.HostConfig.CPUSetMems))
157+
Expect(got.HostConfig.CPUShares).Should(Equal(createOptions.HostConfig.CPUShares))
158+
Expect(got.HostConfig.CPUPeriod).Should(Equal(createOptions.HostConfig.CPUPeriod))
159+
Expect(got.HostConfig.Memory).Should(Equal(createOptions.HostConfig.Memory))
160+
Expect(got.HostConfig.MemorySwap).Should(Equal(createOptions.HostConfig.MemorySwap))
161+
Expect(got.HostConfig.PortBindings).Should(HaveLen(2))
162+
Expect(got.HostConfig.PortBindings[tcpPort]).Should(HaveLen(1))
163+
Expect(got.HostConfig.PortBindings[tcpPort]).Should(HaveLen(1))
164+
Expect(got.HostConfig.PortBindings[tcpPort][0]).Should(Equal(tcpPortBinding))
165+
Expect(got.HostConfig.PortBindings[udpPort]).Should(HaveLen(1))
166+
Expect(got.HostConfig.PortBindings[udpPort][0]).Should(Equal(udpPortBinding))
167+
})
168+
77169
It("should return 404 error when container does not exist", func() {
78170
res, err := uClient.Get(client.ConvertToFinchUrl(version, "/containers/nonexistent/json"))
79171
Expect(err).Should(BeNil())

internal/service/container/inspect.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ func (s *service) Inspect(ctx context.Context, cid string, sizeFlag bool) (*type
6565
Labels: inspect.Config.Labels,
6666
}
6767

68+
cont.HostConfig = getHostConfigFromDockerCompat(inspect.HostConfig)
69+
6870
l, err := c.Labels(ctx)
6971
if err != nil {
7072
return nil, fmt.Errorf("failed to get container labels: %s", err)
@@ -83,6 +85,47 @@ func (s *service) Inspect(ctx context.Context, cid string, sizeFlag bool) (*type
8385
return &cont, nil
8486
}
8587

88+
func getHostConfigFromDockerCompat(c *dockercompat.HostConfig) *types.ContainerHostConfig {
89+
if c == nil {
90+
return nil
91+
}
92+
93+
hostConfigDevices := []types.DeviceMapping{}
94+
for _, device := range c.Devices {
95+
hostConfigDevices = append(hostConfigDevices, types.DeviceMapping{
96+
PathOnHost: device.PathOnHost,
97+
PathInContainer: device.PathInContainer,
98+
CgroupPermissions: device.CgroupPermissions,
99+
})
100+
}
101+
102+
return &types.ContainerHostConfig{
103+
ContainerIDFile: c.ContainerIDFile,
104+
LogConfig: types.LogConfig{
105+
Type: c.LogConfig.Driver,
106+
Config: c.LogConfig.Opts,
107+
},
108+
PortBindings: c.PortBindings,
109+
IpcMode: c.IpcMode,
110+
PidMode: c.PidMode,
111+
ReadonlyRootfs: c.ReadonlyRootfs,
112+
ShmSize: c.ShmSize,
113+
Sysctls: c.Sysctls,
114+
CPUSetMems: c.CPUSetMems,
115+
CPUSetCPUs: c.CPUSetCPUs,
116+
CPUShares: int64(c.CPUShares),
117+
CPUPeriod: int64(c.CPUPeriod),
118+
Memory: c.Memory,
119+
MemorySwap: c.MemorySwap,
120+
OomKillDisable: c.OomKillDisable,
121+
Devices: hostConfigDevices,
122+
// TODO: Uncomment these when https://github.com/runfinch/finch-daemon/pull/267 gets merged
123+
// CPURealtimePeriod: inspect.HostConfig.CPURealtimePeriod,
124+
// CPURealtimeRuntime: inspect.HostConfig.CPURealtimeRuntime,
125+
// TODO: add blkio devices which requires a change in the dockercompat response from Nerdctl
126+
}
127+
}
128+
86129
// updateNetworkSettings updates the settings in the network to match that
87130
// of docker as docker identifies networks by their name in "NetworkSettings",
88131
// but nerdctl uses a sequential ordering "unknown-eth0", "unknown-eth1",...

internal/service/container/inspect_test.go

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import (
99

1010
containerd "github.com/containerd/containerd/v2/client"
1111
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
12-
"go.uber.org/mock/gomock"
12+
"github.com/docker/go-connections/nat"
1313
. "github.com/onsi/ginkgo/v2"
1414
. "github.com/onsi/gomega"
15+
"go.uber.org/mock/gomock"
1516

1617
"github.com/runfinch/finch-daemon/api/handlers/container"
1718
"github.com/runfinch/finch-daemon/api/types"
@@ -86,13 +87,95 @@ var _ = Describe("Container Inspect API ", func() {
8687

8788
ncClient.EXPECT().InspectContainer(gomock.Any(), con, sizeFlag).Return(
8889
&inspect, nil)
89-
9090
con.EXPECT().Labels(gomock.Any()).Return(nil, nil)
9191
result, err := service.Inspect(ctx, cid, sizeFlag)
9292

9393
Expect(*result).Should(Equal(ret))
9494
Expect(err).Should(BeNil())
9595
})
96+
It("should return inspect object with HostConfig", func() {
97+
inspectWithHostConfig := inspect
98+
inspectWithHostConfig.HostConfig = &dockercompat.HostConfig{
99+
ContainerIDFile: "test-container-id-file",
100+
// dockercompat.loggerLogConfig is not exported
101+
PortBindings: nat.PortMap{
102+
"80/tcp": []nat.PortBinding{
103+
{
104+
HostIP: "localhost",
105+
HostPort: "8080",
106+
},
107+
},
108+
},
109+
ShmSize: 0,
110+
IpcMode: "testIpcMode",
111+
PidMode: "testPidMode",
112+
ReadonlyRootfs: false,
113+
Sysctls: map[string]string{
114+
"test": "test",
115+
},
116+
CPUSetMems: "testCPUSetMems",
117+
CPUSetCPUs: "testCPUSetCPUs",
118+
CPUShares: 0,
119+
CPUPeriod: 0,
120+
Memory: 0,
121+
MemorySwap: 0,
122+
OomKillDisable: false,
123+
Devices: []dockercompat.DeviceMapping{
124+
{
125+
PathOnHost: "",
126+
PathInContainer: "",
127+
CgroupPermissions: "",
128+
},
129+
},
130+
}
131+
132+
retWithHostConfig := ret
133+
retWithHostConfig.HostConfig = &types.ContainerHostConfig{
134+
ContainerIDFile: "test-container-id-file",
135+
PortBindings: nat.PortMap{
136+
"80/tcp": []nat.PortBinding{
137+
{
138+
HostIP: "localhost",
139+
HostPort: "8080",
140+
},
141+
},
142+
},
143+
ShmSize: 0,
144+
IpcMode: "testIpcMode",
145+
PidMode: "testPidMode",
146+
ReadonlyRootfs: false,
147+
Sysctls: map[string]string{
148+
"test": "test",
149+
},
150+
CPUSetMems: "testCPUSetMems",
151+
CPUSetCPUs: "testCPUSetCPUs",
152+
CPUShares: 0,
153+
CPUPeriod: 0,
154+
Memory: 0,
155+
MemorySwap: 0,
156+
OomKillDisable: false,
157+
Devices: []types.DeviceMapping{
158+
{
159+
PathOnHost: "",
160+
PathInContainer: "",
161+
CgroupPermissions: "",
162+
},
163+
},
164+
}
165+
166+
// search container method returns one container
167+
cdClient.EXPECT().SearchContainer(gomock.Any(), cid).Return(
168+
[]containerd.Container{con}, nil)
169+
170+
ncClient.EXPECT().InspectContainer(gomock.Any(), con, false).Return(
171+
&inspectWithHostConfig, nil)
172+
173+
con.EXPECT().Labels(gomock.Any()).Return(nil, nil)
174+
result, err := service.Inspect(ctx, cid, false)
175+
176+
Expect(*result).Should(Equal(retWithHostConfig))
177+
Expect(err).Should(BeNil())
178+
})
96179
It("should return NotFound error if container was not found", func() {
97180
// search container method returns no container
98181
cdClient.EXPECT().SearchContainer(gomock.Any(), cid).Return(
@@ -155,7 +238,6 @@ var _ = Describe("Container Inspect API ", func() {
155238

156239
ncClient.EXPECT().InspectContainer(gomock.Any(), con, sizeFlag).Return(
157240
&inspectWithSize, nil)
158-
159241
con.EXPECT().Labels(gomock.Any()).Return(nil, nil)
160242
result, err := service.Inspect(ctx, cid, sizeFlag)
161243
Expect(err).Should(BeNil())
@@ -173,7 +255,6 @@ var _ = Describe("Container Inspect API ", func() {
173255

174256
ncClient.EXPECT().InspectContainer(gomock.Any(), con, sizeFlag).Return(
175257
&inspect, nil)
176-
177258
con.EXPECT().Labels(gomock.Any()).Return(nil, nil)
178259
result, err := service.Inspect(ctx, cid, sizeFlag)
179260
Expect(err).Should(BeNil())

0 commit comments

Comments
 (0)