diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py index df27972f45e..9bdbf07f9ac 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py @@ -1473,11 +1473,13 @@ class OVNClient(object): """Return chassis for scheduling gateway router. Criteria for selecting chassis as candidates - 1) chassis from cms with proper bridge mappings - 2) if no chassis is available from 1) then, - select chassis with proper bridge mappings - 3) Filter the available chassis accordingly to the routers + 1) Chassis from cms with proper bridge mappings only (that means these + gateway chassis with the requested physical network). + 2) Filter the available chassis accordingly to the routers availability zone hints (if present) + + If the logical router port belongs to a tunnelled network, there won't + be any candidate. """ # TODO(lucasagomes): Simplify the logic here, the CMS option has # been introduced long ago and by now all gateway chassis should @@ -1486,15 +1488,13 @@ class OVNClient(object): cms = cms or self._sb_idl.get_gateway_chassis_from_cms_options() chassis_physnets = (chassis_physnets or self._sb_idl.get_chassis_and_physnets()) - cms_bmaps = [] - bmaps = [] + candidates = set() for chassis, physnets in chassis_physnets.items(): - if physnet and physnet in physnets: - if chassis in cms: - cms_bmaps.append(chassis) - else: - bmaps.append(chassis) - candidates = cms_bmaps or bmaps or cms + if (physnet and + physnet in physnets and + chassis in cms): + candidates.add(chassis) + candidates = list(candidates) # Filter for availability zones if availability_zone_hints: @@ -1505,11 +1505,8 @@ class OVNClient(object): if az in utils.get_chassis_availability_zones( self._sb_idl.lookup('Chassis', ch, None))] - if not cms_bmaps: - LOG.debug("No eligible chassis with external connectivity" - " through ovn-cms-options for %s", physnet) - LOG.debug("Chassis candidates for scheduling gateway router ports: %s", - candidates) + LOG.debug('Chassis candidates for scheduling gateway router ports ' + 'for "%s" physical network: %s', physnet, candidates) return candidates def _get_physnet(self, network): diff --git a/neutron/tests/functional/services/ovn_l3/test_plugin.py b/neutron/tests/functional/services/ovn_l3/test_plugin.py index fbaa0b8aded..4f4cd90c781 100644 --- a/neutron/tests/functional/services/ovn_l3/test_plugin.py +++ b/neutron/tests/functional/services/ovn_l3/test_plugin.py @@ -32,12 +32,14 @@ from neutron.tests.functional.resources.ovsdb import events class TestRouter(base.TestOVNFunctionalBase): - def setUp(self): - super(TestRouter, self).setUp() + def setUp(self, **kwargs): + super().setUp(**kwargs) self.chassis1 = self.add_fake_chassis( - 'ovs-host1', physical_nets=['physnet1', 'physnet3']) + 'ovs-host1', physical_nets=['physnet1', 'physnet3'], + enable_chassis_as_gw=True, azs=[]) self.chassis2 = self.add_fake_chassis( - 'ovs-host2', physical_nets=['physnet2', 'physnet3']) + 'ovs-host2', physical_nets=['physnet2', 'physnet3'], + enable_chassis_as_gw=True, azs=[]) self.cr_lrp_pb_event = events.WaitForCrLrpPortBindingEvent() self.sb_api.idl.notify_handler.watch_event(self.cr_lrp_pb_event) @@ -91,12 +93,14 @@ class TestRouter(base.TestOVNFunctionalBase): self.assertCountEqual(expected, chassis) def _check_gateway_chassis_candidates(self, candidates, - router_az_hints=None): + router_az_hints=None, + physnet='physnet1'): # In this test, fake_select() is called once from _create_router() # and later from schedule_unhosted_gateways() ovn_client = self.l3_plugin._ovn_client + net_type = 'vlan' if physnet else 'geneve' ext1 = self._create_ext_network( - 'ext1', 'vlan', 'physnet1', 1, "10.0.0.1", "10.0.0.0/24") + 'ext1', net_type, physnet, 1, "10.0.0.1", "10.0.0.0/24") # mock select function and check if it is called with expected # candidates. @@ -127,12 +131,11 @@ class TestRouter(base.TestOVNFunctionalBase): def test_gateway_chassis_with_cms_and_bridge_mappings(self): # Both chassis1 and chassis3 are having proper bridge mappings, - # but only chassis3 is having enable-chassis-as-gw. - # Test if chassis3 is selected as candidate or not. + # but only chassis1 is having enable-chassis-as-gw. + # Test if chassis1 is selected as candidate or not. self.chassis3 = self.add_fake_chassis( - 'ovs-host3', physical_nets=['physnet1'], - other_config={'ovn-cms-options': 'enable-chassis-as-gw'}) - self._check_gateway_chassis_candidates([self.chassis3]) + 'ovs-host3', physical_nets=['physnet1'], azs=[]) + self._check_gateway_chassis_candidates([self.chassis1]) def test_gateway_chassis_with_cms_and_no_bridge_mappings(self): # chassis1 is having proper bridge mappings. @@ -166,12 +169,10 @@ class TestRouter(base.TestOVNFunctionalBase): # Test if chassis3 is selected as candidate or not. self.chassis3 = self.add_fake_chassis( 'ovs-host3', physical_nets=['physnet1'], - other_config={'ovn-cms-options': 'enable-chassis-as-gw'}, - azs=['ovn']) + azs=['ovn'], enable_chassis_as_gw=True) self.chassis4 = self.add_fake_chassis( 'ovs-host4', physical_nets=['physnet1'], - other_config={'ovn-cms-options': 'enable-chassis-as-gw'}, - azs=['ovn2']) + azs=['ovn2'], enable_chassis_as_gw=True) self._check_gateway_chassis_candidates([self.chassis3], router_az_hints=['ovn']) @@ -181,11 +182,9 @@ class TestRouter(base.TestOVNFunctionalBase): # AvailabilityZoneNotFound. after create will delete if. # add chassis4 is having azs [ovn2], not match routers az_hints [ovn] self.chassis3 = self.add_fake_chassis( - 'ovs-host3', physical_nets=['physnet1'], - other_config={'ovn-cms-options': 'enable-chassis-as-gw'}) + 'ovs-host3', physical_nets=['physnet1'], enable_chassis_as_gw=True) self.chassis4 = self.add_fake_chassis( - 'ovs-host4', physical_nets=['physnet1'], - other_config={'ovn-cms-options': 'enable-chassis-as-gw'}, + 'ovs-host4', physical_nets=['physnet1'], enable_chassis_as_gw=True, azs=['ovn2']) ovn_client = self.l3_plugin._ovn_client ext1 = self._create_ext_network( @@ -213,6 +212,11 @@ class TestRouter(base.TestOVNFunctionalBase): # Test if chassis1 is selected as candidate or not. self._check_gateway_chassis_candidates([self.chassis1]) + def test_gateway_chassis_no_physnet_tunnelled_network(self): + # The GW network is tunnelled, no physnet defined --> no possible + # candidates. + self._check_gateway_chassis_candidates([], physnet=None) + def test_gateway_chassis_least_loaded_scheduler(self): # This test will create 4 routers each with its own gateway. # Using the least loaded policy for scheduling gateway ports, we diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py index e1782669263..6953b091edb 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py @@ -2635,24 +2635,27 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase): return ch ovn_client._sb_idl.lookup = fake_lookup - # The target physnet and availability zones - physnet = 'public' - az_hints = ['az0', 'az2'] - + # List of chassis and chassis:physnet mappings. + physnet_name = 'public' cms = [ch0.name, ch1.name, ch2.name, ch3.name, ch4.name, ch5.name] - ch_physnet = {ch0.name: [physnet], ch1.name: [physnet], - ch2.name: [physnet], ch3.name: [physnet], + ch_physnet = {ch0.name: [physnet_name], ch1.name: [physnet_name], + ch2.name: [physnet_name], ch3.name: [physnet_name], ch4.name: ['another-physnet'], ch5.name: ['yet-another-physnet']} - candidates = ovn_client.get_candidates_for_scheduling( - physnet, cms=cms, chassis_physnets=ch_physnet, - availability_zone_hints=az_hints) - - # Only chassis ch0 and ch2 should match the availability zones - # hints and physnet we passed to get_candidates_for_scheduling() - expected_candidates = [ch0.name, ch2.name] - self.assertEqual(sorted(expected_candidates), sorted(candidates)) + # The target physnets, the availability zones and the expected + # candidates. + results = [{'physnet': physnet_name, 'az_hints': ['az0', 'az2'], + 'expected_candidates': [ch0.name, ch2.name]}, + {'physnet': None, 'az_hints': ['az0', 'az2'], + 'expected_candidates': []}, + ] + for result in results: + candidates = ovn_client.get_candidates_for_scheduling( + result['physnet'], cms=cms, chassis_physnets=ch_physnet, + availability_zone_hints=result['az_hints']) + self.assertEqual(sorted(result['expected_candidates']), + sorted(candidates)) def test__get_info_for_ha_chassis_group_as_extport(self): net_attrs = {az_def.AZ_HINTS: ['az0', 'az1', 'az2']}