Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions contrib/completions/bash/runc
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,12 @@ _runc_update() {

local options_with_args="
--blkio-weight
--blkio-leaf-weight
--blkio-weight-device
--blkio-throttle-readbps-device
--blkio-throttle-writebps-device
--blkio-throttle-readiops-device
--blkio-throttle-writeiops-device
--cpu-period
--cpu-quota
--cpu-share
Expand Down
39 changes: 39 additions & 0 deletions libcontainer/cgroups/fs/blkio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,45 @@ func TestBlkioSetMultipleWeightDevice(t *testing.T) {
}
}

func TestBlkioSetThrottleDevice(t *testing.T) {
helper := NewCgroupTestUtil("blkio", t)
defer helper.cleanup()

const (
throttleDeviceBefore = "8:0 400"
)
deviceSet := map[string]string{"blkio.throttle.read_bps_device": throttleDeviceBefore,
"blkio.throttle.write_bps_device": throttleDeviceBefore,
"blkio.throttle.read_iops_device": throttleDeviceBefore,
"blkio.throttle.write_iops_device": throttleDeviceBefore,
}

td := configs.NewThrottleDevice(8, 0, 500)
throttleDeviceAfter := td.String()

helper.writeFileContents(deviceSet)

helper.CgroupData.config.Resources.BlkioThrottleReadBpsDevice = []*configs.ThrottleDevice{td}
helper.CgroupData.config.Resources.BlkioThrottleWriteBpsDevice = []*configs.ThrottleDevice{td}
helper.CgroupData.config.Resources.BlkioThrottleReadIOPSDevice = []*configs.ThrottleDevice{td}
helper.CgroupData.config.Resources.BlkioThrottleWriteIOPSDevice = []*configs.ThrottleDevice{td}
blkio := &BlkioGroup{}
if err := blkio.Set(helper.CgroupPath, helper.CgroupData.config); err != nil {
t.Fatal(err)
}

for d := range deviceSet {
value, err := getCgroupParamString(helper.CgroupPath, d)
if err != nil {
t.Fatalf("Failed to parse %s - %s", d, err)
}

if value != throttleDeviceAfter {
t.Fatalf("Got the wrong value, set %s failed.", d)
}
}
}

func TestBlkioStats(t *testing.T) {
helper := NewCgroupTestUtil("blkio", t)
defer helper.cleanup()
Expand Down
38 changes: 25 additions & 13 deletions man/runc-update.8.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,35 @@ accepted format is as follow (unchanged values can be omitted):
"mems": ""
},
"blockIO": {
"blkioWeight": 0
"blkioWeight": 0,
"blkioLeafWeight": 0,
"blkioWeightDevice": "",
"blkioThrottleReadBpsDevice": "",
"blkioThrottleWriteBpsDevice": "",
"blkioThrottleReadIOPSDevice": "",
"blkioThrottleWriteIOPSDevice": ""
},
}

Note: if data is to be read from a file or the standard input, all
other options are ignored.

# OPTIONS
--resources value, -r value path to the file containing the resources to update or '-' to read from the standard input
--blkio-weight value Specifies per cgroup weight, range is from 10 to 1000 (default: 0)
--cpu-period value CPU period to be used for hardcapping (in usecs). 0 to use system default
--cpu-quota value CPU hardcap limit (in usecs). Allowed cpu time in a given period
--cpu-share value CPU shares (relative weight vs. other containers)
--cpuset-cpus value CPU(s) to use
--cpuset-mems value Memory node(s) to use
--kernel-memory value Kernel memory limit (in bytes)
--kernel-memory-tcp value Kernel memory limit (in bytes) for tcp buffer
--memory value Memory limit (in bytes)
--memory-reservation value Memory reservation or soft_limit (in bytes)
--memory-swap value Total memory usage (memory + swap); set '-1' to enable unlimited swap
--resources value, -r value path to the file containing the resources to update or '-' to read from the standard input
--blkio-weight value Specifies per cgroup weight
--blkio-leaf-weight value Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, cfq scheduler only
--blkio-weight-device value Weight per cgroup per device, can override blkio-weight. Argument must be of the form "<MAJOR>:<MINOR> <WEIGHT> [LEAF_WEIGHT]"
--blkio-throttle-readbps-device value IO read rate limit per cgroup per device, bytes per second. Argument must be of the form "<MAJOR>:<MINOR> <RATE>"
--blkio-throttle-writebps-device value IO write rate limit per cgroup per divice, bytes per second. Argument must be of the form "<MAJOR>:<MINOR> <RATE>"
--blkio-throttle-readiops-device value IO read rate limit per cgroup per device, IO per second. Argument must be of the form "<MAJOR>:<MINOR> <RATE>"
--blkio-throttle-writeiops-device value IO write rate limit per cgroup per device, IO per second. Argument must be of the form "<MAJOR>:<MINOR> <RATE>"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've added all of these flags ...

