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

The io/ioutil package has been deprecated as of Go 1.16 [1]. This commit replaces the existing io/ioutil functions with their new definitions in io and os packages. [1]: https://golang.org/doc/go1.16#ioutil Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
640 lines
23 KiB
Go
640 lines
23 KiB
Go
package importer
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
|
|
. "github.com/onsi/ginkgo"
|
|
"github.com/onsi/ginkgo/extensions/table"
|
|
. "github.com/onsi/gomega"
|
|
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"kubevirt.io/containerized-data-importer/pkg/image"
|
|
)
|
|
|
|
type fakeInfoOpRetVal struct {
|
|
imgInfo *image.ImgInfo
|
|
e error
|
|
}
|
|
|
|
const TestImagesDir = "../../tests/images"
|
|
|
|
const (
|
|
SmallActualSize = 1024 * 1024
|
|
SmallVirtualSize = 1024 * 1024
|
|
)
|
|
|
|
var (
|
|
fakeSmallImageInfo = image.ImgInfo{Format: "", BackingFile: "", VirtualSize: SmallVirtualSize, ActualSize: SmallActualSize}
|
|
fakeZeroImageInfo = image.ImgInfo{Format: "", BackingFile: "", VirtualSize: 0, ActualSize: 0}
|
|
fakeInfoRet = fakeInfoOpRetVal{imgInfo: &fakeSmallImageInfo, e: nil}
|
|
)
|
|
|
|
type fakeQEMUOperations struct {
|
|
e2 error
|
|
e3 error
|
|
ret4 fakeInfoOpRetVal
|
|
e5 error
|
|
e6 error
|
|
resizeQuantity *resource.Quantity
|
|
}
|
|
|
|
type MockDataProvider struct {
|
|
infoResponse ProcessingPhase
|
|
transferResponse ProcessingPhase
|
|
url *url.URL
|
|
transferPath string
|
|
transferFile string
|
|
calledPhases []ProcessingPhase
|
|
needsScratch bool
|
|
}
|
|
|
|
// Info is called to get initial information about the data
|
|
func (m *MockDataProvider) Info() (ProcessingPhase, error) {
|
|
m.calledPhases = append(m.calledPhases, ProcessingPhaseInfo)
|
|
if m.infoResponse == ProcessingPhaseError {
|
|
return ProcessingPhaseError, errors.New("Info errored")
|
|
}
|
|
return m.infoResponse, nil
|
|
}
|
|
|
|
// Transfer is called to transfer the data from the source to the passed in path.
|
|
func (m *MockDataProvider) Transfer(path string) (ProcessingPhase, error) {
|
|
m.calledPhases = append(m.calledPhases, m.infoResponse)
|
|
m.transferPath = path
|
|
if m.transferResponse == ProcessingPhaseError {
|
|
if m.needsScratch {
|
|
return ProcessingPhaseError, ErrInvalidPath
|
|
}
|
|
return ProcessingPhaseError, errors.New("Transfer errored")
|
|
}
|
|
return m.transferResponse, nil
|
|
}
|
|
|
|
// TransferFile is called to transfer the data from the source to the passed in file.
|
|
func (m *MockDataProvider) TransferFile(fileName string) (ProcessingPhase, error) {
|
|
m.calledPhases = append(m.calledPhases, ProcessingPhaseTransferDataFile)
|
|
m.transferFile = fileName
|
|
if m.transferResponse == ProcessingPhaseError {
|
|
return ProcessingPhaseError, errors.New("TransferFile errored")
|
|
}
|
|
return m.transferResponse, nil
|
|
}
|
|
|
|
// Geturl returns the url that the data processor can use when converting the data.
|
|
func (m *MockDataProvider) GetURL() *url.URL {
|
|
return m.url
|
|
}
|
|
|
|
// Close closes any readers or other open resources.
|
|
func (m *MockDataProvider) Close() error {
|
|
return nil
|
|
}
|
|
|
|
type MockAsyncDataProvider struct {
|
|
MockDataProvider
|
|
ResumePhase ProcessingPhase
|
|
}
|
|
|
|
// Info is called to get initial information about the data.
|
|
func (madp *MockAsyncDataProvider) Info() (ProcessingPhase, error) {
|
|
return madp.MockDataProvider.Info()
|
|
}
|
|
|
|
// Transfer is called to transfer the data from the source to the passed in path.
|
|
func (madp *MockAsyncDataProvider) Transfer(path string) (ProcessingPhase, error) {
|
|
return madp.MockDataProvider.Transfer(path)
|
|
}
|
|
|
|
// TransferFile is called to transfer the data from the source to the passed in file.
|
|
func (madp *MockAsyncDataProvider) TransferFile(fileName string) (ProcessingPhase, error) {
|
|
return madp.MockDataProvider.TransferFile(fileName)
|
|
}
|
|
|
|
// Close closes any readers or other open resources.
|
|
func (madp *MockAsyncDataProvider) Close() error {
|
|
return madp.MockDataProvider.Close()
|
|
}
|
|
|
|
// GetURL returns the url that the data processor can use when converting the data.
|
|
func (madp *MockAsyncDataProvider) GetURL() *url.URL {
|
|
return madp.MockDataProvider.GetURL()
|
|
}
|
|
|
|
// GetResumePhase returns the next phase to process when resuming
|
|
func (madp *MockAsyncDataProvider) GetResumePhase() ProcessingPhase {
|
|
return madp.ResumePhase
|
|
}
|
|
|
|
const ProcessingPhaseFoo ProcessingPhase = "Foo"
|
|
|
|
type MockCustomizedDataProvider struct {
|
|
MockDataProvider
|
|
fooResponse ProcessingPhase
|
|
}
|
|
|
|
func (mcdp *MockCustomizedDataProvider) Foo() (ProcessingPhase, error) {
|
|
mcdp.calledPhases = append(mcdp.calledPhases, ProcessingPhaseFoo)
|
|
return mcdp.fooResponse, nil
|
|
}
|
|
|
|
var _ = Describe("Data Processor", func() {
|
|
It("should call the right phases based on the responses from the provider, Transfer should pass the scratch data dir as a path", func() {
|
|
mdp := &MockDataProvider{
|
|
infoResponse: ProcessingPhaseTransferScratch,
|
|
transferResponse: ProcessingPhaseComplete,
|
|
}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
err := dp.ProcessData()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(2).To(Equal(len(mdp.calledPhases)))
|
|
Expect(ProcessingPhaseInfo).To(Equal(mdp.calledPhases[0]))
|
|
Expect(ProcessingPhaseTransferScratch).To(Equal(mdp.calledPhases[1]))
|
|
Expect("scratchDataDir").To(Equal(mdp.transferPath))
|
|
})
|
|
|
|
It("should call the right phases based on the responses from the provider, TransferTarget should pass the data dir as a path", func() {
|
|
mdp := &MockDataProvider{
|
|
infoResponse: ProcessingPhaseTransferDataDir,
|
|
transferResponse: ProcessingPhaseComplete,
|
|
}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
err := dp.ProcessData()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(2).To(Equal(len(mdp.calledPhases)))
|
|
Expect(ProcessingPhaseInfo).To(Equal(mdp.calledPhases[0]))
|
|
Expect(ProcessingPhaseTransferDataDir).To(Equal(mdp.calledPhases[1]))
|
|
Expect("dataDir").To(Equal(mdp.transferPath))
|
|
})
|
|
|
|
It("should error on Transfer phase", func() {
|
|
mdp := &MockDataProvider{
|
|
infoResponse: ProcessingPhaseTransferScratch,
|
|
transferResponse: ProcessingPhaseError,
|
|
}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
err := dp.ProcessData()
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(2).To(Equal(len(mdp.calledPhases)))
|
|
Expect(ProcessingPhaseInfo).To(Equal(mdp.calledPhases[0]))
|
|
Expect(ProcessingPhaseTransferScratch).To(Equal(mdp.calledPhases[1]))
|
|
})
|
|
|
|
It("should error on Transfer phase if scratch space is required", func() {
|
|
mdp := &MockDataProvider{
|
|
infoResponse: ProcessingPhaseTransferScratch,
|
|
transferResponse: ProcessingPhaseError,
|
|
needsScratch: true,
|
|
}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
err := dp.ProcessData()
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(ErrRequiresScratchSpace).To(Equal(err))
|
|
Expect(2).To(Equal(len(mdp.calledPhases)))
|
|
Expect(ProcessingPhaseInfo).To(Equal(mdp.calledPhases[0]))
|
|
Expect(ProcessingPhaseTransferScratch).To(Equal(mdp.calledPhases[1]))
|
|
})
|
|
|
|
It("should call the right phases based on the responses from the provider, TransferDataFile should pass the data file", func() {
|
|
mdp := &MockDataProvider{
|
|
infoResponse: ProcessingPhaseTransferDataFile,
|
|
transferResponse: ProcessingPhaseComplete,
|
|
}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
qemuOperations := NewFakeQEMUOperations(nil, nil, fakeInfoOpRetVal{&fakeZeroImageInfo, errors.New("Scratch space required, and none found ")}, nil, nil, nil)
|
|
replaceQEMUOperations(qemuOperations, func() {
|
|
err := dp.ProcessData()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(2).To(Equal(len(mdp.calledPhases)))
|
|
Expect(ProcessingPhaseInfo).To(Equal(mdp.calledPhases[0]))
|
|
Expect(ProcessingPhaseTransferDataFile).To(Equal(mdp.calledPhases[1]))
|
|
})
|
|
})
|
|
|
|
It("should fail when TransferDataFile fails", func() {
|
|
mdp := &MockDataProvider{
|
|
infoResponse: ProcessingPhaseTransferDataFile,
|
|
transferResponse: ProcessingPhaseError,
|
|
}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
qemuOperations := NewQEMUAllErrors()
|
|
replaceQEMUOperations(qemuOperations, func() {
|
|
err := dp.ProcessData()
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(2).To(Equal(len(mdp.calledPhases)))
|
|
Expect(ProcessingPhaseInfo).To(Equal(mdp.calledPhases[0]))
|
|
Expect(ProcessingPhaseTransferDataFile).To(Equal(mdp.calledPhases[1]))
|
|
})
|
|
})
|
|
|
|
It("should error on Unknown phase", func() {
|
|
mdp := &MockDataProvider{
|
|
infoResponse: ProcessingPhase("invalidphase"),
|
|
}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
err := dp.ProcessData()
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(1).To(Equal(len(mdp.calledPhases)))
|
|
Expect(ProcessingPhaseInfo).To(Equal(mdp.calledPhases[0]))
|
|
})
|
|
|
|
It("should call Convert after Process phase", func() {
|
|
tmpDir, err := os.MkdirTemp("", "scratch")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
url, err := url.Parse("http://fakeurl-notreal.fake")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
mdp := &MockDataProvider{
|
|
infoResponse: ProcessingPhaseTransferScratch,
|
|
transferResponse: ProcessingPhaseConvert,
|
|
url: url,
|
|
}
|
|
dp := NewDataProcessor(mdp, "", "dataDir", tmpDir, "1G", 0.055, false)
|
|
dp.availableSpace = int64(1536000)
|
|
usableSpace := dp.getUsableSpace()
|
|
|
|
qemuOperations := NewFakeQEMUOperations(nil, nil, fakeInfoRet, nil, nil, resource.NewScaledQuantity(usableSpace, 1024*1024))
|
|
replaceQEMUOperations(qemuOperations, func() {
|
|
err = dp.ProcessData()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(2).To(Equal(len(mdp.calledPhases)))
|
|
Expect(ProcessingPhaseInfo).To(Equal(mdp.calledPhases[0]))
|
|
Expect(ProcessingPhaseTransferScratch).To(Equal(mdp.calledPhases[1]))
|
|
Expect(tmpDir).To(Equal(mdp.transferPath))
|
|
})
|
|
})
|
|
|
|
It("should allow phase regsitry", func() {
|
|
mcdp := &MockCustomizedDataProvider{
|
|
MockDataProvider: MockDataProvider{
|
|
infoResponse: ProcessingPhaseTransferDataDir,
|
|
transferResponse: ProcessingPhaseFoo,
|
|
},
|
|
fooResponse: ProcessingPhaseComplete,
|
|
}
|
|
dp := NewDataProcessor(mcdp, "dest", "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
dp.RegisterPhaseExecutor(ProcessingPhaseFoo, func() (ProcessingPhase, error) {
|
|
return mcdp.Foo()
|
|
})
|
|
err := dp.ProcessData()
|
|
fmt.Println(mcdp.calledPhases)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(3).To(Equal(len(mcdp.calledPhases)))
|
|
Expect(ProcessingPhaseInfo).To(Equal(mcdp.calledPhases[0]))
|
|
Expect(ProcessingPhaseTransferDataDir).To(Equal(mcdp.calledPhases[1]))
|
|
Expect("dataDir").To(Equal(mcdp.transferPath))
|
|
Expect(ProcessingPhaseFoo).To(Equal(mcdp.calledPhases[2]))
|
|
})
|
|
|
|
It("should return error if there is a loop", func() {
|
|
mcdp := &MockCustomizedDataProvider{
|
|
MockDataProvider: MockDataProvider{
|
|
infoResponse: ProcessingPhaseTransferDataDir,
|
|
transferResponse: ProcessingPhaseFoo,
|
|
},
|
|
fooResponse: ProcessingPhaseInfo,
|
|
}
|
|
dp := NewDataProcessor(mcdp, "dest", "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
dp.RegisterPhaseExecutor(ProcessingPhaseFoo, func() (ProcessingPhase, error) {
|
|
return mcdp.Foo()
|
|
})
|
|
err := dp.ProcessData()
|
|
Expect(err).To(HaveOccurred())
|
|
})
|
|
|
|
It("should return error if there is an unknown phase", func() {
|
|
mdp := &MockDataProvider{
|
|
infoResponse: "unknown",
|
|
}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
err := dp.ProcessData()
|
|
Expect(err).To(HaveOccurred())
|
|
})
|
|
|
|
table.DescribeTable("should avoid cleanup before delta copies", func(dataSource DataSourceInterface, expectedCleanup bool) {
|
|
tmpDir, err := os.MkdirTemp("", "scratch")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
dp := NewDataProcessor(dataSource, "dest", "dataDir", tmpDir, "1G", 0.055, false)
|
|
Expect(dp.needsDataCleanup).To(Equal(expectedCleanup))
|
|
},
|
|
table.Entry("ImageIO delta copy", &ImageioDataSource{currentSnapshot: "123", previousSnapshot: "123"}, false),
|
|
table.Entry("ImageIO base copy", &ImageioDataSource{currentSnapshot: "123", previousSnapshot: ""}, true),
|
|
table.Entry("VDDK delta copy", &VDDKDataSource{CurrentSnapshot: "123", PreviousSnapshot: "123"}, false),
|
|
table.Entry("VDDK base copy", &VDDKDataSource{CurrentSnapshot: "123", PreviousSnapshot: ""}, true),
|
|
)
|
|
})
|
|
|
|
var _ = Describe("Convert", func() {
|
|
It("Should successfully convert and return resize", func() {
|
|
url, err := url.Parse("http://fakeurl-notreal.fake")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
mdp := &MockDataProvider{
|
|
url: url,
|
|
}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
qemuOperations := NewFakeQEMUOperations(nil, nil, fakeInfoOpRetVal{&fakeZeroImageInfo, errors.New("Scratch space required, and none found ")}, nil, nil, nil)
|
|
replaceQEMUOperations(qemuOperations, func() {
|
|
nextPhase, err := dp.convert(mdp.GetURL())
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(ProcessingPhaseResize).To(Equal(nextPhase))
|
|
})
|
|
})
|
|
|
|
It("Should fail when validation fails and return Error", func() {
|
|
url, err := url.Parse("http://fakeurl-notreal.fake")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
mdp := &MockDataProvider{
|
|
url: url,
|
|
}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
qemuOperations := NewFakeQEMUOperations(nil, nil, fakeInfoOpRetVal{&fakeZeroImageInfo, errors.New("Scratch space required, and none found ")}, errors.New("Validation failure"), nil, nil)
|
|
replaceQEMUOperations(qemuOperations, func() {
|
|
nextPhase, err := dp.convert(mdp.GetURL())
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(ProcessingPhaseError).To(Equal(nextPhase))
|
|
})
|
|
})
|
|
|
|
It("Should fail when conversion fails and return Error", func() {
|
|
url, err := url.Parse("http://fakeurl-notreal.fake")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
mdp := &MockDataProvider{
|
|
url: url,
|
|
}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
qemuOperations := NewFakeQEMUOperations(errors.New("Conversion failure"), nil, fakeInfoOpRetVal{&fakeZeroImageInfo, errors.New("Scratch space required, and none found ")}, nil, nil, nil)
|
|
replaceQEMUOperations(qemuOperations, func() {
|
|
nextPhase, err := dp.convert(mdp.GetURL())
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(ProcessingPhaseError).To(Equal(nextPhase))
|
|
})
|
|
})
|
|
})
|
|
|
|
var _ = Describe("Resize", func() {
|
|
It("Should not resize and return complete, when requestedSize is blank", func() {
|
|
tempDir, err := os.MkdirTemp(os.TempDir(), "dest")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
url, err := url.Parse("http://fakeurl-notreal.fake")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
mdp := &MockDataProvider{
|
|
url: url,
|
|
}
|
|
dp := NewDataProcessor(mdp, tempDir, "dataDir", "scratchDataDir", "", 0.055, false)
|
|
qemuOperations := NewFakeQEMUOperations(nil, nil, fakeInfoOpRetVal{&fakeZeroImageInfo, nil}, nil, nil, nil)
|
|
replaceQEMUOperations(qemuOperations, func() {
|
|
nextPhase, err := dp.resize()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(ProcessingPhaseComplete).To(Equal(nextPhase))
|
|
})
|
|
})
|
|
|
|
It("Should not resize and return complete, when requestedSize is valid, but datadir doesn't exist (block device)", func() {
|
|
tempDir, err := os.MkdirTemp(os.TempDir(), "dest")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
replaceAvailableSpaceBlockFunc(func(dataDir string) (int64, error) {
|
|
Expect(tempDir).To(Equal(dataDir))
|
|
return int64(100000), nil
|
|
}, func() {
|
|
url, err := url.Parse("http://fakeurl-notreal.fake")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
mdp := &MockDataProvider{
|
|
url: url,
|
|
}
|
|
dp := NewDataProcessor(mdp, tempDir, "dataDir", "scratchDataDir", "1G", 0.055, false)
|
|
qemuOperations := NewFakeQEMUOperations(nil, nil, fakeInfoOpRetVal{&fakeZeroImageInfo, nil}, nil, nil, nil)
|
|
replaceQEMUOperations(qemuOperations, func() {
|
|
nextPhase, err := dp.resize()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(ProcessingPhaseComplete).To(Equal(nextPhase))
|
|
})
|
|
})
|
|
})
|
|
|
|
It("Should resize and return complete, when requestedSize is valid, and datadir exists", func() {
|
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "data")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
url, err := url.Parse("http://fakeurl-notreal.fake")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
mdp := &MockDataProvider{
|
|
url: url,
|
|
}
|
|
dp := NewDataProcessor(mdp, tmpDir, tmpDir, "scratchDataDir", "1G", 0.055, false)
|
|
qemuOperations := NewFakeQEMUOperations(nil, nil, fakeInfoOpRetVal{&fakeZeroImageInfo, nil}, nil, nil, nil)
|
|
replaceQEMUOperations(qemuOperations, func() {
|
|
nextPhase, err := dp.resize()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(ProcessingPhaseComplete).To(Equal(nextPhase))
|
|
})
|
|
})
|
|
|
|
It("Should not resize and return error, when ResizeImage fails", func() {
|
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "data")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
url, err := url.Parse("http://fakeurl-notreal.fake")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
mdp := &MockDataProvider{
|
|
url: url,
|
|
}
|
|
dp := NewDataProcessor(mdp, "dest", tmpDir, "scratchDataDir", "1G", 0.055, false)
|
|
qemuOperations := NewQEMUAllErrors()
|
|
replaceQEMUOperations(qemuOperations, func() {
|
|
nextPhase, err := dp.resize()
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(ProcessingPhaseError).To(Equal(nextPhase))
|
|
})
|
|
})
|
|
|
|
It("Should return same value as replaced function", func() {
|
|
replaceAvailableSpaceBlockFunc(func(dataDir string) (int64, error) {
|
|
return int64(100000), nil
|
|
}, func() {
|
|
mdp := &MockDataProvider{}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "", 0.055, false)
|
|
Expect(int64(100000)).To(Equal(dp.calculateTargetSize()))
|
|
})
|
|
})
|
|
|
|
It("Should fail if calculate size returns failure", func() {
|
|
replaceAvailableSpaceBlockFunc(func(dataDir string) (int64, error) {
|
|
return int64(-1), errors.New("error")
|
|
}, func() {
|
|
mdp := &MockDataProvider{}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "", 0.055, false)
|
|
// We just log the error if one happens.
|
|
Expect(int64(-1)).To(Equal(dp.calculateTargetSize()))
|
|
|
|
})
|
|
})
|
|
})
|
|
|
|
var _ = Describe("ResizeImage", func() {
|
|
//fakeInfoRet has info.VirtualSize=1024
|
|
table.DescribeTable("calling ResizeImage", func(qemuOperations image.QEMUOperations, imageSize string, totalSpace int64, wantErr bool) {
|
|
replaceQEMUOperations(qemuOperations, func() {
|
|
err := ResizeImage("dest", imageSize, totalSpace, false)
|
|
if !wantErr {
|
|
Expect(err).ToNot(HaveOccurred())
|
|
} else {
|
|
Expect(err).To(HaveOccurred())
|
|
}
|
|
})
|
|
},
|
|
table.Entry("successfully resize to imageSize when imageSize > info.VirtualSize and < totalSize", NewFakeQEMUOperations(nil, nil, fakeInfoRet, nil, nil, resource.NewScaledQuantity(int64(1500*1024), 0)), "1536000", int64(2048*1024), false),
|
|
table.Entry("successfully resize to totalSize when imageSize > info.VirtualSize and > totalSize", NewFakeQEMUOperations(nil, nil, fakeInfoRet, nil, nil, resource.NewScaledQuantity(int64(2048*1024), 0)), "2560000", int64(2048*1024), false),
|
|
table.Entry("successfully do nothing when imageSize = info.VirtualSize and > totalSize", NewFakeQEMUOperations(nil, nil, fakeInfoRet, nil, nil, resource.NewScaledQuantity(int64(1024*1024), 0)), "1048576", int64(1024*1024), false),
|
|
table.Entry("fail to resize to with blank imageSize", NewFakeQEMUOperations(nil, nil, fakeInfoRet, nil, nil, resource.NewScaledQuantity(int64(2048), 0)), "", int64(2048), true),
|
|
table.Entry("fail to resize to with blank imageSize", NewQEMUAllErrors(), "", int64(2048), true),
|
|
)
|
|
})
|
|
|
|
var _ = Describe("DataProcessorResume", func() {
|
|
It("Should fail with an error if the data provider cannot resume", func() {
|
|
mdp := &MockDataProvider{}
|
|
dp := NewDataProcessor(mdp, "dest", "dataDir", "scratchDataDir", "", 0.055, false)
|
|
err := dp.ProcessDataResume()
|
|
Expect(err).To(HaveOccurred())
|
|
})
|
|
|
|
It("Should resume properly based on resume phase", func() {
|
|
amdp := &MockAsyncDataProvider{
|
|
ResumePhase: ProcessingPhaseComplete,
|
|
}
|
|
dp := NewDataProcessor(amdp, "dest", "dataDir", "scratchDataDir", "", 0.055, false)
|
|
err := dp.ProcessDataResume()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
var _ = Describe("MergeDelta", func() {
|
|
It("Should correctly move to merge phase, then rebase and commit", func() {
|
|
url := &url.URL{}
|
|
originalBackingFile := "original-backing-file"
|
|
expectedBackingFile := "rebased-backing-file"
|
|
originalActualSize := int64(5)
|
|
expectedActualSize := int64(6)
|
|
|
|
mdp := &MockDataProvider{
|
|
infoResponse: ProcessingPhaseTransferScratch,
|
|
transferResponse: ProcessingPhaseMergeDelta,
|
|
needsScratch: true,
|
|
url: url,
|
|
}
|
|
|
|
dp := NewDataProcessor(mdp, expectedBackingFile, "dataDir", "scratchDataDir", "", 0.055, false)
|
|
err := errors.New("this operation should not be called")
|
|
info := &image.ImgInfo{
|
|
Format: "",
|
|
BackingFile: originalBackingFile,
|
|
VirtualSize: 10,
|
|
ActualSize: originalActualSize,
|
|
}
|
|
qemuOperations := NewFakeQEMUOperations(err, err, fakeInfoOpRetVal{info, nil}, err, err, nil)
|
|
replaceQEMUOperations(qemuOperations, func() {
|
|
// Check original backing file and size before processing
|
|
info, err := qemuOperations.Info(url)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(info.BackingFile).To(Equal(originalBackingFile))
|
|
Expect(info.ActualSize).To(Equal(originalActualSize))
|
|
|
|
// This should run rebase and commit
|
|
err = dp.ProcessData()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(2).To(Equal(len(mdp.calledPhases)))
|
|
Expect(ProcessingPhaseInfo).To(Equal(mdp.calledPhases[0]))
|
|
Expect(ProcessingPhaseTransferScratch).To(Equal(mdp.calledPhases[1]))
|
|
|
|
// Verify backing file was rebased and committed to main data file
|
|
info, err = qemuOperations.Info(url)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(info.BackingFile).To(Equal(expectedBackingFile))
|
|
Expect(info.ActualSize).To(Equal(expectedActualSize))
|
|
})
|
|
})
|
|
})
|
|
|
|
func replaceQEMUOperations(replacement image.QEMUOperations, f func()) {
|
|
orig := qemuOperations
|
|
if replacement != nil {
|
|
qemuOperations = replacement
|
|
defer func() { qemuOperations = orig }()
|
|
}
|
|
f()
|
|
}
|
|
|
|
func NewFakeQEMUOperations(e2, e3 error, ret4 fakeInfoOpRetVal, e5 error, e6 error, targetResize *resource.Quantity) image.QEMUOperations {
|
|
return &fakeQEMUOperations{e2, e3, ret4, e5, e6, targetResize}
|
|
}
|
|
|
|
func (o *fakeQEMUOperations) ConvertToRawStream(*url.URL, string, bool) error {
|
|
return o.e2
|
|
}
|
|
|
|
func (o *fakeQEMUOperations) Validate(*url.URL, int64) error {
|
|
return o.e5
|
|
}
|
|
|
|
func (o *fakeQEMUOperations) Resize(dest string, size resource.Quantity, preallocate bool) error {
|
|
if o.resizeQuantity != nil {
|
|
Expect(o.resizeQuantity.Cmp(size)).To(Equal(0), "sizes don't match %v, %v", o.resizeQuantity.String(), size.String())
|
|
}
|
|
return o.e3
|
|
}
|
|
|
|
func (o *fakeQEMUOperations) Info(url *url.URL) (*image.ImgInfo, error) {
|
|
return o.ret4.imgInfo, o.ret4.e
|
|
}
|
|
|
|
func (o *fakeQEMUOperations) CreateBlankImage(dest string, size resource.Quantity, preallocate bool) error {
|
|
return o.e6
|
|
}
|
|
|
|
// Simulate rebase by changing the backing file.
|
|
func (o *fakeQEMUOperations) Rebase(backingFile string, delta string) error {
|
|
if o.ret4.imgInfo == nil {
|
|
return errors.New("invalid image info")
|
|
}
|
|
o.ret4.imgInfo.BackingFile = backingFile
|
|
return nil
|
|
}
|
|
|
|
// Simulate commit by increasing the image size.
|
|
func (o *fakeQEMUOperations) Commit(image string) error {
|
|
if o.ret4.imgInfo == nil {
|
|
return errors.New("invalid image info")
|
|
}
|
|
o.ret4.imgInfo.ActualSize++
|
|
return nil
|
|
}
|
|
|
|
func NewQEMUAllErrors() image.QEMUOperations {
|
|
err := errors.New("qemu should not be called from this test override with replaceQEMUOperations")
|
|
return NewFakeQEMUOperations(err, err, fakeInfoOpRetVal{nil, err}, err, err, nil)
|
|
}
|
|
|
|
func replaceAvailableSpaceBlockFunc(replacement func(string) (int64, error), f func()) {
|
|
origFunc := getAvailableSpaceBlockFunc
|
|
getAvailableSpaceBlockFunc = replacement
|
|
defer func() {
|
|
getAvailableSpaceBlockFunc = origFunc
|
|
}()
|
|
f()
|
|
}
|
|
|
|
func replaceAvailableSpaceFunc(replacement func(string) (int64, error), f func()) {
|
|
origFunc := getAvailableSpaceFunc
|
|
getAvailableSpaceFunc = replacement
|
|
defer func() {
|
|
getAvailableSpaceFunc = origFunc
|
|
}()
|
|
f()
|
|
}
|