mirror of
https://github.com/kubevirt/containerized-data-importer.git
synced 2025-06-03 06:30:22 +00:00

* Fix qemu O_DIRECT refusing unit tests
These tests relied on tmpfs not supporting direct IO,
but that has changed recently with e88e0d366f
Refactored the direct IO utils in a way that allows testing without
real filesystems.
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
* Drop tmpfs volume from unit tests container
This is not needed anymore, since the concerning unit tests
now avoid real IO altogether.
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
---------
Signed-off-by: Alex Kalenyuk <akalenyu@redhat.com>
645 lines
21 KiB
Go
645 lines
21 KiB
Go
/*
|
|
Copyright 2018 The CDI Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
package image
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"syscall"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
|
|
"kubevirt.io/containerized-data-importer/pkg/common"
|
|
metrics "kubevirt.io/containerized-data-importer/pkg/monitoring/metrics/cdi-importer"
|
|
"kubevirt.io/containerized-data-importer/pkg/system"
|
|
"kubevirt.io/containerized-data-importer/pkg/util/prometheus"
|
|
)
|
|
|
|
// FakeODirectRefusingOS mocks out certain OS calls to avoid perturbing the filesystem
|
|
// If a member of the form `*Fn` is set, that function will be called in place
|
|
// of the real call.
|
|
type FakeODirectRefusingOS struct{}
|
|
|
|
// Stat is a fake that returns an error
|
|
func (FakeODirectRefusingOS) Stat(path string) (os.FileInfo, error) {
|
|
return nil, os.ErrNotExist
|
|
}
|
|
|
|
// Remove is a fake call that returns nil.
|
|
func (FakeODirectRefusingOS) Remove(path string) error {
|
|
return nil
|
|
}
|
|
|
|
// OpenFile is a fake call that return nil.
|
|
func (FakeODirectRefusingOS) OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) {
|
|
if flag&syscall.O_DIRECT != 0 {
|
|
return nil, &os.PathError{Op: "open", Path: name, Err: syscall.EINVAL}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
const goodValidateJSON = `
|
|
{
|
|
"virtual-size": 4294967296,
|
|
"filename": "myimage.qcow2",
|
|
"cluster-size": 65536,
|
|
"format": "qcow2",
|
|
"actual-size": 262152192,
|
|
"format-specific": {
|
|
"type": "qcow2",
|
|
"data": {
|
|
"compat": "0.10",
|
|
"refcount-bits": 16
|
|
}
|
|
},
|
|
"dirty-flag": false
|
|
}
|
|
`
|
|
|
|
const hugeValidateJSON = `
|
|
{
|
|
"virtual-size": 52949672960,
|
|
"filename": "myimage.qcow2",
|
|
"cluster-size": 65536,
|
|
"format": "qcow2",
|
|
"actual-size": 262152192,
|
|
"format-specific": {
|
|
"type": "qcow2",
|
|
"data": {
|
|
"compat": "0.10",
|
|
"refcount-bits": 16
|
|
}
|
|
},
|
|
"dirty-flag": false
|
|
}
|
|
`
|
|
|
|
const badValidateJSON = `
|
|
{
|
|
"virtual-size": 4294967296,
|
|
"filename": "myimage.qcow2",
|
|
"cluster-size": 65536,
|
|
"format": "qcow2",
|
|
"actual-size": 262152192,
|
|
"format-specific": {
|
|
"type": "qcow2",
|
|
"data": {
|
|
"compat": "0.10",
|
|
"refcount-bits": 16
|
|
}
|
|
},
|
|
"dirty-flag": false
|
|
`
|
|
|
|
const badFormatValidateJSON = `
|
|
{
|
|
"virtual-size": 4294967296,
|
|
"filename": "myimage.qcow2",
|
|
"cluster-size": 65536,
|
|
"format": "raw2",
|
|
"actual-size": 262152192,
|
|
"dirty-flag": false
|
|
}
|
|
`
|
|
|
|
const backingFileValidateJSON = `
|
|
{
|
|
"virtual-size": 4294967296,
|
|
"filename": "myimage.qcow2",
|
|
"cluster-size": 65536,
|
|
"format": "qcow2",
|
|
"actual-size": 262152192,
|
|
"format-specific": {
|
|
"type": "qcow2",
|
|
"data": {
|
|
"compat": "0.10",
|
|
"refcount-bits": 16
|
|
}
|
|
},
|
|
"backing-filename": "backing-file.qcow2",
|
|
"dirty-flag": false
|
|
}
|
|
`
|
|
|
|
type execFunctionType func(*system.ProcessLimitValues, func(string), string, ...string) ([]byte, error)
|
|
|
|
func init() {
|
|
ownerUID = "1111-1111-111"
|
|
}
|
|
|
|
var expectedLimits = &system.ProcessLimitValues{AddressSpaceLimit: 1 << 30, CPUTimeLimit: 30}
|
|
|
|
var _ = Describe("Convert to Raw", func() {
|
|
var tmpDir, destPath string
|
|
|
|
BeforeEach(func() {
|
|
var err error
|
|
// dest is usually not tmpfs, stay honest in unit tests as well
|
|
tmpDir, err = os.MkdirTemp("/var/tmp", "qemutestdest")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
By("tmpDir: " + tmpDir)
|
|
destPath = filepath.Join(tmpDir, "dest")
|
|
_, err = os.Create(destPath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
AfterEach(func() {
|
|
os.RemoveAll(tmpDir)
|
|
})
|
|
|
|
It("should return no error if exec function returns no error", func() {
|
|
replaceExecFunction(mockExecFunction("", "", nil, "convert", "-p", "-O", "raw", "source", destPath), func() {
|
|
err := convertToRaw("source", destPath, false, "")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
It("should return conversion error if exec function returns error", func() {
|
|
replaceExecFunction(mockExecFunction("", "exit 1", nil, "convert", "-p", "-O", "raw", "source", destPath), func() {
|
|
err := convertToRaw("source", destPath, false, "")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(strings.Contains(err.Error(), "could not convert image to raw")).To(BeTrue())
|
|
})
|
|
})
|
|
|
|
It("should stream file to destination", func() {
|
|
replaceExecFunction(mockExecFunction("", "", nil, "convert", "-p", "-O", "raw", "/somefile/somewhere", destPath), func() {
|
|
ep, err := url.Parse("/somefile/somewhere")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ConvertToRawStream(ep, destPath, false, "")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
It("should add preallocation if requested", func() {
|
|
replaceExecFunction(mockExecFunctionStrict("", "", nil, "convert", "-o", "preallocation=falloc", "-t", "writeback", "-p", "-O", "raw", "/somefile/somewhere", destPath), func() {
|
|
ep, err := url.Parse("/somefile/somewhere")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ConvertToRawStream(ep, destPath, true, "")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
It("should not add preallocation if not requested", func() {
|
|
replaceExecFunction(mockExecFunctionStrict("", "", nil, "convert", "-t", "writeback", "-p", "-O", "raw", "/somefile/somewhere", destPath), func() {
|
|
ep, err := url.Parse("/somefile/somewhere")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ConvertToRawStream(ep, destPath, false, "")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("cache mode adjusted according to O_DIRECT support", func() {
|
|
var tmpFsDir string
|
|
var originalODirectChecker DirectIOChecker
|
|
|
|
BeforeEach(func() {
|
|
var err error
|
|
|
|
tmpFsDir, err = os.MkdirTemp("/var/tmp", "qemutestdestontmpfs")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
By("tmpFsDir: " + tmpFsDir)
|
|
originalODirectChecker = odirectChecker
|
|
})
|
|
|
|
AfterEach(func() {
|
|
os.RemoveAll(tmpFsDir)
|
|
odirectChecker = originalODirectChecker
|
|
})
|
|
|
|
It("should use cache=none when destination supports O_DIRECT", func() {
|
|
replaceExecFunction(mockExecFunctionStrict("", "", nil, "convert", "-t", "none", "-p", "-O", "raw", "/somefile/somewhere", destPath), func() {
|
|
ep, err := url.Parse("/somefile/somewhere")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ConvertToRawStream(ep, destPath, false, common.CacheModeTryNone)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
It("should use cache=writeback when destination does not support O_DIRECT", func() {
|
|
odirectChecker = NewDirectIOChecker(FakeODirectRefusingOS{})
|
|
|
|
tmpFsDestPath := filepath.Join(tmpFsDir, "dest")
|
|
_, err := os.Create(tmpFsDestPath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
replaceExecFunction(mockExecFunctionStrict("", "", nil, "convert", "-t", "writeback", "-p", "-O", "raw", "/somefile/somewhere", tmpFsDestPath), func() {
|
|
ep, err := url.Parse("/somefile/somewhere")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ConvertToRawStream(ep, tmpFsDestPath, false, common.CacheModeTryNone)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Resize", func() {
|
|
It("Should complete successfully if qemu-img resize succeeds", func() {
|
|
quantity, err := resource.ParseQuantity("10Gi")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
size := convertQuantityToQemuSize(quantity)
|
|
replaceExecFunction(mockExecFunction("", "", nil, "resize", "-f", "raw", "image", size), func() {
|
|
o := NewQEMUOperations()
|
|
err = o.Resize("image", quantity, false)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
It("Should fail if qemu-img resize fails", func() {
|
|
quantity, err := resource.ParseQuantity("10Gi")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
size := convertQuantityToQemuSize(quantity)
|
|
replaceExecFunction(mockExecFunction("", "exit 1", nil, "resize", "-f", "raw", "image", size), func() {
|
|
o := NewQEMUOperations()
|
|
err = o.Resize("image", quantity, false)
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(strings.Contains(err.Error(), "Error resizing image")).To(BeTrue())
|
|
})
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Validate", func() {
|
|
imageName, _ := url.Parse("myimage.qcow2")
|
|
|
|
DescribeTable("Validate should", func(execfunc execFunctionType, errString string, image *url.URL) {
|
|
replaceExecFunction(execfunc, func() {
|
|
err := Validate(image, 42949672960)
|
|
|
|
if errString == "" {
|
|
Expect(err).NotTo(HaveOccurred())
|
|
} else {
|
|
Expect(err).To(HaveOccurred())
|
|
rootErr := errors.Cause(err)
|
|
if rootErr.Error() != errString {
|
|
Fail(fmt.Sprintf("got wrong failure: [%s], expected [%s]", rootErr, errString))
|
|
}
|
|
}
|
|
})
|
|
},
|
|
Entry("should return success", mockExecFunction(goodValidateJSON, "", expectedLimits, "info", "--output=json", imageName.String()), "", imageName),
|
|
Entry("should return error", mockExecFunction("explosion", "exit 1", expectedLimits), "explosion, exit 1", imageName),
|
|
Entry("should return error on bad json", mockExecFunction(badValidateJSON, "", expectedLimits), "unexpected end of JSON input", imageName),
|
|
Entry("should return error on bad format", mockExecFunction(badFormatValidateJSON, "", expectedLimits), fmt.Sprintf("Invalid format raw2 for image %s", imageName), imageName),
|
|
Entry("should return error on invalid backing file", mockExecFunction(backingFileValidateJSON, "", expectedLimits), fmt.Sprintf("Image %s is invalid because it has invalid backing file backing-file.qcow2", imageName), imageName),
|
|
Entry("should return error when PVC is too small", mockExecFunction(hugeValidateJSON, "", expectedLimits), fmt.Sprintf("virtual image size %d is larger than the reported available storage %d. A larger PVC is required", 52949672960, 42949672960), imageName),
|
|
)
|
|
|
|
})
|
|
|
|
var _ = Describe("Report Progress", func() {
|
|
var progressMetric prometheus.ProgressMetric
|
|
|
|
BeforeEach(func() {
|
|
err := metrics.SetupMetrics()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
progressMetric = metrics.Progress(ownerUID)
|
|
})
|
|
|
|
AfterEach(func() {
|
|
progressMetric.Delete()
|
|
})
|
|
|
|
It("Parse valid progress line", func() {
|
|
By("Verifying the initial value is 0")
|
|
progressMetric.Add(0)
|
|
progress, err := progressMetric.Get()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(progress).To(Equal(float64(0)))
|
|
By("Calling reportProgress with value")
|
|
reportProgress("(45.34/100%)")
|
|
progress, err = progressMetric.Get()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(progress).To(Equal(45.34))
|
|
})
|
|
|
|
It("Parse invalid progress line", func() {
|
|
By("Verifying the initial value is 0")
|
|
progressMetric.Add(0)
|
|
progress, err := progressMetric.Get()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(progress).To(Equal(float64(0)))
|
|
By("Calling reportProgress with invalid value")
|
|
reportProgress("45.34")
|
|
progress, err = progressMetric.Get()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(progress).To(Equal(float64(0)))
|
|
})
|
|
})
|
|
|
|
var _ = Describe("quantity to qemu", func() {
|
|
It("Should properly parse quantity to qemu", func() {
|
|
result := convertQuantityToQemuSize(resource.MustParse("1Gi"))
|
|
Expect(result).To(Equal("1073741824"))
|
|
result = convertQuantityToQemuSize(resource.MustParse("1G"))
|
|
Expect(result).To(Equal("1000000000"))
|
|
result = convertQuantityToQemuSize(resource.MustParse("10Ki"))
|
|
Expect(result).To(Equal("10240"))
|
|
result = convertQuantityToQemuSize(resource.MustParse("10k"))
|
|
Expect(result).To(Equal("10000"))
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Create blank image", func() {
|
|
var tmpDir, destPath string
|
|
|
|
BeforeEach(func() {
|
|
var err error
|
|
tmpDir, err = os.MkdirTemp(os.TempDir(), "qemutestdest")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
By("tmpDir: " + tmpDir)
|
|
destPath = filepath.Join(tmpDir, "dest")
|
|
_, err = os.Create(destPath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
AfterEach(func() {
|
|
os.RemoveAll(tmpDir)
|
|
})
|
|
|
|
It("Should complete successfully if qemu-img resize succeeds", func() {
|
|
quantity, err := resource.ParseQuantity("10Gi")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
size := convertQuantityToQemuSize(quantity)
|
|
replaceExecFunction(mockExecFunction("", "", nil, "create", "-f", "raw", destPath, size), func() {
|
|
err = CreateBlankImage(destPath, quantity, false)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
It("Should fail if qemu-img resize fails", func() {
|
|
quantity, err := resource.ParseQuantity("10Gi")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
size := convertQuantityToQemuSize(quantity)
|
|
replaceExecFunction(mockExecFunction("", "exit 1", nil, "create", "-f", "raw", destPath, size), func() {
|
|
err = CreateBlankImage(destPath, quantity, false)
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(strings.Contains(err.Error(), "could not create raw image with size ")).To(BeTrue())
|
|
})
|
|
})
|
|
|
|
It("should add preallocation if requested", func() {
|
|
quantity, err := resource.ParseQuantity("10Gi")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
size := convertQuantityToQemuSize(quantity)
|
|
replaceExecFunction(mockExecFunctionStrict("", "", nil, "create", "-f", "raw", destPath, size, "-o", "preallocation=falloc"), func() {
|
|
err = CreateBlankImage(destPath, quantity, true)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
It("should not add preallocation if not requested", func() {
|
|
quantity, err := resource.ParseQuantity("10Gi")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
size := convertQuantityToQemuSize(quantity)
|
|
replaceExecFunction(mockExecFunctionStrict("", "", nil, "create", "-f", "raw", destPath, size), func() {
|
|
err = CreateBlankImage(destPath, quantity, false)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Create preallocated blank block", func() {
|
|
var tmpDir, tmpFsDir, destPath string
|
|
var originalODirectChecker DirectIOChecker
|
|
|
|
BeforeEach(func() {
|
|
var err error
|
|
// dest is usually not tmpfs, stay honest in unit tests as well
|
|
tmpDir, err = os.MkdirTemp("/var/tmp", "qemutestdest")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
By("tmpDir: " + tmpDir)
|
|
destPath = filepath.Join(tmpDir, "dest")
|
|
_, err = os.Create(destPath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
tmpFsDir, err = os.MkdirTemp("/var/tmp", "qemutestdestontmpfs")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
By("tmpFsDir: " + tmpFsDir)
|
|
originalODirectChecker = odirectChecker
|
|
})
|
|
|
|
AfterEach(func() {
|
|
os.RemoveAll(tmpDir)
|
|
os.RemoveAll(tmpFsDir)
|
|
odirectChecker = originalODirectChecker
|
|
})
|
|
|
|
It("Should complete successfully if preallocation succeeds", func() {
|
|
quantity, err := resource.ParseQuantity("10Gi")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
replaceExecFunction(mockExecFunction("", "", nil, "if=/dev/zero", "of="+destPath, "bs=1048576", "count=10240", "seek=0", "oflag=seek_bytes,direct"), func() {
|
|
err = PreallocateBlankBlock(destPath, quantity)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
It("Should complete successfully with tmpfs dest without O_DIRECT if preallocation succeeds", func() {
|
|
odirectChecker = NewDirectIOChecker(FakeODirectRefusingOS{})
|
|
tmpFsDestPath := filepath.Join(tmpFsDir, "dest")
|
|
_, err := os.Create(tmpFsDestPath)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
quantity, err := resource.ParseQuantity("10Gi")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
replaceExecFunction(mockExecFunction("", "", nil, "if=/dev/zero", "of="+tmpFsDestPath, "bs=1048576", "count=10240", "seek=0", "oflag=seek_bytes"), func() {
|
|
err = PreallocateBlankBlock(tmpFsDestPath, quantity)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
It("Should complete successfully with value not aligned to 1MiB", func() {
|
|
quantity, err := resource.ParseQuantity("5243392Ki")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
firstCallArgs := []string{"if=/dev/zero", "of=" + destPath, "bs=1048576", "count=5120", "seek=0", "oflag=seek_bytes,direct"}
|
|
secondCallArgs := []string{"if=/dev/zero", "of=" + destPath, "bs=524288", "count=1", "seek=5368709120", "oflag=seek_bytes,direct"}
|
|
replaceExecFunction(mockExecFunctionTwoCalls("", "", nil, firstCallArgs, secondCallArgs), func() {
|
|
err = PreallocateBlankBlock(destPath, quantity)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
It("Should fail if preallocation fails", func() {
|
|
quantity, err := resource.ParseQuantity("10Gi")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
replaceExecFunction(mockExecFunction("", "exit 1", nil, "if=/dev/zero", "of="+destPath, "bs=1048576", "count=10240", "seek=0", "oflag=seek_bytes,direct"), func() {
|
|
err = PreallocateBlankBlock(destPath, quantity)
|
|
Expect(strings.Contains(err.Error(), "Could not preallocate blank block volume at")).To(BeTrue())
|
|
})
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Try different preallocation modes", func() {
|
|
It("Should try falloc first", func() {
|
|
calledCount := 0
|
|
err := addPreallocation([]string{"command"}, convertPreallocationMethods, func(args []string) ([]byte, error) {
|
|
Expect(args).To(Equal([]string{"command", "-o", "preallocation=falloc"}))
|
|
calledCount++
|
|
return []byte{}, nil
|
|
})
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(calledCount).To(Equal(1))
|
|
})
|
|
|
|
It("Should try full if falloc fails", func() {
|
|
calledCount := 0
|
|
err := addPreallocation([]string{"command"}, convertPreallocationMethods, func(args []string) ([]byte, error) {
|
|
if args[2] == "preallocation=falloc" {
|
|
calledCount++
|
|
return []byte("Unsupported preallocation mode"), fmt.Errorf("No, no, no")
|
|
}
|
|
Expect(args).To(Equal([]string{"command", "-o", "preallocation=full"}))
|
|
calledCount++
|
|
return []byte{}, nil
|
|
})
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(calledCount).To(Equal(2))
|
|
})
|
|
|
|
It("Should try -S0 if full fails", func() {
|
|
calledCount := 0
|
|
err := addPreallocation([]string{"command"}, convertPreallocationMethods, func(args []string) ([]byte, error) {
|
|
if calledCount < 2 {
|
|
calledCount++
|
|
return []byte("Unsupported preallocation mode"), fmt.Errorf("No, no, no")
|
|
}
|
|
Expect(args).To(Equal([]string{"command", "-S", "0"}))
|
|
calledCount++
|
|
return []byte{}, nil
|
|
})
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(calledCount).To(Equal(3))
|
|
})
|
|
|
|
It("Should fail if output is different than 'Unsupported preallocation'", func() {
|
|
calledCount := 0
|
|
err := addPreallocation([]string{"command"}, convertPreallocationMethods, func(args []string) ([]byte, error) {
|
|
calledCount++
|
|
return []byte("General Protection Fault"), fmt.Errorf("No, no, no")
|
|
})
|
|
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(calledCount).To(Equal(1))
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Rebase and commit", func() {
|
|
It("Should successfully rebase image", func() {
|
|
replaceExecFunction(mockExecFunctionStrict("", "", nil, "rebase", "-p", "-u", "-F", "raw", "-b", "backing-file", "delta"), func() {
|
|
o := NewQEMUOperations()
|
|
err := o.Rebase("backing-file", "delta")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
It("Should successfully commit image to base", func() {
|
|
replaceExecFunction(mockExecFunctionStrict("", "", nil, "commit", "-p", "delta"), func() {
|
|
o := NewQEMUOperations()
|
|
err := o.Commit("delta")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
})
|
|
})
|
|
|
|
func mockExecFunction(output, errString string, expectedLimits *system.ProcessLimitValues, checkArgs ...string) execFunctionType {
|
|
return func(limits *system.ProcessLimitValues, f func(string), cmd string, args ...string) (bytes []byte, err error) {
|
|
Expect(reflect.DeepEqual(expectedLimits, limits)).To(BeTrue())
|
|
|
|
for _, ca := range checkArgs {
|
|
found := false
|
|
for _, a := range args {
|
|
if ca == a {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
// if not found will fail and show the diff in the args
|
|
if found != true {
|
|
Expect(checkArgs).To(Equal(args))
|
|
}
|
|
}
|
|
|
|
if output != "" {
|
|
bytes = []byte(output)
|
|
}
|
|
if errString != "" {
|
|
err = errors.New(errString)
|
|
}
|
|
|
|
return bytes, err
|
|
}
|
|
}
|
|
|
|
func mockExecFunctionStrict(output, errString string, expectedLimits *system.ProcessLimitValues, checkArgs ...string) execFunctionType {
|
|
return func(limits *system.ProcessLimitValues, f func(string), cmd string, args ...string) (bytes []byte, err error) {
|
|
Expect(reflect.DeepEqual(expectedLimits, limits)).To(BeTrue())
|
|
|
|
Expect(checkArgs).To(Equal(args))
|
|
|
|
if output != "" {
|
|
bytes = []byte(output)
|
|
}
|
|
if errString != "" {
|
|
err = errors.New(errString)
|
|
}
|
|
|
|
return bytes, err
|
|
}
|
|
}
|
|
|
|
func mockExecFunctionTwoCalls(output, errString string, expectedLimits *system.ProcessLimitValues, firstCallArgs []string, secondCallArgs []string) execFunctionType {
|
|
firstCall := true
|
|
return func(limits *system.ProcessLimitValues, f func(string), cmd string, args ...string) (bytes []byte, err error) {
|
|
Expect(reflect.DeepEqual(expectedLimits, limits)).To(BeTrue())
|
|
|
|
if firstCall {
|
|
Expect(firstCallArgs).To(Equal(args))
|
|
firstCall = false
|
|
} else {
|
|
Expect(secondCallArgs).To(Equal(args))
|
|
}
|
|
|
|
if output != "" {
|
|
bytes = []byte(output)
|
|
}
|
|
if errString != "" {
|
|
err = errors.New(errString)
|
|
}
|
|
|
|
return bytes, err
|
|
}
|
|
}
|
|
|
|
func replaceExecFunction(replacement execFunctionType, f func()) {
|
|
orig := qemuExecFunction
|
|
if replacement != nil {
|
|
qemuExecFunction = replacement
|
|
defer func() { qemuExecFunction = orig }()
|
|
}
|
|
f()
|
|
}
|