--cpu-period value CPU period to be used for hardcapping (in usecs). 0 to use system default
--cpu-quota value CPU hardcap limit (in usecs). Allowed cpu time in a given period
--cpu-share value CPU shares (relative weight vs. other containers)
--cpuset-cpus value CPU(s) to use
--cpuset-mems value Memory node(s) to use
--kernel-memory value Kernel memory limit (in bytes)
--kernel-memory-tcp value Kernel memory limit (in bytes) for tcp buffer
--memory value Memory limit (in bytes)
--memory-reservation value Memory reservation or soft_limit (in bytes)
--memory-swap value Total memory usage (memory + swap); set '-1' to enable unlimited swap
9 changes: 8 additions & 1 deletion tests/integration/update.bats
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ function setup() {
"cpus": "0"
},
"blockio": {
"blkioWeight": 1000
"blkioWeight": 1000,
"blkioLeafWeight": 1000
},
EOF
)
Expand Down Expand Up @@ -62,6 +63,7 @@ function check_cgroup_value() {

# check that initial values were properly set
check_cgroup_value $CGROUP_BLKIO "blkio.weight" 1000
check_cgroup_value $CGROUP_BLKIO "blkio.leaf_weight" 1000
check_cgroup_value $CGROUP_CPU "cpu.cfs_period_us" 1000000
check_cgroup_value $CGROUP_CPU "cpu.cfs_quota_us" 500000
check_cgroup_value $CGROUP_CPU "cpu.shares" 100
Expand All @@ -76,6 +78,10 @@ function check_cgroup_value() {
[ "$status" -eq 0 ]
check_cgroup_value $CGROUP_BLKIO "blkio.weight" 500

# update blkio-leaf-weight
runc update test_update --blkio-leaf-weight 500
check_cgroup_value $CGROUP_BLKIO "blkio.leaf_weight" 500
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... but only added a test for leaf weight. Is this intentional (meaning, is it possible for us to test the other options as well here)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cyphar thanks for checking this PR. For other parameters of blkio, we cannot test them in current CI machine, because it is lack of cfq device in CI machine. What is your suggestion about the device limitation of CI machine?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add the tests as "optional" if possible (at SUSE we run the test suite on SUSE machines, so the tests are useful outside of the CI setup for this repo). But if we're not testing it in the CI then it's not as important (and could wait for a follow-up PR).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cyphar , currently, the CI tests are wrote with shell framework. If i add the new flags, some tests are not passed due to the "device" problem. I have tried to add the new flags to CI tests by using 'conditional' judgement, but if do so, the code seems very ugly. For example, if i add new flags into 'DATA; section, i will double the 'old flags' twice. I think this is not acceptable. Can I add those new flags into tests later in another PR if the test framework become more flexible?
Welcome your suggestions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we can handle this later I guess. We need to do the test framework improvements for the memory cgroup anyway.


# update cpu-period
runc update test_update --cpu-period 900000
[ "$status" -eq 0 ]
Expand All @@ -102,6 +108,7 @@ function check_cgroup_value() {
# update memory limit
runc update test_update --memory 67108864
[ "$status" -eq 0 ]

check_cgroup_value $CGROUP_MEMORY "memory.limit_in_bytes" 67108864

runc update test_update --memory 50M
Expand Down
149 changes: 145 additions & 4 deletions update.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ import (
"encoding/json"
"fmt"
"os"
"regexp"
"strconv"

"github.com/docker/go-units"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runc/libcontainer/configs"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
)

func u64Ptr(i uint64) *uint64 { return &i }
func u16Ptr(i uint16) *uint16 { return &i }

var regBlkioWeightDevice = regexp.MustCompile(`([0-9]+):([0-9]+) ([0-9]+)(?: ([0-9]+))?`)
var regBlkioThrottleDevice = regexp.MustCompile(`([0-9]+):([0-9]+) ([0-9]+)`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this format defined in the spec (my guess is yes), and if so please at least comment that it comes from the spec or something so it's a bit more clear. In addition, if it comes from the spec maybe all of these conversion functions should go inside libcontainer/specconv (@hqhq what do you think?).


var updateCommand = cli.Command{
Name: "update",
Usage: "update container resource constraints",
Expand Down Expand Up @@ -44,7 +49,13 @@ The accepted format is as follow (unchanged values can be omitted):
"mems": ""
},
"blockIO": {
"blkioWeight": 0
"blkioWeight": 0,
"blkioLeafWeight": 0,
"blkioWeightDevice": "",
"blkioThrottleReadBpsDevice": "",
"blkioThrottleWriteBpsDevice": "",
"blkioThrottleReadIOPSDevice": "",
"blkioThrottleWriteIOPSDevice": ""
},
}

Expand All @@ -55,7 +66,31 @@ other options are ignored.

cli.IntFlag{
Name: "blkio-weight",
Usage: "Specifies per cgroup weight, range is from 10 to 1000",
Usage: "Specifies per cgroup weight",
},
cli.IntFlag{
Name: "blkio-leaf-weight",
Usage: "Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, cfq scheduler only",
},
cli.StringSliceFlag{
Name: "blkio-weight-device",
Usage: "Weight per cgroup per device, can override blkio-weight. Argument must be of the form \"<MAJOR>:<MINOR> <WEIGHT> [LEAF_WEIGHT]\"",
},
cli.StringSliceFlag{
Name: "blkio-throttle-readbps-device",
Usage: "IO read rate limit per cgroup per device, bytes per second. Argument must be of the form \"<MAJOR>:<MINOR> <RATE>\"",
},
cli.StringSliceFlag{
Name: "blkio-throttle-writebps-device",
Usage: "IO write rate limit per cgroup per divice, bytes per second. Argument must be of the form \"<MAJOR>:<MINOR> <RATE>\"",
},
cli.StringSliceFlag{
Name: "blkio-throttle-readiops-device",
Usage: "IO read rate limit per cgroup per device, IO per second. Argument must be of the form \"<MAJOR>:<MINOR> <RATE>\"",
},
cli.StringSliceFlag{
Name: "blkio-throttle-writeiops-device",
Usage: "IO write rate limit per cgroup per device, IO per second. Argument must be of the form \"<MAJOR>:<MINOR> <RATE>\"",
},
cli.StringFlag{
Name: "cpu-period",
Expand Down Expand Up @@ -120,7 +155,8 @@ other options are ignored.
Mems: sPtr(""),
},
BlockIO: &specs.BlockIO{
Weight: u16Ptr(0),
Weight: u16Ptr(0),
LeafWeight: u16Ptr(0),
},
}

Expand Down Expand Up @@ -148,6 +184,26 @@ other options are ignored.
if val := context.Int("blkio-weight"); val != 0 {
r.BlockIO.Weight = u16Ptr(uint16(val))
}
if val := context.Int("blkio-leaf-weight"); val != 0 {
r.BlockIO.LeafWeight = u16Ptr(uint16(val))
}
if val := context.StringSlice("blkio-weight-device"); val != nil {
if r.BlockIO.WeightDevice, err = getBlkioWeightDeviceFromString(val); err != nil {
return fmt.Errorf("invalid value for blkio-weight-device: %v", err)
}
}
for opt, dest := range map[string]*[]specs.ThrottleDevice{
"blkio-throttle-readbps-device": &r.BlockIO.ThrottleReadBpsDevice,
"blkio-throttle-writebps-device": &r.BlockIO.ThrottleWriteBpsDevice,
"blkio-throttle-readiops-device": &r.BlockIO.ThrottleReadIOPSDevice,
"blkio-throttle-writeiops-device": &r.BlockIO.ThrottleWriteIOPSDevice,
} {
if val := context.StringSlice(opt); val != nil {
if *dest, err = getBlkioThrottleDeviceFromString(val); err != nil {
return fmt.Errorf("invalid value for %s: %v", opt, err)
}
}
}
if val := context.String("cpuset-cpus"); val != "" {
r.CPU.Cpus = &val
}
Expand Down Expand Up @@ -188,6 +244,12 @@ other options are ignored.

// Update the value
config.Cgroups.Resources.BlkioWeight = *r.BlockIO.Weight
config.Cgroups.Resources.BlkioLeafWeight = *r.BlockIO.LeafWeight
config.Cgroups.Resources.BlkioWeightDevice = convertSpecWeightDevices(r.BlockIO.WeightDevice)
config.Cgroups.Resources.BlkioThrottleReadBpsDevice = convertSpecThrottleDevices(r.BlockIO.ThrottleReadBpsDevice)
config.Cgroups.Resources.BlkioThrottleWriteBpsDevice = convertSpecThrottleDevices(r.BlockIO.ThrottleWriteBpsDevice)
config.Cgroups.Resources.BlkioThrottleReadIOPSDevice = convertSpecThrottleDevices(r.BlockIO.ThrottleReadIOPSDevice)
config.Cgroups.Resources.BlkioThrottleWriteIOPSDevice = convertSpecThrottleDevices(r.BlockIO.ThrottleWriteIOPSDevice)
config.Cgroups.Resources.CpuPeriod = int64(*r.CPU.Period)
config.Cgroups.Resources.CpuQuota = int64(*r.CPU.Quota)
config.Cgroups.Resources.CpuShares = int64(*r.CPU.Shares)
Expand All @@ -205,3 +267,82 @@ other options are ignored.
return nil
},
}

