// Copyright (c) 2017 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 // package main import ( "bytes" "fmt" "io/ioutil" "os" "path" "path/filepath" "reflect" goruntime "runtime" "strconv" "strings" "syscall" "testing" vc "github.com/kata-containers/runtime/virtcontainers" "github.com/kata-containers/runtime/virtcontainers/pkg/oci" "github.com/kata-containers/runtime/virtcontainers/utils" "github.com/stretchr/testify/assert" ) var ( hypervisorDebug = false proxyDebug = false runtimeDebug = false shimDebug = false netmonDebug = false ) type testRuntimeConfig struct { RuntimeConfig oci.RuntimeConfig RuntimeConfigFile string ConfigPath string ConfigPathLink string LogDir string LogPath string } func makeRuntimeConfigFileData(hypervisor, hypervisorPath, kernelPath, imagePath, kernelParams, machineType, shimPath, proxyPath, netmonPath, logPath string, disableBlock bool, blockDeviceDriver string, enableIOThreads bool, hotplugVFIOOnRootBus bool) string { return ` # Runtime configuration file [hypervisor.` + hypervisor + `] path = "` + hypervisorPath + `" kernel = "` + kernelPath + `" block_device_driver = "` + blockDeviceDriver + `" kernel_params = "` + kernelParams + `" image = "` + imagePath + `" machine_type = "` + machineType + `" default_vcpus = ` + strconv.FormatUint(uint64(defaultVCPUCount), 10) + ` default_maxvcpus = ` + strconv.FormatUint(uint64(defaultMaxVCPUCount), 10) + ` default_memory = ` + strconv.FormatUint(uint64(defaultMemSize), 10) + ` disable_block_device_use = ` + strconv.FormatBool(disableBlock) + ` enable_iothreads = ` + strconv.FormatBool(enableIOThreads) + ` hotplug_vfio_on_root_bus = ` + strconv.FormatBool(hotplugVFIOOnRootBus) + ` msize_9p = ` + strconv.FormatUint(uint64(defaultMsize9p), 10) + ` enable_debug = ` + strconv.FormatBool(hypervisorDebug) + ` [proxy.kata] enable_debug = ` + strconv.FormatBool(proxyDebug) + ` path = "` + proxyPath + `" [shim.kata] path = "` + shimPath + `" enable_debug = ` + strconv.FormatBool(shimDebug) + ` [agent.kata] [netmon] path = "` + netmonPath + `" enable_debug = ` + strconv.FormatBool(netmonDebug) + ` [runtime] enable_debug = ` + strconv.FormatBool(runtimeDebug) } func createConfig(configPath string, fileData string) error { err := ioutil.WriteFile(configPath, []byte(fileData), testFileMode) if err != nil { fmt.Fprintf(os.Stderr, "Unable to create config file %s %v\n", configPath, err) return err } return nil } // createAllRuntimeConfigFiles creates all files necessary to call // loadConfiguration(). func createAllRuntimeConfigFiles(dir, hypervisor string) (config testRuntimeConfig, err error) { if dir == "" { return config, fmt.Errorf("BUG: need directory") } if hypervisor == "" { return config, fmt.Errorf("BUG: need hypervisor") } hypervisorPath := path.Join(dir, "hypervisor") kernelPath := path.Join(dir, "kernel") kernelParams := "foo=bar xyz" imagePath := path.Join(dir, "image") shimPath := path.Join(dir, "shim") proxyPath := path.Join(dir, "proxy") netmonPath := path.Join(dir, "netmon") logDir := path.Join(dir, "logs") logPath := path.Join(logDir, "runtime.log") machineType := "machineType" disableBlockDevice := true blockDeviceDriver := "virtio-scsi" enableIOThreads := true hotplugVFIOOnRootBus := true runtimeConfigFileData := makeRuntimeConfigFileData(hypervisor, hypervisorPath, kernelPath, imagePath, kernelParams, machineType, shimPath, proxyPath, netmonPath, logPath, disableBlockDevice, blockDeviceDriver, enableIOThreads, hotplugVFIOOnRootBus) configPath := path.Join(dir, "runtime.toml") err = createConfig(configPath, runtimeConfigFileData) if err != nil { return config, err } configPathLink := path.Join(filepath.Dir(configPath), "link-to-configuration.toml") // create a link to the config file err = syscall.Symlink(configPath, configPathLink) if err != nil { return config, err } files := []string{hypervisorPath, kernelPath, imagePath, shimPath, proxyPath} for _, file := range files { // create the resource (which must be >0 bytes) err := writeFile(file, "foo", testFileMode) if err != nil { return config, err } } hypervisorConfig := vc.HypervisorConfig{ HypervisorPath: hypervisorPath, KernelPath: kernelPath, ImagePath: imagePath, KernelParams: vc.DeserializeParams(strings.Fields(kernelParams)), HypervisorMachineType: machineType, NumVCPUs: defaultVCPUCount, DefaultMaxVCPUs: uint32(goruntime.NumCPU()), MemorySize: defaultMemSize, DisableBlockDeviceUse: disableBlockDevice, BlockDeviceDriver: defaultBlockDeviceDriver, DefaultBridges: defaultBridgesCount, Mlock: !defaultEnableSwap, EnableIOThreads: enableIOThreads, HotplugVFIOOnRootBus: hotplugVFIOOnRootBus, Msize9p: defaultMsize9p, MemSlots: defaultMemSlots, EntropySource: defaultEntropySource, } agentConfig := vc.KataAgentConfig{} proxyConfig := vc.ProxyConfig{ Path: proxyPath, } shimConfig := vc.ShimConfig{ Path: shimPath, } netmonConfig := vc.NetmonConfig{ Path: netmonPath, Debug: false, Enable: false, } runtimeConfig := oci.RuntimeConfig{ HypervisorType: defaultHypervisor, HypervisorConfig: hypervisorConfig, AgentType: defaultAgent, AgentConfig: agentConfig, ProxyType: defaultProxy, ProxyConfig: proxyConfig, ShimType: defaultShim, ShimConfig: shimConfig, NetmonConfig: netmonConfig, } config = testRuntimeConfig{ RuntimeConfig: runtimeConfig, RuntimeConfigFile: configPath, ConfigPath: configPath, ConfigPathLink: configPathLink, LogDir: logDir, LogPath: logPath, } return config, nil } // testLoadConfiguration accepts an optional function that can be used // to modify the test: if a function is specified, it indicates if the // subsequent call to loadConfiguration() is expected to fail by // returning a bool. If the function itself fails, that is considered an // error. func testLoadConfiguration(t *testing.T, dir string, fn func(config testRuntimeConfig, configFile string, ignoreLogging bool) (bool, error)) { subDir := path.Join(dir, "test") for _, hypervisor := range []string{"qemu"} { Loop: for _, ignoreLogging := range []bool{true, false} { err := os.RemoveAll(subDir) assert.NoError(t, err) err = os.MkdirAll(subDir, testDirMode) assert.NoError(t, err) testConfig, err := createAllRuntimeConfigFiles(subDir, hypervisor) assert.NoError(t, err) configFiles := []string{testConfig.ConfigPath, testConfig.ConfigPathLink, ""} // override defaultRuntimeConfiguration = testConfig.ConfigPath defaultSysConfRuntimeConfiguration = "" for _, file := range configFiles { var err error expectFail := false if fn != nil { expectFail, err = fn(testConfig, file, ignoreLogging) assert.NoError(t, err) } resolvedConfigPath, config, err := loadConfiguration(file, ignoreLogging) if expectFail { assert.Error(t, err) // no point proceeding in the error scenario. break Loop } else { assert.NoError(t, err) } if file == "" { assert.Equal(t, defaultRuntimeConfiguration, resolvedConfigPath) } else { assert.Equal(t, testConfig.ConfigPath, resolvedConfigPath) } assert.Equal(t, defaultRuntimeConfiguration, resolvedConfigPath) result := reflect.DeepEqual(config, testConfig.RuntimeConfig) if !result { t.Fatalf("Expected\n%+v\nGot\n%+v", config, testConfig.RuntimeConfig) } assert.True(t, result) err = os.RemoveAll(testConfig.LogDir) assert.NoError(t, err) } } } } func TestConfigLoadConfiguration(t *testing.T) { tmpdir, err := ioutil.TempDir(testDir, "load-config-") assert.NoError(t, err) defer os.RemoveAll(tmpdir) testLoadConfiguration(t, tmpdir, nil) } func TestConfigLoadConfigurationFailBrokenSymLink(t *testing.T) { tmpdir, err := ioutil.TempDir(testDir, "runtime-config-") assert.NoError(t, err) defer os.RemoveAll(tmpdir) testLoadConfiguration(t, tmpdir, func(config testRuntimeConfig, configFile string, ignoreLogging bool) (bool, error) { expectFail := false if configFile == config.ConfigPathLink { // break the symbolic link err = os.Remove(config.ConfigPathLink) if err != nil { return expectFail, err } expectFail = true } return expectFail, nil }) } func TestConfigLoadConfigurationFailSymLinkLoop(t *testing.T) { tmpdir, err := ioutil.TempDir(testDir, "runtime-config-") assert.NoError(t, err) defer os.RemoveAll(tmpdir) testLoadConfiguration(t, tmpdir, func(config testRuntimeConfig, configFile string, ignoreLogging bool) (bool, error) { expectFail := false if configFile == config.ConfigPathLink { // remove the config file err = os.Remove(config.ConfigPath) if err != nil { return expectFail, err } // now, create a sym-link loop err := os.Symlink(config.ConfigPathLink, config.ConfigPath) if err != nil { return expectFail, err } expectFail = true } return expectFail, nil }) } func TestConfigLoadConfigurationFailMissingHypervisor(t *testing.T) { tmpdir, err := ioutil.TempDir(testDir, "runtime-config-") assert.NoError(t, err) defer os.RemoveAll(tmpdir) testLoadConfiguration(t, tmpdir, func(config testRuntimeConfig, configFile string, ignoreLogging bool) (bool, error) { expectFail := true err = os.Remove(config.RuntimeConfig.HypervisorConfig.HypervisorPath) if err != nil { return expectFail, err } return expectFail, nil }) } func TestConfigLoadConfigurationFailMissingImage(t *testing.T) { tmpdir, err := ioutil.TempDir(testDir, "runtime-config-") assert.NoError(t, err) defer os.RemoveAll(tmpdir) testLoadConfiguration(t, tmpdir, func(config testRuntimeConfig, configFile string, ignoreLogging bool) (bool, error) { expectFail := true err = os.Remove(config.RuntimeConfig.HypervisorConfig.ImagePath) if err != nil { return expectFail, err } return expectFail, nil }) } func TestConfigLoadConfigurationFailMissingKernel(t *testing.T) { tmpdir, err := ioutil.TempDir(testDir, "runtime-config-") assert.NoError(t, err) defer os.RemoveAll(tmpdir) testLoadConfiguration(t, tmpdir, func(config testRuntimeConfig, configFile string, ignoreLogging bool) (bool, error) { expectFail := true err = os.Remove(config.RuntimeConfig.HypervisorConfig.KernelPath) if err != nil { return expectFail, err } return expectFail, nil }) } func TestConfigLoadConfigurationFailMissingShim(t *testing.T) { tmpdir, err := ioutil.TempDir(testDir, "runtime-config-") assert.NoError(t, err) defer os.RemoveAll(tmpdir) testLoadConfiguration(t, tmpdir, func(config testRuntimeConfig, configFile string, ignoreLogging bool) (bool, error) { expectFail := true shimConfig, ok := config.RuntimeConfig.ShimConfig.(vc.ShimConfig) if !ok { return expectFail, fmt.Errorf("cannot determine shim config") } err = os.Remove(shimConfig.Path) if err != nil { return expectFail, err } return expectFail, nil }) } func TestConfigLoadConfigurationFailUnreadableConfig(t *testing.T) { if os.Geteuid() == 0 { t.Skip(testDisabledNeedNonRoot) } tmpdir, err := ioutil.TempDir(testDir, "runtime-config-") assert.NoError(t, err) defer os.RemoveAll(tmpdir) testLoadConfiguration(t, tmpdir, func(config testRuntimeConfig, configFile string, ignoreLogging bool) (bool, error) { expectFail := true // make file unreadable by non-root user err = os.Chmod(config.ConfigPath, 0000) if err != nil { return expectFail, err } return expectFail, nil }) } func TestConfigLoadConfigurationFailTOMLConfigFileInvalidContents(t *testing.T) { if os.Geteuid() == 0 { t.Skip(testDisabledNeedNonRoot) } tmpdir, err := ioutil.TempDir(testDir, "runtime-config-") assert.NoError(t, err) defer os.RemoveAll(tmpdir) testLoadConfiguration(t, tmpdir, func(config testRuntimeConfig, configFile string, ignoreLogging bool) (bool, error) { expectFail := true err := createFile(config.ConfigPath, ` I am not TOML! ;-) I am invalid XML!`) if err != nil { return expectFail, err } return expectFail, nil }) } func TestConfigLoadConfigurationFailTOMLConfigFileDuplicatedData(t *testing.T) { if os.Geteuid() == 0 { t.Skip(testDisabledNeedNonRoot) } tmpdir, err := ioutil.TempDir(testDir, "runtime-config-") assert.NoError(t, err) defer os.RemoveAll(tmpdir) testLoadConfiguration(t, tmpdir, func(config testRuntimeConfig, configFile string, ignoreLogging bool) (bool, error) { expectFail := true text, err := getFileContents(config.ConfigPath) if err != nil { return expectFail, err } // create a config file containing two sets of // data. err = createFile(config.ConfigPath, fmt.Sprintf("%s\n%s\n", text, text)) if err != nil { return expectFail, err } return expectFail, nil }) } func TestMinimalRuntimeConfig(t *testing.T) { dir, err := ioutil.TempDir(testDir, "minimal-runtime-config-") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) shimPath := path.Join(dir, "shim") proxyPath := path.Join(dir, "proxy") hypervisorPath := path.Join(dir, "hypervisor") defaultHypervisorPath = hypervisorPath netmonPath := path.Join(dir, "netmon") imagePath := path.Join(dir, "image.img") initrdPath := path.Join(dir, "initrd.img") kernelPath := path.Join(dir, "kernel") savedDefaultImagePath := defaultImagePath savedDefaultInitrdPath := defaultInitrdPath savedDefaultHypervisorPath := defaultHypervisorPath savedDefaultKernelPath := defaultKernelPath defer func() { defaultImagePath = savedDefaultImagePath defaultInitrdPath = savedDefaultInitrdPath defaultHypervisorPath = savedDefaultHypervisorPath defaultKernelPath = savedDefaultKernelPath }() // Temporarily change the defaults to avoid this test using the real // resource files that might be installed on the system! defaultImagePath = imagePath defaultInitrdPath = initrdPath defaultHypervisorPath = hypervisorPath defaultKernelPath = kernelPath for _, file := range []string{defaultImagePath, defaultInitrdPath, defaultHypervisorPath, defaultKernelPath} { err = writeFile(file, "foo", testFileMode) if err != nil { t.Fatal(err) } } runtimeMinimalConfig := ` # Runtime configuration file [proxy.kata] path = "` + proxyPath + `" [shim.kata] path = "` + shimPath + `" [agent.kata] [netmon] path = "` + netmonPath + `" ` configPath := path.Join(dir, "runtime.toml") err = createConfig(configPath, runtimeMinimalConfig) if err != nil { t.Fatal(err) } _, config, err := loadConfiguration(configPath, false) if err == nil { t.Fatalf("Expected loadConfiguration to fail as shim path does not exist: %+v", config) } err = createEmptyFile(shimPath) if err != nil { t.Error(err) } err = createEmptyFile(proxyPath) if err != nil { t.Error(err) } err = createEmptyFile(hypervisorPath) if err != nil { t.Error(err) } err = createEmptyFile(netmonPath) if err != nil { t.Error(err) } _, config, err = loadConfiguration(configPath, false) if err != nil { t.Fatal(err) } expectedHypervisorConfig := vc.HypervisorConfig{ HypervisorPath: defaultHypervisorPath, KernelPath: defaultKernelPath, ImagePath: defaultImagePath, InitrdPath: defaultInitrdPath, HypervisorMachineType: defaultMachineType, NumVCPUs: defaultVCPUCount, DefaultMaxVCPUs: defaultMaxVCPUCount, MemorySize: defaultMemSize, DisableBlockDeviceUse: defaultDisableBlockDeviceUse, DefaultBridges: defaultBridgesCount, Mlock: !defaultEnableSwap, BlockDeviceDriver: defaultBlockDeviceDriver, Msize9p: defaultMsize9p, } expectedAgentConfig := vc.KataAgentConfig{} expectedProxyConfig := vc.ProxyConfig{ Path: proxyPath, } expectedShimConfig := vc.ShimConfig{ Path: shimPath, } expectedNetmonConfig := vc.NetmonConfig{ Path: netmonPath, Debug: false, Enable: false, } expectedConfig := oci.RuntimeConfig{ HypervisorType: defaultHypervisor, HypervisorConfig: expectedHypervisorConfig, AgentType: defaultAgent, AgentConfig: expectedAgentConfig, ProxyType: defaultProxy, ProxyConfig: expectedProxyConfig, ShimType: defaultShim, ShimConfig: expectedShimConfig, NetmonConfig: expectedNetmonConfig, } if reflect.DeepEqual(config, expectedConfig) == false { t.Fatalf("Got %+v\n expecting %+v", config, expectedConfig) } } func TestMinimalRuntimeConfigWithVsock(t *testing.T) { dir, err := ioutil.TempDir(testDir, "minimal-runtime-config-") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) imagePath := path.Join(dir, "image.img") initrdPath := path.Join(dir, "initrd.img") proxyPath := path.Join(dir, "proxy") shimPath := path.Join(dir, "shim") hypervisorPath := path.Join(dir, "hypervisor") kernelPath := path.Join(dir, "kernel") savedDefaultImagePath := defaultImagePath savedDefaultInitrdPath := defaultInitrdPath savedDefaultHypervisorPath := defaultHypervisorPath savedDefaultKernelPath := defaultKernelPath defer func() { defaultImagePath = savedDefaultImagePath defaultInitrdPath = savedDefaultInitrdPath defaultHypervisorPath = savedDefaultHypervisorPath defaultKernelPath = savedDefaultKernelPath }() // Temporarily change the defaults to avoid this test using the real // resource files that might be installed on the system! defaultImagePath = imagePath defaultInitrdPath = initrdPath defaultHypervisorPath = hypervisorPath defaultKernelPath = kernelPath for _, file := range []string{proxyPath, shimPath, hypervisorPath, kernelPath, imagePath} { err = writeFile(file, "foo", testFileMode) if err != nil { t.Fatal(err) } } // minimal config with vsock enabled runtimeMinimalConfig := ` # Runtime configuration file [hypervisor.qemu] use_vsock = true image = "` + imagePath + `" [proxy.kata] path = "` + proxyPath + `" [shim.kata] path = "` + shimPath + `" [agent.kata] ` orgVHostVSockDevicePath := utils.VHostVSockDevicePath orgVSockDevicePath := utils.VSockDevicePath defer func() { utils.VHostVSockDevicePath = orgVHostVSockDevicePath utils.VSockDevicePath = orgVSockDevicePath }() utils.VHostVSockDevicePath = "/dev/null" utils.VSockDevicePath = "/dev/null" configPath := path.Join(dir, "runtime.toml") err = createConfig(configPath, runtimeMinimalConfig) if err != nil { t.Fatal(err) } _, config, err := loadConfiguration(configPath, false) if err != nil { t.Fatal(err) } if config.ProxyType != vc.NoProxyType { t.Fatalf("Proxy type must be NoProxy, got %+v", config.ProxyType) } if !reflect.DeepEqual(config.ProxyConfig, vc.ProxyConfig{}) { t.Fatalf("Got %+v\n expecting %+v", config.ProxyConfig, vc.ProxyConfig{}) } if config.HypervisorConfig.UseVSock != true { t.Fatalf("use_vsock must be true, got %v", config.HypervisorConfig.UseVSock) } } func TestNewQemuHypervisorConfig(t *testing.T) { dir, err := ioutil.TempDir(testDir, "hypervisor-config-") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) hypervisorPath := path.Join(dir, "hypervisor") kernelPath := path.Join(dir, "kernel") imagePath := path.Join(dir, "image") machineType := "machineType" disableBlock := true enableIOThreads := true hotplugVFIOOnRootBus := true orgVSockDevicePath := utils.VSockDevicePath orgVHostVSockDevicePath := utils.VHostVSockDevicePath defer func() { utils.VSockDevicePath = orgVSockDevicePath utils.VHostVSockDevicePath = orgVHostVSockDevicePath }() utils.VSockDevicePath = "/dev/abc/xyz" utils.VHostVSockDevicePath = "/dev/abc/xyz" hypervisor := hypervisor{ Path: hypervisorPath, Kernel: kernelPath, Image: imagePath, MachineType: machineType, DisableBlockDeviceUse: disableBlock, EnableIOThreads: enableIOThreads, HotplugVFIOOnRootBus: hotplugVFIOOnRootBus, UseVSock: true, } files := []string{hypervisorPath, kernelPath, imagePath} filesLen := len(files) for i, file := range files { _, err := newQemuHypervisorConfig(hypervisor) if err == nil { t.Fatalf("Expected newQemuHypervisorConfig to fail as not all paths exist (not created %v)", strings.Join(files[i:filesLen], ",")) } // create the resource err = createEmptyFile(file) if err != nil { t.Error(err) } } // falling back to legacy serial port config, err := newQemuHypervisorConfig(hypervisor) if err != nil { t.Fatal(err) } utils.VSockDevicePath = "/dev/null" utils.VHostVSockDevicePath = "/dev/null" // all paths exist now config, err = newQemuHypervisorConfig(hypervisor) if err != nil { t.Fatal(err) } if config.HypervisorPath != hypervisor.Path { t.Errorf("Expected hypervisor path %v, got %v", hypervisor.Path, config.HypervisorPath) } if config.KernelPath != hypervisor.Kernel { t.Errorf("Expected kernel path %v, got %v", hypervisor.Kernel, config.KernelPath) } if config.ImagePath != hypervisor.Image { t.Errorf("Expected image path %v, got %v", hypervisor.Image, config.ImagePath) } if config.DisableBlockDeviceUse != disableBlock { t.Errorf("Expected value for disable block usage %v, got %v", disableBlock, config.DisableBlockDeviceUse) } if config.EnableIOThreads != enableIOThreads { t.Errorf("Expected value for enable IOThreads %v, got %v", enableIOThreads, config.EnableIOThreads) } if config.HotplugVFIOOnRootBus != hotplugVFIOOnRootBus { t.Errorf("Expected value for HotplugVFIOOnRootBus %v, got %v", hotplugVFIOOnRootBus, config.HotplugVFIOOnRootBus) } } func TestNewQemuHypervisorConfigImageAndInitrd(t *testing.T) { assert := assert.New(t) tmpdir, err := ioutil.TempDir(testDir, "") assert.NoError(err) defer os.RemoveAll(tmpdir) imagePath := filepath.Join(tmpdir, "image") initrdPath := filepath.Join(tmpdir, "initrd") hypervisorPath := path.Join(tmpdir, "hypervisor") kernelPath := path.Join(tmpdir, "kernel") for _, file := range []string{imagePath, initrdPath, hypervisorPath, kernelPath} { err = createEmptyFile(file) assert.NoError(err) } machineType := "machineType" disableBlock := true enableIOThreads := true hotplugVFIOOnRootBus := true hypervisor := hypervisor{ Path: hypervisorPath, Kernel: kernelPath, Image: imagePath, Initrd: initrdPath, MachineType: machineType, DisableBlockDeviceUse: disableBlock, EnableIOThreads: enableIOThreads, HotplugVFIOOnRootBus: hotplugVFIOOnRootBus, } _, err = newQemuHypervisorConfig(hypervisor) // specifying both an image+initrd is invalid assert.Error(err) } func TestNewShimConfig(t *testing.T) { dir, err := ioutil.TempDir(testDir, "shim-config-") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) shimPath := path.Join(dir, "shim") shim := shim{ Path: shimPath, } _, err = newShimConfig(shim) if err == nil { t.Fatalf("Expected newShimConfig to fail as no paths exist") } err = createEmptyFile(shimPath) if err != nil { t.Error(err) } shConfig, err := newShimConfig(shim) if err != nil { t.Fatalf("newShimConfig failed unexpectedly: %v", err) } if shConfig.Path != shimPath { t.Errorf("Expected shim path %v, got %v", shimPath, shConfig.Path) } } func TestHypervisorDefaults(t *testing.T) { assert := assert.New(t) numCPUs := goruntime.NumCPU() h := hypervisor{} assert.Equal(h.machineType(), defaultMachineType, "default hypervisor machine type wrong") assert.Equal(h.defaultVCPUs(), defaultVCPUCount, "default vCPU number is wrong") assert.Equal(h.defaultMaxVCPUs(), uint32(numCPUs), "default max vCPU number is wrong") assert.Equal(h.defaultMemSz(), defaultMemSize, "default memory size is wrong") machineType := "foo" h.MachineType = machineType assert.Equal(h.machineType(), machineType, "custom hypervisor machine type wrong") // auto inferring h.NumVCPUs = -1 assert.Equal(h.defaultVCPUs(), uint32(numCPUs), "default vCPU number is wrong") h.NumVCPUs = 2 assert.Equal(h.defaultVCPUs(), uint32(2), "default vCPU number is wrong") h.NumVCPUs = int32(numCPUs) + 1 assert.Equal(h.defaultVCPUs(), uint32(numCPUs), "default vCPU number is wrong") h.DefaultMaxVCPUs = 2 assert.Equal(h.defaultMaxVCPUs(), uint32(h.DefaultMaxVCPUs), "default max vCPU number is wrong") h.DefaultMaxVCPUs = uint32(numCPUs) + 1 assert.Equal(h.defaultMaxVCPUs(), uint32(numCPUs), "default max vCPU number is wrong") maxvcpus := vc.MaxQemuVCPUs() h.DefaultMaxVCPUs = uint32(maxvcpus) + 1 assert.Equal(h.defaultMaxVCPUs(), uint32(numCPUs), "default max vCPU number is wrong") h.MemorySize = 1024 assert.Equal(h.defaultMemSz(), uint32(1024), "default memory size is wrong") } func TestHypervisorDefaultsHypervisor(t *testing.T) { assert := assert.New(t) tmpdir, err := ioutil.TempDir(testDir, "") assert.NoError(err) defer os.RemoveAll(tmpdir) testHypervisorPath := filepath.Join(tmpdir, "hypervisor") testHypervisorLinkPath := filepath.Join(tmpdir, "hypervisor-link") err = createEmptyFile(testHypervisorPath) assert.NoError(err) err = syscall.Symlink(testHypervisorPath, testHypervisorLinkPath) assert.NoError(err) savedHypervisorPath := defaultHypervisorPath defer func() { defaultHypervisorPath = savedHypervisorPath }() defaultHypervisorPath = testHypervisorPath h := hypervisor{} p, err := h.path() assert.NoError(err) assert.Equal(p, defaultHypervisorPath, "default hypervisor path wrong") // test path resolution defaultHypervisorPath = testHypervisorLinkPath h = hypervisor{} p, err = h.path() assert.NoError(err) assert.Equal(p, testHypervisorPath) } func TestHypervisorDefaultsKernel(t *testing.T) { assert := assert.New(t) tmpdir, err := ioutil.TempDir(testDir, "") assert.NoError(err) defer os.RemoveAll(tmpdir) testKernelPath := filepath.Join(tmpdir, "kernel") testKernelLinkPath := filepath.Join(tmpdir, "kernel-link") err = createEmptyFile(testKernelPath) assert.NoError(err) err = syscall.Symlink(testKernelPath, testKernelLinkPath) assert.NoError(err) savedKernelPath := defaultKernelPath defer func() { defaultKernelPath = savedKernelPath }() defaultKernelPath = testKernelPath h := hypervisor{} p, err := h.kernel() assert.NoError(err) assert.Equal(p, defaultKernelPath, "default Kernel path wrong") // test path resolution defaultKernelPath = testKernelLinkPath h = hypervisor{} p, err = h.kernel() assert.NoError(err) assert.Equal(p, testKernelPath) assert.Equal(h.kernelParams(), defaultKernelParams, "default hypervisor image wrong") kernelParams := "foo=bar xyz" h.KernelParams = kernelParams assert.Equal(h.kernelParams(), kernelParams, "custom hypervisor kernel parameterms wrong") } // The default initrd path is not returned by h.initrd() func TestHypervisorDefaultsInitrd(t *testing.T) { assert := assert.New(t) tmpdir, err := ioutil.TempDir(testDir, "") assert.NoError(err) defer os.RemoveAll(tmpdir) testInitrdPath := filepath.Join(tmpdir, "initrd") testInitrdLinkPath := filepath.Join(tmpdir, "initrd-link") err = createEmptyFile(testInitrdPath) assert.NoError(err) err = syscall.Symlink(testInitrdPath, testInitrdLinkPath) assert.NoError(err) savedInitrdPath := defaultInitrdPath defer func() { defaultInitrdPath = savedInitrdPath }() defaultInitrdPath = testInitrdPath h := hypervisor{} p, err := h.initrd() assert.NoError(err) assert.Equal(p, "", "default Image path wrong") // test path resolution defaultInitrdPath = testInitrdLinkPath h = hypervisor{} p, err = h.initrd() assert.NoError(err) assert.Equal(p, "") } // The default image path is not returned by h.image() func TestHypervisorDefaultsImage(t *testing.T) { assert := assert.New(t) tmpdir, err := ioutil.TempDir(testDir, "") assert.NoError(err) defer os.RemoveAll(tmpdir) testImagePath := filepath.Join(tmpdir, "image") testImageLinkPath := filepath.Join(tmpdir, "image-link") err = createEmptyFile(testImagePath) assert.NoError(err) err = syscall.Symlink(testImagePath, testImageLinkPath) assert.NoError(err) savedImagePath := defaultImagePath defer func() { defaultImagePath = savedImagePath }() defaultImagePath = testImagePath h := hypervisor{} p, err := h.image() assert.NoError(err) assert.Equal(p, "", "default Image path wrong") // test path resolution defaultImagePath = testImageLinkPath h = hypervisor{} p, err = h.image() assert.NoError(err) assert.Equal(p, "") } func TestProxyDefaults(t *testing.T) { p := proxy{} assert.Equal(t, p.path(), defaultProxyPath, "default proxy path wrong") path := "/foo/bar/baz/proxy" p.Path = path assert.Equal(t, p.path(), path, "custom proxy path wrong") } func TestShimDefaults(t *testing.T) { assert := assert.New(t) tmpdir, err := ioutil.TempDir(testDir, "") assert.NoError(err) defer os.RemoveAll(tmpdir) testShimPath := filepath.Join(tmpdir, "shim") testShimLinkPath := filepath.Join(tmpdir, "shim-link") err = createEmptyFile(testShimPath) assert.NoError(err) err = syscall.Symlink(testShimPath, testShimLinkPath) assert.NoError(err) savedShimPath := defaultShimPath defer func() { defaultShimPath = savedShimPath }() defaultShimPath = testShimPath s := shim{} p, err := s.path() assert.NoError(err) assert.Equal(p, defaultShimPath, "default shim path wrong") // test path resolution defaultShimPath = testShimLinkPath s = shim{} p, err = s.path() assert.NoError(err) assert.Equal(p, testShimPath) assert.False(s.debug()) s.Debug = true assert.True(s.debug()) } func TestGetDefaultConfigFilePaths(t *testing.T) { assert := assert.New(t) results := getDefaultConfigFilePaths() // There should be atleast two config file locations assert.True(len(results) >= 2) for _, f := range results { // Paths cannot be empty assert.NotNil(f) } } func TestGetDefaultConfigFile(t *testing.T) { assert := assert.New(t) tmpdir, err := ioutil.TempDir(testDir, "") assert.NoError(err) defer os.RemoveAll(tmpdir) hypervisor := "qemu" confDir := filepath.Join(tmpdir, "conf") sysConfDir := filepath.Join(tmpdir, "sysconf") for _, dir := range []string{confDir, sysConfDir} { err = os.MkdirAll(dir, testDirMode) assert.NoError(err) } confDirConfig, err := createAllRuntimeConfigFiles(confDir, hypervisor) assert.NoError(err) sysConfDirConfig, err := createAllRuntimeConfigFiles(sysConfDir, hypervisor) assert.NoError(err) savedConf := defaultRuntimeConfiguration savedSysConf := defaultSysConfRuntimeConfiguration defaultRuntimeConfiguration = confDirConfig.ConfigPath defaultSysConfRuntimeConfiguration = sysConfDirConfig.ConfigPath defer func() { defaultRuntimeConfiguration = savedConf defaultSysConfRuntimeConfiguration = savedSysConf }() got, err := getDefaultConfigFile() assert.NoError(err) // defaultSysConfRuntimeConfiguration has priority over defaultRuntimeConfiguration assert.Equal(got, defaultSysConfRuntimeConfiguration) // force defaultRuntimeConfiguration to be returned os.Remove(defaultSysConfRuntimeConfiguration) got, err = getDefaultConfigFile() assert.NoError(err) assert.Equal(got, defaultRuntimeConfiguration) // force error os.Remove(defaultRuntimeConfiguration) _, err = getDefaultConfigFile() assert.Error(err) } func TestDefaultBridges(t *testing.T) { assert := assert.New(t) h := hypervisor{DefaultBridges: 0} bridges := h.defaultBridges() assert.Equal(defaultBridgesCount, bridges) h.DefaultBridges = maxPCIBridges + 1 bridges = h.defaultBridges() assert.Equal(maxPCIBridges, bridges) h.DefaultBridges = maxPCIBridges bridges = h.defaultBridges() assert.Equal(maxPCIBridges, bridges) } func TestDefaultFirmware(t *testing.T) { assert := assert.New(t) // save default firmware path oldDefaultFirmwarePath := defaultFirmwarePath f, err := ioutil.TempFile(os.TempDir(), "qboot.bin") assert.NoError(err) assert.NoError(f.Close()) defer os.RemoveAll(f.Name()) h := hypervisor{} defaultFirmwarePath = "" p, err := h.firmware() assert.NoError(err) assert.Empty(p) defaultFirmwarePath = f.Name() p, err = h.firmware() assert.NoError(err) assert.NotEmpty(p) // restore default firmware path defaultFirmwarePath = oldDefaultFirmwarePath } func TestDefaultMachineAccelerators(t *testing.T) { assert := assert.New(t) machineAccelerators := "abc,123,rgb" h := hypervisor{MachineAccelerators: machineAccelerators} assert.Equal(machineAccelerators, h.machineAccelerators()) machineAccelerators = "" h.MachineAccelerators = machineAccelerators assert.Equal(machineAccelerators, h.machineAccelerators()) machineAccelerators = "abc" h.MachineAccelerators = machineAccelerators assert.Equal(machineAccelerators, h.machineAccelerators()) machineAccelerators = "abc,123" h.MachineAccelerators = "abc,,123" assert.Equal(machineAccelerators, h.machineAccelerators()) machineAccelerators = "abc,123" h.MachineAccelerators = ",,abc,,123,,," assert.Equal(machineAccelerators, h.machineAccelerators()) machineAccelerators = "abc,123" h.MachineAccelerators = "abc,,123,,," assert.Equal(machineAccelerators, h.machineAccelerators()) machineAccelerators = "abc" h.MachineAccelerators = ",,abc," assert.Equal(machineAccelerators, h.machineAccelerators()) machineAccelerators = "abc" h.MachineAccelerators = ", , abc , ," assert.Equal(machineAccelerators, h.machineAccelerators()) machineAccelerators = "abc" h.MachineAccelerators = " abc " assert.Equal(machineAccelerators, h.machineAccelerators()) machineAccelerators = "abc,123" h.MachineAccelerators = ", abc , 123 ," assert.Equal(machineAccelerators, h.machineAccelerators()) machineAccelerators = "abc,123" h.MachineAccelerators = ",, abc ,,, 123 ,," assert.Equal(machineAccelerators, h.machineAccelerators()) } func TestUpdateRuntimeConfiguration(t *testing.T) { assert := assert.New(t) assert.NotEqual(defaultAgent, vc.HyperstartAgent) config := oci.RuntimeConfig{} tomlConf := tomlConfig{ Agent: map[string]agent{ // force a non-default value kataAgentTableType: {}, }, } assert.NotEqual(config.AgentType, vc.AgentType(kataAgentTableType)) assert.NotEqual(config.AgentConfig, vc.KataAgentConfig{}) err := updateRuntimeConfig("", tomlConf, &config) assert.NoError(err) assert.Equal(config.AgentType, vc.AgentType(kataAgentTableType)) assert.Equal(config.AgentConfig, vc.KataAgentConfig{}) } func TestUpdateRuntimeConfigurationVMConfig(t *testing.T) { assert := assert.New(t) vcpus := uint(2) mem := uint32(2048) config := oci.RuntimeConfig{} expectedVMConfig := mem tomlConf := tomlConfig{ Hypervisor: map[string]hypervisor{ qemuHypervisorTableType: { NumVCPUs: int32(vcpus), MemorySize: uint32(mem), Path: "/", Kernel: "/", Image: "/", Firmware: "/", }, }, } err := updateRuntimeConfig("", tomlConf, &config) assert.NoError(err) assert.Equal(expectedVMConfig, config.HypervisorConfig.MemorySize) } func TestUpdateRuntimeConfigurationFactoryConfig(t *testing.T) { assert := assert.New(t) config := oci.RuntimeConfig{} expectedFactoryConfig := oci.FactoryConfig{ Template: true, } tomlConf := tomlConfig{Factory: factory{Template: true}} err := updateRuntimeConfig("", tomlConf, &config) assert.NoError(err) assert.Equal(expectedFactoryConfig, config.FactoryConfig) } func TestCheckHypervisorConfig(t *testing.T) { assert := assert.New(t) dir, err := ioutil.TempDir(testDir, "") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) // Not created on purpose imageENOENT := filepath.Join(dir, "image-ENOENT.img") initrdENOENT := filepath.Join(dir, "initrd-ENOENT.img") imageEmpty := filepath.Join(dir, "image-empty.img") initrdEmpty := filepath.Join(dir, "initrd-empty.img") for _, file := range []string{imageEmpty, initrdEmpty} { err = createEmptyFile(file) assert.NoError(err) } image := filepath.Join(dir, "image.img") initrd := filepath.Join(dir, "initrd.img") mb := uint32(1024 * 1024) fileSizeMB := uint32(3) fileSizeBytes := fileSizeMB * mb fileData := strings.Repeat("X", int(fileSizeBytes)) for _, file := range []string{image, initrd} { err = writeFile(file, fileData, testFileMode) assert.NoError(err) } type testData struct { imagePath string initrdPath string memBytes uint32 expectError bool expectLogWarning bool } // Note that checkHypervisorConfig() does not check to ensure an image // or an initrd has been specified - that's handled by a separate // function, hence no test for it here. data := []testData{ {"", "", 0, true, false}, {imageENOENT, "", 2, true, false}, {"", initrdENOENT, 2, true, false}, {imageEmpty, "", 2, true, false}, {"", initrdEmpty, 2, true, false}, {image, "", fileSizeMB + 2, false, false}, {image, "", fileSizeMB + 1, false, false}, {image, "", fileSizeMB + 0, false, true}, {image, "", fileSizeMB - 1, false, true}, {image, "", fileSizeMB - 2, false, true}, {"", initrd, fileSizeMB + 2, false, false}, {"", initrd, fileSizeMB + 1, false, false}, {"", initrd, fileSizeMB + 0, true, false}, {"", initrd, fileSizeMB - 1, true, false}, {"", initrd, fileSizeMB - 2, true, false}, } for i, d := range data { savedOut := kataLog.Logger.Out // create buffer to save logger output logBuf := &bytes.Buffer{} // capture output to buffer kataLog.Logger.Out = logBuf config := vc.HypervisorConfig{ ImagePath: d.imagePath, InitrdPath: d.initrdPath, MemorySize: d.memBytes, } err := checkHypervisorConfig(config) if d.expectError { assert.Error(err, "test %d (%+v)", i, d) } else { assert.NoError(err, "test %d (%+v)", i, d) } if d.expectLogWarning { assert.True(strings.Contains(logBuf.String(), "warning")) } else { assert.Empty(logBuf.String()) } // reset logger kataLog.Logger.Out = savedOut } }