Merge "Move `determine_bind_host to ovn.utils`"

This commit is contained in:
Zuul 2023-06-02 12:47:28 +00:00 committed by Gerrit Code Review
commit 5d80a01520
6 changed files with 144 additions and 142 deletions

View File

@ -1006,3 +1006,48 @@ def get_port_type_virtual_and_parents(subnets, fixed_ips, network_id, port_id,
break
return port_type, virtual_ip, virtual_parents
def determine_bind_host(sb_idl, port, port_context=None):
"""Determine which host the port should be bound to.
Traditionally it has been Nova's responsibility to create Virtual
Interfaces (VIFs) as part of instance life cycle, and subsequently
manage plug/unplug operations on the Open vSwitch integration bridge.
For the traditional topology the bind host will be the same as the
hypervisor hosting the instance.
With the advent of SmartNIC DPUs which are connected to multiple
distinct CPUs we can have a topology where the instance runs on one
host and Open vSwitch and OVN runs on a different host, the SmartNIC
DPU control plane CPU. In the SmartNIC DPU topology the bind host will
be different than the hypervisor host.
This helper accepts both a port Dict and optionally a PortContext
instance so that it can be used both before and after a port is bound.
:param sb_idl: OVN Southbound IDL
:type sb_idl: ``OvsdbSbOvnIdl``
:param port: Port Dictionary
:type port: Dict[str,any]
:param port_context: PortContext instance describing the port
:type port_context: api.PortContext
:returns: FQDN or Hostname to bind port to.
:rtype: str
:raises: n_exc.InvalidInput, RuntimeError
"""
# Note that we use port_context.host below when called from bind_port
port = port_context.current if port_context else port
vnic_type = port.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL)
if vnic_type != portbindings.VNIC_REMOTE_MANAGED:
# The ``PortContext`` ``host`` property contains handling of
# special cases.
return port_context.host if port_context else port.get(
portbindings.HOST_ID, '')
bp_info = validate_and_get_data_from_binding_profile(port)
if constants.VIF_DETAILS_CARD_SERIAL_NUMBER in bp_info.bp_param:
return sb_idl.get_chassis_by_card_serial_from_cms_options(
bp_info.bp_param[
constants.VIF_DETAILS_CARD_SERIAL_NUMBER]).hostname
return ''

View File

@ -952,9 +952,8 @@ class OVNMechanismDriver(api.MechanismDriver):
# we need to take into account, thus passing both the port Dict
# and the PortContext instance so that the helper can decide
# which to use.
bind_host = self._ovn_client.determine_bind_host(
port,
port_context=context)
bind_host = ovn_utils.determine_bind_host(self._sb_ovn, port,
port_context=context)
except n_exc.InvalidInput as e:
# The port binding profile is validated both on port creation and
# update. The new rules apply to a VNIC type previously not

View File

@ -230,49 +230,6 @@ class OVNClient(object):
external_ids=subnet_dhcp_options['external_ids'])
return {'cmd': add_dhcp_opts_cmd}
def determine_bind_host(self, port, port_context=None):
"""Determine which host the port should be bound to.
Traditionally it has been Nova's responsibility to create Virtual
Interfaces (VIFs) as part of instance life cycle, and subsequently
manage plug/unplug operations on the Open vSwitch integration bridge.
For the traditional topology the bind host will be the same as the
hypervisor hosting the instance.
With the advent of SmartNIC DPUs which are connected to multiple
distinct CPUs we can have a topology where the instance runs on one
host and Open vSwitch and OVN runs on a different host, the SmartNIC
DPU control plane CPU. In the SmartNIC DPU topology the bind host will
be different than the hypervisor host.
This helper accepts both a port Dict and optionally a PortContext
instance so that it can be used both before and after a port is bound.
:param port: Port Dictionary
:type port: Dict[str,any]
:param port_context: PortContext instance describing the port
:type port_context: api.PortContext
:returns: FQDN or Hostname to bind port to.
:rtype: str
:raises: n_exc.InvalidInput, RuntimeError
"""
# Note that we use port_context.host below when called from bind_port
port = port_context.current if port_context else port
vnic_type = port.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL)
if vnic_type != portbindings.VNIC_REMOTE_MANAGED:
# The ``PortContext`` ``host`` property contains handling of
# special cases.
return port_context.host if port_context else port.get(
portbindings.HOST_ID, '')
bp_info = (
utils.validate_and_get_data_from_binding_profile(port))
if ovn_const.VIF_DETAILS_CARD_SERIAL_NUMBER in bp_info.bp_param:
return self._sb_idl.get_chassis_by_card_serial_from_cms_options(
bp_info.bp_param[
ovn_const.VIF_DETAILS_CARD_SERIAL_NUMBER]).hostname
return ''
def _get_port_options(self, port):
context = n_context.get_admin_context()
bp_info = utils.validate_and_get_data_from_binding_profile(port)
@ -389,7 +346,7 @@ class OVNClient(object):
ovn_const.VIF_DETAILS_PF_MAC_ADDRESS)),
ovn_const.LSP_OPTIONS_VIF_PLUG_REPRESENTOR_VF_NUM_KEY: str(
bp_info.bp_param.get(ovn_const.VIF_DETAILS_VF_NUM))})
chassis = self.determine_bind_host(port)
chassis = utils.determine_bind_host(self._sb_idl, port)
if chassis:
# If OVN supports multi-chassis port bindings, use it for live
# migration to asynchronously configure destination port while

