diff --git a/libcontainer/cgroups/fs/apply_raw.go b/libcontainer/cgroups/fs/apply_raw.go index f3ecd4b1226..b69486ac18b 100644 --- a/libcontainer/cgroups/fs/apply_raw.go +++ b/libcontainer/cgroups/fs/apply_raw.go @@ -29,6 +29,7 @@ var ( &NetPrioGroup{}, &PerfEventGroup{}, &FreezerGroup{}, + &PidsGroup{}, } CgroupProcesses = "cgroup.procs" HugePageSizes, _ = cgroups.GetHugePageSize() diff --git a/libcontainer/cgroups/fs/pids.go b/libcontainer/cgroups/fs/pids.go new file mode 100644 index 00000000000..0c6bb98f6b4 --- /dev/null +++ b/libcontainer/cgroups/fs/pids.go @@ -0,0 +1,86 @@ +// +build linux + +package fs + +import ( + "fmt" + "github.com/opencontainers/runc/libcontainer/cgroups" + "github.com/opencontainers/runc/libcontainer/configs" + "strconv" +) + +type PidsGroup struct { +} + +func (s *PidsGroup) Name() string { + return "pids" +} + +func (s *PidsGroup) Apply(d *data) error { + dir, err := d.join("pids") + if err != nil { + // since Linux 4.3 + if cgroups.IsNotFound(err) { + return nil + } + return err + } + + if err := s.Set(dir, d.c); err != nil { + return err + } + + return nil +} + +func (s *PidsGroup) Set(path string, cgroup *configs.Cgroup) error { + + if cgroup.PidsMax > 0 { + if err := writeFile(path, "pids.max", fmt.Sprint(cgroup.PidsMax)); err != nil { + return err + } + } else if cgroup.PidsMax < 0 { + if err := writeFile(path, "pids.max", "max"); err != nil { + return err + } + } + + for pid := range cgroup.Pids { + if err := writeFile(path, "cgroup.procs", strconv.Itoa(pid)); err != nil { + return err + } + } + + return nil +} + +func (s *PidsGroup) Remove(d *data) error { + return removePath(d.path("pids")) +} + +func (s *PidsGroup) GetStats(path string, stats *cgroups.Stats) error { + + values, err := getCgroupParamUintArray(path, "pids.current") + if err != nil { + return fmt.Errorf("failed to parse pids.current - %v", err) + } + + stats.PidsStats.Current = values + + value, err := getCgroupParamString(path, "pids.max") + if err != nil { + return fmt.Errorf("failed to parse pids.max - %v", err) + } + + if "max" == value { + stats.PidsStats.Max = configs.MaxPids + } else { + max, err := strconv.Atoi(value) + if err != nil { + return fmt.Errorf("failed to parse pids.max content %s as int", value) + } + stats.PidsStats.Max = max + } + + return nil +} diff --git a/libcontainer/cgroups/fs/pids_test.go b/libcontainer/cgroups/fs/pids_test.go new file mode 100644 index 00000000000..f5f66b384f2 --- /dev/null +++ b/libcontainer/cgroups/fs/pids_test.go @@ -0,0 +1,55 @@ +// +build linux + +package fs + +import ( + "os" + "strconv" + "testing" + + "github.com/opencontainers/runc/libcontainer/cgroups" + "github.com/opencontainers/runc/libcontainer/configs" +) + +func TestSetPids(t *testing.T) { + helper := NewCgroupTestUtil("pids", t) + defer helper.cleanup() + + pidsArray := make([]uint32, 1) + pidsArray[0] = uint32(os.Getpid()) + + helper.CgroupData.c.Pids = pidsArray + pids := &PidsGroup{} + if err := pids.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } +} + +func TestGetPidsStats(t *testing.T) { + helper := NewCgroupTestUtil("pids", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "pids.current": strconv.Itoa(os.Getpid()), + "pids.max": "max", + }) + + actualStats := *cgroups.NewStats() + pids := &PidsGroup{} + err := pids.GetStats(helper.CgroupPath, &actualStats) + if err != nil { + t.Fatal(err) + } + + if actualStats.PidsStats.Current == nil { + t.Fatal("Expected PidsStats to be set") + } + + if len(actualStats.PidsStats.Current) != 1 { + t.Fatal("Expected PidsStats.Current to have at least one element") + } + + if actualStats.PidsStats.Max != configs.MaxPids { + t.Fatal("Expected PidsStats.Max to return -1") + } +} diff --git a/libcontainer/cgroups/fs/utils.go b/libcontainer/cgroups/fs/utils.go index 852b18391d0..d5b0810854d 100644 --- a/libcontainer/cgroups/fs/utils.go +++ b/libcontainer/cgroups/fs/utils.go @@ -3,9 +3,12 @@ package fs import ( + "bufio" "errors" "fmt" + "io" "io/ioutil" + "os" "path/filepath" "strconv" "strings" @@ -68,6 +71,33 @@ func getCgroupParamUint(cgroupPath, cgroupFile string) (uint64, error) { return res, nil } +// Gets an array of uint64 values from the specified cgroup file. +func getCgroupParamUintArray(cgroupPath, cgroupFile string) ([]uint64, error) { + + var res []uint64 + + fileName := filepath.Join(cgroupPath, cgroupFile) + file, err := os.Open(fileName) + if err != nil { + return res, err + } + defer file.Close() + + reader := bufio.NewReader(file) + for { + line, _, err := reader.ReadLine() + if err == io.EOF { + break + } + item, err := parseUint(strings.TrimSpace(string(line)), 10, 64) + if err != nil { + return res, fmt.Errorf("unable to parse %q as a uint from Cgroup file %q", string(line), fileName) + } + res = append(res, item) + } + return res, nil +} + // Gets a string value from the specified cgroup file func getCgroupParamString(cgroupPath, cgroupFile string) (string, error) { contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile)) diff --git a/libcontainer/cgroups/stats.go b/libcontainer/cgroups/stats.go index bda32b20c3b..cc846cd94a6 100644 --- a/libcontainer/cgroups/stats.go +++ b/libcontainer/cgroups/stats.go @@ -77,12 +77,21 @@ type HugetlbStats struct { Failcnt uint64 `json:"failcnt"` } +type PidsStats struct { + // current pids in cgroup; the set of processes can change at any time + Current []uint64 `json:"current,omitempty"` + + // maximum number of processes allowed in this cgroup (fork limit) + Max int `json:"max,omitempty"` +} + type Stats struct { CpuStats CpuStats `json:"cpu_stats,omitempty"` MemoryStats MemoryStats `json:"memory_stats,omitempty"` BlkioStats BlkioStats `json:"blkio_stats,omitempty"` // the map is in the format "size of hugepage: stats of the hugepage" HugetlbStats map[string]HugetlbStats `json:"hugetlb_stats,omitempty"` + PidsStats PidsStats `json:"pids_stats,omitempty"` } func NewStats() *Stats { diff --git a/libcontainer/configs/cgroup_unix.go b/libcontainer/configs/cgroup_unix.go index 24f93c1ad6e..d99540c946f 100644 --- a/libcontainer/configs/cgroup_unix.go +++ b/libcontainer/configs/cgroup_unix.go @@ -8,6 +8,8 @@ const ( Undefined FreezerState = "" Frozen FreezerState = "FROZEN" Thawed FreezerState = "THAWED" + + MaxPids = -1 ) type Cgroup struct { @@ -97,4 +99,10 @@ type Cgroup struct { // Set class identifier for container's network packets NetClsClassid string `json:"net_cls_classid"` + + // max. number of processes in this cgroup + PidsMax uint32 `json:"pids_max"` + + // a set of processes to add to this cgroup + Pids []uint32 `json:"pids"` }