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/>.
22 from __future__
import print_function
27 # Count the number of errors found
33 # Ensure we don't wrap around or reset to 0 -- the shell only has
34 # an 8-bit return value.
39 def check_fields_match(name
, s_field
, d_field
):
40 if s_field
== d_field
:
43 # Some fields changed names between qemu versions. This list
44 # is used to whitelist such changes in each section / description.
46 'apic': ['timer', 'timer_expiry'],
47 'e1000': ['dev', 'parent_obj'],
48 'ehci': ['dev', 'pcidev'],
49 'I440FX': ['dev', 'parent_obj'],
50 'ich9_ahci': ['card', 'parent_obj'],
51 'ich9-ahci': ['ahci', 'ich9_ahci'],
52 'ioh3420': ['PCIDevice', 'PCIEDevice'],
53 'ioh-3240-express-root-port': ['port.br.dev',
54 'parent_obj.parent_obj.parent_obj',
55 'port.br.dev.exp.aer_log',
56 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
57 'cirrus_vga': ['hw_cursor_x', 'vga.hw_cursor_x',
58 'hw_cursor_y', 'vga.hw_cursor_y'],
59 'lsiscsi': ['dev', 'parent_obj'],
60 'mch': ['d', 'parent_obj'],
61 'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'],
62 'pcnet': ['pci_dev', 'parent_obj'],
63 'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'],
64 'piix4_pm': ['dev', 'parent_obj', 'pci0_status',
65 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]',
66 'pm1a.sts', 'ar.pm1.evt.sts', 'pm1a.en', 'ar.pm1.evt.en',
67 'pm1_cnt.cnt', 'ar.pm1.cnt.cnt',
68 'tmr.timer', 'ar.tmr.timer',
69 'tmr.overflow_time', 'ar.tmr.overflow_time',
71 'rtl8139': ['dev', 'parent_obj'],
72 'qxl': ['num_surfaces', 'ssd.num_surfaces'],
73 'usb-ccid': ['abProtocolDataStructure', 'abProtocolDataStructure.data'],
74 'usb-host': ['dev', 'parent_obj'],
75 'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
76 'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
77 'vmware_vga': ['card', 'parent_obj'],
78 'vmware_vga_internal': ['depth', 'new_depth'],
79 'xhci': ['pci_dev', 'parent_obj'],
80 'x3130-upstream': ['PCIDevice', 'PCIEDevice'],
81 'xio3130-express-downstream-port': ['port.br.dev',
82 'parent_obj.parent_obj.parent_obj',
83 'port.br.dev.exp.aer_log',
84 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
85 'xio3130-downstream': ['PCIDevice', 'PCIEDevice'],
86 'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
88 'parent_obj.parent_obj.exp.aer_log'],
89 'spapr_pci': ['dma_liobn[0]', 'mig_liobn',
90 'mem_win_addr', 'mig_mem_win_addr',
91 'mem_win_size', 'mig_mem_win_size',
92 'io_win_addr', 'mig_io_win_addr',
93 'io_win_size', 'mig_io_win_size'],
96 if not name
in changed_names
:
99 if s_field
in changed_names
[name
] and d_field
in changed_names
[name
]:
104 def get_changed_sec_name(sec
):
105 # Section names can change -- see commit 292b1634 for an example.
107 "ICH9 LPC": "ICH9-LPC",
108 "e1000-82540em": "e1000",
114 if changes
[item
] == sec
:
118 def exists_in_substruct(fields
, item
):
119 # Some QEMU versions moved a few fields inside a substruct. This
120 # kept the on-wire format the same. This function checks if
121 # something got shifted inside a substruct. For example, the
122 # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
124 if not "Description" in fields
:
127 if not "Fields" in fields
["Description"]:
130 substruct_fields
= fields
["Description"]["Fields"]
132 if substruct_fields
== []:
135 return check_fields_match(fields
["Description"]["name"],
136 substruct_fields
[0]["field"], item
)
139 def check_fields(src_fields
, dest_fields
, desc
, sec
):
140 # This function checks for all the fields in a section. If some
141 # fields got embedded into a substruct, this function will also
142 # attempt to check inside the substruct.
144 d_iter
= iter(dest_fields
)
145 s_iter
= iter(src_fields
)
147 # Using these lists as stacks to store previous value of s_iter
148 # and d_iter, so that when time comes to exit out of a substruct,
149 # we can go back one level up and continue from where we left off.
161 s_item
= next(s_iter
)
162 except StopIteration:
163 if s_iter_list
== []:
166 s_iter
= s_iter_list
.pop()
169 if unused_count
== 0:
170 # We want to avoid advancing just once -- when entering a
171 # dest substruct, or when exiting one.
176 d_item
= next(d_iter
)
177 except StopIteration:
178 if d_iter_list
== []:
179 # We were not in a substruct
180 print("Section \"" + sec
+ "\",", end
=' ')
181 print("Description " + "\"" + desc
+ "\":", end
=' ')
182 print("expected field \"" + s_item
["field"] + "\",", end
=' ')
183 print("while dest has no further fields")
187 d_iter
= d_iter_list
.pop()
191 if unused_count
== 0:
194 if unused_count
!= 0:
195 if advance_dest
== False:
196 unused_count
= unused_count
- s_item
["size"]
197 if unused_count
== 0:
201 print("Section \"" + sec
+ "\",", end
=' ')
202 print("Description \"" + desc
+ "\":", end
=' ')
203 print("unused size mismatch near \"", end
=' ')
204 print(s_item
["field"] + "\"")
209 if advance_src
== False:
210 unused_count
= unused_count
- d_item
["size"]
211 if unused_count
== 0:
215 print("Section \"" + sec
+ "\",", end
=' ')
216 print("Description \"" + desc
+ "\":", end
=' ')
217 print("unused size mismatch near \"", end
=' ')
218 print(d_item
["field"] + "\"")
223 if not check_fields_match(desc
, s_item
["field"], d_item
["field"]):
224 # Some fields were put in substructs, keeping the
225 # on-wire format the same, but breaking static tools
228 # First, check if dest has a new substruct.
229 if exists_in_substruct(d_item
, s_item
["field"]):
230 # listiterators don't have a prev() function, so we
231 # have to store our current location, descend into the
232 # substruct, and ensure we come out as if nothing
233 # happened when the substruct is over.
235 # Essentially we're opening the substructs that got
236 # added which didn't change the wire format.
237 d_iter_list
.append(d_iter
)
238 substruct_fields
= d_item
["Description"]["Fields"]
239 d_iter
= iter(substruct_fields
)
243 # Next, check if src has substruct that dest removed
244 # (can happen in backward migration: 2.0 -> 1.5)
245 if exists_in_substruct(s_item
, d_item
["field"]):
246 s_iter_list
.append(s_iter
)
247 substruct_fields
= s_item
["Description"]["Fields"]
248 s_iter
= iter(substruct_fields
)
252 if s_item
["field"] == "unused" or d_item
["field"] == "unused":
253 if s_item
["size"] == d_item
["size"]:
256 if d_item
["field"] == "unused":
258 unused_count
= d_item
["size"] - s_item
["size"]
261 if s_item
["field"] == "unused":
263 unused_count
= s_item
["size"] - d_item
["size"]
266 print("Section \"" + sec
+ "\",", end
=' ')
267 print("Description \"" + desc
+ "\":", end
=' ')
268 print("expected field \"" + s_item
["field"] + "\",", end
=' ')
269 print("got \"" + d_item
["field"] + "\"; skipping rest")
273 check_version(s_item
, d_item
, sec
, desc
)
275 if not "Description" in s_item
:
276 # Check size of this field only if it's not a VMSTRUCT entry
277 check_size(s_item
, d_item
, sec
, desc
, s_item
["field"])
279 check_description_in_list(s_item
, d_item
, sec
, desc
)
282 def check_subsections(src_sub
, dest_sub
, desc
, sec
):
283 for s_item
in src_sub
:
285 for d_item
in dest_sub
:
286 if s_item
["name"] != d_item
["name"]:
290 check_descriptions(s_item
, d_item
, sec
)
293 print("Section \"" + sec
+ "\", Description \"" + desc
+ "\":", end
=' ')
294 print("Subsection \"" + s_item
["name"] + "\" not found")
298 def check_description_in_list(s_item
, d_item
, sec
, desc
):
299 if not "Description" in s_item
:
302 if not "Description" in d_item
:
303 print("Section \"" + sec
+ "\", Description \"" + desc
+ "\",", end
=' ')
304 print("Field \"" + s_item
["field"] + "\": missing description")
308 check_descriptions(s_item
["Description"], d_item
["Description"], sec
)
311 def check_descriptions(src_desc
, dest_desc
, sec
):
312 check_version(src_desc
, dest_desc
, sec
, src_desc
["name"])
314 if not check_fields_match(sec
, src_desc
["name"], dest_desc
["name"]):
315 print("Section \"" + sec
+ "\":", end
=' ')
316 print("Description \"" + src_desc
["name"] + "\"", end
=' ')
317 print("missing, got \"" + dest_desc
["name"] + "\" instead; skipping")
322 if not f
in dest_desc
:
323 print("Section \"" + sec
+ "\"", end
=' ')
324 print("Description \"" + src_desc
["name"] + "\":", end
=' ')
325 print("Entry \"" + f
+ "\" missing")
330 check_fields(src_desc
[f
], dest_desc
[f
], src_desc
["name"], sec
)
332 if f
== 'Subsections':
333 check_subsections(src_desc
[f
], dest_desc
[f
], src_desc
["name"], sec
)
336 def check_version(s
, d
, sec
, desc
=None):
337 if s
["version_id"] > d
["version_id"]:
338 print("Section \"" + sec
+ "\"", end
=' ')
340 print("Description \"" + desc
+ "\":", end
=' ')
341 print("version error:", s
["version_id"], ">", d
["version_id"])
344 if not "minimum_version_id" in d
:
347 if s
["version_id"] < d
["minimum_version_id"]:
348 print("Section \"" + sec
+ "\"", end
=' ')
350 print("Description \"" + desc
+ "\":", end
=' ')
351 print("minimum version error:", s
["version_id"], "<", end
=' ')
352 print(d
["minimum_version_id"])
356 def check_size(s
, d
, sec
, desc
=None, field
=None):
357 if s
["size"] != d
["size"]:
358 print("Section \"" + sec
+ "\"", end
=' ')
360 print("Description \"" + desc
+ "\"", end
=' ')
362 print("Field \"" + field
+ "\"", end
=' ')
363 print("size mismatch:", s
["size"], ",", d
["size"])
367 def check_machine_type(s
, d
):
368 if s
["Name"] != d
["Name"]:
369 print("Warning: checking incompatible machine types:", end
=' ')
370 print("\"" + s
["Name"] + "\", \"" + d
["Name"] + "\"")
375 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."
377 parser
= argparse
.ArgumentParser(description
=help_text
)
378 parser
.add_argument('-s', '--src', type=file, required
=True,
379 help='json dump from src qemu')
380 parser
.add_argument('-d', '--dest', type=file, required
=True,
381 help='json dump from dest qemu')
382 parser
.add_argument('--reverse', required
=False, default
=False,
384 help='reverse the direction')
385 args
= parser
.parse_args()
387 src_data
= json
.load(args
.src
)
388 dest_data
= json
.load(args
.dest
)
399 if not dest_sec
in dest_data
:
400 # Either the section name got changed, or the section
401 # doesn't exist in dest.
402 dest_sec
= get_changed_sec_name(sec
)
403 if not dest_sec
in dest_data
:
404 print("Section \"" + sec
+ "\" does not exist in dest")
409 d
= dest_data
[dest_sec
]
411 if sec
== "vmschkmachine":
412 check_machine_type(s
, d
)
415 check_version(s
, d
, sec
)
419 print("Section \"" + sec
+ "\": Entry \"" + entry
+ "\"", end
=' ')
424 if entry
== "Description":
425 check_descriptions(s
[entry
], d
[entry
], sec
)
430 if __name__
== '__main__':