Laszlo Ersek
2022-Apr-20 16:23 UTC
[Libguestfs] [v2v PATCH 0/9] ensure x86-64-v2 uarch level for RHEL-9.0+ guests
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2076013 We don't specify a VCPU model in the destination domain description if the source description doesn't specify one. This is a problem for x86-64 RHEL-9.0+ guests, when using the QEMU or libvirt output modules. Those outputs default to the "qemu64" VCPU model in the absence of an explicit VCPU model specification, and "qemu64" does not satisfy the "x86-64-v2" microarchitecture level, which is required by RHEL-9.0+ guests. As a result, these guests crash during boot, after conversion. The series recognizes guests that are unable to boot on the default QEMU VCPU model, and specifies host CPU passthrough for them in the QEMU and libvirt output modules. Testing: <https://bugzilla.redhat.com/show_bug.cgi?id=2076013#c18>. Thanks, Laszlo Laszlo Ersek (9): types: introduce the "gcaps_default_cpu" field create_libvirt_xml: simplify match on (s_cpu_vendor, s_cpu_model) create_libvirt_xml: normalize match on (s_cpu_vendor, s_cpu_model) create_libvirt_xml: eliminate childless <cpu match="minimum"/> element create_libvirt_xml: restrict 'match="minimum"' <cpu> attribute production create_libvirt_xml: honor "gcaps_default_cpu" output_qemu: reflect source VCPU model to the QEMU command line output_qemu: honor "gcaps_default_cpu" convert_linux: set "gcaps_default_cpu = false" for x86_64 RHEL-9.0+ guests convert/convert_linux.ml | 9 ++++++ convert/convert_windows.ml | 1 + lib/types.ml | 3 ++ lib/types.mli | 14 +++++++++ output/create_libvirt_xml.ml | 31 +++++++++++--------- output/output_qemu.ml | 7 +++++ 6 files changed, 51 insertions(+), 14 deletions(-) base-commit: 8643970f9791b1c90dfd6a2dd1abfc9afef1fb52 -- 2.19.1.3.g30247aa5d201
Laszlo Ersek
2022-Apr-20 16:23 UTC
[Libguestfs] [v2v PATCH 1/9] types: introduce the "gcaps_default_cpu" field
Conversion will set "gcaps_default_cpu" to true iff the guest OS is capable of running on QEMU's default VCPU model. This capability will only matter for the QEMU and libvirt output modules, where, in case the capability is false *and* the source hypervisor does not specify a VCPU model, we'll have to manually present the guest OS with a VCPU that looks as close as possible to a physical CPU. (In that case, we'll specify host-passthrough.) The management applications targeted by the RHV and OpenStack output modules have their own explicit VCPU defaults, overriding the QEMU default model even in case the source hypervisor does not specify a VCPU model; those modules will ignore this capability therefore. Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2076013 Signed-off-by: Laszlo Ersek <lersek at redhat.com> --- lib/types.mli | 14 ++++++++++++++ convert/convert_linux.ml | 1 + convert/convert_windows.ml | 1 + lib/types.ml | 3 +++ 4 files changed, 19 insertions(+) diff --git a/lib/types.mli b/lib/types.mli index 37238cd75519..87544a0c59eb 100644 --- a/lib/types.mli +++ b/lib/types.mli @@ -251,52 +251,66 @@ val string_of_target_firmware : target_firmware -> string (** {2 Target NICs} *) type target_nics = source_nic list (** {2 Guest capabilities} *) type guestcaps = { gcaps_block_bus : guestcaps_block_type; gcaps_net_bus : guestcaps_net_type; (** Best block device and network device guest can access. These are determined during conversion by inspecting the guest (and in some cases conversion can actually enhance these by installing drivers). Thus this is not known until after conversion. *) gcaps_virtio_rng : bool; (** Guest supports virtio-rng. *) gcaps_virtio_balloon : bool; (** Guest supports virtio balloon. *) gcaps_isa_pvpanic : bool; (** Guest supports ISA pvpanic device. *) gcaps_virtio_socket : bool; (** Guest supports virtio socket. *) gcaps_machine : guestcaps_machine; (** Machine model. *) gcaps_arch : string; (** Architecture that KVM must emulate. *) gcaps_acpi : bool; (** True if guest supports acpi. *) gcaps_virtio_1_0 : bool; (** The guest supports the virtio devices that it does at the virtio-1.0 protocol level. *) + + gcaps_default_cpu : bool; + (** True iff the guest OS is capable of running on QEMU's default VCPU model. + + This capability only matters for the QEMU and libvirt output modules, + where, in case the capability is false *and* the source hypervisor does + not specify a VCPU model, we must manually present the guest OS with a + VCPU that looks as close as possible to a physical CPU. (In that case, we + specify host-passthrough.) + + The management applications targeted by the RHV and OpenStack output + modules have their own explicit VCPU defaults, overriding the QEMU default + model even in case the source hypervisor does not specify a VCPU model; + those modules ignore this capability therefore. Refer to RHBZ#2076013. *) } (** Guest capabilities after conversion. eg. Was virtio found or installed? *) and guestcaps_block_type = Virtio_blk | IDE and guestcaps_net_type = Virtio_net | E1000 | RTL8139 and guestcaps_machine = I440FX | Q35 | Virt val string_of_guestcaps : guestcaps -> string (** {2 Guest buses} *) type target_buses = { target_virtio_blk_bus : target_bus_slot array; target_ide_bus : target_bus_slot array; target_scsi_bus : target_bus_slot array; target_floppy_bus : target_bus_slot array; } (** Mapping of fixed and removable disks to buses. As shown in the diagram below, there are (currently) four buses attached to the target VM. Each contains a chain of fixed or removable disks. Slots can also be empty. We try to assign disks to the same slot number as they would occupy on the source, although that is not always possible. diff --git a/convert/convert_linux.ml b/convert/convert_linux.ml index c2bbce03637f..93b3922cac60 100644 --- a/convert/convert_linux.ml +++ b/convert/convert_linux.ml @@ -151,52 +151,53 @@ let convert (g : G.guestfs) source inspect keep_serial_console _ if major <= 4 then I440FX else Q35 | ("sles"|"suse-based"|"opensuse"), major -> if major < 10 then I440FX else Q35 | ("debian"|"ubuntu"|"linuxmint"|"kalilinux"), major -> if major < 4 then I440FX else Q35 (* reasonable default for all modern Linux kernels *) | _, _ -> Q35 ), true ) | _ -> Virt, true in (* Return guest capabilities from the convert () function. *) let guestcaps = { gcaps_block_bus = block_type; gcaps_net_bus = net_type; gcaps_virtio_rng = kernel.ki_supports_virtio_rng; gcaps_virtio_balloon = kernel.ki_supports_virtio_balloon; gcaps_isa_pvpanic = kernel.ki_supports_isa_pvpanic; gcaps_virtio_socket = kernel.ki_supports_virtio_socket; gcaps_machine = machine; gcaps_arch = Utils.kvm_arch inspect.i_arch; gcaps_acpi = acpi; gcaps_virtio_1_0 = virtio_1_0; + gcaps_default_cpu = true; } in guestcaps and augeas_grub_configuration () if bootloader#set_augeas_configuration () then Linux.augeas_reload g and unconfigure_xen () (* Remove kmod-xenpv-* (RHEL 3). *) let xenmods List.filter_map ( fun { G.app2_name = name } -> if name = "kmod-xenpv" || String.is_prefix name "kmod-xenpv-" then Some name else None ) inspect.i_apps in Linux.remove g inspect xenmods; (* Undo related nastiness if kmod-xenpv was installed. *) if xenmods <> [] then ( (* kmod-xenpv modules may have been manually copied to other kernels. * Hunt them down and destroy them. *) let dirs = g#find "/lib/modules" in diff --git a/convert/convert_windows.ml b/convert/convert_windows.ml index b53312a9aba4..34a5044dd338 100644 --- a/convert/convert_windows.ml +++ b/convert/convert_windows.ml @@ -282,52 +282,53 @@ let convert (g : G.guestfs) _ inspect _ static_ips Libosinfo_utils.os_support_of_osinfo_device_list (devices @ best_drv_devs) in (if q35 then Q35 else I440FX), vio10 with | Not_found -> (* Pivot on the year 2007. Any Windows version from earlier than * 2007 should use i440fx, anything 2007 or newer should use q35. * Luckily this coincides almost exactly with the release of NT 6. *) (if inspect.i_major_version < 6 then I440FX else Q35), true ) | _ -> Virt, true in (* Return guest capabilities from the convert () function. *) let guestcaps = { gcaps_block_bus = block_driver; gcaps_net_bus = net_driver; gcaps_virtio_rng = virtio_rng_supported; gcaps_virtio_balloon = virtio_ballon_supported; gcaps_isa_pvpanic = isa_pvpanic_supported; gcaps_virtio_socket = virtio_socket_supported; gcaps_machine = machine; gcaps_arch = Utils.kvm_arch inspect.i_arch; gcaps_acpi = true; gcaps_virtio_1_0 = virtio_1_0; + gcaps_default_cpu = true; } in guestcaps and configure_firstboot () (* Note that pnp_wait.exe must be the first firstboot script as it * suppresses PnP for all following scripts. *) let tool_path = virt_tools_data_dir () // "pnp_wait.exe" in if Sys.file_exists tool_path then configure_wait_pnp tool_path else debug (f_"%s is missing. Firstboot scripts may conflict with PnP.") tool_path; (* Install VMDP unconditionally, if available, but don't * warn about it if not. *) let tool_path = virt_tools_data_dir () // "vmdp.exe" in if Sys.file_exists tool_path then configure_vmdp tool_path; (* Install QEMU Guest Agent unconditionally and warn if missing *) let qemu_ga_files = Windows_virtio.copy_qemu_ga g inspect in if qemu_ga_files <> [] then configure_qemu_ga qemu_ga_files diff --git a/lib/types.ml b/lib/types.ml index 92ed0e52513d..7ffb868b1fbf 100644 --- a/lib/types.ml +++ b/lib/types.ml @@ -383,92 +383,95 @@ let string_of_target t target format: %s " (match t.target_file with | TargetFile s -> "[file] " ^ s | TargetURI s -> "[qemu] " ^ s) t.target_format type target_firmware = TargetBIOS | TargetUEFI let string_of_target_firmware = function | TargetBIOS -> "bios" | TargetUEFI -> "uefi" type target_nics = source_nic list type guestcaps = { gcaps_block_bus : guestcaps_block_type; gcaps_net_bus : guestcaps_net_type; gcaps_virtio_rng : bool; gcaps_virtio_balloon : bool; gcaps_isa_pvpanic : bool; gcaps_virtio_socket : bool; gcaps_machine : guestcaps_machine; gcaps_arch : string; gcaps_acpi : bool; gcaps_virtio_1_0 : bool; + gcaps_default_cpu : bool; } and guestcaps_block_type = Virtio_blk | IDE and guestcaps_net_type = Virtio_net | E1000 | RTL8139 and guestcaps_machine = I440FX | Q35 | Virt let string_of_block_type = function | Virtio_blk -> "virtio-blk" | IDE -> "ide" let string_of_net_type = function | Virtio_net -> "virtio-net" | E1000 -> "e1000" | RTL8139 -> "rtl8139" let string_of_machine = function | I440FX -> "i440fx" | Q35 -> "q35" | Virt -> "virt" let string_of_guestcaps gcaps sprintf "\ gcaps_block_bus = %s\n\ gcaps_net_bus = %s\n\ gcaps_virtio_rng = %b\n\ gcaps_virtio_balloon = %b\n\ gcaps_isa_pvpanic = %b\n\ gcaps_virtio_socket = %b\n\ gcaps_machine = %s\n\ gcaps_arch = %s\n\ gcaps_acpi = %b\n\ gcaps_virtio_1_0 = %b\n\ + gcaps_default_cpu = %b\n\ " (string_of_block_type gcaps.gcaps_block_bus) (string_of_net_type gcaps.gcaps_net_bus) gcaps.gcaps_virtio_rng gcaps.gcaps_virtio_balloon gcaps.gcaps_isa_pvpanic gcaps.gcaps_virtio_socket (string_of_machine gcaps.gcaps_machine) gcaps.gcaps_arch gcaps.gcaps_acpi gcaps.gcaps_virtio_1_0 + gcaps.gcaps_default_cpu type target_buses = { target_virtio_blk_bus : target_bus_slot array; target_ide_bus : target_bus_slot array; target_scsi_bus : target_bus_slot array; target_floppy_bus : target_bus_slot array; } and target_bus_slot | BusSlotEmpty | BusSlotDisk of source_disk | BusSlotRemovable of source_removable let string_of_target_bus_slots bus_name slots let slots Array.mapi ( fun slot_nr slot -> sprintf "%s slot %d:\n" bus_name slot_nr ^ (match slot with | BusSlotEmpty -> "\t(slot empty)\n" | BusSlotDisk d -> string_of_source_disk d | BusSlotRemovable r -> string_of_source_removable r ^ "\n" ) ) slots in String.concat "" (Array.to_list slots) -- 2.19.1.3.g30247aa5d201
Laszlo Ersek
2022-Apr-20 16:23 UTC
[Libguestfs] [v2v PATCH 2/9] create_libvirt_xml: simplify match on (s_cpu_vendor, s_cpu_model)
Squash the patterns | None, None -> () | Some _, None -> () into the identical | _, None -> () We preserve the behavior added by commit 2a576b7cc5c3 ("v2v: -o libvirt: Don't write only <vendor> without <model> (RHBZ#1591789).", 2018-06-21); the change only simplifies the code. Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2076013 Signed-off-by: Laszlo Ersek <lersek at redhat.com> --- output/create_libvirt_xml.ml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/output/create_libvirt_xml.ml b/output/create_libvirt_xml.ml index 36173e58cd6c..4e5bbceffabd 100644 --- a/output/create_libvirt_xml.ml +++ b/output/create_libvirt_xml.ml @@ -162,55 +162,57 @@ let create_libvirt_xml ?pool source inspect (match get_osinfo_id inspect with | None -> () | Some osinfo_id -> List.push_back_list body [ e "metadata" [] [ e "libosinfo:libosinfo" ["xmlns:libosinfo", "http://libosinfo.org/xmlns/libvirt/domain/1.0"] [ e "libosinfo:os" ["id", osinfo_id] []; ]; ]; ]; ); let memory_k = source.s_memory /^ 1024L in List.push_back_list body [ e "memory" ["unit", "KiB"] [PCData (Int64.to_string memory_k)]; e "currentMemory" ["unit", "KiB"] [PCData (Int64.to_string memory_k)]; e "vcpu" [] [PCData (string_of_int source.s_vcpu)] ]; if source.s_cpu_vendor <> None || source.s_cpu_model <> None || source.s_cpu_topology <> None then ( let cpu = ref [] in (match source.s_cpu_vendor, source.s_cpu_model with - | None, None - (* Avoid libvirt error: "CPU vendor specified without CPU model" *) - | Some _, None -> () + | _, None -> + (* This also avoids the libvirt error: + * "CPU vendor specified without CPU model". + *) + () | None, Some model -> List.push_back cpu (e "model" ["fallback", "allow"] [PCData model]) | Some vendor, Some model -> List.push_back_list cpu [ e "vendor" [] [PCData vendor]; e "model" ["fallback", "allow"] [PCData model] ] ); (match source.s_cpu_topology with | None -> () | Some { s_cpu_sockets; s_cpu_cores; s_cpu_threads } -> let topology_attrs = [ "sockets", string_of_int s_cpu_sockets; "cores", string_of_int s_cpu_cores; "threads", string_of_int s_cpu_threads; ] in List.push_back cpu (e "topology" topology_attrs []) ); List.push_back_list body [ e "cpu" [ "match", "minimum" ] !cpu ] ); let uefi_firmware match target_firmware with | TargetBIOS -> None | TargetUEFI -> Some (find_uefi_firmware guestcaps.gcaps_arch) in -- 2.19.1.3.g30247aa5d201
Laszlo Ersek
2022-Apr-20 16:23 UTC
[Libguestfs] [v2v PATCH 3/9] create_libvirt_xml: normalize match on (s_cpu_vendor, s_cpu_model)
In the match on (s_cpu_vendor, s_cpu_model), the following expression is duplicated: e "model" ["fallback", "allow"] [PCData model] For eliminating this, normalize the match: match each component in separation, in the proper order, creating a tree-like match rather than a table-like one. This is done in preparation for a subsequent patch, which would otherwise duplicate even more code. We preserve the behavior added by commit 2a576b7cc5c3 ("v2v: -o libvirt: Don't write only <vendor> without <model> (RHBZ#1591789).", 2018-06-21); the change only simplifies the code. With the elimination of the table-like pattern matching, we need not specifically mention the "CPU vendor specified without CPU model" libvirt error. Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2076013 Signed-off-by: Laszlo Ersek <lersek at redhat.com> --- output/create_libvirt_xml.ml | 22 ++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/output/create_libvirt_xml.ml b/output/create_libvirt_xml.ml index 4e5bbceffabd..d2ca894d60bb 100644 --- a/output/create_libvirt_xml.ml +++ b/output/create_libvirt_xml.ml @@ -161,65 +161,61 @@ let create_libvirt_xml ?pool source inspect ); (match get_osinfo_id inspect with | None -> () | Some osinfo_id -> List.push_back_list body [ e "metadata" [] [ e "libosinfo:libosinfo" ["xmlns:libosinfo", "http://libosinfo.org/xmlns/libvirt/domain/1.0"] [ e "libosinfo:os" ["id", osinfo_id] []; ]; ]; ]; ); let memory_k = source.s_memory /^ 1024L in List.push_back_list body [ e "memory" ["unit", "KiB"] [PCData (Int64.to_string memory_k)]; e "currentMemory" ["unit", "KiB"] [PCData (Int64.to_string memory_k)]; e "vcpu" [] [PCData (string_of_int source.s_vcpu)] ]; if source.s_cpu_vendor <> None || source.s_cpu_model <> None || source.s_cpu_topology <> None then ( let cpu = ref [] in - (match source.s_cpu_vendor, source.s_cpu_model with - | _, None -> - (* This also avoids the libvirt error: - * "CPU vendor specified without CPU model". - *) - () - | None, Some model -> - List.push_back cpu (e "model" ["fallback", "allow"] [PCData model]) - | Some vendor, Some model -> - List.push_back_list cpu [ - e "vendor" [] [PCData vendor]; - e "model" ["fallback", "allow"] [PCData model] - ] + (match source.s_cpu_model with + | None -> () + | Some model -> + (match source.s_cpu_vendor with + | None -> () + | Some vendor -> + List.push_back cpu (e "vendor" [] [PCData vendor]) + ); + List.push_back cpu (e "model" ["fallback", "allow"] [PCData model]) ); (match source.s_cpu_topology with | None -> () | Some { s_cpu_sockets; s_cpu_cores; s_cpu_threads } -> let topology_attrs = [ "sockets", string_of_int s_cpu_sockets; "cores", string_of_int s_cpu_cores; "threads", string_of_int s_cpu_threads; ] in List.push_back cpu (e "topology" topology_attrs []) ); List.push_back_list body [ e "cpu" [ "match", "minimum" ] !cpu ] ); let uefi_firmware match target_firmware with | TargetBIOS -> None | TargetUEFI -> Some (find_uefi_firmware guestcaps.gcaps_arch) in let machine, secure_boot_required match guestcaps.gcaps_machine, uefi_firmware with | _, Some { Uefi.flags = flags } when List.mem Uefi.UEFI_FLAG_SECURE_BOOT_REQUIRED flags -> (* Force machine type to Q35 because PC does not support * secure boot. We must remove this when we get the * correct machine type from libosinfo in future. XXX -- 2.19.1.3.g30247aa5d201
Laszlo Ersek
2022-Apr-20 16:23 UTC
[Libguestfs] [v2v PATCH 4/9] create_libvirt_xml: eliminate childless <cpu match="minimum"/> element
Commit 2a576b7cc5c3 ("v2v: -o libvirt: Don't write only <vendor> without <model> (RHBZ#1591789).", 2018-06-21) introduced a path in the code where we create a childless <cpu match="minimum"/> element. Namely, after said commit, in case source.s_cpu_vendor <> None && source.s_cpu_model = None && source.s_cpu_topology = None we no longer trigger a libvirt error; however, we do create the element <cpu match="minimum"/> without any children. Surprisingly, libvirt doesn't complain; it silently ignores and eliminates this element from the domain XML. Remove this code path by restricting the outer condition, for creating the <cpu> element, to: source.s_cpu_model <> None || source.s_cpu_topology <> None This reflects that "s_cpu_vendor" only plays a role if "s_cpu_model" is specified -- and the latter guarantees in itself that the <cpu> element will be generated with at least one child element. Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2076013 Signed-off-by: Laszlo Ersek <lersek at redhat.com> --- output/create_libvirt_xml.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/output/create_libvirt_xml.ml b/output/create_libvirt_xml.ml index d2ca894d60bb..4a71f8bb27f5 100644 --- a/output/create_libvirt_xml.ml +++ b/output/create_libvirt_xml.ml @@ -157,53 +157,53 @@ let create_libvirt_xml ?pool source inspect (match source.s_genid with | None -> () | Some genid -> List.push_back body (e "genid" [] [PCData genid]) ); (match get_osinfo_id inspect with | None -> () | Some osinfo_id -> List.push_back_list body [ e "metadata" [] [ e "libosinfo:libosinfo" ["xmlns:libosinfo", "http://libosinfo.org/xmlns/libvirt/domain/1.0"] [ e "libosinfo:os" ["id", osinfo_id] []; ]; ]; ]; ); let memory_k = source.s_memory /^ 1024L in List.push_back_list body [ e "memory" ["unit", "KiB"] [PCData (Int64.to_string memory_k)]; e "currentMemory" ["unit", "KiB"] [PCData (Int64.to_string memory_k)]; e "vcpu" [] [PCData (string_of_int source.s_vcpu)] ]; - if source.s_cpu_vendor <> None || source.s_cpu_model <> None || + if source.s_cpu_model <> None || source.s_cpu_topology <> None then ( let cpu = ref [] in (match source.s_cpu_model with | None -> () | Some model -> (match source.s_cpu_vendor with | None -> () | Some vendor -> List.push_back cpu (e "vendor" [] [PCData vendor]) ); List.push_back cpu (e "model" ["fallback", "allow"] [PCData model]) ); (match source.s_cpu_topology with | None -> () | Some { s_cpu_sockets; s_cpu_cores; s_cpu_threads } -> let topology_attrs = [ "sockets", string_of_int s_cpu_sockets; "cores", string_of_int s_cpu_cores; "threads", string_of_int s_cpu_threads; ] in List.push_back cpu (e "topology" topology_attrs []) ); List.push_back_list body [ e "cpu" [ "match", "minimum" ] !cpu ] ); -- 2.19.1.3.g30247aa5d201
Laszlo Ersek
2022-Apr-20 16:23 UTC
[Libguestfs] [v2v PATCH 5/9] create_libvirt_xml: restrict 'match="minimum"' <cpu> attribute production
The 'match="minimum"' attribute of the <cpu> element only makes sense if the CPU model is specified. If the CPU model is not specified by "s_cpu_model", i.e., we produce the <cpu> element only due to "s_cpu_topology", then we need no attributes for <cpu> at all. Restrict 'match="minimum"' to when "s_cpu_model" is specified. Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2076013 Signed-off-by: Laszlo Ersek <lersek at redhat.com> --- output/create_libvirt_xml.ml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/output/create_libvirt_xml.ml b/output/create_libvirt_xml.ml index 4a71f8bb27f5..1e0242a768a0 100644 --- a/output/create_libvirt_xml.ml +++ b/output/create_libvirt_xml.ml @@ -159,76 +159,78 @@ let create_libvirt_xml ?pool source inspect | None -> () | Some genid -> List.push_back body (e "genid" [] [PCData genid]) ); (match get_osinfo_id inspect with | None -> () | Some osinfo_id -> List.push_back_list body [ e "metadata" [] [ e "libosinfo:libosinfo" ["xmlns:libosinfo", "http://libosinfo.org/xmlns/libvirt/domain/1.0"] [ e "libosinfo:os" ["id", osinfo_id] []; ]; ]; ]; ); let memory_k = source.s_memory /^ 1024L in List.push_back_list body [ e "memory" ["unit", "KiB"] [PCData (Int64.to_string memory_k)]; e "currentMemory" ["unit", "KiB"] [PCData (Int64.to_string memory_k)]; e "vcpu" [] [PCData (string_of_int source.s_vcpu)] ]; if source.s_cpu_model <> None || source.s_cpu_topology <> None then ( - let cpu = ref [] in + let cpu_attrs = ref [] + and cpu = ref [] in (match source.s_cpu_model with | None -> () | Some model -> + List.push_back cpu_attrs ("match", "minimum"); (match source.s_cpu_vendor with | None -> () | Some vendor -> List.push_back cpu (e "vendor" [] [PCData vendor]) ); List.push_back cpu (e "model" ["fallback", "allow"] [PCData model]) ); (match source.s_cpu_topology with | None -> () | Some { s_cpu_sockets; s_cpu_cores; s_cpu_threads } -> let topology_attrs = [ "sockets", string_of_int s_cpu_sockets; "cores", string_of_int s_cpu_cores; "threads", string_of_int s_cpu_threads; ] in List.push_back cpu (e "topology" topology_attrs []) ); - List.push_back_list body [ e "cpu" [ "match", "minimum" ] !cpu ] + List.push_back_list body [ e "cpu" !cpu_attrs !cpu ] ); let uefi_firmware match target_firmware with | TargetBIOS -> None | TargetUEFI -> Some (find_uefi_firmware guestcaps.gcaps_arch) in let machine, secure_boot_required match guestcaps.gcaps_machine, uefi_firmware with | _, Some { Uefi.flags = flags } when List.mem Uefi.UEFI_FLAG_SECURE_BOOT_REQUIRED flags -> (* Force machine type to Q35 because PC does not support * secure boot. We must remove this when we get the * correct machine type from libosinfo in future. XXX *) Q35, true | machine, _ -> machine, false in let smm = secure_boot_required in (* We have the machine features of the guest when it was on the * source hypervisor (source.s_features). We have the acpi flag * which tells us whether acpi is required by this guest * (guestcaps.gcaps_acpi). And we have the set of hypervisor * features supported by the target (target_features). Combine all * this into a final list of features. *) -- 2.19.1.3.g30247aa5d201
Laszlo Ersek
2022-Apr-20 16:23 UTC
[Libguestfs] [v2v PATCH 6/9] create_libvirt_xml: honor "gcaps_default_cpu"
If "s_cpu_model" is None and "gcaps_default_cpu" is "false", generate the <cpu mode="host-passthrough"/> element for libvirt. This element produces an (almost) exact copy of the host (i.e., physical) CPU for the guest, which is the best choice for guest OSes that cannot run on QEMU's default VCPU type -- considering that domains converted by virt-v2v are not expected to be migrateable without further tweaks by an administrator. Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2076013 Signed-off-by: Laszlo Ersek <lersek at redhat.com> --- output/create_libvirt_xml.ml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/output/create_libvirt_xml.ml b/output/create_libvirt_xml.ml index 1e0242a768a0..68d0a90918f3 100644 --- a/output/create_libvirt_xml.ml +++ b/output/create_libvirt_xml.ml @@ -158,58 +158,61 @@ let create_libvirt_xml ?pool source inspect (match source.s_genid with | None -> () | Some genid -> List.push_back body (e "genid" [] [PCData genid]) ); (match get_osinfo_id inspect with | None -> () | Some osinfo_id -> List.push_back_list body [ e "metadata" [] [ e "libosinfo:libosinfo" ["xmlns:libosinfo", "http://libosinfo.org/xmlns/libvirt/domain/1.0"] [ e "libosinfo:os" ["id", osinfo_id] []; ]; ]; ]; ); let memory_k = source.s_memory /^ 1024L in List.push_back_list body [ e "memory" ["unit", "KiB"] [PCData (Int64.to_string memory_k)]; e "currentMemory" ["unit", "KiB"] [PCData (Int64.to_string memory_k)]; e "vcpu" [] [PCData (string_of_int source.s_vcpu)] ]; if source.s_cpu_model <> None || + not guestcaps.gcaps_default_cpu || source.s_cpu_topology <> None then ( let cpu_attrs = ref [] and cpu = ref [] in (match source.s_cpu_model with - | None -> () + | None -> + if not guestcaps.gcaps_default_cpu then + List.push_back cpu_attrs ("mode", "host-passthrough"); | Some model -> List.push_back cpu_attrs ("match", "minimum"); (match source.s_cpu_vendor with | None -> () | Some vendor -> List.push_back cpu (e "vendor" [] [PCData vendor]) ); List.push_back cpu (e "model" ["fallback", "allow"] [PCData model]) ); (match source.s_cpu_topology with | None -> () | Some { s_cpu_sockets; s_cpu_cores; s_cpu_threads } -> let topology_attrs = [ "sockets", string_of_int s_cpu_sockets; "cores", string_of_int s_cpu_cores; "threads", string_of_int s_cpu_threads; ] in List.push_back cpu (e "topology" topology_attrs []) ); List.push_back_list body [ e "cpu" !cpu_attrs !cpu ] ); let uefi_firmware match target_firmware with | TargetBIOS -> None -- 2.19.1.3.g30247aa5d201
Laszlo Ersek
2022-Apr-20 16:23 UTC
[Libguestfs] [v2v PATCH 7/9] output_qemu: reflect source VCPU model to the QEMU command line
At the moment, QEMU is always started with the default "qemu64" VCPU model, even if the source hypervisor sets a particular VCPU model. "qemu64" is not suitable for some guest OSes. Honor "source.s_cpu_model" via the "-cpu" option, if the source specifies the VCPU model. The logic is basically copied from "lib/create_ovf.ml". Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2076013 Signed-off-by: Laszlo Ersek <lersek at redhat.com> --- output/output_qemu.ml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/output/output_qemu.ml b/output/output_qemu.ml index f5e43705d101..385065f0579d 100644 --- a/output/output_qemu.ml +++ b/output/output_qemu.ml @@ -146,52 +146,58 @@ module QEMU = struct flag "-no-user-config"; flag "-nodefaults"; arg "-name" output_name; (match source.s_genid with | None -> () | Some genid -> arg_list "-device" ["vmgenid"; sprintf "guid=%s" genid; "id=vmgenid0"] ); arg_list "-machine" (machine_str :: (if smm then ["smm=on"] else []) @ ["accel=kvm:tcg"]); (match uefi_firmware with | None -> () | Some { Uefi.code } -> if secure_boot_required then arg_list "-global" ["driver=cfi.pflash01"; "property=secure"; "value=on"]; arg_list "-drive" ["if=pflash"; "format=raw"; "file=" ^ code; "readonly"]; arg_noquote "-drive" "if=pflash,format=raw,file=\"$uefi_vars\""; ); arg "-m" (Int64.to_string (source.s_memory /^ 1024L /^ 1024L)); + + (match source.s_cpu_model with + | None -> () + | Some model -> arg "-cpu" model + ); + if source.s_vcpu > 1 then ( (match source.s_cpu_topology with | None -> arg "-smp" (string_of_int source.s_vcpu) | Some { s_cpu_sockets; s_cpu_cores; s_cpu_threads } -> let args = [ sprintf "cpus=%d" source.s_vcpu; sprintf "sockets=%d" s_cpu_sockets; sprintf "cores=%d" s_cpu_cores; sprintf "threads=%d" s_cpu_threads; ] in arg_list "-smp" args ); ); (* For IDE disks, IDE CD-ROMs, SCSI disks, SCSI CD-ROMs, and floppies, we * need host-bus adapters (HBAs) between these devices and the PCI(e) root * bus. Some machine types create these HBAs automatically (despite * "-no-user-config -nodefaults"), some don't... *) let disk_cdrom_filter function | BusSlotDisk _ | BusSlotRemovable { s_removable_type = CDROM } -> true | _ -> false and floppy_filter -- 2.19.1.3.g30247aa5d201
Laszlo Ersek
2022-Apr-20 16:23 UTC
[Libguestfs] [v2v PATCH 8/9] output_qemu: honor "gcaps_default_cpu"
If "s_cpu_model" is None and "gcaps_default_cpu" is "false", generate the "-cpu host" option for QEMU. "-cpu host" produces an (almost) exact copy of the host (i.e., physical) CPU for the guest, which is the best choice for guest OSes that cannot run on QEMU's default VCPU type -- considering that domains converted by virt-v2v are not expected to be migrateable without further tweaks by an administrator. Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2076013 Signed-off-by: Laszlo Ersek <lersek at redhat.com> --- output/output_qemu.ml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/output/output_qemu.ml b/output/output_qemu.ml index 385065f0579d..29adcba901a3 100644 --- a/output/output_qemu.ml +++ b/output/output_qemu.ml @@ -147,55 +147,56 @@ module QEMU = struct flag "-no-user-config"; flag "-nodefaults"; arg "-name" output_name; (match source.s_genid with | None -> () | Some genid -> arg_list "-device" ["vmgenid"; sprintf "guid=%s" genid; "id=vmgenid0"] ); arg_list "-machine" (machine_str :: (if smm then ["smm=on"] else []) @ ["accel=kvm:tcg"]); (match uefi_firmware with | None -> () | Some { Uefi.code } -> if secure_boot_required then arg_list "-global" ["driver=cfi.pflash01"; "property=secure"; "value=on"]; arg_list "-drive" ["if=pflash"; "format=raw"; "file=" ^ code; "readonly"]; arg_noquote "-drive" "if=pflash,format=raw,file=\"$uefi_vars\""; ); arg "-m" (Int64.to_string (source.s_memory /^ 1024L /^ 1024L)); - (match source.s_cpu_model with - | None -> () - | Some model -> arg "-cpu" model + (match source.s_cpu_model, guestcaps.gcaps_default_cpu with + | None, true -> () + | None, false -> arg "-cpu" "host" + | Some model, _ -> arg "-cpu" model ); if source.s_vcpu > 1 then ( (match source.s_cpu_topology with | None -> arg "-smp" (string_of_int source.s_vcpu) | Some { s_cpu_sockets; s_cpu_cores; s_cpu_threads } -> let args = [ sprintf "cpus=%d" source.s_vcpu; sprintf "sockets=%d" s_cpu_sockets; sprintf "cores=%d" s_cpu_cores; sprintf "threads=%d" s_cpu_threads; ] in arg_list "-smp" args ); ); (* For IDE disks, IDE CD-ROMs, SCSI disks, SCSI CD-ROMs, and floppies, we * need host-bus adapters (HBAs) between these devices and the PCI(e) root * bus. Some machine types create these HBAs automatically (despite * "-no-user-config -nodefaults"), some don't... *) let disk_cdrom_filter function | BusSlotDisk _ | BusSlotRemovable { s_removable_type = CDROM } -> true -- 2.19.1.3.g30247aa5d201
Laszlo Ersek
2022-Apr-20 16:23 UTC
[Libguestfs] [v2v PATCH 9/9] convert_linux: set "gcaps_default_cpu = false" for x86_64 RHEL-9.0+ guests
RHEL >= 9.0 on x86_64 requires the processor to support the "x86-64-v2" microarchitecture level, which the default QEMU VCPU model does not satisfy. Activate host-passthrough for such guest OSes. (Libosinfo does not track processor support, so in the future we may have to extend the expression added in this patch to other distros.) Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2076013 Signed-off-by: Laszlo Ersek <lersek at redhat.com> --- convert/convert_linux.ml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/convert/convert_linux.ml b/convert/convert_linux.ml index 93b3922cac60..5660494973b1 100644 --- a/convert/convert_linux.ml +++ b/convert/convert_linux.ml @@ -139,65 +139,73 @@ let convert (g : G.guestfs) source inspect keep_serial_console _ let { Libosinfo_utils.q35; vio10 } Libosinfo_utils.os_support_of_osinfo_device_list devices in (if q35 then Q35 else I440FX), vio10 with | Not_found -> (* Pivot on the year 2007. Any Linux distro from earlier than 2007 * should use i440fx, anything 2007 or newer should use q35. *) (match inspect.i_distro, inspect.i_major_version with | "fedora", _ -> Q35 | ("rhel"|"centos"|"scientificlinux"|"redhat-based"|"oraclelinux"), major -> if major <= 4 then I440FX else Q35 | ("sles"|"suse-based"|"opensuse"), major -> if major < 10 then I440FX else Q35 | ("debian"|"ubuntu"|"linuxmint"|"kalilinux"), major -> if major < 4 then I440FX else Q35 (* reasonable default for all modern Linux kernels *) | _, _ -> Q35 ), true ) | _ -> Virt, true in + (* RHEL >= 9.0 on x86_64 requires the processor to support the "x86-64-v2" + * microarchitecture level, which the default QEMU VCPU model does not + * satisfy. Refer to RHBZ#2076013. + *) + let default_cpu_suffices = inspect.i_distro <> "rhel" || + inspect.i_arch <> "x86_64" || + inspect.i_major_version < 9 in + (* Return guest capabilities from the convert () function. *) let guestcaps = { gcaps_block_bus = block_type; gcaps_net_bus = net_type; gcaps_virtio_rng = kernel.ki_supports_virtio_rng; gcaps_virtio_balloon = kernel.ki_supports_virtio_balloon; gcaps_isa_pvpanic = kernel.ki_supports_isa_pvpanic; gcaps_virtio_socket = kernel.ki_supports_virtio_socket; gcaps_machine = machine; gcaps_arch = Utils.kvm_arch inspect.i_arch; gcaps_acpi = acpi; gcaps_virtio_1_0 = virtio_1_0; - gcaps_default_cpu = true; + gcaps_default_cpu = default_cpu_suffices; } in guestcaps and augeas_grub_configuration () if bootloader#set_augeas_configuration () then Linux.augeas_reload g and unconfigure_xen () (* Remove kmod-xenpv-* (RHEL 3). *) let xenmods List.filter_map ( fun { G.app2_name = name } -> if name = "kmod-xenpv" || String.is_prefix name "kmod-xenpv-" then Some name else None ) inspect.i_apps in Linux.remove g inspect xenmods; (* Undo related nastiness if kmod-xenpv was installed. *) if xenmods <> [] then ( (* kmod-xenpv modules may have been manually copied to other kernels. * Hunt them down and destroy them. *) let dirs = g#find "/lib/modules" in -- 2.19.1.3.g30247aa5d201
Laszlo Ersek
2022-Apr-20 16:37 UTC
[Libguestfs] [v2v PATCH 0/9] ensure x86-64-v2 uarch level for RHEL-9.0+ guests
On 04/20/22 18:23, Laszlo Ersek wrote:> Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2076013 > > We don't specify a VCPU model in the destination domain description if > the source description doesn't specify one. This is a problem for x86-64 > RHEL-9.0+ guests, when using the QEMU or libvirt output modules. Those > outputs default to the "qemu64" VCPU model in the absence of an explicit > VCPU model specification, and "qemu64" does not satisfy the "x86-64-v2" > microarchitecture level, which is required by RHEL-9.0+ guests. As a > result, these guests crash during boot, after conversion. > > The series recognizes guests that are unable to boot on the default QEMU > VCPU model, and specifies host CPU passthrough for them in the QEMU and > libvirt output modules. > > Testing: <https://bugzilla.redhat.com/show_bug.cgi?id=2076013#c18>. > > Thanks, > Laszlo > > Laszlo Ersek (9): > types: introduce the "gcaps_default_cpu" field > create_libvirt_xml: simplify match on (s_cpu_vendor, s_cpu_model) > create_libvirt_xml: normalize match on (s_cpu_vendor, s_cpu_model) > create_libvirt_xml: eliminate childless <cpu match="minimum"/> element > create_libvirt_xml: restrict 'match="minimum"' <cpu> attribute > production > create_libvirt_xml: honor "gcaps_default_cpu" > output_qemu: reflect source VCPU model to the QEMU command line > output_qemu: honor "gcaps_default_cpu" > convert_linux: set "gcaps_default_cpu = false" for x86_64 RHEL-9.0+ > guests > > convert/convert_linux.ml | 9 ++++++ > convert/convert_windows.ml | 1 + > lib/types.ml | 3 ++ > lib/types.mli | 14 +++++++++ > output/create_libvirt_xml.ml | 31 +++++++++++--------- > output/output_qemu.ml | 7 +++++ > 6 files changed, 51 insertions(+), 14 deletions(-) > > > base-commit: 8643970f9791b1c90dfd6a2dd1abfc9afef1fb52 >The patches are also available here: Repo: https://github.com/lersek/virt-v2v.git Branch: vcpu-model-rhbz-2076013 Thanks Laszlo