func getBlkioWeightDeviceFromString(deviceStr []string) ([]specs.WeightDevice, error) {
var devs []specs.WeightDevice
for _, s := range deviceStr {
elems := regBlkioWeightDevice.FindStringSubmatch(s)
if elems == nil || len(elems) < 5 {
return nil, fmt.Errorf("invalid value for %s", s)
}
var dev specs.WeightDevice
var err error
var weight uint64
if dev.Major, err = strconv.ParseInt(elems[1], 10, 64); err != nil {
return nil, fmt.Errorf("invalid value for %s, err: %v", elems[1], err)
}
if dev.Minor, err = strconv.ParseInt(elems[2], 10, 64); err != nil {
return nil, fmt.Errorf("invalid value for %s, err: %v", elems[2], err)
}
if weight, err = strconv.ParseUint(elems[3], 10, 16); err != nil {
return nil, fmt.Errorf("invalid value for %s, err: %v", elems[3], err)
}
dev.Weight = u16Ptr(uint16(weight))
if elems[4] != "" {
if weight, err = strconv.ParseUint(elems[4], 10, 16); err != nil {
return nil, fmt.Errorf("invalid value for %s, err: %v", elems[4], err)
}
dev.LeafWeight = u16Ptr(uint16(weight))
}
devs = append(devs, dev)
}

return devs, nil
}

