Blame SOURCES/0042-v2v-Copy-static-IP-address-information-over-for-Wind.patch

da373f
From 77606e831f8891b65350effb6502d233d74f8cfc Mon Sep 17 00:00:00 2001
3efd08
From: "Richard W.M. Jones" <rjones@redhat.com>
3efd08
Date: Tue, 4 Dec 2018 16:09:42 +0000
3efd08
Subject: [PATCH] v2v: Copy static IP address information over for Windows
3efd08
 guests (RHBZ#1626503).
3efd08
3efd08
For Linux the guest itself remembers the IP address associated with
3efd08
each MAC address.  Thus it doesn't matter if the interface type
3efd08
changes (ie. to virtio-net), because as long as we preserve the MAC
3efd08
address the guest will use the same IP address or the same DHCP
3efd08
configuration.
3efd08
3efd08
However on Windows this association is not maintained by MAC address.
3efd08
In fact the MAC address isn't saved anywhere in the guest registry.
3efd08
(It seems instead this is likely done through PCI device type and
3efd08
address which we don't record at the moment and is almost impossible
3efd08
to preserve.)  When a guest which doesn't use DHCP is migrated, the
3efd08
guest sees the brand new virtio-net devices and doesn't know what to
3efd08
do with them, and meanwhile the right static IPs are still associated
3efd08
with the old and now-defunct interfaces in the registry.
3efd08
3efd08
We cannot collect the required information from within the guest.
3efd08
However we can collect it outside the tool by some other means
3efd08
(eg. using VMware Tools APIs) and present this information to virt-v2v
3efd08
which then writes it into the Windows guest at firstboot time.
3efd08
3efd08
This commit adds the --mac ..:ip:.. sub-option which creates a
3efd08
Powershell script to set network adapters at firstboot.  An option
3efd08
such as:
3efd08
3efd08
  --mac 00:0c:29:e6:3d:9d:ip:192.168.0.89,192.168.0.1,24,192.168.0.254
3efd08
3efd08
approximately turns into this script:
3efd08
3efd08
  # Wait for the netkvm (virtio-net) driver to become active.
3efd08
  $adapters = @()
3efd08
  While (-Not $adapters) {
3efd08
      Start-Sleep -Seconds 5
3efd08
      $adapters = Get-NetAdapter -Physical |
3efd08
                     Where DriverFileName -eq "netkvm.sys"
3efd08
  }
3efd08
  $mac_address = '00-0c-29-e6-3d-9d'
3efd08
  $ifindex = (Get-NetAdapter -Physical |
3efd08
                 Where MacAddress -eq $mac_address).ifIndex
3efd08
  if ($ifindex) {
3efd08
      New-NetIPAddress -InterfaceIndex $ifindex
3efd08
                       -IPAddress '192.168.0.89'
3efd08
                       -DefaultGateway '192.168.0.1'
3efd08
                       -PrefixLength 24
3efd08
      Set-DnsClientServerAddress -InterfaceIndex $ifindex
3efd08
                       -ServerAddresses ('192.168.0.254')
3efd08
  }
3efd08
3efd08
Thanks: Brett Thurber for diagnosing the problem and suggesting paths
3efd08
towards a fix.
3efd08
3efd08
(cherry picked from commit dfd9fac7435cf2f9293961b6cc1fe316af4feebc)
3efd08
---
3efd08
 v2v/cmdline.ml         | 41 ++++++++++++++++++-----
3efd08
 v2v/cmdline.mli        |  1 +
3efd08
 v2v/convert_linux.ml   |  2 +-
3efd08
 v2v/convert_windows.ml | 76 +++++++++++++++++++++++++++++++++++++++++-
3efd08
 v2v/modules_list.ml    |  2 +-
3efd08
 v2v/modules_list.mli   |  2 +-
3efd08
 v2v/types.ml           |  8 +++++
3efd08
 v2v/types.mli          |  9 +++++
3efd08
 v2v/v2v.ml             |  7 ++--
3efd08
 v2v/virt-v2v.pod       | 18 ++++++++++
3efd08
 10 files changed, 150 insertions(+), 16 deletions(-)
3efd08
3efd08
diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml
3efd08
index 4d390f249..686631271 100644
3efd08
--- a/v2v/cmdline.ml
3efd08
+++ b/v2v/cmdline.ml
3efd08
@@ -40,11 +40,12 @@ type cmdline = {
3efd08
   print_estimate : bool;
3efd08
   print_source : bool;
3efd08
   root_choice : root_choice;
3efd08
+  static_ips : static_ip list;
3efd08
   ks : Tools_utils.key_store;
3efd08
 }
3efd08
 
3efd08
 (* Matches --mac command line parameters. *)
3efd08
-let mac_re = PCRE.compile ~anchored:true "([[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}):(network|bridge):(.*)"
3efd08
+let mac_re = PCRE.compile ~anchored:true "([[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}):(network|bridge|ip):(.*)"
3efd08
 
3efd08
 let parse_cmdline () =
3efd08
   let compressed = ref false in
3efd08
@@ -97,6 +98,7 @@ let parse_cmdline () =
3efd08
   in
3efd08
 
3efd08
   let network_map = Networks.create () in
3efd08
+  let static_ips = ref [] in
3efd08
   let add_network str =
3efd08
     match String.split ":" str with
3efd08
     | "", "" ->
3efd08
@@ -119,11 +121,30 @@ let parse_cmdline () =
3efd08
     if not (PCRE.matches mac_re str) then
3efd08
       error (f_"cannot parse --mac \"%s\" parameter") str;
3efd08
     let mac = PCRE.sub 1 and out = PCRE.sub 3 in
3efd08
-    let vnet_type =
3efd08
-      match PCRE.sub 2 with
3efd08
-      | "network" -> Network | "bridge" -> Bridge
3efd08
-      | _ -> assert false in
3efd08
-    Networks.add_mac network_map mac vnet_type out
3efd08
+    match PCRE.sub 2 with
3efd08
+    | "network" ->
3efd08
+       Networks.add_mac network_map mac Network out
3efd08
+    | "bridge" ->
3efd08
+       Networks.add_mac network_map mac Bridge out
3efd08
+    | "ip" ->
3efd08
+       let add if_mac_addr if_ip_address if_default_gateway
3efd08
+               if_prefix_length if_nameservers =
3efd08
+         List.push_back static_ips
3efd08
+                        { if_mac_addr; if_ip_address; if_default_gateway;
3efd08
+                          if_prefix_length; if_nameservers }
3efd08
+       in
3efd08
+       (match String.nsplit "," out with
3efd08
+        | [] ->
3efd08
+           error (f_"invalid --mac ip option")
3efd08
+        | [ip] -> add mac ip None None []
3efd08
+        | [ip; gw] -> add mac ip (Some gw) None []
3efd08
+        | ip :: gw :: len :: nameservers ->
3efd08
+           let len =
3efd08
+             try int_of_string len with
3efd08
+             | Failure _ -> error (f_"cannot parse --mac ip prefix length field as an integer: %s") len in
3efd08
+           add mac ip (Some gw) (Some len) nameservers
3efd08
+       );
3efd08
+    | _ -> assert false
3efd08
   in
3efd08
 
3efd08
   let no_trim_warning _ =
3efd08
@@ -211,8 +232,8 @@ let parse_cmdline () =
3efd08
                                     s_"Input transport";
3efd08
     [ L"in-place" ], Getopt.Set in_place,
3efd08
                                     s_"Only tune the guest in the input VM";
3efd08
-    [ L"mac" ],      Getopt.String ("mac:network|bridge:out", add_mac),
3efd08
-                                    s_"Map NIC to network or bridge";
3efd08
+    [ L"mac" ],      Getopt.String ("mac:network|bridge|ip:out", add_mac),
3efd08
+                                    s_"Map NIC to network or bridge or assign static IP";
3efd08
     [ S 'n'; L"network" ], Getopt.String ("in:out", add_network),
3efd08
                                     s_"Map network ‘in’ to ‘out’";
3efd08
     [ L"no-copy" ],  Getopt.Clear do_copy,
3efd08
@@ -335,6 +356,7 @@ read the man page virt-v2v(1).
3efd08
   let print_source = !print_source in
3efd08
   let qemu_boot = !qemu_boot in
3efd08
   let root_choice = !root_choice in
3efd08
+  let static_ips = !static_ips in
3efd08
 
3efd08
   (* No arguments and machine-readable mode?  Print out some facts
3efd08
    * about what this binary supports.
3efd08
@@ -351,6 +373,7 @@ read the man page virt-v2v(1).
3efd08
     pr "in-place\n";
3efd08
     pr "io/oo\n";
3efd08
     pr "mac-option\n";
3efd08
+    pr "mac-ip-option\n";
3efd08
     List.iter (pr "input:%s\n") (Modules_list.input_modules ());
3efd08
     List.iter (pr "output:%s\n") (Modules_list.output_modules ());
3efd08
     List.iter (pr "convert:%s\n") (Modules_list.convert_modules ());
3efd08
@@ -685,7 +708,7 @@ read the man page virt-v2v(1).
3efd08
   {
3efd08
     compressed; debug_overlays; do_copy; in_place; network_map;
3efd08
     output_alloc; output_format; output_name;
3efd08
-    print_estimate; print_source; root_choice;
3efd08
+    print_estimate; print_source; root_choice; static_ips;
3efd08
     ks = opthandle.ks;
3efd08
   },
3efd08
   input, output
3efd08
diff --git a/v2v/cmdline.mli b/v2v/cmdline.mli
3efd08
index 78601e191..a009e9888 100644
3efd08
--- a/v2v/cmdline.mli
3efd08
+++ b/v2v/cmdline.mli
3efd08
@@ -30,6 +30,7 @@ type cmdline = {
3efd08
   print_estimate : bool;
3efd08
   print_source : bool;
3efd08
   root_choice : Types.root_choice;
3efd08
+  static_ips : Types.static_ip list;
3efd08
   ks : Tools_utils.key_store;
3efd08
 }
3efd08
 
3efd08
diff --git a/v2v/convert_linux.ml b/v2v/convert_linux.ml
3efd08
index f9e811c8d..1ada36115 100644
3efd08
--- a/v2v/convert_linux.ml
3efd08
+++ b/v2v/convert_linux.ml
3efd08
@@ -34,7 +34,7 @@ open Linux_kernels
3efd08
 module G = Guestfs
3efd08
 
3efd08
 (* The conversion function. *)
3efd08
-let convert (g : G.guestfs) inspect source output rcaps =
3efd08
+let convert (g : G.guestfs) inspect source output rcaps _ =
3efd08
   (*----------------------------------------------------------------------*)
3efd08
   (* Inspect the guest first.  We already did some basic inspection in
3efd08
    * the common v2v.ml code, but that has to deal with generic guests
3efd08
diff --git a/v2v/convert_windows.ml b/v2v/convert_windows.ml
3efd08
index 1db3c0ea6..75e609d61 100644
3efd08
--- a/v2v/convert_windows.ml
3efd08
+++ b/v2v/convert_windows.ml
3efd08
@@ -38,7 +38,7 @@ module G = Guestfs
3efd08
  * time the Windows VM is booted on KVM.
3efd08
  *)
3efd08
 
3efd08
-let convert (g : G.guestfs) inspect source output rcaps =
3efd08
+let convert (g : G.guestfs) inspect source output rcaps static_ips =
3efd08
   (*----------------------------------------------------------------------*)
3efd08
   (* Inspect the Windows guest. *)
3efd08
 
3efd08
@@ -228,6 +228,8 @@ let convert (g : G.guestfs) inspect source output rcaps =
3efd08
     Registry.with_hive_write g inspect.i_windows_software_hive
3efd08
                              update_software_hive;
3efd08
 
3efd08
+    configure_network_interfaces net_driver;
3efd08
+
3efd08
     fix_ntfs_heads ();
3efd08
 
3efd08
     fix_win_esp ();
3efd08
@@ -603,6 +605,78 @@ if errorlevel 3010 exit /b 0
3efd08
     | None ->
3efd08
        warning (f_"could not find registry key HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion")
3efd08
 
3efd08
+  and configure_network_interfaces net_driver =
3efd08
+    (* If we were asked to force network interfaces to have particular
3efd08
+     * static IP addresses then it is done here by installing a
3efd08
+     * Powershell script which runs at boot.
3efd08
+     *)
3efd08
+    if static_ips <> [] then (
3efd08
+      let psh_filename = "v2vnetcf.ps1" in
3efd08
+      let psh = ref [] in
3efd08
+      let add = List.push_back psh in
3efd08
+
3efd08
+      add "# Uncomment this line for lots of debug output.";
3efd08
+      add "# Set-PSDebug -Trace 1";
3efd08
+      add "";
3efd08
+
3efd08
+      (* If virtio-net was added to the registry, we must wait for
3efd08
+       * it to be installed at runtime.
3efd08
+       *)
3efd08
+      if net_driver = Virtio_net then (
3efd08
+        add "# Wait for the netkvm (virtio-net) driver to become active.";
3efd08
+        add "$adapters = @()";
3efd08
+        add "While (-Not $adapters) {";
3efd08
+        add "    Start-Sleep -Seconds 5";
3efd08
+        add "    $adapters = Get-NetAdapter -Physical | Where DriverFileName -eq \"netkvm.sys\"";
3efd08
+        add "    Write-Host \"adapters = '$adapters'\"";
3efd08
+        add "}";
3efd08
+        add ""
3efd08
+      );
3efd08
+
3efd08
+      List.iter (
3efd08
+        fun { if_mac_addr; if_ip_address; if_default_gateway;
3efd08
+              if_prefix_length; if_nameservers } ->
3efd08
+          add (sprintf "$mac_address = '%s'"
3efd08
+                       (String.replace if_mac_addr ":" "-"));
3efd08
+          add "$ifindex = (Get-NetAdapter -Physical | Where MacAddress -eq $mac_address).ifIndex";
3efd08
+          add "if ($ifindex) {";
3efd08
+
3efd08
+          add "    Write-Host \"setting IP address of adapter at $ifindex\"";
3efd08
+
3efd08
+          (* New-NetIPAddress command *)
3efd08
+          let args = ref [] in
3efd08
+          List.push_back args "-InterfaceIndex";
3efd08
+          List.push_back args "$ifindex";
3efd08
+          List.push_back args "-IPAddress";
3efd08
+          List.push_back args (sprintf "'%s'" if_ip_address);
3efd08
+          (match if_default_gateway with
3efd08
+           | None -> ()
3efd08
+           | Some gw ->
3efd08
+              List.push_back args "-DefaultGateway";
3efd08
+              List.push_back args (sprintf "'%s'" gw)
3efd08
+          );
3efd08
+          (match if_prefix_length with
3efd08
+           | None -> ()
3efd08
+           | Some len ->
3efd08
+              List.push_back args "-PrefixLength";
3efd08
+              List.push_back args (string_of_int len)
3efd08
+          );
3efd08
+          let cmd1 = "New-NetIPAddress " ^ String.concat " " !args in
3efd08
+          add ("    " ^ cmd1);
3efd08
+
3efd08
+          (* Set-DnsClientServerAddress command *)
3efd08
+          if if_nameservers <> [] then (
3efd08
+            add (sprintf "    Set-DnsClientServerAddress -InterfaceIndex $ifindex -ServerAddresses (%s)"
3efd08
+                         (String.concat "," (List.map (sprintf "'%s'") if_nameservers)))
3efd08
+          );
3efd08
+          add "}";
3efd08
+          add ""
3efd08
+      ) static_ips;
3efd08
+
3efd08
+      (* Install the Powershell script to run at firstboot. *)
3efd08
+      Windows.install_firstboot_powershell g inspect psh_filename !psh
3efd08
+    ) (* static_ips <> [] *)
3efd08
+
3efd08
   and fix_ntfs_heads () =
3efd08
     (* NTFS hardcodes the number of heads on the drive which created
3efd08
        it in the filesystem header. Modern versions of Windows
3efd08
diff --git a/v2v/modules_list.ml b/v2v/modules_list.ml
3efd08
index a0a74aaf2..76b3def5d 100644
3efd08
--- a/v2v/modules_list.ml
3efd08
+++ b/v2v/modules_list.ml
3efd08
@@ -38,7 +38,7 @@ type inspection_fn = Types.inspect -> bool
3efd08
 
3efd08
 type conversion_fn =
3efd08
   Guestfs.guestfs -> Types.inspect -> Types.source -> Types.output_settings ->
3efd08
-  Types.requested_guestcaps -> Types.guestcaps
3efd08
+  Types.requested_guestcaps -> Types.static_ip list -> Types.guestcaps
3efd08
 
3efd08
 let convert_modules = ref []
3efd08
 
3efd08
diff --git a/v2v/modules_list.mli b/v2v/modules_list.mli
3efd08
index 3e80d3e23..ad2024755 100644
3efd08
--- a/v2v/modules_list.mli
3efd08
+++ b/v2v/modules_list.mli
3efd08
@@ -34,7 +34,7 @@ type inspection_fn = Types.inspect -> bool
3efd08
 
3efd08
 type conversion_fn =
3efd08
   Guestfs.guestfs -> Types.inspect -> Types.source -> Types.output_settings ->
3efd08
-  Types.requested_guestcaps -> Types.guestcaps
3efd08
+  Types.requested_guestcaps -> Types.static_ip list -> Types.guestcaps
3efd08
 
3efd08
 val register_convert_module : inspection_fn -> string -> conversion_fn -> unit
3efd08
 (** [register_convert_module inspect_fn name fn] registers a
3efd08
diff --git a/v2v/types.ml b/v2v/types.ml
3efd08
index 714b30014..4ba6117fd 100644
3efd08
--- a/v2v/types.ml
3efd08
+++ b/v2v/types.ml
3efd08
@@ -506,6 +506,14 @@ type root_choice = AskRoot | SingleRoot | FirstRoot | RootDev of string
3efd08
 
3efd08
 type output_allocation = Sparse | Preallocated
3efd08
 
3efd08
+type static_ip = {
3efd08
+  if_mac_addr : string;
3efd08
+  if_ip_address : string;
3efd08
+  if_default_gateway : string option;
3efd08
+  if_prefix_length : int option;
3efd08
+  if_nameservers : string list;
3efd08
+}
3efd08
+
3efd08
 class virtual input = object
3efd08
   method precheck () = ()
3efd08
   method virtual as_options : string
3efd08
diff --git a/v2v/types.mli b/v2v/types.mli
3efd08
index f595ab0ef..528d77965 100644
3efd08
--- a/v2v/types.mli
3efd08
+++ b/v2v/types.mli
3efd08
@@ -361,6 +361,15 @@ type root_choice = AskRoot | SingleRoot | FirstRoot | RootDev of string
3efd08
 type output_allocation = Sparse | Preallocated
3efd08
 (** Type of [-oa] (output allocation) option. *)
3efd08
 
3efd08
+type static_ip = {
3efd08
+  if_mac_addr : string;
3efd08
+  if_ip_address : string;
3efd08
+  if_default_gateway : string option;
3efd08
+  if_prefix_length : int option;
3efd08
+  if_nameservers : string list;
3efd08
+}
3efd08
+(** [--mac ..:ip:..] option. *)
3efd08
+
3efd08
 (** {2 Input object}
3efd08
 
3efd08
     This is subclassed for the various input [-i] options.
3efd08
diff --git a/v2v/v2v.ml b/v2v/v2v.ml
3efd08
index 63e809030..d7a868659 100644
3efd08
--- a/v2v/v2v.ml
3efd08
+++ b/v2v/v2v.ml
3efd08
@@ -133,7 +133,7 @@ let rec main () =
3efd08
       | In_place ->
3efd08
          rcaps_from_source source in
3efd08
 
3efd08
-    do_convert g inspect source output rcaps in
3efd08
+    do_convert g inspect source output rcaps cmdline.static_ips in
3efd08
 
3efd08
   g#umount_all ();
3efd08
 
3efd08
@@ -556,7 +556,7 @@ and estimate_target_size mpstats overlays =
3efd08
   )
3efd08
 
3efd08
 (* Conversion. *)
3efd08
-and do_convert g inspect source output rcaps =
3efd08
+and do_convert g inspect source output rcaps interfaces =
3efd08
   (match inspect.i_product_name with
3efd08
   | "unknown" ->
3efd08
     message (f_"Converting the guest to run on KVM")
3efd08
@@ -572,7 +572,8 @@ and do_convert g inspect source output rcaps =
3efd08
   debug "picked conversion module %s" conversion_name;
3efd08
   debug "requested caps: %s" (string_of_requested_guestcaps rcaps);
3efd08
   let guestcaps =
3efd08
-    convert g inspect source (output :> Types.output_settings) rcaps in
3efd08
+    convert g inspect source (output :> Types.output_settings) rcaps
3efd08
+            interfaces in
3efd08
   debug "%s" (string_of_guestcaps guestcaps);
3efd08
 
3efd08
   (* Did we manage to install virtio drivers? *)
3efd08
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
3efd08
index 9a555c3be..0642d158f 100644
3efd08
--- a/v2v/virt-v2v.pod
3efd08
+++ b/v2v/virt-v2v.pod
3efd08
@@ -368,6 +368,24 @@ Map source NIC MAC address to a network or bridge.
3efd08
 
3efd08
 See L</Networks and bridges> below.
3efd08
 
3efd08
+=item B<--mac> aa:bb:cc:dd:ee:ffB<:ip:>ipaddr[,gw[,len[,ns,ns,...]]]
3efd08
+
3efd08
+Force a particular interface (controlled by its MAC address) to have a
3efd08
+static IP address after boot.
3efd08
+
3efd08
+The fields in the parameter are: C<ipaddr> is the IP address.  C<gw>
3efd08
+is the optional gateway IP address.  C<len> is the subnet mask length
3efd08
+(an integer).  The final parameters are zero or more nameserver IP
3efd08
+addresses.
3efd08
+
3efd08
+This option can be supplied zero or more times.
3efd08
+
3efd08
+You only need to use this option for certain broken guests such as
3efd08
+Windows which are unable to preserve MAC to static IP address mappings
3efd08
+automatically.  You don't need to use it if Windows is using DHCP.  It
3efd08
+is currently ignored for Linux guests since they do not have this
3efd08
+problem.
3efd08
+
3efd08
 =item B<--machine-readable>
3efd08
 
3efd08
 =item B<--machine-readable>=format
3efd08
-- 
da373f
2.18.4
3efd08