Skip to content

Commit

Permalink
chore: add api options to container create
Browse files Browse the repository at this point in the history
Signed-off-by: Arjun Raja Yogidas <[email protected]>
  • Loading branch information
coderbirju committed Dec 18, 2024
1 parent 5c99f3e commit 82a5f79
Show file tree
Hide file tree
Showing 4 changed files with 431 additions and 34 deletions.
64 changes: 57 additions & 7 deletions api/handlers/container/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,22 @@ 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())
}
}
// Environment vars:
env := []string{}
if req.Env != nil {
Expand All @@ -119,6 +135,36 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
capAdd = req.HostConfig.CapAdd
}

capDrop := []string{}
if req.HostConfig.CapDrop != nil {
capDrop = req.HostConfig.CapDrop
}

memoryReservation := ""
if req.HostConfig.MemoryReservation != 0 {
memoryReservation = fmt.Sprint(req.HostConfig.MemoryReservation)
}

memorySwap := ""
if req.HostConfig.MemorySwap != 0 {
memorySwap = fmt.Sprint(req.HostConfig.MemorySwap)
}

memorySwappiness := int64(-1)
if req.HostConfig.MemorySwappiness > 0 {
memorySwappiness = req.HostConfig.MemorySwappiness
}

pidLimit := int64(-1)
if req.HostConfig.PidsLimit > 0 {
pidLimit = req.HostConfig.PidsLimit
}

CpuQuota := int64(-1)
if req.HostConfig.CPUQuota != 0 {
CpuQuota = req.HostConfig.CPUQuota
}

globalOpt := ncTypes.GlobalCommandOptions(*h.Config)
createOpt := ncTypes.ContainerCreateOptions{
Stdout: nil,
Expand All @@ -134,6 +180,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
Pull: "missing", // nerdctl default.
StopSignal: stopSignal,
StopTimeout: stopTimeout,
CidFile: req.HostConfig.ContainerIDFile, // CidFile write the container ID to the file
// #endregion

// #region for platform flags
Expand All @@ -147,23 +194,26 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
// #region for resource flags
CPUShares: uint64(req.HostConfig.CPUShares), // CPU shares (relative weight)
Memory: memory, // memory limit (in bytes)
CPUQuota: -1, // nerdctl default.
MemorySwappiness64: -1, // nerdctl default.
PidsLimit: -1, // nerdctl default.
CPUQuota: CpuQuota, // nerdctl default.
MemorySwappiness64: memorySwappiness, // nerdctl default.
PidsLimit: pidLimit, // PidsLimit specifies the tune container pids limit
Cgroupns: defaults.CgroupnsMode(), // nerdctl default.
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),
// #endregion

// #region for user flags
User: req.User,
GroupAdd: []string{}, // nerdctl default.
User: req.User,
// #endregion

// #region for security flags
SecurityOpt: []string{}, // nerdctl default.
CapAdd: capAdd,
CapDrop: []string{}, // nerdctl default.
CapDrop: capDrop,
Privileged: req.HostConfig.Privileged,
// #endregion

// #region for runtime flags
Runtime: defaults.Runtime, // nerdctl default.
// #endregion
Expand Down
171 changes: 169 additions & 2 deletions api/handlers/container/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,172 @@ 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 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 set MemoryReservation, MemorySwap and MemorySwappiness create options for resources", func() {
body := []byte(`{
"Image": "test-image",
"HostConfig": {
"MemoryReservation": 209710,
"MemorySwap": 514288000,
"MemorySwappiness": 25
}
}`)
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))

// expected create options
createOpt.MemoryReservation = "209710"
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 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 set Privileged option", func() {
body := []byte(`{
"Image": "test-image",
"HostConfig": {
"Privileged": true
}
}`)
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))

// expected create options
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 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 set PidLimit option", func() {
body := []byte(`{
"Image": "test-image",
"HostConfig": {
"PidsLimit": 200
}
}`)
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))

