Merge "port-hint-ovs-tx-steering: agent side"

This commit is contained in:
Zuul 2023-05-22 12:23:16 +00:00 committed by Gerrit Code Review
commit de1a3a84b6
4 changed files with 160 additions and 1 deletions

View File

@ -384,6 +384,7 @@ class CacheBackedPluginApi(PluginApi):
'vnic_type': binding.vnic_type,
'security_groups': list(port_obj.security_group_ids),
'migrating_to': migrating_to,
'hints': port_obj.hints.hints if port_obj.hints else None,
}
LOG.debug("Returning: %s", entry)
return entry

View File

@ -83,6 +83,10 @@ cfg.CONF.import_group('OVS', 'neutron.plugins.ml2.drivers.openvswitch.agent.'
INIT_MAX_TRIES = 3
PORT_HINTS_TX_STEERING = 'tx-steering'
PORT_HINTS_TX_STEERING_HASH = 'hash'
PORT_HINTS_TX_STEERING_THREAD = 'thread'
class _mac_mydialect(netaddr.mac_unix):
word_fmt = '%.2x'
@ -1250,6 +1254,28 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
if self.prevent_arp_spoofing:
self.setup_arp_spoofing_protection(self.int_br,
port, port_detail)
# Apply port hints
try:
to_set = self.sanitize_ovs_iface_other_config(
port_detail['hints']['openvswitch']['other_config'])
self.int_br.set_db_attribute(
'Interface', port.port_name, 'other_config', to_set)
# Clear the config from ovs when we receive:
# * an empty hints attribute
# * an invalid hints value, for example:
# * invalid key: KeyError
# * anything that's not a dict in hints, like None: TypeError
except (KeyError, TypeError):
self.int_br.clear_db_attribute(
'Interface', port.port_name, 'other_config')
finally:
LOG.debug(
'port-hint-ovs-tx-steering: vif=%s other_config=%s',
port.port_name,
self.int_br.db_get_val(
'Interface', port.port_name, 'other_config')
)
if cur_tag != lvm.vlan:
ovsdb = self.int_br.ovsdb
with ovsdb.transaction() as txn:
@ -1302,6 +1328,27 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
{'up': devices_up, 'down': devices_down})
return set(failed_devices)
@staticmethod
def sanitize_ovs_iface_other_config(other_config):
'''Take an other_config dict meant for an ovs interface.
Return a filtered/sanitized version of it with only keys and values
we consider accepted. Log if the output differs from the input.
'''
output = {}
if PORT_HINTS_TX_STEERING in other_config:
if other_config[PORT_HINTS_TX_STEERING] in (
PORT_HINTS_TX_STEERING_HASH,
PORT_HINTS_TX_STEERING_THREAD):
output[PORT_HINTS_TX_STEERING] = other_config[
PORT_HINTS_TX_STEERING]
if other_config != output:
LOG.warning(
'Got unexpected other_config, using sanitized version!'
' got=%s sanitized=%s', other_config, output,
)
return output
@staticmethod
def setup_arp_spoofing_protection(bridge, vif, port_details):
if not port_details.get('port_security_enabled', True):

View File

@ -29,6 +29,7 @@ from oslo_utils import uuidutils
from neutron.agent import rpc
from neutron.objects import network
from neutron.objects.port.extensions import port_hints
from neutron.objects import ports
from neutron.tests import base
@ -221,7 +222,10 @@ class TestCacheBackedPluginApi(base.BaseTestCase):
host='host1',
level=0,
segment=self._segment)],
status='ACTIVE')
status='ACTIVE',
hints=port_hints.PortHints(hints={
"openvswitch": {"other_config": {"tx-steering": "hash"}}}),
)
def test__legacy_notifier_resource_delete(self):
self._api._legacy_notifier(resources.PORT, events.AFTER_DELETE, self,
@ -360,6 +364,15 @@ class TestCacheBackedPluginApi(base.BaseTestCase):
mock.ANY, 'host2')
self.assertEqual('host2', entry['migrating_to'])
def test_get_device_details_hints(self):
self._api.remote_resource_cache.get_resource_by_id.side_effect = [
self._port, self._network]
entry = self._api.get_device_details(
mock.ANY, self._port_id, mock.ANY, mock.ANY)
self.assertEqual(
{"openvswitch": {"other_config": {"tx-steering": "hash"}}},
entry['hints'])
@mock.patch('neutron.agent.resource_cache.RemoteResourceCache')
def test_initialization_with_default_resources(self, rcache_class):
rcache_obj = mock.MagicMock()

