Skip to content

Commit d8679f2

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Move determine_bind_host to ovn.utils" into stable/2023.1
2 parents 240fed6 + 3c0e54f commit d8679f2

File tree

6 files changed

+144
-142
lines changed

6 files changed

+144
-142
lines changed

neutron/common/ovn/utils.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,3 +931,48 @@ def get_port_type_virtual_and_parents(subnets, fixed_ips, network_id, port_id,
931931
break
932932

933933
return port_type, virtual_ip, virtual_parents
934+
935+
936+
def determine_bind_host(sb_idl, port, port_context=None):
937+
"""Determine which host the port should be bound to.
938+
939+
Traditionally it has been Nova's responsibility to create Virtual
940+
Interfaces (VIFs) as part of instance life cycle, and subsequently
941+
manage plug/unplug operations on the Open vSwitch integration bridge.
942+
For the traditional topology the bind host will be the same as the
943+
hypervisor hosting the instance.
944+
945+
With the advent of SmartNIC DPUs which are connected to multiple
946+
distinct CPUs we can have a topology where the instance runs on one
947+
host and Open vSwitch and OVN runs on a different host, the SmartNIC
948+
DPU control plane CPU. In the SmartNIC DPU topology the bind host will
949+
be different than the hypervisor host.
950+
951+
This helper accepts both a port Dict and optionally a PortContext
952+
instance so that it can be used both before and after a port is bound.
953+
954+
:param sb_idl: OVN Southbound IDL
955+
:type sb_idl: ``OvsdbSbOvnIdl``
956+
:param port: Port Dictionary
957+
:type port: Dict[str,any]
958+
:param port_context: PortContext instance describing the port
959+
:type port_context: api.PortContext
960+
:returns: FQDN or Hostname to bind port to.
961+
:rtype: str
962+
:raises: n_exc.InvalidInput, RuntimeError
963+
"""
964+
# Note that we use port_context.host below when called from bind_port
965+
port = port_context.current if port_context else port
966+
vnic_type = port.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL)
967+
if vnic_type != portbindings.VNIC_REMOTE_MANAGED:
968+
# The ``PortContext`` ``host`` property contains handling of
969+
# special cases.
970+
return port_context.host if port_context else port.get(
971+
portbindings.HOST_ID, '')
972+
973+
bp_info = validate_and_get_data_from_binding_profile(port)
974+
if constants.VIF_DETAILS_CARD_SERIAL_NUMBER in bp_info.bp_param:
975+
return sb_idl.get_chassis_by_card_serial_from_cms_options(
976+
bp_info.bp_param[
977+
constants.VIF_DETAILS_CARD_SERIAL_NUMBER]).hostname
978+
return ''

neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -953,9 +953,8 @@ def bind_port(self, context):
953953
# we need to take into account, thus passing both the port Dict
954954
# and the PortContext instance so that the helper can decide
955955
# which to use.
956-
bind_host = self._ovn_client.determine_bind_host(
957-
port,
958-
port_context=context)
956+
bind_host = ovn_utils.determine_bind_host(self._sb_ovn, port,
957+
port_context=context)
959958
except n_exc.InvalidInput as e:
960959
# The port binding profile is validated both on port creation and
961960
# update. The new rules apply to a VNIC type previously not

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -232,49 +232,6 @@ def _get_port_dhcp_options(self, port, ip_version):
232232
external_ids=subnet_dhcp_options['external_ids'])
233233
return {'cmd': add_dhcp_opts_cmd}
234234

