From 2616e87cad0307793dc60d5964363f765bc57cd9 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 30 May 2014 15:09:07 -0700 Subject: [PATCH] Implement systemd support for freezer These PR does a few things. It ensures that the freezer cgroup is joined in the systemd driver. It also provides a public api for setting the freezer state via the cgroups package. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- libcontainer/cgroups/cgroups.go | 13 ++- libcontainer/cgroups/fs/apply_raw.go | 103 ++++++++---------- libcontainer/cgroups/fs/freezer.go | 5 +- .../cgroups/systemd/apply_nosystemd.go | 4 + libcontainer/cgroups/systemd/apply_systemd.go | 61 ++++++++++- 5 files changed, 118 insertions(+), 68 deletions(-) diff --git a/libcontainer/cgroups/cgroups.go b/libcontainer/cgroups/cgroups.go index 905d0ca..85607b5 100644 --- a/libcontainer/cgroups/cgroups.go +++ b/libcontainer/cgroups/cgroups.go @@ -10,6 +10,14 @@ var ( ErrNotFound = errors.New("mountpoint not found") ) +type FreezerState string + +const ( + Undefined FreezerState = "" + Frozen FreezerState = "FROZEN" + Thawed FreezerState = "THAWED" +) + type Cgroup struct { Name string `json:"name,omitempty"` Parent string `json:"parent,omitempty"` // name of parent cgroup or slice @@ -23,9 +31,8 @@ type Cgroup struct { CpuQuota int64 `json:"cpu_quota,omitempty"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period. CpuPeriod int64 `json:"cpu_period,omitempty"` // CPU period to be used for hardcapping (in usecs). 0 to use system default. CpusetCpus string `json:"cpuset_cpus,omitempty"` // CPU to use - Freezer string `json:"freezer,omitempty"` // set the freeze value for the process - - Slice string `json:"slice,omitempty"` // Parent slice to use for systemd + Freezer FreezerState `json:"freezer,omitempty"` // set the freeze value for the process + Slice string `json:"slice,omitempty"` // Parent slice to use for systemd } type ActiveCgroup interface { diff --git a/libcontainer/cgroups/fs/apply_raw.go b/libcontainer/cgroups/fs/apply_raw.go index 291d1e8..8fa2599 100644 --- a/libcontainer/cgroups/fs/apply_raw.go +++ b/libcontainer/cgroups/fs/apply_raw.go @@ -37,65 +37,28 @@ type data struct { } func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { - // We have two implementation of cgroups support, one is based on - // systemd and the dbus api, and one is based on raw cgroup fs operations - // following the pre-single-writer model docs at: - // http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/ - // - // we can pick any subsystem to find the root - - cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu") + d, err := getCgroupData(c, pid) if err != nil { return nil, err } - cgroupRoot = filepath.Dir(cgroupRoot) - if _, err := os.Stat(cgroupRoot); err != nil { - return nil, fmt.Errorf("cgroups fs not found") - } - - cgroup := c.Name - if c.Parent != "" { - cgroup = filepath.Join(c.Parent, cgroup) - } - - d := &data{ - root: cgroupRoot, - cgroup: cgroup, - c: c, - pid: pid, - } for _, sys := range subsystems { if err := sys.Set(d); err != nil { d.Cleanup() return nil, err } } + return d, nil } func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) { stats := cgroups.NewStats() - cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu") + + d, err := getCgroupData(c, 0) if err != nil { return nil, err } - cgroupRoot = filepath.Dir(cgroupRoot) - - if _, err := os.Stat(cgroupRoot); err != nil { - return nil, fmt.Errorf("cgroups fs not found") - } - - cgroup := c.Name - if c.Parent != "" { - cgroup = filepath.Join(c.Parent, cgroup) - } - - d := &data{ - root: cgroupRoot, - cgroup: cgroup, - c: c, - } for _, sys := range subsystems { if err := sys.GetStats(d, stats); err != nil { @@ -106,27 +69,26 @@ func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) { return stats, nil } +// Freeze toggles the container's freezer cgroup depending on the state +// provided +func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error { + d, err := getCgroupData(c, 0) + if err != nil { + return err + } + + c.Freezer = state + + freezer := subsystems["freezer"] + + return freezer.Set(d) +} + func GetPids(c *cgroups.Cgroup) ([]int, error) { - cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu") + d, err := getCgroupData(c, 0) if err != nil { return nil, err } - cgroupRoot = filepath.Dir(cgroupRoot) - - if _, err := os.Stat(cgroupRoot); err != nil { - return nil, fmt.Errorf("cgroup root %s not found", cgroupRoot) - } - - cgroup := c.Name - if c.Parent != "" { - cgroup = filepath.Join(c.Parent, cgroup) - } - - d := &data{ - root: cgroupRoot, - cgroup: cgroup, - c: c, - } dir, err := d.path("devices") if err != nil { @@ -136,6 +98,31 @@ func GetPids(c *cgroups.Cgroup) ([]int, error) { return cgroups.ReadProcsFile(dir) } +func getCgroupData(c *cgroups.Cgroup, pid int) (*data, error) { + // we can pick any subsystem to find the root + cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu") + if err != nil { + return nil, err + } + cgroupRoot = filepath.Dir(cgroupRoot) + + if _, err := os.Stat(cgroupRoot); err != nil { + return nil, fmt.Errorf("cgroups fs not found") + } + + cgroup := c.Name + if c.Parent != "" { + cgroup = filepath.Join(c.Parent, cgroup) + } + + return &data{ + root: cgroupRoot, + cgroup: cgroup, + c: c, + pid: pid, + }, nil +} + func (raw *data) parent(subsystem string) (string, error) { initPath, err := cgroups.GetInitCgroupDir(subsystem) if err != nil { diff --git a/libcontainer/cgroups/fs/freezer.go b/libcontainer/cgroups/fs/freezer.go index a9a27ef..ed57f8b 100644 --- a/libcontainer/cgroups/fs/freezer.go +++ b/libcontainer/cgroups/fs/freezer.go @@ -20,11 +20,12 @@ func (s *freezerGroup) Set(d *data) error { return nil } - if d.c.Freezer != "" { - if err := writeFile(dir, "freezer.state", d.c.Freezer); err != nil { + if d.c.Freezer != cgroups.Undefined { + if err := writeFile(dir, "freezer.state", string(d.c.Freezer)); err != nil { return err } } + return nil } diff --git a/libcontainer/cgroups/systemd/apply_nosystemd.go b/libcontainer/cgroups/systemd/apply_nosystemd.go index 0fff3e4..c72bb11 100644 --- a/libcontainer/cgroups/systemd/apply_nosystemd.go +++ b/libcontainer/cgroups/systemd/apply_nosystemd.go @@ -19,3 +19,7 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { func GetPids(c *cgroups.Cgroup) ([]int, error) { return nil, fmt.Errorf("Systemd not supported") } + +func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error { + return fmt.Errorf("Systemd not supported") +} diff --git a/libcontainer/cgroups/systemd/apply_systemd.go b/libcontainer/cgroups/systemd/apply_systemd.go index 622ad44..eb1425b 100644 --- a/libcontainer/cgroups/systemd/apply_systemd.go +++ b/libcontainer/cgroups/systemd/apply_systemd.go @@ -218,6 +218,14 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { } } + // we need to manually join the freezer cgroup in systemd because it does not currently support it + // via the dbus api + freezerPath, err := joinFreezer(c, pid) + if err != nil { + return nil, err + } + res.cleanupDirs = append(res.cleanupDirs, freezerPath) + if len(cpusetArgs) != 0 { // systemd does not atm set up the cpuset controller, so we must manually // join it. Additionally that is a very finicky controller where each @@ -227,14 +235,19 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { if err != nil { return nil, err } + initPath, err := cgroups.GetInitCgroupDir("cpuset") if err != nil { return nil, err } - rootPath := filepath.Join(mountpoint, initPath) + var ( + foundCpus bool + foundMems bool - path := filepath.Join(mountpoint, initPath, c.Parent+"-"+c.Name) + rootPath = filepath.Join(mountpoint, initPath) + path = filepath.Join(mountpoint, initPath, c.Parent+"-"+c.Name) + ) res.cleanupDirs = append(res.cleanupDirs, path) @@ -242,9 +255,6 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { return nil, err } - foundCpus := false - foundMems := false - for _, arg := range cpusetArgs { if arg.File == "cpuset.cpus" { foundCpus = true @@ -303,6 +313,47 @@ func (c *systemdCgroup) Cleanup() error { return nil } +func joinFreezer(c *cgroups.Cgroup, pid int) (string, error) { + path, err := getFreezerPath(c) + if err != nil { + return "", err + } + + if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { + return "", err + } + + if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil { + return "", err + } + + return path, nil +} + +func getFreezerPath(c *cgroups.Cgroup) (string, error) { + mountpoint, err := cgroups.FindCgroupMountpoint("freezer") + if err != nil { + return "", err + } + + initPath, err := cgroups.GetInitCgroupDir("freezer") + if err != nil { + return "", err + } + + return filepath.Join(mountpoint, initPath, fmt.Sprintf("%s-%s", c.Parent, c.Name)), nil + +} + +func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error { + path, err := getFreezerPath(c) + if err != nil { + return err + } + + return ioutil.WriteFile(filepath.Join(path, "freezer.state"), []byte(state), 0) +} + func GetPids(c *cgroups.Cgroup) ([]int, error) { unitName := getUnitName(c)