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 }