Repository: moby/moby (language: go)

Issue:
[regression in master] `docker info` always shows `WARNING: No swap limit support` on cgroup v2 hosts
<!--
If you are reporting a new issue, make sure that we do not have any duplicates
already open. You can ensure this by searching the issue list for this
repository. If there is a duplicate, please close your issue and add a comment
to the existing issue instead.

If you suspect your issue is a bug, please edit your issue description to
include the BUG REPORT INFORMATION shown below. If you fail to provide this
information within 7 days, we cannot debug your issue and will close it. We
will, however, reopen it if you later provide the information.

For more information about reporting issues, see
https://github.com/moby/moby/blob/master/CONTRIBUTING.md#reporting-other-issues

---------------------------------------------------
GENERAL SUPPORT INFORMATION
---------------------------------------------------

The GitHub issue tracker is for bug reports and feature requests.
General support for **docker** can be found at the following locations:

- Docker Support Forums - https://forums.docker.com
- Slack - community.docker.com #general channel
- Post a question on StackOverflow, using the Docker tag

General support for **moby** can be found at the following locations:

- Moby Project Forums - https://forums.mobyproject.org
- Slack - community.docker.com #moby-project channel
- Post a question on StackOverflow, using the Moby tag

---------------------------------------------------
BUG REPORT INFORMATION
---------------------------------------------------
Use the commands below to provide key information from your environment:
You do NOT have to include this information if this is a FEATURE REQUEST
-->

**Description**

<!--
Briefly describe the problem you are having in a few paragraphs.
-->

`docker info` always shows `WARNING: No swap limit support` on cgroup v2 hosts.

Regression in https://github.com/moby/moby/pull/43347.
The commit in the master branch is not backported to the 20.10 branch, so v20.10.16 is not affected.

**Steps to reproduce the issue:**
`docker info`

**Describe the results you received:**
Always shows `WARNING: No swap limit support`

**Describe the results you expected:**
No warning


- - -

Being fixed in:
- https://github.com/containerd/cgroups/pull/232


Discussion / hints:

