Skip to content

Commit

Permalink
v2v: Implement MAC address to network/bridge mapping.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rwmjones committed Jul 17, 2018
1 parent 682d3f8 commit fe1a886
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 52 deletions.
4 changes: 4 additions & 0 deletions v2v/Makefile.am
Expand Up @@ -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 \
Expand Down Expand Up @@ -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 \
Expand Down
15 changes: 15 additions & 0 deletions v2v/cmdline.ml
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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),
Expand Down
113 changes: 68 additions & 45 deletions v2v/networks.ml
Expand Up @@ -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

Expand All @@ -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;
Expand Down
13 changes: 11 additions & 2 deletions v2v/networks.mli
Expand Up @@ -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. *)

Expand All @@ -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. *)
32 changes: 32 additions & 0 deletions 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>
57 changes: 57 additions & 0 deletions 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
81 changes: 81 additions & 0 deletions 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>

0 comments on commit fe1a886

Please sign in to comment.