gitee.com/leisunstar/runtime@v0.0.0-20200521203717-5cef3e7b53f9/pkg/katautils/create_test.go (about)

     1  // Copyright (c) 2018 Intel Corporation
     2  // Copyright (c) 2018 HyperHQ Inc.
     3  //
     4  // SPDX-License-Identifier: Apache-2.0
     5  //
     6  
     7  package katautils
     8  
     9  import (
    10  	"context"
    11  	"encoding/json"
    12  	"errors"
    13  	"fmt"
    14  	"io/ioutil"
    15  	"os"
    16  	"path"
    17  	"path/filepath"
    18  	"strings"
    19  	"syscall"
    20  	"testing"
    21  
    22  	ktu "github.com/kata-containers/runtime/pkg/katatestutils"
    23  	vc "github.com/kata-containers/runtime/virtcontainers"
    24  	"github.com/kata-containers/runtime/virtcontainers/pkg/compatoci"
    25  	"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
    26  	"github.com/kata-containers/runtime/virtcontainers/pkg/vcmock"
    27  	"github.com/opencontainers/runtime-spec/specs-go"
    28  	"github.com/stretchr/testify/assert"
    29  )
    30  
    31  const (
    32  	testConsole                 = "/dev/pts/999"
    33  	testContainerTypeAnnotation = "io.kubernetes.cri-o.ContainerType"
    34  	testSandboxIDAnnotation     = "io.kubernetes.cri-o.SandboxID"
    35  	testContainerTypeContainer  = "container"
    36  )
    37  
    38  var (
    39  	testBundleDir = ""
    40  
    41  	// testingImpl is a concrete mock RVC implementation used for testing
    42  	testingImpl = &vcmock.VCMock{}
    43  
    44  	tc ktu.TestConstraint
    45  )
    46  
    47  func init() {
    48  	tc = ktu.NewTestConstraint(false)
    49  }
    50  
    51  func writeOCIConfigFile(spec specs.Spec, configPath string) error {
    52  	if configPath == "" {
    53  		return errors.New("BUG: need config file path")
    54  	}
    55  
    56  	bytes, err := json.MarshalIndent(spec, "", "\t")
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	return ioutil.WriteFile(configPath, bytes, testFileMode)
    62  }
    63  
    64  // Create an OCI bundle in the specified directory.
    65  //
    66  // Note that the directory will be created, but it's parent is expected to exist.
    67  //
    68  // This function works by copying the already-created test bundle. Ideally,
    69  // the bundle would be recreated for each test, but createRootfs() uses
    70  // docker which on some systems is too slow, resulting in the tests timing
    71  // out.
    72  func makeOCIBundle(bundleDir string) error {
    73  	from := testBundleDir
    74  	to := bundleDir
    75  
    76  	// only the basename of bundleDir needs to exist as bundleDir
    77  	// will get created by cp(1).
    78  	base := filepath.Dir(bundleDir)
    79  
    80  	for _, dir := range []string{from, base} {
    81  		if !FileExists(dir) {
    82  			return fmt.Errorf("BUG: directory %v should exist", dir)
    83  		}
    84  	}
    85  
    86  	output, err := RunCommandFull([]string{"cp", "-a", from, to}, true)
    87  	if err != nil {
    88  		return fmt.Errorf("failed to copy test OCI bundle from %v to %v: %v (output: %v)", from, to, err, output)
    89  	}
    90  
    91  	return nil
    92  }
    93  
    94  // newTestRuntimeConfig creates a new RuntimeConfig
    95  func newTestRuntimeConfig(dir, consolePath string, create bool) (oci.RuntimeConfig, error) {
    96  	if dir == "" {
    97  		return oci.RuntimeConfig{}, errors.New("BUG: need directory")
    98  	}
    99  
   100  	hypervisorConfig, err := newTestHypervisorConfig(dir, create)
   101  	if err != nil {
   102  		return oci.RuntimeConfig{}, err
   103  	}
   104  
   105  	return oci.RuntimeConfig{
   106  		HypervisorType:   vc.QemuHypervisor,
   107  		HypervisorConfig: hypervisorConfig,
   108  		AgentType:        vc.KataContainersAgent,
   109  		ProxyType:        vc.KataProxyType,
   110  		ShimType:         vc.KataShimType,
   111  		Console:          consolePath,
   112  	}, nil
   113  }
   114  
   115  // newTestHypervisorConfig creaets a new virtcontainers
   116  // HypervisorConfig, ensuring that the required resources are also
   117  // created.
   118  //
   119  // Note: no parameter validation in case caller wishes to create an invalid
   120  // object.
   121  func newTestHypervisorConfig(dir string, create bool) (vc.HypervisorConfig, error) {
   122  	kernelPath := path.Join(dir, "kernel")
   123  	imagePath := path.Join(dir, "image")
   124  	hypervisorPath := path.Join(dir, "hypervisor")
   125  
   126  	if create {
   127  		for _, file := range []string{kernelPath, imagePath, hypervisorPath} {
   128  			err := createEmptyFile(file)
   129  			if err != nil {
   130  				return vc.HypervisorConfig{}, err
   131  			}
   132  		}
   133  	}
   134  
   135  	return vc.HypervisorConfig{
   136  		KernelPath:            kernelPath,
   137  		ImagePath:             imagePath,
   138  		HypervisorPath:        hypervisorPath,
   139  		HypervisorMachineType: "pc-lite",
   140  	}, nil
   141  }
   142  
   143  // return the value of the *last* param with the specified key
   144  func findLastParam(key string, params []vc.Param) (string, error) {
   145  	if key == "" {
   146  		return "", errors.New("ERROR: need non-nil key")
   147  	}
   148  
   149  	l := len(params)
   150  	if l == 0 {
   151  		return "", errors.New("ERROR: no params")
   152  	}
   153  
   154  	for i := l - 1; i >= 0; i-- {
   155  		p := params[i]
   156  
   157  		if key == p.Key {
   158  			return p.Value, nil
   159  		}
   160  	}
   161  
   162  	return "", fmt.Errorf("no param called %q found", name)
   163  }
   164  
   165  func TestSetEphemeralStorageType(t *testing.T) {
   166  	if tc.NotValid(ktu.NeedRoot()) {
   167  		t.Skip(ktu.TestDisabledNeedRoot)
   168  	}
   169  
   170  	assert := assert.New(t)
   171  
   172  	dir, err := ioutil.TempDir(testDir, "foo")
   173  	if err != nil {
   174  		t.Fatal(err)
   175  	}
   176  	defer os.RemoveAll(dir)
   177  
   178  	ephePath := filepath.Join(dir, vc.K8sEmptyDir, "tmp-volume")
   179  	err = os.MkdirAll(ephePath, testDirMode)
   180  	assert.Nil(err)
   181  
   182  	err = syscall.Mount("tmpfs", ephePath, "tmpfs", 0, "")
   183  	assert.Nil(err)
   184  	defer syscall.Unmount(ephePath, 0)
   185  
   186  	ociSpec := specs.Spec{}
   187  	var ociMounts []specs.Mount
   188  	mount := specs.Mount{
   189  		Source: ephePath,
   190  	}
   191  
   192  	ociMounts = append(ociMounts, mount)
   193  	ociSpec.Mounts = ociMounts
   194  	ociSpec = SetEphemeralStorageType(ociSpec)
   195  
   196  	mountType := ociSpec.Mounts[0].Type
   197  	assert.Equal(mountType, "ephemeral",
   198  		"Unexpected mount type, got %s expected ephemeral", mountType)
   199  }
   200  
   201  func TestSetKernelParams(t *testing.T) {
   202  	assert := assert.New(t)
   203  
   204  	config := oci.RuntimeConfig{}
   205  
   206  	assert.Empty(config.HypervisorConfig.KernelParams)
   207  
   208  	err := SetKernelParams(&config)
   209  	assert.NoError(err)
   210  
   211  	config.HypervisorConfig.BlockDeviceDriver = "virtio-scsi"
   212  	err = SetKernelParams(&config)
   213  	assert.NoError(err)
   214  
   215  	if needSystemd(config.HypervisorConfig) {
   216  		assert.NotEmpty(config.HypervisorConfig.KernelParams)
   217  	}
   218  }
   219  
   220  func TestSetKernelParamsUserOptionTakesPriority(t *testing.T) {
   221  	assert := assert.New(t)
   222  
   223  	initName := "init"
   224  	initValue := "/sbin/myinit"
   225  
   226  	ipName := "ip"
   227  	ipValue := "127.0.0.1"
   228  
   229  	params := []vc.Param{
   230  		{Key: initName, Value: initValue},
   231  		{Key: ipName, Value: ipValue},
   232  	}
   233  
   234  	hypervisorConfig := vc.HypervisorConfig{
   235  		KernelParams: params,
   236  	}
   237  
   238  	// Config containing user-specified kernel parameters
   239  	config := oci.RuntimeConfig{
   240  		HypervisorConfig: hypervisorConfig,
   241  	}
   242  
   243  	assert.NotEmpty(config.HypervisorConfig.KernelParams)
   244  
   245  	err := SetKernelParams(&config)
   246  	assert.NoError(err)
   247  
   248  	kernelParams := config.HypervisorConfig.KernelParams
   249  
   250  	init, err := findLastParam(initName, kernelParams)
   251  	assert.NoError(err)
   252  	assert.Equal(initValue, init)
   253  
   254  	ip, err := findLastParam(ipName, kernelParams)
   255  	assert.NoError(err)
   256  	assert.Equal(ipValue, ip)
   257  
   258  }
   259  
   260  func TestCreateSandboxConfigFail(t *testing.T) {
   261  	assert := assert.New(t)
   262  
   263  	path, err := ioutil.TempDir("", "containers-mapping")
   264  	assert.NoError(err)
   265  	defer os.RemoveAll(path)
   266  	ctrsMapTreePath = path
   267  
   268  	tmpdir, err := ioutil.TempDir("", "")
   269  	assert.NoError(err)
   270  	defer os.RemoveAll(tmpdir)
   271  
   272  	runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
   273  	assert.NoError(err)
   274  
   275  	bundlePath := filepath.Join(tmpdir, "bundle")
   276  
   277  	err = makeOCIBundle(bundlePath)
   278  	assert.NoError(err)
   279  
   280  	ociConfigFile := filepath.Join(bundlePath, "config.json")
   281  	assert.True(FileExists(ociConfigFile))
   282  
   283  	spec, err := compatoci.ParseConfigJSON(bundlePath)
   284  	assert.NoError(err)
   285  
   286  	quota := int64(0)
   287  	limit := int64(0)
   288  
   289  	spec.Linux.Resources.Memory = &specs.LinuxMemory{
   290  		Limit: &limit,
   291  	}
   292  
   293  	spec.Linux.Resources.CPU = &specs.LinuxCPU{
   294  		// specify an invalid value
   295  		Quota: &quota,
   296  	}
   297  
   298  	rootFs := vc.RootFs{Mounted: true}
   299  
   300  	_, _, err = CreateSandbox(context.Background(), testingImpl, spec, runtimeConfig, rootFs, testContainerID, bundlePath, testConsole, true, true, false)
   301  	assert.Error(err)
   302  }
   303  
   304  func TestCreateSandboxFail(t *testing.T) {
   305  	if tc.NotValid(ktu.NeedRoot()) {
   306  		t.Skip(ktu.TestDisabledNeedRoot)
   307  	}
   308  
   309  	assert := assert.New(t)
   310  
   311  	path, err := ioutil.TempDir("", "containers-mapping")
   312  	assert.NoError(err)
   313  	defer os.RemoveAll(path)
   314  	ctrsMapTreePath = path
   315  
   316  	tmpdir, err := ioutil.TempDir("", "")
   317  	assert.NoError(err)
   318  	defer os.RemoveAll(tmpdir)
   319  
   320  	runtimeConfig, err := newTestRuntimeConfig(tmpdir, testConsole, true)
   321  	assert.NoError(err)
   322  
   323  	bundlePath := filepath.Join(tmpdir, "bundle")
   324  
   325  	err = makeOCIBundle(bundlePath)
   326  	assert.NoError(err)
   327  
   328  	ociConfigFile := filepath.Join(bundlePath, "config.json")
   329  	assert.True(FileExists(ociConfigFile))
   330  
   331  	spec, err := compatoci.ParseConfigJSON(bundlePath)
   332  	assert.NoError(err)
   333  
   334  	rootFs := vc.RootFs{Mounted: true}
   335  
   336  	_, _, err = CreateSandbox(context.Background(), testingImpl, spec, runtimeConfig, rootFs, testContainerID, bundlePath, testConsole, true, true, false)
   337  	assert.Error(err)
   338  	assert.True(vcmock.IsMockError(err))
   339  }
   340  
   341  func TestCheckForFips(t *testing.T) {
   342  	assert := assert.New(t)
   343  
   344  	path, err := ioutil.TempDir("", "")
   345  	assert.NoError(err)
   346  	defer os.RemoveAll(path)
   347  
   348  	val := procFIPS
   349  	procFIPS = filepath.Join(path, "fips-enabled")
   350  	defer func() {
   351  		procFIPS = val
   352  	}()
   353  
   354  	err = ioutil.WriteFile(procFIPS, []byte("1"), 0644)
   355  	assert.NoError(err)
   356  
   357  	hconfig := vc.HypervisorConfig{
   358  		KernelParams: []vc.Param{
   359  			{Key: "init", Value: "/sys/init"},
   360  		},
   361  	}
   362  	config := vc.SandboxConfig{
   363  		HypervisorConfig: hconfig,
   364  	}
   365  	assert.NoError(checkForFIPS(&config))
   366  
   367  	params := config.HypervisorConfig.KernelParams
   368  	assert.Equal(len(params), 2)
   369  	assert.Equal(params[1].Key, "fips")
   370  	assert.Equal(params[1].Value, "1")
   371  
   372  	config.HypervisorConfig = hconfig
   373  	err = ioutil.WriteFile(procFIPS, []byte("unexpected contents"), 0644)
   374  	assert.NoError(err)
   375  	assert.NoError(checkForFIPS(&config))
   376  	assert.Equal(config.HypervisorConfig, hconfig)
   377  
   378  	assert.NoError(os.Remove(procFIPS))
   379  	assert.NoError(checkForFIPS(&config))
   380  	assert.Equal(config.HypervisorConfig, hconfig)
   381  }
   382  
   383  func TestCreateContainerContainerConfigFail(t *testing.T) {
   384  	assert := assert.New(t)
   385  
   386  	path, err := ioutil.TempDir("", "containers-mapping")
   387  	assert.NoError(err)
   388  	defer os.RemoveAll(path)
   389  	ctrsMapTreePath = path
   390  
   391  	tmpdir, err := ioutil.TempDir("", "")
   392  	assert.NoError(err)
   393  	defer os.RemoveAll(tmpdir)
   394  
   395  	bundlePath := filepath.Join(tmpdir, "bundle")
   396  
   397  	err = makeOCIBundle(bundlePath)
   398  	assert.NoError(err)
   399  
   400  	ociConfigFile := filepath.Join(bundlePath, "config.json")
   401  	assert.True(FileExists(ociConfigFile))
   402  
   403  	spec, err := compatoci.ParseConfigJSON(bundlePath)
   404  	assert.NoError(err)
   405  
   406  	// Set invalid container type
   407  	containerType := "你好,世界"
   408  	spec.Annotations = make(map[string]string)
   409  	spec.Annotations[testContainerTypeAnnotation] = containerType
   410  
   411  	// rewrite file
   412  	err = writeOCIConfigFile(spec, ociConfigFile)
   413  	assert.NoError(err)
   414  
   415  	rootFs := vc.RootFs{Mounted: true}
   416  
   417  	for _, disableOutput := range []bool{true, false} {
   418  		_, err = CreateContainer(context.Background(), testingImpl, nil, spec, rootFs, testContainerID, bundlePath, testConsole, disableOutput, false)
   419  		assert.Error(err)
   420  		assert.False(vcmock.IsMockError(err))
   421  		assert.True(strings.Contains(err.Error(), containerType))
   422  		os.RemoveAll(path)
   423  	}
   424  }
   425  
   426  func TestCreateContainerFail(t *testing.T) {
   427  	assert := assert.New(t)
   428  
   429  	path, err := ioutil.TempDir("", "containers-mapping")
   430  	assert.NoError(err)
   431  	defer os.RemoveAll(path)
   432  	ctrsMapTreePath = path
   433  
   434  	tmpdir, err := ioutil.TempDir("", "")
   435  	assert.NoError(err)
   436  	defer os.RemoveAll(tmpdir)
   437  
   438  	bundlePath := filepath.Join(tmpdir, "bundle")
   439  
   440  	err = makeOCIBundle(bundlePath)
   441  	assert.NoError(err)
   442  
   443  	ociConfigFile := filepath.Join(bundlePath, "config.json")
   444  	assert.True(FileExists(ociConfigFile))
   445  
   446  	spec, err := compatoci.ParseConfigJSON(bundlePath)
   447  	assert.NoError(err)
   448  
   449  	// set expected container type and sandboxID
   450  	spec.Annotations = make(map[string]string)
   451  	spec.Annotations[testContainerTypeAnnotation] = testContainerTypeContainer
   452  	spec.Annotations[testSandboxIDAnnotation] = testSandboxID
   453  
   454  	// rewrite file
   455  	err = writeOCIConfigFile(spec, ociConfigFile)
   456  	assert.NoError(err)
   457  
   458  	rootFs := vc.RootFs{Mounted: true}
   459  
   460  	for _, disableOutput := range []bool{true, false} {
   461  		_, err = CreateContainer(context.Background(), testingImpl, nil, spec, rootFs, testContainerID, bundlePath, testConsole, disableOutput, false)
   462  		assert.Error(err)
   463  		assert.True(vcmock.IsMockError(err))
   464  		os.RemoveAll(path)
   465  	}
   466  }
   467  
   468  func TestCreateContainer(t *testing.T) {
   469  	assert := assert.New(t)
   470  
   471  	path, err := ioutil.TempDir("", "containers-mapping")
   472  	assert.NoError(err)
   473  	defer os.RemoveAll(path)
   474  	ctrsMapTreePath = path
   475  
   476  	testingImpl.CreateContainerFunc = func(ctx context.Context, sandboxID string, containerConfig vc.ContainerConfig) (vc.VCSandbox, vc.VCContainer, error) {
   477  		return &vcmock.Sandbox{}, &vcmock.Container{}, nil
   478  	}
   479  
   480  	defer func() {
   481  		testingImpl.CreateContainerFunc = nil
   482  	}()
   483  
   484  	tmpdir, err := ioutil.TempDir("", "")
   485  	assert.NoError(err)
   486  	defer os.RemoveAll(tmpdir)
   487  
   488  	bundlePath := filepath.Join(tmpdir, "bundle")
   489  
   490  	err = makeOCIBundle(bundlePath)
   491  	assert.NoError(err)
   492  
   493  	ociConfigFile := filepath.Join(bundlePath, "config.json")
   494  	assert.True(FileExists(ociConfigFile))
   495  
   496  	spec, err := compatoci.ParseConfigJSON(bundlePath)
   497  	assert.NoError(err)
   498  
   499  	// set expected container type and sandboxID
   500  	spec.Annotations = make(map[string]string)
   501  	spec.Annotations[testContainerTypeAnnotation] = testContainerTypeContainer
   502  	spec.Annotations[testSandboxIDAnnotation] = testSandboxID
   503  
   504  	// rewrite file
   505  	err = writeOCIConfigFile(spec, ociConfigFile)
   506  	assert.NoError(err)
   507  
   508  	rootFs := vc.RootFs{Mounted: true}
   509  
   510  	for _, disableOutput := range []bool{true, false} {
   511  		_, err = CreateContainer(context.Background(), testingImpl, nil, spec, rootFs, testContainerID, bundlePath, testConsole, disableOutput, false)
   512  		assert.NoError(err)
   513  		os.RemoveAll(path)
   514  	}
   515  }