github.com/castai/kvisor@v1.7.1-0.20240516114728-b3572a2607b5/pkg/proc/proc_test.go (about)

     1  package proc
     2  
     3  import (
     4  	"fmt"
     5  	"syscall"
     6  	"testing"
     7  	"testing/fstest"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/require"
    11  )
    12  
    13  func TestLoadMountNSOldestProcesses(t *testing.T) {
    14  	t.Run("should return process if only one in namespace", func(t *testing.T) {
    15  		r := require.New(t)
    16  
    17  		wantedPID := PID(12)
    18  		wantedMountNSID := NamespaceID(10)
    19  
    20  		procFS := fstest.MapFS{}
    21  		procFS[fmt.Sprintf("%d/stat", wantedPID)] = buildStatMapFile(0)
    22  		procFS[fmt.Sprintf("%d/ns/mnt", wantedPID)] = buildNamespaceMapFile(wantedMountNSID)
    23  
    24  		procFS["99/stat"] = buildStatMapFile(99)
    25  		procFS["99/ns/mnt"] = buildNamespaceMapFile(99)
    26  
    27  		proc := Proc{procFS: procFS}
    28  
    29  		result, err := proc.LoadMountNSOldestProcesses()
    30  		if err != nil {
    31  			r.NoError(err)
    32  		}
    33  
    34  		r.Equal(wantedPID, result[wantedMountNSID])
    35  	})
    36  
    37  	t.Run("should return oldest process if multiple in namespace", func(t *testing.T) {
    38  		r := require.New(t)
    39  
    40  		oldestPID := PID(12)
    41  		mountNSID := NamespaceID(10)
    42  
    43  		procFS := fstest.MapFS{}
    44  		procFS[fmt.Sprintf("%d/stat", oldestPID)] = buildStatMapFile(0)
    45  		procFS[fmt.Sprintf("%d/ns/mnt", oldestPID)] = buildNamespaceMapFile(mountNSID)
    46  
    47  		procFS["99/stat"] = buildStatMapFile(99)
    48  		procFS["99/ns/mnt"] = buildNamespaceMapFile(mountNSID)
    49  
    50  		proc := Proc{procFS: procFS}
    51  
    52  		result, err := proc.LoadMountNSOldestProcesses()
    53  		if err != nil {
    54  			r.NoError(err)
    55  		}
    56  
    57  		r.Equal(oldestPID, result[mountNSID])
    58  	})
    59  
    60  	t.Run("should not error if namespaces does not exist for one process", func(t *testing.T) {
    61  		r := require.New(t)
    62  
    63  		wantedPID := PID(12)
    64  		wantedNSID := NamespaceID(10)
    65  
    66  		procFS := fstest.MapFS{}
    67  		procFS[fmt.Sprintf("%d/stat", wantedPID)] = buildStatMapFile(0)
    68  		procFS[fmt.Sprintf("%d/ns/mnt", wantedPID)] = buildNamespaceMapFile(wantedNSID)
    69  
    70  		procFS["99/stat"] = buildStatMapFile(99)
    71  
    72  		proc := Proc{procFS: procFS}
    73  
    74  		result, err := proc.LoadMountNSOldestProcesses()
    75  		if err != nil {
    76  			r.NoError(err)
    77  		}
    78  
    79  		r.Equal(wantedPID, result[wantedNSID])
    80  	})
    81  }
    82  
    83  func buildStatMapFile(age int) *fstest.MapFile {
    84  	return &fstest.MapFile{
    85  		Data:    []byte(fmt.Sprintf("1 (systemd) S 0 1 1 0 -1 4194560 49750 11902233 171 14858 475 453 49354 59480 20 0 1 0 %d 172085248 2208 18446744073709551615 1 1 0 0 0 0 671173123 4096 1260 0 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0", age)),
    86  		Mode:    0666,
    87  		ModTime: time.Now(),
    88  	}
    89  }
    90  
    91  func buildNamespaceMapFile(id NamespaceID) *fstest.MapFile {
    92  	return &fstest.MapFile{
    93  		Data:    []byte{},
    94  		Mode:    0666,
    95  		ModTime: time.Now(),
    96  		Sys: &syscall.Stat_t{
    97  			Ino: uint64(id),
    98  		},
    99  	}
   100  }
   101  
   102  func TestGetNSForPID(t *testing.T) {
   103  	type testCase struct {
   104  		title           string
   105  		namespaces      map[NamespaceType]uint64
   106  		wantedNamespace NamespaceType
   107  		expectedID      uint64
   108  		expectError     bool
   109  	}
   110  
   111  	pidNSID := uint64(20)
   112  	mntNSID := uint64(90)
   113  
   114  	testCases := []testCase{
   115  		{
   116  			title: "should return requested namespace id",
   117  			namespaces: map[NamespaceType]uint64{
   118  				MountNamespace: mntNSID,
   119  				PIDNamespace:   pidNSID,
   120  			},
   121  			wantedNamespace: MountNamespace,
   122  			expectedID:      mntNSID,
   123  		},
   124  		{
   125  			title: "should fail when requesting unknown namespace type",
   126  			namespaces: map[NamespaceType]uint64{
   127  				MountNamespace: mntNSID,
   128  				PIDNamespace:   pidNSID,
   129  			},
   130  			wantedNamespace: "non existing",
   131  			expectError:     true,
   132  		},
   133  	}
   134  	testPID := PID(22)
   135  
   136  	for _, test := range testCases {
   137  		t.Run(test.title, func(t *testing.T) {
   138  			r := require.New(t)
   139  
   140  			procFS := buildTestNSProcFS(testPID, test.namespaces)
   141  			proc := Proc{procFS: procFS}
   142  
   143  			result, err := proc.GetNSForPID(testPID, test.wantedNamespace)
   144  			if test.expectError {
   145  				r.Error(err)
   146  				return
   147  			} else {
   148  				r.NoError(err)
   149  			}
   150  
   151  			r.Equal(test.expectedID, result)
   152  		})
   153  	}
   154  }
   155  
   156  func buildTestNSProcFS(pid PID, namespaces map[NamespaceType]uint64) ProcFS {
   157  	if namespaces == nil {
   158  		return fstest.MapFS{}
   159  	}
   160  
   161  	result := fstest.MapFS{}
   162  
   163  	for namespace, id := range namespaces {
   164  		result[fmt.Sprintf("%d/ns/%s", pid, namespace)] = &fstest.MapFile{
   165  			Data:    []byte{},
   166  			Mode:    0666,
   167  			ModTime: time.Now(),
   168  			Sys: &syscall.Stat_t{
   169  				Ino: id,
   170  			},
   171  		}
   172  	}
   173  
   174  	return result
   175  }
   176  
   177  func TestGetProcessStartTime(t *testing.T) {
   178  	type testCase struct {
   179  		title             string
   180  		statFileData      []byte
   181  		expectedStartTime uint64
   182  		expectError       bool
   183  	}
   184  
   185  	testCases := []testCase{
   186  		{
   187  			title:             "parse simple stat file",
   188  			statFileData:      []byte("1 (systemd) S 0 1 1 0 -1 4194560 49750 11902233 171 14858 475 453 49354 59480 20 0 1 0 0 172085248 2208 18446744073709551615 1 1 0 0 0 0 671173123 4096 1260 0 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0"),
   189  			expectedStartTime: 0,
   190  		},
   191  		{
   192  			title:             "parse simple stat file with start time",
   193  			statFileData:      []byte("1 (bash) S 0 1 1 0 -1 4194560 49750 11902233 171 14858 475 453 49354 59480 20 0 1 0 20 172085248 2208 18446744073709551615 1 1 0 0 0 0 671173123 4096 1260 0 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0"),
   194  			expectedStartTime: 20,
   195  		},
   196  		{
   197  			title:             "parse simple stat file with space in comm",
   198  			statFileData:      []byte("1 (bash 1) S 0 1 1 0 -1 4194560 49750 11902233 171 14858 475 453 49354 59480 20 0 1 0 20 172085248 2208 18446744073709551615 1 1 0 0 0 0 671173123 4096 1260 0 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0"),
   199  			expectedStartTime: 20,
   200  		},
   201  		{
   202  			title:             "parse simple stat file with extremely long runtime",
   203  			statFileData:      []byte("1 (bash 1) S 0 1 1 0 -1 4194560 49750 11902233 171 14858 475 453 49354 59480 20 0 1 0 99999999 172085248 2208 18446744073709551615 1 1 0 0 0 0 671173123 4096 1260 0 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0"),
   204  			expectedStartTime: 99999999,
   205  		},
   206  		{
   207  			title:       "should fail with error if stat file is missing",
   208  			expectError: true,
   209  		},
   210  		{
   211  			title:        "should fail with error if stat file does not have enough fields",
   212  			statFileData: []byte("1 (bash 1) S 0 1 1 0 "),
   213  			expectError:  true,
   214  		},
   215  	}
   216  	testPID := PID(22)
   217  
   218  	for _, test := range testCases {
   219  		t.Run(test.title, func(t *testing.T) {
   220  			r := require.New(t)
   221  
   222  			procFS := buildTestProcStatFileFS(testPID, test.statFileData)
   223  			proc := Proc{procFS: procFS}
   224  
   225  			result, err := proc.GetProcessStartTime(testPID)
   226  			if test.expectError {
   227  				r.Error(err)
   228  				return
   229  			} else {
   230  				r.NoError(err)
   231  			}
   232  
   233  			r.Equal(test.expectedStartTime, result)
   234  		})
   235  	}
   236  }
   237  
   238  func buildTestProcStatFileFS(pid PID, statFileData []byte) ProcFS {
   239  	if statFileData == nil {
   240  		return fstest.MapFS{}
   241  	}
   242  
   243  	return fstest.MapFS{
   244  		fmt.Sprintf("%d/stat", pid): &fstest.MapFile{
   245  			Data:    statFileData,
   246  			Mode:    0666,
   247  			ModTime: time.Now(),
   248  		},
   249  	}
   250  }