Merge pull request #376 from hqhq/hq_add_set_interface
add a new api Set
This commit is contained in:
commit
3ca0e1ff95
|
@ -31,6 +31,9 @@ type Manager interface {
|
||||||
// Returns cgroup paths to save in a state file and to be able to
|
// Returns cgroup paths to save in a state file and to be able to
|
||||||
// restore the object later.
|
// restore the object later.
|
||||||
GetPaths() map[string]string
|
GetPaths() map[string]string
|
||||||
|
|
||||||
|
// Set the cgroup as configured.
|
||||||
|
Set(container *configs.Config) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type NotFoundError struct {
|
type NotFoundError struct {
|
||||||
|
|
|
@ -31,7 +31,9 @@ type subsystem interface {
|
||||||
// Removes the cgroup represented by 'data'.
|
// Removes the cgroup represented by 'data'.
|
||||||
Remove(*data) error
|
Remove(*data) error
|
||||||
// Creates and joins the cgroup represented by data.
|
// Creates and joins the cgroup represented by data.
|
||||||
Set(*data) error
|
Apply(*data) error
|
||||||
|
// Set the cgroup represented by cgroup.
|
||||||
|
Set(path string, cgroup *configs.Cgroup) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
|
@ -91,7 +93,7 @@ func (m *Manager) Apply(pid int) error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
for name, sys := range subsystems {
|
for name, sys := range subsystems {
|
||||||
if err := sys.Set(d); err != nil {
|
if err := sys.Apply(d); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// TODO: Apply should, ideally, be reentrant or be broken up into a separate
|
// TODO: Apply should, ideally, be reentrant or be broken up into a separate
|
||||||
|
@ -129,7 +131,7 @@ func ApplyDevices(c *configs.Cgroup, pid int) error {
|
||||||
|
|
||||||
devices := subsystems["devices"]
|
devices := subsystems["devices"]
|
||||||
|
|
||||||
return devices.Set(d)
|
return devices.Apply(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) GetStats() (*cgroups.Stats, error) {
|
func (m *Manager) GetStats() (*cgroups.Stats, error) {
|
||||||
|
@ -147,6 +149,20 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) {
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Set(container *configs.Config) error {
|
||||||
|
for name, path := range m.Paths {
|
||||||
|
sys, ok := subsystems[name]
|
||||||
|
if !ok || !cgroups.PathExists(path) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := sys.Set(path, container.Cgroups); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Freeze toggles the container's freezer cgroup depending on the state
|
// Freeze toggles the container's freezer cgroup depending on the state
|
||||||
// provided
|
// provided
|
||||||
func (m *Manager) Freeze(state configs.FreezerState) error {
|
func (m *Manager) Freeze(state configs.FreezerState) error {
|
||||||
|
@ -159,7 +175,7 @@ func (m *Manager) Freeze(state configs.FreezerState) error {
|
||||||
m.Cgroups.Freezer = state
|
m.Cgroups.Freezer = state
|
||||||
|
|
||||||
freezer := subsystems["freezer"]
|
freezer := subsystems["freezer"]
|
||||||
err = freezer.Set(d)
|
err = freezer.Apply(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.Cgroups.Freezer = prevState
|
m.Cgroups.Freezer = prevState
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -9,19 +9,28 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
|
"github.com/docker/libcontainer/configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BlkioGroup struct {
|
type BlkioGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BlkioGroup) Set(d *data) error {
|
func (s *BlkioGroup) Apply(d *data) error {
|
||||||
dir, err := d.join("blkio")
|
dir, err := d.join("blkio")
|
||||||
if err != nil && !cgroups.IsNotFound(err) {
|
if err != nil && !cgroups.IsNotFound(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.c.BlkioWeight != 0 {
|
if err := s.Set(dir, d.c); err != nil {
|
||||||
if err := writeFile(dir, "blkio.weight", strconv.FormatInt(d.c.BlkioWeight, 10)); err != nil {
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error {
|
||||||
|
if cgroup.BlkioWeight != 0 {
|
||||||
|
if err := writeFile(path, "blkio.weight", strconv.FormatInt(cgroup.BlkioWeight, 10)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
|
@ -72,6 +73,35 @@ func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, min
|
||||||
*blkioStatEntries = append(*blkioStatEntries, cgroups.BlkioStatEntry{Major: major, Minor: minor, Value: value, Op: op})
|
*blkioStatEntries = append(*blkioStatEntries, cgroups.BlkioStatEntry{Major: major, Minor: minor, Value: value, Op: op})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBlkioSetWeight(t *testing.T) {
|
||||||
|
helper := NewCgroupTestUtil("blkio", t)
|
||||||
|
defer helper.cleanup()
|
||||||
|
|
||||||
|
const (
|
||||||
|
weightBefore = 100
|
||||||
|
weightAfter = 200
|
||||||
|
)
|
||||||
|
|
||||||
|
helper.writeFileContents(map[string]string{
|
||||||
|
"blkio.weight": strconv.Itoa(weightBefore),
|
||||||
|
})
|
||||||
|
|
||||||
|
helper.CgroupData.c.BlkioWeight = weightAfter
|
||||||
|
blkio := &BlkioGroup{}
|
||||||
|
if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := getCgroupParamUint(helper.CgroupPath, "blkio.weight")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse blkio.weight - %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value != weightAfter {
|
||||||
|
t.Fatal("Got the wrong value, set blkio.weight failed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBlkioStats(t *testing.T) {
|
func TestBlkioStats(t *testing.T) {
|
||||||
helper := NewCgroupTestUtil("blkio", t)
|
helper := NewCgroupTestUtil("blkio", t)
|
||||||
defer helper.cleanup()
|
defer helper.cleanup()
|
||||||
|
|
|
@ -7,33 +7,44 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
|
"github.com/docker/libcontainer/configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CpuGroup struct {
|
type CpuGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CpuGroup) Set(d *data) error {
|
func (s *CpuGroup) Apply(d *data) error {
|
||||||
// We always want to join the cpu group, to allow fair cpu scheduling
|
// We always want to join the cpu group, to allow fair cpu scheduling
|
||||||
// on a container basis
|
// on a container basis
|
||||||
dir, err := d.join("cpu")
|
dir, err := d.join("cpu")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if d.c.CpuShares != 0 {
|
|
||||||
if err := writeFile(dir, "cpu.shares", strconv.FormatInt(d.c.CpuShares, 10)); err != nil {
|
if err := s.Set(dir, d.c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CpuGroup) Set(path string, cgroup *configs.Cgroup) error {
|
||||||
|
if cgroup.CpuShares != 0 {
|
||||||
|
if err := writeFile(path, "cpu.shares", strconv.FormatInt(cgroup.CpuShares, 10)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if d.c.CpuPeriod != 0 {
|
if cgroup.CpuPeriod != 0 {
|
||||||
if err := writeFile(dir, "cpu.cfs_period_us", strconv.FormatInt(d.c.CpuPeriod, 10)); err != nil {
|
if err := writeFile(path, "cpu.cfs_period_us", strconv.FormatInt(cgroup.CpuPeriod, 10)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if d.c.CpuQuota != 0 {
|
if cgroup.CpuQuota != 0 {
|
||||||
if err := writeFile(dir, "cpu.cfs_quota_us", strconv.FormatInt(d.c.CpuQuota, 10)); err != nil {
|
if err := writeFile(path, "cpu.cfs_quota_us", strconv.FormatInt(cgroup.CpuQuota, 10)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,81 @@ package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestCpuSetShares(t *testing.T) {
|
||||||
|
helper := NewCgroupTestUtil("cpu", t)
|
||||||
|
defer helper.cleanup()
|
||||||
|
|
||||||
|
const (
|
||||||
|
sharesBefore = 1024
|
||||||
|
sharesAfter = 512
|
||||||
|
)
|
||||||
|
|
||||||
|
helper.writeFileContents(map[string]string{
|
||||||
|
"cpu.shares": strconv.Itoa(sharesBefore),
|
||||||
|
})
|
||||||
|
|
||||||
|
helper.CgroupData.c.CpuShares = sharesAfter
|
||||||
|
cpu := &CpuGroup{}
|
||||||
|
if err := cpu.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := getCgroupParamUint(helper.CgroupPath, "cpu.shares")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse cpu.shares - %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value != sharesAfter {
|
||||||
|
t.Fatal("Got the wrong value, set cpu.shares failed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCpuSetBandWidth(t *testing.T) {
|
||||||
|
helper := NewCgroupTestUtil("cpu", t)
|
||||||
|
defer helper.cleanup()
|
||||||
|
|
||||||
|
const (
|
||||||
|
quotaBefore = 8000
|
||||||
|
quotaAfter = 5000
|
||||||
|
periodBefore = 10000
|
||||||
|
periodAfter = 7000
|
||||||
|
)
|
||||||
|
|
||||||
|
helper.writeFileContents(map[string]string{
|
||||||
|
"cpu.cfs_quota_us": strconv.Itoa(quotaBefore),
|
||||||
|
"cpu.cfs_period_us": strconv.Itoa(periodBefore),
|
||||||
|
})
|
||||||
|
|
||||||
|
helper.CgroupData.c.CpuQuota = quotaAfter
|
||||||
|
helper.CgroupData.c.CpuPeriod = periodAfter
|
||||||
|
cpu := &CpuGroup{}
|
||||||
|
if err := cpu.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quota, err := getCgroupParamUint(helper.CgroupPath, "cpu.cfs_quota_us")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse cpu.cfs_quota_us - %s", err)
|
||||||
|
}
|
||||||
|
if quota != quotaAfter {
|
||||||
|
t.Fatal("Got the wrong value, set cpu.cfs_quota_us failed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
period, err := getCgroupParamUint(helper.CgroupPath, "cpu.cfs_period_us")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse cpu.cfs_period_us - %s", err)
|
||||||
|
}
|
||||||
|
if period != periodAfter {
|
||||||
|
t.Fatal("Got the wrong value, set cpu.cfs_period_us failed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCpuStats(t *testing.T) {
|
func TestCpuStats(t *testing.T) {
|
||||||
helper := NewCgroupTestUtil("cpu", t)
|
helper := NewCgroupTestUtil("cpu", t)
|
||||||
defer helper.cleanup()
|
defer helper.cleanup()
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
|
"github.com/docker/libcontainer/configs"
|
||||||
"github.com/docker/libcontainer/system"
|
"github.com/docker/libcontainer/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ var clockTicks = uint64(system.GetClockTicks())
|
||||||
type CpuacctGroup struct {
|
type CpuacctGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CpuacctGroup) Set(d *data) error {
|
func (s *CpuacctGroup) Apply(d *data) error {
|
||||||
// we just want to join this group even though we don't set anything
|
// we just want to join this group even though we don't set anything
|
||||||
if _, err := d.join("cpuacct"); err != nil && !cgroups.IsNotFound(err) {
|
if _, err := d.join("cpuacct"); err != nil && !cgroups.IsNotFound(err) {
|
||||||
return err
|
return err
|
||||||
|
@ -30,6 +31,10 @@ func (s *CpuacctGroup) Set(d *data) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *CpuacctGroup) Set(path string, cgroup *configs.Cgroup) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *CpuacctGroup) Remove(d *data) error {
|
func (s *CpuacctGroup) Remove(d *data) error {
|
||||||
return removePath(d.path("cpuacct"))
|
return removePath(d.path("cpuacct"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,34 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
|
"github.com/docker/libcontainer/configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CpusetGroup struct {
|
type CpusetGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CpusetGroup) Set(d *data) error {
|
func (s *CpusetGroup) Apply(d *data) error {
|
||||||
dir, err := d.path("cpuset")
|
dir, err := d.path("cpuset")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return s.SetDir(dir, d.c.CpusetCpus, d.c.CpusetMems, d.pid)
|
return s.ApplyDir(dir, d.c, d.pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CpusetGroup) Set(path string, cgroup *configs.Cgroup) error {
|
||||||
|
if cgroup.CpusetCpus != "" {
|
||||||
|
if err := writeFile(path, "cpuset.cpus", cgroup.CpusetCpus); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cgroup.CpusetMems != "" {
|
||||||
|
if err := writeFile(path, "cpuset.mems", cgroup.CpusetMems); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CpusetGroup) Remove(d *data) error {
|
func (s *CpusetGroup) Remove(d *data) error {
|
||||||
|
@ -29,7 +46,7 @@ func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CpusetGroup) SetDir(dir, cpus string, mems string, pid int) error {
|
func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) error {
|
||||||
if err := s.ensureParent(dir); err != nil {
|
if err := s.ensureParent(dir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -40,17 +57,10 @@ func (s *CpusetGroup) SetDir(dir, cpus string, mems string, pid int) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we don't use --cpuset-xxx, the default value inherit from parent cgroup
|
// the default values inherit from parent cgroup are already set in
|
||||||
// is set in s.ensureParent, otherwise, use the value we set
|
// s.ensureParent, cover these if we have our own
|
||||||
if cpus != "" {
|
if err := s.Set(dir, cgroup); err != nil {
|
||||||
if err := writeFile(dir, "cpuset.cpus", cpus); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if mems != "" {
|
|
||||||
if err := writeFile(dir, "cpuset.mems", mems); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCpusetSetCpus(t *testing.T) {
|
||||||
|
helper := NewCgroupTestUtil("cpuset", t)
|
||||||
|
defer helper.cleanup()
|
||||||
|
|
||||||
|
const (
|
||||||
|
cpusBefore = "0"
|
||||||
|
cpusAfter = "1-3"
|
||||||
|
)
|
||||||
|
|
||||||
|
helper.writeFileContents(map[string]string{
|
||||||
|
"cpuset.cpus": cpusBefore,
|
||||||
|
})
|
||||||
|
|
||||||
|
helper.CgroupData.c.CpusetCpus = cpusAfter
|
||||||
|
cpuset := &CpusetGroup{}
|
||||||
|
if err := cpuset.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := getCgroupParamString(helper.CgroupPath, "cpuset.cpus")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse cpuset.cpus - %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value != cpusAfter {
|
||||||
|
t.Fatal("Got the wrong value, set cpuset.cpus failed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCpusetSetMems(t *testing.T) {
|
||||||
|
helper := NewCgroupTestUtil("cpuset", t)
|
||||||
|
defer helper.cleanup()
|
||||||
|
|
||||||
|
const (
|
||||||
|
memsBefore = "0"
|
||||||
|
memsAfter = "1"
|
||||||
|
)
|
||||||
|
|
||||||
|
helper.writeFileContents(map[string]string{
|
||||||
|
"cpuset.mems": memsBefore,
|
||||||
|
})
|
||||||
|
|
||||||
|
helper.CgroupData.c.CpusetMems = memsAfter
|
||||||
|
cpuset := &CpusetGroup{}
|
||||||
|
if err := cpuset.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := getCgroupParamString(helper.CgroupPath, "cpuset.mems")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse cpuset.mems - %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value != memsAfter {
|
||||||
|
t.Fatal("Got the wrong value, set cpuset.mems failed.")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +1,39 @@
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
import "github.com/docker/libcontainer/cgroups"
|
import (
|
||||||
|
"github.com/docker/libcontainer/cgroups"
|
||||||
|
"github.com/docker/libcontainer/configs"
|
||||||
|
)
|
||||||
|
|
||||||
type DevicesGroup struct {
|
type DevicesGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DevicesGroup) Set(d *data) error {
|
func (s *DevicesGroup) Apply(d *data) error {
|
||||||
dir, err := d.join("devices")
|
dir, err := d.join("devices")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !d.c.AllowAllDevices {
|
if err := s.Set(dir, d.c); err != nil {
|
||||||
if err := writeFile(dir, "devices.deny", "a"); err != nil {
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error {
|
||||||
|
if !cgroup.AllowAllDevices {
|
||||||
|
if err := writeFile(path, "devices.deny", "a"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dev := range d.c.AllowedDevices {
|
for _, dev := range cgroup.AllowedDevices {
|
||||||
if err := writeFile(dir, "devices.allow", dev.CgroupString()); err != nil {
|
if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer/configs"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
allowedDevices = []*configs.Device{
|
||||||
|
{
|
||||||
|
Path: "/dev/zero",
|
||||||
|
Type: 'c',
|
||||||
|
Major: 1,
|
||||||
|
Minor: 5,
|
||||||
|
Permissions: "rwm",
|
||||||
|
FileMode: 0666,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
allowedList = "c 1:5 rwm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDevicesSetAllow(t *testing.T) {
|
||||||
|
helper := NewCgroupTestUtil("devices", t)
|
||||||
|
defer helper.cleanup()
|
||||||
|
|
||||||
|
helper.writeFileContents(map[string]string{
|
||||||
|
"device.deny": "a",
|
||||||
|
})
|
||||||
|
|
||||||
|
helper.CgroupData.c.AllowAllDevices = false
|
||||||
|
helper.CgroupData.c.AllowedDevices = allowedDevices
|
||||||
|
devices := &DevicesGroup{}
|
||||||
|
if err := devices.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: this doesn't make sence, the file devices.allow under real cgroupfs
|
||||||
|
// is not allowed to read. Our test path don't have cgroupfs mounted.
|
||||||
|
value, err := getCgroupParamString(helper.CgroupPath, "devices.allow")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse devices.allow - %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value != allowedList {
|
||||||
|
t.Fatal("Got the wrong value, set devices.allow failed.")
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import (
|
||||||
type FreezerGroup struct {
|
type FreezerGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FreezerGroup) Set(d *data) error {
|
func (s *FreezerGroup) Apply(d *data) error {
|
||||||
switch d.c.Freezer {
|
switch d.c.Freezer {
|
||||||
case configs.Frozen, configs.Thawed:
|
case configs.Frozen, configs.Thawed:
|
||||||
dir, err := d.path("freezer")
|
dir, err := d.path("freezer")
|
||||||
|
@ -19,20 +19,9 @@ func (s *FreezerGroup) Set(d *data) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeFile(dir, "freezer.state", string(d.c.Freezer)); err != nil {
|
if err := s.Set(dir, d.c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
|
||||||
state, err := readFile(dir, "freezer.state")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(state) == string(d.c.Freezer) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
if _, err := d.join("freezer"); err != nil && !cgroups.IsNotFound(err) {
|
if _, err := d.join("freezer"); err != nil && !cgroups.IsNotFound(err) {
|
||||||
return err
|
return err
|
||||||
|
@ -42,6 +31,28 @@ func (s *FreezerGroup) Set(d *data) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error {
|
||||||
|
switch cgroup.Freezer {
|
||||||
|
case configs.Frozen, configs.Thawed:
|
||||||
|
if err := writeFile(path, "freezer.state", string(cgroup.Freezer)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
state, err := readFile(path, "freezer.state")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(state) == string(cgroup.Freezer) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FreezerGroup) Remove(d *data) error {
|
func (s *FreezerGroup) Remove(d *data) error {
|
||||||
return removePath(d.path("freezer"))
|
return removePath(d.path("freezer"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,13 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
|
"github.com/docker/libcontainer/configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MemoryGroup struct {
|
type MemoryGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryGroup) Set(d *data) error {
|
func (s *MemoryGroup) Apply(d *data) error {
|
||||||
dir, err := d.join("memory")
|
dir, err := d.join("memory")
|
||||||
// only return an error for memory if it was specified
|
// only return an error for memory if it was specified
|
||||||
if err != nil && (d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0) {
|
if err != nil && (d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0) {
|
||||||
|
@ -25,31 +26,36 @@ func (s *MemoryGroup) Set(d *data) error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Only set values if some config was specified.
|
if err := s.Set(dir, d.c); err != nil {
|
||||||
if d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0 {
|
return err
|
||||||
if d.c.Memory != 0 {
|
}
|
||||||
if err := writeFile(dir, "memory.limit_in_bytes", strconv.FormatInt(d.c.Memory, 10)); err != nil {
|
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if d.c.MemoryReservation != 0 {
|
func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error {
|
||||||
if err := writeFile(dir, "memory.soft_limit_in_bytes", strconv.FormatInt(d.c.MemoryReservation, 10)); err != nil {
|
if cgroup.Memory != 0 {
|
||||||
return err
|
if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Memory, 10)); err != nil {
|
||||||
}
|
return err
|
||||||
}
|
|
||||||
// By default, MemorySwap is set to twice the size of RAM.
|
|
||||||
// If you want to omit MemorySwap, set it to '-1'.
|
|
||||||
if d.c.MemorySwap == 0 {
|
|
||||||
if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(d.c.Memory*2, 10)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if d.c.MemorySwap > 0 {
|
|
||||||
if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(d.c.MemorySwap, 10)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if cgroup.MemoryReservation != 0 {
|
||||||
|
if err := writeFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(cgroup.MemoryReservation, 10)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// By default, MemorySwap is set to twice the size of Memory.
|
||||||
|
if cgroup.MemorySwap == 0 && cgroup.Memory != 0 {
|
||||||
|
if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Memory*2, 10)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cgroup.MemorySwap > 0 {
|
||||||
|
if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.MemorySwap, 10)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
|
@ -14,6 +15,46 @@ rss 1024`
|
||||||
memoryFailcnt = "100\n"
|
memoryFailcnt = "100\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMemorySetMemory(t *testing.T) {
|
||||||
|
helper := NewCgroupTestUtil("memory", t)
|
||||||
|
defer helper.cleanup()
|
||||||
|
|
||||||
|
const (
|
||||||
|
memoryBefore = 314572800 // 300M
|
||||||
|
memoryAfter = 524288000 // 500M
|
||||||
|
reservationBefore = 209715200 // 200M
|
||||||
|
reservationAfter = 314572800 // 300M
|
||||||
|
)
|
||||||
|
|
||||||
|
helper.writeFileContents(map[string]string{
|
||||||
|
"memory.limit_in_bytes": strconv.Itoa(memoryBefore),
|
||||||
|
"memory.soft_limit_in_bytes": strconv.Itoa(reservationBefore),
|
||||||
|
})
|
||||||
|
|
||||||
|
helper.CgroupData.c.Memory = memoryAfter
|
||||||
|
helper.CgroupData.c.MemoryReservation = reservationAfter
|
||||||
|
memory := &MemoryGroup{}
|
||||||
|
if err := memory.Set(helper.CgroupPath, helper.CgroupData.c); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := getCgroupParamUint(helper.CgroupPath, "memory.limit_in_bytes")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse memory.limit_in_bytes - %s", err)
|
||||||
|
}
|
||||||
|
if value != memoryAfter {
|
||||||
|
t.Fatal("Got the wrong value, set memory.limit_in_bytes failed.")
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err = getCgroupParamUint(helper.CgroupPath, "memory.soft_limit_in_bytes")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse memory.soft_limit_in_bytes - %s", err)
|
||||||
|
}
|
||||||
|
if value != reservationAfter {
|
||||||
|
t.Fatal("Got the wrong value, set memory.soft_limit_in_bytes failed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMemoryStats(t *testing.T) {
|
func TestMemoryStats(t *testing.T) {
|
||||||
helper := NewCgroupTestUtil("memory", t)
|
helper := NewCgroupTestUtil("memory", t)
|
||||||
defer helper.cleanup()
|
defer helper.cleanup()
|
||||||
|
|
|
@ -2,12 +2,13 @@ package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
|
"github.com/docker/libcontainer/configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PerfEventGroup struct {
|
type PerfEventGroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PerfEventGroup) Set(d *data) error {
|
func (s *PerfEventGroup) Apply(d *data) error {
|
||||||
// we just want to join this group even though we don't set anything
|
// we just want to join this group even though we don't set anything
|
||||||
if _, err := d.join("perf_event"); err != nil && !cgroups.IsNotFound(err) {
|
if _, err := d.join("perf_event"); err != nil && !cgroups.IsNotFound(err) {
|
||||||
return err
|
return err
|
||||||
|
@ -15,6 +16,10 @@ func (s *PerfEventGroup) Set(d *data) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PerfEventGroup) Set(path string, cgroup *configs.Cgroup) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PerfEventGroup) Remove(d *data) error {
|
func (s *PerfEventGroup) Remove(d *data) error {
|
||||||
return removePath(d.path("perf_event"))
|
return removePath(d.path("perf_event"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer/configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cgroupTestUtil struct {
|
type cgroupTestUtil struct {
|
||||||
|
@ -26,7 +28,9 @@ type cgroupTestUtil struct {
|
||||||
|
|
||||||
// Creates a new test util for the specified subsystem
|
// Creates a new test util for the specified subsystem
|
||||||
func NewCgroupTestUtil(subsystem string, t *testing.T) *cgroupTestUtil {
|
func NewCgroupTestUtil(subsystem string, t *testing.T) *cgroupTestUtil {
|
||||||
d := &data{}
|
d := &data{
|
||||||
|
c: &configs.Cgroup{},
|
||||||
|
}
|
||||||
tempDir, err := ioutil.TempDir("", fmt.Sprintf("%s_cgroup_test", subsystem))
|
tempDir, err := ioutil.TempDir("", fmt.Sprintf("%s_cgroup_test", subsystem))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -60,3 +60,13 @@ func getCgroupParamUint(cgroupPath, cgroupFile string) (uint64, error) {
|
||||||
|
|
||||||
return parseUint(strings.TrimSpace(string(contents)), 10, 64)
|
return parseUint(strings.TrimSpace(string(contents)), 10, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gets a string value from the specified cgroup file
|
||||||
|
func getCgroupParamString(cgroupPath, cgroupFile string) (string, error) {
|
||||||
|
contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSpace(string(contents)), nil
|
||||||
|
}
|
||||||
|
|
|
@ -38,6 +38,10 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) {
|
||||||
return nil, fmt.Errorf("Systemd not supported")
|
return nil, fmt.Errorf("Systemd not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Set(container *configs.Config) error {
|
||||||
|
return nil, fmt.Errorf("Systemd not supported")
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) Freeze(state configs.FreezerState) error {
|
func (m *Manager) Freeze(state configs.FreezerState) error {
|
||||||
return fmt.Errorf("Systemd not supported")
|
return fmt.Errorf("Systemd not supported")
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,10 @@ type Manager struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type subsystem interface {
|
type subsystem interface {
|
||||||
GetStats(string, *cgroups.Stats) error
|
// Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
|
||||||
|
GetStats(path string, stats *cgroups.Stats) error
|
||||||
|
// Set the cgroup represented by cgroup.
|
||||||
|
Set(path string, cgroup *configs.Cgroup) error
|
||||||
}
|
}
|
||||||
|
|
||||||
var subsystems = map[string]subsystem{
|
var subsystems = map[string]subsystem{
|
||||||
|
@ -323,6 +326,10 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) {
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Set(container *configs.Config) error {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func getUnitName(c *configs.Cgroup) string {
|
func getUnitName(c *configs.Cgroup) string {
|
||||||
return fmt.Sprintf("%s-%s.scope", c.Parent, c.Name)
|
return fmt.Sprintf("%s-%s.scope", c.Parent, c.Name)
|
||||||
}
|
}
|
||||||
|
@ -398,5 +405,5 @@ func joinCpuset(c *configs.Cgroup, pid int) error {
|
||||||
|
|
||||||
s := &fs.CpusetGroup{}
|
s := &fs.CpusetGroup{}
|
||||||
|
|
||||||
return s.SetDir(path, c.CpusetCpus, c.CpusetMems, pid)
|
return s.ApplyDir(path, c, pid)
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,14 @@ type Container interface {
|
||||||
// Systemerror - System error.
|
// Systemerror - System error.
|
||||||
Stats() (*Stats, error)
|
Stats() (*Stats, error)
|
||||||
|
|
||||||
|
// Set cgroup resources of container as configured
|
||||||
|
//
|
||||||
|
// We can use this to change resources when containers are running.
|
||||||
|
//
|
||||||
|
// errors:
|
||||||
|
// Systemerror - System error.
|
||||||
|
Set() error
|
||||||
|
|
||||||
// Start a process inside the container. Returns error if process fails to
|
// Start a process inside the container. Returns error if process fails to
|
||||||
// start. You can track process lifecycle with passed Process structure.
|
// start. You can track process lifecycle with passed Process structure.
|
||||||
//
|
//
|
||||||
|
|
|
@ -78,6 +78,12 @@ func (c *linuxContainer) Stats() (*Stats, error) {
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *linuxContainer) Set() error {
|
||||||
|
c.m.Lock()
|
||||||
|
defer c.m.Unlock()
|
||||||
|
return c.cgroupManager.Set(c.config)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *linuxContainer) Start(process *Process) error {
|
func (c *linuxContainer) Start(process *Process) error {
|
||||||
c.m.Lock()
|
c.m.Lock()
|
||||||
defer c.m.Unlock()
|
defer c.m.Unlock()
|
||||||
|
|
|
@ -29,6 +29,10 @@ func (m *mockCgroupManager) Apply(pid int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockCgroupManager) Set(container *configs.Config) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockCgroupManager) Destroy() error {
|
func (m *mockCgroupManager) Destroy() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue