Skip to content

Commit 0f76472

Browse files
authored
Merge pull request #114 from stackhpc/upstream/yoga-2024-01-22
Synchronise yoga with upstream
2 parents 09e1ab0 + 37abd72 commit 0f76472

File tree

7 files changed

+241
-5
lines changed

7 files changed

+241
-5
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1344,7 +1344,8 @@ def sync_hostname_and_physical_networks(self, ctx):
13441344
LOG.debug('OVN-SB Sync hostname and physical networks started')
13451345
host_phynets_map = self.ovn_api.get_chassis_hostname_and_physnets()
13461346
current_hosts = set(host_phynets_map)
1347-
previous_hosts = segments_db.get_hosts_mapped_with_segments(ctx)
1347+
previous_hosts = segments_db.get_hosts_mapped_with_segments(
1348+
ctx, include_agent_types={ovn_const.OVN_CONTROLLER_AGENT})
13481349

13491350
stale_hosts = previous_hosts - current_hosts
13501351
for host in stale_hosts:

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,8 @@ def notify(self, event, row, updates=None):
666666
class BaseOvnSbIdl(Ml2OvnIdlBase):
667667
@classmethod
668668
def from_server(cls, connection_string, helper):
669+
if 'Chassis_Private' in helper.schema_json['tables']:
670+
helper.register_table('Chassis_Private')
669671
helper.register_table('Chassis')
670672
helper.register_table('Encap')
671673
helper.register_table('Port_Binding')

neutron/services/segments/db.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
from oslo_log import helpers as log_helpers
3131
from oslo_utils import uuidutils
3232

33+
from neutron.db.models import agent as agent_model
34+
from neutron.db.models import segment as segment_model
3335
from neutron.db import segments_db as db
3436
from neutron import manager
3537
from neutron.objects import base as base_obj
@@ -237,14 +239,43 @@ def update_segment_host_mapping(context, host, current_segment_ids):
237239
entry.delete()
238240

239241

240-
def get_hosts_mapped_with_segments(context):
242+
def get_hosts_mapped_with_segments(context, include_agent_types=None,
243+
exclude_agent_types=None):
241244
"""Get hosts that are mapped with segments.
242245
243246
L2 providers can use this method to get an overview of SegmentHostMapping,
244247
and then delete the stale SegmentHostMapping.
248+
249+
When using both include_agent_types and exclude_agent_types,
250+
exclude_agent_types is most significant.
251+
All hosts without agent are excluded when using any agent_type filter.
252+
253+
:param context: current running context information
254+
:param include_agent_types: (set) List of agent types, include hosts
255+
with matching agents.
256+
:param exclude_agent_types: (set) List of agent types, exclude hosts
257+
with matching agents.
245258
"""
246-
segment_host_mapping = network.SegmentHostMapping.get_objects(context)
247-
return {row.host for row in segment_host_mapping}
259+
def add_filter_by_agent_types(qry, include, exclude):
260+
qry = qry.join(
261+
agent_model.Agent,
262+
segment_model.SegmentHostMapping.host == agent_model.Agent.host)
263+
if include:
264+
qry = qry.filter(agent_model.Agent.agent_type.in_(include))
265+
if exclude:
266+
qry = qry.filter(agent_model.Agent.agent_type.not_in(exclude))
267+
268+
return qry
269+
270+
with db_api.CONTEXT_READER.using(context):
271+
query = context.session.query(segment_model.SegmentHostMapping)
272+
if include_agent_types or exclude_agent_types:
273+
query = add_filter_by_agent_types(query, include_agent_types,
274+
exclude_agent_types)
275+
276+
res = query.all()
277+
278+
return {row.host for row in res}
248279

249280

250281
def _get_phys_nets(agent):

neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_sync.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1794,6 +1794,22 @@ def setUp(self):
17941794
def _sync_resources(self):
17951795
self.sb_synchronizer.sync_hostname_and_physical_networks(self.ctx)
17961796

