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