Skip to content

Commit 8dc97f8

Browse files
authored
feat: Add Blkio related options (#229)
Signed-off-by: Arjun Raja Yogidas <[email protected]>
1 parent e706101 commit 8dc97f8

File tree

4 files changed

+308
-15
lines changed

4 files changed

+308
-15
lines changed

api/handlers/container/create.go

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
ncTypes "github.com/containerd/nerdctl/v2/pkg/api/types"
1616
"github.com/containerd/nerdctl/v2/pkg/defaults"
1717
"github.com/docker/go-connections/nat"
18+
"github.com/moby/moby/api/types/blkiodev"
1819
"github.com/sirupsen/logrus"
1920

2021
"github.com/runfinch/finch-daemon/api/response"
@@ -193,18 +194,24 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
193194
// #endregion
194195

195196
// #region for resource flags
196-
CPUShares: uint64(req.HostConfig.CPUShares), // CPU shares (relative weight)
197-
Memory: memory, // memory limit (in bytes)
198-
CPUQuota: CpuQuota, // CPUQuota limits the CPU CFS (Completely Fair Scheduler) quota
199-
MemorySwappiness64: memorySwappiness, // Tuning container memory swappiness behaviour
200-
PidsLimit: pidLimit, // PidsLimit specifies the tune container pids limit
201-
Cgroupns: defaults.CgroupnsMode(), // nerdctl default.
202-
MemoryReservation: memoryReservation, // Memory soft limit (in bytes)
203-
MemorySwap: memorySwap, // Total memory usage (memory + swap); set `-1` to enable unlimited swap
204-
Ulimit: ulimits, // List of ulimits to be set in the container
205-
CPUPeriod: uint64(req.HostConfig.CPUPeriod), // CPU CFS (Completely Fair Scheduler) period
206-
CPUSetCPUs: req.HostConfig.CPUSetCPUs, // CpusetCpus 0-2, 0,1
207-
CPUSetMems: req.HostConfig.CPUSetMems, // CpusetMems 0-2, 0,1
197+
CPUSetCPUs: req.HostConfig.CPUSetCPUs, // CpusetCpus 0-2, 0,1
198+
CPUSetMems: req.HostConfig.CPUSetMems, // CpusetMems 0-2, 0,1
199+
CPUShares: uint64(req.HostConfig.CPUShares), // CPU shares (relative weight)
200+
CPUQuota: CpuQuota, // CPUQuota limits the CPU CFS (Completely Fair Scheduler) quota
201+
CPUPeriod: uint64(req.HostConfig.CPUPeriod),
202+
Memory: memory, // memory limit (in bytes)
203+
MemorySwap: memorySwap, // Total memory usage (memory + swap); set `-1` to enable unlimited swap
204+
MemoryReservation: memoryReservation, // Memory soft limit (in bytes)
205+
MemorySwappiness64: memorySwappiness, // Tuning container memory swappiness behaviour
206+
Ulimit: ulimits, // List of ulimits to be set in the container
207+
PidsLimit: pidLimit, // PidsLimit specifies the tune container pids limit
208+
Cgroupns: defaults.CgroupnsMode(), // nerdctl default.
209+
BlkioWeight: req.HostConfig.BlkioWeight,
210+
BlkioWeightDevice: weightDevicesToStrings(req.HostConfig.BlkioWeightDevice),
211+
BlkioDeviceReadBps: throttleDevicesToStrings(req.HostConfig.BlkioDeviceReadBps),
212+
BlkioDeviceWriteBps: throttleDevicesToStrings(req.HostConfig.BlkioDeviceWriteBps),
213+
BlkioDeviceReadIOps: throttleDevicesToStrings(req.HostConfig.BlkioDeviceReadIOps),
214+
BlkioDeviceWriteIOps: throttleDevicesToStrings(req.HostConfig.BlkioDeviceWriteIOps),
208215
// #endregion
209216

210217
// #region for user flags
@@ -332,3 +339,21 @@ func translatePortMappings(portMappings nat.PortMap) ([]gocni.PortMapping, error
332339
}
333340
return ports, nil
334341
}
342+
343+
// Helper function to convert WeightDevice array to string array.
344+
func weightDevicesToStrings(devices []*blkiodev.WeightDevice) []string {
345+
strings := make([]string, len(devices))
346+
for i, d := range devices {
347+
strings[i] = d.String()
348+
}
349+
return strings
350+
}
351+
352+
// Helper function to convert ThrottleDevice array to string array.
353+
func throttleDevicesToStrings(devices []*blkiodev.ThrottleDevice) []string {
354+
strings := make([]string, len(devices))
355+
for i, d := range devices {
356+
strings[i] = d.String()
357+
}
358+
return strings
359+
}

