mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-07-04 02:56:18 +00:00
config: Detect if VM memory smaller than image
Add a heuristic to ensure the amount of memory allocated to the hypervisor is bigger than the size of the image. This catches simple configuration issues where `default_memory=` is set to a smaller value than the size of either the `image=` or `initrd=` files. If the configured image type is `initrd`, fail but only warn in the logs for `image` as although it seems a highly unlikely scenario, it is permitted. Update tests to ensure that created resources have `>0` bytes. Fixes #636. Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
This commit is contained in:
parent
1ba4841865
commit
b5ea753ff4
@ -577,9 +577,79 @@ func loadConfiguration(configPath string, ignoreLogging bool) (resolvedConfigPat
|
|||||||
config.ProxyConfig = vc.ProxyConfig{}
|
config.ProxyConfig = vc.ProxyConfig{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := checkHypervisorConfig(config.HypervisorConfig); err != nil {
|
||||||
|
return "", config, err
|
||||||
|
}
|
||||||
|
|
||||||
return resolved, config, nil
|
return resolved, config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkHypervisorConfig performs basic "sanity checks" on the hypervisor
|
||||||
|
// config.
|
||||||
|
func checkHypervisorConfig(config vc.HypervisorConfig) error {
|
||||||
|
type image struct {
|
||||||
|
path string
|
||||||
|
initrd bool
|
||||||
|
}
|
||||||
|
|
||||||
|
images := []image{
|
||||||
|
{
|
||||||
|
path: config.ImagePath,
|
||||||
|
initrd: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: config.InitrdPath,
|
||||||
|
initrd: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
memSizeMB := int64(config.DefaultMemSz)
|
||||||
|
|
||||||
|
if memSizeMB == 0 {
|
||||||
|
return errors.New("VM memory cannot be zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
mb := int64(1024 * 1024)
|
||||||
|
|
||||||
|
for _, image := range images {
|
||||||
|
if image.path == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
imageSizeBytes, err := fileSize(image.path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if imageSizeBytes == 0 {
|
||||||
|
return fmt.Errorf("image %q is empty", image.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if imageSizeBytes > mb {
|
||||||
|
imageSizeMB := imageSizeBytes / mb
|
||||||
|
|
||||||
|
msg := fmt.Sprintf("VM memory (%dMB) smaller than image %q size (%dMB)",
|
||||||
|
memSizeMB, image.path, imageSizeMB)
|
||||||
|
|
||||||
|
if imageSizeMB >= memSizeMB {
|
||||||
|
if image.initrd {
|
||||||
|
// Initrd's need to be fully read into memory
|
||||||
|
return errors.New(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Images do not need to be fully read
|
||||||
|
// into memory, but it would be highly
|
||||||
|
// unusual to have an image larger
|
||||||
|
// than the amount of memory assigned
|
||||||
|
// to the VM.
|
||||||
|
kataLog.Warn(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// getDefaultConfigFilePaths returns a list of paths that will be
|
// getDefaultConfigFilePaths returns a list of paths that will be
|
||||||
// considered as configuration files in priority order.
|
// considered as configuration files in priority order.
|
||||||
func getDefaultConfigFilePaths() []string {
|
func getDefaultConfigFilePaths() []string {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@ -119,8 +120,8 @@ func createAllRuntimeConfigFiles(dir, hypervisor string) (config testRuntimeConf
|
|||||||
files := []string{hypervisorPath, kernelPath, imagePath, shimPath, proxyPath}
|
files := []string{hypervisorPath, kernelPath, imagePath, shimPath, proxyPath}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
// create the resource
|
// create the resource (which must be >0 bytes)
|
||||||
err = createEmptyFile(file)
|
err := writeFile(file, "foo", testFileMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return config, err
|
return config, err
|
||||||
}
|
}
|
||||||
@ -1306,3 +1307,107 @@ func TestUpdateRuntimeConfigurationFactoryConfig(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(expectedFactoryConfig, config.FactoryConfig)
|
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,
|
||||||
|
DefaultMemSz: 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -76,7 +76,8 @@ func makeRuntimeConfig(prefixDir string) (configFile string, config oci.RuntimeC
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range filesToCreate {
|
for _, file := range filesToCreate {
|
||||||
err := createEmptyFile(file)
|
// files must exist and be >0 bytes.
|
||||||
|
err := writeFile(file, "foo", testFileMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", oci.RuntimeConfig{}, err
|
return "", oci.RuntimeConfig{}, err
|
||||||
}
|
}
|
||||||
|
13
cli/utils.go
13
cli/utils.go
@ -13,6 +13,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -230,3 +231,15 @@ func writeFile(filePath string, data string, fileMode os.FileMode) error {
|
|||||||
func isEmptyString(b []byte) bool {
|
func isEmptyString(b []byte) bool {
|
||||||
return len(bytes.Trim(b, "\n")) == 0
|
return len(bytes.Trim(b, "\n")) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fileSize returns the number of bytes in the specified file
|
||||||
|
func fileSize(file string) (int64, error) {
|
||||||
|
st := syscall.Stat_t{}
|
||||||
|
|
||||||
|
err := syscall.Stat(file, &st)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return st.Size, nil
|
||||||
|
}
|
||||||
|
@ -362,3 +362,37 @@ func TestWriteFileErrNoPath(t *testing.T) {
|
|||||||
err = writeFile(dir, "", 0000)
|
err = writeFile(dir, "", 0000)
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFileSize(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
dir, err := ioutil.TempDir(testDir, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
file := filepath.Join(dir, "foo")
|
||||||
|
|
||||||
|
// ENOENT
|
||||||
|
_, err = fileSize(file)
|
||||||
|
assert.Error(err)
|
||||||
|
|
||||||
|
err = createEmptyFile(file)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
// zero size
|
||||||
|
size, err := fileSize(file)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(size, int64(0))
|
||||||
|
|
||||||
|
msg := "hello"
|
||||||
|
msgLen := len(msg)
|
||||||
|
|
||||||
|
err = writeFile(file, msg, testFileMode)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
size, err = fileSize(file)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(size, int64(msgLen))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user