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  }