api/handlers/container/create_test.go

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ var _ = Describe("Container Create API ", func() {
676676
"Image": "test-image",
677677
"HostConfig": {
678678
"OomKillDisable": true
679-
}
679+
}
680680
}`)
681681
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
682682

@@ -690,6 +690,90 @@ var _ = Describe("Container Create API ", func() {
690690
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
691691
Expect(rr.Body).Should(MatchJSON(jsonResponse))
692692
})
693+
It("should set the BlkioWeight to a user specified value", func() {
694+
body := []byte(`{
695+
"Image": "test-image",
696+
"HostConfig": {
697+
"BlkioWeight": 300
698+
}
699+
}`)
700+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
701+
702+
// expected network options
703+
createOpt.BlkioWeight = 300
704+
705+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
706+
cid, nil)
707+
708+
// handler should return success message with 201 status code.
709+
h.create(rr, req)
710+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
711+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
712+
})
713+
714+
It("should set blkio device settings", func() {
715+
body := []byte(`{
716+
"Image": "test-image",
717+
"HostConfig": {
718+
"BlkioWeightDevice": [
719+
{
720+
"Path": "/dev/sda",
721+
"Weight": 400
722+
}
723+
],
724+
"BlkioDeviceReadBps": [
725+
{
726+
"Path": "/dev/sda",
727+
"Rate": 1048576
728+
}
729+
],
730+
"BlkioDeviceWriteBps": [
731+
{
732+
"Path": "/dev/sda",
733+
"Rate": 2097152
734+
}
735+
],
736+
"BlkioDeviceReadIOps": [
737+
{
738+
"Path": "/dev/sda",
739+
"Rate": 1000
740+
}
741+
],
742+
"BlkioDeviceWriteIOps": [
743+
{
744+
"Path": "/dev/sda",
745+
"Rate": 2000
746+
}
747+
]
748+
}
749+
}`)
750+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
751+
752+
// expected create options with blkio settings as strings
753+
createOpt.BlkioWeightDevice = []string{
754+
"/dev/sda:400",
755+
}
756+
createOpt.BlkioDeviceReadBps = []string{
757+
"/dev/sda:1048576",
758+
}
759+
createOpt.BlkioDeviceWriteBps = []string{
760+
"/dev/sda:2097152",
761+
}
762+
createOpt.BlkioDeviceReadIOps = []string{
763+
"/dev/sda:1000",
764+
}
765+
createOpt.BlkioDeviceWriteIOps = []string{
766+
"/dev/sda:2000",
767+
}
768+
769+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
770+
cid, nil)
771+
772+
// handler should return success message with 201 status code.
773+
h.create(rr, req)
774+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
775+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
776+
})
693777

694778
Context("translate port mappings", func() {
695779
It("should return empty if port mappings is nil", func() {
@@ -809,6 +893,12 @@ func getDefaultCreateOpt(conf config.Config) types.ContainerCreateOptions {
809893
PidsLimit: -1, // nerdctl default.
810894
Cgroupns: defaults.CgroupnsMode(), // nerdctl default.
811895
Ulimit: []string{},
896+
897+
BlkioWeightDevice: []string{},
898+
BlkioDeviceReadBps: []string{},
899+
BlkioDeviceWriteBps: []string{},
900+
BlkioDeviceReadIOps: []string{},
901+
BlkioDeviceWriteIOps: []string{},
812902
// #endregion
813903

814904
// #region for user flags

api/types/container_types.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
dockertypes "github.com/docker/docker/api/types/container"
1313
"github.com/docker/go-connections/nat"
1414
"github.com/docker/go-units"
15+
"github.com/moby/moby/api/types/blkiodev"
1516
)
1617

1718
// AttachOptions defines the available options for the container attach call.
@@ -112,8 +113,13 @@ type ContainerHostConfig struct {
112113
MemorySwappiness int64 // MemorySwappiness64 specifies the tune container memory swappiness (0 to 100) (default -1)
113114
// TODO: Resources
114115

115-
Ulimits []*Ulimit // List of ulimits to be set in the container
116-
// TODO: BlkioWeight uint16 // Block IO weight (relative weight vs. other containers)
116+
Ulimits []*Ulimit // List of ulimits to be set in the container
117+
BlkioWeight uint16 // Block IO weight (relative weight vs. other containers)
118+
BlkioWeightDevice []*blkiodev.WeightDevice
119+
BlkioDeviceReadBps []*blkiodev.ThrottleDevice
120+
BlkioDeviceWriteBps []*blkiodev.ThrottleDevice
121+
BlkioDeviceReadIOps []*blkiodev.ThrottleDevice
122+
BlkioDeviceWriteIOps []*blkiodev.ThrottleDevice
117123
// TODO: Devices []DeviceMapping // List of devices to map inside the container
118124
PidsLimit int64 // Setting PIDs limit for a container; Set `0` or `-1` for unlimited, or `null` to not change.
119125
// Mounts specs used by the container

e2e/tests/container_create.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@ import (
99
"fmt"
1010
"net/http"
1111
"os"
12+
"os/exec"
1213
"path/filepath"
1314
"strings"
1415
"time"
1516

1617
"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
1718
"github.com/docker/go-connections/nat"
19+
"github.com/moby/moby/api/types/blkiodev"
1820
. "github.com/onsi/ginkgo/v2"
1921
. "github.com/onsi/gomega"
22+
"github.com/opencontainers/runtime-spec/specs-go"
2023
"github.com/runfinch/common-tests/command"
2124
"github.com/runfinch/common-tests/ffs"
2225
"github.com/runfinch/common-tests/option"
@@ -790,6 +793,175 @@ func ContainerCreate(opt *option.Option) {
790793
Expect(ok).Should(BeTrue())
791794
Expect(memSet).Should(Equal("0"))
792795
})
796+
797+
It("should create container with specified blkio settings options", func() {
798+
// Create dummy device paths
799+
dummyDev1 := "/dev/dummy-zero1"
800+
dummyDev2 := "/dev/dummy-zero2"
801+
802+
// Create dummy devices (major number 1 for char devices)
803+
err := exec.Command("mknod", dummyDev1, "c", "1", "5").Run()
804+
Expect(err).Should(BeNil())
805+
err = exec.Command("mknod", dummyDev2, "c", "1", "6").Run()
806+
Expect(err).Should(BeNil())
807+
808+
// Cleanup dummy devices after test
809+
defer func() {
810+
exec.Command("rm", "-f", dummyDev1).Run()
811+
exec.Command("rm", "-f", dummyDev2).Run()
812+
}()
813+
814+
// define options
815+
options.Cmd = []string{"sleep", "Infinity"}
816+
options.HostConfig.BlkioWeight = 500 // valid values: 0-1000
817+
818+
// Create WeightDevice objects for input
819+
weightDevices := []*blkiodev.WeightDevice{
820+
{
821+
Path: dummyDev1,
822+
Weight: 400,
823+
},
824+
{
825+
Path: dummyDev2,
826+
Weight: 300,
827+
},
828+
}
829+
830+
// Create ThrottleDevice objects for input
831+
readBpsDevices := []*blkiodev.ThrottleDevice{
832+
{
833+
Path: dummyDev1,
834+
Rate: 1048576, // 1MB/s
835+
},
836+
}
837+
838+
writeBpsDevices := []*blkiodev.ThrottleDevice{
839+
{
840+
Path: dummyDev1,
841+
Rate: 2097152, // 2MB/s
842+
},
843+
}
844+
845+
readIopsDevices := []*blkiodev.ThrottleDevice{
846+
{
847+
Path: dummyDev1,
848+
Rate: 1000,
849+
},
850+
}
851+
852+
writeIopsDevices := []*blkiodev.ThrottleDevice{
853+
{
854+
Path: dummyDev1,
855+
Rate: 2000,
856+
},
857+
}
858+
859+
// Set the original device objects in the options
860+
options.HostConfig.BlkioWeightDevice = weightDevices
861+
options.HostConfig.BlkioDeviceReadBps = readBpsDevices
862+
options.HostConfig.BlkioDeviceWriteBps = writeBpsDevices
863+
options.HostConfig.BlkioDeviceReadIOps = readIopsDevices
864+
options.HostConfig.BlkioDeviceWriteIOps = writeIopsDevices
865+
866+
// create container
867+
statusCode, ctr := createContainer(uClient, url, testContainerName, options)
868+
Expect(statusCode).Should(Equal(http.StatusCreated))
869+
Expect(ctr.ID).ShouldNot(BeEmpty())
870+
871+
// start container
872+
command.Run(opt, "start", testContainerName)
873+
874+
// inspect container
875+
resp := command.Stdout(opt, "inspect", testContainerName)
876+
var inspect []*dockercompat.Container
877+
err = json.Unmarshal(resp, &inspect)
878+
Expect(err).Should(BeNil())
879+
Expect(inspect).Should(HaveLen(1))
880+
881+
// Verify blkio settings in LinuxBlkioSettings
882+
blkioSettings := inspect[0].HostConfig.LinuxBlkioSettings
883+
// Verify BlkioWeight
884+
Expect(blkioSettings.BlkioWeight).Should(Equal(options.HostConfig.BlkioWeight))
885+
886+
// Helper function to map major/minor to device path
887+
devicePathFromMajorMinor := func(major, minor int64) string {
888+
if major == 1 && minor == 5 {
889+
return dummyDev1
890+
}
891+
if major == 1 && minor == 6 {
892+
return dummyDev2
893+
}
894+
return fmt.Sprintf("/dev/unknown-%d-%d", major, minor)
895+
}
896+
897+
// Helper function to convert specs.LinuxWeightDevice to blkiodev.WeightDevice
898+
convertWeightDevice := func(wd *specs.LinuxWeightDevice) *blkiodev.WeightDevice {
899+
if wd == nil || wd.Weight == nil {
900+
return nil
901+
}
902+
return &blkiodev.WeightDevice{
903+
Path: devicePathFromMajorMinor(wd.Major, wd.Minor),
904+
Weight: *wd.Weight,
905+
}
906+
}
907+
908+
// Helper function to convert specs.LinuxThrottleDevice to blkiodev.ThrottleDevice
909+
convertThrottleDevice := func(td *specs.LinuxThrottleDevice) *blkiodev.ThrottleDevice {
910+
if td == nil {
911+
return nil
912+
}
913+
return &blkiodev.ThrottleDevice{
914+
Path: devicePathFromMajorMinor(td.Major, td.Minor),
915+
Rate: td.Rate,
916+
}
917+
}
918+
919+
// Convert response devices to blkiodev types
920+
responseWeightDevices := make([]*blkiodev.WeightDevice, 0, len(blkioSettings.BlkioWeightDevice))
921+
for _, d := range blkioSettings.BlkioWeightDevice {
922+
if converted := convertWeightDevice(d); converted != nil {
923+
responseWeightDevices = append(responseWeightDevices, converted)
924+
}
925+
}
926+
927+
responseReadBpsDevices := make([]*blkiodev.ThrottleDevice, 0, len(blkioSettings.BlkioDeviceReadBps))
928+
for _, d := range blkioSettings.BlkioDeviceReadBps {
929+
if converted := convertThrottleDevice(d); converted != nil {
930+
responseReadBpsDevices = append(responseReadBpsDevices, converted)
931+
}
932+
}
933+
934+
responseWriteBpsDevices := make([]*blkiodev.ThrottleDevice, 0, len(blkioSettings.BlkioDeviceWriteBps))
935+
for _, d := range blkioSettings.BlkioDeviceWriteBps {
936+
if converted := convertThrottleDevice(d); converted != nil {
937+
responseWriteBpsDevices = append(responseWriteBpsDevices, converted)
938+
}
939+
}
940+
941+
responseReadIopsDevices := make([]*blkiodev.ThrottleDevice, 0, len(blkioSettings.BlkioDeviceReadIOps))
942+
for _, d := range blkioSettings.BlkioDeviceReadIOps {
943+
if converted := convertThrottleDevice(d); converted != nil {
944+
responseReadIopsDevices = append(responseReadIopsDevices, converted)
945+
}
946+
}
947+
948+
responseWriteIopsDevices := make([]*blkiodev.ThrottleDevice, 0, len(blkioSettings.BlkioDeviceWriteIOps))
949+
for _, d := range blkioSettings.BlkioDeviceWriteIOps {
950+
if converted := convertThrottleDevice(d); converted != nil {
951+
responseWriteIopsDevices = append(responseWriteIopsDevices, converted)
952+
}
953+
}
954+
955+
// Compare string representations
956+
for i, wd := range weightDevices {
957+
Expect(responseWeightDevices[i].String()).Should(Equal(wd.String()))
958+
}
959+
960+
Expect(responseReadBpsDevices[0].String()).Should(Equal(readBpsDevices[0].String()))
961+
Expect(responseWriteBpsDevices[0].String()).Should(Equal(writeBpsDevices[0].String()))
962+
Expect(responseReadIopsDevices[0].String()).Should(Equal(readIopsDevices[0].String()))
963+
Expect(responseWriteIopsDevices[0].String()).Should(Equal(writeIopsDevices[0].String()))
964+
})
793965
})
794966
}
795967

0 commit comments

Comments
 (0)