View File

@ -1049,3 +1049,98 @@ class GetPortTypeVirtualAndParentsTestCase(base.BaseTestCase):
self.assertEqual((constants.LSP_TYPE_VIRTUAL, '1.2.3.4',
'parent1,parent2'),
(port_type, virtual_ip, virtual_parents))
class DetermineBindHostTestCase(base.BaseTestCase):
def setUp(self):
super().setUp()
self.mock_sb_idl = mock.Mock()
self.get_chassis_by_card_serial_from_cms_options = (
self.mock_sb_idl.get_chassis_by_card_serial_from_cms_options)
self.fake_smartnic_hostname = 'fake-chassis-hostname'
self.get_chassis_by_card_serial_from_cms_options.return_value = (
fakes.FakeChassis.create(
attrs={'hostname': self.fake_smartnic_hostname}))
def test_vnic_normal_unbound_port(self):
self.assertEqual(
'',
utils.determine_bind_host(self.mock_sb_idl, {}))
def test_vnic_normal_bound_port(self):
port = {
portbindings.HOST_ID: 'fake-binding-host-id',
}
self.assertEqual(
'fake-binding-host-id',
utils.determine_bind_host(self.mock_sb_idl, port))
def test_vnic_normal_port_context(self):
context = mock.MagicMock()
context.host = 'fake-binding-host-id'
self.assertEqual(
'fake-binding-host-id',
utils.determine_bind_host(self.mock_sb_idl, {},
port_context=context))
def test_vnic_remote_managed_unbound_port_no_binding_profile(self):
port = {
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
constants.OVN_PORT_BINDING_PROFILE: {},
}
self.assertEqual(
'',
utils.determine_bind_host(self.mock_sb_idl, port))
def test_vnic_remote_managed_unbound_port(self):
port = {
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
constants.OVN_PORT_BINDING_PROFILE: {
constants.VIF_DETAILS_PCI_VENDOR_INFO: 'fake-pci-vendor-info',
constants.VIF_DETAILS_PCI_SLOT: 'fake-pci-slot',
constants.VIF_DETAILS_PHYSICAL_NETWORK: None,
constants.VIF_DETAILS_CARD_SERIAL_NUMBER: 'fake-serial',
constants.VIF_DETAILS_PF_MAC_ADDRESS: 'fake-pf-mac',
constants.VIF_DETAILS_VF_NUM: 42,
},
}
self.assertEqual(
self.fake_smartnic_hostname,
utils.determine_bind_host(self.mock_sb_idl, port))
def test_vnic_remote_managed_bound_port(self):
port = {
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
portbindings.HOST_ID: 'fake-binding-host-id',
constants.OVN_PORT_BINDING_PROFILE: {
constants.VIF_DETAILS_PCI_VENDOR_INFO: 'fake-pci-vendor-info',
constants.VIF_DETAILS_PCI_SLOT: 'fake-pci-slot',
constants.VIF_DETAILS_PHYSICAL_NETWORK: None,
constants.VIF_DETAILS_CARD_SERIAL_NUMBER: 'fake-serial',
constants.VIF_DETAILS_PF_MAC_ADDRESS: 'fake-pf-mac',
constants.VIF_DETAILS_VF_NUM: 42,
},
}
self.assertEqual(
self.fake_smartnic_hostname,
utils.determine_bind_host(self.mock_sb_idl, port))
def test_vnic_remote_managed_port_context(self):
context = mock.MagicMock()
context.current = {
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
constants.OVN_PORT_BINDING_PROFILE: {
constants.VIF_DETAILS_PCI_VENDOR_INFO: 'fake-pci-vendor-info',
constants.VIF_DETAILS_PCI_SLOT: 'fake-pci-slot',
constants.VIF_DETAILS_PHYSICAL_NETWORK: None,
constants.VIF_DETAILS_CARD_SERIAL_NUMBER: 'fake-serial',
constants.VIF_DETAILS_PF_MAC_ADDRESS: 'fake-pf-mac',
constants.VIF_DETAILS_VF_NUM: 42,
},
}
context.host = 'fake-binding-host-id'
self.assertEqual(
self.fake_smartnic_hostname,
utils.determine_bind_host(self.mock_sb_idl, {},
port_context=context))

View File

@ -20,11 +20,9 @@ from neutron.conf.plugins.ml2 import config as ml2_conf
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
from neutron.tests import base
from neutron.tests.unit import fake_resources as fakes
from neutron.tests.unit.services.logapi.drivers.ovn \
import test_driver as test_log_driver
from neutron_lib.api.definitions import l3
from neutron_lib.api.definitions import portbindings
from neutron_lib import constants as const
from neutron_lib.services.logapi import constants as log_const
@ -106,98 +104,6 @@ class TestOVNClient(TestOVNClientBase):
self.nb_idl.add_static_route.assert_not_called()
class TestOVNClientDetermineBindHost(TestOVNClientBase):
def setUp(self):
super(TestOVNClientDetermineBindHost, self).setUp()
self.get_chassis_by_card_serial_from_cms_options = (
self.sb_idl.get_chassis_by_card_serial_from_cms_options)
self.fake_smartnic_hostname = 'fake-chassis-hostname'
self.get_chassis_by_card_serial_from_cms_options.return_value = (
fakes.FakeChassis.create(
attrs={'hostname': self.fake_smartnic_hostname}))
def test_vnic_normal_unbound_port(self):
self.assertEqual(
'',
self.ovn_client.determine_bind_host({}))
def test_vnic_normal_bound_port(self):
port = {
portbindings.HOST_ID: 'fake-binding-host-id',
}
self.assertEqual(
'fake-binding-host-id',
self.ovn_client.determine_bind_host(port))
def test_vnic_normal_port_context(self):
context = mock.MagicMock()
context.host = 'fake-binding-host-id'
self.assertEqual(
'fake-binding-host-id',
self.ovn_client.determine_bind_host({}, port_context=context))
def test_vnic_remote_managed_unbound_port_no_binding_profile(self):
port = {
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
constants.OVN_PORT_BINDING_PROFILE: {},
}
self.assertEqual(
'',
self.ovn_client.determine_bind_host(port))
def test_vnic_remote_managed_unbound_port(self):
port = {
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
constants.OVN_PORT_BINDING_PROFILE: {
constants.VIF_DETAILS_PCI_VENDOR_INFO: 'fake-pci-vendor-info',
constants.VIF_DETAILS_PCI_SLOT: 'fake-pci-slot',
constants.VIF_DETAILS_PHYSICAL_NETWORK: None,
constants.VIF_DETAILS_CARD_SERIAL_NUMBER: 'fake-serial',
constants.VIF_DETAILS_PF_MAC_ADDRESS: 'fake-pf-mac',
constants.VIF_DETAILS_VF_NUM: 42,
},
}
self.assertEqual(
self.fake_smartnic_hostname,
self.ovn_client.determine_bind_host(port))
def test_vnic_remote_managed_bound_port(self):
port = {
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
portbindings.HOST_ID: 'fake-binding-host-id',
constants.OVN_PORT_BINDING_PROFILE: {
constants.VIF_DETAILS_PCI_VENDOR_INFO: 'fake-pci-vendor-info',
constants.VIF_DETAILS_PCI_SLOT: 'fake-pci-slot',
constants.VIF_DETAILS_PHYSICAL_NETWORK: None,
constants.VIF_DETAILS_CARD_SERIAL_NUMBER: 'fake-serial',
constants.VIF_DETAILS_PF_MAC_ADDRESS: 'fake-pf-mac',
constants.VIF_DETAILS_VF_NUM: 42,
},
}
self.assertEqual(
self.fake_smartnic_hostname,
self.ovn_client.determine_bind_host(port))
def test_vnic_remote_managed_port_context(self):
context = mock.MagicMock()
context.current = {
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
constants.OVN_PORT_BINDING_PROFILE: {
constants.VIF_DETAILS_PCI_VENDOR_INFO: 'fake-pci-vendor-info',
constants.VIF_DETAILS_PCI_SLOT: 'fake-pci-slot',
constants.VIF_DETAILS_PHYSICAL_NETWORK: None,
constants.VIF_DETAILS_CARD_SERIAL_NUMBER: 'fake-serial',
constants.VIF_DETAILS_PF_MAC_ADDRESS: 'fake-pf-mac',
constants.VIF_DETAILS_VF_NUM: 42,
},
}
context.host = 'fake-binding-host-id'
self.assertEqual(
self.fake_smartnic_hostname,
self.ovn_client.determine_bind_host({}, port_context=context))
class TestOVNClientFairMeter(TestOVNClientBase,
test_log_driver.TestOVNDriverBase):

View File

@ -4347,7 +4347,7 @@ class TestOVNVVirtualPort(OVNMechanismDriverTestCase):
self.fmt, {'network': self.net},
'10.0.0.1', '10.0.0.0/24')['subnet']
@mock.patch.object(ovn_client.OVNClient, 'determine_bind_host')
@mock.patch.object(ovn_utils, 'determine_bind_host')
def test_create_port_with_virtual_type_and_options(self, *args):
fake_parents = ['parent-0', 'parent-1']
self.mock_vp_parents.return_value = fake_parents