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 }