// expected create options
createOpt.PidsLimit = 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))
Expand Down Expand Up @@ -542,17 +708,18 @@ func getDefaultCreateOpt(conf config.Config) types.ContainerCreateOptions {
MemorySwappiness64: -1, // nerdctl default.
PidsLimit: -1, // nerdctl default.
Cgroupns: defaults.CgroupnsMode(), // nerdctl default.
Ulimit: []string{},
// #endregion

// #region for user flags
User: "",
GroupAdd: []string{}, // nerdctl default.
User: "",
// #endregion

// #region for security flags
SecurityOpt: []string{}, // nerdctl default.
CapAdd: []string{}, // nerdctl default.
CapDrop: []string{}, // nerdctl default.
Privileged: false,
// #endregion

// #region for runtime flags
Expand Down
65 changes: 40 additions & 25 deletions api/types/container_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/containerd/nerdctl/pkg/inspecttypes/dockercompat"
dockertypes "github.com/docker/docker/api/types"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
)

// AttachOptions defines the available options for the container attach call.
Expand Down Expand Up @@ -57,52 +58,64 @@ type ContainerConfig struct {
// HostConfig is from https://github.com/moby/moby/blob/v24.0.2/api/types/container/hostconfig.go#L376-L436
type ContainerHostConfig struct {
// Applicable to all platforms
Binds []string // List of volume bindings for this container
// TODO: ContainerIDFile string // File (path) where the containerId is written
LogConfig LogConfig // Configuration of the logs for this container
NetworkMode string // Network mode to use for the container
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
Binds []string // List of volume bindings for this container
ContainerIDFile string // File (path) where the containerId is written
LogConfig LogConfig // Configuration of the logs for this container
NetworkMode string // Network mode to use for the container
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
// TODO: VolumeDriver string // Name of the volume driver used to mount volumes
// TODO: VolumesFrom []string // List of volumes to take from other container
// 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

// Applicable to UNIX platforms
CapAdd []string // List of kernel capabilities to add to the container
// TODO: CapDrop strslice.StrSlice // List of kernel capabilities to remove from the container
// TODO: CgroupnsMode CgroupnsMode // Cgroup namespace mode to use for the container
CapAdd []string // List of kernel capabilities to add to the container KEEP
CapDrop []string // List of kernel capabilities to remove from the container KEEP
// 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
// TODO: GroupAdd []string // List of additional groups that the container process will run as
// TODO: IpcMode IpcMode // IPC namespace to use for the container
// TODO: 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)
// TODO: OomScoreAdj int // Container preference for OOM-killing
// TODO: PidMode PidMode // PID namespace to use for the container
// TODO: Privileged bool // Is the container in privileged mode
// TODO: 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 KEEP
// 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
// TODO: PublishAllPorts bool // Should docker publish all exposed port for the container
// 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.
// TODO: StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container.
// TODO: Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container
// TODO: UTSMode UTSMode // UTS namespace to use for the container
// TODO: UsernsMode UsernsMode // The user namespace to use for the container
// TODO: ShmSize int64 // Total shm memory usage
// 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

// Applicable to Windows
// 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)
CPUShares int64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers) KEEP
Memory int64 // Memory limit (in bytes) KEEP
CPUPeriod int64 `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period KEEP
CPUQuota int64 `json:"CpuQuota"` // CPU CFS (Completely Fair Scheduler) quota KEEP
// 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) KEEP
MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap KEEP
MemorySwappiness int64 // MemorySwappiness64 specifies the tune container memory swappiness (0 to 100) (default -1) KEEP
// TODO: Resources

Ulimits []*Ulimit // List of ulimits to be set in the container KEEP
// TODO: 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. KEEP
// Mounts specs used by the container
// TODO: Mounts []mount.Mount `json:",omitempty"`

Expand Down Expand Up @@ -249,3 +262,5 @@ type StatsJSON struct {
// Networks request version >=1.21
Networks map[string]dockertypes.NetworkStats `json:"networks,omitempty"`
}

type Ulimit = units.Ulimit
Loading

0 comments on commit 82a5f79

Please sign in to comment.