github.com/containerd/Containerd@v1.4.13/oci/spec_test.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package oci
    18  
    19  import (
    20  	"context"
    21  	"runtime"
    22  	"testing"
    23  
    24  	"github.com/containerd/containerd/containers"
    25  	"github.com/containerd/containerd/namespaces"
    26  	specs "github.com/opencontainers/runtime-spec/specs-go"
    27  )
    28  
    29  func TestGenerateSpec(t *testing.T) {
    30  	t.Parallel()
    31  
    32  	ctx := namespaces.WithNamespace(context.Background(), "testing")
    33  	s, err := GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()})
    34  	if err != nil {
    35  		t.Fatal(err)
    36  	}
    37  	if s == nil {
    38  		t.Fatal("GenerateSpec() returns a nil spec")
    39  	}
    40  
    41  	if runtime.GOOS != "windows" {
    42  		// check for matching caps
    43  		defaults := defaultUnixCaps()
    44  		for _, cl := range [][]string{
    45  			s.Process.Capabilities.Bounding,
    46  			s.Process.Capabilities.Permitted,
    47  			s.Process.Capabilities.Inheritable,
    48  			s.Process.Capabilities.Effective,
    49  		} {
    50  			for i := 0; i < len(defaults); i++ {
    51  				if cl[i] != defaults[i] {
    52  					t.Errorf("cap at %d does not match set %q != %q", i, defaults[i], cl[i])
    53  				}
    54  			}
    55  		}
    56  
    57  		// check default namespaces
    58  		defaultNS := defaultUnixNamespaces()
    59  		for i, ns := range s.Linux.Namespaces {
    60  			if defaultNS[i] != ns {
    61  				t.Errorf("ns at %d does not match set %q != %q", i, defaultNS[i], ns)
    62  			}
    63  		}
    64  	} else {
    65  		if s.Windows == nil {
    66  			t.Fatal("Windows section of spec not filled in on Windows platform")
    67  		}
    68  	}
    69  
    70  	// test that we don't have tty set
    71  	if s.Process.Terminal {
    72  		t.Error("terminal set on default process")
    73  	}
    74  }
    75  
    76  func TestGenerateSpecWithPlatform(t *testing.T) {
    77  	t.Parallel()
    78  
    79  	ctx := namespaces.WithNamespace(context.Background(), "testing")
    80  	platforms := []string{"windows/amd64", "linux/amd64"}
    81  	for _, p := range platforms {
    82  		t.Logf("Testing platform: %s", p)
    83  		s, err := GenerateSpecWithPlatform(ctx, nil, p, &containers.Container{ID: t.Name()})
    84  		if err != nil {
    85  			t.Fatalf("failed to generate spec: %v", err)
    86  		}
    87  
    88  		if s.Root == nil {
    89  			t.Fatal("expected non nil Root section.")
    90  		}
    91  		if s.Process == nil {
    92  			t.Fatal("expected non nil Process section.")
    93  		}
    94  		if p == "windows/amd64" {
    95  			if s.Linux != nil {
    96  				t.Fatal("expected nil Linux section")
    97  			}
    98  			if s.Windows == nil {
    99  				t.Fatal("expected non nil Windows section")
   100  			}
   101  		} else {
   102  			if s.Linux == nil {
   103  				t.Fatal("expected non nil Linux section")
   104  			}
   105  			if runtime.GOOS == "windows" && s.Windows == nil {
   106  				t.Fatal("expected non nil Windows section for LCOW")
   107  			} else if runtime.GOOS != "windows" && s.Windows != nil {
   108  				t.Fatal("expected nil Windows section")
   109  			}
   110  		}
   111  	}
   112  }
   113  
   114  func TestSpecWithTTY(t *testing.T) {
   115  	t.Parallel()
   116  
   117  	ctx := namespaces.WithNamespace(context.Background(), "testing")
   118  	s, err := GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()}, WithTTY)
   119  	if err != nil {
   120  		t.Fatal(err)
   121  	}
   122  	if !s.Process.Terminal {
   123  		t.Error("terminal net set WithTTY()")
   124  	}
   125  	if runtime.GOOS != "windows" {
   126  		v := s.Process.Env[len(s.Process.Env)-1]
   127  		if v != "TERM=xterm" {
   128  			t.Errorf("xterm not set in env for TTY")
   129  		}
   130  	} else {
   131  		if len(s.Process.Env) != 0 {
   132  			t.Fatal("Windows process args should be empty by default")
   133  		}
   134  	}
   135  }
   136  
   137  func TestWithLinuxNamespace(t *testing.T) {
   138  	t.Parallel()
   139  
   140  	ctx := namespaces.WithNamespace(context.Background(), "testing")
   141  	replacedNS := specs.LinuxNamespace{Type: specs.NetworkNamespace, Path: "/var/run/netns/test"}
   142  
   143  	var s *specs.Spec
   144  	var err error
   145  	if runtime.GOOS != "windows" {
   146  		s, err = GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()}, WithLinuxNamespace(replacedNS))
   147  	} else {
   148  		s, err = GenerateSpecWithPlatform(ctx, nil, "linux/amd64", &containers.Container{ID: t.Name()}, WithLinuxNamespace(replacedNS))
   149  	}
   150  	if err != nil {
   151  		t.Fatal(err)
   152  	}
   153  
   154  	defaultNS := defaultUnixNamespaces()
   155  	found := false
   156  	for i, ns := range s.Linux.Namespaces {
   157  		if ns == replacedNS && !found {
   158  			found = true
   159  			continue
   160  		}
   161  		if defaultNS[i] != ns {
   162  			t.Errorf("ns at %d does not match set %q != %q", i, defaultNS[i], ns)
   163  		}
   164  	}
   165  }
   166  
   167  func TestWithCapabilities(t *testing.T) {
   168  	t.Parallel()
   169  
   170  	ctx := namespaces.WithNamespace(context.Background(), "testing")
   171  
   172  	opts := []SpecOpts{
   173  		WithCapabilities([]string{"CAP_SYS_ADMIN"}),
   174  	}
   175  	var s *specs.Spec
   176  	var err error
   177  	if runtime.GOOS != "windows" {
   178  		s, err = GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()}, opts...)
   179  	} else {
   180  		s, err = GenerateSpecWithPlatform(ctx, nil, "linux/amd64", &containers.Container{ID: t.Name()}, opts...)
   181  	}
   182  	if err != nil {
   183  		t.Fatal(err)
   184  	}
   185  
   186  	if len(s.Process.Capabilities.Bounding) != 1 || s.Process.Capabilities.Bounding[0] != "CAP_SYS_ADMIN" {
   187  		t.Error("Unexpected capabilities set")
   188  	}
   189  	if len(s.Process.Capabilities.Effective) != 1 || s.Process.Capabilities.Effective[0] != "CAP_SYS_ADMIN" {
   190  		t.Error("Unexpected capabilities set")
   191  	}
   192  	if len(s.Process.Capabilities.Permitted) != 1 || s.Process.Capabilities.Permitted[0] != "CAP_SYS_ADMIN" {
   193  		t.Error("Unexpected capabilities set")
   194  	}
   195  	if len(s.Process.Capabilities.Inheritable) != 1 || s.Process.Capabilities.Inheritable[0] != "CAP_SYS_ADMIN" {
   196  		t.Error("Unexpected capabilities set")
   197  	}
   198  }
   199  
   200  func TestWithCapabilitiesNil(t *testing.T) {
   201  	t.Parallel()
   202  
   203  	ctx := namespaces.WithNamespace(context.Background(), "testing")
   204  
   205  	s, err := GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()},
   206  		WithCapabilities(nil),
   207  	)
   208  	if err != nil {
   209  		t.Fatal(err)
   210  	}
   211  
   212  	if len(s.Process.Capabilities.Bounding) != 0 {
   213  		t.Errorf("Unexpected capabilities set: length is non zero (%d)", len(s.Process.Capabilities.Bounding))
   214  	}
   215  	if len(s.Process.Capabilities.Effective) != 0 {
   216  		t.Errorf("Unexpected capabilities set: length is non zero (%d)", len(s.Process.Capabilities.Effective))
   217  	}
   218  	if len(s.Process.Capabilities.Permitted) != 0 {
   219  		t.Errorf("Unexpected capabilities set: length is non zero (%d)", len(s.Process.Capabilities.Permitted))
   220  	}
   221  	if len(s.Process.Capabilities.Inheritable) != 0 {
   222  		t.Errorf("Unexpected capabilities set: length is non zero (%d)", len(s.Process.Capabilities.Inheritable))
   223  	}
   224  }
   225  
   226  func TestPopulateDefaultWindowsSpec(t *testing.T) {
   227  	var (
   228  		c   = containers.Container{ID: "TestWithDefaultSpec"}
   229  		ctx = namespaces.WithNamespace(context.Background(), "test")
   230  	)
   231  	var expected Spec
   232  
   233  	populateDefaultWindowsSpec(ctx, &expected, c.ID)
   234  	if expected.Windows == nil {
   235  		t.Error("Cannot populate windows Spec")
   236  	}
   237  }
   238  
   239  func TestPopulateDefaultUnixSpec(t *testing.T) {
   240  	var (
   241  		c   = containers.Container{ID: "TestWithDefaultSpec"}
   242  		ctx = namespaces.WithNamespace(context.Background(), "test")
   243  	)
   244  	var expected Spec
   245  
   246  	populateDefaultUnixSpec(ctx, &expected, c.ID)
   247  	if expected.Linux == nil {
   248  		t.Error("Cannot populate Unix Spec")
   249  	}
   250  }
   251  
   252  func TestWithPrivileged(t *testing.T) {
   253  	t.Parallel()
   254  
   255  	ctx := namespaces.WithNamespace(context.Background(), "testing")
   256  
   257  	opts := []SpecOpts{
   258  		WithCapabilities(nil),
   259  		WithMounts([]specs.Mount{
   260  			{Type: "cgroup", Destination: "/sys/fs/cgroup", Options: []string{"ro"}},
   261  		}),
   262  		WithPrivileged,
   263  	}
   264  	var s *specs.Spec
   265  	var err error
   266  	if runtime.GOOS != "windows" {
   267  		s, err = GenerateSpec(ctx, nil, &containers.Container{ID: t.Name()}, opts...)
   268  	} else {
   269  		s, err = GenerateSpecWithPlatform(ctx, nil, "linux/amd64", &containers.Container{ID: t.Name()}, opts...)
   270  	}
   271  	if err != nil {
   272  		t.Fatal(err)
   273  	}
   274  
   275  	if len(s.Process.Capabilities.Bounding) == 0 {
   276  		t.Error("Expected capabilities to be set with privileged")
   277  	}
   278  
   279  	var foundSys, foundCgroup bool
   280  	for _, m := range s.Mounts {
   281  		switch m.Type {
   282  		case "sysfs":
   283  			foundSys = true
   284  			var found bool
   285  			for _, o := range m.Options {
   286  				switch o {
   287  				case "ro":
   288  					t.Errorf("Found unexpected read only %s mount", m.Type)
   289  				case "rw":
   290  					found = true
   291  				}
   292  			}
   293  			if !found {
   294  				t.Errorf("Did not find rw mount option for %s", m.Type)
   295  			}
   296  		case "cgroup":
   297  			foundCgroup = true
   298  			var found bool
   299  			for _, o := range m.Options {
   300  				switch o {
   301  				case "ro":
   302  					t.Errorf("Found unexpected read only %s mount", m.Type)
   303  				case "rw":
   304  					found = true
   305  				}
   306  			}
   307  			if !found {
   308  				t.Errorf("Did not find rw mount option for %s", m.Type)
   309  			}
   310  		}
   311  	}
   312  	if !foundSys {
   313  		t.Error("Did not find mount for sysfs")
   314  	}
   315  	if !foundCgroup {
   316  		t.Error("Did not find mount for cgroupfs")
   317  	}
   318  }