View File

@ -738,6 +738,78 @@ class TestOvsNeutronAgent(object):
mock.ANY, mock.ANY,
refresh_tunnels=True)
def test_bind_devices_hints_valid_hints(self):
self.agent.vlan_manager.mapping['net1']['seg1'] = mock.Mock()
ovs_db_list = [{'name': 'tap1', 'tag': []}]
vif_port1 = mock.Mock()
vif_port1.port_name = 'tap1'
port_details = [
{'network_id': 'net1',
'vif_port': vif_port1,
'segmentation_id': 'seg1',
'device': 'tap1',
'device_owner': 'network:dhcp',
'admin_state_up': True,
'hints': {
'openvswitch': {'other_config': {'tx-steering': 'hash'}}}},
]
with mock.patch.object(self.agent.plugin_rpc, 'update_device_list'), \
mock.patch.object(self.agent, 'int_br') as mock_int_br:
mock_int_br.get_ports_attributes.return_value = ovs_db_list
self.agent._bind_devices(port_details)
mock_int_br.set_db_attribute.assert_called_once_with(
'Interface',
vif_port1.port_name,
'other_config',
{'tx-steering': 'hash'})
def test_bind_devices_hints_no_hints(self):
self.agent.vlan_manager.mapping['net1']['seg1'] = mock.Mock()
ovs_db_list = [{'name': 'tap1', 'tag': []}]
vif_port1 = mock.Mock()
vif_port1.port_name = 'tap1'
port_details = [
{'network_id': 'net1',
'vif_port': vif_port1,
'segmentation_id': 'seg1',
'device': 'tap1',
'device_owner': 'network:dhcp',
'admin_state_up': True,
'hints': {}},
]
with mock.patch.object(self.agent.plugin_rpc, 'update_device_list'), \
mock.patch.object(self.agent, 'int_br') as mock_int_br:
mock_int_br.get_ports_attributes.return_value = ovs_db_list
self.agent._bind_devices(port_details)
mock_int_br.clear_db_attribute.assert_called_once_with(
'Interface',
vif_port1.port_name,
'other_config')
def test_bind_devices_hints_invalid_hints(self):
self.agent.vlan_manager.mapping['net1']['seg1'] = mock.Mock()
ovs_db_list = [{'name': 'tap1', 'tag': []}]
vif_port1 = mock.Mock()
vif_port1.port_name = 'tap1'
port_details = [
{'network_id': 'net1',
'vif_port': vif_port1,
'segmentation_id': 'seg1',
'device': 'tap1',
'device_owner': 'network:dhcp',
'admin_state_up': True,
'hints': {
'openvswitch': {'not-a-valid-key': {'tx-steering': 'hash'}}}},
]
with mock.patch.object(self.agent.plugin_rpc, 'update_device_list'), \
mock.patch.object(self.agent, 'int_br') as mock_int_br:
mock_int_br.get_ports_attributes.return_value = ovs_db_list
self.agent._bind_devices(port_details)
mock_int_br.clear_db_attribute.assert_called_once_with(
'Interface',
vif_port1.port_name,
'other_config')
def _test_bind_devices_sets_refresh_tunnels(self, tun_ofports, expected):
self.agent.iter_num = 3
self.agent.prevent_arp_spoofing = False
@ -2910,6 +2982,32 @@ class TestOvsNeutronAgent(object):
self.agent.create_smartnic_port_map_entry_data(mac, rep_port)
self.assertEqual(int_br_smartnic_port_map, expected_return_value)
def test_sanitize_ovs_iface_other_config(self):
self.assertEqual(
{},
self.agent.sanitize_ovs_iface_other_config({}),
)
self.assertEqual(
{"tx-steering": "hash"},
self.agent.sanitize_ovs_iface_other_config(
{"tx-steering": "hash"}),
)
self.assertEqual(
{"tx-steering": "thread"},
self.agent.sanitize_ovs_iface_other_config(
{"tx-steering": "thread"}),
)
self.assertEqual(
{},
self.agent.sanitize_ovs_iface_other_config(
{"tx-steering": "invalid"}),
)
self.assertEqual(
{},
self.agent.sanitize_ovs_iface_other_config(
{"invalid": "thread"}),
)
class TestOvsNeutronAgentOSKen(TestOvsNeutronAgent,
ovs_test_base.OVSOSKenTestBase):