diff --git a/os_win/tests/unit/utils/network/test_networkutils.py b/os_win/tests/unit/utils/network/test_networkutils.py index 3c252f69..f24266f6 100644 --- a/os_win/tests/unit/utils/network/test_networkutils.py +++ b/os_win/tests/unit/utils/network/test_networkutils.py @@ -653,7 +653,7 @@ class NetworkUtilsTestCase(test_base.OsWinBaseTestCase): 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 + mock_hw_offload_sd.IOVOffloadWeight = self.netutils._OFFLOAD_ENABLED self.netutils.set_vswitch_port_sriov(mock.sentinel.port_name, True) @@ -674,10 +674,51 @@ class NetworkUtilsTestCase(test_base.OsWinBaseTestCase): 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) + desired_state = (self.netutils._OFFLOAD_ENABLED if state else + self.netutils._OFFLOAD_DISABLED) self.assertEqual(desired_state, mock_hw_offload_sd.IOVOffloadWeight) + @ddt.data({'iov_queues_requested': 0}, + {'offloaded_sa': 0}) + @ddt.unpack + def test_set_vswitch_port_offload_invalid(self, iov_queues_requested=1, + offloaded_sa=1024): + self.assertRaises(exceptions.InvalidParameterValue, + self.netutils.set_vswitch_port_offload, + mock.sentinel.port_name, + iov_queues_requested=iov_queues_requested, + offloaded_sa=offloaded_sa) + + @mock.patch.object(networkutils.NetworkUtils, + '_get_hw_offload_sd_from_port_alloc') + def test_set_vswitch_port_offload_noop(self, mock_get_hw_offload_sd): + self._mock_get_switch_port_alloc() + self.netutils.set_vswitch_port_offload(mock.sentinel.port_name) + self.netutils._jobutils.modify_virt_feature.assert_not_called() + + @mock.patch.object(networkutils.NetworkUtils, + '_get_hw_offload_sd_from_port_alloc') + def test_set_vswitch_port_offload(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 + iov_queues = 1 + offloaded_sa = 1 + + self.netutils.set_vswitch_port_offload( + mock.sentinel.port_name, True, iov_queues, True, offloaded_sa) + + 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) + self.assertEqual(self.netutils._OFFLOAD_ENABLED, + mock_hw_offload_sd.IOVOffloadWeight) + self.assertEqual(iov_queues, + mock_hw_offload_sd.IOVQueuePairsRequested) + self.assertEqual(self.netutils._OFFLOAD_ENABLED, + mock_hw_offload_sd.VMQOffloadWeight) + self.assertEqual(offloaded_sa, + mock_hw_offload_sd.IPSecOffloadLimit) + @mock.patch.object(networkutils.NetworkUtils, '_get_setting_data_from_port_alloc') def test_get_profile_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 34cca848..bda47313 100644 --- a/os_win/tests/unit/utils/test_hostutils.py +++ b/os_win/tests/unit/utils/test_hostutils.py @@ -217,6 +217,62 @@ class HostUtilsTestCase(test_base.OsWinBaseTestCase): self._conn_scimv2.MSFT_NetAdapter.assert_called_once_with( InterfaceDescription=mock.sentinel.nic_name) + @mock.patch.object(hostutils.HostUtils, '_get_nic_hw_offload_info') + def test_get_nic_hardware_offload_info(self, mock_get_nic_offload): + mock_vswitch_sd = mock.Mock(VirtualSystemIdentifier=mock.sentinel.vsid) + mock_hw_offload_sd = mock.Mock(SystemName=mock.sentinel.vsid) + + vswitch_sds_class = self._conn.Msvm_VirtualEthernetSwitchSettingData + vswitch_sds_class.return_value = [mock_vswitch_sd] + hw_offload_class = self._conn.Msvm_EthernetSwitchHardwareOffloadData + hw_offload_class.return_value = [mock_hw_offload_sd] + + hw_offload_info = self._hostutils.get_nic_hardware_offload_info() + + self.assertEqual([mock_get_nic_offload.return_value], hw_offload_info) + vswitch_sds_class.assert_called_once_with() + hw_offload_class.assert_called_once_with() + mock_get_nic_offload.assert_called_once_with(mock_vswitch_sd, + mock_hw_offload_sd) + + def test_get_nic_hardware_offload_info_no_nic(self): + self._netutils.get_vswitch_external_network_name.return_value = None + mock_vswitch_sd = mock.Mock() + + hw_offload_info = self._hostutils._get_nic_hw_offload_info( + mock_vswitch_sd, mock.sentinel.hw_offload_sd) + + self.assertIsNone(hw_offload_info) + + @mock.patch.object(hostutils.LOG, 'warning') + def test_get_nic_hw_offload_info(self, mock_warning): + mock_vswitch_sd = mock.Mock() + mock_hw_offload_sd = mock.Mock(IovVfCapacity=0) + mock_nic = mock.Mock() + self._conn_scimv2.MSFT_NetAdapter.return_value = [mock_nic] + + hw_offload_info = self._hostutils._get_nic_hw_offload_info( + mock_vswitch_sd, mock_hw_offload_sd) + + expected = { + 'vswitch_name': mock_vswitch_sd.ElementName, + 'device_id': mock_nic.PnPDeviceID, + 'total_vfs': mock_hw_offload_sd.IovVfCapacity, + 'used_vfs': mock_hw_offload_sd.IovVfUsage, + 'total_iov_queue_pairs': mock_hw_offload_sd.IovQueuePairCapacity, + 'used_iov_queue_pairs': mock_hw_offload_sd.IovQueuePairUsage, + 'total_vmqs': mock_hw_offload_sd.VmqCapacity, + 'used_vmqs': mock_hw_offload_sd.VmqUsage, + 'total_ipsecsa': mock_hw_offload_sd.IPsecSACapacity, + 'used_ipsecsa': mock_hw_offload_sd.IPsecSAUsage, + } + self.assertEqual(expected, hw_offload_info) + get_ext_net_name = self._netutils.get_vswitch_external_network_name + get_ext_net_name.assert_called_once_with(mock_vswitch_sd.ElementName) + self.assertTrue(mock_warning.called) + self._conn_scimv2.MSFT_NetAdapter.assert_called_once_with( + InterfaceDescription=get_ext_net_name.return_value) + def _check_get_numa_nodes_missing_info(self): numa_node = mock.MagicMock() self._hostutils._conn.Msvm_NumaNode.return_value = [ diff --git a/os_win/utils/hostutils.py b/os_win/utils/hostutils.py index d90cf090..ea89bac6 100644 --- a/os_win/utils/hostutils.py +++ b/os_win/utils/hostutils.py @@ -193,6 +193,10 @@ class HostUtils(baseutils.BaseUtilsVirt): - 'used_vfs': the vSwitch's number of used VFs. (<= 'total_vfs') """ + # TODO(claudiub): We have added a different method that returns all + # of the offloading capabilities available, including SR-IOV. + # Remove this method in S. + vfs = [] # NOTE(claudiub): A vSwitch will have to be configured to enable @@ -229,6 +233,79 @@ class HostUtils(baseutils.BaseUtilsVirt): return vfs + def get_nic_hardware_offload_info(self): + """Get host's NIC hardware offload information. + + Hyper-V offers a few different hardware offloading options for VMs and + their vNICs, depending on the vSwitches' NICs hardware resources and + capabilities. These resources are managed and assigned automatically by + Hyper-V. These resources are: VFs, IOV queue pairs, VMQs, IPsec + security association offloads. + + :returns: a list of dictionaries, containing the following fields: + - 'vswitch_name': the switch name. + - 'device_id': the switch's physical NIC's PnP device ID. + - 'total_vfs': the switch's maximum number of VFs. (>= 0) + - 'used_vfs': the switch's number of used VFs. (<= 'total_vfs') + - 'total_iov_queue_pairs': the switch's maximum number of IOV + queue pairs. (>= 'total_vfs') + - 'used_iov_queue_pairs': the switch's number of used IOV queue + pairs (<= 'total_iov_queue_pairs') + - 'total_vmqs': the switch's maximum number of VMQs. (>= 0) + - 'used_vmqs': the switch's number of used VMQs. (<= 'total_vmqs') + - 'total_ipsecsa': the maximum number of IPsec SA offloads + supported by the switch. (>= 0) + - 'used_ipsecsa': the switch's number of IPsec SA offloads + currently in use. (<= 'total_ipsecsa') + """ + + hw_offload_data = [] + + vswitch_sds = self._conn.Msvm_VirtualEthernetSwitchSettingData() + hw_offload_sds = self._conn.Msvm_EthernetSwitchHardwareOffloadData() + for vswitch_sd in vswitch_sds: + hw_offload = [ + s for s in hw_offload_sds if + s.SystemName == vswitch_sd.VirtualSystemIdentifier][0] + + vswitch_offload_data = self._get_nic_hw_offload_info( + vswitch_sd, hw_offload) + if vswitch_offload_data: + hw_offload_data.append(vswitch_offload_data) + + return hw_offload_data + + def _get_nic_hw_offload_info(self, vswitch_sd, hw_offload_sd): + 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) + return + + # check if the vSwitch is misconfigured. + if vswitch_sd.IOVPreferred and not hw_offload_sd.IovVfCapacity: + LOG.warning("VSwitch %s has SR-IOV enabled, but it is not " + "supported by the NIC or by the OS.", + vswitch_sd.ElementName) + + nic = self._conn_scimv2.MSFT_NetAdapter( + InterfaceDescription=nic_name)[0] + + return { + 'vswitch_name': vswitch_sd.ElementName, + 'device_id': nic.PnPDeviceID, + 'total_vfs': hw_offload_sd.IovVfCapacity, + 'used_vfs': hw_offload_sd.IovVfUsage, + 'total_iov_queue_pairs': hw_offload_sd.IovQueuePairCapacity, + 'used_iov_queue_pairs': hw_offload_sd.IovQueuePairUsage, + 'total_vmqs': hw_offload_sd.VmqCapacity, + 'used_vmqs': hw_offload_sd.VmqUsage, + 'total_ipsecsa': hw_offload_sd.IPsecSACapacity, + 'used_ipsecsa': hw_offload_sd.IPsecSAUsage, + } + def get_numa_nodes(self): """Returns the host's list of NUMA nodes. diff --git a/os_win/utils/network/networkutils.py b/os_win/utils/network/networkutils.py index 03238eaa..f24638ac 100644 --- a/os_win/utils/network/networkutils.py +++ b/os_win/utils/network/networkutils.py @@ -73,8 +73,8 @@ class NetworkUtils(baseutils.BaseUtilsVirt): _VM_SUMMARY_ENABLED_STATE = 100 _HYPERV_VM_STATE_ENABLED = 2 - _IOV_ENABLED = 100 - _IOV_DISABLED = 0 + _OFFLOAD_ENABLED = 100 + _OFFLOAD_DISABLED = 0 _ACL_DIR_IN = 1 _ACL_DIR_OUT = 2 @@ -580,20 +580,72 @@ class NetworkUtils(baseutils.BaseUtilsVirt): enabled or disabled. :param enabled: boolean, if SR-IOV should be turned on or off. """ + # TODO(claudiub): We have added a different method that sets all sorts + # of offloading options on a vswitch port, including SR-IOV. + # Remove this method in S. + self.set_vswitch_port_offload(switch_port_name, sriov_enabled=enabled) + + def set_vswitch_port_offload(self, switch_port_name, sriov_enabled=None, + iov_queues_requested=None, vmq_enabled=None, + offloaded_sa=None): + """Enables / Disables different offload options for the given port. + + Optional prameters are ignored if they are None. + + :param switch_port_name: the name of the port which will have VMQ + enabled or disabled. + :param sriov_enabled: if SR-IOV should be turned on or off. + :param iov_queues_requested: the number of IOV queues to use. (> 1) + :param vmq_enabled: if VMQ should be turned on or off. + :param offloaded_sa: the number of IPsec SA offloads to use. (> 1) + :raises os_win.exceptions.InvalidParameterValue: if an invalid value + is passed for the iov_queues_requested or offloaded_sa parameters. + """ + + if iov_queues_requested is not None and iov_queues_requested < 1: + raise exceptions.InvalidParameterValue( + param_name='iov_queues_requested', + param_value=iov_queues_requested) + + if offloaded_sa is not None and offloaded_sa < 1: + raise exceptions.InvalidParameterValue( + param_name='offloaded_sa', + param_value=offloaded_sa) + 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 + sd_changed = False - hw_offload_sd.IOVOffloadWeight = desired_state + if sriov_enabled is not None: + desired_state = (self._OFFLOAD_ENABLED if sriov_enabled else + self._OFFLOAD_DISABLED) + if hw_offload_sd.IOVOffloadWeight != desired_state: + hw_offload_sd.IOVOffloadWeight = desired_state + sd_changed = True + + if iov_queues_requested is not None: + if hw_offload_sd.IOVQueuePairsRequested != iov_queues_requested: + hw_offload_sd.IOVQueuePairsRequested = iov_queues_requested + sd_changed = True + + if vmq_enabled is not None: + desired_state = (self._OFFLOAD_ENABLED if vmq_enabled else + self._OFFLOAD_DISABLED) + if hw_offload_sd.VMQOffloadWeight != desired_state: + hw_offload_sd.VMQOffloadWeight = desired_state + sd_changed = True + + if offloaded_sa is not None: + if hw_offload_sd.IPSecOffloadLimit != offloaded_sa: + hw_offload_sd.IPSecOffloadLimit = offloaded_sa + sd_changed = True # 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) + if sd_changed: + 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( diff --git a/releasenotes/notes/nic-hardware-offload-support-5912c91b2da93044.yaml b/releasenotes/notes/nic-hardware-offload-support-5912c91b2da93044.yaml new file mode 100644 index 00000000..3b52b145 --- /dev/null +++ b/releasenotes/notes/nic-hardware-offload-support-5912c91b2da93044.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Hyper-V can take advantage of different NIC hardware offload capabilities + and can assign different offloading resources to VM NICs (SR-IOV, IOV + queues, VMQs, IPSec). + hostutils can now retrieve all the different NIC hardware offloading + resources available to Hyper-V (depending on the physical NICs assigned). + networkutils can now enable / assign different NIC hardware offloading + resources to a given vSwitch port.