Fix patch:
diff --git a/pkg/sysinfo/cgroup2_linux.go b/pkg/sysinfo/cgroup2_linux.go
index 6a5caff4c0754..fa6338e19bcca 100644
--- a/pkg/sysinfo/cgroup2_linux.go
+++ b/pkg/sysinfo/cgroup2_linux.go
@@ -56,12 +56,11 @@ func newV2(options ...Opt) *SysInfo {
 }
 
 func getSwapLimitV2() bool {
-	groups, err := cgroups.ParseCgroupFile("/proc/self/cgroup")
+	_, g, err := cgroups.ParseCgroupFileUnified("/proc/self/cgroup")
 	if err != nil {
 		return false
 	}
 
-	g := groups[""]
 	if g == "" {
 		return false
 	}
diff --git a/vendor.mod b/vendor.mod
index 4c9d300d6bd3e..14416ac6705b3 100644
--- a/vendor.mod
+++ b/vendor.mod
@@ -17,7 +17,7 @@ require (
 	github.com/aws/aws-sdk-go v1.31.6
 	github.com/bsphere/le_go v0.0.0-20170215134836-7a984a84b549
 	github.com/cloudflare/cfssl v0.0.0-20180323000720-5d63dbd981b5
-	github.com/containerd/cgroups v1.0.3
+	github.com/containerd/cgroups v1.0.4
 	github.com/containerd/containerd v1.6.4
 	github.com/containerd/continuity v0.3.0
 	github.com/containerd/fifo v1.0.0
diff --git a/vendor.sum b/vendor.sum
index e372703cca55b..2a01ed1f589e4 100644
--- a/vendor.sum
+++ b/vendor.sum
@@ -209,8 +209,9 @@ github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4S
 github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo=
 github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
 github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU=
-github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4=
 github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8=
+github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA=
+github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA=
 github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
 github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
 github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
diff --git a/vendor/github.com/containerd/cgroups/README.md b/vendor/github.com/containerd/cgroups/README.md
index fc2c7a9be4d2f..eccb9d984555f 100644
--- a/vendor/github.com/containerd/cgroups/README.md
+++ b/vendor/github.com/containerd/cgroups/README.md
@@ -26,7 +26,7 @@ uses the v1 implementation of cgroups.
 ```go
 shares := uint64(100)
 control, err := cgroups.New(cgroups.V1, cgroups.StaticPath("/test"), &specs.LinuxResources{
-    CPU: &specs.CPU{
+    CPU: &specs.LinuxCPU{
         Shares: &shares,
     },
 })
diff --git a/vendor/github.com/containerd/cgroups/Vagrantfile b/vendor/github.com/containerd/cgroups/Vagrantfile
index 4596ad8a7d455..9a4aac8cb3447 100644
--- a/vendor/github.com/containerd/cgroups/Vagrantfile
+++ b/vendor/github.com/containerd/cgroups/Vagrantfile
@@ -3,19 +3,19 @@
 
 Vagrant.configure("2") do |config|
 # Fedora box is used for testing cgroup v2 support
-  config.vm.box = "fedora/32-cloud-base"
+  config.vm.box = "fedora/35-cloud-base"
   config.vm.provider :virtualbox do |v|
-    v.memory = 2048
+    v.memory = 4096
     v.cpus = 2
   end
   config.vm.provider :libvirt do |v|
-    v.memory = 2048
+    v.memory = 4096
     v.cpus = 2
   end
   config.vm.provision "shell", inline: <<-SHELL
     set -eux -o pipefail
     # configuration
-    GO_VERSION="1.15"
+    GO_VERSION="1.17.7"
 
     # install gcc and Golang
     dnf -y install gcc
diff --git a/vendor/github.com/containerd/cgroups/utils.go b/vendor/github.com/containerd/cgroups/utils.go
index 2297980d93ce7..217138975682a 100644
--- a/vendor/github.com/containerd/cgroups/utils.go
+++ b/vendor/github.com/containerd/cgroups/utils.go
@@ -261,21 +261,28 @@ func parseKV(raw string) (string, uint64, error) {
 //   "pids": "/user.slice/user-1000.slice"
 // etc.
 //
-// Note that for cgroup v2 unified hierarchy, there are no per-controller
-// cgroup paths, so the resulting map will have a single element where the key
-// is empty string ("") and the value is the cgroup path the <pid> is in.
+// The resulting map does not have an element for cgroup v2 unified hierarchy.
+// Use ParseCgroupFileUnified to get the unified path.
 func ParseCgroupFile(path string) (map[string]string, error) {
+	x, _, err := ParseCgroupFileUnified(path)
+	return x, err
+}
+
+// ParseCgroupFileUnified returns legacy subsystem paths as the first value,
+// and returns the unified path as the second value.
+func ParseCgroupFileUnified(path string) (map[string]string, string, error) {
 	f, err := os.Open(path)
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
 	defer f.Close()
-	return parseCgroupFromReader(f)
+	return parseCgroupFromReaderUnified(f)
 }
 
-func parseCgroupFromReader(r io.Reader) (map[string]string, error) {
+func parseCgroupFromReaderUnified(r io.Reader) (map[string]string, string, error) {
 	var (
 		cgroups = make(map[string]string)
+		unified = ""
 		s       = bufio.NewScanner(r)
 	)
 	for s.Scan() {
@@ -284,18 +291,20 @@ func parseCgroupFromReader(r io.Reader) (map[string]string, error) {
 			parts = strings.SplitN(text, ":", 3)
 		)
 		if len(parts) < 3 {
-			return nil, fmt.Errorf("invalid cgroup entry: %q", text)
+			return nil, unified, fmt.Errorf("invalid cgroup entry: %q", text)
 		}
 		for _, subs := range strings.Split(parts[1], ",") {
-			if subs != "" {
+			if subs == "" {
+				unified = parts[2]
+			} else {
 				cgroups[subs] = parts[2]
 			}
 		}
 	}
 	if err := s.Err(); err != nil {
-		return nil, err
+		return nil, unified, err
 	}
-	return cgroups, nil
+	return cgroups, unified, nil
 }
 
 func getCgroupDestination(subsystem string) (string, error) {
diff --git a/vendor/github.com/containerd/cgroups/v2/manager.go b/vendor/github.com/containerd/cgroups/v2/manager.go
index afed14c6ef3b4..1f017509f119a 100644
--- a/vendor/github.com/containerd/cgroups/v2/manager.go
+++ b/vendor/github.com/containerd/cgroups/v2/manager.go
@@ -240,6 +240,10 @@ func (c *Manager) Controllers() ([]string, error) {
 	return strings.Fields(string(b)), nil
 }
 
+func (c *Manager) Update(resources *Resources) error {
+	return setResources(c.path, resources)
+}
+
 type ControllerToggle int
 
 const (
@@ -701,12 +705,39 @@ func setDevices(path string, devices []specs.LinuxDeviceCgroup) error {
 	return nil
 }
 
+// getSystemdFullPath returns the full systemd path when creating a systemd slice group.
+// the reason this is necessary is because the "-" character has a special meaning in
+// systemd slice. For example, when creating a slice called "my-group-112233.slice",
+// systemd will create a hierarchy like this:
+//      /sys/fs/cgroup/my.slice/my-group.slice/my-group-112233.slice
+func getSystemdFullPath(slice, group string) string {
+	return filepath.Join(defaultCgroup2Path, dashesToPath(slice), dashesToPath(group))
+}
+
+// dashesToPath converts a slice name with dashes to it's corresponding systemd filesystem path.
+func dashesToPath(in string) string {
+	path := ""
+	if strings.HasSuffix(in, ".slice") && strings.Contains(in, "-") {
+		parts := strings.Split(in, "-")
+		for i := range parts {
+			s := strings.Join(parts[0:i+1], "-")
+			if !strings.HasSuffix(s, ".slice") {
+				s += ".slice"
+			}
+			path = filepath.Join(path, s)
+		}
+	} else {
+		path = filepath.Join(path, in)
+	}
+	return path
+}
+
 func NewSystemd(slice, group string, pid int, resources *Resources) (*Manager, error) {
 	if slice == "" {
 		slice = defaultSlice
 	}
 	ctx := context.TODO()
-	path := filepath.Join(defaultCgroup2Path, slice, group)
+	path := getSystemdFullPath(slice, group)
 	conn, err := systemdDbus.NewWithContext(ctx)
 	if err != nil {
 		return &Manager{}, err
@@ -734,12 +765,17 @@ func NewSystemd(slice, group string, pid int, resources *Resources) (*Manager, e
 		properties = append(properties, newSystemdProperty("PIDs", []uint32{uint32(pid)}))
 	}
 
-	if resources.Memory != nil && *resources.Memory.Max != 0 {
+	if resources.Memory != nil && resources.Memory.Min != nil && *resources.Memory.Min != 0 {
+		properties = append(properties,
+			newSystemdProperty("MemoryMin", uint64(*resources.Memory.Min)))
+	}
+
+	if resources.Memory != nil && resources.Memory.Max != nil && *resources.Memory.Max != 0 {
 		properties = append(properties,
 			newSystemdProperty("MemoryMax", uint64(*resources.Memory.Max)))
 	}
 
-	if resources.CPU != nil && *resources.CPU.Weight != 0 {
+	if resources.CPU != nil && resources.CPU.Weight != nil && *resources.CPU.Weight != 0 {
 		properties = append(properties,
 			newSystemdProperty("CPUWeight", *resources.CPU.Weight))
 	}
@@ -796,9 +832,9 @@ func LoadSystemd(slice, group string) (*Manager, error) {
 	if slice == "" {
 		slice = defaultSlice
 	}
-	group = filepath.Join(defaultCgroup2Path, slice, group)
+	path := getSystemdFullPath(slice, group)
 	return &Manager{
-		path: group,
+		path: path,
 	}, nil
 }
 
diff --git a/vendor/github.com/containerd/cgroups/v2/memory.go b/vendor/github.com/containerd/cgroups/v2/memory.go
index 72f94b738b8f3..6f4733be60ffc 100644
--- a/vendor/github.com/containerd/cgroups/v2/memory.go
+++ b/vendor/github.com/containerd/cgroups/v2/memory.go
@@ -18,6 +18,7 @@ package v2
 
 type Memory struct {
 	Swap *int64
+	Min  *int64
 	Max  *int64
 	Low  *int64
 	High *int64
@@ -30,6 +31,12 @@ func (r *Memory) Values() (o []Value) {
 			value:    *r.Swap,
 		})
 	}
+	if r.Min != nil {
+		o = append(o, Value{
+			filename: "memory.min",
+			value:    *r.Min,
+		})
+	}
 	if r.Max != nil {
 		o = append(o, Value{
 			filename: "memory.max",
diff --git a/vendor/github.com/containerd/cgroups/v2/utils.go b/vendor/github.com/containerd/cgroups/v2/utils.go
index 902466f51be22..240c9267798a0 100644
--- a/vendor/github.com/containerd/cgroups/v2/utils.go
+++ b/vendor/github.com/containerd/cgroups/v2/utils.go
@@ -227,7 +227,7 @@ func ToResources(spec *specs.LinuxResources) *Resources {
 	if i := spec.Rdma; i != nil {
 		resources.RDMA = &RDMA{}
 		for device, value := range spec.Rdma {
-			if device != "" && (value.HcaHandles != nil || value.HcaObjects != nil) {
+			if device != "" && (value.HcaHandles != nil && value.HcaObjects != nil) {
 				resources.RDMA.Limit = append(resources.RDMA.Limit, RDMAEntry{
 					Device:     device,
 					HcaHandles: *value.HcaHandles,
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 34ec47a2c6871..75d0e9d07024b 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -144,8 +144,8 @@ github.com/cloudflare/cfssl/signer/local
 # github.com/container-storage-interface/spec v1.5.0
 ## explicit; go 1.16
 github.com/container-storage-interface/spec/lib/go/csi
-# github.com/containerd/cgroups v1.0.3
-## explicit; go 1.16
+# github.com/containerd/cgroups v1.0.4
+## explicit; go 1.17
 github.com/containerd/cgroups
 github.com/containerd/cgroups/stats/v1
 github.com/containerd/cgroups/v2


Test patch: