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 break
return port_type, virtual_ip, virtual_parents 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 # we need to take into account, thus passing both the port Dict
# and the PortContext instance so that the helper can decide # and the PortContext instance so that the helper can decide
# which to use. # which to use.
bind_host = self._ovn_client.determine_bind_host( bind_host = ovn_utils.determine_bind_host(self._sb_ovn, port,
port, port_context=context)
port_context=context)
except n_exc.InvalidInput as e: except n_exc.InvalidInput as e:
# The port binding profile is validated both on port creation and # The port binding profile is validated both on port creation and
# update. The new rules apply to a VNIC type previously not # 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']) external_ids=subnet_dhcp_options['external_ids'])
return {'cmd': add_dhcp_opts_cmd} 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): def _get_port_options(self, port):
context = n_context.get_admin_context() context = n_context.get_admin_context()
bp_info = utils.validate_and_get_data_from_binding_profile(port) 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.VIF_DETAILS_PF_MAC_ADDRESS)),
ovn_const.LSP_OPTIONS_VIF_PLUG_REPRESENTOR_VF_NUM_KEY: str( ovn_const.LSP_OPTIONS_VIF_PLUG_REPRESENTOR_VF_NUM_KEY: str(
bp_info.bp_param.get(ovn_const.VIF_DETAILS_VF_NUM))}) 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 chassis:
# If OVN supports multi-chassis port bindings, use it for live # If OVN supports multi-chassis port bindings, use it for live
# migration to asynchronously configure destination port while # 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', self.assertEqual((constants.LSP_TYPE_VIRTUAL, '1.2.3.4',
'parent1,parent2'), 'parent1,parent2'),
(port_type, virtual_ip, virtual_parents)) (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.conf.plugins.ml2.drivers.ovn import ovn_conf
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
from neutron.tests import base from neutron.tests import base
from neutron.tests.unit import fake_resources as fakes
from neutron.tests.unit.services.logapi.drivers.ovn \ from neutron.tests.unit.services.logapi.drivers.ovn \
import test_driver as test_log_driver import test_driver as test_log_driver
from neutron_lib.api.definitions import l3 from neutron_lib.api.definitions import l3
from neutron_lib.api.definitions import portbindings
from neutron_lib import constants as const from neutron_lib import constants as const
from neutron_lib.services.logapi import constants as log_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() 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, class TestOVNClientFairMeter(TestOVNClientBase,
test_log_driver.TestOVNDriverBase): test_log_driver.TestOVNDriverBase):

View File

@ -4347,7 +4347,7 @@ class TestOVNVVirtualPort(OVNMechanismDriverTestCase):
self.fmt, {'network': self.net}, self.fmt, {'network': self.net},
'10.0.0.1', '10.0.0.0/24')['subnet'] '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): def test_create_port_with_virtual_type_and_options(self, *args):
fake_parents = ['parent-0', 'parent-1'] fake_parents = ['parent-0', 'parent-1']
self.mock_vp_parents.return_value = fake_parents self.mock_vp_parents.return_value = fake_parents