Richard W.M. Jones
2018-Jul-04 12:04 UTC
[Libguestfs] [PATCH 0/3] v2v: Implement MAC address to network/bridge mapping.
Deep in the discussion of this bug, unfortunately mostly in private comments: https://bugzilla.redhat.com/show_bug.cgi?id=1594515 we decided it'd be more flexible for RHV if we had a way to map individual NICs to target networks and bridges. This can be done by adding a new --mac option so you can specify the exact mapping you need: $ virt-v2v [...] \ --mac 52:54:00:d0:cf:0e:network:mgmt \ --mac 52:54:00:d0:cf:0f:network:clientdata Rich.
Richard W.M. Jones
2018-Jul-04 12:04 UTC
[Libguestfs] [PATCH 1/3] v2v: Make Cmdline.NetworkMap into an abstract data type.
No change, just code refactoring. --- v2v/Makefile.am | 2 ++ v2v/cmdline.ml | 47 +++++++++++---------------- v2v/cmdline.mli | 16 +-------- v2v/networks.ml | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ v2v/networks.mli | 48 +++++++++++++++++++++++++++ v2v/v2v.ml | 19 +---------- 6 files changed, 155 insertions(+), 61 deletions(-) diff --git a/v2v/Makefile.am b/v2v/Makefile.am index 335e1a59d..adf48eca7 100644 --- a/v2v/Makefile.am +++ b/v2v/Makefile.am @@ -65,6 +65,7 @@ SOURCES_MLI = \ linux_kernels.mli \ modules_list.mli \ name_from_disk.mli \ + networks.mli \ output_glance.mli \ output_libvirt.mli \ output_local.mli \ @@ -136,6 +137,7 @@ SOURCES_ML = \ output_vdsm.ml \ inspect_source.ml \ target_bus_assignment.ml \ + networks.ml \ cmdline.ml \ v2v.ml diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml index eaa11dba0..218200a12 100644 --- a/v2v/cmdline.ml +++ b/v2v/cmdline.ml @@ -28,18 +28,12 @@ open Getopt.OptionName open Types open Utils -module NetTypeAndName = struct - type t = Types.vnet_type * string option - let compare = Pervasives.compare -end -module NetworkMap = Map.Make (NetTypeAndName) - type cmdline = { compressed : bool; debug_overlays : bool; do_copy : bool; in_place : bool; - network_map : string NetworkMap.t; + network_map : Networks.t; output_alloc : output_allocation; output_format : string option; output_name : string option; @@ -99,26 +93,24 @@ let parse_cmdline () set_input_option_compat k v in - let network_map = ref NetworkMap.empty in - let add_network, add_bridge - let add flag name t str - match String.split ":" str with - | "", "" -> - error (f_"invalid %s parameter") flag - | out, "" | "", out -> - let key = t, None in - if NetworkMap.mem key !network_map then - error (f_"duplicate %s parameter. Only one default mapping is allowed.") flag; - network_map := NetworkMap.add key out !network_map - | in_, out -> - let key = t, Some in_ in - if NetworkMap.mem key !network_map then - error (f_"duplicate %s parameter. Duplicate mappings specified for %s ?%s?.") flag name in_; - network_map := NetworkMap.add key out !network_map - in - let add_network str = add "-n/--network" (s_"network") Network str - and add_bridge str = add "-b/--bridge" (s_"bridge") Bridge str in - add_network, add_bridge + let network_map = Networks.create () in + let add_network str + match String.split ":" str with + | "", "" -> + error (f_"invalid -n/--network parameter") + | out, "" | "", out -> + Networks.add_default_network network_map out + | in_, out -> + Networks.add_network network_map in_ out + in + let add_bridge str + match String.split ":" str with + | "", "" -> + error (f_"invalid -b/--bridge parameter") + | out, "" | "", out -> + Networks.add_default_bridge network_map out + | in_, out -> + Networks.add_bridge network_map in_ out in let no_trim_warning _ @@ -316,7 +308,6 @@ read the man page virt-v2v(1). error (f_"unknown input transport ?-it %s?") transport in let in_place = !in_place in let machine_readable = !machine_readable in - let network_map = !network_map in let output_alloc match !output_alloc with | `Not_set | `Sparse -> Sparse diff --git a/v2v/cmdline.mli b/v2v/cmdline.mli index 0e1d54f40..87d36bf5d 100644 --- a/v2v/cmdline.mli +++ b/v2v/cmdline.mli @@ -18,26 +18,12 @@ (** Command line argument parsing. *) -module NetTypeAndName : sig - type t = Types.vnet_type * string option - (** To find the mapping for a specific named network or bridge, use - the key [(Network|Bridge, Some name)]. To find the default mapping - use [(Network|Bridge, None)]. *) - val compare : t -> t -> int -end -module NetworkMap : sig - type key = NetTypeAndName.t - type 'a t = 'a Map.Make(NetTypeAndName).t - val mem : key -> 'a t -> bool - val find : key -> 'a t -> 'a -end - type cmdline = { compressed : bool; debug_overlays : bool; do_copy : bool; in_place : bool; - network_map : string NetworkMap.t; + network_map : Networks.t; output_alloc : Types.output_allocation; output_format : string option; output_name : string option; diff --git a/v2v/networks.ml b/v2v/networks.ml new file mode 100644 index 000000000..f6da77704 --- /dev/null +++ b/v2v/networks.ml @@ -0,0 +1,84 @@ +(* virt-v2v + * Copyright (C) 2009-2018 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +(* Network, bridge mapping. *) + +open Tools_utils +open Common_gettext.Gettext + +open Types + +type t = { + (* For networks we use this to map a named network, or use the + * default network if no named network exists. + *) + mutable network_map : string StringMap.t; + mutable default_network : string option; + + (* Same as above but for bridges. *) + mutable bridge_map : string StringMap.t; + mutable default_bridge : string option; +} + +let map t nic + match nic.s_vnet_type with + | Network -> + (try + let vnet = StringMap.find nic.s_vnet t.network_map in + { nic with s_vnet = vnet } + with Not_found -> + match t.default_network with + | None -> nic (* no mapping done *) + | Some default_network -> { nic with s_vnet = default_network } + ) + | Bridge -> + (try + let vnet = StringMap.find nic.s_vnet t.bridge_map in + { nic with s_vnet = vnet } + with Not_found -> + match t.default_bridge with + | None -> nic (* no mapping done *) + | Some default_bridge -> { nic with s_vnet = default_bridge } + ) + +let create () = { + network_map = StringMap.empty; + default_network = None; + bridge_map = StringMap.empty; + default_bridge = None +} + +let add_network t i o + if StringMap.mem i t.network_map then + error (f_"duplicate -n/--network parameter. Duplicate mappings specified for network %s.") i; + t.network_map <- StringMap.add i o t.network_map + +let add_default_network t o + if t.default_network <> None then + error (f_"duplicate -n/--network parameter. Only one default mapping is allowed."); + t.default_network <- Some o + +let add_bridge t i o + if StringMap.mem i t.bridge_map then + error (f_"duplicate -b/--bridge parameter. Duplicate mappings specified for bridge %s.") i; + t.bridge_map <- StringMap.add i o t.bridge_map + +let add_default_bridge t o + if t.default_bridge <> None then + error (f_"duplicate -b/--bridge parameter. Only one default mapping is allowed."); + t.default_bridge <- Some o diff --git a/v2v/networks.mli b/v2v/networks.mli new file mode 100644 index 000000000..5090080ac --- /dev/null +++ b/v2v/networks.mli @@ -0,0 +1,48 @@ +(* virt-v2v + * Copyright (C) 2009-2018 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +(** Network, bridge mapping. *) + +type t (** The map. *) + +val create : unit -> t +(** Create an empty mapping. *) + +val add_network : t -> string -> string -> unit +(** Add a network mapping from C<in> to C<out>. + + Equivalent to the [--network in:out] option. *) + +val add_default_network : t -> string -> unit +(** Add a default network mapping. + + Equivalent to the [--network out] option. *) + +val add_bridge : t -> string -> string -> unit +(** Add a bridge mapping from C<in> to C<out>. + + Equivalent to the [--bridge in:out] option. *) + +val add_default_bridge : t -> string -> unit +(** Add a default bridge mapping. + + Equivalent to the [--bridge out] option. *) + +val map : t -> Types.source_nic -> Types.source_nic +(** Apply the mapping to the source NIC, returning the updated + NIC with possibly modified [s_vnet] field. *) diff --git a/v2v/v2v.ml b/v2v/v2v.ml index 75f60c9c3..21eba4d55 100644 --- a/v2v/v2v.ml +++ b/v2v/v2v.ml @@ -256,24 +256,7 @@ and set_source_name cmdline source (* Map networks and bridges. *) and set_source_networks_and_bridges cmdline source - let nics = List.map ( - fun ({ s_vnet_type = t; s_vnet = vnet } as nic) -> - try - (* Look for a --network or --bridge parameter which names this - * network/bridge (eg. --network in:out). - *) - let new_name = NetworkMap.find (t, Some vnet) cmdline.network_map in - { nic with s_vnet = new_name } - with Not_found -> - try - (* Not found, so look for a default mapping (eg. --network out). *) - let new_name = NetworkMap.find (t, None) cmdline.network_map in - { nic with s_vnet = new_name } - with Not_found -> - (* Not found, so return the original NIC unchanged. *) - nic - ) source.s_nics in - + let nics = List.map (Networks.map cmdline.network_map) source.s_nics in { source with s_nics = nics } and overlay_dir = (open_guestfs ())#get_cachedir () -- 2.17.1
Richard W.M. Jones
2018-Jul-04 12:04 UTC
[Libguestfs] [PATCH 2/3] v2v: Add s_mapping_explanation field to source NIC.
Instead of storing the original network name (s_vnet_orig) in the source NIC struct and then trying to guess what happened much later when we're writing target metadata, get our newly created Networks abstract data type to store the precise mapping explanation. --- v2v/create_libvirt_xml.ml | 10 +++--- v2v/create_ovf.ml | 10 +++--- v2v/input_disk.ml | 5 +-- v2v/input_vmx.ml | 5 +-- v2v/networks.ml | 32 ++++++++++++++++--- v2v/networks.mli | 5 ++- v2v/parse_libvirt_xml.ml | 4 +-- v2v/parse_ovf_from_ova.ml | 2 +- ...test-v2v-networks-and-bridges-expected.xml | 6 ++-- v2v/types.ml | 2 +- v2v/types.mli | 5 ++- 11 files changed, 58 insertions(+), 28 deletions(-) diff --git a/v2v/create_libvirt_xml.ml b/v2v/create_libvirt_xml.ml index 4b36ffb8e..f5603db99 100644 --- a/v2v/create_libvirt_xml.ml +++ b/v2v/create_libvirt_xml.ml @@ -250,7 +250,7 @@ let create_libvirt_xml ?pool source target_buses guestcaps | Virtio_net -> "virtio" | E1000 -> "e1000" | RTL8139 -> "rtl8139" in List.map ( fun { s_mac = mac; s_vnet_type = vnet_type; - s_vnet = vnet; s_vnet_orig = vnet_orig } -> + s_vnet = vnet; s_mapping_explanation = explanation } -> let vnet_type_str match vnet_type with | Bridge -> "bridge" | Network -> "network" in @@ -261,11 +261,9 @@ let create_libvirt_xml ?pool source target_buses guestcaps e "model" [ "type", net_model ] []; ] in let children - if vnet_orig <> vnet then - Comment (sprintf "%s mapped from \"%s\" to \"%s\"" - vnet_type_str vnet_orig vnet) :: children - else - children in + match explanation with + | Some explanation -> Comment explanation :: children + | None -> children in e "interface" [ "type", vnet_type_str ] children in (match mac with diff --git a/v2v/create_ovf.ml b/v2v/create_ovf.ml index 3b866754a..b2a240907 100644 --- a/v2v/create_ovf.ml +++ b/v2v/create_ovf.ml @@ -918,7 +918,7 @@ and add_networks nics guestcaps ovf_flavour ovf (* Iterate over the NICs, adding them to the OVF document. *) List.iteri ( fun i { s_mac = mac; s_vnet_type = vnet_type; - s_vnet = vnet; s_vnet_orig = vnet_orig } -> + s_vnet = vnet; s_mapping_explanation = explanation } -> let dev = sprintf "eth%d" i in let model @@ -931,10 +931,10 @@ and add_networks nics guestcaps ovf_flavour ovf bus dev; "1" *) in - if vnet_orig <> vnet then ( - let c - Comment (sprintf "mapped from \"%s\" to \"%s\"" vnet_orig vnet) in - append_child c network_section + (match explanation with + | None -> () + | Some explanation -> + append_child (Comment explanation) network_section ); let network = e "Network" ["ovf:name", vnet] [] in diff --git a/v2v/input_disk.ml b/v2v/input_disk.ml index 7ecd19fd3..624644532 100644 --- a/v2v/input_disk.ml +++ b/v2v/input_disk.ml @@ -72,8 +72,9 @@ class input_disk input_format disk = object let network = { s_mac = None; s_nic_model = None; - s_vnet = "default"; s_vnet_orig = "default"; - s_vnet_type = Network + s_vnet = "default"; + s_vnet_type = Network; + s_mapping_explanation = None } in let source = { diff --git a/v2v/input_vmx.ml b/v2v/input_vmx.ml index a0b3208f5..1a8015545 100644 --- a/v2v/input_vmx.ml +++ b/v2v/input_vmx.ml @@ -372,8 +372,9 @@ and find_nics vmx | Some _ | None -> Network in Some (port, { s_mac = mac; s_nic_model = model; - s_vnet = vnet; s_vnet_orig = vnet; - s_vnet_type = vnet_type }) + s_vnet = vnet; + s_vnet_type = vnet_type; + s_mapping_explanation = None }) | _ -> None ) nics in let nics = List.filter_map identity nics in diff --git a/v2v/networks.ml b/v2v/networks.ml index f6da77704..973e193b7 100644 --- a/v2v/networks.ml +++ b/v2v/networks.ml @@ -18,6 +18,8 @@ (* Network, bridge mapping. *) +open Printf + open Tools_utils open Common_gettext.Gettext @@ -40,20 +42,42 @@ let map t nic | Network -> (try let vnet = StringMap.find nic.s_vnet t.network_map in - { nic with s_vnet = vnet } + { nic with + s_vnet = vnet; + s_mapping_explanation + Some (sprintf "network mapped from %S to %S" + nic.s_vnet vnet) + } with Not_found -> match t.default_network with | None -> nic (* no mapping done *) - | Some default_network -> { nic with s_vnet = default_network } + | Some default_network -> + { nic with + s_vnet = default_network; + s_mapping_explanation + Some (sprintf "network mapped from %S to default %S" + nic.s_vnet default_network) + } ) | Bridge -> (try let vnet = StringMap.find nic.s_vnet t.bridge_map in - { nic with s_vnet = vnet } + { nic with + s_vnet = vnet; + s_mapping_explanation + Some (sprintf "bridge mapped from %S to %S" + nic.s_vnet vnet) + } with Not_found -> match t.default_bridge with | None -> nic (* no mapping done *) - | Some default_bridge -> { nic with s_vnet = default_bridge } + | Some default_bridge -> + { nic with + s_vnet = default_bridge; + s_mapping_explanation + Some (sprintf "bridge mapped from %S to default %S" + nic.s_vnet default_bridge) + } ) let create () = { diff --git a/v2v/networks.mli b/v2v/networks.mli index 5090080ac..af2e5a302 100644 --- a/v2v/networks.mli +++ b/v2v/networks.mli @@ -45,4 +45,7 @@ val add_default_bridge : t -> string -> unit val map : t -> Types.source_nic -> Types.source_nic (** Apply the mapping to the source NIC, returning the updated - NIC with possibly modified [s_vnet] field. *) + NIC with possibly modified [s_vnet] field. + + [s_mapping_explanation] is set in the output with an + informational message about what was done. *) diff --git a/v2v/parse_libvirt_xml.ml b/v2v/parse_libvirt_xml.ml index 03a201e77..cf6593043 100644 --- a/v2v/parse_libvirt_xml.ml +++ b/v2v/parse_libvirt_xml.ml @@ -445,8 +445,8 @@ let parse_libvirt_xml ?conn xml s_mac = mac; s_nic_model = model; s_vnet = vnet; - s_vnet_orig = vnet; - s_vnet_type = vnet_type + s_vnet_type = vnet_type; + s_mapping_explanation = None } in List.push_front nic nics in diff --git a/v2v/parse_ovf_from_ova.ml b/v2v/parse_ovf_from_ova.ml index 7d4f2f543..25b59db3f 100644 --- a/v2v/parse_ovf_from_ova.ml +++ b/v2v/parse_ovf_from_ova.ml @@ -238,8 +238,8 @@ and parse_nics xpathctx s_mac = mac; s_nic_model = nic_model; s_vnet = vnet; - s_vnet_orig = vnet; s_vnet_type = vnet_type; + s_mapping_explanation = None } in List.push_front nic nics done; diff --git a/v2v/test-v2v-networks-and-bridges-expected.xml b/v2v/test-v2v-networks-and-bridges-expected.xml index fef9979b0..7d24d990e 100644 --- a/v2v/test-v2v-networks-and-bridges-expected.xml +++ b/v2v/test-v2v-networks-and-bridges-expected.xml @@ -3,7 +3,7 @@ <source bridge='bridge1'/> </interface> <interface type='bridge'> - <!-- bridge mapped from "bob" to "bridge2" --> + <!-- bridge mapped from "bob" to default "bridge2" --> <source bridge='bridge2'/> <mac address='52:54:00:01:02:03'/> </interface> @@ -23,12 +23,12 @@ <mac address='52:54:00:01:02:06'/> </interface> <interface type='network'> - <!-- network mapped from "george" to "network4" --> + <!-- network mapped from "george" to default "network4" --> <source network='network4'/> <mac address='52:54:00:01:02:07'/> </interface> <interface type='network'> - <!-- network mapped from "ringo" to "network4" --> + <!-- network mapped from "ringo" to default "network4" --> <source network='network4'/> <mac address='52:54:00:01:02:08'/> </interface> diff --git a/v2v/types.ml b/v2v/types.ml index 5e4dc8dd4..118d0ccf3 100644 --- a/v2v/types.ml +++ b/v2v/types.ml @@ -71,8 +71,8 @@ and source_nic = { s_mac : string option; s_nic_model : s_nic_model option; s_vnet : string; - s_vnet_orig : string; s_vnet_type : vnet_type; + s_mapping_explanation : string option; } and s_nic_model = Source_other_nic of string | Source_rtl8139 | Source_e1000 | Source_virtio_net diff --git a/v2v/types.mli b/v2v/types.mli index 041548227..79c0c1021 100644 --- a/v2v/types.mli +++ b/v2v/types.mli @@ -130,8 +130,11 @@ and source_nic = { s_mac : string option; (** MAC address. *) s_nic_model : s_nic_model option; (** Network adapter model. *) s_vnet : string; (** Source network name. *) - s_vnet_orig : string; (** Original network (if we map it). *) s_vnet_type : vnet_type; (** Source network type. *) + s_mapping_explanation : string option; + (** If the NIC or network was mapped, this contains an English + explanation of the change which can be written to the target + hypervisor metadata for informational purposes. *) } (** Network adapter models. *) and s_nic_model = Source_other_nic of string | -- 2.17.1
Richard W.M. Jones
2018-Jul-04 12:04 UTC
[Libguestfs] [PATCH 3/3] v2v: Implement MAC address to network/bridge mapping.
This allows specific NICs (identified by their source MAC address) to be mapped to networks or bridges on the target. You can use the --mac parameter to select this mapping, eg: $ virt-v2v ... \ --mac 52:54:00:d0:cf:0e:network:mgmt \ --mac 52:54:00:d0:cf:0f:network:clientdata The old --network and --bridge mappings can also be used but --mac takes precedence. Note this does not adjust MAC addresses inside the guest which is a hard problem to solve. For this to work you must still carry over the MAC addresses from the source to target hypervisor as that is how most guests identify and associate functions with specific network interfaces. --- v2v/Makefile.am | 4 ++ v2v/cmdline.ml | 15 +++++ v2v/networks.ml | 113 ++++++++++++++++++++-------------- v2v/networks.mli | 13 +++- v2v/test-v2v-mac-expected.xml | 32 ++++++++++ v2v/test-v2v-mac.sh | 57 +++++++++++++++++ v2v/test-v2v-mac.xml | 81 ++++++++++++++++++++++++ v2v/types.ml | 6 +- v2v/types.mli | 1 + v2v/virt-v2v.pod | 34 ++++++++-- 10 files changed, 304 insertions(+), 52 deletions(-) diff --git a/v2v/Makefile.am b/v2v/Makefile.am index adf48eca7..948c4bf05 100644 --- a/v2v/Makefile.am +++ b/v2v/Makefile.am @@ -365,6 +365,7 @@ TESTS += \ test-v2v-cdrom.sh \ test-v2v-floppy.sh \ test-v2v-in-place.sh \ + test-v2v-mac.sh \ test-v2v-networks-and-bridges.sh \ test-v2v-no-copy.sh \ test-v2v-o-glance.sh \ @@ -511,6 +512,9 @@ EXTRA_DIST += \ test-v2v-in-place.sh \ test-v2v-it-vddk-io-query.sh \ test-v2v-machine-readable.sh \ + test-v2v-mac-expected.xml \ + test-v2v-mac.sh \ + test-v2v-mac.xml \ test-v2v-networks-and-bridges-expected.xml \ test-v2v-networks-and-bridges.sh \ test-v2v-networks-and-bridges.xml \ diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml index 218200a12..1d95c7887 100644 --- a/v2v/cmdline.ml +++ b/v2v/cmdline.ml @@ -42,6 +42,9 @@ type cmdline = { root_choice : root_choice; } +(* Matches --mac command line parameters. *) +let mac_re = PCRE.compile ~anchored:true "([[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}):(network|bridge):(.*)" + let parse_cmdline () let compressed = ref false in let debug_overlays = ref false in @@ -112,6 +115,16 @@ let parse_cmdline () | in_, out -> Networks.add_bridge network_map in_ out in + let add_mac str + if not (PCRE.matches mac_re str) then + error (f_"cannot parse --mac \"%s\" parameter") str; + let mac = PCRE.sub 1 and out = PCRE.sub 3 in + let vnet_type + match PCRE.sub 2 with + | "network" -> Network | "bridge" -> Bridge + | _ -> assert false in + Networks.add_mac network_map mac vnet_type out + in let no_trim_warning _ warning (f_"the --no-trim option has been removed and now does nothing") @@ -196,6 +209,8 @@ let parse_cmdline () s_"Input transport"; [ L"in-place" ], Getopt.Set in_place, s_"Only tune the guest in the input VM"; + [ L"mac" ], Getopt.String ("mac:network|bridge:out", add_mac), + s_"Map NIC to network or bridge"; [ L"machine-readable" ], Getopt.Set machine_readable, s_"Make output machine readable"; [ S 'n'; L"network" ], Getopt.String ("in:out", add_network), diff --git a/v2v/networks.ml b/v2v/networks.ml index 973e193b7..b443b7fe2 100644 --- a/v2v/networks.ml +++ b/v2v/networks.ml @@ -16,7 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) -(* Network, bridge mapping. *) +(* Network, bridge and MAC address mapping. *) open Printf @@ -26,67 +26,90 @@ open Common_gettext.Gettext open Types type t = { - (* For networks we use this to map a named network, or use the - * default network if no named network exists. + (* Map specific NIC with MAC address to a network or bridge. *) + mutable macs : (vnet_type * string) StringMap.t; + + (* If specific NIC mapping fails, for networks we use this to map + * a named network, or use the default network if no named + * network exists. *) mutable network_map : string StringMap.t; mutable default_network : string option; - (* Same as above but for bridges. *) + (* If that fails, same as above but for bridges. *) mutable bridge_map : string StringMap.t; mutable default_bridge : string option; } let map t nic - match nic.s_vnet_type with - | Network -> - (try - let vnet = StringMap.find nic.s_vnet t.network_map in - { nic with - s_vnet = vnet; - s_mapping_explanation - Some (sprintf "network mapped from %S to %S" - nic.s_vnet vnet) - } - with Not_found -> - match t.default_network with - | None -> nic (* no mapping done *) - | Some default_network -> - { nic with - s_vnet = default_network; - s_mapping_explanation - Some (sprintf "network mapped from %S to default %S" - nic.s_vnet default_network) - } - ) - | Bridge -> - (try - let vnet = StringMap.find nic.s_vnet t.bridge_map in - { nic with - s_vnet = vnet; - s_mapping_explanation - Some (sprintf "bridge mapped from %S to %S" - nic.s_vnet vnet) - } - with Not_found -> - match t.default_bridge with - | None -> nic (* no mapping done *) - | Some default_bridge -> - { nic with - s_vnet = default_bridge; - s_mapping_explanation - Some (sprintf "bridge mapped from %S to default %S" - nic.s_vnet default_bridge) - } - ) + try + let mac = match nic.s_mac with None -> raise Not_found | Some mac -> mac in + let mac = String.lowercase_ascii mac in + let vnet_type, vnet = StringMap.find mac t.macs in + { nic with + s_vnet_type = vnet_type; + s_vnet = vnet; + s_mapping_explanation + Some (sprintf "NIC mapped by MAC address to %s:%s" + (string_of_vnet_type vnet_type) vnet) + } + with Not_found -> + match nic.s_vnet_type with + | Network -> + (try + let vnet = StringMap.find nic.s_vnet t.network_map in + { nic with + s_vnet = vnet; + s_mapping_explanation + Some (sprintf "network mapped from %S to %S" + nic.s_vnet vnet) + } + with Not_found -> + match t.default_network with + | None -> nic (* no mapping done *) + | Some default_network -> + { nic with + s_vnet = default_network; + s_mapping_explanation + Some (sprintf "network mapped from %S to default %S" + nic.s_vnet default_network) + } + ) + | Bridge -> + (try + let vnet = StringMap.find nic.s_vnet t.bridge_map in + { nic with + s_vnet = vnet; + s_mapping_explanation + Some (sprintf "bridge mapped from %S to %S" + nic.s_vnet vnet) + } + with Not_found -> + match t.default_bridge with + | None -> nic (* no mapping done *) + | Some default_bridge -> + { nic with + s_vnet = default_bridge; + s_mapping_explanation + Some (sprintf "bridge mapped from %S to default %S" + nic.s_vnet default_bridge) + } + ) let create () = { + macs = StringMap.empty; network_map = StringMap.empty; default_network = None; bridge_map = StringMap.empty; default_bridge = None } +let add_mac t mac vnet_type vnet + let mac = String.lowercase_ascii mac in + if StringMap.mem mac t.macs then + error (f_"duplicate --mac parameter. Duplicate mappings specified for MAC address %s.") mac; + t.macs <- StringMap.add mac (vnet_type, vnet) t.macs + let add_network t i o if StringMap.mem i t.network_map then error (f_"duplicate -n/--network parameter. Duplicate mappings specified for network %s.") i; diff --git a/v2v/networks.mli b/v2v/networks.mli index af2e5a302..4b87477b6 100644 --- a/v2v/networks.mli +++ b/v2v/networks.mli @@ -16,7 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) -(** Network, bridge mapping. *) +(** Network, bridge and MAC address mapping. *) type t (** The map. *) @@ -43,9 +43,18 @@ val add_default_bridge : t -> string -> unit Equivalent to the [--bridge out] option. *) +val add_mac : t -> string -> Types.vnet_type -> string -> unit +(** Add a MAC address mapping. + + Equivalent to the [-mac MAC:<network|bridge>:out] option. *) + val map : t -> Types.source_nic -> Types.source_nic (** Apply the mapping to the source NIC, returning the updated - NIC with possibly modified [s_vnet] field. + NIC with possibly modified [s_vnet] and [s_vnet_type] fields. + + MAC address mappings take precedence, followed by network + and bridge mappings if no MAC address mapping for the NIC can + be found. [s_mapping_explanation] is set in the output with an informational message about what was done. *) diff --git a/v2v/test-v2v-mac-expected.xml b/v2v/test-v2v-mac-expected.xml new file mode 100644 index 000000000..f93f2eb0e --- /dev/null +++ b/v2v/test-v2v-mac-expected.xml @@ -0,0 +1,32 @@ + <interface type='bridge'> + <source bridge='VM Network'/> + </interface> + <interface type='network'> + <!-- NIC mapped by MAC address to Network:nancy --> + <source network='nancy'/> + <mac address='52:54:00:01:02:03'/> + </interface> + <interface type='bridge'> + <!-- NIC mapped by MAC address to Bridge:bob --> + <source bridge='bob'/> + <mac address='52:54:00:01:02:04'/> + </interface> + <interface type='network'> + <!-- network mapped from "john" to default "default_network" --> + <source network='default_network'/> + <mac address='52:54:00:01:02:05'/> + </interface> + <interface type='network'> + <!-- network mapped from "paul" to default "default_network" --> + <source network='default_network'/> + <mac address='52:54:00:01:02:06'/> + </interface> + <interface type='network'> + <!-- network mapped from "george" to default "default_network" --> + <source network='default_network'/> + <mac address='52:54:00:01:02:07'/> + </interface> + <interface type='bridge'> + <source bridge='ringo'/> + <mac address='52:54:00:01:02:08'/> + </interface> diff --git a/v2v/test-v2v-mac.sh b/v2v/test-v2v-mac.sh new file mode 100755 index 000000000..9b73032b5 --- /dev/null +++ b/v2v/test-v2v-mac.sh @@ -0,0 +1,57 @@ +#!/bin/bash - +# libguestfs virt-v2v test script +# Copyright (C) 2014-2018 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Test --mac parameter. + +set -e + +$TEST_FUNCTIONS +skip_if_skipped +skip_if_backend uml +skip_unless_phony_guest windows.img + +libvirt_uri="test://$abs_builddir/test-v2v-mac.xml" +f=$top_builddir/test-data/phony-guests/windows.img + +export VIRT_TOOLS_DATA_DIR="$top_srcdir/test-data/fake-virt-tools" + +d=test-v2v-mac.d +rm -rf $d +mkdir $d + +# Use --no-copy because we only care about metadata for this test. +$VG virt-v2v --debug-gc \ + -i libvirt -ic "$libvirt_uri" windows \ + -o local -os $d --no-copy \ + --mac 52:54:00:01:02:03:network:nancy \ + --mac 52:54:00:01:02:04:bridge:bob \ + --network default_network + +# Test the libvirt XML metadata was created. +test -f $d/windows.xml + +# Extract just the network interfaces from the XML. +# Delete the network model XML because that can change depending +# on whether virtio-win is installed or not. +sed -n '/interface/,/\/interface/p' $d/windows.xml | + grep -v 'model type=' > $d/networks + +# Test that the output has mapped the networks and bridges correctly. +diff -ur test-v2v-mac-expected.xml $d/networks + +rm -r $d diff --git a/v2v/test-v2v-mac.xml b/v2v/test-v2v-mac.xml new file mode 100644 index 000000000..b02c3aac8 --- /dev/null +++ b/v2v/test-v2v-mac.xml @@ -0,0 +1,81 @@ +<!-- +libguestfs virt-v2v tool +Copyright (C) 2009-2018 Red Hat Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +--> +<node> + <domain type='test'> + <name>windows</name> + <memory>1048576</memory> + <os> + <type>hvm</type> + <boot dev='hd'/> + </os> + <devices> + <disk type='file' device='disk'> + <driver name='qemu' type='raw'/> + <source file='../test-data/phony-guests/windows.img'/> + <target dev='vda' bus='virtio'/> + </disk> + + <!-- standard ESX bridge --> + <interface type='bridge'> + <mac address='00:00:00:00:00:00'/> + <source bridge='VM Network'/> + <model type='e1000'/> + </interface> + + <!-- various different NICs which can be remapped --> + + <interface type='bridge'> + <mac address='52:54:00:01:02:03'/> + <source bridge='bob'/> + <model type='e1000'/> + </interface> + + <interface type='network'> + <mac address='52:54:00:01:02:04'/> + <source network='default'/> + <model type='virtio'/> + </interface> + + <interface type='network'> + <mac address='52:54:00:01:02:05'/> + <source network='john'/> + <model type='virtio'/> + </interface> + + <interface type='network'> + <source network='paul'/> + <model type='virtio'/> + <mac address='52:54:00:01:02:06'/> + </interface> + + <interface type='network'> + <model type='rtl8139'/> + <source network='george'/> + <mac address='52:54:00:01:02:07'/> + </interface> + + <interface type='bridge'> + <mac address='52:54:00:01:02:08'/> + <model type='virtio'/> + <source bridge='ringo'/> + </interface> + + </devices> + </domain> +</node> diff --git a/v2v/types.ml b/v2v/types.ml index 118d0ccf3..672c8bf97 100644 --- a/v2v/types.ml +++ b/v2v/types.ml @@ -224,7 +224,7 @@ and string_of_source_removable { s_removable_type = typ; and string_of_source_nic { s_mac = mac; s_nic_model = model; s_vnet = vnet; s_vnet_type = typ } sprintf "\t%s \"%s\"%s%s" - (match typ with Bridge -> "Bridge" | Network -> "Network") + (string_of_vnet_type typ) vnet (match mac with | None -> "" @@ -233,6 +233,10 @@ and string_of_source_nic { s_mac = mac; s_nic_model = model; s_vnet = vnet; | None -> "" | Some model -> " [" ^ string_of_nic_model model ^ "]") +and string_of_vnet_type = function + | Bridge -> "Bridge" + | Network -> "Network" + and string_of_nic_model = function | Source_virtio_net -> "virtio" | Source_e1000 -> "e1000" diff --git a/v2v/types.mli b/v2v/types.mli index 79c0c1021..2bc29fa68 100644 --- a/v2v/types.mli +++ b/v2v/types.mli @@ -178,6 +178,7 @@ val string_of_source : source -> string val string_of_source_disk : source_disk -> string val string_of_controller : s_controller -> string val string_of_nic_model : s_nic_model -> string +val string_of_vnet_type : vnet_type -> string val string_of_source_sound_model : source_sound_model -> string val string_of_source_video : source_video -> string val string_of_source_cpu_topology : source_cpu_topology -> string diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod index 163d500af..ecc7b1c1e 100644 --- a/v2v/virt-v2v.pod +++ b/v2v/virt-v2v.pod @@ -469,6 +469,14 @@ Note this options only applies to keys and passphrases for encrypted devices and partitions, not for passwords used to connect to remote servers. +=item B<--mac> aa:bb:cc:dd:ee:ffB<:network:>out + +=item B<--mac> aa:bb:cc:dd:ee:ffB<:bridge:>out + +Map source NIC MAC address to a network or bridge. + +See L</NETWORKS AND BRIDGES> below. + =item B<--machine-readable> This option is used to make the output more machine friendly @@ -1140,8 +1148,8 @@ Not supported. Guests are usually connected to one or more networks, and when converted to the target hypervisor you usually want to reconnect those -networks at the destination. The options I<--network> and I<--bridge> -allow you to do that. +networks at the destination. The options I<--network>, I<--bridge> +and I<--mac> allow you to do that. If you are unsure of what networks and bridges are in use on the source hypervisor, then you can examine the source metadata (libvirt @@ -1165,8 +1173,8 @@ named external network on the source hypervisor, for example: NICs: Bridge "br0" -To map a specific bridge to a target network, for example C<br0> on -the source to C<ovirtmgmt> on the target, use: +To map a specific source bridge to a target network, for example +C<br0> on the source to C<ovirtmgmt> on the target, use: virt-v2v [...] --bridge br0:ovirtmgmt @@ -1174,6 +1182,24 @@ To map every bridge to a target network, use: virt-v2v [...] --bridge ovirtmgmt +=head2 Fine-grained mapping of guest NICs + +The I<--mac> option gives you more control over the mapping, letting +you map single NICs to either networks or bridges on the target. For +example a source guest with two NICs could map them individually to +two networks called C<mgmt> and C<clientdata> like this: + + $ virt-v2v [...] \ + --mac 52:54:00:d0:cf:0e:network:mgmt \ + --mac 52:54:00:d0:cf:0f:network:clientdata + +Note that virt-v2v does not have the ability to change a guest?s MAC +address. The MAC address is part of the guest metadata and must +remain the same on source and target hypervisors. Most guests will +use the MAC address to set up persistent associations between NICs and +internal names (like C<eth0>), with firewall settings, or even for +other purposes like software licensing. + =head1 INPUT FROM VMWARE VCENTER SERVER Virt-v2v is able to import guests from VMware vCenter Server. -- 2.17.1
Tomáš Golembiovský
2018-Jul-17 14:42 UTC
Re: [Libguestfs] [PATCH 0/3] v2v: Implement MAC address to network/bridge mapping.
On Wed, 4 Jul 2018 13:04:40 +0100 "Richard W.M. Jones" <rjones@redhat.com> wrote:> Deep in the discussion of this bug, unfortunately mostly in private > comments: > > https://bugzilla.redhat.com/show_bug.cgi?id=1594515 > > we decided it'd be more flexible for RHV if we had a way to map > individual NICs to target networks and bridges. This can be done by > adding a new --mac option so you can specify the exact mapping you > need: > > $ virt-v2v [...] \ > --mac 52:54:00:d0:cf:0e:network:mgmt \ > --mac 52:54:00:d0:cf:0f:network:clientdataThe series LGTM -- Tomáš Golembiovský <tgolembi@redhat.com>