diff --git a/coriolis/osmorphing/windows.py b/coriolis/osmorphing/windows.py index 016c5c16..6a467907 100644 --- a/coriolis/osmorphing/windows.py +++ b/coriolis/osmorphing/windows.py @@ -77,6 +77,9 @@ INTERFACES_PATH_FORMAT = ( "HKLM:\\%s\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces") +DEVICE_CLASS_BASE_PATH_FORMAT = ( + "HKLM:\\%s\\ControlSet001\\Control\\Network") + STATIC_IP_SCRIPT_TEMPLATE = """ $ErrorActionPreference = "Stop" @@ -175,6 +178,16 @@ Set-DnsClientServerAddress -InterfaceIndex $interface.IfIndex -ResetServerAddresses Set-IPAddresses $interface $nic.ip_addresses $ips_info + + if ($nic.PSObject.Properties['interface_name'] -and $nic.interface_name) { + $conflictAdapter = Get-NetAdapter -Name $nic.interface_name -ErrorAction SilentlyContinue + if ($conflictAdapter -and $conflictAdapter.IfIndex -ne $interface.IfIndex) { + Write-Host "Removing conflicting adapter '$($conflictAdapter.Name)' to free up name '$($nic.interface_name)'" + Remove-NetAdapter -Name $conflictAdapter.Name -Confirm:$false + } + Rename-NetAdapter -Name $interface.Name -NewName $nic.interface_name + Write-Host "Renamed network adapter '$($interface.Name)' to '$($nic.interface_name)'" + } } } @@ -599,12 +612,41 @@ def _install_cloudbase_init(self, download_url, return cloudbaseinit_base_dir + def _get_guid_to_adapter_info_map(self, key_name): + network_base_path = DEVICE_CLASS_BASE_PATH_FORMAT % key_name + ps_command = ( + "$ErrorActionPreference = 'SilentlyContinue'; " + "$networkBase = '%(base)s'; " + "Get-ChildItem -Path $networkBase | " + "ForEach-Object { $classPath = $_.PSPath; " + "Get-ChildItem -Path $classPath | " + "ForEach-Object { $netGuid = $_.PSChildName; " + "$connPath = \"$classPath\\$netGuid\\Connection\"; " + "$p = Get-ItemProperty -Path $connPath; " + "if ($p.Name) { \"$netGuid|$($p.Name)\" } } }; " + "exit 0" % {'base': network_base_path}) + result = self._conn.exec_ps_command(ps_command) + LOG.debug( + "GUID-to-adapter-info PS command output: %s", result) + guid_map = {} + for line in result.splitlines(): + if '|' in line: + parts = line.split('|', 1) + guid = parts[0].strip().upper() + name = parts[1].strip() + guid_map[guid] = {'interface_name': name} + LOG.debug( + "Found interface name '%s' for adapter GUID '%s'", + name, guid) + return guid_map + def _compile_static_ip_conf_from_registry(self, key_name): ips_info = [] interfaces_reg_path = INTERFACES_PATH_FORMAT % key_name interfaces = self._conn.exec_ps_command( "(((Get-ChildItem -Path '%s').Name | Select-String -Pattern " "'[^\\\\]+$').Matches).Value" % interfaces_reg_path) + guid_to_adapter_info = self._get_guid_to_adapter_info_map(key_name) for interface in interfaces.splitlines(): reg_path = '%s\\%s' % (interfaces_reg_path, interface) enable_dhcp = self._conn.exec_ps_command( @@ -630,11 +672,20 @@ def _compile_static_ip_conf_from_registry(self, key_name): prefix_lengths.append( ipaddress.IPv4Network((0, submask)).prefixlen) + adapter_info = guid_to_adapter_info.get(interface.upper(), {}) + interface_name = adapter_info.get('interface_name', '') + if not interface_name: + LOG.warning( + "Could not find interface name for interface GUID " + "'%s'. Adapter name will not be preserved." + % interface) + ip_info = { "ip_addresses": ip_addresses.splitlines(), "prefix_lengths": prefix_lengths, "default_gateway": default_gateway, - "dns_addresses": name_server} + "dns_addresses": name_server, + "interface_name": interface_name} LOG.debug( "Found static IP configuration for interface '%s': " "%s" % (interface, ip_info)) @@ -678,6 +729,13 @@ def _get_static_nics_info(self, nics_info, ips_info): f"NIC ({nic.get('mac_address')}). Skipping") continue static_nic['ip_addresses'] = ip_matches + for ip_info in ips_info: + if set(static_nic['ip_addresses']).intersection( + set(ip_info.get('ip_addresses', []))): + interface_name = ip_info.get('interface_name') + if interface_name: + static_nic['interface_name'] = interface_name + break static_nics_info.append(static_nic) return static_nics_info diff --git a/coriolis/tests/osmorphing/test_windows.py b/coriolis/tests/osmorphing/test_windows.py index 75d03965..c1d9d474 100644 --- a/coriolis/tests/osmorphing/test_windows.py +++ b/coriolis/tests/osmorphing/test_windows.py @@ -677,8 +677,23 @@ def test_install_cloudbase_init_existing_service( "HKLM\\%s" % mock_uuid4.return_value) def test__compile_static_ip_conf_from_registry(self): + network_base_path = (windows.DEVICE_CLASS_BASE_PATH_FORMAT % + mock.sentinel.key_name) + adapter_map_ps_cmd = ( + "$ErrorActionPreference = 'SilentlyContinue'; " + "$networkBase = '%(base)s'; " + "Get-ChildItem -Path $networkBase | " + "ForEach-Object { $classPath = $_.PSPath; " + "Get-ChildItem -Path $classPath | " + "ForEach-Object { $netGuid = $_.PSChildName; " + "$connPath = \"$classPath\\$netGuid\\Connection\"; " + "$p = Get-ItemProperty -Path $connPath; " + "if ($p.Name) { \"$netGuid|$($p.Name)\" } } }; " + "exit 0" % {'base': network_base_path}) + self.conn.exec_ps_command.side_effect = [ 'interface1\ninterface2', + '', '0', '192.168.1.254', '8.8.8.8', @@ -696,6 +711,7 @@ def test__compile_static_ip_conf_from_registry(self): mock.call( "(((Get-ChildItem -Path '%s').Name | Select-String -Pattern " "'[^\\\\]+$').Matches).Value" % interfaces_reg_path), + mock.call(adapter_map_ps_cmd), mock.call( "(Get-ItemProperty -Path '%s\\interface1').EnableDHCP" % interfaces_reg_path), @@ -720,13 +736,29 @@ def test__compile_static_ip_conf_from_registry(self): {"ip_addresses": ['192.168.1.1'], "prefix_lengths": [24], "default_gateway": '192.168.1.254', - "dns_addresses": '8.8.8.8'} + "dns_addresses": '8.8.8.8', + "interface_name": ''} ] self.assertEqual(result, expected_ips_info) def test_compile_static_ip_conf_from_registry_no_ip_or_subnet(self): + network_base_path = (windows.DEVICE_CLASS_BASE_PATH_FORMAT % + mock.sentinel.key_name) + adapter_map_ps_cmd = ( + "$ErrorActionPreference = 'SilentlyContinue'; " + "$networkBase = '%(base)s'; " + "Get-ChildItem -Path $networkBase | " + "ForEach-Object { $classPath = $_.PSPath; " + "Get-ChildItem -Path $classPath | " + "ForEach-Object { $netGuid = $_.PSChildName; " + "$connPath = \"$classPath\\$netGuid\\Connection\"; " + "$p = Get-ItemProperty -Path $connPath; " + "if ($p.Name) { \"$netGuid|$($p.Name)\" } } }; " + "exit 0" % {'base': network_base_path}) + self.conn.exec_ps_command.side_effect = [ 'interface1', + '', '0', "default_gateway", "nameservers", @@ -745,6 +777,7 @@ def test_compile_static_ip_conf_from_registry_no_ip_or_subnet(self): mock.call( "(((Get-ChildItem -Path '%s').Name | Select-String -Pattern " "'[^\\\\]+$').Matches).Value" % interfaces_reg_path), + mock.call(adapter_map_ps_cmd), mock.call("(Get-ItemProperty -Path '%s\\interface1').EnableDHCP" % interfaces_reg_path), mock.call( @@ -760,8 +793,23 @@ def test_compile_static_ip_conf_from_registry_no_ip_or_subnet(self): ]) def test_compile_static_ip_conf_from_registry_no_static_ip(self): + network_base_path = (windows.DEVICE_CLASS_BASE_PATH_FORMAT % + mock.sentinel.key_name) + adapter_map_ps_cmd = ( + "$ErrorActionPreference = 'SilentlyContinue'; " + "$networkBase = '%(base)s'; " + "Get-ChildItem -Path $networkBase | " + "ForEach-Object { $classPath = $_.PSPath; " + "Get-ChildItem -Path $classPath | " + "ForEach-Object { $netGuid = $_.PSChildName; " + "$connPath = \"$classPath\\$netGuid\\Connection\"; " + "$p = Get-ItemProperty -Path $connPath; " + "if ($p.Name) { \"$netGuid|$($p.Name)\" } } }; " + "exit 0" % {'base': network_base_path}) + self.conn.exec_ps_command.side_effect = [ 'interface1', + '', '1', ] interfaces_reg_path = (windows.INTERFACES_PATH_FORMAT % @@ -775,6 +823,7 @@ def test_compile_static_ip_conf_from_registry_no_static_ip(self): mock.call( "(((Get-ChildItem -Path '%s').Name | Select-String -Pattern " "'[^\\\\]+$').Matches).Value" % interfaces_reg_path), + mock.call(adapter_map_ps_cmd), mock.call("(Get-ItemProperty -Path '%s\\interface1').EnableDHCP" % interfaces_reg_path), ])