[Libguestfs] [PATCH 3/3] v2v: Implement MAC address to network/bridge mapping.

Richard W.M. Jones rjones at redhat.com
Wed Jul 4 12:04:43 UTC 2018


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




More information about the Libguestfs mailing list