Skip to content

Commit efe1c47

Browse files
committed
pkg/qemu/imgutil: add Info fields
Signed-off-by: Akihiro Suda <[email protected]>
1 parent 3dbc08c commit efe1c47

File tree

2 files changed

+287
-7
lines changed

2 files changed

+287
-7
lines changed

pkg/qemu/imgutil/imgutil.go

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,74 @@ import (
99
"strings"
1010
)
1111

12+
type InfoChild struct {
13+
Name string `json:"name,omitempty"` // since QEMU 8.0
14+
Info Info `json:"info,omitempty"` // since QEMU 8.0
15+
}
16+
17+
type InfoFormatSpecific struct {
18+
Type string `json:"type,omitempty"` // since QEMU 1.7
19+
Data json.RawMessage `json:"data,omitempty"` // since QEMU 1.7
20+
}
21+
22+
func (sp *InfoFormatSpecific) Qcow2() *InfoFormatSpecificDataQcow2 {
23+
if sp.Type != "qcow2" {
24+
return nil
25+
}
26+
var x InfoFormatSpecificDataQcow2
27+
if err := json.Unmarshal(sp.Data, &x); err != nil {
28+
panic(err)
29+
}
30+
return &x
31+
}
32+
33+
func (sp *InfoFormatSpecific) Vmdk() *InfoFormatSpecificDataVmdk {
34+
if sp.Type != "vmdk" {
35+
return nil
36+
}
37+
var x InfoFormatSpecificDataVmdk
38+
if err := json.Unmarshal(sp.Data, &x); err != nil {
39+
panic(err)
40+
}
41+
return &x
42+
}
43+
44+
type InfoFormatSpecificDataQcow2 struct {
45+
Compat string `json:"compat,omitempty"` // since QEMU 1.7
46+
LazyRefcounts bool `json:"lazy-refcounts,omitempty"` // since QEMU 1.7
47+
Corrupt bool `json:"corrupt,omitempty"` // since QEMU 2.2
48+
RefcountBits int `json:"refcount-bits,omitempty"` // since QEMU 2.3
49+
CompressionType string `json:"compression-type,omitempty"` // since QEMU 5.1
50+
ExtendedL2 bool `json:"extended-l2,omitempty"` // since QEMU 5.2
51+
}
52+
53+
type InfoFormatSpecificDataVmdk struct {
54+
CreateType string `json:"create-type,omitempty"` // since QEMU 1.7
55+
CID int `json:"cid,omitempty"` // since QEMU 1.7
56+
ParentCID int `json:"parent-cid,omitempty"` // since QEMU 1.7
57+
Extents []InfoFormatSpecificDataVmdkExtent `json:"extents,omitempty"` // since QEMU 1.7
58+
}
59+
60+
type InfoFormatSpecificDataVmdkExtent struct {
61+
Filename string `json:"filename,omitempty"` // since QEMU 1.7
62+
Format string `json:"format,omitempty"` // since QEMU 1.7
63+
VSize int64 `json:"virtual-size,omitempty"` // since QEMU 1.7
64+
ClusterSize int `json:"cluster-size,omitempty"` // since QEMU 1.7
65+
}
66+
1267
// Info corresponds to the output of `qemu-img info --output=json FILE`
1368
type Info struct {
14-
Format string `json:"format,omitempty"` // since QEMU 1.3
15-
VSize int64 `json:"virtual-size,omitempty"`
69+
Filename string `json:"filename,omitempty"` // since QEMU 1.3
70+
Format string `json:"format,omitempty"` // since QEMU 1.3
71+
VSize int64 `json:"virtual-size,omitempty"` // since QEMU 1.3
72+
ActualSize int64 `json:"actual-size,omitempty"` // since QEMU 1.3
73+
DirtyFlag bool `json:"dirty-flag,omitempty"` // since QEMU 5.2
74+
ClusterSize int `json:"cluster-size,omitempty"` // since QEMU 1.3
75+
BackingFilename string `json:"backing-filename,omitempty"` // since QEMU 1.3
76+
FullBackingFilename string `json:"full-backing-filename,omitempty"` // since QEMU 1.3
77+
BackingFilenameFormat string `json:"backing-filename-format,omitempty"` // since QEMU 1.3
78+
FormatSpecific *InfoFormatSpecific `json:"format-specific,omitempty"` // since QEMU 1.7
79+
Children []InfoChild `json:"children,omitempty"` // since QEMU 8.0
1680
}
1781

