From fe1a886612aa1f930070b321ef57aa4012224026 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Wed, 4 Jul 2018 12:00:58 +0100 Subject: [PATCH] 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(-) create mode 100644 v2v/test-v2v-mac-expected.xml create mode 100755 v2v/test-v2v-mac.sh create mode 100644 v2v/test-v2v-mac.xml diff --git a/v2v/Makefile.am b/v2v/Makefile.am index b4b2209f7..d0112c879 100644 --- a/v2v/Makefile.am +++ b/v2v/Makefile.am @@ -361,6 +361,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 \ @@ -503,6 +504,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::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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + windows + 1048576 + + hvm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v2v/types.ml b/v2v/types.ml index 60d178b6d..3e3f0fc26 100644 --- a/v2v/types.ml +++ b/v2v/types.ml @@ -227,7 +227,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 -> "" @@ -236,6 +236,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 4b0cdce67..891caeafe 100644 --- a/v2v/types.mli +++ b/v2v/types.mli @@ -179,6 +179,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 a533e95da..e5675a570 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 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 on -the source to C on the target, use: +To map a specific source bridge to a target network, for example +C on the source to C 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 and C 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), 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.