Merge "Move `determine_bind_host
to
ovn.utils
`"
This commit is contained in:
commit
5d80a01520
@ -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 ''
|
||||
|
@ -952,8 +952,7 @@ 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,
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user