1882
func ConvertToRaw(source string, dest string) error {
@@ -27,6 +91,14 @@ func ConvertToRaw(source string, dest string) error {
2791
return nil
2892
}
2993

94+
func ParseInfo(b []byte) (*Info, error) {
95+
var imgInfo Info
96+
if err := json.Unmarshal(b, &imgInfo); err != nil {
97+
return nil, err
98+
}
99+
return &imgInfo, nil
100+
}
101+
30102
func GetInfo(f string) (*Info, error) {
31103
var stdout, stderr bytes.Buffer
32104
cmd := exec.Command("qemu-img", "info", "--output=json", "--force-share", f)
@@ -36,11 +108,7 @@ func GetInfo(f string) (*Info, error) {
36108
return nil, fmt.Errorf("failed to run %v: stdout=%q, stderr=%q: %w",
37109
cmd.Args, stdout.String(), stderr.String(), err)
38110
}
39-
var imgInfo Info
40-
if err := json.Unmarshal(stdout.Bytes(), &imgInfo); err != nil {
41-
return nil, err
42-
}
43-
return &imgInfo, nil
111+
return ParseInfo(stdout.Bytes())
44112
}
45113

46114
func DetectFormat(f string) (string, error) {

pkg/qemu/imgutil/imgutil_test.go

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package imgutil
2+
3+
import (
4+
"testing"
5+
6+
"gotest.tools/v3/assert"
7+
)
8+
9+
func TestParseInfo(t *testing.T) {
10+
t.Run("qcow2", func(t *testing.T) {
11+
// qemu-img create -f qcow2 foo.qcow2 4G
12+
// (QEMU 8.0)
13+
const s = `{
14+
"children": [
15+
{
16+
"name": "file",
17+
"info": {
18+
"children": [
19+
],
20+
"virtual-size": 197120,
21+
"filename": "foo.qcow2",
22+
"format": "file",
23+
"actual-size": 200704,
24+
"format-specific": {
25+
"type": "file",
26+
"data": {
27+
}
28+
},
29+
"dirty-flag": false
30+
}
31+
}
32+
],
33+
"virtual-size": 4294967296,
34+
"filename": "foo.qcow2",
35+
"cluster-size": 65536,
36+
"format": "qcow2",
37+
"actual-size": 200704,
38+
"format-specific": {
39+
"type": "qcow2",
40+
"data": {
41+
"compat": "1.1",
42+
"compression-type": "zlib",
43+
"lazy-refcounts": false,
44+
"refcount-bits": 16,
45+
"corrupt": false,
46+
"extended-l2": false
47+
}
48+
},
49+
"dirty-flag": false
50+
}`
51+
52+
info, err := ParseInfo([]byte(s))
53+
assert.NilError(t, err)
54+
assert.Equal(t, 1, len(info.Children))
55+
assert.Check(t, info.FormatSpecific != nil)
56+
qcow2 := info.FormatSpecific.Qcow2()
57+
assert.Check(t, qcow2 != nil)
58+
assert.Equal(t, qcow2.Compat, "1.1")
59+
60+
t.Run("diff", func(t *testing.T) {
61+
// qemu-img create -f qcow2 -F qcow2 -b foo.qcow2 bar.qcow2
62+
// (QEMU 8.0)
63+
const s = `{
64+
"children": [
65+
{
66+
"name": "file",
67+
"info": {
68+
"children": [
69+
],
70+
"virtual-size": 197120,
71+
"filename": "bar.qcow2",
72+
"format": "file",
73+
"actual-size": 200704,
74+
"format-specific": {
75+
"type": "file",
76+
"data": {
77+
}
78+
},
79+
"dirty-flag": false
80+
}
81+
}
82+
],
83+
"backing-filename-format": "qcow2",
84+
"virtual-size": 4294967296,
85+
"filename": "bar.qcow2",
86+
"cluster-size": 65536,
87+
"format": "qcow2",
88+
"actual-size": 200704,
89+
"format-specific": {
90+
"type": "qcow2",
91+
"data": {
92+
"compat": "1.1",
93+
"compression-type": "zlib",
94+
"lazy-refcounts": false,
95+
"refcount-bits": 16,
96+
"corrupt": false,
97+
"extended-l2": false
98+
}
99+
},
100+
"full-backing-filename": "foo.qcow2",
101+
"backing-filename": "foo.qcow2",
102+
"dirty-flag": false
103+
}`
104+
info, err := ParseInfo([]byte(s))
105+
assert.NilError(t, err)
106+
assert.Equal(t, 1, len(info.Children))
107+
assert.Equal(t, "foo.qcow2", info.BackingFilename)
108+
assert.Equal(t, "bar.qcow2", info.Filename)
109+
assert.Check(t, info.FormatSpecific != nil)
110+
qcow2 := info.FormatSpecific.Qcow2()
111+
assert.Check(t, qcow2 != nil)
112+
assert.Equal(t, qcow2.Compat, "1.1")
113+
})
114+
})
115+
t.Run("vmdk", func(t *testing.T) {
116+
t.Run("twoGbMaxExtentSparse", func(t *testing.T) {
117+
// qemu-img create -f vmdk foo.vmdk 4G -o subformat=twoGbMaxExtentSparse
118+
// (QEMU 8.0)
119+
const s = `{
120+
"children": [
121+
{
122+
"name": "extents.1",
123+
"info": {
124+
"children": [
125+
],
126+
"virtual-size": 327680,
127+
"filename": "foo-s002.vmdk",
128+
"format": "file",
129+
"actual-size": 327680,
130+
"format-specific": {
131+
"type": "file",
132+
"data": {
133+
}
134+
},
135+
"dirty-flag": false
136+
}
137+
},
138+
{
139+
"name": "extents.0",
140+
"info": {
141+
"children": [
142+
],
143+
"virtual-size": 327680,
144+
"filename": "foo-s001.vmdk",
145+
"format": "file",
146+
"actual-size": 327680,
147+
"format-specific": {
148+
"type": "file",
149+
"data": {
150+
}
151+
},
152+
"dirty-flag": false
153+
}
154+
},
155+
{
156+
"name": "file",
157+
"info": {
158+
"children": [
159+
],
160+
"virtual-size": 512,
161+
"filename": "foo.vmdk",
162+
"format": "file",
163+
"actual-size": 4096,
164+
"format-specific": {
165+
"type": "file",
166+
"data": {
167+
}
168+
},
169+
"dirty-flag": false
170+
}
171+
}
172+
],
173+
"virtual-size": 4294967296,
174+
"filename": "foo.vmdk",
175+
"cluster-size": 65536,
176+
"format": "vmdk",
177+
"actual-size": 659456,
178+
"format-specific": {
179+
"type": "vmdk",
180+
"data": {
181+
"cid": 918420663,
182+
"parent-cid": 4294967295,
183+
"create-type": "twoGbMaxExtentSparse",
184+
"extents": [
185+
{
186+
"virtual-size": 2147483648,
187+
"filename": "foo-s001.vmdk",
188+
"cluster-size": 65536,
189+
"format": "SPARSE"
190+
},
191+
{
192+
"virtual-size": 2147483648,
193+
"filename": "foo-s002.vmdk",
194+
"cluster-size": 65536,
195+
"format": "SPARSE"
196+
}
197+
]
198+
}
199+
},
200+
"dirty-flag": false
201+
}`
202+
info, err := ParseInfo([]byte(s))
203+
assert.NilError(t, err)
204+
assert.Equal(t, 3, len(info.Children))
205+
assert.Equal(t, "foo.vmdk", info.Filename)
206+
assert.Check(t, info.FormatSpecific != nil)
207+
vmdk := info.FormatSpecific.Vmdk()
208+
assert.Check(t, vmdk != nil)
209+
assert.Equal(t, vmdk.CreateType, "twoGbMaxExtentSparse")
210+
})
211+
})
212+
}

0 commit comments

Comments
 (0)