github.com/kata-containers/runtime@v0.0.0-20210505125100-04f29832a923/virtcontainers/clh_test.go (about)

     1  // Copyright (c) 2019 Ericsson Eurolab Deutschland G.m.b.H.
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  
     6  package virtcontainers
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"net/http"
    12  	"os"
    13  	"path/filepath"
    14  	"reflect"
    15  	"testing"
    16  
    17  	"github.com/kata-containers/runtime/virtcontainers/device/config"
    18  	"github.com/kata-containers/runtime/virtcontainers/persist"
    19  	chclient "github.com/kata-containers/runtime/virtcontainers/pkg/cloud-hypervisor/client"
    20  	"github.com/kata-containers/runtime/virtcontainers/utils"
    21  	"github.com/pkg/errors"
    22  	"github.com/stretchr/testify/assert"
    23  )
    24  
    25  const (
    26  	FAIL = true
    27  	PASS = !FAIL
    28  )
    29  
    30  func newClhConfig() (HypervisorConfig, error) {
    31  
    32  	setupClh()
    33  
    34  	if testClhPath == "" {
    35  		return HypervisorConfig{}, errors.New("hypervisor fake path is empty")
    36  	}
    37  
    38  	if testVirtiofsdPath == "" {
    39  		return HypervisorConfig{}, errors.New("hypervisor fake path is empty")
    40  	}
    41  
    42  	if _, err := os.Stat(testClhPath); os.IsNotExist(err) {
    43  		return HypervisorConfig{}, err
    44  	}
    45  
    46  	if _, err := os.Stat(testVirtiofsdPath); os.IsNotExist(err) {
    47  		return HypervisorConfig{}, err
    48  	}
    49  
    50  	return HypervisorConfig{
    51  		KernelPath:        testClhKernelPath,
    52  		ImagePath:         testClhImagePath,
    53  		HypervisorPath:    testClhPath,
    54  		NumVCPUs:          defaultVCPUs,
    55  		BlockDeviceDriver: config.VirtioBlock,
    56  		MemorySize:        defaultMemSzMiB,
    57  		DefaultBridges:    defaultBridges,
    58  		DefaultMaxVCPUs:   uint32(64),
    59  		SharedFS:          config.VirtioFS,
    60  		VirtioFSCache:     virtioFsCacheAlways,
    61  		VirtioFSDaemon:    testVirtiofsdPath,
    62  	}, nil
    63  }
    64  
    65  type clhClientMock struct {
    66  	vmInfo chclient.VmInfo
    67  }
    68  
    69  func (c *clhClientMock) VmmPingGet(ctx context.Context) (chclient.VmmPingResponse, *http.Response, error) {
    70  	return chclient.VmmPingResponse{}, nil, nil
    71  }
    72  
    73  func (c *clhClientMock) ShutdownVMM(ctx context.Context) (*http.Response, error) {
    74  	return nil, nil
    75  }
    76  
    77  func (c *clhClientMock) CreateVM(ctx context.Context, vmConfig chclient.VmConfig) (*http.Response, error) {
    78  	c.vmInfo.State = clhStateCreated
    79  	return nil, nil
    80  }
    81  
    82  //nolint:golint
    83  func (c *clhClientMock) VmInfoGet(ctx context.Context) (chclient.VmInfo, *http.Response, error) {
    84  	return c.vmInfo, nil, nil
    85  }
    86  
    87  func (c *clhClientMock) BootVM(ctx context.Context) (*http.Response, error) {
    88  	c.vmInfo.State = clhStateRunning
    89  	return nil, nil
    90  }
    91  
    92  //nolint:golint
    93  func (c *clhClientMock) VmResizePut(ctx context.Context, vmResize chclient.VmResize) (*http.Response, error) {
    94  	return nil, nil
    95  }
    96  
    97  //nolint:golint
    98  func (c *clhClientMock) VmAddDevicePut(ctx context.Context, vmAddDevice chclient.VmAddDevice) (chclient.PciDeviceInfo, *http.Response, error) {
    99  	return chclient.PciDeviceInfo{}, nil, nil
   100  }
   101  
   102  //nolint:golint
   103  func (c *clhClientMock) VmAddDiskPut(ctx context.Context, diskConfig chclient.DiskConfig) (chclient.PciDeviceInfo, *http.Response, error) {
   104  	return chclient.PciDeviceInfo{}, nil, nil
   105  }
   106  
   107  //nolint:golint
   108  func (c *clhClientMock) VmRemoveDevicePut(ctx context.Context, vmRemoveDevice chclient.VmRemoveDevice) (*http.Response, error) {
   109  	return nil, nil
   110  }
   111  
   112  func TestCloudHypervisorAddVSock(t *testing.T) {
   113  	assert := assert.New(t)
   114  	clh := cloudHypervisor{}
   115  
   116  	clh.addVSock(1, "path")
   117  	assert.Equal(clh.vmconfig.Vsock.Cid, int64(1))
   118  	assert.Equal(clh.vmconfig.Vsock.Socket, "path")
   119  }
   120  
   121  // Check addNet appends to the network config list new configurations.
   122  // Check that the elements in the list has the correct values
   123  func TestCloudHypervisorAddNetCheckNetConfigListValues(t *testing.T) {
   124  	macTest := "00:00:00:00:00"
   125  	tapPath := "/path/to/tap"
   126  
   127  	assert := assert.New(t)
   128  
   129  	clh := cloudHypervisor{}
   130  
   131  	e := &VethEndpoint{}
   132  	e.NetPair.TAPIface.HardAddr = macTest
   133  	e.NetPair.TapInterface.TAPIface.Name = tapPath
   134  
   135  	err := clh.addNet(e)
   136  	assert.Nil(err)
   137  
   138  	assert.Equal(len(clh.vmconfig.Net), 1)
   139  	if err == nil {
   140  		assert.Equal(clh.vmconfig.Net[0].Mac, macTest)
   141  		assert.Equal(clh.vmconfig.Net[0].Tap, tapPath)
   142  	}
   143  
   144  	err = clh.addNet(e)
   145  	assert.Nil(err)
   146  
   147  	assert.Equal(len(clh.vmconfig.Net), 2)
   148  	if err == nil {
   149  		assert.Equal(clh.vmconfig.Net[1].Mac, macTest)
   150  		assert.Equal(clh.vmconfig.Net[1].Tap, tapPath)
   151  	}
   152  }
   153  
   154  // Check addNet with valid values, and fail with invalid values
   155  // For Cloud Hypervisor only tap is be required
   156  func TestCloudHypervisorAddNetCheckEnpointTypes(t *testing.T) {
   157  	assert := assert.New(t)
   158  
   159  	tapPath := "/path/to/tap"
   160  
   161  	validVeth := &VethEndpoint{}
   162  	validVeth.NetPair.TapInterface.TAPIface.Name = tapPath
   163  
   164  	type args struct {
   165  		e Endpoint
   166  	}
   167  	tests := []struct {
   168  		name    string
   169  		args    args
   170  		wantErr bool
   171  	}{
   172  		{"TapEndpoint", args{e: &TapEndpoint{}}, true},
   173  		{"Empty VethEndpoint", args{e: &VethEndpoint{}}, true},
   174  		{"Valid VethEndpoint", args{e: validVeth}, false},
   175  	}
   176  	for _, tt := range tests {
   177  		t.Run(tt.name, func(t *testing.T) {
   178  			clh := &cloudHypervisor{}
   179  			if err := clh.addNet(tt.args.e); (err != nil) != tt.wantErr {
   180  				t.Errorf("cloudHypervisor.addNet() error = %v, wantErr %v", err, tt.wantErr)
   181  
   182  			} else if err == nil {
   183  				assert.Equal(clh.vmconfig.Net[0].Tap, tapPath)
   184  			}
   185  		})
   186  	}
   187  }
   188  
   189  func TestCloudHypervisorBootVM(t *testing.T) {
   190  	clh := &cloudHypervisor{}
   191  	clh.APIClient = &clhClientMock{}
   192  	var ctx context.Context
   193  	if err := clh.bootVM(ctx); err != nil {
   194  		t.Errorf("cloudHypervisor.bootVM() error = %v", err)
   195  	}
   196  }
   197  
   198  func TestCloudHypervisorCleanupVM(t *testing.T) {
   199  	assert := assert.New(t)
   200  	store, err := persist.GetDriver()
   201  	assert.NoError(err, "persist.GetDriver() unexpected error")
   202  
   203  	clh := &cloudHypervisor{
   204  		store: store,
   205  	}
   206  
   207  	err = clh.cleanupVM(true)
   208  	assert.Error(err, "persist.GetDriver() expected error")
   209  
   210  	clh.id = "cleanVMID"
   211  
   212  	err = clh.cleanupVM(true)
   213  	assert.NoError(err, "persist.GetDriver() unexpected error")
   214  
   215  	dir := filepath.Join(clh.store.RunVMStoragePath(), clh.id)
   216  	os.MkdirAll(dir, os.ModePerm)
   217  
   218  	err = clh.cleanupVM(false)
   219  	assert.NoError(err, "persist.GetDriver() unexpected error")
   220  
   221  	_, err = os.Stat(dir)
   222  	assert.Error(err, "dir should not exist %s", dir)
   223  
   224  	assert.True(os.IsNotExist(err), "persist.GetDriver() unexpected error")
   225  }
   226  
   227  func TestClhCreateSandbox(t *testing.T) {
   228  	assert := assert.New(t)
   229  
   230  	clhConfig, err := newClhConfig()
   231  	assert.NoError(err)
   232  
   233  	store, err := persist.GetDriver()
   234  	assert.NoError(err)
   235  
   236  	clh := &cloudHypervisor{
   237  		config: clhConfig,
   238  		store:  store,
   239  	}
   240  
   241  	sandbox := &Sandbox{
   242  		ctx: context.Background(),
   243  		id:  "testSandbox",
   244  		config: &SandboxConfig{
   245  			HypervisorConfig: clhConfig,
   246  		},
   247  	}
   248  
   249  	err = clh.createSandbox(context.Background(), sandbox.id, NetworkNamespace{}, &sandbox.config.HypervisorConfig, false)
   250  	assert.NoError(err)
   251  	assert.Exactly(clhConfig, clh.config)
   252  }
   253  
   254  func TestClooudHypervisorStartSandbox(t *testing.T) {
   255  	assert := assert.New(t)
   256  	clhConfig, err := newClhConfig()
   257  	assert.NoError(err)
   258  
   259  	store, err := persist.GetDriver()
   260  	assert.NoError(err)
   261  
   262  	clh := &cloudHypervisor{
   263  		config:    clhConfig,
   264  		APIClient: &clhClientMock{},
   265  		virtiofsd: &virtiofsdMock{},
   266  		store:     store,
   267  	}
   268  
   269  	err = clh.startSandbox(10)
   270  	assert.NoError(err)
   271  }
   272  
   273  func TestCloudHypervisorResizeMemory(t *testing.T) {
   274  	assert := assert.New(t)
   275  	clhConfig, err := newClhConfig()
   276  	type args struct {
   277  		reqMemMB          uint32
   278  		memoryBlockSizeMB uint32
   279  	}
   280  	tests := []struct {
   281  		name           string
   282  		args           args
   283  		expectedMemDev memoryDevice
   284  		wantErr        bool
   285  	}{
   286  		{"Resize to zero", args{0, 128}, memoryDevice{probe: false, sizeMB: 0}, FAIL},
   287  		{"Resize to aligned size", args{clhConfig.MemorySize + 128, 128}, memoryDevice{probe: false, sizeMB: 128}, PASS},
   288  		{"Resize to aligned size", args{clhConfig.MemorySize + 129, 128}, memoryDevice{probe: false, sizeMB: 256}, PASS},
   289  		{"Resize to NOT aligned size", args{clhConfig.MemorySize + 125, 128}, memoryDevice{probe: false, sizeMB: 128}, PASS},
   290  	}
   291  	for _, tt := range tests {
   292  		t.Run(tt.name, func(t *testing.T) {
   293  			assert.NoError(err)
   294  			clh := cloudHypervisor{}
   295  
   296  			mockClient := &clhClientMock{}
   297  			mockClient.vmInfo.Config.Memory.Size = int64(utils.MemUnit(clhConfig.MemorySize) * utils.MiB)
   298  			mockClient.vmInfo.Config.Memory.HotplugSize = int64(40 * utils.GiB.ToBytes())
   299  
   300  			clh.APIClient = mockClient
   301  			clh.config = clhConfig
   302  
   303  			newMem, memDev, err := clh.resizeMemory(tt.args.reqMemMB, tt.args.memoryBlockSizeMB, false)
   304  
   305  			if (err != nil) != tt.wantErr {
   306  				t.Errorf("cloudHypervisor.resizeMemory() error = %v, expected to fail = %v", err, tt.wantErr)
   307  				return
   308  			}
   309  
   310  			if err != nil {
   311  				return
   312  			}
   313  
   314  			expectedMem := clhConfig.MemorySize + uint32(tt.expectedMemDev.sizeMB)
   315  
   316  			if newMem != expectedMem {
   317  				t.Errorf("cloudHypervisor.resizeMemory() got = %+v, want %+v", newMem, expectedMem)
   318  			}
   319  
   320  			if !reflect.DeepEqual(memDev, tt.expectedMemDev) {
   321  				t.Errorf("cloudHypervisor.resizeMemory() got = %+v, want %+v", memDev, tt.expectedMemDev)
   322  			}
   323  		})
   324  	}
   325  }
   326  
   327  func TestCheckVersion(t *testing.T) {
   328  	clh := &cloudHypervisor{}
   329  	assert := assert.New(t)
   330  	testcases := []struct {
   331  		name  string
   332  		major int
   333  		minor int
   334  		pass  bool
   335  	}{
   336  		{
   337  			name:  "minor lower than supported version",
   338  			major: supportedMajorVersion,
   339  			minor: 2,
   340  			pass:  false,
   341  		},
   342  		{
   343  			name:  "minor equal to supported version",
   344  			major: supportedMajorVersion,
   345  			minor: supportedMinorVersion,
   346  			pass:  true,
   347  		},
   348  		{
   349  			name:  "major exceeding supported version",
   350  			major: 1,
   351  			minor: supportedMinorVersion,
   352  			pass:  true,
   353  		},
   354  	}
   355  	for _, tc := range testcases {
   356  		clh.version = CloudHypervisorVersion{
   357  			Major:    tc.major,
   358  			Minor:    tc.minor,
   359  			Revision: 0,
   360  		}
   361  		err := clh.checkVersion()
   362  		msg := fmt.Sprintf("test: %+v, clh.version: %v, result: %v", tc, clh.version, err)
   363  		if tc.pass {
   364  			assert.NoError(err, msg)
   365  		} else {
   366  			assert.Error(err, msg)
   367  		}
   368  	}
   369  }
   370  
   371  func TestCloudHypervisorHotplugAddBlockDevice(t *testing.T) {
   372  	assert := assert.New(t)
   373  
   374  	clhConfig, err := newClhConfig()
   375  	assert.NoError(err)
   376  
   377  	clh := &cloudHypervisor{}
   378  	clh.config = clhConfig
   379  	clh.APIClient = &clhClientMock{}
   380  
   381  	clh.config.BlockDeviceDriver = config.VirtioBlock
   382  	err = clh.hotplugAddBlockDevice(&config.BlockDrive{Pmem: false})
   383  	assert.NoError(err, "Hotplug disk block device expected no error")
   384  
   385  	err = clh.hotplugAddBlockDevice(&config.BlockDrive{Pmem: true})
   386  	assert.Error(err, "Hotplug pmem block device expected error")
   387  
   388  	clh.config.BlockDeviceDriver = config.VirtioSCSI
   389  	err = clh.hotplugAddBlockDevice(&config.BlockDrive{Pmem: false})
   390  	assert.Error(err, "Hotplug block device not using 'virtio-blk' expected error")
   391  }
   392  
   393  func TestCloudHypervisorHotplugRemoveDevice(t *testing.T) {
   394  	assert := assert.New(t)
   395  
   396  	clhConfig, err := newClhConfig()
   397  	assert.NoError(err)
   398  
   399  	clh := &cloudHypervisor{}
   400  	clh.config = clhConfig
   401  	clh.APIClient = &clhClientMock{}
   402  
   403  	_, err = clh.hotplugRemoveDevice(&config.BlockDrive{}, blockDev)
   404  	assert.NoError(err, "Hotplug remove block device expected no error")
   405  
   406  	_, err = clh.hotplugRemoveDevice(&config.VFIODev{}, vfioDev)
   407  	assert.NoError(err, "Hotplug remove vfio block device expected no error")
   408  
   409  	_, err = clh.hotplugRemoveDevice(nil, netDev)
   410  	assert.Error(err, "Hotplug remove pmem block device expected error")
   411  }