From 6f682c19370d07d5c9aa2ef056ed0abe183d0dcb Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Wed, 4 Dec 2024 23:24:12 +0000 Subject: [PATCH 01/24] feat: add support for network disabled flag Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 5 ++++- api/types/container_types.go | 10 +++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 5e1b012f..ea861925 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -260,13 +260,16 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { if networkMode == "" || networkMode == "default" { networkMode = "bridge" } + if req.NetworkDisabled { + networkMode = "none" + } dnsOpt := []string{} if req.HostConfig.DNSOptions != nil { dnsOpt = req.HostConfig.DNSOptions } netOpt := ncTypes.NetworkOptions{ Hostname: req.Hostname, - NetworkSlice: []string{networkMode}, // TODO: Set to none if "NetworkDisabled" is true in request + NetworkSlice: []string{networkMode}, DNSServers: req.HostConfig.DNS, // Custom DNS lookup servers. DNSResolvConfOptions: dnsOpt, // DNS options. DNSSearchDomains: req.HostConfig.DNSSearch, // Custom DNS search domains. diff --git a/api/types/container_types.go b/api/types/container_types.go index 44f44197..f19df7a6 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -42,11 +42,11 @@ type ContainerConfig struct { Cmd []string `json:",omitempty"` // Command to run when starting the container // TODO Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy // TODO: ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (meaning treat as a command line) (Windows specific). - Image string // Name of the image as it was passed by the operator (e.g. could be symbolic) - Volumes map[string]struct{} `json:",omitempty"` // List of volumes (mounts) used for the container - WorkingDir string `json:",omitempty"` // Current directory (PWD) in the command will be launched - Entrypoint []string `json:",omitempty"` // Entrypoint to run when starting the container - // TODO: NetworkDisabled bool `json:",omitempty"` // Is network disabled + Image string // Name of the image as it was passed by the operator (e.g. could be symbolic) + Volumes map[string]struct{} `json:",omitempty"` // List of volumes (mounts) used for the container + WorkingDir string `json:",omitempty"` // Current directory (PWD) in the command will be launched + Entrypoint []string `json:",omitempty"` // Entrypoint to run when starting the container + NetworkDisabled bool `json:",omitempty"` // Is network disabled // TODO: MacAddress string `json:",omitempty"` // Mac Address of the container // TODO: OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile Labels map[string]string `json:",omitempty"` // List of labels set to this container From edfd76d1183b4513fa7579bb5fd832e8bf620773 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Thu, 5 Dec 2024 00:43:31 +0000 Subject: [PATCH 02/24] chore: add support for OomKillDisable flag Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 20 +++++++++++--------- api/types/container_types.go | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index ea861925..027a31f8 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -172,15 +172,17 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { GOptions: globalOpt, // #region for basic flags - Interactive: false, // TODO: update this after attach supports STDIN - TTY: false, // TODO: update this after attach supports STDIN - Detach: true, // TODO: current implementation of create does not support AttachStdin, AttachStdout, and AttachStderr flags - Restart: restart, // Restart policy to apply when a container exits. - Rm: req.HostConfig.AutoRemove, // Automatically remove container upon exit. - Pull: "missing", // nerdctl default. - StopSignal: stopSignal, - StopTimeout: stopTimeout, - CidFile: req.HostConfig.ContainerIDFile, // CidFile write the container ID to the file + Interactive: false, // TODO: update this after attach supports STDIN + TTY: false, // TODO: update this after attach supports STDIN + Detach: true, // TODO: current implementation of create does not support AttachStdin, AttachStdout, and AttachStderr flags + Restart: restart, // Restart policy to apply when a container exits. + Rm: req.HostConfig.AutoRemove, // Automatically remove container upon exit. + Pull: "missing", // nerdctl default. + StopSignal: stopSignal, + StopTimeout: stopTimeout, + CidFile: req.HostConfig.ContainerIDFile, // CidFile write the container ID to the file + OomKillDisable: req.HostConfig.OomKillDisable, + // #endregion // #region for platform flags diff --git a/api/types/container_types.go b/api/types/container_types.go index f19df7a6..240dc68b 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -82,7 +82,7 @@ type ContainerHostConfig struct { // TODO: IpcMode IpcMode // IPC namespace to use for the container // TODO: Cgroup CgroupSpec // Cgroup to use for the container // TODO: Links []string // List of links (in the name:alias form) - // TODO: OomKillDisable bool // specifies whether to disable OOM Killer + OomKillDisable bool // specifies whether to disable OOM Killer // TODO: OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000) // TODO: PidMode string // PID namespace to use for the container Privileged bool // Is the container in privileged mode From 43cea9c525cd887f67ce800ed4af5e80a8b2a4ef Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Thu, 5 Dec 2024 16:46:47 +0000 Subject: [PATCH 03/24] chore: add support for MACAddress option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 1 + api/types/container_types.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 027a31f8..145429ef 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -277,6 +277,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { DNSSearchDomains: req.HostConfig.DNSSearch, // Custom DNS search domains. PortMappings: portMappings, AddHost: req.HostConfig.ExtraHosts, // Extra hosts. + MACAddress: req.MacAddress, } ctx := namespaces.WithNamespace(r.Context(), h.Config.Namespace) diff --git a/api/types/container_types.go b/api/types/container_types.go index 240dc68b..62c04aef 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -47,7 +47,7 @@ type ContainerConfig struct { WorkingDir string `json:",omitempty"` // Current directory (PWD) in the command will be launched Entrypoint []string `json:",omitempty"` // Entrypoint to run when starting the container NetworkDisabled bool `json:",omitempty"` // Is network disabled - // TODO: MacAddress string `json:",omitempty"` // Mac Address of the container + MacAddress string `json:",omitempty"` // Mac Address of the container // TODO: OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile Labels map[string]string `json:",omitempty"` // List of labels set to this container StopSignal string `json:",omitempty"` // Signal to stop a container From 880dbaf5980a8fcff7ee4a9ac299ca7e1a0a27ba Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Fri, 6 Dec 2024 01:14:19 +0000 Subject: [PATCH 04/24] chore: add unit tests for OomKillDisable, MACAddress and NetworkDisabled Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create_test.go | 57 +++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 34bd4b27..bc58b830 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -357,6 +357,43 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set specified NetworkDisabled setting", func() { + body := []byte(`{ + "Image": "test-image", + "NetworkDisabled": true + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected network options + netOpt.NetworkSlice = []string{"none"} + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + + It("should set the MACAddress to a user specified value", func() { + body := []byte(`{ + "Image": "test-image", + "MacAddress": "12:34:56:78:9a:bc" + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected network options + netOpt.MACAddress = "12:34:56:78:9a:bc" + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should set CPUPeriod create options for resources", func() { body := []byte(`{ "Image": "test-image", @@ -378,6 +415,26 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set the OomKillDisable option", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "OomKillDisable": true + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected network options + createOpt.OomKillDisable = true + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should set CpuQuota create options for resources", func() { body := []byte(`{ "Image": "test-image", From c67fa65ed98442d630d82942a6d2385fd4cb6986 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Fri, 6 Dec 2024 01:39:04 +0000 Subject: [PATCH 05/24] chore: add support for BlkioWeight Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 1 + api/handlers/container/create_test.go | 21 +++++++++++++++++++++ api/types/container_types.go | 5 +++-- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 145429ef..9b2d57be 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -204,6 +204,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { MemorySwap: memorySwap, // Total memory usage (memory + swap); set `-1` to enable unlimited swap Ulimit: ulimits, // List of ulimits to be set in the container CPUPeriod: uint64(req.HostConfig.CPUPeriod), + BlkioWeight: req.HostConfig.BlkioWeight, // block IO weight (relative) // #endregion // #region for user flags diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index bc58b830..e0b8e739 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -580,6 +580,27 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set the BlkioWeight to a user specified value", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "BlkioWeight": 300 + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected network options + createOpt.BlkioWeight = 300 + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) diff --git a/api/types/container_types.go b/api/types/container_types.go index 62c04aef..04a72179 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -112,10 +112,11 @@ type ContainerHostConfig struct { MemorySwappiness int64 // MemorySwappiness64 specifies the tune container memory swappiness (0 to 100) (default -1) // TODO: Resources - Ulimits []*Ulimit // List of ulimits to be set in the container - // TODO: BlkioWeight uint16 // Block IO weight (relative weight vs. other containers) + Ulimits []*Ulimit // List of ulimits to be set in the container + BlkioWeight uint16 // Block IO weight (relative weight vs. other containers) // TODO: Devices []DeviceMapping // List of devices to map inside the container PidsLimit int64 // Setting PIDs limit for a container; Set `0` or `-1` for unlimited, or `null` to not change. + // Mounts specs used by the container // TODO: Mounts []mount.Mount `json:",omitempty"` From b4dc13fa863cb1f150c86c70e388706a89099bc3 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Fri, 6 Dec 2024 21:55:25 +0000 Subject: [PATCH 06/24] chore: add cpushares option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index e0b8e739..9cef210a 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -601,6 +601,27 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set CPUPeriod create options for resources", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "CpuPeriod": 100000 + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.CPUPeriod = 100000 + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) From 5afa8d822688f4ecb75142d83c6a75639b8a38f4 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Sat, 7 Dec 2024 01:25:05 +0000 Subject: [PATCH 07/24] chore: add CPUQuota option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create_test.go | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 9cef210a..28486f87 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -622,6 +622,43 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set CpuQuota create options for resources", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "CpuQuota": 50000 + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.CPUQuota = 50000 + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + + It("should set CpuQuota to -1 by default", func() { + body := []byte(`{ + "Image": "test-image" + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.CPUQuota = -1 + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) From 0812dbeba40b5ab6ca2b6643944d1c315e206bdf Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Sun, 8 Dec 2024 16:06:54 +0000 Subject: [PATCH 08/24] chore: add Memory options Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 21 ++++++++++-- api/handlers/container/create_test.go | 46 +++++++++++++++++++++++++++ api/types/container_types.go | 18 +++++------ 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 9b2d57be..eea6985b 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -165,6 +165,21 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { CpuQuota = req.HostConfig.CPUQuota } + memoryReservation := "" + if req.HostConfig.MemoryReservation != 0 { + memoryReservation = strconv.FormatInt(req.HostConfig.MemoryReservation, 10) + } + + memorySwap := "" + if req.HostConfig.MemorySwap != 0 { + memorySwap = strconv.FormatInt(req.HostConfig.MemorySwap, 10) + } + + memorySwappiness := int64(-1) + if req.HostConfig.MemorySwappiness != 0 && req.HostConfig.MemorySwappiness > -1 { + memorySwappiness = req.HostConfig.MemorySwappiness + } + globalOpt := ncTypes.GlobalCommandOptions(*h.Config) createOpt := ncTypes.ContainerCreateOptions{ Stdout: nil, @@ -203,8 +218,10 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { MemoryReservation: memoryReservation, // Memory soft limit (in bytes) MemorySwap: memorySwap, // Total memory usage (memory + swap); set `-1` to enable unlimited swap Ulimit: ulimits, // List of ulimits to be set in the container - CPUPeriod: uint64(req.HostConfig.CPUPeriod), - BlkioWeight: req.HostConfig.BlkioWeight, // block IO weight (relative) + BlkioWeight: req.HostConfig.BlkioWeight, // block IO weight (relative) + CPUPeriod: uint64(req.HostConfig.CPUPeriod), // CPU CFS (Completely Fair Scheduler) period + CPUSetCPUs: req.HostConfig.CPUSetCPUs, // CpusetCpus 0-2, 0,1 + CPUSetMems: req.HostConfig.CPUSetMems, // CpusetMems 0-2, 0,1 // #endregion // #region for user flags diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 28486f87..be0bc4f3 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -659,6 +659,52 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set CpuSet create options for resources", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "CpusetCpus": "0,1", + "CpusetMems": "0,3" + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.CPUSetCPUs = "0,1" + createOpt.CPUSetMems = "0,3" + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + + It("should set MemoryReservation, MemorySwap and MemorySwappiness create options for resources", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "MemoryReservation": 209715200, + "MemorySwap": 514288000, + "MemorySwappiness": 25 + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.MemoryReservation = "209715200" + createOpt.MemorySwap = "514288000" + createOpt.MemorySwappiness64 = 25 + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) diff --git a/api/types/container_types.go b/api/types/container_types.go index 04a72179..f5ddd14e 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -101,15 +101,15 @@ type ContainerHostConfig struct { // TODO: Isolation Isolation // Isolation technology of the container (e.g. default, hyperv) // Contains container's resources (cgroups, ulimits) - CPUShares int64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers) - Memory int64 // Memory limit (in bytes) - CPUPeriod int64 `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period - CPUQuota int64 `json:"CpuQuota"` // CPU CFS (Completely Fair Scheduler) quota - // TODO: CPUSetCPUs string `json:"CpusetCpus"` // CPUSetCPUs specifies the CPUs in which to allow execution (0-3, 0,1) - // TODO: CPUSetMems string `json:"CpusetMems"` // CPUSetMems specifies the memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. - MemoryReservation int64 // MemoryReservation specifies the memory soft limit (in bytes) - MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap - MemorySwappiness int64 // MemorySwappiness64 specifies the tune container memory swappiness (0 to 100) (default -1) + CPUShares int64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers) + Memory int64 // Memory limit (in bytes) + CPUPeriod int64 `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period + CPUQuota int64 `json:"CpuQuota"` // CPU CFS (Completely Fair Scheduler) quota + CPUSetCPUs string `json:"CpusetCpus"` // CPUSetCPUs specifies the CPUs in which to allow execution (0-3, 0,1) + CPUSetMems string `json:"CpusetMems"` // CPUSetMems specifies the memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. + MemoryReservation int64 // MemoryReservation specifies the memory soft limit (in bytes) + MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap + MemorySwappiness int64 // MemorySwappiness64 specifies the tune container memory swappiness (0 to 100) (default -1) // TODO: Resources Ulimits []*Ulimit // List of ulimits to be set in the container From 52f625ea505e14e6af9ba31e6ddf4e18baabec02 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Mon, 9 Dec 2024 17:27:36 +0000 Subject: [PATCH 09/24] chore: add ContainerIDFile options Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 1 - api/handlers/container/create_test.go | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index eea6985b..b883e39b 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -197,7 +197,6 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { StopTimeout: stopTimeout, CidFile: req.HostConfig.ContainerIDFile, // CidFile write the container ID to the file OomKillDisable: req.HostConfig.OomKillDisable, - // #endregion // #region for platform flags diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index be0bc4f3..b02893bf 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -705,6 +705,27 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set ContainerIdFile option", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "ContainerIDFile": "/lib/example.txt" + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.CidFile = "/lib/example.txt" + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) From a36b88ddfcbe2c0f4bf3df80f28b8b2350b2ad49 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Mon, 9 Dec 2024 18:00:51 +0000 Subject: [PATCH 10/24] chore: add VolumesFrom option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 8 +++++++- api/handlers/container/create_test.go | 24 +++++++++++++++++++++++- api/types/container_types.go | 2 +- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index b883e39b..352ad154 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -180,6 +180,11 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { memorySwappiness = req.HostConfig.MemorySwappiness } + volumesFrom := []string{} + if req.HostConfig.VolumesFrom != nil { + volumesFrom = req.HostConfig.VolumesFrom + } + globalOpt := ncTypes.GlobalCommandOptions(*h.Config) createOpt := ncTypes.ContainerCreateOptions{ Stdout: nil, @@ -238,7 +243,8 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { // #endregion // #region for volume flags - Volume: volumes, + Volume: volumes, + VolumesFrom: volumesFrom, // #endregion // #region for env flags diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index b02893bf..debd3d40 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -726,6 +726,27 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set VolumesFrom option", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "VolumesFrom": [ "parent", "other:ro"] + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.VolumesFrom = []string{"parent", "other:ro"} + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) @@ -930,7 +951,8 @@ func getDefaultCreateOpt(conf config.Config) types.ContainerCreateOptions { // #endregion // #region for volume flags - Volume: nil, + Volume: nil, + VolumesFrom: []string{}, // nerdctl default. // #endregion // #region for env flags diff --git a/api/types/container_types.go b/api/types/container_types.go index f5ddd14e..a9711c42 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -65,8 +65,8 @@ type ContainerHostConfig struct { PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host RestartPolicy RestartPolicy // Restart policy to be used for the container AutoRemove bool // Automatically remove container when it exits + VolumesFrom []string // List of volumes to take from other container // TODO: VolumeDriver string // Name of the volume driver used to mount volumes - // TODO: VolumesFrom []string // List of volumes to take from other container // TODO: ConsoleSize [2]uint // Initial console size (height,width) // TODO: Annotations map[string]string `json:",omitempty"` // Arbitrary non-identifying metadata attached to container and provided to the runtime From 630ac1d2fe0094c4ca15f8276c6a26428afe584b Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Mon, 9 Dec 2024 19:39:05 +0000 Subject: [PATCH 11/24] chore: add CapAdd option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 15 --------------- api/handlers/container/create_test.go | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 352ad154..b5bab955 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -165,21 +165,6 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { CpuQuota = req.HostConfig.CPUQuota } - memoryReservation := "" - if req.HostConfig.MemoryReservation != 0 { - memoryReservation = strconv.FormatInt(req.HostConfig.MemoryReservation, 10) - } - - memorySwap := "" - if req.HostConfig.MemorySwap != 0 { - memorySwap = strconv.FormatInt(req.HostConfig.MemorySwap, 10) - } - - memorySwappiness := int64(-1) - if req.HostConfig.MemorySwappiness != 0 && req.HostConfig.MemorySwappiness > -1 { - memorySwappiness = req.HostConfig.MemorySwappiness - } - volumesFrom := []string{} if req.HostConfig.VolumesFrom != nil { volumesFrom = req.HostConfig.VolumesFrom diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index debd3d40..f63b8c2a 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -747,6 +747,27 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set CapDrop option", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "CapDrop": ["MKNOD"] + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.CapDrop = []string{"MKNOD"} + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) From 32c5a115fbcce7c1a729e40aba4c946fc2ee1b90 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Mon, 9 Dec 2024 20:25:51 +0000 Subject: [PATCH 12/24] chore: add GroupAdd option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 8 +++++++- api/handlers/container/create_test.go | 6 ++++-- api/types/container_types.go | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index b5bab955..7213ddb6 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -170,6 +170,11 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { volumesFrom = req.HostConfig.VolumesFrom } + groupAdd := []string{} + if req.HostConfig.GroupAdd != nil { + groupAdd = req.HostConfig.GroupAdd + } + globalOpt := ncTypes.GlobalCommandOptions(*h.Config) createOpt := ncTypes.ContainerCreateOptions{ Stdout: nil, @@ -214,7 +219,8 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { // #endregion // #region for user flags - User: req.User, + User: req.User, + GroupAdd: groupAdd, // #endregion // #region for security flags diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index f63b8c2a..459955b9 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -747,17 +747,19 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) - It("should set CapDrop option", func() { + It("should set CapDrop and GroupAdd option", func() { body := []byte(`{ "Image": "test-image", "HostConfig": { - "CapDrop": ["MKNOD"] + "CapDrop": ["MKNOD"], + "GroupAdd": ["someGroup"] } }`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) // expected create options createOpt.CapDrop = []string{"MKNOD"} + createOpt.GroupAdd = []string{"someGroup"} service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( cid, nil) diff --git a/api/types/container_types.go b/api/types/container_types.go index a9711c42..ccc7f6ba 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -78,8 +78,8 @@ type ContainerHostConfig struct { DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for ExtraHosts []string // List of extra hosts - // TODO: GroupAdd []string // List of additional groups that the container process will run as - // TODO: IpcMode IpcMode // IPC namespace to use for the container + GroupAdd []string // List of additional groups that the container process will run as + // TODO: IpcMode IpcMode // IPC namespace to use for the container // TODO: Cgroup CgroupSpec // Cgroup to use for the container // TODO: Links []string // List of links (in the name:alias form) OomKillDisable bool // specifies whether to disable OOM Killer From 630904e15564bb94bc7964da919076db02dd9eb9 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Mon, 9 Dec 2024 23:15:00 +0000 Subject: [PATCH 13/24] chore: add IPC and OomScoreAdj option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 2 ++ api/handlers/container/create_test.go | 23 +++++++++++++++++++++++ api/types/container_types.go | 8 +++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 7213ddb6..f68626cb 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -192,6 +192,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { StopTimeout: stopTimeout, CidFile: req.HostConfig.ContainerIDFile, // CidFile write the container ID to the file OomKillDisable: req.HostConfig.OomKillDisable, + OomScoreAdj: req.HostConfig.OomScoreAdj, // #endregion // #region for platform flags @@ -216,6 +217,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { CPUPeriod: uint64(req.HostConfig.CPUPeriod), // CPU CFS (Completely Fair Scheduler) period CPUSetCPUs: req.HostConfig.CPUSetCPUs, // CpusetCpus 0-2, 0,1 CPUSetMems: req.HostConfig.CPUSetMems, // CpusetMems 0-2, 0,1 + IPC: req.HostConfig.IpcMode, // IPC namespace to use // #endregion // #region for user flags diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 459955b9..ac7d70b3 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -770,6 +770,29 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set IPC and OomScoreAdj option", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "IpcMode": "host", + "OomScoreAdj": 200 + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.IPC = "host" + createOpt.OomScoreAdj = 200 + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) diff --git a/api/types/container_types.go b/api/types/container_types.go index ccc7f6ba..7da7aa1a 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -79,10 +79,11 @@ type ContainerHostConfig struct { DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for ExtraHosts []string // List of extra hosts GroupAdd []string // List of additional groups that the container process will run as - // TODO: IpcMode IpcMode // IPC namespace to use for the container + IpcMode string // IPC namespace to use for the container // TODO: Cgroup CgroupSpec // Cgroup to use for the container // TODO: Links []string // List of links (in the name:alias form) OomKillDisable bool // specifies whether to disable OOM Killer +<<<<<<< HEAD // TODO: OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000) // TODO: PidMode string // PID namespace to use for the container Privileged bool // Is the container in privileged mode @@ -93,6 +94,11 @@ type ContainerHostConfig struct { // TODO: ShmSize int64 // Size of /dev/shm in bytes. The size must be greater than 0. // TODO: Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container // TODO: Runtime string `json:",omitempty"` // Runtime to use with this container +======= + OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000) + // TODO: PidMode PidMode // PID namespace to use for the container + // TODO: Privileged bool // Is the container in privileged mode +>>>>>>> 59e99f3 (chore: add IPC and OomScoreAdj option) // TODO: PublishAllPorts bool // Should docker publish all exposed port for the container // TODO: StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container. // TODO: UsernsMode UsernsMode // The user namespace to use for the container From 9feb788eb4c5fc09662dfe66e521bd74c9f55aa9 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Mon, 9 Dec 2024 23:38:33 +0000 Subject: [PATCH 14/24] chore: add PidMode and Priviledged option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 1 + api/handlers/container/create_test.go | 23 +++++++++++++++++++++++ api/types/container_types.go | 21 ++++----------------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index f68626cb..7270838b 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -193,6 +193,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { CidFile: req.HostConfig.ContainerIDFile, // CidFile write the container ID to the file OomKillDisable: req.HostConfig.OomKillDisable, OomScoreAdj: req.HostConfig.OomScoreAdj, + Pid: req.HostConfig.PidMode, // Pid namespace to use // #endregion // #region for platform flags diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index ac7d70b3..5615cadc 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -793,6 +793,29 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set PidMode and Privileged option", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "PidMode": "host", + "Privileged": true + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.Pid = "host" + createOpt.Privileged = true + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) diff --git a/api/types/container_types.go b/api/types/container_types.go index 7da7aa1a..8b1ff150 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -82,23 +82,10 @@ type ContainerHostConfig struct { IpcMode string // IPC namespace to use for the container // TODO: Cgroup CgroupSpec // Cgroup to use for the container // TODO: Links []string // List of links (in the name:alias form) - OomKillDisable bool // specifies whether to disable OOM Killer -<<<<<<< HEAD - // TODO: OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000) - // TODO: PidMode string // PID namespace to use for the container - Privileged bool // Is the container in privileged mode - // TODO: ReadonlyRootfs bool // Is the container root filesystem in read-only - // TODO: SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux. (["key=value"]) - // TODO: Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container - // TODO: UTSMode string // UTS namespace to use for the container - // TODO: ShmSize int64 // Size of /dev/shm in bytes. The size must be greater than 0. - // TODO: Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container - // TODO: Runtime string `json:",omitempty"` // Runtime to use with this container -======= - OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000) - // TODO: PidMode PidMode // PID namespace to use for the container - // TODO: Privileged bool // Is the container in privileged mode ->>>>>>> 59e99f3 (chore: add IPC and OomScoreAdj option) + OomKillDisable bool // specifies whether to disable OOM Killer + OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000) + PidMode string // PID namespace to use for the container + Privileged bool // Is the container in privileged mode // TODO: PublishAllPorts bool // Should docker publish all exposed port for the container // TODO: StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container. // TODO: UsernsMode UsernsMode // The user namespace to use for the container From 3b47b072afd9cb6162fb00f42a9c5052b034221d Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Tue, 10 Dec 2024 00:04:00 +0000 Subject: [PATCH 15/24] chore: add ReadonlyRootfs and SecurityOpt option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 10 +++++++++- api/handlers/container/create_test.go | 23 +++++++++++++++++++++++ api/types/container_types.go | 10 ++++++---- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 7270838b..13bb607c 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -175,6 +175,11 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { groupAdd = req.HostConfig.GroupAdd } + securityOpt := []string{} + if req.HostConfig.SecurityOpt != nil { + securityOpt = req.HostConfig.SecurityOpt + } + globalOpt := ncTypes.GlobalCommandOptions(*h.Config) createOpt := ncTypes.ContainerCreateOptions{ Stdout: nil, @@ -227,7 +232,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { // #endregion // #region for security flags - SecurityOpt: []string{}, // nerdctl default. + SecurityOpt: securityOpt, // nerdctl default. CapAdd: capAdd, CapDrop: capDrop, Privileged: req.HostConfig.Privileged, @@ -267,6 +272,9 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { Stderr: nil, }, // #endregion + + // #region for rootfs flags + ReadOnly: req.HostConfig.ReadonlyRootfs, // Is the container root filesystem in read-only } portMappings, err := translatePortMappings(req.HostConfig.PortBindings) diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 5615cadc..ad856bb8 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -816,6 +816,29 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set ReadonlyRootfs and SecurityOpt option", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "ReadonlyRootfs": true, + "SecurityOpt": [ "seccomp=/path/to/custom_seccomp.json", "apparmor=unconfined"] + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.ReadOnly = true + createOpt.SecurityOpt = []string{"seccomp=/path/to/custom_seccomp.json", "apparmor=unconfined"} + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) diff --git a/api/types/container_types.go b/api/types/container_types.go index 8b1ff150..d468073d 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -82,10 +82,12 @@ type ContainerHostConfig struct { IpcMode string // IPC namespace to use for the container // TODO: Cgroup CgroupSpec // Cgroup to use for the container // TODO: Links []string // List of links (in the name:alias form) - OomKillDisable bool // specifies whether to disable OOM Killer - OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000) - PidMode string // PID namespace to use for the container - Privileged bool // Is the container in privileged mode + OomKillDisable bool // specifies whether to disable OOM Killer + OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000) + PidMode string // PID namespace to use for the container + Privileged bool // Is the container in privileged mode + ReadonlyRootfs bool // Is the container root filesystem in read-only + SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux. (["key=value"]) // TODO: PublishAllPorts bool // Should docker publish all exposed port for the container // TODO: StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container. // TODO: UsernsMode UsernsMode // The user namespace to use for the container From da1bf3d86dfac73602a27ffd85d7b929ec187690 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Wed, 11 Dec 2024 19:36:50 +0000 Subject: [PATCH 16/24] chore: add Tmpfs and UTSMode option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 12 ++++++++++++ api/handlers/container/create_test.go | 24 ++++++++++++++++++++++++ api/types/container_types.go | 14 ++++++++------ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 13bb607c..cffaf241 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -123,6 +123,16 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { ulimits = append(ulimits, ulimit.String()) } } + // Tmpfs: + // Tmpfs are passed in as a map of strings, + // but nerdctl expects an array of strings with format [TMPFS1:VALUE1, TMPFS2:VALUE2, ...]. + tmpfs := []string{} + if req.HostConfig.Tmpfs != nil { + for key, val := range req.HostConfig.Tmpfs { + tmpfs = append(tmpfs, fmt.Sprintf("%s:%s", key, val)) + } + } + // Environment vars: env := []string{} if req.Env != nil { @@ -244,6 +254,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { // #region for volume flags Volume: volumes, VolumesFrom: volumesFrom, + Tmpfs: tmpfs, // #endregion // #region for env flags @@ -303,6 +314,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { PortMappings: portMappings, AddHost: req.HostConfig.ExtraHosts, // Extra hosts. MACAddress: req.MacAddress, + UTSNamespace: req.HostConfig.UTSMode, } ctx := namespaces.WithNamespace(r.Context(), h.Config.Namespace) diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index ad856bb8..31a1879e 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -839,6 +839,29 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set Tmpfs and UTSMode option", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "Tmpfs": { "/run": "rw,noexec,nosuid,size=65536k" }, + "UTSMode": "host" + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.Tmpfs = []string{"/run:rw,noexec,nosuid,size=65536k"} + netOpt.UTSNamespace = "host" + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) @@ -1045,6 +1068,7 @@ func getDefaultCreateOpt(conf config.Config) types.ContainerCreateOptions { // #region for volume flags Volume: nil, VolumesFrom: []string{}, // nerdctl default. + Tmpfs: []string{}, // #endregion // #region for env flags diff --git a/api/types/container_types.go b/api/types/container_types.go index d468073d..fc254817 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -82,12 +82,14 @@ type ContainerHostConfig struct { IpcMode string // IPC namespace to use for the container // TODO: Cgroup CgroupSpec // Cgroup to use for the container // TODO: Links []string // List of links (in the name:alias form) - OomKillDisable bool // specifies whether to disable OOM Killer - OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000) - PidMode string // PID namespace to use for the container - Privileged bool // Is the container in privileged mode - ReadonlyRootfs bool // Is the container root filesystem in read-only - SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux. (["key=value"]) + OomKillDisable bool // specifies whether to disable OOM Killer + OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000) + PidMode string // PID namespace to use for the container + Privileged bool // Is the container in privileged mode + ReadonlyRootfs bool // Is the container root filesystem in read-only + SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux. (["key=value"]) + Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container + UTSMode string // UTS namespace to use for the container // TODO: PublishAllPorts bool // Should docker publish all exposed port for the container // TODO: StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container. // TODO: UsernsMode UsernsMode // The user namespace to use for the container From 7a13bff72d33d51afc14435afad03dcd1432b579 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Wed, 11 Dec 2024 22:30:31 +0000 Subject: [PATCH 17/24] chore: add ShmSize, Sysctl and Runtime option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 24 +++++++++++++++++++++++- api/handlers/container/create_test.go | 26 ++++++++++++++++++++++++++ api/types/container_types.go | 3 +++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index cffaf241..50b0fa3d 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -133,6 +133,16 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { } } + // Sysctls: + // Sysctls are passed in as a map of strings, + // but nerdctl expects an array of strings with format [Sysctls1=VALUE1, Sysctls2=VALUE2, ...]. + sysctls := []string{} + if req.HostConfig.Sysctls != nil { + for key, val := range req.HostConfig.Sysctls { + sysctls = append(sysctls, fmt.Sprintf("%s=%s", key, val)) + } + } + // Environment vars: env := []string{} if req.Env != nil { @@ -174,6 +184,15 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { if req.HostConfig.CPUQuota != 0 { CpuQuota = req.HostConfig.CPUQuota } + shmSize := "" + if req.HostConfig.ShmSize > 0 { + shmSize = fmt.Sprint(req.HostConfig.ShmSize) + } + + runtime := defaults.Runtime + if req.HostConfig.Runtime != "" { + runtime = req.HostConfig.Runtime + } volumesFrom := []string{} if req.HostConfig.VolumesFrom != nil { @@ -234,6 +253,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { CPUSetCPUs: req.HostConfig.CPUSetCPUs, // CpusetCpus 0-2, 0,1 CPUSetMems: req.HostConfig.CPUSetMems, // CpusetMems 0-2, 0,1 IPC: req.HostConfig.IpcMode, // IPC namespace to use + ShmSize: shmSize, // ShmSize set the size of /dev/shm // #endregion // #region for user flags @@ -248,7 +268,8 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { Privileged: req.HostConfig.Privileged, // #endregion // #region for runtime flags - Runtime: defaults.Runtime, // nerdctl default. + Runtime: runtime, // Runtime to use for this container, e.g. "crun", or "io.containerd.runc.v2". + Sysctl: sysctls, // Sysctl set sysctl options, e.g "net.ipv4.ip_forward=1" // #endregion // #region for volume flags @@ -286,6 +307,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { // #region for rootfs flags ReadOnly: req.HostConfig.ReadonlyRootfs, // Is the container root filesystem in read-only + // #endregion } portMappings, err := translatePortMappings(req.HostConfig.PortBindings) diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 31a1879e..5a8071c2 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -862,6 +862,31 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set ShmSize, Sysctl and Runtime option", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "Sysctls": { "net.ipv4.ip_forward": "1" }, + "ShmSize": 302348, + "Runtime": "crun" + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.ShmSize = "302348" + createOpt.Sysctl = []string{"net.ipv4.ip_forward=1"} + createOpt.Runtime = "crun" + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) @@ -1063,6 +1088,7 @@ func getDefaultCreateOpt(conf config.Config) types.ContainerCreateOptions { // #region for runtime flags Runtime: defaults.Runtime, // nerdctl default. + Sysctl: []string{}, // #endregion // #region for volume flags diff --git a/api/types/container_types.go b/api/types/container_types.go index fc254817..75ca683b 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -90,6 +90,9 @@ type ContainerHostConfig struct { SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux. (["key=value"]) Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container UTSMode string // UTS namespace to use for the container + ShmSize int64 // Size of /dev/shm in bytes. The size must be greater than 0. + Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container + Runtime string `json:",omitempty"` // Runtime to use with this container // TODO: PublishAllPorts bool // Should docker publish all exposed port for the container // TODO: StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container. // TODO: UsernsMode UsernsMode // The user namespace to use for the container From c7f395a7e05ad2e40a47a7c1e418503e9efe7a61 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Wed, 11 Dec 2024 23:48:48 +0000 Subject: [PATCH 18/24] chore: add Ulimits option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 8 ++++++++ api/handlers/container/create_test.go | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 50b0fa3d..9487cc28 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -143,6 +143,13 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { } } + ulimits := []string{} + if req.HostConfig.Ulimits != nil { + for _, ulimit := range req.HostConfig.Ulimits { + ulimits = append(ulimits, ulimit.String()) + } + } + // Environment vars: env := []string{} if req.Env != nil { @@ -254,6 +261,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { CPUSetMems: req.HostConfig.CPUSetMems, // CpusetMems 0-2, 0,1 IPC: req.HostConfig.IpcMode, // IPC namespace to use ShmSize: shmSize, // ShmSize set the size of /dev/shm + Ulimit: ulimits, // List of ulimits to be set in the container // #endregion // #region for user flags diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 5a8071c2..8db731e8 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -887,6 +887,27 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set Ulimit option", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "Ulimits": [{"Name": "nofile", "Soft": 1024, "Hard": 2048},{"Name": "nproc", "Soft": 1024, "Hard": 4048}] + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.Ulimit = []string{"nofile=1024:2048", "nproc=1024:4048"} + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) From f268dca981f07e062786bddcdd4f696c7cbbaad3 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Thu, 12 Dec 2024 18:40:01 +0000 Subject: [PATCH 19/24] chore: add Device option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 25 +++++++++++++++++++++++++ api/handlers/container/create_test.go | 22 ++++++++++++++++++++++ api/types/container_types.go | 14 ++++++++++---- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 9487cc28..50e4eb09 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -150,6 +150,30 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { } } + // devices: + // devices are passed in as a map of DeviceMapping, + // but nerdctl expects an array of strings with format [devices1:VALUE1, devices2:VALUE2, ...]. + devices := []string{} + if req.HostConfig.Devices != nil { + for _, deviceMap := range req.HostConfig.Devices { + deviceString := "" + if deviceMap.PathOnHost != "" { + deviceString += deviceMap.PathOnHost + } + + if deviceMap.PathInContainer != "" { + deviceString += ":" + deviceString += deviceMap.PathInContainer + } + + if deviceMap.CgroupPermissions != "" { + deviceString += ":" + deviceString += deviceMap.CgroupPermissions + } + devices = append(devices, deviceString) + } + } + // Environment vars: env := []string{} if req.Env != nil { @@ -262,6 +286,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { IPC: req.HostConfig.IpcMode, // IPC namespace to use ShmSize: shmSize, // ShmSize set the size of /dev/shm Ulimit: ulimits, // List of ulimits to be set in the container + Device: devices, // Device specifies add a host device to the container // #endregion // #region for user flags diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 8db731e8..73a89426 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -908,6 +908,27 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set Devices option", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "Devices": [{"PathOnHost": "/dev/null", "PathInContainer": "/dev/null", "CgroupPermissions": "rwm"},{"PathOnHost": "/var/lib", "CgroupPermissions": "ro"}] + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.Device = []string{"/dev/null:/dev/null:rwm", "/var/lib:ro"} + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) @@ -1094,6 +1115,7 @@ func getDefaultCreateOpt(conf config.Config) types.ContainerCreateOptions { PidsLimit: -1, // nerdctl default. Cgroupns: defaults.CgroupnsMode(), // nerdctl default. Ulimit: []string{}, + Device: []string{}, // #endregion // #region for user flags diff --git a/api/types/container_types.go b/api/types/container_types.go index 75ca683b..02e7a64b 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -112,10 +112,10 @@ type ContainerHostConfig struct { MemorySwappiness int64 // MemorySwappiness64 specifies the tune container memory swappiness (0 to 100) (default -1) // TODO: Resources - Ulimits []*Ulimit // List of ulimits to be set in the container - BlkioWeight uint16 // Block IO weight (relative weight vs. other containers) - // TODO: Devices []DeviceMapping // List of devices to map inside the container - PidsLimit int64 // Setting PIDs limit for a container; Set `0` or `-1` for unlimited, or `null` to not change. + Ulimits []*Ulimit // List of ulimits to be set in the container + BlkioWeight uint16 // Block IO weight (relative weight vs. other containers) + Devices []DeviceMapping // List of devices to map inside the container + PidsLimit int64 // Setting PIDs limit for a container; Set `0` or `-1` for unlimited, or `null` to not change. // Mounts specs used by the container // TODO: Mounts []mount.Mount `json:",omitempty"` @@ -265,3 +265,9 @@ type StatsJSON struct { } type Ulimit = units.Ulimit + +type DeviceMapping struct { + PathOnHost string + PathInContainer string + CgroupPermissions string +} From e80657aaadbd12ef40fe203166a17f09da3cc98b Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Thu, 12 Dec 2024 19:46:21 +0000 Subject: [PATCH 20/24] chore: add PidLimit option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 6 +++++- api/handlers/container/create_test.go | 6 ++++-- api/types/container_types.go | 1 - 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 50e4eb09..698e3f2a 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -152,7 +152,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { // devices: // devices are passed in as a map of DeviceMapping, - // but nerdctl expects an array of strings with format [devices1:VALUE1, devices2:VALUE2, ...]. + // but nerdctl expects an array of strings with format [PathOnHost1:PathInContainer1:CgroupPermissions1, PathOnHost2:PathInContainer2:CgroupPermissions2, ...]. devices := []string{} if req.HostConfig.Devices != nil { for _, deviceMap := range req.HostConfig.Devices { @@ -240,6 +240,10 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { securityOpt = req.HostConfig.SecurityOpt } + pidLimit := int64(-1) + if req.HostConfig.PidsLimit > 0 { + pidLimit = req.HostConfig.PidsLimit + } globalOpt := ncTypes.GlobalCommandOptions(*h.Config) createOpt := ncTypes.ContainerCreateOptions{ Stdout: nil, diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 73a89426..26c352e7 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -908,17 +908,19 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) - It("should set Devices option", func() { + It("should set Devices and PidLimit option", func() { body := []byte(`{ "Image": "test-image", "HostConfig": { - "Devices": [{"PathOnHost": "/dev/null", "PathInContainer": "/dev/null", "CgroupPermissions": "rwm"},{"PathOnHost": "/var/lib", "CgroupPermissions": "ro"}] + "Devices": [{"PathOnHost": "/dev/null", "PathInContainer": "/dev/null", "CgroupPermissions": "rwm"},{"PathOnHost": "/var/lib", "CgroupPermissions": "ro"}], + "PidsLimit": 200 } }`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) // expected create options createOpt.Device = []string{"/dev/null:/dev/null:rwm", "/var/lib:ro"} + createOpt.PidsLimit = 200 service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( cid, nil) diff --git a/api/types/container_types.go b/api/types/container_types.go index 02e7a64b..b0fa3a3b 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -116,7 +116,6 @@ type ContainerHostConfig struct { BlkioWeight uint16 // Block IO weight (relative weight vs. other containers) Devices []DeviceMapping // List of devices to map inside the container PidsLimit int64 // Setting PIDs limit for a container; Set `0` or `-1` for unlimited, or `null` to not change. - // Mounts specs used by the container // TODO: Mounts []mount.Mount `json:",omitempty"` From bd295b0873294ba8cd6dd74c180d937ad62a9588 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Fri, 13 Dec 2024 22:53:27 +0000 Subject: [PATCH 21/24] chore: add CgroupnsMode option Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 18 ++++++++++++++- api/handlers/container/create_test.go | 21 +++++++++++++++++ api/types/container_types.go | 33 +++++++++++++++++++-------- 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 698e3f2a..9f0a76bc 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -143,6 +143,16 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { } } + // Annotations: TODO - available in nerdctl 2.0 + // Annotations are passed in as a map of strings, + // but nerdctl expects an array of strings with format [annotations1=VALUE1, annotations2=VALUE2, ...]. + // annotations := []string{} + // if req.HostConfig.Annotations != nil { + // for key, val := range req.HostConfig.Annotations { + // annotations = append(annotations, fmt.Sprintf("%s=%s", key, val)) + // } + // } + ulimits := []string{} if req.HostConfig.Ulimits != nil { for _, ulimit := range req.HostConfig.Ulimits { @@ -244,6 +254,12 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { if req.HostConfig.PidsLimit > 0 { pidLimit = req.HostConfig.PidsLimit } + + cgroupnsMode := defaults.CgroupnsMode() + if req.HostConfig.CgroupnsMode.Valid() { + cgroupnsMode = string(req.HostConfig.CgroupnsMode) + } + globalOpt := ncTypes.GlobalCommandOptions(*h.Config) createOpt := ncTypes.ContainerCreateOptions{ Stdout: nil, @@ -279,7 +295,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { CPUQuota: CpuQuota, // CPUQuota limits the CPU CFS (Completely Fair Scheduler) quota MemorySwappiness64: memorySwappiness, // Tuning container memory swappiness behaviour PidsLimit: pidLimit, // PidsLimit specifies the tune container pids limit - Cgroupns: defaults.CgroupnsMode(), // nerdctl default. + Cgroupns: cgroupnsMode, // Cgroupns specifies the cgroup namespace to use MemoryReservation: memoryReservation, // Memory soft limit (in bytes) MemorySwap: memorySwap, // Total memory usage (memory + swap); set `-1` to enable unlimited swap Ulimit: ulimits, // List of ulimits to be set in the container diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 26c352e7..262c4cfc 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -931,6 +931,27 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set CgroupnsMode option", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "CgroupnsMode": "host" + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.Cgroupns = "host" + + service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( + cid, nil) + + // handler should return success message with 201 status code. + h.create(rr, req) + Expect(rr).Should(HaveHTTPStatus(http.StatusCreated)) + Expect(rr.Body).Should(MatchJSON(jsonResponse)) + }) + It("should return 404 if the image was not found", func() { body := []byte(`{"Image": "test-image"}`) req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) diff --git a/api/types/container_types.go b/api/types/container_types.go index b0fa3a3b..0edac3ea 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -71,15 +71,15 @@ type ContainerHostConfig struct { // TODO: Annotations map[string]string `json:",omitempty"` // Arbitrary non-identifying metadata attached to container and provided to the runtime // Applicable to UNIX platforms - CapAdd []string // List of kernel capabilities to add to the container - CapDrop []string // List of kernel capabilities to remove from the container - // TODO: CgroupnsMode CgroupnsMode // Cgroup namespace mode to use for the container - DNS []string `json:"Dns"` // List of DNS server to lookup - DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for - DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for - ExtraHosts []string // List of extra hosts - GroupAdd []string // List of additional groups that the container process will run as - IpcMode string // IPC namespace to use for the container + CapAdd []string // List of kernel capabilities to add to the container + CapDrop []string // List of kernel capabilities to remove from the container + CgroupnsMode CgroupnsMode // Cgroup namespace mode to use for the container + DNS []string `json:"Dns"` // List of DNS server to lookup + DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for + DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for + ExtraHosts []string // List of extra hosts + GroupAdd []string // List of additional groups that the container process will run as + IpcMode string // IPC namespace to use for the container // TODO: Cgroup CgroupSpec // Cgroup to use for the container // TODO: Links []string // List of links (in the name:alias form) OomKillDisable bool // specifies whether to disable OOM Killer @@ -270,3 +270,18 @@ type DeviceMapping struct { PathInContainer string CgroupPermissions string } + +// CgroupnsMode represents the cgroup namespace mode of the container +type CgroupnsMode string + +// cgroup namespace modes for containers +const ( + CgroupnsModeEmpty CgroupnsMode = "" + CgroupnsModePrivate CgroupnsMode = "private" + CgroupnsModeHost CgroupnsMode = "host" +) + +// Valid indicates whether the cgroup namespace mode is valid +func (c CgroupnsMode) Valid() bool { + return c == CgroupnsModePrivate || c == CgroupnsModeHost +} From 8a25c0308ba9fd505833f21c8f0e18e13f5adcf7 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Tue, 17 Dec 2024 03:39:46 +0000 Subject: [PATCH 22/24] chore: add e2e tests Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create_test.go | 4 ++-- api/types/container_types.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 262c4cfc..714ccc24 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -685,7 +685,7 @@ var _ = Describe("Container Create API ", func() { body := []byte(`{ "Image": "test-image", "HostConfig": { - "MemoryReservation": 209715200, + "MemoryReservation": 209710, "MemorySwap": 514288000, "MemorySwappiness": 25 } @@ -693,7 +693,7 @@ var _ = Describe("Container Create API ", func() { req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) // expected create options - createOpt.MemoryReservation = "209715200" + createOpt.MemoryReservation = "209710" createOpt.MemorySwap = "514288000" createOpt.MemorySwappiness64 = 25 service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( diff --git a/api/types/container_types.go b/api/types/container_types.go index 0edac3ea..2e0ffd83 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -271,17 +271,17 @@ type DeviceMapping struct { CgroupPermissions string } -// CgroupnsMode represents the cgroup namespace mode of the container +// CgroupnsMode represents the cgroup namespace mode of the container. type CgroupnsMode string -// cgroup namespace modes for containers +// cgroup namespace modes for containers. const ( CgroupnsModeEmpty CgroupnsMode = "" CgroupnsModePrivate CgroupnsMode = "private" CgroupnsModeHost CgroupnsMode = "host" ) -// Valid indicates whether the cgroup namespace mode is valid +// Valid indicates whether the cgroup namespace mode is valid. func (c CgroupnsMode) Valid() bool { return c == CgroupnsModePrivate || c == CgroupnsModeHost } From 5956e50d303c014aedc289e5b7195a75aee18d33 Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Tue, 14 Jan 2025 19:48:06 +0000 Subject: [PATCH 23/24] fix: unit test cases Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 23 ----------------------- api/handlers/container/create_test.go | 1 + 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 9f0a76bc..e6d08781 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -143,23 +143,6 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { } } - // Annotations: TODO - available in nerdctl 2.0 - // Annotations are passed in as a map of strings, - // but nerdctl expects an array of strings with format [annotations1=VALUE1, annotations2=VALUE2, ...]. - // annotations := []string{} - // if req.HostConfig.Annotations != nil { - // for key, val := range req.HostConfig.Annotations { - // annotations = append(annotations, fmt.Sprintf("%s=%s", key, val)) - // } - // } - - ulimits := []string{} - if req.HostConfig.Ulimits != nil { - for _, ulimit := range req.HostConfig.Ulimits { - ulimits = append(ulimits, ulimit.String()) - } - } - // devices: // devices are passed in as a map of DeviceMapping, // but nerdctl expects an array of strings with format [PathOnHost1:PathInContainer1:CgroupPermissions1, PathOnHost2:PathInContainer2:CgroupPermissions2, ...]. @@ -250,11 +233,6 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { securityOpt = req.HostConfig.SecurityOpt } - pidLimit := int64(-1) - if req.HostConfig.PidsLimit > 0 { - pidLimit = req.HostConfig.PidsLimit - } - cgroupnsMode := defaults.CgroupnsMode() if req.HostConfig.CgroupnsMode.Valid() { cgroupnsMode = string(req.HostConfig.CgroupnsMode) @@ -305,7 +283,6 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { CPUSetMems: req.HostConfig.CPUSetMems, // CpusetMems 0-2, 0,1 IPC: req.HostConfig.IpcMode, // IPC namespace to use ShmSize: shmSize, // ShmSize set the size of /dev/shm - Ulimit: ulimits, // List of ulimits to be set in the container Device: devices, // Device specifies add a host device to the container // #endregion diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 714ccc24..6192321e 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -1150,6 +1150,7 @@ func getDefaultCreateOpt(conf config.Config) types.ContainerCreateOptions { CapAdd: []string{}, // nerdctl default. CapDrop: []string{}, // nerdctl default. Privileged: false, + GroupAdd: []string{}, // nerdctl default. // #endregion // #region for runtime flags From bc7a9bde7d3b9e3577a3f77517738312fc3b85ab Mon Sep 17 00:00:00 2001 From: Arjun Raja Yogidas Date: Mon, 27 Jan 2025 05:37:21 +0000 Subject: [PATCH 24/24] chore: add OomScoreAdjChanged Signed-off-by: Arjun Raja Yogidas --- api/handlers/container/create.go | 30 ++++++++++++++++----------- api/handlers/container/create_test.go | 1 + api/types/container_types.go | 23 ++++++++++---------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index e6d08781..cadfdddc 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -238,6 +238,11 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { cgroupnsMode = string(req.HostConfig.CgroupnsMode) } + var oomScoreAdjChanged bool + if req.HostConfig.OomScoreAdj != 0 || req.HostConfig.OomScoreAdjChanged { + oomScoreAdjChanged = req.HostConfig.OomScoreAdjChanged + } + globalOpt := ncTypes.GlobalCommandOptions(*h.Config) createOpt := ncTypes.ContainerCreateOptions{ Stdout: nil, @@ -245,18 +250,19 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { GOptions: globalOpt, // #region for basic flags - Interactive: false, // TODO: update this after attach supports STDIN - TTY: false, // TODO: update this after attach supports STDIN - Detach: true, // TODO: current implementation of create does not support AttachStdin, AttachStdout, and AttachStderr flags - Restart: restart, // Restart policy to apply when a container exits. - Rm: req.HostConfig.AutoRemove, // Automatically remove container upon exit. - Pull: "missing", // nerdctl default. - StopSignal: stopSignal, - StopTimeout: stopTimeout, - CidFile: req.HostConfig.ContainerIDFile, // CidFile write the container ID to the file - OomKillDisable: req.HostConfig.OomKillDisable, - OomScoreAdj: req.HostConfig.OomScoreAdj, - Pid: req.HostConfig.PidMode, // Pid namespace to use + Interactive: false, // TODO: update this after attach supports STDIN + TTY: false, // TODO: update this after attach supports STDIN + Detach: true, // TODO: current implementation of create does not support AttachStdin, AttachStdout, and AttachStderr flags + Restart: restart, // Restart policy to apply when a container exits. + Rm: req.HostConfig.AutoRemove, // Automatically remove container upon exit. + Pull: "missing", // nerdctl default. + StopSignal: stopSignal, + StopTimeout: stopTimeout, + CidFile: req.HostConfig.ContainerIDFile, // CidFile write the container ID to the file + OomKillDisable: req.HostConfig.OomKillDisable, + OomScoreAdj: req.HostConfig.OomScoreAdj, + OomScoreAdjChanged: oomScoreAdjChanged, + Pid: req.HostConfig.PidMode, // Pid namespace to use // #endregion // #region for platform flags diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 6192321e..4dcc9f9c 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -783,6 +783,7 @@ var _ = Describe("Container Create API ", func() { // expected create options createOpt.IPC = "host" createOpt.OomScoreAdj = 200 + createOpt.OomScoreAdjChanged = true service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return( cid, nil) diff --git a/api/types/container_types.go b/api/types/container_types.go index 2e0ffd83..b36aaec1 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -82,17 +82,18 @@ type ContainerHostConfig struct { IpcMode string // IPC namespace to use for the container // TODO: Cgroup CgroupSpec // Cgroup to use for the container // TODO: Links []string // List of links (in the name:alias form) - OomKillDisable bool // specifies whether to disable OOM Killer - OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000) - PidMode string // PID namespace to use for the container - Privileged bool // Is the container in privileged mode - ReadonlyRootfs bool // Is the container root filesystem in read-only - SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux. (["key=value"]) - Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container - UTSMode string // UTS namespace to use for the container - ShmSize int64 // Size of /dev/shm in bytes. The size must be greater than 0. - Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container - Runtime string `json:",omitempty"` // Runtime to use with this container + OomKillDisable bool // specifies whether to disable OOM Killer + OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000) + OomScoreAdjChanged bool // OomScoreAdjChanged specifies whether the OOM preferences has been changed + PidMode string // PID namespace to use for the container + Privileged bool // Is the container in privileged mode + ReadonlyRootfs bool // Is the container root filesystem in read-only + SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux. (["key=value"]) + Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container + UTSMode string // UTS namespace to use for the container + ShmSize int64 // Size of /dev/shm in bytes. The size must be greater than 0. + Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container + Runtime string `json:",omitempty"` // Runtime to use with this container // TODO: PublishAllPorts bool // Should docker publish all exposed port for the container // TODO: StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container. // TODO: UsernsMode UsernsMode // The user namespace to use for the container