3 # Compares vmstate information stored in JSON format, obtained from
4 # the -dump-vmstate QEMU command.
6 # Copyright 2014 Amit Shah <amit.shah@redhat.com>
7 # Copyright 2014 Red Hat, Inc.
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License along
20 # with this program; if not, see <http://www.gnu.org/licenses/>.
26 # Count the number of errors found
32 # Ensure we don't wrap around or reset to 0 -- the shell only has
33 # an 8-bit return value.
38 def check_fields_match(name
, s_field
, d_field
):
39 if s_field
== d_field
:
42 # Some fields changed names between qemu versions. This list
43 # is used to whitelist such changes in each section / description.
45 'apic': ['timer', 'timer_expiry'],
46 'e1000': ['dev', 'parent_obj'],
47 'ehci': ['dev', 'pcidev'],
48 'I440FX': ['dev', 'parent_obj'],
49 'ich9_ahci': ['card', 'parent_obj'],
50 'ich9-ahci': ['ahci', 'ich9_ahci'],
51 'ioh3420': ['PCIDevice', 'PCIEDevice'],
52 'ioh-3240-express-root-port': ['port.br.dev',
53 'parent_obj.parent_obj.parent_obj',
54 'port.br.dev.exp.aer_log',
55 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
56 'lsiscsi': ['dev', 'parent_obj'],
57 'mch': ['d', 'parent_obj'],
58 'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'],
59 'pcnet': ['pci_dev', 'parent_obj'],
60 'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'],
61 'piix4_pm': ['dev', 'parent_obj', 'pci0_status',
62 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]',
63 'pm1a.sts', 'ar.pm1.evt.sts', 'pm1a.en', 'ar.pm1.evt.en',
64 'pm1_cnt.cnt', 'ar.pm1.cnt.cnt',
65 'tmr.timer', 'ar.tmr.timer',
66 'tmr.overflow_time', 'ar.tmr.overflow_time',
68 'rtl8139': ['dev', 'parent_obj'],
69 'qxl': ['num_surfaces', 'ssd.num_surfaces'],
70 'usb-ccid': ['abProtocolDataStructure', 'abProtocolDataStructure.data'],
71 'usb-host': ['dev', 'parent_obj'],
72 'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
73 'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
74 'vmware_vga': ['card', 'parent_obj'],
75 'vmware_vga_internal': ['depth', 'new_depth'],
76 'xhci': ['pci_dev', 'parent_obj'],
77 'x3130-upstream': ['PCIDevice', 'PCIEDevice'],
78 'xio3130-express-downstream-port': ['port.br.dev',
79 'parent_obj.parent_obj.parent_obj',
80 'port.br.dev.exp.aer_log',
81 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
82 'xio3130-downstream': ['PCIDevice', 'PCIEDevice'],
83 'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
85 'parent_obj.parent_obj.exp.aer_log'],
88 if not name
in changed_names
:
91 if s_field
in changed_names
[name
] and d_field
in changed_names
[name
]:
96 def get_changed_sec_name(sec
):
97 # Section names can change -- see commit 292b1634 for an example.
99 "ICH9 LPC": "ICH9-LPC",
105 if changes
[item
] == sec
:
109 def exists_in_substruct(fields
, item
):
110 # Some QEMU versions moved a few fields inside a substruct. This
111 # kept the on-wire format the same. This function checks if
112 # something got shifted inside a substruct. For example, the
113 # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
115 if not "Description" in fields
:
118 if not "Fields" in fields
["Description"]:
121 substruct_fields
= fields
["Description"]["Fields"]
123 if substruct_fields
== []:
126 return check_fields_match(fields
["Description"]["name"],
127 substruct_fields
[0]["field"], item
)
130 def check_fields(src_fields
, dest_fields
, desc
, sec
):
131 # This function checks for all the fields in a section. If some
132 # fields got embedded into a substruct, this function will also
133 # attempt to check inside the substruct.
135 d_iter
= iter(dest_fields
)
136 s_iter
= iter(src_fields
)
138 # Using these lists as stacks to store previous value of s_iter
139 # and d_iter, so that when time comes to exit out of a substruct,
140 # we can go back one level up and continue from where we left off.
152 s_item
= s_iter
.next()
153 except StopIteration:
154 if s_iter_list
== []:
157 s_iter
= s_iter_list
.pop()
160 if unused_count
== 0:
161 # We want to avoid advancing just once -- when entering a
162 # dest substruct, or when exiting one.
167 d_item
= d_iter
.next()
168 except StopIteration:
169 if d_iter_list
== []:
170 # We were not in a substruct
171 print "Section \"" + sec
+ "\",",
172 print "Description " + "\"" + desc
+ "\":",
173 print "expected field \"" + s_item
["field"] + "\",",
174 print "while dest has no further fields"
178 d_iter
= d_iter_list
.pop()
182 if unused_count
== 0:
186 if advance_dest
== False:
187 unused_count
= unused_count
- s_item
["size"]
188 if unused_count
== 0:
192 print "Section \"" + sec
+ "\",",
193 print "Description \"" + desc
+ "\":",
194 print "unused size mismatch near \"",
195 print s_item
["field"] + "\""
200 if advance_src
== False:
201 unused_count
= unused_count
- d_item
["size"]
202 if unused_count
== 0:
206 print "Section \"" + sec
+ "\",",
207 print "Description \"" + desc
+ "\":",
208 print "unused size mismatch near \"",
209 print d_item
["field"] + "\""
214 if not check_fields_match(desc
, s_item
["field"], d_item
["field"]):
215 # Some fields were put in substructs, keeping the
216 # on-wire format the same, but breaking static tools
219 # First, check if dest has a new substruct.
220 if exists_in_substruct(d_item
, s_item
["field"]):
221 # listiterators don't have a prev() function, so we
222 # have to store our current location, descend into the
223 # substruct, and ensure we come out as if nothing
224 # happened when the substruct is over.
226 # Essentially we're opening the substructs that got
227 # added which didn't change the wire format.
228 d_iter_list
.append(d_iter
)
229 substruct_fields
= d_item
["Description"]["Fields"]
230 d_iter
= iter(substruct_fields
)
234 # Next, check if src has substruct that dest removed
235 # (can happen in backward migration: 2.0 -> 1.5)
236 if exists_in_substruct(s_item
, d_item
["field"]):
237 s_iter_list
.append(s_iter
)
238 substruct_fields
= s_item
["Description"]["Fields"]
239 s_iter
= iter(substruct_fields
)
243 if s_item
["field"] == "unused" or d_item
["field"] == "unused":
244 if s_item
["size"] == d_item
["size"]:
247 if d_item
["field"] == "unused":
249 unused_count
= d_item
["size"] - s_item
["size"]
252 if s_item
["field"] == "unused":
254 unused_count
= s_item
["size"] - d_item
["size"]
257 print "Section \"" + sec
+ "\",",
258 print "Description \"" + desc
+ "\":",
259 print "expected field \"" + s_item
["field"] + "\",",
260 print "got \"" + d_item
["field"] + "\"; skipping rest"
264 check_version(s_item
, d_item
, sec
, desc
)
266 if not "Description" in s_item
:
267 # Check size of this field only if it's not a VMSTRUCT entry
268 check_size(s_item
, d_item
, sec
, desc
, s_item
["field"])
270 check_description_in_list(s_item
, d_item
, sec
, desc
)
273 def check_subsections(src_sub
, dest_sub
, desc
, sec
):
274 for s_item
in src_sub
:
276 for d_item
in dest_sub
:
277 if s_item
["name"] != d_item
["name"]:
281 check_descriptions(s_item
, d_item
, sec
)
284 print "Section \"" + sec
+ "\", Description \"" + desc
+ "\":",
285 print "Subsection \"" + s_item
["name"] + "\" not found"
289 def check_description_in_list(s_item
, d_item
, sec
, desc
):
290 if not "Description" in s_item
:
293 if not "Description" in d_item
:
294 print "Section \"" + sec
+ "\", Description \"" + desc
+ "\",",
295 print "Field \"" + s_item
["field"] + "\": missing description"
299 check_descriptions(s_item
["Description"], d_item
["Description"], sec
)
302 def check_descriptions(src_desc
, dest_desc
, sec
):
303 check_version(src_desc
, dest_desc
, sec
, src_desc
["name"])
305 if not check_fields_match(sec
, src_desc
["name"], dest_desc
["name"]):
306 print "Section \"" + sec
+ "\":",
307 print "Description \"" + src_desc
["name"] + "\"",
308 print "missing, got \"" + dest_desc
["name"] + "\" instead; skipping"
313 if not f
in dest_desc
:
314 print "Section \"" + sec
+ "\"",
315 print "Description \"" + src_desc
["name"] + "\":",
316 print "Entry \"" + f
+ "\" missing"
321 check_fields(src_desc
[f
], dest_desc
[f
], src_desc
["name"], sec
)
323 if f
== 'Subsections':
324 check_subsections(src_desc
[f
], dest_desc
[f
], src_desc
["name"], sec
)
327 def check_version(s
, d
, sec
, desc
=None):
328 if s
["version_id"] > d
["version_id"]:
329 print "Section \"" + sec
+ "\"",
331 print "Description \"" + desc
+ "\":",
332 print "version error:", s
["version_id"], ">", d
["version_id"]
335 if not "minimum_version_id" in d
:
338 if s
["version_id"] < d
["minimum_version_id"]:
339 print "Section \"" + sec
+ "\"",
341 print "Description \"" + desc
+ "\":",
342 print "minimum version error:", s
["version_id"], "<",
343 print d
["minimum_version_id"]
347 def check_size(s
, d
, sec
, desc
=None, field
=None):
348 if s
["size"] != d
["size"]:
349 print "Section \"" + sec
+ "\"",
351 print "Description \"" + desc
+ "\"",
353 print "Field \"" + field
+ "\"",
354 print "size mismatch:", s
["size"], ",", d
["size"]
358 def check_machine_type(s
, d
):
359 if s
["Name"] != d
["Name"]:
360 print "Warning: checking incompatible machine types:",
361 print "\"" + s
["Name"] + "\", \"" + d
["Name"] + "\""
366 help_text
= "Parse JSON-formatted vmstate dumps from QEMU in files SRC and DEST. Checks whether migration from SRC to DEST QEMU versions would break based on the VMSTATE information contained within the JSON outputs. The JSON output is created from a QEMU invocation with the -dump-vmstate parameter and a filename argument to it. Other parameters to QEMU do not matter, except the -M (machine type) parameter."
368 parser
= argparse
.ArgumentParser(description
=help_text
)
369 parser
.add_argument('-s', '--src', type=file, required
=True,
370 help='json dump from src qemu')
371 parser
.add_argument('-d', '--dest', type=file, required
=True,
372 help='json dump from dest qemu')
373 parser
.add_argument('--reverse', required
=False, default
=False,
375 help='reverse the direction')
376 args
= parser
.parse_args()
378 src_data
= json
.load(args
.src
)
379 dest_data
= json
.load(args
.dest
)
390 if not dest_sec
in dest_data
:
391 # Either the section name got changed, or the section
392 # doesn't exist in dest.
393 dest_sec
= get_changed_sec_name(sec
)
394 if not dest_sec
in dest_data
:
395 print "Section \"" + sec
+ "\" does not exist in dest"
400 d
= dest_data
[dest_sec
]
402 if sec
== "vmschkmachine":
403 check_machine_type(s
, d
)
406 check_version(s
, d
, sec
)
410 print "Section \"" + sec
+ "\": Entry \"" + entry
+ "\"",
415 if entry
== "Description":
416 check_descriptions(s
[entry
], d
[entry
], sec
)
421 if __name__
== '__main__':