1797+
def create_agent(self, host, bridge_mappings=None, agent_type=None):
1798+
if agent_type is None:
1799+
agent_type = ovn_const.OVN_CONTROLLER_AGENT
1800+
if bridge_mappings is None:
1801+
bridge_mappings = {}
1802+
agent = {
1803+
'host': host,
1804+
'agent_type': agent_type,
1805+
'binary': '/bin/test',
1806+
'topic': 'test_topic',
1807+
'configurations': {'bridge_mappings': bridge_mappings}
1808+
}
1809+
_, status = self.plugin.create_or_update_agent(self.context, agent)
1810+
1811+
return status['id']
1812+
17971813
def create_segment(self, network_id, physical_network, segmentation_id):
17981814
segment_data = {'network_id': network_id,
17991815
'physical_network': physical_network,
@@ -1834,6 +1850,7 @@ def test_ovn_sb_sync_delete_stale_host(self):
18341850
segment = self.create_segment(network_id, 'physnet1', 50)
18351851
segments_db.update_segment_host_mapping(
18361852
self.ctx, 'host1', {segment['id']})
1853+
_ = self.create_agent('host1', bridge_mappings={'physnet1': 'eth0'})
18371854
segment_hosts = segments_db.get_hosts_mapped_with_segments(self.ctx)
18381855
self.assertEqual({'host1'}, segment_hosts)
18391856
# Since there is no chassis in the sb DB, host1 is the stale host
@@ -1842,6 +1859,36 @@ def test_ovn_sb_sync_delete_stale_host(self):
18421859
segment_hosts = segments_db.get_hosts_mapped_with_segments(self.ctx)
18431860
self.assertFalse(segment_hosts)
18441861

1862+
def test_ovn_sb_sync_host_with_no_agent_not_deleted(self):
1863+
with self.network() as network:
1864+
network_id = network['network']['id']
1865+
segment = self.create_segment(network_id, 'physnet1', 50)
1866+
segments_db.update_segment_host_mapping(
1867+
self.ctx, 'host1', {segment['id']})
1868+
_ = self.create_agent('host1', bridge_mappings={'physnet1': 'eth0'},
1869+
agent_type="Not OVN Agent")
1870+
segment_hosts = segments_db.get_hosts_mapped_with_segments(self.ctx)
1871+
self.assertEqual({'host1'}, segment_hosts)
1872+
# There is no chassis in the sb DB, host1 does not have an agent
1873+
# so it is not deleted.
1874+
self._sync_resources()
1875+
segment_hosts = segments_db.get_hosts_mapped_with_segments(self.ctx)
1876+
self.assertEqual({'host1'}, segment_hosts)
1877+
1878+
def test_ovn_sb_sync_host_with_other_agent_type_not_deleted(self):
1879+
with self.network() as network:
1880+
network_id = network['network']['id']
1881+
segment = self.create_segment(network_id, 'physnet1', 50)
1882+
segments_db.update_segment_host_mapping(
1883+
self.ctx, 'host1', {segment['id']})
1884+
segment_hosts = segments_db.get_hosts_mapped_with_segments(self.ctx)
1885+
self.assertEqual({'host1'}, segment_hosts)
1886+
# There is no chassis in the sb DB, host1 does not have an agent
1887+
# so it is not deleted.
1888+
self._sync_resources()
1889+
segment_hosts = segments_db.get_hosts_mapped_with_segments(self.ctx)
1890+
self.assertEqual({'host1'}, segment_hosts)
1891+
18451892
def test_ovn_sb_sync(self):
18461893
with self.network() as network:
18471894
network_id = network['network']['id']
@@ -1854,6 +1901,9 @@ def test_ovn_sb_sync(self):
18541901
segments_db.update_segment_host_mapping(
18551902
self.ctx, 'host3', {seg1['id']})
18561903
segment_hosts = segments_db.get_hosts_mapped_with_segments(self.ctx)
1904+
_ = self.create_agent('host1')
1905+
_ = self.create_agent('host2', bridge_mappings={'physnet2': 'eth0'})
1906+
_ = self.create_agent('host3', bridge_mappings={'physnet3': 'eth0'})
18571907
self.assertEqual({'host1', 'host2', 'host3'}, segment_hosts)
18581908
self.add_fake_chassis('host2', ['physnet2'])
18591909
self.add_fake_chassis('host3', ['physnet3'])

neutron/tests/unit/extensions/test_segment.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,10 +746,37 @@ def test_get_all_hosts_mapped_with_segments(self):
746746
actual_hosts = db.get_hosts_mapped_with_segments(ctx)
747747
self.assertEqual(hosts, actual_hosts)
748748

749+
def test_get_all_hosts_mapped_with_segments_agent_type_filter(self):
750+
ctx = context.get_admin_context()
751+
hosts = set()
752+
with self.network() as network:
753+
network_id = network['network']['id']
754+
for i in range(1, 3):
755+
host = "host%s" % i
756+
segment = self._test_create_segment(
757+
network_id=network_id, physical_network='physnet%s' % i,
758+
segmentation_id=200 + i, network_type=constants.TYPE_VLAN)
759+
db.update_segment_host_mapping(
760+
ctx, host, {segment['segment']['id']})
761+
hosts.add(host)
762+
763+
# Now they are 2 hosts with segment being mapped.
764+
# host1 does not have an agent
765+
# host2 does not have an agent
766+
# Any agent_type filter excludes hosts that does not have an agent
767+
actual_hosts = db.get_hosts_mapped_with_segments(
768+
ctx, exclude_agent_types={'fake-agent-type'})
769+
self.assertEqual(set(), actual_hosts)
770+
actual_hosts = db.get_hosts_mapped_with_segments(
771+
ctx, include_agent_types={'fake-agent-type'})
772+
self.assertEqual(set(), actual_hosts)
773+
749774

750775
class TestMl2HostSegmentMappingOVS(HostSegmentMappingTestCase):
751776
_mechanism_drivers = ['openvswitch', 'logger']
752777
mock_path = 'neutron.services.segments.db.update_segment_host_mapping'
778+
agent_type_a = constants.AGENT_TYPE_OVS
779+
agent_type_b = constants.AGENT_TYPE_LINUXBRIDGE
753780

754781
def test_new_agent(self):
755782
host = 'host1'
@@ -869,9 +896,118 @@ def test_agent_with_no_mappings(self, mock):
869896
self.assertFalse(segments_host_db)
870897
self.assertFalse(mock.mock_calls)
871898

899+
def test_get_all_hosts_mapped_with_segments(self):
900+
ctx = context.get_admin_context()
901+
hosts = set()
902+
with self.network() as network:
903+
network_id = network['network']['id']
904+
for i in range(1, 3):
905+
host = "host%s" % i
906+
segment = self._test_create_segment(
907+
network_id=network_id, physical_network='physnet%s' % i,
908+
segmentation_id=200 + i, network_type=constants.TYPE_VLAN)
909+
self._register_agent(host, mappings={'physnet%s' % i: 'br-eth-1'},
910+
plugin=self.plugin)
911+
db.update_segment_host_mapping(
912+
ctx, host, {segment['segment']['id']})
913+
hosts.add(host)
914+
915+
# Now they are 2 hosts with segment being mapped.
916+
actual_hosts = db.get_hosts_mapped_with_segments(ctx)
917+
self.assertEqual(hosts, actual_hosts)
918+
919+
def test_get_all_hosts_mapped_with_segments_agent_type_filters(self):
920+
ctx = context.get_admin_context()
921+
with self.network() as network:
922+
network_id = network['network']['id']
923+
for i in range(1, 3):
924+
host = "host%s" % i
925+
segment = self._test_create_segment(
926+
network_id=network_id, physical_network='physnet%s' % i,
927+
segmentation_id=200 + i, network_type=constants.TYPE_VLAN)
928+
if i == 2:
929+
agent_type = self.agent_type_a
930+
else:
931+
agent_type = self.agent_type_b
932+
helpers.register_ovs_agent(
933+
host, agent_type=agent_type,
934+
bridge_mappings={'physnet%s' % i: 'br-eth-1'},
935+
plugin=self.plugin, start_flag=True)
936+
db.update_segment_host_mapping(
937+
ctx, host, {segment['segment']['id']})
938+
939+
# Now they are 2 hosts with segment being mapped.
940+
# host1 is agent_type_b
941+
# host2 is agent_type_a
942+
# get all hosts (host1 and host2) when not using any filtering
943+
actual_hosts = db.get_hosts_mapped_with_segments(ctx)
944+
self.assertEqual({"host1", "host2"}, actual_hosts)
945+
# get host1 when exclude agent_type_a agents
946+
actual_hosts = db.get_hosts_mapped_with_segments(
947+
ctx, exclude_agent_types={self.agent_type_a})
948+
self.assertEqual({"host1"}, actual_hosts)
949+
# get host2 when exclude agent_type_b agents
950+
actual_hosts = db.get_hosts_mapped_with_segments(
951+
ctx, exclude_agent_types={self.agent_type_b})
952+
self.assertEqual({"host2"}, actual_hosts)
953+
# get host2 when include agent_type_a agents
954+
actual_hosts = db.get_hosts_mapped_with_segments(
955+
ctx, include_agent_types={self.agent_type_a})
956+
self.assertEqual({"host2"}, actual_hosts)
957+
# get host1 when include agent_type_b agents
958+
actual_hosts = db.get_hosts_mapped_with_segments(
959+
ctx, include_agent_types={self.agent_type_b})
960+
self.assertEqual({"host1"}, actual_hosts)
961+
# get host1 and host2 when include both agent_type_a and agent_type_b
962+
actual_hosts = db.get_hosts_mapped_with_segments(
963+
ctx, include_agent_types={self.agent_type_b, self.agent_type_a})
964+
self.assertEqual({"host1", "host2"}, actual_hosts)
965+
# When using both include and exclude, exclude is most significant
966+
actual_hosts = db.get_hosts_mapped_with_segments(
967+
ctx,
968+
include_agent_types={self.agent_type_b, self.agent_type_a},
969+
exclude_agent_types={self.agent_type_b}
970+
)
971+
self.assertEqual({"host2"}, actual_hosts)
972+
# include and exclude both agent types - exclude is most significant
973+
actual_hosts = db.get_hosts_mapped_with_segments(
974+
ctx,
975+
include_agent_types={self.agent_type_b, self.agent_type_a},
976+
exclude_agent_types={self.agent_type_b, self.agent_type_a}
977+
)
978+
self.assertEqual(set(), actual_hosts)
979+
980+
def test_get_all_hosts_mapped_with_segments_agent_type_filter(self):
981+
ctx = context.get_admin_context()
982+
hosts = set()
983+
with self.network() as network:
984+
network_id = network['network']['id']
985+
for i in range(1, 3):
986+
host = "host%s" % i
987+
segment = self._test_create_segment(
988+
network_id=network_id, physical_network='physnet%s' % i,
989+
segmentation_id=200 + i, network_type=constants.TYPE_VLAN)
990+
self._register_agent(host, mappings={'physnet%s' % i: 'br-eth-1'},
991+
plugin=self.plugin)
992+
db.update_segment_host_mapping(
993+
ctx, host, {segment['segment']['id']})
994+
hosts.add(host)
995+
996+
# Now they are 2 hosts with segment being mapped.
997+
# host1 is agent_type_a
998+
# host2 is agent_type_a
999+
actual_hosts = db.get_hosts_mapped_with_segments(
1000+
ctx, exclude_agent_types={self.agent_type_a})
1001+
self.assertEqual(set(), actual_hosts)
1002+
actual_hosts = db.get_hosts_mapped_with_segments(
1003+
ctx, include_agent_types={self.agent_type_a})
1004+
self.assertEqual(hosts, actual_hosts)
1005+
8721006

8731007
class TestMl2HostSegmentMappingLinuxBridge(TestMl2HostSegmentMappingOVS):
8741008
_mechanism_drivers = ['linuxbridge', 'logger']
1009+
agent_type_a = constants.AGENT_TYPE_LINUXBRIDGE
1010+
agent_type_b = constants.AGENT_TYPE_OVS
8751011

8761012
def _register_agent(self, host, mappings=None, plugin=None):
8771013
helpers.register_linuxbridge_agent(host=host,
@@ -881,6 +1017,8 @@ def _register_agent(self, host, mappings=None, plugin=None):
8811017

8821018
class TestMl2HostSegmentMappingMacvtap(TestMl2HostSegmentMappingOVS):
8831019
_mechanism_drivers = ['macvtap', 'logger']
1020+
agent_type_a = constants.AGENT_TYPE_MACVTAP
1021+
agent_type_b = constants.AGENT_TYPE_OVS
8841022

8851023
def _register_agent(self, host, mappings=None, plugin=None):
8861024
helpers.register_macvtap_agent(host=host, interface_mappings=mappings,
@@ -889,6 +1027,8 @@ def _register_agent(self, host, mappings=None, plugin=None):
8891027

8901028
class TestMl2HostSegmentMappingSriovNicSwitch(TestMl2HostSegmentMappingOVS):
8911029
_mechanism_drivers = ['sriovnicswitch', 'logger']
1030+
agent_type_a = constants.AGENT_TYPE_NIC_SWITCH
1031+
agent_type_b = constants.AGENT_TYPE_OVS
8921032

8931033
def _register_agent(self, host, mappings=None, plugin=None):
8941034
helpers.register_sriovnicswitch_agent(host=host,

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1169,8 +1169,10 @@ def test_ovn_sb_sync(self):
11691169

11701170
with mock.patch.object(ovn_db_sync.segments_db,
11711171
'get_hosts_mapped_with_segments',
1172-
return_value=hosts_in_neutron):
1172+
return_value=hosts_in_neutron) as mock_ghmws:
11731173
ovn_sb_synchronizer.sync_hostname_and_physical_networks(mock.ANY)
1174+
mock_ghmws.assert_called_once_with(
1175+
mock.ANY, include_agent_types={ovn_const.OVN_CONTROLLER_AGENT})
11741176
all_hosts = set(hostname_with_physnets.keys()) | hosts_in_neutron
11751177
self.assertEqual(
11761178
len(all_hosts),
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
fixes:
3+
- |
4+
When synchronizing the OVN databases, either when running the migration
5+
command or during startup, the code responsible for synchronization will
6+
only clean up segment-to-host mappings for hosts with agent_type
7+
``OVN Controller agent``. Before, the synchronization would clean up
8+
(delete) segment-to-host mappings for non-OVN hosts. Fixes bug:
9+
`2040172 <https://bugs.launchpad.net/neutron/+bug/2040172>`_.
10+

0 commit comments

Comments
 (0)