# Copyright 2019 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import collections import mock from neutron_lib import constants as const from neutron.common.ovn import acl from neutron.common.ovn import constants as ovn_const from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import impl_idl_ovn from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_db_sync from neutron.services.ovn_l3 import plugin as ovn_plugin from neutron.tests.unit.plugins.ml2.drivers.ovn.mech_driver import \ test_mech_driver OvnPortInfo = collections.namedtuple('OvnPortInfo', ['name']) @mock.patch.object(ovn_plugin.OVNL3RouterPlugin, '_sb_ovn', mock.Mock()) class TestOvnNbSyncML2(test_mech_driver.OVNMechanismDriverTestCase): l3_plugin = 'ovn-router' def setUp(self): super(TestOvnNbSyncML2, self).setUp() self.subnet = {'cidr': '10.0.0.0/24', 'id': 'subnet1', 'subnetpool_id': None, 'name': 'private-subnet', 'enable_dhcp': True, 'network_id': 'n1', 'tenant_id': 'tenant1', 'gateway_ip': '10.0.0.1', 'ip_version': 4, 'shared': False} self.matches = ["", "", "", ""] self.networks = [{'id': 'n1', 'mtu': 1450, 'provider:physical_network': 'physnet1', 'provider:segmentation_id': 1000}, {'id': 'n2', 'mtu': 1450}, {'id': 'n4', 'mtu': 1450, 'provider:physical_network': 'physnet2'}] self.subnets = [{'id': 'n1-s1', 'network_id': 'n1', 'enable_dhcp': True, 'cidr': '10.0.0.0/24', 'tenant_id': 'tenant1', 'gateway_ip': '10.0.0.1', 'dns_nameservers': [], 'host_routes': [], 'ip_version': 4}, {'id': 'n1-s2', 'network_id': 'n1', 'enable_dhcp': True, 'cidr': 'fd79:e1c:a55::/64', 'tenant_id': 'tenant1', 'gateway_ip': 'fd79:e1c:a55::1', 'dns_nameservers': [], 'host_routes': [], 'ip_version': 6}, {'id': 'n2', 'network_id': 'n2', 'enable_dhcp': True, 'cidr': '20.0.0.0/24', 'tenant_id': 'tenant1', 'gateway_ip': '20.0.0.1', 'dns_nameservers': [], 'host_routes': [], 'ip_version': 4}] self.security_groups = [ {'id': 'sg1', 'tenant_id': 'tenant1', 'security_group_rules': [{'remote_group_id': None, 'direction': 'ingress', 'remote_ip_prefix': '0.0.0.0/0', 'protocol': 'tcp', 'ethertype': 'IPv4', 'tenant_id': 'tenant1', 'port_range_max': 65535, 'port_range_min': 1, 'id': 'ruleid1', 'security_group_id': 'sg1'}], 'name': 'all-tcp'}, {'id': 'sg2', 'tenant_id': 'tenant1', 'security_group_rules': [{'remote_group_id': 'sg2', 'direction': 'egress', 'remote_ip_prefix': '0.0.0.0/0', 'protocol': 'tcp', 'ethertype': 'IPv4', 'tenant_id': 'tenant1', 'port_range_max': 65535, 'port_range_min': 1, 'id': 'ruleid1', 'security_group_id': 'sg2'}], 'name': 'all-tcpe'}] self.port_groups_ovn = [mock.Mock(), mock.Mock(), mock.Mock()] self.port_groups_ovn[0].configure_mock( name='pg_sg1', external_ids={ovn_const.OVN_SG_EXT_ID_KEY: 'sg1'}, ports=[], acls=[]) self.port_groups_ovn[1].configure_mock( name='pg_unknown_del', external_ids={ovn_const.OVN_SG_EXT_ID_KEY: 'sg2'}, ports=[], acls=[]) self.port_groups_ovn[2].configure_mock( name='neutron_pg_drop', external_ids=[], ports=[], acls=[]) self.ports = [ {'id': 'p1n1', 'device_owner': 'compute:None', 'fixed_ips': [{'subnet_id': 'b142f5e3-d434-4740-8e88-75e8e5322a40', 'ip_address': '10.0.0.4'}, {'subnet_id': 'subnet1', 'ip_address': 'fd79:e1c:a55::816:eff:eff:ff2'}], 'security_groups': ['sg1'], 'network_id': 'n1'}, {'id': 'p2n1', 'device_owner': 'compute:None', 'fixed_ips': [{'subnet_id': 'b142f5e3-d434-4740-8e88-75e8e5322a40', 'ip_address': '10.0.0.4'}, {'subnet_id': 'subnet1', 'ip_address': 'fd79:e1c:a55::816:eff:eff:ff2'}], 'security_groups': ['sg2'], 'network_id': 'n1', 'extra_dhcp_opts': [{'ip_version': 6, 'opt_name': 'domain-search', 'opt_value': 'foo-domain'}]}, {'id': 'p1n2', 'device_owner': 'compute:None', 'fixed_ips': [{'subnet_id': 'b142f5e3-d434-4740-8e88-75e8e5322a40', 'ip_address': '10.0.0.4'}, {'subnet_id': 'subnet1', 'ip_address': 'fd79:e1c:a55::816:eff:eff:ff2'}], 'security_groups': ['sg1'], 'network_id': 'n2', 'extra_dhcp_opts': [{'ip_version': 4, 'opt_name': 'tftp-server', 'opt_value': '20.0.0.20'}, {'ip_version': 4, 'opt_name': 'dns-server', 'opt_value': '8.8.8.8'}, {'ip_version': 6, 'opt_name': 'domain-search', 'opt_value': 'foo-domain'}]}, {'id': 'p2n2', 'device_owner': 'compute:None', 'fixed_ips': [{'subnet_id': 'b142f5e3-d434-4740-8e88-75e8e5322a40', 'ip_address': '10.0.0.4'}, {'subnet_id': 'subnet1', 'ip_address': 'fd79:e1c:a55::816:eff:eff:ff2'}], 'security_groups': ['sg2'], 'network_id': 'n2'}, {'id': 'fp1', 'device_owner': 'network:floatingip', 'fixed_ips': [{'subnet_id': 'ext-subnet', 'ip_address': '90.0.0.10'}], 'network_id': 'ext-net'}] self.ports_ovn = [OvnPortInfo('p1n1'), OvnPortInfo('p1n2'), OvnPortInfo('p2n1'), OvnPortInfo('p2n2'), OvnPortInfo('p3n1'), OvnPortInfo('p3n3')] self.acls_ovn = { 'lport1': # ACLs need to be removed by the sync tool [{'id': 'acl1', 'priority': 00, 'policy': 'allow', 'lswitch': 'lswitch1', 'lport': 'lport1'}], 'lport2': [{'id': 'acl2', 'priority': 00, 'policy': 'drop', 'lswitch': 'lswitch2', 'lport': 'lport2'}], # ACLs need to be kept as-is by the sync tool 'p2n2': [{'lport': 'p2n2', 'direction': 'to-lport', 'log': False, 'lswitch': 'neutron-n2', 'priority': 1001, 'action': 'drop', 'external_ids': {'neutron:lport': 'p2n2'}, 'match': 'outport == "p2n2" && ip'}, {'lport': 'p2n2', 'direction': 'to-lport', 'log': False, 'lswitch': 'neutron-n2', 'priority': 1002, 'action': 'allow', 'external_ids': {'neutron:lport': 'p2n2'}, 'match': 'outport == "p2n2" && ip4 && ' 'ip4.src == 10.0.0.0/24 && udp && ' 'udp.src == 67 && udp.dst == 68'}]} self.address_sets_ovn = { 'as_ip4_sg1': {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'all-tcp'}, 'name': 'as_ip4_sg1', 'addresses': ['10.0.0.4']}, 'as_ip4_sg2': {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'all-tcpe'}, 'name': 'as_ip4_sg2', 'addresses': []}, 'as_ip6_sg2': {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'all-tcpe'}, 'name': 'as_ip6_sg2', 'addresses': ['fd79:e1c:a55::816:eff:eff:ff2', 'fd79:e1c:a55::816:eff:eff:ff3']}, 'as_ip4_del': {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'all-delete'}, 'name': 'as_ip4_delete', 'addresses': ['10.0.0.4']}, } self.routers = [{'id': 'r1', 'routes': [{'nexthop': '20.0.0.100', 'destination': '11.0.0.0/24'}, { 'nexthop': '20.0.0.101', 'destination': '12.0.0.0/24'}], 'gw_port_id': 'gpr1', 'external_gateway_info': { 'network_id': "ext-net", 'enable_snat': True, 'external_fixed_ips': [ {'subnet_id': 'ext-subnet', 'ip_address': '90.0.0.2'}]}}, {'id': 'r2', 'routes': [{'nexthop': '40.0.0.100', 'destination': '30.0.0.0/24'}], 'gw_port_id': 'gpr2', 'external_gateway_info': { 'network_id': "ext-net", 'enable_snat': True, 'external_fixed_ips': [ {'subnet_id': 'ext-subnet', 'ip_address': '100.0.0.2'}]}}, {'id': 'r4', 'routes': []}] self.get_sync_router_ports = [ {'fixed_ips': [{'subnet_id': 'subnet1', 'ip_address': '192.168.1.1'}], 'id': 'p1r1', 'device_id': 'r1', 'mac_address': 'fa:16:3e:d7:fd:5f'}, {'fixed_ips': [{'subnet_id': 'subnet2', 'ip_address': '192.168.2.1'}], 'id': 'p1r2', 'device_id': 'r2', 'mac_address': 'fa:16:3e:d6:8b:ce'}, {'fixed_ips': [{'subnet_id': 'subnet4', 'ip_address': '192.168.4.1'}], 'id': 'p1r4', 'device_id': 'r4', 'mac_address': 'fa:16:3e:12:34:56'}] self.floating_ips = [{'id': 'fip1', 'router_id': 'r1', 'floating_ip_address': '90.0.0.10', 'fixed_ip_address': '172.16.0.10'}, {'id': 'fip2', 'router_id': 'r1', 'floating_ip_address': '90.0.0.12', 'fixed_ip_address': '172.16.2.12'}, {'id': 'fip3', 'router_id': 'r2', 'floating_ip_address': '100.0.0.10', 'fixed_ip_address': '192.168.2.10'}, {'id': 'fip4', 'router_id': 'r2', 'floating_ip_address': '100.0.0.11', 'fixed_ip_address': '192.168.2.11'}] self.lrouters_with_rports = [{'name': 'r3', 'ports': {'p1r3': ['fake']}, 'static_routes': [], 'snats': [], 'dnat_and_snats': []}, {'name': 'r4', 'ports': {'p1r4': ['fdad:123:456::1/64', 'fdad:789:abc::1/64']}, 'static_routes': [], 'snats': [], 'dnat_and_snats': []}, {'name': 'r1', 'ports': {'p3r1': ['fake']}, 'static_routes': [{'nexthop': '20.0.0.100', 'destination': '11.0.0.0/24'}, {'nexthop': '20.0.0.100', 'destination': '10.0.0.0/24'}], 'snats': [{'logical_ip': '172.16.0.0/24', 'external_ip': '90.0.0.2', 'type': 'snat'}, {'logical_ip': '172.16.1.0/24', 'external_ip': '90.0.0.2', 'type': 'snat'}], 'dnat_and_snats': [{'logical_ip': '172.16.0.10', 'external_ip': '90.0.0.10', 'type': 'dnat_and_snat'}, {'logical_ip': '172.16.1.11', 'external_ip': '90.0.0.11', 'type': 'dnat_and_snat'}, {'logical_ip': '192.168.2.11', 'external_ip': '100.0.0.11', 'type': 'dnat_and_snat', 'external_mac': '01:02:03:04:05:06', 'logical_port': 'vm1'}]}] self.lswitches_with_ports = [{'name': 'neutron-n1', 'ports': ['p1n1', 'p3n1'], 'provnet_port': None}, {'name': 'neutron-n3', 'ports': ['p1n3', 'p2n3'], 'provnet_port': None}, {'name': 'neutron-n4', 'ports': [], 'provnet_port': 'provnet-n4'}] self.lrport_networks = ['fdad:123:456::1/64', 'fdad:cafe:a1b2::1/64'] def _fake_get_ovn_dhcp_options(self, subnet, network, server_mac=None): if subnet['id'] == 'n1-s1': return {'cidr': '10.0.0.0/24', 'options': {'server_id': '10.0.0.1', 'server_mac': '01:02:03:04:05:06', 'lease_time': str(12 * 60 * 60), 'mtu': '1450', 'router': '10.0.0.1'}, 'external_ids': {'subnet_id': 'n1-s1'}} return {'cidr': '', 'options': '', 'external_ids': {}} def _fake_get_gw_info(self, ctx, router): return { 'r1': [ovn_client.GW_INFO(router_ip='90.0.0.2', gateway_ip='90.0.0.1', network_id='', subnet_id='', ip_version=4, ip_prefix=const.IPv4_ANY)], 'r2': [ovn_client.GW_INFO(router_ip='100.0.0.2', gateway_ip='100.0.0.1', network_id='', subnet_id='', ip_version=4, ip_prefix=const.IPv4_ANY)] }.get(router['id'], []) def _fake_get_v4_network_of_all_router_ports(self, ctx, router_id): return {'r1': ['172.16.0.0/24', '172.16.2.0/24'], 'r2': ['192.168.2.0/24']}.get(router_id, []) def _test_mocks_helper(self, ovn_nb_synchronizer): core_plugin = ovn_nb_synchronizer.core_plugin ovn_api = ovn_nb_synchronizer.ovn_api ovn_driver = ovn_nb_synchronizer.ovn_driver l3_plugin = ovn_nb_synchronizer.l3_plugin core_plugin.get_networks = mock.Mock() core_plugin.get_networks.return_value = self.networks core_plugin.get_subnets = mock.Mock() core_plugin.get_subnets.return_value = self.subnets # following block is used for acl syncing unit-test # With the given set of values in the unit testing, # 19 neutron acls should have been there, # 4 acls are returned as current ovn acls, # two of which will match with neutron. # So, in this example 17 will be added, 2 removed core_plugin.get_ports = mock.Mock() core_plugin.get_ports.return_value = self.ports mock.patch.object(acl, '_get_subnet_from_cache', return_value=self.subnet).start() mock.patch.object(acl, 'acl_remote_group_id', side_effect=self.matches).start() core_plugin.get_security_group = mock.MagicMock( side_effect=self.security_groups) ovn_nb_synchronizer.get_acls = mock.Mock() ovn_nb_synchronizer.get_acls.return_value = self.acls_ovn core_plugin.get_security_groups = mock.MagicMock( return_value=self.security_groups) ovn_nb_synchronizer.get_address_sets = mock.Mock() ovn_nb_synchronizer.get_address_sets.return_value =\ self.address_sets_ovn get_port_groups = mock.MagicMock() get_port_groups.execute.return_value = self.port_groups_ovn ovn_api.db_list_rows.return_value = get_port_groups ovn_api.lsp_list.execute.return_value = self.ports_ovn # end of acl-sync block # The following block is used for router and router port syncing tests # With the give set of values in the unit test, # The Neutron db has Routers r1 and r2 present. # The OVN db has Routers r1 and r3 present. # During the sync r2 will need to be created and r3 will need # to be deleted from the OVN db. When Router r3 is deleted, all LRouter # ports associated with r3 is deleted too. # # Neutron db has Router ports p1r1 in Router r1 and p1r2 in Router r2 # OVN db has p1r3 in Router 3 and p3r1 in Router 1. # During the sync p1r1 and p1r2 will be added and p1r3 and p3r1 # will be deleted from the OVN db l3_plugin.get_routers = mock.Mock() l3_plugin.get_routers.return_value = self.routers l3_plugin._get_sync_interfaces = mock.Mock() l3_plugin._get_sync_interfaces.return_value = ( self.get_sync_router_ports) ovn_nb_synchronizer._ovn_client = mock.Mock() ovn_nb_synchronizer._ovn_client.\ _get_nets_and_ipv6_ra_confs_for_router_port.return_value = ( self.lrport_networks, {}) ovn_nb_synchronizer._ovn_client._get_v4_network_of_all_router_ports. \ side_effect = self._fake_get_v4_network_of_all_router_ports ovn_nb_synchronizer._ovn_client._get_gw_info = mock.Mock() ovn_nb_synchronizer._ovn_client._get_gw_info.side_effect = ( self._fake_get_gw_info) # end of router-sync block l3_plugin.get_floatingips = mock.Mock() l3_plugin.get_floatingips.return_value = self.floating_ips ovn_api.get_all_logical_switches_with_ports = mock.Mock() ovn_api.get_all_logical_switches_with_ports.return_value = ( self.lswitches_with_ports) ovn_api.get_all_logical_routers_with_rports = mock.Mock() ovn_api.get_all_logical_routers_with_rports.return_value = ( self.lrouters_with_rports) ovn_api.transaction = mock.MagicMock() ovn_nb_synchronizer._ovn_client.create_network = mock.Mock() ovn_nb_synchronizer._ovn_client.create_port = mock.Mock() ovn_driver.validate_and_get_data_from_binding_profile = mock.Mock() ovn_nb_synchronizer._ovn_client.create_port = mock.Mock() ovn_nb_synchronizer._ovn_client.create_port.return_value = mock.ANY ovn_nb_synchronizer._ovn_client._create_provnet_port = mock.Mock() ovn_api.ls_del = mock.Mock() ovn_api.delete_lswitch_port = mock.Mock() ovn_api.delete_lrouter = mock.Mock() ovn_api.delete_lrouter_port = mock.Mock() ovn_api.add_static_route = mock.Mock() ovn_api.delete_static_route = mock.Mock() ovn_api.get_all_dhcp_options.return_value = { 'subnets': {'n1-s1': {'cidr': '10.0.0.0/24', 'options': {'server_id': '10.0.0.1', 'server_mac': '01:02:03:04:05:06', 'lease_time': str(12 * 60 * 60), 'mtu': '1450', 'router': '10.0.0.1'}, 'external_ids': {'subnet_id': 'n1-s1'}, 'uuid': 'UUID1'}, 'n1-s3': {'cidr': '30.0.0.0/24', 'options': {'server_id': '30.0.0.1', 'server_mac': '01:02:03:04:05:06', 'lease_time': str(12 * 60 * 60), 'mtu': '1450', 'router': '30.0.0.1'}, 'external_ids': {'subnet_id': 'n1-s3'}, 'uuid': 'UUID2'}}, 'ports_v4': {'p1n2': {'cidr': '10.0.0.0/24', 'options': {'server_id': '10.0.0.1', 'server_mac': '01:02:03:04:05:06', 'lease_time': '1000', 'mtu': '1400', 'router': '10.0.0.1'}, 'external_ids': {'subnet_id': 'n1-s1', 'port_id': 'p1n2'}, 'uuid': 'UUID3'}, 'p5n2': {'cidr': '10.0.0.0/24', 'options': {'server_id': '10.0.0.1', 'server_mac': '01:02:03:04:05:06', 'lease_time': '1000', 'mtu': '1400', 'router': '10.0.0.1'}, 'external_ids': {'subnet_id': 'n1-s1', 'port_id': 'p5n2'}, 'uuid': 'UUID4'}}, 'ports_v6': {'p1n1': {'cidr': 'fd79:e1c:a55::/64', 'options': {'server_id': '01:02:03:04:05:06', 'mtu': '1450'}, 'external_ids': {'subnet_id': 'fake', 'port_id': 'p1n1'}, 'uuid': 'UUID5'}, 'p1n2': {'cidr': 'fd79:e1c:a55::/64', 'options': {'server_id': '01:02:03:04:05:06', 'mtu': '1450'}, 'external_ids': {'subnet_id': 'fake', 'port_id': 'p1n2'}, 'uuid': 'UUID6'}}} ovn_api.create_address_set = mock.Mock() ovn_api.delete_address_set = mock.Mock() ovn_api.update_address_set = mock.Mock() ovn_nb_synchronizer._ovn_client._add_subnet_dhcp_options = mock.Mock() ovn_nb_synchronizer._ovn_client._get_ovn_dhcp_options = mock.Mock() ovn_nb_synchronizer._ovn_client._get_ovn_dhcp_options.side_effect = ( self._fake_get_ovn_dhcp_options) ovn_api.delete_dhcp_options = mock.Mock() ovn_nb_synchronizer._ovn_client.get_port_dns_records = mock.Mock() ovn_nb_synchronizer._ovn_client.get_port_dns_records.return_value = {} def _test_ovn_nb_sync_helper(self, ovn_nb_synchronizer, networks, ports, routers, router_ports, create_router_list, create_router_port_list, update_router_port_list, del_router_list, del_router_port_list, create_network_list, create_port_list, create_provnet_port_list, del_network_list, del_port_list, add_static_route_list, del_static_route_list, add_snat_list, del_snat_list, add_floating_ip_list, del_floating_ip_list, add_address_set_list, del_address_set_list, update_address_set_list, add_subnet_dhcp_options_list, delete_dhcp_options_list, add_port_groups_list, del_port_groups_list, port_groups_supported=False): self._test_mocks_helper(ovn_nb_synchronizer) core_plugin = ovn_nb_synchronizer.core_plugin ovn_api = ovn_nb_synchronizer.ovn_api ovn_api.is_port_groups_supported.return_value = port_groups_supported mock.patch.object(impl_idl_ovn, 'get_connection').start() ovn_nb_synchronizer.do_sync() if not ovn_api.is_port_groups_supported(): get_security_group_calls = [mock.call(mock.ANY, sg['id']) for sg in self.security_groups] self.assertEqual(len(self.security_groups), core_plugin.get_security_group.call_count) core_plugin.get_security_group.assert_has_calls( get_security_group_calls, any_order=True) create_address_set_calls = [mock.call(**a) for a in add_address_set_list] self.assertEqual( len(add_address_set_list), ovn_api.create_address_set.call_count) ovn_api.create_address_set.assert_has_calls( create_address_set_calls, any_order=True) del_address_set_calls = [mock.call(**d) for d in del_address_set_list] self.assertEqual( len(del_address_set_list), ovn_api.delete_address_set.call_count) ovn_api.delete_address_set.assert_has_calls( del_address_set_calls, any_order=True) update_address_set_calls = [mock.call(**u) for u in update_address_set_list] self.assertEqual( len(update_address_set_list), ovn_api.update_address_set.call_count) ovn_api.update_address_set.assert_has_calls( update_address_set_calls, any_order=True) create_port_groups_calls = [mock.call(**a) for a in add_port_groups_list] self.assertEqual( len(add_port_groups_list), ovn_api.pg_add.call_count) ovn_api.pg_add.assert_has_calls( create_port_groups_calls, any_order=True) del_port_groups_calls = [mock.call(d) for d in del_port_groups_list] self.assertEqual( len(del_port_groups_list), ovn_api.pg_del.call_count) ovn_api.pg_del.assert_has_calls( del_port_groups_calls, any_order=True) self.assertEqual( len(create_network_list), ovn_nb_synchronizer._ovn_client.create_network.call_count) create_network_calls = [mock.call(net['net']) for net in create_network_list] ovn_nb_synchronizer._ovn_client.create_network.assert_has_calls( create_network_calls, any_order=True) self.assertEqual( len(create_port_list), ovn_nb_synchronizer._ovn_client.create_port.call_count) create_port_calls = [mock.call(port) for port in create_port_list] ovn_nb_synchronizer._ovn_client.create_port.assert_has_calls( create_port_calls, any_order=True) create_provnet_port_calls = [ mock.call(mock.ANY, mock.ANY, network['provider:physical_network'], network['provider:segmentation_id']) for network in create_provnet_port_list] self.assertEqual( len(create_provnet_port_list), ovn_nb_synchronizer._ovn_client._create_provnet_port.call_count) ovn_nb_synchronizer._ovn_client._create_provnet_port.assert_has_calls( create_provnet_port_calls, any_order=True) self.assertEqual(len(del_network_list), ovn_api.ls_del.call_count) ls_del_calls = [mock.call(net_name) for net_name in del_network_list] ovn_api.ls_del.assert_has_calls( ls_del_calls, any_order=True) self.assertEqual(len(del_port_list), ovn_api.delete_lswitch_port.call_count) delete_lswitch_port_calls = [mock.call(lport_name=port['id'], lswitch_name=port['lswitch']) for port in del_port_list] ovn_api.delete_lswitch_port.assert_has_calls( delete_lswitch_port_calls, any_order=True) add_route_calls = [mock.call(mock.ANY, ip_prefix=route['destination'], nexthop=route['nexthop']) for route in add_static_route_list] ovn_api.add_static_route.assert_has_calls(add_route_calls, any_order=True) self.assertEqual(len(add_static_route_list), ovn_api.add_static_route.call_count) del_route_calls = [mock.call(mock.ANY, ip_prefix=route['destination'], nexthop=route['nexthop']) for route in del_static_route_list] ovn_api.delete_static_route.assert_has_calls(del_route_calls, any_order=True) self.assertEqual(len(del_static_route_list), ovn_api.delete_static_route.call_count) add_nat_calls = [mock.call(mock.ANY, **nat) for nat in add_snat_list] ovn_api.add_nat_rule_in_lrouter.assert_has_calls(add_nat_calls, any_order=True) self.assertEqual(len(add_snat_list), ovn_api.add_nat_rule_in_lrouter.call_count) add_fip_calls = [mock.call(nat, txn=mock.ANY) for nat in add_floating_ip_list] (ovn_nb_synchronizer._ovn_client._create_or_update_floatingip. assert_has_calls(add_fip_calls)) self.assertEqual( len(add_floating_ip_list), ovn_nb_synchronizer._ovn_client._create_or_update_floatingip. call_count) del_nat_calls = [mock.call(mock.ANY, **nat) for nat in del_snat_list] ovn_api.delete_nat_rule_in_lrouter.assert_has_calls(del_nat_calls, any_order=True) self.assertEqual(len(del_snat_list), ovn_api.delete_nat_rule_in_lrouter.call_count) del_fip_calls = [mock.call(nat, mock.ANY, txn=mock.ANY) for nat in del_floating_ip_list] ovn_nb_synchronizer._ovn_client._delete_floatingip.assert_has_calls( del_fip_calls, any_order=True) self.assertEqual( len(del_floating_ip_list), ovn_nb_synchronizer._ovn_client._delete_floatingip.call_count) create_router_calls = [mock.call(r, add_external_gateway=False) for r in create_router_list] self.assertEqual( len(create_router_list), ovn_nb_synchronizer._ovn_client.create_router.call_count) ovn_nb_synchronizer._ovn_client.create_router.assert_has_calls( create_router_calls, any_order=True) create_router_port_calls = [mock.call(p['device_id'], mock.ANY) for p in create_router_port_list] self.assertEqual( len(create_router_port_list), ovn_nb_synchronizer._ovn_client._create_lrouter_port.call_count) ovn_nb_synchronizer._ovn_client._create_lrouter_port.assert_has_calls( create_router_port_calls, any_order=True) self.assertEqual(len(del_router_list), ovn_api.delete_lrouter.call_count) update_router_port_calls = [mock.call(p) for p in update_router_port_list] self.assertEqual( len(update_router_port_list), ovn_nb_synchronizer._ovn_client.update_router_port.call_count) ovn_nb_synchronizer._ovn_client.update_router_port.assert_has_calls( update_router_port_calls, any_order=True) delete_lrouter_calls = [mock.call(r['router']) for r in del_router_list] ovn_api.delete_lrouter.assert_has_calls( delete_lrouter_calls, any_order=True) self.assertEqual( len(del_router_port_list), ovn_api.delete_lrouter_port.call_count) delete_lrouter_port_calls = [mock.call(port['id'], port['router'], if_exists=False) for port in del_router_port_list] ovn_api.delete_lrouter_port.assert_has_calls( delete_lrouter_port_calls, any_order=True) self.assertEqual( len(add_subnet_dhcp_options_list), ovn_nb_synchronizer._ovn_client._add_subnet_dhcp_options. call_count) add_subnet_dhcp_options_calls = [ mock.call(subnet, net, mock.ANY) for (subnet, net) in add_subnet_dhcp_options_list] ovn_nb_synchronizer._ovn_client._add_subnet_dhcp_options. \ assert_has_calls(add_subnet_dhcp_options_calls, any_order=True) self.assertEqual(ovn_api.delete_dhcp_options.call_count, len(delete_dhcp_options_list)) delete_dhcp_options_calls = [ mock.call(dhcp_opt_uuid) for dhcp_opt_uuid in delete_dhcp_options_list] ovn_api.delete_dhcp_options.assert_has_calls( delete_dhcp_options_calls, any_order=True) def _test_ovn_nb_sync_mode_repair_helper(self, port_groups_supported=True): create_network_list = [{'net': {'id': 'n2', 'mtu': 1450}, 'ext_ids': {}}] del_network_list = ['neutron-n3'] del_port_list = [{'id': 'p3n1', 'lswitch': 'neutron-n1'}, {'id': 'p1n1', 'lswitch': 'neutron-n1'}] create_port_list = self.ports for port in create_port_list: if port['id'] in ['p1n1', 'fp1']: # this will be skipped by the logic, # because p1n1 is already in lswitch-port list # and fp1 is a floating IP port create_port_list.remove(port) create_provnet_port_list = [{'id': 'n1', 'mtu': 1450, 'provider:physical_network': 'physnet1', 'provider:segmentation_id': 1000}] create_router_list = [{ 'id': 'r2', 'routes': [ {'nexthop': '40.0.0.100', 'destination': '30.0.0.0/24'}], 'gw_port_id': 'gpr2', 'external_gateway_info': { 'network_id': "ext-net", 'enable_snat': True, 'external_fixed_ips': [{ 'subnet_id': 'ext-subnet', 'ip_address': '100.0.0.2'}]}}] # Test adding and deleting routes snats fips behaviors for router r1 # existing in both neutron DB and OVN DB. # Test adding behaviors for router r2 only existing in neutron DB. # Static routes with destination 0.0.0.0/0 are default gateway routes add_static_route_list = [{'nexthop': '20.0.0.101', 'destination': '12.0.0.0/24'}, {'nexthop': '90.0.0.1', 'destination': '0.0.0.0/0'}, {'nexthop': '40.0.0.100', 'destination': '30.0.0.0/24'}, {'nexthop': '100.0.0.1', 'destination': '0.0.0.0/0'}] del_static_route_list = [{'nexthop': '20.0.0.100', 'destination': '10.0.0.0/24'}] add_snat_list = [{'logical_ip': '172.16.2.0/24', 'external_ip': '90.0.0.2', 'type': 'snat'}, {'logical_ip': '192.168.2.0/24', 'external_ip': '100.0.0.2', 'type': 'snat'}] del_snat_list = [{'logical_ip': '172.16.1.0/24', 'external_ip': '90.0.0.2', 'type': 'snat'}] # fip 100.0.0.11 exists in OVN with distributed type and in Neutron # with centralized type. This fip is used to test # enable_distributed_floating_ip switch and migration add_floating_ip_list = [{'id': 'fip2', 'router_id': 'r1', 'floating_ip_address': '90.0.0.12', 'fixed_ip_address': '172.16.2.12'}, {'id': 'fip3', 'router_id': 'r2', 'floating_ip_address': '100.0.0.10', 'fixed_ip_address': '192.168.2.10'}, {'id': 'fip4', 'router_id': 'r2', 'floating_ip_address': '100.0.0.11', 'fixed_ip_address': '192.168.2.11'}] del_floating_ip_list = [{'logical_ip': '172.16.1.11', 'external_ip': '90.0.0.11', 'type': 'dnat_and_snat'}, {'logical_ip': '192.168.2.11', 'external_ip': '100.0.0.11', 'type': 'dnat_and_snat', 'external_mac': '01:02:03:04:05:06', 'logical_port': 'vm1'}] del_router_list = [{'router': 'neutron-r3'}] del_router_port_list = [{'id': 'lrp-p3r1', 'router': 'neutron-r1'}] create_router_port_list = self.get_sync_router_ports[:2] update_router_port_list = [self.get_sync_router_ports[2]] update_router_port_list[0].update( {'networks': self.lrport_networks}) if not port_groups_supported: add_address_set_list = [ {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'sg1'}, 'name': 'as_ip6_sg1', 'addresses': ['fd79:e1c:a55::816:eff:eff:ff2']}] del_address_set_list = [{'name': 'as_ip4_del'}] update_address_set_list = [ {'addrs_remove': [], 'addrs_add': ['10.0.0.4'], 'name': 'as_ip4_sg2'}, {'addrs_remove': ['fd79:e1c:a55::816:eff:eff:ff3'], 'addrs_add': [], 'name': 'as_ip6_sg2'}] # If Port Groups are not supported, we don't expect any of those # to be created/deleted. add_port_groups_list = [] del_port_groups_list = [] else: add_port_groups_list = [ {'external_ids': {ovn_const.OVN_SG_EXT_ID_KEY: 'sg2'}, 'name': 'pg_sg2', 'acls': []}] del_port_groups_list = ['pg_unknown_del'] # If using Port Groups, no Address Set shall be created/updated # and all the existing ones have to be removed. add_address_set_list = [] update_address_set_list = [] del_address_set_list = [{'name': 'as_ip4_sg1'}, {'name': 'as_ip4_sg2'}, {'name': 'as_ip6_sg2'}, {'name': 'as_ip4_del'}] add_subnet_dhcp_options_list = [(self.subnets[2], self.networks[1]), (self.subnets[1], self.networks[0])] delete_dhcp_options_list = ['UUID2', 'UUID4', 'UUID5'] ovn_nb_synchronizer = ovn_db_sync.OvnNbSynchronizer( self.plugin, self.mech_driver._nb_ovn, self.mech_driver._sb_ovn, 'repair', self.mech_driver) self._test_ovn_nb_sync_helper(ovn_nb_synchronizer, self.networks, self.ports, self.routers, self.get_sync_router_ports, create_router_list, create_router_port_list, update_router_port_list, del_router_list, del_router_port_list, create_network_list, create_port_list, create_provnet_port_list, del_network_list, del_port_list, add_static_route_list, del_static_route_list, add_snat_list, del_snat_list, add_floating_ip_list, del_floating_ip_list, add_address_set_list, del_address_set_list, update_address_set_list, add_subnet_dhcp_options_list, delete_dhcp_options_list, add_port_groups_list, del_port_groups_list, port_groups_supported) def test_ovn_nb_sync_mode_repair_no_pgs(self): self._test_ovn_nb_sync_mode_repair_helper(port_groups_supported=False) def test_ovn_nb_sync_mode_repair_pgs(self): self._test_ovn_nb_sync_mode_repair_helper(port_groups_supported=True) def _test_ovn_nb_sync_mode_log_helper(self, port_groups_supported=True): create_network_list = [] create_port_list = [] create_provnet_port_list = [] del_network_list = [] del_port_list = [] create_router_list = [] create_router_port_list = [] update_router_port_list = [] del_router_list = [] del_router_port_list = [] add_static_route_list = [] del_static_route_list = [] add_snat_list = [] del_snat_list = [] add_floating_ip_list = [] del_floating_ip_list = [] add_address_set_list = [] del_address_set_list = [] update_address_set_list = [] add_subnet_dhcp_options_list = [] delete_dhcp_options_list = [] add_port_groups_list = [] del_port_groups_list = [] ovn_nb_synchronizer = ovn_db_sync.OvnNbSynchronizer( self.plugin, self.mech_driver._nb_ovn, self.mech_driver._sb_ovn, 'log', self.mech_driver) self._test_ovn_nb_sync_helper(ovn_nb_synchronizer, self.networks, self.ports, self.routers, self.get_sync_router_ports, create_router_list, create_router_port_list, update_router_port_list, del_router_list, del_router_port_list, create_network_list, create_port_list, create_provnet_port_list, del_network_list, del_port_list, add_static_route_list, del_static_route_list, add_snat_list, del_snat_list, add_floating_ip_list, del_floating_ip_list, add_address_set_list, del_address_set_list, update_address_set_list, add_subnet_dhcp_options_list, delete_dhcp_options_list, add_port_groups_list, del_port_groups_list, port_groups_supported) def test_ovn_nb_sync_mode_log_pgs(self): self._test_ovn_nb_sync_mode_log_helper(port_groups_supported=True) def test_ovn_nb_sync_mode_log_no_pgs(self): self._test_ovn_nb_sync_mode_log_helper(port_groups_supported=False) class TestOvnSbSyncML2(test_mech_driver.OVNMechanismDriverTestCase): def test_ovn_sb_sync(self): ovn_sb_synchronizer = ovn_db_sync.OvnSbSynchronizer( self.plugin, self.mech_driver._sb_ovn, self.mech_driver) ovn_api = ovn_sb_synchronizer.ovn_api hostname_with_physnets = {'hostname1': ['physnet1', 'physnet2'], 'hostname2': ['physnet1']} ovn_api.get_chassis_hostname_and_physnets.return_value = ( hostname_with_physnets) ovn_driver = ovn_sb_synchronizer.ovn_driver ovn_driver.update_segment_host_mapping = mock.Mock() hosts_in_neutron = {'hostname2', 'hostname3'} with mock.patch.object(ovn_db_sync.segments_db, 'get_hosts_mapped_with_segments', return_value=hosts_in_neutron): ovn_sb_synchronizer.sync_hostname_and_physical_networks(mock.ANY) all_hosts = set(hostname_with_physnets.keys()) | hosts_in_neutron self.assertEqual( len(all_hosts), ovn_driver.update_segment_host_mapping.call_count) update_segment_host_mapping_calls = [mock.call( host, hostname_with_physnets[host]) for host in hostname_with_physnets] update_segment_host_mapping_calls += [ mock.call(host, []) for host in hosts_in_neutron - set(hostname_with_physnets.keys())] ovn_driver.update_segment_host_mapping.assert_has_calls( update_segment_host_mapping_calls, any_order=True)