diff --git a/os_win/tests/unit/utils/network/test_networkutils.py b/os_win/tests/unit/utils/network/test_networkutils.py index 849024f3..3c252f69 100644 --- a/os_win/tests/unit/utils/network/test_networkutils.py +++ b/os_win/tests/unit/utils/network/test_networkutils.py @@ -73,6 +73,7 @@ class NetworkUtilsTestCase(test_base.OsWinBaseTestCase): self.netutils._switch_ports = {} self.netutils._vlan_sds = {} self.netutils._profile_sds = {} + self.netutils._hw_offload_sds = {} self.netutils._vsid_sds = {} self.netutils._bandwidth_sds = {} conn = self.netutils._conn @@ -94,6 +95,8 @@ class NetworkUtilsTestCase(test_base.OsWinBaseTestCase): mock_bad_sd, mock_sd] conn.Msvm_EthernetSwitchPortBandwidthSettingData.return_value = [ mock_bad_sd, mock_sd] + conn.Msvm_EthernetSwitchPortOffloadSettingData.return_value = [ + mock_bad_sd, mock_sd] self.netutils.init_caches() @@ -106,6 +109,8 @@ class NetworkUtilsTestCase(test_base.OsWinBaseTestCase): self.assertEqual([mock_sd], list(self.netutils._vsid_sds.values())) self.assertEqual([mock_sd], list(self.netutils._bandwidth_sds.values())) + self.assertEqual([mock_sd], + list(self.netutils._hw_offload_sds.values())) def test_update_cache_disabled(self): self.netutils._enable_cache = False @@ -643,6 +648,36 @@ class NetworkUtilsTestCase(test_base.OsWinBaseTestCase): self.assertFalse(self.netutils._jobutils.add_virt_feature.called) + @mock.patch.object(networkutils.NetworkUtils, + '_get_hw_offload_sd_from_port_alloc') + def test_set_vswitch_port_sriov_already_set(self, mock_get_hw_offload_sd): + mock_port_alloc = self._mock_get_switch_port_alloc() + mock_hw_offload_sd = mock_get_hw_offload_sd.return_value + mock_hw_offload_sd.IOVOffloadWeight = self.netutils._IOV_ENABLED + + self.netutils.set_vswitch_port_sriov(mock.sentinel.port_name, + True) + + mock_get_hw_offload_sd.assert_called_once_with(mock_port_alloc) + self.netutils._jobutils.modify_virt_feature.assert_not_called() + + @ddt.data(True, False) + @mock.patch.object(networkutils.NetworkUtils, + '_get_hw_offload_sd_from_port_alloc') + def test_set_vswitch_port_sriov(self, state, mock_get_hw_offload_sd): + mock_port_alloc = self._mock_get_switch_port_alloc() + mock_hw_offload_sd = mock_get_hw_offload_sd.return_value + + self.netutils.set_vswitch_port_sriov(mock.sentinel.port_name, + state) + + mock_get_hw_offload_sd.assert_called_once_with(mock_port_alloc) + self.netutils._jobutils.modify_virt_feature.assert_called_with( + mock_hw_offload_sd) + desired_state = (self.netutils._IOV_ENABLED if state else + self.netutils._IOV_DISABLED) + self.assertEqual(desired_state, mock_hw_offload_sd.IOVOffloadWeight) + @mock.patch.object(networkutils.NetworkUtils, '_get_setting_data_from_port_alloc') def test_get_profile_setting_data_from_port_alloc(self, mock_get_sd): @@ -677,6 +712,17 @@ class NetworkUtilsTestCase(test_base.OsWinBaseTestCase): mock_port, self.netutils._vsid_sds, self.netutils._PORT_SECURITY_SET_DATA) + @mock.patch.object(networkutils.NetworkUtils, + '_get_setting_data_from_port_alloc') + def test_get_hw_offload_sd_from_port_alloc(self, mock_get_sd): + mock_port = mock.MagicMock() + result = self.netutils._get_hw_offload_sd_from_port_alloc(mock_port) + + self.assertEqual(mock_get_sd.return_value, result) + mock_get_sd.assert_called_once_with( + mock_port, self.netutils._hw_offload_sds, + self.netutils._PORT_HW_OFFLOAD_SET_DATA) + @mock.patch.object(networkutils.NetworkUtils, '_get_setting_data_from_port_alloc') def test_get_bandwidth_setting_data_from_port_alloc(self, mock_get_sd): diff --git a/os_win/tests/unit/utils/test_hostutils.py b/os_win/tests/unit/utils/test_hostutils.py index 086dbade..34cca848 100644 --- a/os_win/tests/unit/utils/test_hostutils.py +++ b/os_win/tests/unit/utils/test_hostutils.py @@ -47,10 +47,21 @@ class HostUtilsTestCase(test_base.OsWinBaseTestCase): def setUp(self): self._hostutils = hostutils.HostUtils() self._hostutils._conn_cimv2 = mock.MagicMock() + self._hostutils._conn_scimv2 = mock.MagicMock() self._hostutils._conn_attr = mock.MagicMock() + self._hostutils._netutils_prop = mock.MagicMock() + self._conn = self._hostutils._conn + self._conn_scimv2 = self._hostutils._conn_scimv2 + self._netutils = self._hostutils._netutils super(HostUtilsTestCase, self).setUp() + @mock.patch('os_win.utilsfactory.get_networkutils') + def test_netutils(self, mock_get_networkutils): + self._hostutils._netutils_prop = None + self.assertEqual(self._hostutils._netutils, + mock_get_networkutils.return_value) + @mock.patch('os_win.utils.hostutils.kernel32') def test_get_host_tick_count64(self, mock_kernel32): tick_count64 = "100" @@ -175,6 +186,37 @@ class HostUtilsTestCase(test_base.OsWinBaseTestCase): mock_sv_feature_cls.assert_called_once_with( ID=mock.sentinel.feature_id) + def test_get_nic_sriov_vfs(self): + mock_vswitch_sd = mock.Mock() + mock_hw_offload_sd_bad = mock.Mock(IovVfCapacity=0) + mock_hw_offload_sd_ok = mock.Mock() + vswitch_sds_class = self._conn.Msvm_VirtualEthernetSwitchSettingData + vswitch_sds_class.return_value = [mock_vswitch_sd] * 3 + self._conn.Msvm_EthernetSwitchHardwareOffloadData.side_effect = [ + [mock_hw_offload_sd_bad], [mock_hw_offload_sd_ok], + [mock_hw_offload_sd_ok]] + self._netutils.get_vswitch_external_network_name.side_effect = [ + None, mock.sentinel.nic_name] + mock_nic = mock.Mock() + self._conn_scimv2.MSFT_NetAdapter.return_value = [mock_nic] + + vfs = self._hostutils.get_nic_sriov_vfs() + + expected = { + 'vswitch_name': mock_vswitch_sd.ElementName, + 'device_id': mock_nic.PnPDeviceID, + 'total_vfs': mock_hw_offload_sd_ok.IovVfCapacity, + 'used_vfs': mock_hw_offload_sd_ok.IovVfUsage, + } + self.assertEqual([expected], vfs) + vswitch_sds_class.assert_called_once_with(IOVPreferred=True) + self._conn.Msvm_EthernetSwitchHardwareOffloadData.assert_has_calls([ + mock.call(SystemName=mock_vswitch_sd.VirtualSystemIdentifier)] * 3) + self._netutils.get_vswitch_external_network_name.assert_has_calls([ + mock.call(mock_vswitch_sd.ElementName)] * 2) + self._conn_scimv2.MSFT_NetAdapter.assert_called_once_with( + InterfaceDescription=mock.sentinel.nic_name) + def _check_get_numa_nodes_missing_info(self): numa_node = mock.MagicMock() self._hostutils._conn.Msvm_NumaNode.return_value = [ diff --git a/os_win/tests/unit/utils/test_jobutils.py b/os_win/tests/unit/utils/test_jobutils.py index 3e3469b2..8ab6a186 100644 --- a/os_win/tests/unit/utils/test_jobutils.py +++ b/os_win/tests/unit/utils/test_jobutils.py @@ -238,6 +238,11 @@ class JobUtilsTestCase(test_base.OsWinBaseTestCase): True, mock.sentinel.vm_path, [mock.sentinel.res_data]) + def test_modify_virt_feature(self): + self._test_virt_method('ModifyFeatureSettings', 3, + 'modify_virt_feature', False, + FeatureSettings=[mock.sentinel.res_data]) + def test_remove_virt_feature(self): self._test_virt_method('RemoveFeatureSettings', 2, 'remove_virt_feature', False, diff --git a/os_win/utils/compute/vmutils.py b/os_win/utils/compute/vmutils.py index f065274e..20c83a11 100644 --- a/os_win/utils/compute/vmutils.py +++ b/os_win/utils/compute/vmutils.py @@ -652,17 +652,24 @@ class VMUtils(baseutils.BaseUtilsVirt): raise exceptions.HyperVvNicNotFound(vnic_name=name) - def create_nic(self, vm_name, nic_name, mac_address): - """Create a (synthetic) nic and attach it to the vm.""" + def create_nic(self, vm_name, nic_name, mac_address=None): + """Create a (synthetic) nic and attach it to the vm. + + :param vm_name: The VM name to which the NIC will be attached to. + :param nic_name: The name of the NIC to be attached. + :param mac_address: The VM NIC's MAC address. If None, a Dynamic MAC + address will be used instead. + """ # Create a new nic new_nic_data = self._get_new_setting_data( self._SYNTHETIC_ETHERNET_PORT_SETTING_DATA_CLASS) # Configure the nic new_nic_data.ElementName = nic_name - new_nic_data.Address = mac_address.replace(':', '') - new_nic_data.StaticMacAddress = 'True' new_nic_data.VirtualSystemIdentifiers = ['{' + str(uuid.uuid4()) + '}'] + if mac_address: + new_nic_data.Address = mac_address.replace(':', '') + new_nic_data.StaticMacAddress = 'True' # Add the new nic to the vm vmsettings = self._lookup_vm_check(vm_name) diff --git a/os_win/utils/hostutils.py b/os_win/utils/hostutils.py index 47d3fc4d..d90cf090 100644 --- a/os_win/utils/hostutils.py +++ b/os_win/utils/hostutils.py @@ -47,11 +47,25 @@ class HostUtils(baseutils.BaseUtilsVirt): FEATURE_MPIO = 57 _wmi_cimv2_namespace = '//./root/cimv2' + _wmi_standard_cimv2_namespace = '//./root/StandardCimv2' def __init__(self, host='.'): super(HostUtils, self).__init__(host) self._conn_cimv2 = self._get_wmi_conn(self._wmi_cimv2_namespace, privileges=["Shutdown"]) + self._conn_scimv2 = self._get_wmi_conn( + self._wmi_standard_cimv2_namespace) + self._netutils_prop = None + + @property + def _netutils(self): + if not self._netutils_prop: + # NOTE(claudiub): we're importing utilsfactory here in order to + # avoid circular dependencies. + from os_win import utilsfactory + self._netutils_prop = utilsfactory.get_networkutils() + + return self._netutils_prop def get_cpus_info(self): """Returns dictionary containing information about the host's CPUs.""" @@ -167,6 +181,54 @@ class HostUtils(baseutils.BaseUtilsVirt): """Checks if the given feature exists on the host.""" return len(self._conn_cimv2.Win32_ServerFeature(ID=feature_id)) > 0 + def get_nic_sriov_vfs(self): + """Get host's NIC SR-IOV VFs. + + This method will ignore the vSwitches which do not have SR-IOV enabled, + or which are poorly configured (the NIC does not support SR-IOV). + + :returns: a list of dictionaries, containing the following fields: + - 'vswitch_name': the vSwtch name. + - 'total_vfs': the vSwitch's maximum number of VFs. (> 0) + - 'used_vfs': the vSwitch's number of used VFs. (<= 'total_vfs') + """ + + vfs = [] + + # NOTE(claudiub): A vSwitch will have to be configured to enable + # SR-IOV, otherwise its IOVPreferred flag will be False. + vswitch_sds = self._conn.Msvm_VirtualEthernetSwitchSettingData( + IOVPreferred=True) + for vswitch_sd in vswitch_sds: + hw_offload = self._conn.Msvm_EthernetSwitchHardwareOffloadData( + SystemName=vswitch_sd.VirtualSystemIdentifier)[0] + if not hw_offload.IovVfCapacity: + LOG.warning("VSwitch %s has SR-IOV enabled, but it is not " + "supported by the NIC or by the OS.", + vswitch_sd.ElementName) + continue + + nic_name = self._netutils.get_vswitch_external_network_name( + vswitch_sd.ElementName) + if not nic_name: + # NOTE(claudiub): This can happen if the vSwitch is not + # external. + LOG.warning("VSwitch %s is not external.", + vswitch_sd.ElementName) + continue + + nic = self._conn_scimv2.MSFT_NetAdapter( + InterfaceDescription=nic_name)[0] + + vfs.append({ + 'vswitch_name': vswitch_sd.ElementName, + 'device_id': nic.PnPDeviceID, + 'total_vfs': hw_offload.IovVfCapacity, + 'used_vfs': hw_offload.IovVfUsage, + }) + + return vfs + def get_numa_nodes(self): """Returns the host's list of NUMA nodes. diff --git a/os_win/utils/jobutils.py b/os_win/utils/jobutils.py index e54a3a1d..bb58a949 100644 --- a/os_win/utils/jobutils.py +++ b/os_win/utils/jobutils.py @@ -215,6 +215,14 @@ class JobUtils(baseutils.BaseUtilsVirt): parent.path_(), [f.GetText_(1) for f in virt_features]) self.check_ret_val(ret_val, job_path) + @_utils.not_found_decorator() + @_utils.retry_decorator(exceptions=exceptions.HyperVException) + def modify_virt_feature(self, virt_feature): + (job_path, out_set_data, + ret_val) = self._vs_man_svc.ModifyFeatureSettings( + FeatureSettings=[virt_feature.GetText_(1)]) + self.check_ret_val(ret_val, job_path) + def remove_virt_feature(self, virt_feature): self.remove_multiple_virt_features([virt_feature]) diff --git a/os_win/utils/network/networkutils.py b/os_win/utils/network/networkutils.py index cf51116e..03238eaa 100644 --- a/os_win/utils/network/networkutils.py +++ b/os_win/utils/network/networkutils.py @@ -62,6 +62,7 @@ class NetworkUtils(baseutils.BaseUtilsVirt): _PORT_VLAN_SET_DATA = 'Msvm_EthernetSwitchPortVlanSettingData' _PORT_PROFILE_SET_DATA = 'Msvm_EthernetSwitchPortProfileSettingData' _PORT_SECURITY_SET_DATA = 'Msvm_EthernetSwitchPortSecuritySettingData' + _PORT_HW_OFFLOAD_SET_DATA = 'Msvm_EthernetSwitchPortOffloadSettingData' _PORT_ALLOC_ACL_SET_DATA = 'Msvm_EthernetSwitchPortAclSettingData' _PORT_BANDWIDTH_SET_DATA = 'Msvm_EthernetSwitchPortBandwidthSettingData' _PORT_EXT_ACL_SET_DATA = _PORT_ALLOC_ACL_SET_DATA @@ -72,6 +73,9 @@ class NetworkUtils(baseutils.BaseUtilsVirt): _VM_SUMMARY_ENABLED_STATE = 100 _HYPERV_VM_STATE_ENABLED = 2 + _IOV_ENABLED = 100 + _IOV_DISABLED = 0 + _ACL_DIR_IN = 1 _ACL_DIR_OUT = 2 @@ -103,6 +107,7 @@ class NetworkUtils(baseutils.BaseUtilsVirt): _switch_ports = {} _vlan_sds = {} _profile_sds = {} + _hw_offload_sds = {} _vsid_sds = {} _sg_acl_sds = {} _bandwidth_sds = {} @@ -159,6 +164,14 @@ class NetworkUtils(baseutils.BaseUtilsVirt): if match: self._bandwidth_sds[match.group()] = bandwidth_sd + # map between switch port's InstanceID and their HW offload setting + # data WMI objects. + hw_offloads = self._conn.Msvm_EthernetSwitchPortOffloadSettingData() + for hw_offload_sd in hw_offloads: + match = switch_port_id_regex.match(hw_offload_sd.InstanceID) + if match: + self._hw_offload_sds[match.group()] = hw_offload_sd + def update_cache(self): if not self._enable_cache: return @@ -359,6 +372,7 @@ class NetworkUtils(baseutils.BaseUtilsVirt): self._vlan_sds.pop(sw_port.InstanceID, None) self._vsid_sds.pop(sw_port.InstanceID, None) self._bandwidth_sds.pop(sw_port.InstanceID, None) + self._hw_offload_sds.pop(sw_port.InstanceID, None) def set_vswitch_port_profile_id(self, switch_port_name, profile_id, profile_data, profile_name, vendor_name, @@ -559,6 +573,28 @@ class NetworkUtils(baseutils.BaseUtilsVirt): raise exceptions.HyperVException( _('Port Security Settings not found: %s') % switch_port_name) + def set_vswitch_port_sriov(self, switch_port_name, enabled): + """Enables / Disables SR-IOV for the given port. + + :param switch_port_name: the name of the port which will have SR-IOV + enabled or disabled. + :param enabled: boolean, if SR-IOV should be turned on or off. + """ + port_alloc = self._get_switch_port_allocation(switch_port_name)[0] + + # NOTE(claudiub): All ports have a HW offload SD. + hw_offload_sd = self._get_hw_offload_sd_from_port_alloc(port_alloc) + desired_state = self._IOV_ENABLED if enabled else self._IOV_DISABLED + if hw_offload_sd.IOVOffloadWeight == desired_state: + # already in the desired state. noop. + return + + hw_offload_sd.IOVOffloadWeight = desired_state + + # NOTE(claudiub): The HW offload SD can simply be modified. No need to + # remove it and create a new one. + self._jobutils.modify_virt_feature(hw_offload_sd) + def _get_profile_setting_data_from_port_alloc(self, port_alloc): return self._get_setting_data_from_port_alloc( port_alloc, self._profile_sds, self._PORT_PROFILE_SET_DATA) @@ -571,6 +607,10 @@ class NetworkUtils(baseutils.BaseUtilsVirt): return self._get_setting_data_from_port_alloc( port_alloc, self._vsid_sds, self._PORT_SECURITY_SET_DATA) + def _get_hw_offload_sd_from_port_alloc(self, port_alloc): + return self._get_setting_data_from_port_alloc( + port_alloc, self._hw_offload_sds, self._PORT_HW_OFFLOAD_SET_DATA) + def _get_bandwidth_setting_data_from_port_alloc(self, port_alloc): return self._get_setting_data_from_port_alloc( port_alloc, self._bandwidth_sds, self._PORT_BANDWIDTH_SET_DATA)