func getBlkioThrottleDeviceFromString(deviceStr []string) ([]specs.ThrottleDevice, error) {
var devs []specs.ThrottleDevice
for _, s := range deviceStr {
elems := regBlkioThrottleDevice.FindStringSubmatch(s)
if elems == nil || len(elems) < 4 {
return nil, fmt.Errorf("invalid value for %s", s)
}
var dev specs.ThrottleDevice
var err error
var rate uint64
if dev.Major, err = strconv.ParseInt(elems[1], 10, 64); err != nil {
return nil, fmt.Errorf("invalid value for %s, err: %v", elems[1], err)
}
if dev.Minor, err = strconv.ParseInt(elems[2], 10, 64); err != nil {
return nil, fmt.Errorf("invalid value for %s, err: %v", elems[2], err)
}
if rate, err = strconv.ParseUint(elems[3], 10, 64); err != nil {
return nil, fmt.Errorf("invalid value for %s, err: %v", elems[3], err)
}
dev.Rate = u64Ptr(rate)
devs = append(devs, dev)
}
return devs, nil
}

func convertSpecWeightDevices(devices []specs.WeightDevice) []*configs.WeightDevice {
var cwds []*configs.WeightDevice
for _, d := range devices {
leafWeight := uint16(0)
if d.LeafWeight != nil {
leafWeight = *d.LeafWeight
}
wd := configs.NewWeightDevice(d.Major, d.Minor, *d.Weight, leafWeight)
cwds = append(cwds, wd)
}
return cwds
}

func convertSpecThrottleDevices(devices []specs.ThrottleDevice) []*configs.ThrottleDevice {
var ctds []*configs.ThrottleDevice
for _, d := range devices {
td := configs.NewThrottleDevice(d.Major, d.Minor, *d.Rate)
ctds = append(ctds, td)
}
return ctds
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use configs.NewWeightDevice and configs.NewThrottleDevice instead of these.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hqhq Good suggestion, I will amend this. Thanks.