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
|
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 ''
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
||||||
|
@ -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):
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user