Skip to content

Commit

Permalink
pkg/qemu/imgutil: add Info fields
Browse files Browse the repository at this point in the history
Signed-off-by: Akihiro Suda <[email protected]>
  • Loading branch information
AkihiroSuda committed May 29, 2023
1 parent 3dbc08c commit efe1c47
Show file tree
Hide file tree
Showing 2 changed files with 287 additions and 7 deletions.
82 changes: 75 additions & 7 deletions pkg/qemu/imgutil/imgutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,74 @@ import (
"strings"
)

type InfoChild struct {
Name string `json:"name,omitempty"` // since QEMU 8.0
Info Info `json:"info,omitempty"` // since QEMU 8.0
}

type InfoFormatSpecific struct {
Type string `json:"type,omitempty"` // since QEMU 1.7
Data json.RawMessage `json:"data,omitempty"` // since QEMU 1.7
}

func (sp *InfoFormatSpecific) Qcow2() *InfoFormatSpecificDataQcow2 {
if sp.Type != "qcow2" {
return nil
}
var x InfoFormatSpecificDataQcow2
if err := json.Unmarshal(sp.Data, &x); err != nil {
panic(err)
}
return &x
}

func (sp *InfoFormatSpecific) Vmdk() *InfoFormatSpecificDataVmdk {
if sp.Type != "vmdk" {
return nil
}
var x InfoFormatSpecificDataVmdk
if err := json.Unmarshal(sp.Data, &x); err != nil {
panic(err)
}
return &x
}

type InfoFormatSpecificDataQcow2 struct {
Compat string `json:"compat,omitempty"` // since QEMU 1.7
LazyRefcounts bool `json:"lazy-refcounts,omitempty"` // since QEMU 1.7
Corrupt bool `json:"corrupt,omitempty"` // since QEMU 2.2
RefcountBits int `json:"refcount-bits,omitempty"` // since QEMU 2.3
CompressionType string `json:"compression-type,omitempty"` // since QEMU 5.1
ExtendedL2 bool `json:"extended-l2,omitempty"` // since QEMU 5.2
}

type InfoFormatSpecificDataVmdk struct {
CreateType string `json:"create-type,omitempty"` // since QEMU 1.7
CID int `json:"cid,omitempty"` // since QEMU 1.7
ParentCID int `json:"parent-cid,omitempty"` // since QEMU 1.7
Extents []InfoFormatSpecificDataVmdkExtent `json:"extents,omitempty"` // since QEMU 1.7
}

type InfoFormatSpecificDataVmdkExtent struct {
Filename string `json:"filename,omitempty"` // since QEMU 1.7
Format string `json:"format,omitempty"` // since QEMU 1.7
VSize int64 `json:"virtual-size,omitempty"` // since QEMU 1.7
ClusterSize int `json:"cluster-size,omitempty"` // since QEMU 1.7
}

// Info corresponds to the output of `qemu-img info --output=json FILE`
type Info struct {
Format string `json:"format,omitempty"` // since QEMU 1.3
VSize int64 `json:"virtual-size,omitempty"`
Filename string `json:"filename,omitempty"` // since QEMU 1.3
Format string `json:"format,omitempty"` // since QEMU 1.3
VSize int64 `json:"virtual-size,omitempty"` // since QEMU 1.3
ActualSize int64 `json:"actual-size,omitempty"` // since QEMU 1.3
DirtyFlag bool `json:"dirty-flag,omitempty"` // since QEMU 5.2
ClusterSize int `json:"cluster-size,omitempty"` // since QEMU 1.3
BackingFilename string `json:"backing-filename,omitempty"` // since QEMU 1.3
FullBackingFilename string `json:"full-backing-filename,omitempty"` // since QEMU 1.3
BackingFilenameFormat string `json:"backing-filename-format,omitempty"` // since QEMU 1.3
FormatSpecific *InfoFormatSpecific `json:"format-specific,omitempty"` // since QEMU 1.7
Children []InfoChild `json:"children,omitempty"` // since QEMU 8.0
}

