gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/cgroup/systemd_test.go (about)

     1  // Copyright The runc Authors.
     2  // Copyright The containerd Authors.
     3  // Copyright 2022 The gVisor Authors.
     4  //
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     https://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  package cgroup
    18  
    19  import (
    20  	"errors"
    21  	"testing"
    22  
    23  	systemdDbus "github.com/coreos/go-systemd/v22/dbus"
    24  	dbus "github.com/godbus/dbus/v5"
    25  	"github.com/google/go-cmp/cmp"
    26  	"github.com/google/go-cmp/cmp/cmpopts"
    27  	specs "github.com/opencontainers/runtime-spec/specs-go"
    28  )
    29  
    30  var (
    31  	defaultProps         = []systemdDbus.Property{}
    32  	mandatoryControllers = []string{"cpu", "cpuset", "io", "memory", "pids"}
    33  )
    34  
    35  func TestIsValidSlice(t *testing.T) {
    36  	for _, tc := range []struct {
    37  		name  string
    38  		slice string
    39  		err   error
    40  	}{
    41  		{
    42  			name:  "success",
    43  			slice: "system.slice",
    44  		},
    45  		{
    46  			name:  "root slice",
    47  			slice: "-.slice",
    48  		},
    49  		{
    50  			name:  "path in slice",
    51  			slice: "system-child-grandchild.slice",
    52  		},
    53  		{
    54  			name:  "bad suffix",
    55  			slice: "system.scope",
    56  			err:   ErrInvalidSlice,
    57  		},
    58  		{
    59  			name:  "has path seperators",
    60  			slice: "systemd.slice/child.slice",
    61  			err:   ErrInvalidSlice,
    62  		},
    63  		{
    64  			name:  "invalid separator pattern",
    65  			slice: "systemd--child.slice",
    66  			err:   ErrInvalidSlice,
    67  		},
    68  	} {
    69  		t.Run(tc.name, func(t *testing.T) {
    70  			err := validSlice(tc.slice)
    71  			if !errors.Is(err, tc.err) {
    72  				t.Errorf("validSlice(%s) = %v, want %v", tc.slice, err, tc.err)
    73  			}
    74  		})
    75  	}
    76  }
    77  
    78  func TestExpandSlice(t *testing.T) {
    79  	original := "test-a-b.slice"
    80  	want := "/test.slice/test-a.slice/test-a-b.slice"
    81  	expanded := expandSlice(original)
    82  	if expanded != want {
    83  		t.Errorf("expandSlice(%q) = %q, want %q", original, expanded, want)
    84  	}
    85  }
    86  
    87  func TestInstall(t *testing.T) {
    88  	for _, tc := range []struct {
    89  		name      string
    90  		res       *specs.LinuxResources
    91  		wantProps []systemdDbus.Property
    92  		err       error
    93  	}{
    94  		{
    95  			name: "defaults",
    96  			res:  nil,
    97  			wantProps: []systemdDbus.Property{
    98  				{"Slice", dbus.MakeVariant("parent.slice")},
    99  				{Name: "Description", Value: dbus.MakeVariant("Secure container 123")},
   100  				{Name: "MemoryAccounting", Value: dbus.MakeVariant(true)},
   101  				{Name: "CPUAccounting", Value: dbus.MakeVariant(true)},
   102  				{Name: "TasksAccounting", Value: dbus.MakeVariant(true)},
   103  				{Name: "IOAccounting", Value: dbus.MakeVariant(true)},
   104  				{Name: "Delegate", Value: dbus.MakeVariant(true)},
   105  				{Name: "DefaultDependencies", Value: dbus.MakeVariant(false)},
   106  			},
   107  		},
   108  		{
   109  			name: "memory",
   110  			res: &specs.LinuxResources{
   111  				Memory: &specs.LinuxMemory{
   112  					Limit:       int64Ptr(1),
   113  					Swap:        int64Ptr(2),
   114  					Reservation: int64Ptr(3),
   115  				},
   116  			},
   117  			wantProps: []systemdDbus.Property{
   118  				{"MemoryMax", dbus.MakeVariant(uint64(1))},
   119  				{"MemoryLow", dbus.MakeVariant(uint64(3))},
   120  				{"MemorySwapMax", dbus.MakeVariant(uint64(1))},
   121  			},
   122  		},
   123  		{
   124  			name: "memory no limit",
   125  			res: &specs.LinuxResources{
   126  				Memory: &specs.LinuxMemory{
   127  					Swap: int64Ptr(1),
   128  				},
   129  			},
   130  			err: ErrBadResourceSpec,
   131  		},
   132  		{
   133  			name: "cpu defaults",
   134  			res: &specs.LinuxResources{
   135  				CPU: &specs.LinuxCPU{
   136  					Shares: uint64Ptr(0),
   137  					Quota:  int64Ptr(5),
   138  					Period: uint64Ptr(0),
   139  				},
   140  			},
   141  			wantProps: []systemdDbus.Property{
   142  				{"CPUQuotaPerSecUSec", dbus.MakeVariant(uint64(10000))},
   143  			},
   144  		},
   145  		{
   146  			name: "cpu",
   147  			res: &specs.LinuxResources{
   148  				CPU: &specs.LinuxCPU{
   149  					Shares: uint64Ptr(1),
   150  					Period: uint64Ptr(20000),
   151  					Quota:  int64Ptr(300000),
   152  					Cpus:   "4",
   153  					Mems:   "5",
   154  				},
   155  			},
   156  			wantProps: []systemdDbus.Property{
   157  				{"CPUWeight", dbus.MakeVariant(convertCPUSharesToCgroupV2Value(1))},
   158  				{"CPUQuotaPeriodUSec", dbus.MakeVariant(uint64(20000))},
   159  				{"CPUQuotaPerSecUSec", dbus.MakeVariant(uint64(15000000))},
   160  				{"AllowedCPUs", dbus.MakeVariant([]byte{1 << 4})},
   161  				{"AllowedMemoryNodes", dbus.MakeVariant([]byte{1 << 5})},
   162  			},
   163  		},
   164  		{
   165  			name: "cpuset",
   166  			res: &specs.LinuxResources{
   167  				CPU: &specs.LinuxCPU{
   168  					Cpus: "1-3,5",
   169  					Mems: "5-8",
   170  				},
   171  			},
   172  			wantProps: []systemdDbus.Property{
   173  				{"AllowedCPUs", dbus.MakeVariant([]byte{0b_101110})},
   174  				{"AllowedMemoryNodes", dbus.MakeVariant([]byte{1, 0b_11100000})},
   175  			},
   176  		},
   177  		{
   178  			name: "io",
   179  			res: &specs.LinuxResources{
   180  				BlockIO: &specs.LinuxBlockIO{
   181  					Weight: uint16Ptr(1),
   182  					WeightDevice: []specs.LinuxWeightDevice{
   183  						makeLinuxWeightDevice(2, 3, uint16Ptr(4), uint16Ptr(0)),
   184  						makeLinuxWeightDevice(5, 6, uint16Ptr(7), uint16Ptr(0)),
   185  					},
   186  					ThrottleReadBpsDevice: []specs.LinuxThrottleDevice{
   187  						makeLinuxThrottleDevice(8, 9, 10),
   188  						makeLinuxThrottleDevice(11, 12, 13),
   189  					},
   190  					ThrottleWriteBpsDevice: []specs.LinuxThrottleDevice{
   191  						makeLinuxThrottleDevice(14, 15, 16),
   192  					},
   193  					ThrottleReadIOPSDevice: []specs.LinuxThrottleDevice{
   194  						makeLinuxThrottleDevice(17, 18, 19),
   195  					},
   196  					ThrottleWriteIOPSDevice: []specs.LinuxThrottleDevice{
   197  						makeLinuxThrottleDevice(20, 21, 22),
   198  					},
   199  				},
   200  			},
   201  			wantProps: []systemdDbus.Property{
   202  				{"IOWeight", dbus.MakeVariant(convertBlkIOToIOWeightValue(1))},
   203  				{"IODeviceWeight", dbus.MakeVariant("2:3 4")},
   204  				{"IODeviceWeight", dbus.MakeVariant("5:6 7")},
   205  				{"IOReadBandwidthMax", dbus.MakeVariant("8:9 10")},
   206  				{"IOReadBandwidthMax", dbus.MakeVariant("11:12 13")},
   207  				{"IOWriteBandwidthMax", dbus.MakeVariant("14:15 16")},
   208  				{"IOReadIOPSMax", dbus.MakeVariant("17:18 19")},
   209  				{"IOWriteIOPSMax", dbus.MakeVariant("20:21 22")},
   210  			},
   211  		},
   212  	} {
   213  		t.Run(tc.name, func(t *testing.T) {
   214  			cg := cgroupSystemd{Name: "123", Parent: "parent.slice"}
   215  			cg.Controllers = mandatoryControllers
   216  			err := cg.Install(tc.res)
   217  			if !errors.Is(err, tc.err) {
   218  				t.Fatalf("Wrong error, got: %s, want: %s", err, tc.err)
   219  			}
   220  			cmper := cmp.Comparer(func(a dbus.Variant, b dbus.Variant) bool {
   221  				return a.String() == b.String()
   222  			})
   223  			sorter := cmpopts.SortSlices(func(a systemdDbus.Property, b systemdDbus.Property) bool {
   224  				return (a.Name + a.Value.String()) > (b.Name + b.Value.String())
   225  			})
   226  			filteredProps := filterProperties(cg.properties, tc.wantProps)
   227  			if diff := cmp.Diff(filteredProps, tc.wantProps, cmper, sorter); diff != "" {
   228  				t.Errorf("cgroup properties list diff %s", diff)
   229  			}
   230  		})
   231  	}
   232  }
   233  
   234  // filterProperties filters the list of properties in got to ones with
   235  // the names of properties specified in want.
   236  func filterProperties(got []systemdDbus.Property, want []systemdDbus.Property) []systemdDbus.Property {
   237  	if want == nil {
   238  		return nil
   239  	}
   240  	filterMap := map[string]any{}
   241  	for _, prop := range want {
   242  		filterMap[prop.Name] = nil
   243  	}
   244  	filtered := []systemdDbus.Property{}
   245  	for _, prop := range got {
   246  		if _, ok := filterMap[prop.Name]; ok {
   247  			filtered = append(filtered, prop)
   248  		}
   249  	}
   250  	return filtered
   251  }