235-
def determine_bind_host(self, port, port_context=None):
236-
"""Determine which host the port should be bound to.
237-
238-
Traditionally it has been Nova's responsibility to create Virtual
239-
Interfaces (VIFs) as part of instance life cycle, and subsequently
240-
manage plug/unplug operations on the Open vSwitch integration bridge.
241-
For the traditional topology the bind host will be the same as the
242-
hypervisor hosting the instance.
243-
244-
With the advent of SmartNIC DPUs which are connected to multiple
245-
distinct CPUs we can have a topology where the instance runs on one
246-
host and Open vSwitch and OVN runs on a different host, the SmartNIC
247-
DPU control plane CPU. In the SmartNIC DPU topology the bind host will
248-
be different than the hypervisor host.
249-
250-
This helper accepts both a port Dict and optionally a PortContext
251-
instance so that it can be used both before and after a port is bound.
252-
253-
:param port: Port Dictionary
254-
:type port: Dict[str,any]
255-
:param port_context: PortContext instance describing the port
256-
:type port_context: api.PortContext
257-
:returns: FQDN or Hostname to bind port to.
258-
:rtype: str
259-
:raises: n_exc.InvalidInput, RuntimeError
260-
"""
261-
# Note that we use port_context.host below when called from bind_port
262-
port = port_context.current if port_context else port
263-
vnic_type = port.get(portbindings.VNIC_TYPE, portbindings.VNIC_NORMAL)
264-
if vnic_type != portbindings.VNIC_REMOTE_MANAGED:
265-
# The ``PortContext`` ``host`` property contains handling of
266-
# special cases.
267-
return port_context.host if port_context else port.get(
268-
portbindings.HOST_ID, '')
269-
270-
bp_info = (
271-
utils.validate_and_get_data_from_binding_profile(port))
272-
if ovn_const.VIF_DETAILS_CARD_SERIAL_NUMBER in bp_info.bp_param:
273-
return self._sb_idl.get_chassis_by_card_serial_from_cms_options(
274-
bp_info.bp_param[
275-
ovn_const.VIF_DETAILS_CARD_SERIAL_NUMBER]).hostname
276-
return ''
277-
278235
def update_lsp_host_info(self, context, db_port, up=True):
279236
"""Update the binding hosting information for the LSP.
280237
@@ -421,7 +378,7 @@ def _get_port_options(self, port):
421378
ovn_const.VIF_DETAILS_PF_MAC_ADDRESS)),
422379
ovn_const.LSP_OPTIONS_VIF_PLUG_REPRESENTOR_VF_NUM_KEY: str(
423380
bp_info.bp_param.get(ovn_const.VIF_DETAILS_VF_NUM))})
424-
chassis = self.determine_bind_host(port)
381+
chassis = utils.determine_bind_host(self._sb_idl, port)
425382
if chassis:
426383
# If OVN supports multi-chassis port bindings, use it for live
427384
# migration to asynchronously configure destination port while

neutron/tests/unit/common/ovn/test_utils.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,3 +1049,98 @@ def test_with_parents(self, *args):
10491049
self.assertEqual((constants.LSP_TYPE_VIRTUAL, '1.2.3.4',
10501050
'parent1,parent2'),
10511051
(port_type, virtual_ip, virtual_parents))
1052+
1053+
1054+
class DetermineBindHostTestCase(base.BaseTestCase):
1055+
1056+
def setUp(self):
1057+
super().setUp()
1058+
self.mock_sb_idl = mock.Mock()
1059+
self.get_chassis_by_card_serial_from_cms_options = (
1060+
self.mock_sb_idl.get_chassis_by_card_serial_from_cms_options)
1061+
self.fake_smartnic_hostname = 'fake-chassis-hostname'
1062+
self.get_chassis_by_card_serial_from_cms_options.return_value = (
1063+
fakes.FakeChassis.create(
1064+
attrs={'hostname': self.fake_smartnic_hostname}))
1065+
1066+
def test_vnic_normal_unbound_port(self):
1067+
self.assertEqual(
1068+
'',
1069+
utils.determine_bind_host(self.mock_sb_idl, {}))
1070+
1071+
def test_vnic_normal_bound_port(self):
1072+
port = {
1073+
portbindings.HOST_ID: 'fake-binding-host-id',
1074+
}
1075+
self.assertEqual(
1076+
'fake-binding-host-id',
1077+
utils.determine_bind_host(self.mock_sb_idl, port))
1078+
1079+
def test_vnic_normal_port_context(self):
1080+
context = mock.MagicMock()
1081+
context.host = 'fake-binding-host-id'
1082+
self.assertEqual(
1083+
'fake-binding-host-id',
1084+
utils.determine_bind_host(self.mock_sb_idl, {},
1085+
port_context=context))
1086+
1087+
def test_vnic_remote_managed_unbound_port_no_binding_profile(self):
1088+
port = {
1089+
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
1090+
constants.OVN_PORT_BINDING_PROFILE: {},
1091+
}
1092+
self.assertEqual(
1093+
'',
1094+
utils.determine_bind_host(self.mock_sb_idl, port))
1095+
1096+
def test_vnic_remote_managed_unbound_port(self):
1097+
port = {
1098+
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
1099+
constants.OVN_PORT_BINDING_PROFILE: {
1100+
constants.VIF_DETAILS_PCI_VENDOR_INFO: 'fake-pci-vendor-info',
1101+
constants.VIF_DETAILS_PCI_SLOT: 'fake-pci-slot',
1102+
constants.VIF_DETAILS_PHYSICAL_NETWORK: None,
1103+
constants.VIF_DETAILS_CARD_SERIAL_NUMBER: 'fake-serial',
1104+
constants.VIF_DETAILS_PF_MAC_ADDRESS: 'fake-pf-mac',
1105+
constants.VIF_DETAILS_VF_NUM: 42,
1106+
},
1107+
}
1108+
self.assertEqual(
1109+
self.fake_smartnic_hostname,
1110+
utils.determine_bind_host(self.mock_sb_idl, port))
1111+
1112+
def test_vnic_remote_managed_bound_port(self):
1113+
port = {
1114+
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
1115+
portbindings.HOST_ID: 'fake-binding-host-id',
1116+
constants.OVN_PORT_BINDING_PROFILE: {
1117+
constants.VIF_DETAILS_PCI_VENDOR_INFO: 'fake-pci-vendor-info',
1118+
constants.VIF_DETAILS_PCI_SLOT: 'fake-pci-slot',
1119+
constants.VIF_DETAILS_PHYSICAL_NETWORK: None,
1120+
constants.VIF_DETAILS_CARD_SERIAL_NUMBER: 'fake-serial',
1121+
constants.VIF_DETAILS_PF_MAC_ADDRESS: 'fake-pf-mac',
1122+
constants.VIF_DETAILS_VF_NUM: 42,
1123+
},
1124+
}
1125+
self.assertEqual(
1126+
self.fake_smartnic_hostname,
1127+
utils.determine_bind_host(self.mock_sb_idl, port))
1128+
1129+
def test_vnic_remote_managed_port_context(self):
1130+
context = mock.MagicMock()
1131+
context.current = {
1132+
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
1133+
constants.OVN_PORT_BINDING_PROFILE: {
1134+
constants.VIF_DETAILS_PCI_VENDOR_INFO: 'fake-pci-vendor-info',
1135+
constants.VIF_DETAILS_PCI_SLOT: 'fake-pci-slot',
1136+
constants.VIF_DETAILS_PHYSICAL_NETWORK: None,
1137+
constants.VIF_DETAILS_CARD_SERIAL_NUMBER: 'fake-serial',
1138+
constants.VIF_DETAILS_PF_MAC_ADDRESS: 'fake-pf-mac',
1139+
constants.VIF_DETAILS_VF_NUM: 42,
1140+
},
1141+
}
1142+
context.host = 'fake-binding-host-id'
1143+
self.assertEqual(
1144+
self.fake_smartnic_hostname,
1145+
utils.determine_bind_host(self.mock_sb_idl, {},
1146+
port_context=context))

neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_client.py

Lines changed: 0 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,9 @@
2020
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
2121
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
2222
from neutron.tests import base
23-
from neutron.tests.unit import fake_resources as fakes
2423
from neutron.tests.unit.services.logapi.drivers.ovn \
2524
import test_driver as test_log_driver
2625
from neutron_lib.api.definitions import l3
27-
from neutron_lib.api.definitions import portbindings
2826
from neutron_lib import constants as const
2927
from neutron_lib.services.logapi import constants as log_const
3028

@@ -128,98 +126,6 @@ def test_update_lsp_host_info_down(self):
128126
constants.OVN_HOST_ID_EXT_ID_KEY, if_exists=True)
129127

130128

131-
class TestOVNClientDetermineBindHost(TestOVNClientBase):
132-
133-
def setUp(self):
134-
super(TestOVNClientDetermineBindHost, self).setUp()
135-
self.get_chassis_by_card_serial_from_cms_options = (
136-
self.sb_idl.get_chassis_by_card_serial_from_cms_options)
137-
self.fake_smartnic_hostname = 'fake-chassis-hostname'
138-
self.get_chassis_by_card_serial_from_cms_options.return_value = (
139-
fakes.FakeChassis.create(
140-
attrs={'hostname': self.fake_smartnic_hostname}))
141-
142-
def test_vnic_normal_unbound_port(self):
143-
self.assertEqual(
144-
'',
145-
self.ovn_client.determine_bind_host({}))
146-
147-
def test_vnic_normal_bound_port(self):
148-
port = {
149-
portbindings.HOST_ID: 'fake-binding-host-id',
150-
}
151-
self.assertEqual(
152-
'fake-binding-host-id',
153-
self.ovn_client.determine_bind_host(port))
154-
155-
def test_vnic_normal_port_context(self):
156-
context = mock.MagicMock()
157-
context.host = 'fake-binding-host-id'
158-
self.assertEqual(
159-
'fake-binding-host-id',
160-
self.ovn_client.determine_bind_host({}, port_context=context))
161-
162-
def test_vnic_remote_managed_unbound_port_no_binding_profile(self):
163-
port = {
164-
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
165-
constants.OVN_PORT_BINDING_PROFILE: {},
166-
}
167-
self.assertEqual(
168-
'',
169-
self.ovn_client.determine_bind_host(port))
170-
171-
def test_vnic_remote_managed_unbound_port(self):
172-
port = {
173-
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
174-
constants.OVN_PORT_BINDING_PROFILE: {
175-
constants.VIF_DETAILS_PCI_VENDOR_INFO: 'fake-pci-vendor-info',
176-
constants.VIF_DETAILS_PCI_SLOT: 'fake-pci-slot',
177-
constants.VIF_DETAILS_PHYSICAL_NETWORK: None,
178-
constants.VIF_DETAILS_CARD_SERIAL_NUMBER: 'fake-serial',
179-
constants.VIF_DETAILS_PF_MAC_ADDRESS: 'fake-pf-mac',
180-
constants.VIF_DETAILS_VF_NUM: 42,
181-
},
182-
}
183-
self.assertEqual(
184-
self.fake_smartnic_hostname,
185-
self.ovn_client.determine_bind_host(port))
186-
187-
def test_vnic_remote_managed_bound_port(self):
188-
port = {
189-
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
190-
portbindings.HOST_ID: 'fake-binding-host-id',
191-
constants.OVN_PORT_BINDING_PROFILE: {
192-
constants.VIF_DETAILS_PCI_VENDOR_INFO: 'fake-pci-vendor-info',
193-
constants.VIF_DETAILS_PCI_SLOT: 'fake-pci-slot',
194-
constants.VIF_DETAILS_PHYSICAL_NETWORK: None,
195-
constants.VIF_DETAILS_CARD_SERIAL_NUMBER: 'fake-serial',
196-
constants.VIF_DETAILS_PF_MAC_ADDRESS: 'fake-pf-mac',
197-
constants.VIF_DETAILS_VF_NUM: 42,
198-
},
199-
}
200-
self.assertEqual(
201-
self.fake_smartnic_hostname,
202-
self.ovn_client.determine_bind_host(port))
203-
204-
def test_vnic_remote_managed_port_context(self):
205-
context = mock.MagicMock()
206-
context.current = {
207-
portbindings.VNIC_TYPE: portbindings.VNIC_REMOTE_MANAGED,
208-
constants.OVN_PORT_BINDING_PROFILE: {
209-
constants.VIF_DETAILS_PCI_VENDOR_INFO: 'fake-pci-vendor-info',
210-
constants.VIF_DETAILS_PCI_SLOT: 'fake-pci-slot',
211-
constants.VIF_DETAILS_PHYSICAL_NETWORK: None,
212-
constants.VIF_DETAILS_CARD_SERIAL_NUMBER: 'fake-serial',
213-
constants.VIF_DETAILS_PF_MAC_ADDRESS: 'fake-pf-mac',
214-
constants.VIF_DETAILS_VF_NUM: 42,
215-
},
216-
}
217-
context.host = 'fake-binding-host-id'
218-
self.assertEqual(
219-
self.fake_smartnic_hostname,
220-
self.ovn_client.determine_bind_host({}, port_context=context))
221-
222-
223129
class TestOVNClientFairMeter(TestOVNClientBase,
224130
test_log_driver.TestOVNDriverBase):
225131

neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4344,7 +4344,7 @@ def setUp(self):
43444344
self.fmt, {'network': self.net},
43454345
'10.0.0.1', '10.0.0.0/24')['subnet']
43464346

4347-
@mock.patch.object(ovn_client.OVNClient, 'determine_bind_host')
4347+
@mock.patch.object(ovn_utils, 'determine_bind_host')
43484348
def test_create_port_with_virtual_type_and_options(self, *args):
43494349
fake_parents = ['parent-0', 'parent-1']
43504350
self.mock_vp_parents.return_value = fake_parents

0 commit comments

Comments
 (0)