diff --git a/api/handlers/container/create.go b/api/handlers/container/create.go index 5ff17444..ef002c9a 100644 --- a/api/handlers/container/create.go +++ b/api/handlers/container/create.go @@ -246,6 +246,8 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) { CPUShares: uint64(req.HostConfig.CPUShares), // CPU shares (relative weight) CPUQuota: CpuQuota, // CPUQuota limits the CPU CFS (Completely Fair Scheduler) quota CPUPeriod: uint64(req.HostConfig.CPUPeriod), + CPURealtimePeriod: uint64(req.HostConfig.CPURealtimePeriod), + CPURealtimeRuntime: uint64(req.HostConfig.CPURealtimeRuntime), Memory: memory, // memory limit (in bytes) MemorySwap: memorySwap, // Total memory usage (memory + swap); set `-1` to enable unlimited swap MemoryReservation: memoryReservation, // Memory soft limit (in bytes) diff --git a/api/handlers/container/create_test.go b/api/handlers/container/create_test.go index 5920d9e1..9f12503f 100644 --- a/api/handlers/container/create_test.go +++ b/api/handlers/container/create_test.go @@ -17,9 +17,9 @@ import ( "github.com/containerd/nerdctl/v2/pkg/config" "github.com/containerd/nerdctl/v2/pkg/defaults" "github.com/docker/go-connections/nat" - "go.uber.org/mock/gomock" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "go.uber.org/mock/gomock" "github.com/runfinch/finch-daemon/mocks/mocks_container" "github.com/runfinch/finch-daemon/mocks/mocks_logger" @@ -437,6 +437,29 @@ var _ = Describe("Container Create API ", func() { Expect(rr.Body).Should(MatchJSON(jsonResponse)) }) + It("should set both CPURealtimePeriod and CPURealtimeRuntime create options for resources", func() { + body := []byte(`{ + "Image": "test-image", + "HostConfig": { + "CpuRealtimePeriod": 1000000, + "CpuRealtimeRuntime": 950000 + } + }`) + req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body)) + + // expected create options + createOpt.CPURealtimePeriod = 1000000 + createOpt.CPURealtimeRuntime = 950000 + + 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", diff --git a/api/types/container_types.go b/api/types/container_types.go index 5bb19f4b..303f0176 100644 --- a/api/types/container_types.go +++ b/api/types/container_types.go @@ -103,15 +103,17 @@ 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) - 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. - Memory int64 // Memory limit (in bytes) - 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) + 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. + CPURealtimePeriod int64 `json:"CpuRealtimePeriod"` // CPU real-time period + CPURealtimeRuntime int64 `json:"CpuRealtimeRuntime"` // CPU real-time runtime + Memory int64 // Memory limit (in bytes) + 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 diff --git a/e2e/tests/container_create.go b/e2e/tests/container_create.go index 2d93d107..ddf7e293 100644 --- a/e2e/tests/container_create.go +++ b/e2e/tests/container_create.go @@ -478,6 +478,46 @@ func ContainerCreate(opt *option.Option, pOpt util.NewOpt) { Expect(int64(period)).Should(Equal(int64(100000))) }) + It("should create a container with both CPURealtimePeriod and CPURealtimeRuntime options", func() { + // define options + options.Cmd = []string{"sleep", "Infinity"} + options.HostConfig.CPURealtimePeriod = 1000000 + options.HostConfig.CPURealtimeRuntime = 950000 + + // create container + statusCode, ctr := createContainer(uClient, url, testContainerName, options) + Expect(statusCode).Should(Equal(http.StatusCreated)) + Expect(ctr.ID).ShouldNot(BeEmpty()) + + // start container + command.Run(opt, "start", testContainerName) + + nativeResp := command.Stdout(opt, "inspect", "--mode=native", testContainerName) + var nativeInspect []map[string]interface{} + err := json.Unmarshal(nativeResp, &nativeInspect) + Expect(err).Should(BeNil()) + Expect(nativeInspect).Should(HaveLen(1)) + + // Navigate to the CPU settings + spec, ok := nativeInspect[0]["Spec"].(map[string]interface{}) + Expect(ok).Should(BeTrue()) + linux, ok := spec["linux"].(map[string]interface{}) + Expect(ok).Should(BeTrue()) + resources, ok := linux["resources"].(map[string]interface{}) + Expect(ok).Should(BeTrue()) + cpu, ok := resources["cpu"].(map[string]interface{}) + Expect(ok).Should(BeTrue()) + + // Verify both CPURealtimePeriod and CPURealtimeRuntime values + realtimePeriod, ok := cpu["realtimePeriod"].(float64) + Expect(ok).Should(BeTrue()) + Expect(int64(realtimePeriod)).Should(Equal(int64(1000000))) + + realtimeRuntime, ok := cpu["realtimeRuntime"].(float64) + Expect(ok).Should(BeTrue()) + Expect(int64(realtimeRuntime)).Should(Equal(int64(950000))) + }) + It("should create a container with specified Memory qouta and PidLimits options", func() { // define options options.Cmd = []string{"sleep", "Infinity"} diff --git a/mocks/mocks_container/container.go b/mocks/mocks_container/container.go index e287d25b..95a8e8e6 100644 --- a/mocks/mocks_container/container.go +++ b/mocks/mocks_container/container.go @@ -193,7 +193,7 @@ func (m *MockContainer) Restore(arg0 context.Context, arg1 cio.Creator, arg2 str } // Restore indicates an expected call of Restore. -func (mr *MockContainerMockRecorder) Restore(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockContainerMockRecorder) Restore(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Restore", reflect.TypeOf((*MockContainer)(nil).Restore), arg0, arg1, arg2) }