gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/specutils/specutils_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 specutils
    16  
    17  import (
    18  	"fmt"
    19  	"os/exec"
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  
    24  	specs "github.com/opencontainers/runtime-spec/specs-go"
    25  )
    26  
    27  func TestWaitForReadyHappy(t *testing.T) {
    28  	cmd := exec.Command("/bin/sleep", "1000")
    29  	if err := cmd.Start(); err != nil {
    30  		t.Fatalf("cmd.Start() failed, err: %v", err)
    31  	}
    32  	defer func() { _ = cmd.Wait() }()
    33  
    34  	var count int
    35  	err := WaitForReady(cmd.Process.Pid, 5*time.Second, func() (bool, error) {
    36  		if count < 3 {
    37  			count++
    38  			return false, nil
    39  		}
    40  		return true, nil
    41  	})
    42  	if err != nil {
    43  		t.Errorf("ProcessWaitReady got: %v, expected: nil", err)
    44  	}
    45  	if err := cmd.Process.Kill(); err != nil {
    46  		t.Errorf("cmd.ProcessKill(): %v", err)
    47  	}
    48  }
    49  
    50  func TestWaitForReadyFail(t *testing.T) {
    51  	cmd := exec.Command("/bin/sleep", "1000")
    52  	if err := cmd.Start(); err != nil {
    53  		t.Fatalf("cmd.Start() failed, err: %v", err)
    54  	}
    55  	defer func() { _ = cmd.Wait() }()
    56  
    57  	var count int
    58  	err := WaitForReady(cmd.Process.Pid, 5*time.Second, func() (bool, error) {
    59  		if count < 3 {
    60  			count++
    61  			return false, nil
    62  		}
    63  		return false, fmt.Errorf("fake error")
    64  	})
    65  	if err == nil {
    66  		t.Errorf("ProcessWaitReady got: nil, expected: error")
    67  	}
    68  	if err := cmd.Process.Kill(); err != nil {
    69  		t.Errorf("cmd.ProcessKill(): %v", err)
    70  	}
    71  }
    72  
    73  func TestWaitForReadyNotRunning(t *testing.T) {
    74  	cmd := exec.Command("/bin/true")
    75  	if err := cmd.Start(); err != nil {
    76  		t.Fatalf("cmd.Start() failed, err: %v", err)
    77  	}
    78  	defer func() { _ = cmd.Wait() }()
    79  
    80  	err := WaitForReady(cmd.Process.Pid, 5*time.Second, func() (bool, error) {
    81  		return false, nil
    82  	})
    83  	if err != nil && !strings.Contains(err.Error(), "terminated") {
    84  		t.Errorf("ProcessWaitReady got: %v, expected: process terminated", err)
    85  	}
    86  	if err == nil {
    87  		t.Errorf("ProcessWaitReady incorrectly succeeded")
    88  	}
    89  }
    90  
    91  func TestWaitForReadyTimeout(t *testing.T) {
    92  	cmd := exec.Command("/bin/sleep", "1000")
    93  	if err := cmd.Start(); err != nil {
    94  		t.Fatalf("cmd.Start() failed, err: %v", err)
    95  	}
    96  	defer func() { _ = cmd.Wait() }()
    97  
    98  	err := WaitForReady(cmd.Process.Pid, 50*time.Millisecond, func() (bool, error) {
    99  		return false, nil
   100  	})
   101  	if err == nil || !strings.Contains(err.Error(), "not running yet") {
   102  		t.Errorf("ProcessWaitReady got: %v, expected: not running yet", err)
   103  	}
   104  	if err := cmd.Process.Kill(); err != nil {
   105  		t.Errorf("cmd.ProcessKill(): %v", err)
   106  	}
   107  }
   108  
   109  func TestSpecInvalid(t *testing.T) {
   110  	for _, test := range []struct {
   111  		name  string
   112  		spec  specs.Spec
   113  		error string
   114  	}{
   115  		{
   116  			name: "valid",
   117  			spec: specs.Spec{
   118  				Root: &specs.Root{Path: "/"},
   119  				Process: &specs.Process{
   120  					Args: []string{"/bin/true"},
   121  				},
   122  				Mounts: []specs.Mount{
   123  					{
   124  						Source:      "src",
   125  						Destination: "/dst",
   126  					},
   127  				},
   128  			},
   129  			error: "",
   130  		},
   131  		{
   132  			name: "valid+warning",
   133  			spec: specs.Spec{
   134  				Root: &specs.Root{Path: "/"},
   135  				Process: &specs.Process{
   136  					Args: []string{"/bin/true"},
   137  					// This is normally set by docker and will just cause warnings to be logged.
   138  					ApparmorProfile: "someprofile",
   139  				},
   140  				// This is normally set by docker and will just cause warnings to be logged.
   141  				Linux: &specs.Linux{Seccomp: &specs.LinuxSeccomp{}},
   142  			},
   143  			error: "",
   144  		},
   145  		{
   146  			name: "no root",
   147  			spec: specs.Spec{
   148  				Process: &specs.Process{
   149  					Args: []string{"/bin/true"},
   150  				},
   151  			},
   152  			error: "must be defined",
   153  		},
   154  		{
   155  			name: "empty root",
   156  			spec: specs.Spec{
   157  				Root: &specs.Root{},
   158  				Process: &specs.Process{
   159  					Args: []string{"/bin/true"},
   160  				},
   161  			},
   162  			error: "must be defined",
   163  		},
   164  		{
   165  			name: "no process",
   166  			spec: specs.Spec{
   167  				Root: &specs.Root{Path: "/"},
   168  			},
   169  			error: "must be defined",
   170  		},
   171  		{
   172  			name: "empty args",
   173  			spec: specs.Spec{
   174  				Root:    &specs.Root{Path: "/"},
   175  				Process: &specs.Process{},
   176  			},
   177  			error: "must be defined",
   178  		},
   179  		{
   180  			name: "selinux",
   181  			spec: specs.Spec{
   182  				Root: &specs.Root{Path: "/"},
   183  				Process: &specs.Process{
   184  					Args:         []string{"/bin/true"},
   185  					SelinuxLabel: "somelabel",
   186  				},
   187  			},
   188  			error: "is not supported",
   189  		},
   190  		{
   191  			name: "solaris",
   192  			spec: specs.Spec{
   193  				Root: &specs.Root{Path: "/"},
   194  				Process: &specs.Process{
   195  					Args: []string{"/bin/true"},
   196  				},
   197  				Solaris: &specs.Solaris{},
   198  			},
   199  			error: "is not supported",
   200  		},
   201  		{
   202  			name: "windows",
   203  			spec: specs.Spec{
   204  				Root: &specs.Root{Path: "/"},
   205  				Process: &specs.Process{
   206  					Args: []string{"/bin/true"},
   207  				},
   208  				Windows: &specs.Windows{},
   209  			},
   210  			error: "is not supported",
   211  		},
   212  		{
   213  			name: "relative mount destination",
   214  			spec: specs.Spec{
   215  				Root: &specs.Root{Path: "/"},
   216  				Process: &specs.Process{
   217  					Args: []string{"/bin/true"},
   218  				},
   219  				Mounts: []specs.Mount{
   220  					{
   221  						Source:      "src",
   222  						Destination: "dst",
   223  					},
   224  				},
   225  			},
   226  			error: "must be an absolute path",
   227  		},
   228  		{
   229  			name: "invalid mount option",
   230  			spec: specs.Spec{
   231  				Root: &specs.Root{Path: "/"},
   232  				Process: &specs.Process{
   233  					Args: []string{"/bin/true"},
   234  				},
   235  				Mounts: []specs.Mount{
   236  					{
   237  						Source:      "/src",
   238  						Destination: "/dst",
   239  						Type:        "bind",
   240  						Options:     []string{"shared"},
   241  					},
   242  				},
   243  			},
   244  			error: "is not supported",
   245  		},
   246  		{
   247  			name: "invalid rootfs propagation",
   248  			spec: specs.Spec{
   249  				Root: &specs.Root{Path: "/"},
   250  				Process: &specs.Process{
   251  					Args: []string{"/bin/true"},
   252  				},
   253  				Linux: &specs.Linux{
   254  					RootfsPropagation: "foo",
   255  				},
   256  			},
   257  			error: "root mount propagation option must specify private or slave",
   258  		},
   259  	} {
   260  		err := ValidateSpec(&test.spec)
   261  		if len(test.error) == 0 {
   262  			if err != nil {
   263  				t.Errorf("ValidateSpec(%q) failed, err: %v", test.name, err)
   264  			}
   265  		} else {
   266  			if err == nil || !strings.Contains(err.Error(), test.error) {
   267  				t.Errorf("ValidateSpec(%q) wrong error, got: %v, want: .*%s.*", test.name, err, test.error)
   268  			}
   269  		}
   270  	}
   271  }
   272  
   273  func TestSeccomp(t *testing.T) {
   274  	const containerName = "cont1"
   275  	for _, tc := range []struct {
   276  		name           string
   277  		spec           specs.Spec
   278  		seccompPresent bool
   279  	}{
   280  		{
   281  			name:           "seccomp set",
   282  			seccompPresent: true,
   283  			spec: specs.Spec{
   284  				Annotations: map[string]string{
   285  					annotationContainerName: containerName,
   286  				},
   287  				Linux: &specs.Linux{
   288  					Seccomp: &specs.LinuxSeccomp{},
   289  				},
   290  			},
   291  		},
   292  		{
   293  			name:           "another container",
   294  			seccompPresent: true,
   295  			spec: specs.Spec{
   296  				Annotations: map[string]string{
   297  					annotationContainerName:     containerName,
   298  					annotationSeccomp + "cont2": annotationSeccompRuntimeDefault,
   299  				},
   300  				Linux: &specs.Linux{
   301  					Seccomp: &specs.LinuxSeccomp{},
   302  				},
   303  			},
   304  		},
   305  		{
   306  			name:           "not RuntimeDefault",
   307  			seccompPresent: true,
   308  			spec: specs.Spec{
   309  				Annotations: map[string]string{
   310  					annotationContainerName:           containerName,
   311  					annotationSeccomp + containerName: "foobar",
   312  				},
   313  				Linux: &specs.Linux{
   314  					Seccomp: &specs.LinuxSeccomp{},
   315  				},
   316  			},
   317  		},
   318  		{
   319  			name:           "not RuntimeDefault many names",
   320  			seccompPresent: true,
   321  			spec: specs.Spec{
   322  				Annotations: map[string]string{
   323  					annotationContainerName:           containerName,
   324  					annotationSeccomp + containerName: "foobar",
   325  					annotationSeccomp + "cont2":       annotationSeccompRuntimeDefault,
   326  				},
   327  				Linux: &specs.Linux{
   328  					Seccomp: &specs.LinuxSeccomp{},
   329  				},
   330  			},
   331  		},
   332  		{
   333  			name: "remove",
   334  			spec: specs.Spec{
   335  				Annotations: map[string]string{
   336  					annotationContainerName:           containerName,
   337  					annotationSeccomp + containerName: annotationSeccompRuntimeDefault,
   338  				},
   339  				Linux: &specs.Linux{
   340  					Seccomp: &specs.LinuxSeccomp{},
   341  				},
   342  			},
   343  		},
   344  		{
   345  			name: "remove many names",
   346  			spec: specs.Spec{
   347  				Annotations: map[string]string{
   348  					annotationContainerName:           containerName,
   349  					annotationSeccomp + containerName: annotationSeccompRuntimeDefault,
   350  					annotationSeccomp + "cont2":       "foobar",
   351  				},
   352  				Linux: &specs.Linux{
   353  					Seccomp: &specs.LinuxSeccomp{},
   354  				},
   355  			},
   356  		},
   357  		{
   358  			name: "remove-nonexistent",
   359  			spec: specs.Spec{
   360  				Annotations: map[string]string{
   361  					annotationSeccomp + containerName: annotationSeccompRuntimeDefault,
   362  				},
   363  			},
   364  		},
   365  		{
   366  			name: "empty",
   367  		},
   368  	} {
   369  		t.Run(tc.name, func(t *testing.T) {
   370  			tc.spec.Root = &specs.Root{}
   371  			fixSpec(&tc.spec, "", nil)
   372  			if tc.seccompPresent {
   373  				if tc.spec.Linux == nil || tc.spec.Linux.Seccomp == nil {
   374  					t.Errorf("seccomp is not in the spec: %+v", tc.spec)
   375  				}
   376  			} else if tc.spec.Linux != nil && tc.spec.Linux.Seccomp != nil {
   377  				t.Errorf("seccomp is in the spec: %+v", tc.spec)
   378  			}
   379  		})
   380  	}
   381  }