github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/runsc/cgroup/cgroup_test.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cgroup
    16  
    17  import (
    18  	"io/ioutil"
    19  	"os"
    20  	"path/filepath"
    21  	"strings"
    22  	"testing"
    23  
    24  	specs "github.com/opencontainers/runtime-spec/specs-go"
    25  	"github.com/SagerNet/gvisor/pkg/test/testutil"
    26  )
    27  
    28  var debianMountinfo = `
    29  35 24 0:30 / /sys/fs/cgroup ro shared:9 - tmpfs tmpfs ro
    30  36 35 0:31 / /sys/fs/cgroup/unified rw shared:10 - cgroup2 cgroup2 rw
    31  37 35 0:32 / /sys/fs/cgroup/systemd rw - cgroup cgroup rw,name=systemd
    32  41 35 0:36 / /sys/fs/cgroup/cpu,cpuacct rw shared:16 - cgroup cgroup rw,cpu,cpuacct
    33  42 35 0:37 / /sys/fs/cgroup/freezer rw shared:17 - cgroup cgroup rw,freezer
    34  43 35 0:38 / /sys/fs/cgroup/hugetlb rw shared:18 - cgroup cgroup rw,hugetlb
    35  44 35 0:39 / /sys/fs/cgroup/cpuset rw shared:19 - cgroup cgroup rw,cpuset
    36  45 35 0:40 / /sys/fs/cgroup/net_cls,net_prio rw shared:20 - cgroup cgroup rw,net_cls,net_prio
    37  46 35 0:41 / /sys/fs/cgroup/pids rw shared:21 - cgroup cgroup rw,pids
    38  47 35 0:42 / /sys/fs/cgroup/perf_event rw shared:22 - cgroup cgroup rw,perf_event
    39  48 35 0:43 / /sys/fs/cgroup/memory rw shared:23 - cgroup cgroup rw,memory
    40  49 35 0:44 / /sys/fs/cgroup/blkio rw shared:24 - cgroup cgroup rw,blkio
    41  50 35 0:45 / /sys/fs/cgroup/devices rw shared:25 - cgroup cgroup rw,devices
    42  51 35 0:46 / /sys/fs/cgroup/rdma rw shared:26 - cgroup cgroup rw,rdma
    43  `
    44  
    45  var dindMountinfo = `
    46  05 04 0:64 / /sys/fs/cgroup rw - tmpfs tmpfs rw,mode=755
    47  06 05 0:32 /docker/136 /sys/fs/cgroup/systemd ro master:11 - cgroup cgroup rw,xattr,name=systemd
    48  07 05 0:36 /docker/136 /sys/fs/cgroup/cpu,cpuacct ro master:16 - cgroup cgroup rw,cpu,cpuacct
    49  08 05 0:37 /docker/136 /sys/fs/cgroup/freezer ro master:17 - cgroup cgroup rw,freezer
    50  09 05 0:38 /docker/136 /sys/fs/cgroup/hugetlb ro master:18 - cgroup cgroup rw,hugetlb
    51  10 05 0:39 /docker/136 /sys/fs/cgroup/cpuset ro master:19 - cgroup cgroup rw,cpuset
    52  11 05 0:40 /docker/136 /sys/fs/cgroup/net_cls,net_prio ro master:20 - cgroup cgroup rw,net_cls,net_prio
    53  12 05 0:41 /docker/136 /sys/fs/cgroup/pids ro master:21 - cgroup cgroup rw,pids
    54  13 05 0:42 /docker/136 /sys/fs/cgroup/perf_event ro master:22 - cgroup cgroup rw,perf_event
    55  14 05 0:43 /docker/136 /sys/fs/cgroup/memory ro master:23 - cgroup cgroup rw,memory
    56  16 05 0:44 /docker/136 /sys/fs/cgroup/blkio ro master:24 - cgroup cgroup rw,blkio
    57  17 05 0:45 /docker/136 /sys/fs/cgroup/devices ro master:25 - cgroup cgroup rw,devices
    58  18 05 0:46 / /sys/fs/cgroup/rdma ro master:26 - cgroup cgroup rw,rdma
    59  `
    60  
    61  func TestUninstallEnoent(t *testing.T) {
    62  	c := Cgroup{
    63  		// Use a non-existent name.
    64  		Name: "runsc-test-uninstall-656e6f656e740a",
    65  		Own:  make(map[string]bool),
    66  	}
    67  	for key := range controllers {
    68  		c.Own[key] = true
    69  	}
    70  	if err := c.Uninstall(); err != nil {
    71  		t.Errorf("Uninstall() failed: %v", err)
    72  	}
    73  }
    74  
    75  func TestCountCpuset(t *testing.T) {
    76  	for _, tc := range []struct {
    77  		str   string
    78  		want  int
    79  		error bool
    80  	}{
    81  		{str: "0", want: 1},
    82  		{str: "0,1,2,8,9,10", want: 6},
    83  		{str: "0-1", want: 2},
    84  		{str: "0-7", want: 8},
    85  		{str: "0-7,16,32-39,64,65", want: 19},
    86  		{str: "a", error: true},
    87  		{str: "5-a", error: true},
    88  		{str: "a-5", error: true},
    89  		{str: "-10", error: true},
    90  		{str: "15-", error: true},
    91  		{str: "-", error: true},
    92  		{str: "--", error: true},
    93  	} {
    94  		t.Run(tc.str, func(t *testing.T) {
    95  			got, err := countCpuset(tc.str)
    96  			if tc.error {
    97  				if err == nil {
    98  					t.Errorf("countCpuset(%q) should have failed", tc.str)
    99  				}
   100  			} else {
   101  				if err != nil {
   102  					t.Errorf("countCpuset(%q) failed: %v", tc.str, err)
   103  				}
   104  				if tc.want != got {
   105  					t.Errorf("countCpuset(%q) want: %d, got: %d", tc.str, tc.want, got)
   106  				}
   107  			}
   108  		})
   109  	}
   110  }
   111  
   112  func uint16Ptr(v uint16) *uint16 {
   113  	return &v
   114  }
   115  
   116  func uint32Ptr(v uint32) *uint32 {
   117  	return &v
   118  }
   119  
   120  func int64Ptr(v int64) *int64 {
   121  	return &v
   122  }
   123  
   124  func uint64Ptr(v uint64) *uint64 {
   125  	return &v
   126  }
   127  
   128  func boolPtr(v bool) *bool {
   129  	return &v
   130  }
   131  
   132  func checkDir(t *testing.T, dir string, contents map[string]string) {
   133  	all, err := ioutil.ReadDir(dir)
   134  	if err != nil {
   135  		t.Fatalf("ReadDir(%q): %v", dir, err)
   136  	}
   137  	fileCount := 0
   138  	for _, file := range all {
   139  		if file.IsDir() {
   140  			// Only want to compare files.
   141  			continue
   142  		}
   143  		fileCount++
   144  
   145  		want, ok := contents[file.Name()]
   146  		if !ok {
   147  			t.Errorf("file not expected: %q", file.Name())
   148  			continue
   149  		}
   150  		gotBytes, err := ioutil.ReadFile(filepath.Join(dir, file.Name()))
   151  		if err != nil {
   152  			t.Fatal(err.Error())
   153  		}
   154  		got := strings.TrimSuffix(string(gotBytes), "\n")
   155  		if got != want {
   156  			t.Errorf("wrong file content, file: %q, want: %q, got: %q", file.Name(), want, got)
   157  		}
   158  	}
   159  	if fileCount != len(contents) {
   160  		t.Errorf("file is missing, want: %v, got: %v", contents, all)
   161  	}
   162  }
   163  
   164  func makeLinuxWeightDevice(major, minor int64, weight, leafWeight *uint16) specs.LinuxWeightDevice {
   165  	rv := specs.LinuxWeightDevice{
   166  		Weight:     weight,
   167  		LeafWeight: leafWeight,
   168  	}
   169  	rv.Major = major
   170  	rv.Minor = minor
   171  	return rv
   172  }
   173  
   174  func makeLinuxThrottleDevice(major, minor int64, rate uint64) specs.LinuxThrottleDevice {
   175  	rv := specs.LinuxThrottleDevice{
   176  		Rate: rate,
   177  	}
   178  	rv.Major = major
   179  	rv.Minor = minor
   180  	return rv
   181  }
   182  
   183  func TestBlockIO(t *testing.T) {
   184  	for _, tc := range []struct {
   185  		name  string
   186  		spec  *specs.LinuxBlockIO
   187  		wants map[string]string
   188  	}{
   189  		{
   190  			name: "simple",
   191  			spec: &specs.LinuxBlockIO{
   192  				Weight:     uint16Ptr(1),
   193  				LeafWeight: uint16Ptr(2),
   194  			},
   195  			wants: map[string]string{
   196  				"blkio.weight":      "1",
   197  				"blkio.leaf_weight": "2",
   198  			},
   199  		},
   200  		{
   201  			name: "weight_device",
   202  			spec: &specs.LinuxBlockIO{
   203  				WeightDevice: []specs.LinuxWeightDevice{
   204  					makeLinuxWeightDevice(1, 2, uint16Ptr(3), uint16Ptr(4)),
   205  				},
   206  			},
   207  			wants: map[string]string{
   208  				"blkio.weight_device":      "1:2 3",
   209  				"blkio.leaf_weight_device": "1:2 4",
   210  			},
   211  		},
   212  		{
   213  			name: "weight_device_nil_values",
   214  			spec: &specs.LinuxBlockIO{
   215  				WeightDevice: []specs.LinuxWeightDevice{
   216  					makeLinuxWeightDevice(1, 2, nil, nil),
   217  				},
   218  			},
   219  		},
   220  		{
   221  			name: "throttle",
   222  			spec: &specs.LinuxBlockIO{
   223  				ThrottleReadBpsDevice: []specs.LinuxThrottleDevice{
   224  					makeLinuxThrottleDevice(1, 2, 3),
   225  				},
   226  				ThrottleReadIOPSDevice: []specs.LinuxThrottleDevice{
   227  					makeLinuxThrottleDevice(4, 5, 6),
   228  				},
   229  				ThrottleWriteBpsDevice: []specs.LinuxThrottleDevice{
   230  					makeLinuxThrottleDevice(7, 8, 9),
   231  				},
   232  				ThrottleWriteIOPSDevice: []specs.LinuxThrottleDevice{
   233  					makeLinuxThrottleDevice(10, 11, 12),
   234  				},
   235  			},
   236  			wants: map[string]string{
   237  				"blkio.throttle.read_bps_device":   "1:2 3",
   238  				"blkio.throttle.read_iops_device":  "4:5 6",
   239  				"blkio.throttle.write_bps_device":  "7:8 9",
   240  				"blkio.throttle.write_iops_device": "10:11 12",
   241  			},
   242  		},
   243  		{
   244  			name: "nil_values",
   245  			spec: &specs.LinuxBlockIO{},
   246  		},
   247  		{
   248  			name: "nil",
   249  		},
   250  	} {
   251  		t.Run(tc.name, func(t *testing.T) {
   252  			dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup")
   253  			if err != nil {
   254  				t.Fatalf("error creating temporary directory: %v", err)
   255  			}
   256  			defer os.RemoveAll(dir)
   257  
   258  			spec := &specs.LinuxResources{
   259  				BlockIO: tc.spec,
   260  			}
   261  			ctrlr := blockIO{}
   262  			if err := ctrlr.set(spec, dir); err != nil {
   263  				t.Fatalf("ctrlr.set(): %v", err)
   264  			}
   265  			checkDir(t, dir, tc.wants)
   266  		})
   267  	}
   268  }
   269  
   270  func TestCPU(t *testing.T) {
   271  	for _, tc := range []struct {
   272  		name  string
   273  		spec  *specs.LinuxCPU
   274  		wants map[string]string
   275  	}{
   276  		{
   277  			name: "all",
   278  			spec: &specs.LinuxCPU{
   279  				Shares:          uint64Ptr(1),
   280  				Quota:           int64Ptr(2),
   281  				Period:          uint64Ptr(3),
   282  				RealtimeRuntime: int64Ptr(4),
   283  				RealtimePeriod:  uint64Ptr(5),
   284  			},
   285  			wants: map[string]string{
   286  				"cpu.shares":        "1",
   287  				"cpu.cfs_quota_us":  "2",
   288  				"cpu.cfs_period_us": "3",
   289  				"cpu.rt_runtime_us": "4",
   290  				"cpu.rt_period_us":  "5",
   291  			},
   292  		},
   293  		{
   294  			name: "nil_values",
   295  			spec: &specs.LinuxCPU{},
   296  		},
   297  		{
   298  			name: "nil",
   299  		},
   300  	} {
   301  		t.Run(tc.name, func(t *testing.T) {
   302  			dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup")
   303  			if err != nil {
   304  				t.Fatalf("error creating temporary directory: %v", err)
   305  			}
   306  			defer os.RemoveAll(dir)
   307  
   308  			spec := &specs.LinuxResources{
   309  				CPU: tc.spec,
   310  			}
   311  			ctrlr := cpu{}
   312  			if err := ctrlr.set(spec, dir); err != nil {
   313  				t.Fatalf("ctrlr.set(): %v", err)
   314  			}
   315  			checkDir(t, dir, tc.wants)
   316  		})
   317  	}
   318  }
   319  
   320  func TestCPUSet(t *testing.T) {
   321  	for _, tc := range []struct {
   322  		name  string
   323  		spec  *specs.LinuxCPU
   324  		wants map[string]string
   325  	}{
   326  		{
   327  			name: "all",
   328  			spec: &specs.LinuxCPU{
   329  				Cpus: "foo",
   330  				Mems: "bar",
   331  			},
   332  			wants: map[string]string{
   333  				"cpuset.cpus": "foo",
   334  				"cpuset.mems": "bar",
   335  			},
   336  		},
   337  		// Don't test nil values because they are copied from the parent.
   338  		// See TestCPUSetAncestor().
   339  	} {
   340  		t.Run(tc.name, func(t *testing.T) {
   341  			dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup")
   342  			if err != nil {
   343  				t.Fatalf("error creating temporary directory: %v", err)
   344  			}
   345  			defer os.RemoveAll(dir)
   346  
   347  			spec := &specs.LinuxResources{
   348  				CPU: tc.spec,
   349  			}
   350  			ctrlr := cpuSet{}
   351  			if err := ctrlr.set(spec, dir); err != nil {
   352  				t.Fatalf("ctrlr.set(): %v", err)
   353  			}
   354  			checkDir(t, dir, tc.wants)
   355  		})
   356  	}
   357  }
   358  
   359  // TestCPUSetAncestor checks that, when not available, value is read from
   360  // parent directory.
   361  func TestCPUSetAncestor(t *testing.T) {
   362  	// Prepare master directory with cgroup files that will be propagated to
   363  	// children.
   364  	grandpa, err := ioutil.TempDir(testutil.TmpDir(), "cgroup")
   365  	if err != nil {
   366  		t.Fatalf("error creating temporary directory: %v", err)
   367  	}
   368  	defer os.RemoveAll(grandpa)
   369  
   370  	if err := ioutil.WriteFile(filepath.Join(grandpa, "cpuset.cpus"), []byte("parent-cpus"), 0666); err != nil {
   371  		t.Fatalf("ioutil.WriteFile(): %v", err)
   372  	}
   373  	if err := ioutil.WriteFile(filepath.Join(grandpa, "cpuset.mems"), []byte("parent-mems"), 0666); err != nil {
   374  		t.Fatalf("ioutil.WriteFile(): %v", err)
   375  	}
   376  
   377  	for _, tc := range []struct {
   378  		name string
   379  		spec *specs.LinuxCPU
   380  	}{
   381  		{
   382  			name: "nil_values",
   383  			spec: &specs.LinuxCPU{},
   384  		},
   385  		{
   386  			name: "nil",
   387  		},
   388  	} {
   389  		t.Run(tc.name, func(t *testing.T) {
   390  			// Create empty files in intermediate directory. They should be ignored
   391  			// when reading, and then populated from parent.
   392  			parent, err := ioutil.TempDir(grandpa, "parent")
   393  			if err != nil {
   394  				t.Fatalf("error creating temporary directory: %v", err)
   395  			}
   396  			defer os.RemoveAll(parent)
   397  			if _, err := os.Create(filepath.Join(parent, "cpuset.cpus")); err != nil {
   398  				t.Fatalf("os.Create(): %v", err)
   399  			}
   400  			if _, err := os.Create(filepath.Join(parent, "cpuset.mems")); err != nil {
   401  				t.Fatalf("os.Create(): %v", err)
   402  			}
   403  
   404  			// cgroup files mmust exist.
   405  			dir, err := ioutil.TempDir(parent, "child")
   406  			if err != nil {
   407  				t.Fatalf("error creating temporary directory: %v", err)
   408  			}
   409  			if _, err := os.Create(filepath.Join(dir, "cpuset.cpus")); err != nil {
   410  				t.Fatalf("os.Create(): %v", err)
   411  			}
   412  			if _, err := os.Create(filepath.Join(dir, "cpuset.mems")); err != nil {
   413  				t.Fatalf("os.Create(): %v", err)
   414  			}
   415  
   416  			spec := &specs.LinuxResources{
   417  				CPU: tc.spec,
   418  			}
   419  			ctrlr := cpuSet{}
   420  			if err := ctrlr.set(spec, dir); err != nil {
   421  				t.Fatalf("ctrlr.set(): %v", err)
   422  			}
   423  			want := map[string]string{
   424  				"cpuset.cpus": "parent-cpus",
   425  				"cpuset.mems": "parent-mems",
   426  			}
   427  			// Both path and dir must have been populated from grandpa.
   428  			checkDir(t, parent, want)
   429  			checkDir(t, dir, want)
   430  		})
   431  	}
   432  }
   433  
   434  func TestHugeTlb(t *testing.T) {
   435  	for _, tc := range []struct {
   436  		name  string
   437  		spec  []specs.LinuxHugepageLimit
   438  		wants map[string]string
   439  	}{
   440  		{
   441  			name: "single",
   442  			spec: []specs.LinuxHugepageLimit{
   443  				{
   444  					Pagesize: "1G",
   445  					Limit:    123,
   446  				},
   447  			},
   448  			wants: map[string]string{
   449  				"hugetlb.1G.limit_in_bytes": "123",
   450  			},
   451  		},
   452  		{
   453  			name: "multiple",
   454  			spec: []specs.LinuxHugepageLimit{
   455  				{
   456  					Pagesize: "1G",
   457  					Limit:    123,
   458  				},
   459  				{
   460  					Pagesize: "2G",
   461  					Limit:    456,
   462  				},
   463  				{
   464  					Pagesize: "1P",
   465  					Limit:    789,
   466  				},
   467  			},
   468  			wants: map[string]string{
   469  				"hugetlb.1G.limit_in_bytes": "123",
   470  				"hugetlb.2G.limit_in_bytes": "456",
   471  				"hugetlb.1P.limit_in_bytes": "789",
   472  			},
   473  		},
   474  		{
   475  			name: "nil",
   476  		},
   477  	} {
   478  		t.Run(tc.name, func(t *testing.T) {
   479  			dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup")
   480  			if err != nil {
   481  				t.Fatalf("error creating temporary directory: %v", err)
   482  			}
   483  			defer os.RemoveAll(dir)
   484  
   485  			spec := &specs.LinuxResources{
   486  				HugepageLimits: tc.spec,
   487  			}
   488  			ctrlr := hugeTLB{}
   489  			if err := ctrlr.set(spec, dir); err != nil {
   490  				t.Fatalf("ctrlr.set(): %v", err)
   491  			}
   492  			checkDir(t, dir, tc.wants)
   493  		})
   494  	}
   495  }
   496  
   497  func TestMemory(t *testing.T) {
   498  	for _, tc := range []struct {
   499  		name  string
   500  		spec  *specs.LinuxMemory
   501  		wants map[string]string
   502  	}{
   503  		{
   504  			name: "all",
   505  			spec: &specs.LinuxMemory{
   506  				Limit:            int64Ptr(1),
   507  				Reservation:      int64Ptr(2),
   508  				Swap:             int64Ptr(3),
   509  				Kernel:           int64Ptr(4),
   510  				KernelTCP:        int64Ptr(5),
   511  				Swappiness:       uint64Ptr(6),
   512  				DisableOOMKiller: boolPtr(true),
   513  			},
   514  			wants: map[string]string{
   515  				"memory.limit_in_bytes":          "1",
   516  				"memory.soft_limit_in_bytes":     "2",
   517  				"memory.memsw.limit_in_bytes":    "3",
   518  				"memory.kmem.limit_in_bytes":     "4",
   519  				"memory.kmem.tcp.limit_in_bytes": "5",
   520  				"memory.swappiness":              "6",
   521  				"memory.oom_control":             "1",
   522  			},
   523  		},
   524  		{
   525  			// Disable OOM killer should only write when set to true.
   526  			name: "oomkiller",
   527  			spec: &specs.LinuxMemory{
   528  				DisableOOMKiller: boolPtr(false),
   529  			},
   530  		},
   531  		{
   532  			name: "nil_values",
   533  			spec: &specs.LinuxMemory{},
   534  		},
   535  		{
   536  			name: "nil",
   537  		},
   538  	} {
   539  		t.Run(tc.name, func(t *testing.T) {
   540  			dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup")
   541  			if err != nil {
   542  				t.Fatalf("error creating temporary directory: %v", err)
   543  			}
   544  			defer os.RemoveAll(dir)
   545  
   546  			spec := &specs.LinuxResources{
   547  				Memory: tc.spec,
   548  			}
   549  			ctrlr := memory{}
   550  			if err := ctrlr.set(spec, dir); err != nil {
   551  				t.Fatalf("ctrlr.set(): %v", err)
   552  			}
   553  			checkDir(t, dir, tc.wants)
   554  		})
   555  	}
   556  }
   557  
   558  func TestNetworkClass(t *testing.T) {
   559  	for _, tc := range []struct {
   560  		name  string
   561  		spec  *specs.LinuxNetwork
   562  		wants map[string]string
   563  	}{
   564  		{
   565  			name: "all",
   566  			spec: &specs.LinuxNetwork{
   567  				ClassID: uint32Ptr(1),
   568  			},
   569  			wants: map[string]string{
   570  				"net_cls.classid": "1",
   571  			},
   572  		},
   573  		{
   574  			name: "nil_values",
   575  			spec: &specs.LinuxNetwork{},
   576  		},
   577  		{
   578  			name: "nil",
   579  		},
   580  	} {
   581  		t.Run(tc.name, func(t *testing.T) {
   582  			dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup")
   583  			if err != nil {
   584  				t.Fatalf("error creating temporary directory: %v", err)
   585  			}
   586  			defer os.RemoveAll(dir)
   587  
   588  			spec := &specs.LinuxResources{
   589  				Network: tc.spec,
   590  			}
   591  			ctrlr := networkClass{}
   592  			if err := ctrlr.set(spec, dir); err != nil {
   593  				t.Fatalf("ctrlr.set(): %v", err)
   594  			}
   595  			checkDir(t, dir, tc.wants)
   596  		})
   597  	}
   598  }
   599  
   600  func TestNetworkPriority(t *testing.T) {
   601  	for _, tc := range []struct {
   602  		name  string
   603  		spec  *specs.LinuxNetwork
   604  		wants map[string]string
   605  	}{
   606  		{
   607  			name: "all",
   608  			spec: &specs.LinuxNetwork{
   609  				Priorities: []specs.LinuxInterfacePriority{
   610  					{
   611  						Name:     "foo",
   612  						Priority: 1,
   613  					},
   614  				},
   615  			},
   616  			wants: map[string]string{
   617  				"net_prio.ifpriomap": "foo 1",
   618  			},
   619  		},
   620  		{
   621  			name: "nil_values",
   622  			spec: &specs.LinuxNetwork{},
   623  		},
   624  		{
   625  			name: "nil",
   626  		},
   627  	} {
   628  		t.Run(tc.name, func(t *testing.T) {
   629  			dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup")
   630  			if err != nil {
   631  				t.Fatalf("error creating temporary directory: %v", err)
   632  			}
   633  			defer os.RemoveAll(dir)
   634  
   635  			spec := &specs.LinuxResources{
   636  				Network: tc.spec,
   637  			}
   638  			ctrlr := networkPrio{}
   639  			if err := ctrlr.set(spec, dir); err != nil {
   640  				t.Fatalf("ctrlr.set(): %v", err)
   641  			}
   642  			checkDir(t, dir, tc.wants)
   643  		})
   644  	}
   645  }
   646  
   647  func TestPids(t *testing.T) {
   648  	for _, tc := range []struct {
   649  		name  string
   650  		spec  *specs.LinuxPids
   651  		wants map[string]string
   652  	}{
   653  		{
   654  			name: "all",
   655  			spec: &specs.LinuxPids{Limit: 1},
   656  			wants: map[string]string{
   657  				"pids.max": "1",
   658  			},
   659  		},
   660  		{
   661  			name: "nil_values",
   662  			spec: &specs.LinuxPids{},
   663  		},
   664  		{
   665  			name: "nil",
   666  		},
   667  	} {
   668  		t.Run(tc.name, func(t *testing.T) {
   669  			dir, err := ioutil.TempDir(testutil.TmpDir(), "cgroup")
   670  			if err != nil {
   671  				t.Fatalf("error creating temporary directory: %v", err)
   672  			}
   673  			defer os.RemoveAll(dir)
   674  
   675  			spec := &specs.LinuxResources{
   676  				Pids: tc.spec,
   677  			}
   678  			ctrlr := pids{}
   679  			if err := ctrlr.set(spec, dir); err != nil {
   680  				t.Fatalf("ctrlr.set(): %v", err)
   681  			}
   682  			checkDir(t, dir, tc.wants)
   683  		})
   684  	}
   685  }
   686  
   687  func TestLoadPaths(t *testing.T) {
   688  	for _, tc := range []struct {
   689  		name      string
   690  		cgroups   string
   691  		mountinfo string
   692  		want      map[string]string
   693  		err       string
   694  	}{
   695  		{
   696  			name:      "empty",
   697  			mountinfo: debianMountinfo,
   698  		},
   699  		{
   700  			name:      "abs-path",
   701  			cgroups:   "0:cpu:/path",
   702  			mountinfo: debianMountinfo,
   703  			want:      map[string]string{"cpu": "/path"},
   704  		},
   705  		{
   706  			name:      "rel-path",
   707  			cgroups:   "0:cpu:rel-path",
   708  			mountinfo: debianMountinfo,
   709  			want:      map[string]string{"cpu": "rel-path"},
   710  		},
   711  		{
   712  			name:      "non-controller",
   713  			cgroups:   "0:name=systemd:/path",
   714  			mountinfo: debianMountinfo,
   715  			want:      map[string]string{"systemd": "/path"},
   716  		},
   717  		{
   718  			name:      "unknown-controller",
   719  			cgroups:   "0:ctr:/path",
   720  			mountinfo: debianMountinfo,
   721  			want:      map[string]string{},
   722  		},
   723  		{
   724  			name: "multiple",
   725  			cgroups: "0:cpu:/path0\n" +
   726  				"1:memory:/path1\n" +
   727  				"2::/empty\n",
   728  			mountinfo: debianMountinfo,
   729  			want: map[string]string{
   730  				"cpu":    "/path0",
   731  				"memory": "/path1",
   732  			},
   733  		},
   734  		{
   735  			name:      "missing-field",
   736  			cgroups:   "0:nopath\n",
   737  			mountinfo: debianMountinfo,
   738  			err:       "invalid cgroups file",
   739  		},
   740  		{
   741  			name:      "too-many-fields",
   742  			cgroups:   "0:ctr:/path:extra\n",
   743  			mountinfo: debianMountinfo,
   744  			err:       "invalid cgroups file",
   745  		},
   746  		{
   747  			name: "multiple-malformed",
   748  			cgroups: "0:ctr0:/path0\n" +
   749  				"1:ctr1:/path1\n" +
   750  				"2:\n",
   751  			mountinfo: debianMountinfo,
   752  			err:       "invalid cgroups file",
   753  		},
   754  		{
   755  			name: "nested-cgroup",
   756  			cgroups: "9:memory:/docker/136\n" +
   757  				"2:cpu,cpuacct:/docker/136\n" +
   758  				"1:name=systemd:/docker/136\n" +
   759  				"0::/system.slice/containerd.service\n",
   760  			mountinfo: dindMountinfo,
   761  			// we want relative path to /sys/fs/cgroup inside the nested container.
   762  			// Subcroup inside the container will be created at /sys/fs/cgroup/cpu
   763  			// This will be /sys/fs/cgroup/cpu/docker/136/CGROUP_NAME
   764  			// outside the container
   765  			want: map[string]string{
   766  				"memory":  ".",
   767  				"cpu":     ".",
   768  				"cpuacct": ".",
   769  				"systemd": ".",
   770  			},
   771  		},
   772  		{
   773  			name:      "nested-cgroup-submount",
   774  			cgroups:   "9:memory:/docker/136/test",
   775  			mountinfo: dindMountinfo,
   776  			want: map[string]string{
   777  				"memory": "test",
   778  			},
   779  		},
   780  		{
   781  			name:      "invalid-mount-info",
   782  			cgroups:   "0:memory:/path",
   783  			mountinfo: "41 35 0:36 / /sys/fs/cgroup/memory rw shared:16 - invalid",
   784  			want: map[string]string{
   785  				"memory": "/path",
   786  			},
   787  		},
   788  		{
   789  			name:      "invalid-rel-path-in-proc-cgroup",
   790  			cgroups:   "9:memory:invalid",
   791  			mountinfo: dindMountinfo,
   792  			err:       "can't make invalid relative to /docker/136",
   793  		},
   794  	} {
   795  		t.Run(tc.name, func(t *testing.T) {
   796  			r := strings.NewReader(tc.cgroups)
   797  			mountinfo := strings.NewReader(tc.mountinfo)
   798  			got, err := loadPathsHelper(r, mountinfo)
   799  			if len(tc.err) == 0 {
   800  				if err != nil {
   801  					t.Fatalf("Unexpected error: %v", err)
   802  				}
   803  			} else if !strings.Contains(err.Error(), tc.err) {
   804  				t.Fatalf("Wrong error message, want: *%s*, got: %v", tc.err, err)
   805  			}
   806  			for key, vWant := range tc.want {
   807  				vGot, ok := got[key]
   808  				if !ok {
   809  					t.Errorf("Missing controller %q", key)
   810  				}
   811  				if vWant != vGot {
   812  					t.Errorf("Wrong controller %q value, want: %q, got: %q", key, vWant, vGot)
   813  				}
   814  				delete(got, key)
   815  			}
   816  			for k, v := range got {
   817  				t.Errorf("Unexpected controller %q: %q", k, v)
   818  			}
   819  		})
   820  	}
   821  }
   822  
   823  func TestOptional(t *testing.T) {
   824  	for _, tc := range []struct {
   825  		name  string
   826  		ctrlr controller
   827  		spec  *specs.LinuxResources
   828  		err   string
   829  	}{
   830  		{
   831  			name:  "net-cls",
   832  			ctrlr: &networkClass{},
   833  			spec:  &specs.LinuxResources{Network: &specs.LinuxNetwork{ClassID: uint32Ptr(1)}},
   834  			err:   "Network.ClassID set but net_cls cgroup controller not found",
   835  		},
   836  		{
   837  			name:  "net-prio",
   838  			ctrlr: &networkPrio{},
   839  			spec: &specs.LinuxResources{Network: &specs.LinuxNetwork{
   840  				Priorities: []specs.LinuxInterfacePriority{
   841  					{Name: "foo", Priority: 1},
   842  				},
   843  			}},
   844  			err: "Network.Priorities set but net_prio cgroup controller not found",
   845  		},
   846  		{
   847  			name:  "hugetlb",
   848  			ctrlr: &hugeTLB{},
   849  			spec: &specs.LinuxResources{HugepageLimits: []specs.LinuxHugepageLimit{
   850  				{Pagesize: "1", Limit: 2},
   851  			}},
   852  			err: "HugepageLimits set but hugetlb cgroup controller not found",
   853  		},
   854  	} {
   855  		t.Run(tc.name, func(t *testing.T) {
   856  			err := tc.ctrlr.skip(tc.spec)
   857  			if err == nil {
   858  				t.Fatalf("ctrlr.skip() didn't fail")
   859  			}
   860  			if !strings.Contains(err.Error(), tc.err) {
   861  				t.Errorf("ctrlr.skip() want: *%s*, got: %q", tc.err, err)
   862  			}
   863  		})
   864  	}
   865  }