|
@@ -21,7 +21,7 @@ import (
|
|
)
|
|
)
|
|
|
|
|
|
type systemdCgroup struct {
|
|
type systemdCgroup struct {
|
|
- cleanupDirs []string
|
|
|
|
|
|
+ cgroup *cgroups.Cgroup
|
|
}
|
|
}
|
|
|
|
|
|
type subsystem interface {
|
|
type subsystem interface {
|
|
@@ -84,39 +84,15 @@ func getIfaceForUnit(unitName string) string {
|
|
return "Unit"
|
|
return "Unit"
|
|
}
|
|
}
|
|
|
|
|
|
-type cgroupArg struct {
|
|
|
|
- File string
|
|
|
|
- Value string
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) {
|
|
func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) {
|
|
var (
|
|
var (
|
|
unitName = getUnitName(c)
|
|
unitName = getUnitName(c)
|
|
slice = "system.slice"
|
|
slice = "system.slice"
|
|
properties []systemd1.Property
|
|
properties []systemd1.Property
|
|
- cpuArgs []cgroupArg
|
|
|
|
- cpusetArgs []cgroupArg
|
|
|
|
- memoryArgs []cgroupArg
|
|
|
|
- res systemdCgroup
|
|
|
|
|
|
+ res = &systemdCgroup{}
|
|
)
|
|
)
|
|
|
|
|
|
- // First set up things not supported by systemd
|
|
|
|
-
|
|
|
|
- // -1 disables memorySwap
|
|
|
|
- if c.MemorySwap >= 0 && (c.Memory != 0 || c.MemorySwap > 0) {
|
|
|
|
- memorySwap := c.MemorySwap
|
|
|
|
-
|
|
|
|
- if memorySwap == 0 {
|
|
|
|
- // By default, MemorySwap is set to twice the size of RAM.
|
|
|
|
- memorySwap = c.Memory * 2
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- memoryArgs = append(memoryArgs, cgroupArg{"memory.memsw.limit_in_bytes", strconv.FormatInt(memorySwap, 10)})
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if c.CpusetCpus != "" {
|
|
|
|
- cpusetArgs = append(cpusetArgs, cgroupArg{"cpuset.cpus", c.CpusetCpus})
|
|
|
|
- }
|
|
|
|
|
|
+ res.cgroup = c
|
|
|
|
|
|
if c.Slice != "" {
|
|
if c.Slice != "" {
|
|
slice = c.Slice
|
|
slice = c.Slice
|
|
@@ -150,201 +126,84 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
|
|
- // To work around the lack of /dev/pts/* support above we need to manually add these
|
|
|
|
- // so, ask systemd for the cgroup used
|
|
|
|
- props, err := theConn.GetUnitTypeProperties(unitName, getIfaceForUnit(unitName))
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- cgroup := props["ControlGroup"].(string)
|
|
|
|
-
|
|
|
|
if !c.AllowAllDevices {
|
|
if !c.AllowAllDevices {
|
|
- // Atm we can't use the systemd device support because of two missing things:
|
|
|
|
- // * Support for wildcards to allow mknod on any device
|
|
|
|
- // * Support for wildcards to allow /dev/pts support
|
|
|
|
- //
|
|
|
|
- // The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is
|
|
|
|
- // in wide use. When both these are availalable we will be able to switch, but need to keep the old
|
|
|
|
- // implementation for backwards compat.
|
|
|
|
- //
|
|
|
|
- // Note: we can't use systemd to set up the initial limits, and then change the cgroup
|
|
|
|
- // because systemd will re-write the device settings if it needs to re-apply the cgroup context.
|
|
|
|
- // This happens at least for v208 when any sibling unit is started.
|
|
|
|
-
|
|
|
|
- mountpoint, err := cgroups.FindCgroupMountpoint("devices")
|
|
|
|
- if err != nil {
|
|
|
|
|
|
+ if err := joinDevices(c, pid); err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
-
|
|
|
|
- initPath, err := cgroups.GetInitCgroupDir("devices")
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- dir := filepath.Join(mountpoint, initPath, c.Parent, c.Name)
|
|
|
|
-
|
|
|
|
- res.cleanupDirs = append(res.cleanupDirs, dir)
|
|
|
|
-
|
|
|
|
- if err := os.MkdirAll(dir, 0755); err != nil && !os.IsExist(err) {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if err := ioutil.WriteFile(filepath.Join(dir, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if err := writeFile(dir, "devices.deny", "a"); err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- for _, dev := range c.AllowedDevices {
|
|
|
|
- if err := writeFile(dir, "devices.allow", dev.GetCgroupAllowString()); err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- if len(cpuArgs) != 0 {
|
|
|
|
- mountpoint, err := cgroups.FindCgroupMountpoint("cpu")
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- path := filepath.Join(mountpoint, cgroup)
|
|
|
|
-
|
|
|
|
- for _, arg := range cpuArgs {
|
|
|
|
- if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if len(memoryArgs) != 0 {
|
|
|
|
- mountpoint, err := cgroups.FindCgroupMountpoint("memory")
|
|
|
|
- if err != nil {
|
|
|
|
|
|
+ // -1 disables memorySwap
|
|
|
|
+ if c.MemorySwap >= 0 && (c.Memory != 0 || c.MemorySwap > 0) {
|
|
|
|
+ if err := joinMemory(c, pid); err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
|
|
- path := filepath.Join(mountpoint, cgroup)
|
|
|
|
-
|
|
|
|
- for _, arg := range memoryArgs {
|
|
|
|
- if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
}
|
|
}
|
|
|
|
|
|
// we need to manually join the freezer cgroup in systemd because it does not currently support it
|
|
// we need to manually join the freezer cgroup in systemd because it does not currently support it
|
|
// via the dbus api
|
|
// via the dbus api
|
|
- freezerPath, err := joinFreezer(c, pid)
|
|
|
|
- if err != nil {
|
|
|
|
|
|
+ if err := joinFreezer(c, pid); err != nil {
|
|
return nil, err
|
|
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
|
|
|
|
- // level must have a full setup as the default for a new directory is "no cpus",
|
|
|
|
- // so we avoid using any hierarchies here, creating a toplevel directory.
|
|
|
|
- mountpoint, err := cgroups.FindCgroupMountpoint("cpuset")
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- initPath, err := cgroups.GetInitCgroupDir("cpuset")
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- var (
|
|
|
|
- foundCpus bool
|
|
|
|
- foundMems bool
|
|
|
|
-
|
|
|
|
- rootPath = filepath.Join(mountpoint, initPath)
|
|
|
|
- path = filepath.Join(mountpoint, initPath, c.Parent+"-"+c.Name)
|
|
|
|
- )
|
|
|
|
-
|
|
|
|
- res.cleanupDirs = append(res.cleanupDirs, path)
|
|
|
|
-
|
|
|
|
- if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
|
|
|
|
|
|
+ if c.CpusetCpus != "" {
|
|
|
|
+ if err := joinCpuset(c, pid); err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- for _, arg := range cpusetArgs {
|
|
|
|
- if arg.File == "cpuset.cpus" {
|
|
|
|
- foundCpus = true
|
|
|
|
- }
|
|
|
|
- if arg.File == "cpuset.mems" {
|
|
|
|
- foundMems = true
|
|
|
|
- }
|
|
|
|
- if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // These are required, if not specified inherit from parent
|
|
|
|
- if !foundCpus {
|
|
|
|
- s, err := ioutil.ReadFile(filepath.Join(rootPath, "cpuset.cpus"))
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
|
|
+ return res, nil
|
|
|
|
+}
|
|
|
|
|
|
- if err := ioutil.WriteFile(filepath.Join(path, "cpuset.cpus"), s, 0700); err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+func writeFile(dir, file, data string) error {
|
|
|
|
+ return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700)
|
|
|
|
+}
|
|
|
|
|
|
- // These are required, if not specified inherit from parent
|
|
|
|
- if !foundMems {
|
|
|
|
- s, err := ioutil.ReadFile(filepath.Join(rootPath, "cpuset.mems"))
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
|
|
+func (c *systemdCgroup) Paths() (map[string]string, error) {
|
|
|
|
+ paths := make(map[string]string)
|
|
|
|
|
|
- if err := ioutil.WriteFile(filepath.Join(path, "cpuset.mems"), s, 0700); err != nil {
|
|
|
|
- return nil, err
|
|
|
|
|
|
+ for sysname := range subsystems {
|
|
|
|
+ subsystemPath, err := getSubsystemPath(c.cgroup, sysname)
|
|
|
|
+ if err != nil {
|
|
|
|
+ // Don't fail if a cgroup hierarchy was not found, just skip this subsystem
|
|
|
|
+ if err == cgroups.ErrNotFound {
|
|
|
|
+ continue
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil {
|
|
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- return &res, nil
|
|
|
|
-}
|
|
|
|
|
|
+ paths[sysname] = subsystemPath
|
|
|
|
+ }
|
|
|
|
|
|
-func writeFile(dir, file, data string) error {
|
|
|
|
- return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700)
|
|
|
|
|
|
+ return paths, nil
|
|
}
|
|
}
|
|
|
|
|
|
func (c *systemdCgroup) Cleanup() error {
|
|
func (c *systemdCgroup) Cleanup() error {
|
|
// systemd cleans up, we don't need to do much
|
|
// systemd cleans up, we don't need to do much
|
|
|
|
+ paths, err := c.Paths()
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
|
|
- for _, path := range c.cleanupDirs {
|
|
|
|
|
|
+ for _, path := range paths {
|
|
os.RemoveAll(path)
|
|
os.RemoveAll(path)
|
|
}
|
|
}
|
|
|
|
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
-func joinFreezer(c *cgroups.Cgroup, pid int) (string, error) {
|
|
|
|
|
|
+func joinFreezer(c *cgroups.Cgroup, pid int) error {
|
|
path, err := getSubsystemPath(c, "freezer")
|
|
path, err := getSubsystemPath(c, "freezer")
|
|
if err != nil {
|
|
if err != nil {
|
|
- return "", err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(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 err
|
|
}
|
|
}
|
|
|
|
|
|
- return path, nil
|
|
|
|
|
|
+ return ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700)
|
|
}
|
|
}
|
|
|
|
|
|
func getSubsystemPath(c *cgroups.Cgroup, subsystem string) (string, error) {
|
|
func getSubsystemPath(c *cgroups.Cgroup, subsystem string) (string, error) {
|
|
@@ -389,20 +248,12 @@ func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error {
|
|
}
|
|
}
|
|
|
|
|
|
func GetPids(c *cgroups.Cgroup) ([]int, error) {
|
|
func GetPids(c *cgroups.Cgroup) ([]int, error) {
|
|
- unitName := getUnitName(c)
|
|
|
|
-
|
|
|
|
- mountpoint, err := cgroups.FindCgroupMountpoint("cpu")
|
|
|
|
|
|
+ path, err := getSubsystemPath(c, "cpu")
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
|
|
- props, err := theConn.GetUnitTypeProperties(unitName, getIfaceForUnit(unitName))
|
|
|
|
- if err != nil {
|
|
|
|
- return nil, err
|
|
|
|
- }
|
|
|
|
- cgroup := props["ControlGroup"].(string)
|
|
|
|
-
|
|
|
|
- return cgroups.ReadProcsFile(filepath.Join(mountpoint, cgroup))
|
|
|
|
|
|
+ return cgroups.ReadProcsFile(path)
|
|
}
|
|
}
|
|
|
|
|
|
func getUnitName(c *cgroups.Cgroup) string {
|
|
func getUnitName(c *cgroups.Cgroup) string {
|
|
@@ -437,3 +288,71 @@ func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) {
|
|
|
|
|
|
return stats, nil
|
|
return stats, nil
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+// Atm we can't use the systemd device support because of two missing things:
|
|
|
|
+// * Support for wildcards to allow mknod on any device
|
|
|
|
+// * Support for wildcards to allow /dev/pts support
|
|
|
|
+//
|
|
|
|
+// The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is
|
|
|
|
+// in wide use. When both these are availalable we will be able to switch, but need to keep the old
|
|
|
|
+// implementation for backwards compat.
|
|
|
|
+//
|
|
|
|
+// Note: we can't use systemd to set up the initial limits, and then change the cgroup
|
|
|
|
+// because systemd will re-write the device settings if it needs to re-apply the cgroup context.
|
|
|
|
+// This happens at least for v208 when any sibling unit is started.
|
|
|
|
+func joinDevices(c *cgroups.Cgroup, pid int) error {
|
|
|
|
+ path, err := getSubsystemPath(c, "devices")
|
|
|
|
+ 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
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if err := writeFile(path, "devices.deny", "a"); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, dev := range c.AllowedDevices {
|
|
|
|
+ if err := writeFile(path, "devices.allow", dev.GetCgroupAllowString()); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func joinMemory(c *cgroups.Cgroup, pid int) error {
|
|
|
|
+ memorySwap := c.MemorySwap
|
|
|
|
+
|
|
|
|
+ if memorySwap == 0 {
|
|
|
|
+ // By default, MemorySwap is set to twice the size of RAM.
|
|
|
|
+ memorySwap = c.Memory * 2
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ path, err := getSubsystemPath(c, "memory")
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return ioutil.WriteFile(filepath.Join(path, "memory.memsw.limit_in_bytes"), []byte(strconv.FormatInt(memorySwap, 10)), 0700)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// systemd does not atm set up the cpuset controller, so we must manually
|
|
|
|
+// join it. Additionally that is a very finicky controller where each
|
|
|
|
+// level must have a full setup as the default for a new directory is "no cpus"
|
|
|
|
+func joinCpuset(c *cgroups.Cgroup, pid int) error {
|
|
|
|
+ path, err := getSubsystemPath(c, "cpuset")
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ s := &fs.CpusetGroup{}
|
|
|
|
+
|
|
|
|
+ return s.SetDir(path, c.CpusetCpus, pid)
|
|
|
|
+}
|