func ConvertToRaw(source string, dest string) error {
Expand All @@ -27,6 +91,14 @@ func ConvertToRaw(source string, dest string) error {
return nil
}

func ParseInfo(b []byte) (*Info, error) {
var imgInfo Info
if err := json.Unmarshal(b, &imgInfo); err != nil {
return nil, err
}
return &imgInfo, nil
}

func GetInfo(f string) (*Info, error) {
var stdout, stderr bytes.Buffer
cmd := exec.Command("qemu-img", "info", "--output=json", "--force-share", f)
Expand All @@ -36,11 +108,7 @@ func GetInfo(f string) (*Info, error) {
return nil, fmt.Errorf("failed to run %v: stdout=%q, stderr=%q: %w",
cmd.Args, stdout.String(), stderr.String(), err)
}
var imgInfo Info
if err := json.Unmarshal(stdout.Bytes(), &imgInfo); err != nil {
return nil, err
}
return &imgInfo, nil
return ParseInfo(stdout.Bytes())
}

func DetectFormat(f string) (string, error) {
Expand Down
212 changes: 212 additions & 0 deletions pkg/qemu/imgutil/imgutil_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package imgutil

import (
"testing"

"gotest.tools/v3/assert"
)

func TestParseInfo(t *testing.T) {
t.Run("qcow2", func(t *testing.T) {
// qemu-img create -f qcow2 foo.qcow2 4G
// (QEMU 8.0)
const s = `{
"children": [
{
"name": "file",
"info": {
"children": [
],
"virtual-size": 197120,
"filename": "foo.qcow2",
"format": "file",
"actual-size": 200704,
"format-specific": {
"type": "file",
"data": {
}
},
"dirty-flag": false
}
}
],
"virtual-size": 4294967296,
"filename": "foo.qcow2",
"cluster-size": 65536,
"format": "qcow2",
"actual-size": 200704,
"format-specific": {
"type": "qcow2",
"data": {
"compat": "1.1",
"compression-type": "zlib",
"lazy-refcounts": false,
"refcount-bits": 16,
"corrupt": false,
"extended-l2": false
}
},
"dirty-flag": false
}`

info, err := ParseInfo([]byte(s))
assert.NilError(t, err)
assert.Equal(t, 1, len(info.Children))
assert.Check(t, info.FormatSpecific != nil)
qcow2 := info.FormatSpecific.Qcow2()
assert.Check(t, qcow2 != nil)
assert.Equal(t, qcow2.Compat, "1.1")

t.Run("diff", func(t *testing.T) {
// qemu-img create -f qcow2 -F qcow2 -b foo.qcow2 bar.qcow2
// (QEMU 8.0)
const s = `{
"children": [
{
"name": "file",
"info": {
"children": [
],
"virtual-size": 197120,
"filename": "bar.qcow2",
"format": "file",
"actual-size": 200704,
"format-specific": {
"type": "file",
"data": {
}
},
"dirty-flag": false
}
}
],
"backing-filename-format": "qcow2",
"virtual-size": 4294967296,
"filename": "bar.qcow2",
"cluster-size": 65536,
"format": "qcow2",
"actual-size": 200704,
"format-specific": {
"type": "qcow2",
"data": {
"compat": "1.1",
"compression-type": "zlib",
"lazy-refcounts": false,
"refcount-bits": 16,
"corrupt": false,
"extended-l2": false
}
},
"full-backing-filename": "foo.qcow2",
"backing-filename": "foo.qcow2",
"dirty-flag": false
}`
info, err := ParseInfo([]byte(s))
assert.NilError(t, err)
assert.Equal(t, 1, len(info.Children))
assert.Equal(t, "foo.qcow2", info.BackingFilename)
assert.Equal(t, "bar.qcow2", info.Filename)
assert.Check(t, info.FormatSpecific != nil)
qcow2 := info.FormatSpecific.Qcow2()
assert.Check(t, qcow2 != nil)
assert.Equal(t, qcow2.Compat, "1.1")
})
})
t.Run("vmdk", func(t *testing.T) {
t.Run("twoGbMaxExtentSparse", func(t *testing.T) {
// qemu-img create -f vmdk foo.vmdk 4G -o subformat=twoGbMaxExtentSparse
// (QEMU 8.0)
const s = `{
"children": [
{
"name": "extents.1",
"info": {
"children": [
],
"virtual-size": 327680,
"filename": "foo-s002.vmdk",
"format": "file",
"actual-size": 327680,
"format-specific": {
"type": "file",
"data": {
}
},
"dirty-flag": false
}
},
{
"name": "extents.0",
"info": {
"children": [
],
"virtual-size": 327680,
"filename": "foo-s001.vmdk",
"format": "file",
"actual-size": 327680,
"format-specific": {
"type": "file",
"data": {
}
},
"dirty-flag": false
}
},
{
"name": "file",
"info": {
"children": [
],
"virtual-size": 512,
"filename": "foo.vmdk",
"format": "file",
"actual-size": 4096,
"format-specific": {
"type": "file",
"data": {
}
},
"dirty-flag": false
}
}
],
"virtual-size": 4294967296,
"filename": "foo.vmdk",
"cluster-size": 65536,
"format": "vmdk",
"actual-size": 659456,
"format-specific": {
"type": "vmdk",
"data": {
"cid": 918420663,
"parent-cid": 4294967295,
"create-type": "twoGbMaxExtentSparse",
"extents": [
{
"virtual-size": 2147483648,
"filename": "foo-s001.vmdk",
"cluster-size": 65536,
"format": "SPARSE"
},
{
"virtual-size": 2147483648,
"filename": "foo-s002.vmdk",
"cluster-size": 65536,
"format": "SPARSE"
}
]
}
},
"dirty-flag": false
}`
info, err := ParseInfo([]byte(s))
assert.NilError(t, err)
assert.Equal(t, 3, len(info.Children))
assert.Equal(t, "foo.vmdk", info.Filename)
assert.Check(t, info.FormatSpecific != nil)
vmdk := info.FormatSpecific.Vmdk()
assert.Check(t, vmdk != nil)
assert.Equal(t, vmdk.CreateType, "twoGbMaxExtentSparse")
})
})
}

0 comments on commit efe1c47

Please sign in to comment.