Skip to content

Commit

Permalink
Test image with a backing file
Browse files Browse the repository at this point in the history
We cannot have useful tests for raw backing file since we have only a
stub implementation reporting that the entire backing file is allocated,
so we test only qcow2 image backing file.

We test backing file in qcow2 and qcow2 compressed. When both images are
uncompressed, allocated extents from top and base are merged. When base
is compressed, allocated extents from top and based cannot be merged,
since all base clusters are compressed.

Compressed image with backing file may be possible to crate with
qemu-io, but is not real use case so we don't test it.

Signed-off-by: Nir Soffer <nirsof@gmail.com>
  • Loading branch information
nirs committed Nov 16, 2024
1 parent 9200043 commit 58a059d
Showing 1 changed file with 209 additions and 0 deletions.
209 changes: 209 additions & 0 deletions qcow2reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,215 @@ func TestExtentsZero(t *testing.T) {
})
}

func TestExtentsBackingFile(t *testing.T) {
// Create an image with some clusters in the backing file, and some cluasters
// in the image. Accessing extents should present a unified view using both
// image and backing file.
tmpDir := t.TempDir()
baseExtents := []image.Extent{
{
Start: 0 * clusterSize,
Length: 1 * clusterSize,
Allocated: true,
},
{
Start: 1 * clusterSize,
Length: 9 * clusterSize,
Zero: true,
},
{
Start: 10 * clusterSize,
Length: 2 * clusterSize,
Allocated: true,
},
{
Start: 12 * clusterSize,
Length: 88 * clusterSize,
Zero: true,
},
{
Start: 100 * clusterSize,
Length: 1 * clusterSize,
Allocated: true,
},
{
Start: 101 * clusterSize,
Length: 899 * clusterSize,
Zero: true,
},
}
topExtents := []image.Extent{
{
Start: 0 * clusterSize,
Length: 1 * clusterSize,
Zero: true,
},
{
Start: 1 * clusterSize,
Length: 1 * clusterSize,
Allocated: true,
},
{
Start: 2 * clusterSize,
Length: 9 * clusterSize,
Zero: true,
},
{
Start: 11 * clusterSize,
Length: 2 * clusterSize,
Allocated: true,
},
{
Start: 13 * clusterSize,
Length: 986 * clusterSize,
Zero: true,
},
{
Start: 999 * clusterSize,
Length: 1 * clusterSize,
Allocated: true,
},
}
baseRaw := filepath.Join(tmpDir, "base.raw")
if err := createTestImageWithExtents(baseRaw, qemuimg.FormatRaw, baseExtents, "", ""); err != nil {
t.Fatal(err)
}

t.Run("qcow2", func(t *testing.T) {
tmpDir := t.TempDir()
baseQcow2 := filepath.Join(tmpDir, "base.qcow2")
if err := qemuimg.Convert(baseRaw, baseQcow2, qemuimg.FormatQcow2, qemuimg.CompressionNone); err != nil {
t.Fatal(err)
}
top := filepath.Join(tmpDir, "top.qcow2")
if err := createTestImageWithExtents(top, qemuimg.FormatQcow2, topExtents, baseQcow2, qemuimg.FormatQcow2); err != nil {
t.Fatal(err)
}
// When top and base are uncompressed, extents from to and based are merged.
expected := []image.Extent{
{
Start: 0 * clusterSize,
Length: 2 * clusterSize,
Allocated: true,
},
{
Start: 2 * clusterSize,
Length: 8 * clusterSize,
Zero: true,
},
{
Start: 10 * clusterSize,
Length: 3 * clusterSize,
Allocated: true,
},
{
Start: 13 * clusterSize,
Length: 87 * clusterSize,
Zero: true,
},
{
Start: 100 * clusterSize,
Length: 1 * clusterSize,
Allocated: true,
},
{
Start: 101 * clusterSize,
Length: 898 * clusterSize,
Zero: true,
},
{
Start: 999 * clusterSize,
Length: 1 * clusterSize,
Allocated: true,
},
}
actual, err := listExtents(top)
if err != nil {
t.Fatal(err)
}
if !slices.Equal(expected, actual) {
t.Fatalf("expected %v, got %v", expected, actual)
}
})
t.Run("qcow2 zlib", func(t *testing.T) {
tmpDir := t.TempDir()
baseQcow2Zlib := filepath.Join(tmpDir, "base.qcow2")
if err := qemuimg.Convert(baseRaw, baseQcow2Zlib, qemuimg.FormatQcow2, qemuimg.CompressionZlib); err != nil {
t.Fatal(err)
}
top := filepath.Join(tmpDir, "top.qcow2")
if err := createTestImageWithExtents(top, qemuimg.FormatQcow2, topExtents, baseQcow2Zlib, qemuimg.FormatQcow2); err != nil {
t.Fatal(err)
}
// When base is compressed, extents from to and based cannot be merged since
// allocated extents from base are compressed. When copying we can merge
// extents with different types that read as zero.
expected := []image.Extent{
// From base
{
Start: 0 * clusterSize,
Length: 1 * clusterSize,
Allocated: true,
Compressed: true,
},
// From top
{
Start: 1 * clusterSize,
Length: 1 * clusterSize,
Allocated: true,
},
{
Start: 2 * clusterSize,
Length: 8 * clusterSize,
Zero: true,
},
// From base
{
Start: 10 * clusterSize,
Length: 1 * clusterSize,
Allocated: true,
Compressed: true,
},
// From top (top clusters hide base clusters)
{
Start: 11 * clusterSize,
Length: 2 * clusterSize,
Allocated: true,
},
{
Start: 13 * clusterSize,
Length: 87 * clusterSize,
Zero: true,
},
// From base
{
Start: 100 * clusterSize,
Length: 1 * clusterSize,
Allocated: true,
Compressed: true,
},
{
Start: 101 * clusterSize,
Length: 898 * clusterSize,
Zero: true,
},
// From top
{
Start: 999 * clusterSize,
Length: 1 * clusterSize,
Allocated: true,
},
}
actual, err := listExtents(top)
if err != nil {
t.Fatal(err)
}
if !slices.Equal(expected, actual) {
t.Fatalf("expected %v, got %v", expected, actual)
}
})
}

func compressed(extents []image.Extent) []image.Extent {
var res []image.Extent
for _, extent := range extents {
Expand Down

0 comments on commit 58a059d

Please sign in to comment.