gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/root/oom_score_adj_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 root 16 17 import ( 18 "fmt" 19 "os" 20 "testing" 21 22 specs "github.com/opencontainers/runtime-spec/specs-go" 23 "gvisor.dev/gvisor/pkg/cleanup" 24 "gvisor.dev/gvisor/pkg/test/testutil" 25 "gvisor.dev/gvisor/runsc/container" 26 "gvisor.dev/gvisor/runsc/specutils" 27 ) 28 29 var ( 30 maxOOMScoreAdj = 1000 31 highOOMScoreAdj = 500 32 lowOOMScoreAdj = -500 33 minOOMScoreAdj = -1000 34 ) 35 36 // Tests for oom_score_adj have to be run as root (rather than in a user 37 // namespace) because we need to adjust oom_score_adj for PIDs other than our 38 // own and test values below 0. 39 40 // TestOOMScoreAdjSingle tests that oom_score_adj is set properly in a 41 // single container sandbox. 42 func TestOOMScoreAdjSingle(t *testing.T) { 43 parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(os.Getppid()) 44 if err != nil { 45 t.Fatalf("getting parent oom_score_adj: %v", err) 46 } 47 48 testCases := []struct { 49 Name string 50 51 // OOMScoreAdj is the oom_score_adj set to the OCI spec. If nil then 52 // no value is set. 53 OOMScoreAdj *int 54 }{ 55 { 56 Name: "max", 57 OOMScoreAdj: &maxOOMScoreAdj, 58 }, 59 { 60 Name: "high", 61 OOMScoreAdj: &highOOMScoreAdj, 62 }, 63 { 64 Name: "low", 65 OOMScoreAdj: &lowOOMScoreAdj, 66 }, 67 { 68 Name: "min", 69 OOMScoreAdj: &minOOMScoreAdj, 70 }, 71 { 72 Name: "nil", 73 OOMScoreAdj: &parentOOMScoreAdj, 74 }, 75 } 76 77 for _, testCase := range testCases { 78 t.Run(testCase.Name, func(t *testing.T) { 79 id := testutil.RandomContainerID() 80 s := testutil.NewSpecWithArgs("sleep", "1000") 81 s.Process.OOMScoreAdj = testCase.OOMScoreAdj 82 83 containers, cleanup, err := startContainers(t, []*specs.Spec{s}, []string{id}) 84 if err != nil { 85 t.Fatalf("error starting containers: %v", err) 86 } 87 defer cleanup() 88 89 c := containers[0] 90 91 // Verify the gofer's oom_score_adj 92 if testCase.OOMScoreAdj != nil { 93 goferScore, err := specutils.GetOOMScoreAdj(c.GoferPid) 94 if err != nil { 95 t.Fatalf("error reading gofer oom_score_adj: %v", err) 96 } 97 if goferScore != *testCase.OOMScoreAdj { 98 t.Errorf("gofer oom_score_adj got: %d, want: %d", goferScore, *testCase.OOMScoreAdj) 99 } 100 101 // Verify the sandbox's oom_score_adj. 102 // 103 // The sandbox should be the same for all containers so just use 104 // the first one. 105 sandboxPid := c.Sandbox.Getpid() 106 sandboxScore, err := specutils.GetOOMScoreAdj(sandboxPid) 107 if err != nil { 108 t.Fatalf("error reading sandbox oom_score_adj: %v", err) 109 } 110 if sandboxScore != *testCase.OOMScoreAdj { 111 t.Errorf("sandbox oom_score_adj got: %d, want: %d", sandboxScore, *testCase.OOMScoreAdj) 112 } 113 } 114 }) 115 } 116 } 117 118 // TestOOMScoreAdjMulti tests that oom_score_adj is set properly in a 119 // multi-container sandbox. 120 func TestOOMScoreAdjMulti(t *testing.T) { 121 parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(os.Getppid()) 122 if err != nil { 123 t.Fatalf("getting parent oom_score_adj: %v", err) 124 } 125 126 testCases := []struct { 127 Name string 128 129 // OOMScoreAdj is the oom_score_adj set to the OCI spec. If nil then 130 // no value is set. One value for each container. The first value is the 131 // root container. 132 OOMScoreAdj []*int 133 134 // Expected is the expected oom_score_adj of the sandbox. If nil, then 135 // this value is ignored. 136 Expected *int 137 138 // Remove is a set of container indexes to remove from the sandbox. 139 Remove []int 140 141 // ExpectedAfterRemove is the expected oom_score_adj of the sandbox 142 // after containers are removed. Ignored if nil. 143 ExpectedAfterRemove *int 144 }{ 145 // A single container CRI test case. This should not happen in 146 // practice as there should be at least one container besides the pause 147 // container. However, we include a test case to ensure sane behavior. 148 { 149 Name: "single", 150 OOMScoreAdj: []*int{&highOOMScoreAdj}, 151 Expected: &parentOOMScoreAdj, 152 }, 153 { 154 Name: "multi_no_value", 155 OOMScoreAdj: []*int{nil, nil, nil}, 156 Expected: &parentOOMScoreAdj, 157 }, 158 { 159 Name: "multi_non_nil_root", 160 OOMScoreAdj: []*int{&minOOMScoreAdj, nil, nil}, 161 Expected: &parentOOMScoreAdj, 162 }, 163 { 164 Name: "multi_value", 165 OOMScoreAdj: []*int{&minOOMScoreAdj, &highOOMScoreAdj, &lowOOMScoreAdj}, 166 // The lowest value excluding the root container is expected. 167 Expected: &lowOOMScoreAdj, 168 }, 169 { 170 Name: "multi_min_value", 171 OOMScoreAdj: []*int{&minOOMScoreAdj, &lowOOMScoreAdj}, 172 // The lowest value excluding the root container is expected. 173 Expected: &lowOOMScoreAdj, 174 }, 175 { 176 Name: "multi_max_value", 177 OOMScoreAdj: []*int{&minOOMScoreAdj, &maxOOMScoreAdj, &highOOMScoreAdj}, 178 // The lowest value excluding the root container is expected. 179 Expected: &highOOMScoreAdj, 180 }, 181 { 182 Name: "remove_adjusted", 183 OOMScoreAdj: []*int{&minOOMScoreAdj, &maxOOMScoreAdj, &highOOMScoreAdj}, 184 // The lowest value excluding the root container is expected. 185 Expected: &highOOMScoreAdj, 186 // Remove highOOMScoreAdj container. 187 Remove: []int{2}, 188 ExpectedAfterRemove: &maxOOMScoreAdj, 189 }, 190 { 191 // This test removes all non-root sandboxes with a specified oomScoreAdj. 192 Name: "remove_to_nil", 193 OOMScoreAdj: []*int{&minOOMScoreAdj, nil, &lowOOMScoreAdj}, 194 Expected: &lowOOMScoreAdj, 195 // Remove lowOOMScoreAdj container. 196 Remove: []int{2}, 197 // The oom_score_adj expected after remove is that of the parent process. 198 ExpectedAfterRemove: &parentOOMScoreAdj, 199 }, 200 { 201 Name: "remove_no_effect", 202 OOMScoreAdj: []*int{&minOOMScoreAdj, &maxOOMScoreAdj, &highOOMScoreAdj}, 203 // The lowest value excluding the root container is expected. 204 Expected: &highOOMScoreAdj, 205 // Remove the maxOOMScoreAdj container. 206 Remove: []int{1}, 207 ExpectedAfterRemove: &highOOMScoreAdj, 208 }, 209 } 210 211 for _, testCase := range testCases { 212 t.Run(testCase.Name, func(t *testing.T) { 213 var cmds [][]string 214 var oomScoreAdj []*int 215 var toRemove []string 216 217 for _, oomScore := range testCase.OOMScoreAdj { 218 oomScoreAdj = append(oomScoreAdj, oomScore) 219 cmds = append(cmds, []string{"sleep", "100"}) 220 } 221 222 specs, ids := createSpecs(cmds...) 223 for i, spec := range specs { 224 // Ensure the correct value is set, including no value. 225 spec.Process.OOMScoreAdj = oomScoreAdj[i] 226 227 for _, j := range testCase.Remove { 228 if i == j { 229 toRemove = append(toRemove, ids[i]) 230 } 231 } 232 } 233 234 containers, cleanup, err := startContainers(t, specs, ids) 235 if err != nil { 236 t.Fatalf("error starting containers: %v", err) 237 } 238 defer cleanup() 239 240 for i, c := range containers { 241 if oomScoreAdj[i] != nil { 242 // Verify the gofer's oom_score_adj 243 score, err := specutils.GetOOMScoreAdj(c.GoferPid) 244 if err != nil { 245 t.Fatalf("error reading gofer oom_score_adj: %v", err) 246 } 247 if score != *oomScoreAdj[i] { 248 t.Errorf("gofer oom_score_adj got: %d, want: %d", score, *oomScoreAdj[i]) 249 } 250 } 251 } 252 253 // Verify the sandbox's oom_score_adj. 254 // 255 // The sandbox should be the same for all containers so just use 256 // the first one. 257 sandboxPid := containers[0].Sandbox.Getpid() 258 if testCase.Expected != nil { 259 score, err := specutils.GetOOMScoreAdj(sandboxPid) 260 if err != nil { 261 t.Fatalf("error reading sandbox oom_score_adj: %v", err) 262 } 263 if score != *testCase.Expected { 264 t.Errorf("sandbox oom_score_adj got: %d, want: %d", score, *testCase.Expected) 265 } 266 } 267 268 if len(toRemove) == 0 { 269 return 270 } 271 272 // Remove containers. 273 for _, removeID := range toRemove { 274 for _, c := range containers { 275 if c.ID == removeID { 276 c.Destroy() 277 } 278 } 279 } 280 281 // Check the new adjusted oom_score_adj. 282 if testCase.ExpectedAfterRemove != nil { 283 scoreAfterRemove, err := specutils.GetOOMScoreAdj(sandboxPid) 284 if err != nil { 285 t.Fatalf("error reading sandbox oom_score_adj: %v", err) 286 } 287 if scoreAfterRemove != *testCase.ExpectedAfterRemove { 288 t.Errorf("sandbox oom_score_adj got: %d, want: %d", scoreAfterRemove, *testCase.ExpectedAfterRemove) 289 } 290 } 291 }) 292 } 293 } 294 295 func createSpecs(cmds ...[]string) ([]*specs.Spec, []string) { 296 var specs []*specs.Spec 297 var ids []string 298 rootID := testutil.RandomContainerID() 299 300 for i, cmd := range cmds { 301 spec := testutil.NewSpecWithArgs(cmd...) 302 if i == 0 { 303 spec.Annotations = map[string]string{ 304 specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeSandbox, 305 } 306 ids = append(ids, rootID) 307 } else { 308 spec.Annotations = map[string]string{ 309 specutils.ContainerdContainerTypeAnnotation: specutils.ContainerdContainerTypeContainer, 310 specutils.ContainerdSandboxIDAnnotation: rootID, 311 } 312 ids = append(ids, testutil.RandomContainerID()) 313 } 314 specs = append(specs, spec) 315 } 316 return specs, ids 317 } 318 319 func startContainers(t *testing.T, specs []*specs.Spec, ids []string) ([]*container.Container, func(), error) { 320 var containers []*container.Container 321 322 // All containers must share the same root. 323 rootDir, clean, err := testutil.SetupRootDir() 324 if err != nil { 325 t.Fatalf("error creating root dir: %v", err) 326 } 327 cu := cleanup.Make(clean) 328 defer cu.Clean() 329 330 // Point this to from the configuration. 331 conf := testutil.TestConfig(t) 332 conf.RootDir = rootDir 333 334 for i, spec := range specs { 335 bundleDir, clean, err := testutil.SetupBundleDir(spec) 336 if err != nil { 337 return nil, nil, fmt.Errorf("error setting up bundle: %v", err) 338 } 339 cu.Add(clean) 340 341 args := container.Args{ 342 ID: ids[i], 343 Spec: spec, 344 BundleDir: bundleDir, 345 } 346 cont, err := container.New(conf, args) 347 if err != nil { 348 return nil, nil, fmt.Errorf("error creating container: %v", err) 349 } 350 containers = append(containers, cont) 351 352 if err := cont.Start(conf); err != nil { 353 return nil, nil, fmt.Errorf("error starting container: %v", err) 354 } 355 } 356 357 return containers, cu.Release(), nil 358 }