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 'cirrus_vga': ['hw_cursor_x', 'vga.hw_cursor_x',
57 'hw_cursor_y', 'vga.hw_cursor_y'],
58 'lsiscsi': ['dev', 'parent_obj'],
59 'mch': ['d', 'parent_obj'],
60 'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'],
61 'pcnet': ['pci_dev', 'parent_obj'],
62 'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'],
63 'piix4_pm': ['dev', 'parent_obj', 'pci0_status',
64 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]',
65 'pm1a.sts', 'ar.pm1.evt.sts', 'pm1a.en', 'ar.pm1.evt.en',
66 'pm1_cnt.cnt', 'ar.pm1.cnt.cnt',
67 'tmr.timer', 'ar.tmr.timer',
68 'tmr.overflow_time', 'ar.tmr.overflow_time',
70 'rtl8139': ['dev', 'parent_obj'],
71 'qxl': ['num_surfaces', 'ssd.num_surfaces'],
72 'usb-ccid': ['abProtocolDataStructure', 'abProtocolDataStructure.data'],
73 'usb-host': ['dev', 'parent_obj'],
74 'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
75 'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
76 'vmware_vga': ['card', 'parent_obj'],
77 'vmware_vga_internal': ['depth', 'new_depth'],
78 'xhci': ['pci_dev', 'parent_obj'],
79 'x3130-upstream': ['PCIDevice', 'PCIEDevice'],
80 'xio3130-express-downstream-port': ['port.br.dev',
81 'parent_obj.parent_obj.parent_obj',
82 'port.br.dev.exp.aer_log',
83 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
84 'xio3130-downstream': ['PCIDevice', 'PCIEDevice'],
85 'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
87 'parent_obj.parent_obj.exp.aer_log'],
90 if not name
in changed_names
:
93 if s_field
in changed_names
[name
] and d_field
in changed_names
[name
]:
98 def get_changed_sec_name(sec
):
99 # Section names can change -- see commit 292b1634 for an example.
101 "ICH9 LPC": "ICH9-LPC",
102 "e1000-82540em": "e1000",
108 if changes
[item
] == sec
:
112 def exists_in_substruct(fields
, item
):
113 # Some QEMU versions moved a few fields inside a substruct. This
114 # kept the on-wire format the same. This function checks if
115 # something got shifted inside a substruct. For example, the
116 # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
118 if not "Description" in fields
:
121 if not "Fields" in fields
["Description"]:
124 substruct_fields
= fields
["Description"]["Fields"]
126 if substruct_fields
== []:
129 return check_fields_match(fields
["Description"]["name"],
130 substruct_fields
[0]["field"], item
)
133 def check_fields(src_fields
, dest_fields
, desc
, sec
):
134 # This function checks for all the fields in a section. If some
135 # fields got embedded into a substruct, this function will also
136 # attempt to check inside the substruct.
138 d_iter
= iter(dest_fields
)
139 s_iter
= iter(src_fields
)
141 # Using these lists as stacks to store previous value of s_iter
142 # and d_iter, so that when time comes to exit out of a substruct,
143 # we can go back one level up and continue from where we left off.
155 s_item
= s_iter
.next()
156 except StopIteration:
157 if s_iter_list
== []:
160 s_iter
= s_iter_list
.pop()
163 if unused_count
== 0:
164 # We want to avoid advancing just once -- when entering a
165 # dest substruct, or when exiting one.
170 d_item
= d_iter
.next()
171 except StopIteration:
172 if d_iter_list
== []:
173 # We were not in a substruct
174 print "Section \"" + sec
+ "\",",
175 print "Description " + "\"" + desc
+ "\":",
176 print "expected field \"" + s_item
["field"] + "\",",
177 print "while dest has no further fields"
181 d_iter
= d_iter_list
.pop()
185 if unused_count
== 0:
189 if advance_dest
== False:
190 unused_count
= unused_count
- s_item
["size"]
191 if unused_count
== 0:
195 print "Section \"" + sec
+ "\",",
196 print "Description \"" + desc
+ "\":",
197 print "unused size mismatch near \"",
198 print s_item
["field"] + "\""
203 if advance_src
== False:
204 unused_count
= unused_count
- d_item
["size"]
205 if unused_count
== 0:
209 print "Section \"" + sec
+ "\",",
210 print "Description \"" + desc
+ "\":",
211 print "unused size mismatch near \"",
212 print d_item
["field"] + "\""
217 if not check_fields_match(desc
, s_item
["field"], d_item
["field"]):
218 # Some fields were put in substructs, keeping the
219 # on-wire format the same, but breaking static tools
222 # First, check if dest has a new substruct.
223 if exists_in_substruct(d_item
, s_item
["field"]):
224 # listiterators don't have a prev() function, so we
225 # have to store our current location, descend into the
226 # substruct, and ensure we come out as if nothing
227 # happened when the substruct is over.
229 # Essentially we're opening the substructs that got
230 # added which didn't change the wire format.
231 d_iter_list
.append(d_iter
)
232 substruct_fields
= d_item
["Description"]["Fields"]
233 d_iter
= iter(substruct_fields
)
237 # Next, check if src has substruct that dest removed
238 # (can happen in backward migration: 2.0 -> 1.5)
239 if exists_in_substruct(s_item
, d_item
["field"]):
240 s_iter_list
.append(s_iter
)
241 substruct_fields
= s_item
["Description"]["Fields"]
242 s_iter
= iter(substruct_fields
)
246 if s_item
["field"] == "unused" or d_item
["field"] == "unused":
247 if s_item
["size"] == d_item
["size"]:
250 if d_item
["field"] == "unused":
252 unused_count
= d_item
["size"] - s_item
["size"]
255 if s_item
["field"] == "unused":
257 unused_count
= s_item
["size"] - d_item
["size"]
260 print "Section \"" + sec
+ "\",",
261 print "Description \"" + desc
+ "\":",
262 print "expected field \"" + s_item
["field"] + "\",",
263 print "got \"" + d_item
["field"] + "\"; skipping rest"
267 check_version(s_item
, d_item
, sec
, desc
)
269 if not "Description" in s_item
:
270 # Check size of this field only if it's not a VMSTRUCT entry
271 check_size(s_item
, d_item
, sec
, desc
, s_item
["field"])
273 check_description_in_list(s_item
, d_item
, sec
, desc
)
276 def check_subsections(src_sub
, dest_sub
, desc
, sec
):
277 for s_item
in src_sub
:
279 for d_item
in dest_sub
:
280 if s_item
["name"] != d_item
["name"]:
284 check_descriptions(s_item
, d_item
, sec
)
287 print "Section \"" + sec
+ "\", Description \"" + desc
+ "\":",
288 print "Subsection \"" + s_item
["name"] + "\" not found"
292 def check_description_in_list(s_item
, d_item
, sec
, desc
):
293 if not "Description" in s_item
:
296 if not "Description" in d_item
:
297 print "Section \"" + sec
+ "\", Description \"" + desc
+ "\",",
298 print "Field \"" + s_item
["field"] + "\": missing description"
302 check_descriptions(s_item
["Description"], d_item
["Description"], sec
)
305 def check_descriptions(src_desc
, dest_desc
, sec
):
306 check_version(src_desc
, dest_desc
, sec
, src_desc
["name"])
308 if not check_fields_match(sec
, src_desc
["name"], dest_desc
["name"]):
309 print "Section \"" + sec
+ "\":",
310 print "Description \"" + src_desc
["name"] + "\"",
311 print "missing, got \"" + dest_desc
["name"] + "\" instead; skipping"
316 if not f
in dest_desc
:
317 print "Section \"" + sec
+ "\"",
318 print "Description \"" + src_desc
["name"] + "\":",
319 print "Entry \"" + f
+ "\" missing"
324 check_fields(src_desc
[f
], dest_desc
[f
], src_desc
["name"], sec
)
326 if f
== 'Subsections':
327 check_subsections(src_desc
[f
], dest_desc
[f
], src_desc
["name"], sec
)
330 def check_version(s
, d
, sec
, desc
=None):
331 if s
["version_id"] > d
["version_id"]:
332 print "Section \"" + sec
+ "\"",
334 print "Description \"" + desc
+ "\":",
335 print "version error:", s
["version_id"], ">", d
["version_id"]
338 if not "minimum_version_id" in d
:
341 if s
["version_id"] < d
["minimum_version_id"]:
342 print "Section \"" + sec
+ "\"",
344 print "Description \"" + desc
+ "\":",
345 print "minimum version error:", s
["version_id"], "<",
346 print d
["minimum_version_id"]
350 def check_size(s
, d
, sec
, desc
=None, field
=None):
351 if s
["size"] != d
["size"]:
352 print "Section \"" + sec
+ "\"",
354 print "Description \"" + desc
+ "\"",
356 print "Field \"" + field
+ "\"",
357 print "size mismatch:", s
["size"], ",", d
["size"]
361 def check_machine_type(s
, d
):
362 if s
["Name"] != d
["Name"]:
363 print "Warning: checking incompatible machine types:",
364 print "\"" + s
["Name"] + "\", \"" + d
["Name"] + "\""
369 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."
371 parser
= argparse
.ArgumentParser(description
=help_text
)
372 parser
.add_argument('-s', '--src', type=file, required
=True,
373 help='json dump from src qemu')
374 parser
.add_argument('-d', '--dest', type=file, required
=True,
375 help='json dump from dest qemu')
376 parser
.add_argument('--reverse', required
=False, default
=False,
378 help='reverse the direction')
379 args
= parser
.parse_args()
381 src_data
= json
.load(args
.src
)
382 dest_data
= json
.load(args
.dest
)
393 if not dest_sec
in dest_data
:
394 # Either the section name got changed, or the section
395 # doesn't exist in dest.
396 dest_sec
= get_changed_sec_name(sec
)
397 if not dest_sec
in dest_data
:
398 print "Section \"" + sec
+ "\" does not exist in dest"
403 d
= dest_data
[dest_sec
]
405 if sec
== "vmschkmachine":
406 check_machine_type(s
, d
)
409 check_version(s
, d
, sec
)
413 print "Section \"" + sec
+ "\": Entry \"" + entry
+ "\"",
418 if entry
== "Description":
419 check_descriptions(s
[entry
], d
[entry
], sec
)
424 if __name__
== '__main__':