github.com/demonoid81/containerd@v1.3.4/oci/spec_opts_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  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"io/ioutil"
    26  	"log"
    27  	"os"
    28  	"reflect"
    29  	"runtime"
    30  	"strings"
    31  	"testing"
    32  
    33  	"github.com/containerd/containerd/content"
    34  	"github.com/opencontainers/go-digest"
    35  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    36  
    37  	"github.com/containerd/containerd/containers"
    38  	"github.com/containerd/containerd/namespaces"
    39  	specs "github.com/opencontainers/runtime-spec/specs-go"
    40  )
    41  
    42  type blob []byte
    43  
    44  func (b blob) ReadAt(p []byte, off int64) (int, error) {
    45  	if off >= int64(len(b)) {
    46  		return 0, io.EOF
    47  	}
    48  	return copy(p, b[off:]), nil
    49  }
    50  
    51  func (b blob) Close() error {
    52  	return nil
    53  }
    54  
    55  func (b blob) Size() int64 {
    56  	return int64(len(b))
    57  }
    58  
    59  type fakeImage struct {
    60  	config ocispec.Descriptor
    61  	blobs  map[string]blob
    62  }
    63  
    64  func newFakeImage(config ocispec.Image) (Image, error) {
    65  	configBlob, err := json.Marshal(config)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	configDescriptor := ocispec.Descriptor{
    70  		MediaType: ocispec.MediaTypeImageConfig,
    71  		Digest:    digest.NewDigestFromBytes(digest.SHA256, configBlob),
    72  	}
    73  
    74  	return fakeImage{
    75  		config: configDescriptor,
    76  		blobs: map[string]blob{
    77  			configDescriptor.Digest.String(): configBlob,
    78  		},
    79  	}, nil
    80  }
    81  
    82  func (i fakeImage) Config(ctx context.Context) (ocispec.Descriptor, error) {
    83  	return i.config, nil
    84  }
    85  
    86  func (i fakeImage) ContentStore() content.Store {
    87  	return i
    88  }
    89  
    90  func (i fakeImage) ReaderAt(ctx context.Context, dec ocispec.Descriptor) (content.ReaderAt, error) {
    91  	blob, found := i.blobs[dec.Digest.String()]
    92  	if !found {
    93  		return nil, errors.New("not found")
    94  	}
    95  	return blob, nil
    96  }
    97  
    98  func (i fakeImage) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {
    99  	return content.Info{}, errors.New("not implemented")
   100  }
   101  
   102  func (i fakeImage) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
   103  	return content.Info{}, errors.New("not implemented")
   104  }
   105  
   106  func (i fakeImage) Walk(ctx context.Context, fn content.WalkFunc, filters ...string) error {
   107  	return errors.New("not implemented")
   108  }
   109  
   110  func (i fakeImage) Delete(ctx context.Context, dgst digest.Digest) error {
   111  	return errors.New("not implemented")
   112  }
   113  
   114  func (i fakeImage) Status(ctx context.Context, ref string) (content.Status, error) {
   115  	return content.Status{}, errors.New("not implemented")
   116  }
   117  
   118  func (i fakeImage) ListStatuses(ctx context.Context, filters ...string) ([]content.Status, error) {
   119  	return nil, errors.New("not implemented")
   120  }
   121  
   122  func (i fakeImage) Abort(ctx context.Context, ref string) error {
   123  	return errors.New("not implemented")
   124  }
   125  
   126  func (i fakeImage) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
   127  	return nil, errors.New("not implemented")
   128  }
   129  
   130  func TestReplaceOrAppendEnvValues(t *testing.T) {
   131  	t.Parallel()
   132  
   133  	defaults := []string{
   134  		"o=ups", "p=$e", "x=foo", "y=boo", "z", "t=",
   135  	}
   136  	overrides := []string{
   137  		"x=bar", "y", "a=42", "o=", "e", "s=",
   138  	}
   139  	expected := []string{
   140  		"o=", "p=$e", "x=bar", "z", "t=", "a=42", "s=",
   141  	}
   142  
   143  	defaultsOrig := make([]string, len(defaults))
   144  	copy(defaultsOrig, defaults)
   145  	overridesOrig := make([]string, len(overrides))
   146  	copy(overridesOrig, overrides)
   147  
   148  	results := replaceOrAppendEnvValues(defaults, overrides)
   149  
   150  	if err := assertEqualsStringArrays(defaults, defaultsOrig); err != nil {
   151  		t.Fatal(err)
   152  	}
   153  	if err := assertEqualsStringArrays(overrides, overridesOrig); err != nil {
   154  		t.Fatal(err)
   155  	}
   156  
   157  	if err := assertEqualsStringArrays(results, expected); err != nil {
   158  		t.Fatal(err)
   159  	}
   160  }
   161  
   162  func TestWithEnv(t *testing.T) {
   163  	t.Parallel()
   164  
   165  	s := Spec{}
   166  	s.Process = &specs.Process{
   167  		Env: []string{"DEFAULT=test"},
   168  	}
   169  
   170  	WithEnv([]string{"env=1"})(context.Background(), nil, nil, &s)
   171  
   172  	if len(s.Process.Env) != 2 {
   173  		t.Fatal("didn't append")
   174  	}
   175  
   176  	WithEnv([]string{"env2=1"})(context.Background(), nil, nil, &s)
   177  
   178  	if len(s.Process.Env) != 3 {
   179  		t.Fatal("didn't append")
   180  	}
   181  
   182  	WithEnv([]string{"env2=2"})(context.Background(), nil, nil, &s)
   183  
   184  	if s.Process.Env[2] != "env2=2" {
   185  		t.Fatal("couldn't update")
   186  	}
   187  
   188  	WithEnv([]string{"env2"})(context.Background(), nil, nil, &s)
   189  
   190  	if len(s.Process.Env) != 2 {
   191  		t.Fatal("couldn't unset")
   192  	}
   193  }
   194  
   195  func TestWithMounts(t *testing.T) {
   196  
   197  	t.Parallel()
   198  
   199  	s := Spec{
   200  		Mounts: []specs.Mount{
   201  			{
   202  				Source:      "default-source",
   203  				Destination: "default-dest",
   204  			},
   205  		},
   206  	}
   207  
   208  	WithMounts([]specs.Mount{
   209  		{
   210  			Source:      "new-source",
   211  			Destination: "new-dest",
   212  		},
   213  	})(nil, nil, nil, &s)
   214  
   215  	if len(s.Mounts) != 2 {
   216  		t.Fatal("didn't append")
   217  	}
   218  
   219  	if s.Mounts[1].Source != "new-source" {
   220  		t.Fatal("invalid mount")
   221  	}
   222  
   223  	if s.Mounts[1].Destination != "new-dest" {
   224  		t.Fatal("invalid mount")
   225  	}
   226  }
   227  
   228  func TestWithDefaultSpec(t *testing.T) {
   229  	t.Parallel()
   230  	var (
   231  		s   Spec
   232  		c   = containers.Container{ID: "TestWithDefaultSpec"}
   233  		ctx = namespaces.WithNamespace(context.Background(), "test")
   234  	)
   235  
   236  	if err := ApplyOpts(ctx, nil, &c, &s, WithDefaultSpec()); err != nil {
   237  		t.Fatal(err)
   238  	}
   239  
   240  	var expected Spec
   241  	var err error
   242  	if runtime.GOOS == "windows" {
   243  		err = populateDefaultWindowsSpec(ctx, &expected, c.ID)
   244  	} else {
   245  		err = populateDefaultUnixSpec(ctx, &expected, c.ID)
   246  	}
   247  	if err != nil {
   248  		t.Fatal(err)
   249  	}
   250  
   251  	if reflect.DeepEqual(s, Spec{}) {
   252  		t.Fatalf("spec should not be empty")
   253  	}
   254  
   255  	if !reflect.DeepEqual(&s, &expected) {
   256  		t.Fatalf("spec from option differs from default: \n%#v != \n%#v", &s, expected)
   257  	}
   258  }
   259  
   260  func TestWithSpecFromFile(t *testing.T) {
   261  	t.Parallel()
   262  	var (
   263  		s   Spec
   264  		c   = containers.Container{ID: "TestWithDefaultSpec"}
   265  		ctx = namespaces.WithNamespace(context.Background(), "test")
   266  	)
   267  
   268  	fp, err := ioutil.TempFile("", "testwithdefaultspec.json")
   269  	if err != nil {
   270  		t.Fatal(err)
   271  	}
   272  	defer func() {
   273  		if err := os.Remove(fp.Name()); err != nil {
   274  			log.Printf("failed to remove tempfile %v: %v", fp.Name(), err)
   275  		}
   276  	}()
   277  	defer fp.Close()
   278  
   279  	expected, err := GenerateSpec(ctx, nil, &c)
   280  	if err != nil {
   281  		t.Fatal(err)
   282  	}
   283  
   284  	p, err := json.Marshal(expected)
   285  	if err != nil {
   286  		t.Fatal(err)
   287  	}
   288  
   289  	if _, err := fp.Write(p); err != nil {
   290  		t.Fatal(err)
   291  	}
   292  
   293  	if err := ApplyOpts(ctx, nil, &c, &s, WithSpecFromFile(fp.Name())); err != nil {
   294  		t.Fatal(err)
   295  	}
   296  
   297  	if reflect.DeepEqual(s, Spec{}) {
   298  		t.Fatalf("spec should not be empty")
   299  	}
   300  
   301  	if !reflect.DeepEqual(&s, expected) {
   302  		t.Fatalf("spec from option differs from default: \n%#v != \n%#v", &s, expected)
   303  	}
   304  }
   305  
   306  func TestWithMemoryLimit(t *testing.T) {
   307  	var (
   308  		ctx = namespaces.WithNamespace(context.Background(), "testing")
   309  		c   = containers.Container{ID: t.Name()}
   310  		m   = uint64(768 * 1024 * 1024)
   311  		o   = WithMemoryLimit(m)
   312  	)
   313  	// Test with all three supported scenarios
   314  	platforms := []string{"", "linux/amd64", "windows/amd64"}
   315  	for _, p := range platforms {
   316  		var spec *Spec
   317  		var err error
   318  		if p == "" {
   319  			t.Log("Testing GenerateSpec default platform")
   320  			spec, err = GenerateSpec(ctx, nil, &c, o)
   321  
   322  			// Convert the platform to the default based on GOOS like
   323  			// GenerateSpec does.
   324  			switch runtime.GOOS {
   325  			case "linux":
   326  				p = "linux/amd64"
   327  			case "windows":
   328  				p = "windows/amd64"
   329  			}
   330  		} else {
   331  			t.Logf("Testing GenerateSpecWithPlatform with platform: '%s'", p)
   332  			spec, err = GenerateSpecWithPlatform(ctx, nil, p, &c, o)
   333  		}
   334  		if err != nil {
   335  			t.Fatalf("failed to generate spec with: %v", err)
   336  		}
   337  		switch p {
   338  		case "linux/amd64":
   339  			if *spec.Linux.Resources.Memory.Limit != int64(m) {
   340  				t.Fatalf("spec.Linux.Resources.Memory.Limit expected: %v, got: %v", m, *spec.Linux.Resources.Memory.Limit)
   341  			}
   342  			// If we are linux/amd64 on Windows GOOS it is LCOW
   343  			if runtime.GOOS == "windows" {
   344  				// Verify that we also set the Windows section.
   345  				if *spec.Windows.Resources.Memory.Limit != m {
   346  					t.Fatalf("for LCOW spec.Windows.Resources.Memory.Limit is also expected: %v, got: %v", m, *spec.Windows.Resources.Memory.Limit)
   347  				}
   348  			} else {
   349  				if spec.Windows != nil {
   350  					t.Fatalf("spec.Windows section should not be set for linux/amd64 spec on non-windows platform")
   351  				}
   352  			}
   353  		case "windows/amd64":
   354  			if *spec.Windows.Resources.Memory.Limit != m {
   355  				t.Fatalf("spec.Windows.Resources.Memory.Limit expected: %v, got: %v", m, *spec.Windows.Resources.Memory.Limit)
   356  			}
   357  			if spec.Linux != nil {
   358  				t.Fatalf("spec.Linux section should not be set for windows/amd64 spec ever")
   359  			}
   360  		}
   361  	}
   362  }
   363  
   364  func isEqualStringArrays(values, expected []string) bool {
   365  	if len(values) != len(expected) {
   366  		return false
   367  	}
   368  
   369  	for i, x := range expected {
   370  		if values[i] != x {
   371  			return false
   372  		}
   373  	}
   374  	return true
   375  }
   376  
   377  func assertEqualsStringArrays(values, expected []string) error {
   378  	if !isEqualStringArrays(values, expected) {
   379  		return fmt.Errorf("expected %s, but found %s", expected, values)
   380  	}
   381  	return nil
   382  }
   383  
   384  func TestWithImageConfigArgs(t *testing.T) {
   385  	t.Parallel()
   386  
   387  	img, err := newFakeImage(ocispec.Image{
   388  		Config: ocispec.ImageConfig{
   389  			Env:        []string{"z=bar", "y=baz"},
   390  			Entrypoint: []string{"create", "--namespace=test"},
   391  			Cmd:        []string{"", "--debug"},
   392  		},
   393  	})
   394  	if err != nil {
   395  		t.Fatal(err)
   396  	}
   397  
   398  	s := Spec{
   399  		Version: specs.Version,
   400  		Root:    &specs.Root{},
   401  		Windows: &specs.Windows{},
   402  	}
   403  
   404  	opts := []SpecOpts{
   405  		WithEnv([]string{"x=foo", "y=boo"}),
   406  		WithProcessArgs("run", "--foo", "xyz", "--bar"),
   407  		WithImageConfigArgs(img, []string{"--boo", "bar"}),
   408  	}
   409  
   410  	expectedEnv := []string{"z=bar", "y=boo", "x=foo"}
   411  	expectedArgs := []string{"create", "--namespace=test", "--boo", "bar"}
   412  
   413  	for _, opt := range opts {
   414  		if err := opt(nil, nil, nil, &s); err != nil {
   415  			t.Fatal(err)
   416  		}
   417  	}
   418  
   419  	if err := assertEqualsStringArrays(s.Process.Env, expectedEnv); err != nil {
   420  		t.Fatal(err)
   421  	}
   422  	if err := assertEqualsStringArrays(s.Process.Args, expectedArgs); err != nil {
   423  		t.Fatal(err)
   424  	}
   425  }
   426  
   427  func TestAddCaps(t *testing.T) {
   428  	t.Parallel()
   429  
   430  	var s specs.Spec
   431  
   432  	if err := WithAddedCapabilities([]string{"CAP_CHOWN"})(context.Background(), nil, nil, &s); err != nil {
   433  		t.Fatal(err)
   434  	}
   435  	for i, cl := range [][]string{
   436  		s.Process.Capabilities.Bounding,
   437  		s.Process.Capabilities.Effective,
   438  		s.Process.Capabilities.Permitted,
   439  		s.Process.Capabilities.Inheritable,
   440  	} {
   441  		if !capsContain(cl, "CAP_CHOWN") {
   442  			t.Errorf("cap list %d does not contain added cap", i)
   443  		}
   444  	}
   445  }
   446  
   447  func TestDropCaps(t *testing.T) {
   448  	t.Parallel()
   449  
   450  	var s specs.Spec
   451  
   452  	if err := WithAllCapabilities(context.Background(), nil, nil, &s); err != nil {
   453  		t.Fatal(err)
   454  	}
   455  	if err := WithDroppedCapabilities([]string{"CAP_CHOWN"})(context.Background(), nil, nil, &s); err != nil {
   456  		t.Fatal(err)
   457  	}
   458  
   459  	for i, cl := range [][]string{
   460  		s.Process.Capabilities.Bounding,
   461  		s.Process.Capabilities.Effective,
   462  		s.Process.Capabilities.Permitted,
   463  		s.Process.Capabilities.Inheritable,
   464  	} {
   465  		if capsContain(cl, "CAP_CHOWN") {
   466  			t.Errorf("cap list %d contains dropped cap", i)
   467  		}
   468  	}
   469  
   470  	// Add all capabilities back and drop a different cap.
   471  	if err := WithAllCapabilities(context.Background(), nil, nil, &s); err != nil {
   472  		t.Fatal(err)
   473  	}
   474  	if err := WithDroppedCapabilities([]string{"CAP_FOWNER"})(context.Background(), nil, nil, &s); err != nil {
   475  		t.Fatal(err)
   476  	}
   477  
   478  	for i, cl := range [][]string{
   479  		s.Process.Capabilities.Bounding,
   480  		s.Process.Capabilities.Effective,
   481  		s.Process.Capabilities.Permitted,
   482  		s.Process.Capabilities.Inheritable,
   483  	} {
   484  		if capsContain(cl, "CAP_FOWNER") {
   485  			t.Errorf("cap list %d contains dropped cap", i)
   486  		}
   487  		if !capsContain(cl, "CAP_CHOWN") {
   488  			t.Errorf("cap list %d doesn't contain non-dropped cap", i)
   489  		}
   490  	}
   491  
   492  	// Drop all duplicated caps.
   493  	if err := WithCapabilities([]string{"CAP_CHOWN", "CAP_CHOWN"})(context.Background(), nil, nil, &s); err != nil {
   494  		t.Fatal(err)
   495  	}
   496  	if err := WithDroppedCapabilities([]string{"CAP_CHOWN"})(context.Background(), nil, nil, &s); err != nil {
   497  		t.Fatal(err)
   498  	}
   499  	for i, cl := range [][]string{
   500  		s.Process.Capabilities.Bounding,
   501  		s.Process.Capabilities.Effective,
   502  		s.Process.Capabilities.Permitted,
   503  		s.Process.Capabilities.Inheritable,
   504  	} {
   505  		if len(cl) != 0 {
   506  			t.Errorf("cap list %d is not empty", i)
   507  		}
   508  	}
   509  }
   510  
   511  func TestDevShmSize(t *testing.T) {
   512  	t.Parallel()
   513  	var (
   514  		s   Spec
   515  		c   = containers.Container{ID: t.Name()}
   516  		ctx = namespaces.WithNamespace(context.Background(), "test")
   517  	)
   518  
   519  	err := populateDefaultUnixSpec(ctx, &s, c.ID)
   520  	if err != nil {
   521  		t.Fatal(err)
   522  	}
   523  
   524  	expected := "1024k"
   525  	if err := WithDevShmSize(1024)(nil, nil, nil, &s); err != nil {
   526  		t.Fatal(err)
   527  	}
   528  	m := getShmMount(&s)
   529  	if m == nil {
   530  		t.Fatal("no shm mount found")
   531  	}
   532  	o := getShmSize(m.Options)
   533  	if o == "" {
   534  		t.Fatal("shm size not specified")
   535  	}
   536  	parts := strings.Split(o, "=")
   537  	if len(parts) != 2 {
   538  		t.Fatal("invalid size format")
   539  	}
   540  	size := parts[1]
   541  	if size != expected {
   542  		t.Fatalf("size %s not equal %s", size, expected)
   543  	}
   544  }
   545  
   546  func getShmMount(s *Spec) *specs.Mount {
   547  	for _, m := range s.Mounts {
   548  		if m.Source == "shm" && m.Type == "tmpfs" {
   549  			return &m
   550  		}
   551  	}
   552  	return nil
   553  }
   554  
   555  func getShmSize(opts []string) string {
   556  	for _, o := range opts {
   557  		if strings.HasPrefix(o, "size=") {
   558  			return o
   559  		}
   560  	}
   561  	return ""
   562  }