diff --git a/doc/source/contributor/bgp_mode_design.rst b/doc/source/contributor/bgp_mode_design.rst index 1ff73136..f9411aa2 100644 --- a/doc/source/contributor/bgp_mode_design.rst +++ b/doc/source/contributor/bgp_mode_design.rst @@ -140,7 +140,7 @@ The folloging events are watched and handled by the BGP watcher: The BGP watcher detects OVN Southbound Database events at the ``Port_Binding`` and ``Load_Balancer`` tables. It creates new event classes named -``PortBindingChassisEvent`` and ``OVNLBMemberEvent``, that all the events +``PortBindingChassisEvent`` and ``OVNLBEvent``, that all the events watched for BGP use as the base (inherit from). The specific defined events to react to are: diff --git a/doc/source/contributor/bgp_mode_stretched_l2_design.rst b/doc/source/contributor/bgp_mode_stretched_l2_design.rst index 63e5f355..a52d2d00 100644 --- a/doc/source/contributor/bgp_mode_stretched_l2_design.rst +++ b/doc/source/contributor/bgp_mode_stretched_l2_design.rst @@ -79,7 +79,7 @@ networking accordingly. The BGP watcher detects OVN Southbound Database events at the ``Port_Binding`` and ``Load_Balancer`` tables. It creates new event classes named -``PortBindingChassisEvent`` and ``OVNLBMemberEvent``, that all the events +``PortBindingChassisEvent`` and ``OVNLBEvent``, that all the events watched for BGP use as the base (inherit from). The driver react specifically to the following events: diff --git a/ovn_bgp_agent/constants.py b/ovn_bgp_agent/constants.py index 14f7bff1..559f36a8 100644 --- a/ovn_bgp_agent/constants.py +++ b/ovn_bgp_agent/constants.py @@ -33,6 +33,10 @@ OVN_DEVICE_OWNER_EXT_ID_KEY = 'neutron:device_owner' OVN_FIP_EXT_ID_KEY = 'neutron:port_fip' OVN_FIP_NET_EXT_ID_KEY = 'neutron:fip_network_id' LB_VIP_PORT_PREFIX = "ovn-lb-vip-" +OVN_LB_VIP_IP_EXT_ID_KEY = 'neutron:vip' +OVN_LB_VIP_FIP_EXT_ID_KEY = 'neutron:vip_fip' +OVN_LB_VIP_PORT_EXT_ID_KEY = 'neutron:vip_port_id' +OVN_LB_LR_REF_EXT_ID_KEY = 'lr_ref' OVS_RULE_COOKIE = "999" OVS_VRF_RULE_COOKIE = "998" diff --git a/ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py b/ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py index 39ffcfc3..b883224d 100644 --- a/ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py +++ b/ovn_bgp_agent/drivers/openstack/nb_ovn_bgp_driver.py @@ -36,7 +36,7 @@ LOG = logging.getLogger(__name__) # logging.basicConfig(level=logging.DEBUG) OVN_TABLES = ['Logical_Switch_Port', 'NAT', 'Logical_Switch', - 'Logical_Router_Port'] + 'Logical_Router_Port', 'Load_Balancer'] LOCAL_CLUSTER_OVN_TABLES = ['Logical_Switch', 'Logical_Switch_Port', 'Logical_Router', 'Logical_Router_Port', 'Logical_Router_Policy', @@ -146,7 +146,9 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase): "LogicalSwitchPortProviderDeleteEvent", "LogicalSwitchPortFIPCreateEvent", "LogicalSwitchPortFIPDeleteEvent", - "LocalnetCreateDeleteEvent"]) + "LocalnetCreateDeleteEvent", + "OVNLBCreateEvent", + "OVNLBDeleteEvent"]) if self._expose_tenant_networks: events.update(["ChassisRedirectCreateEvent", "ChassisRedirectDeleteEvent", @@ -202,6 +204,9 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase): continue self._ensure_lsp_exposed(port) + # add missing routes/ips for OVN loadbalancers + self._expose_lbs(self.ovn_local_cr_lrps.keys()) + # remove extra wiring leftovers wire_utils.cleanup_wiring(self.nb_idl, self.ovn_bridge_mappings, @@ -323,6 +328,22 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase): bridge_vlan = localnet.tag[0] return bridge_device, bridge_vlan + def _expose_lbs(self, router_list): + lbs = self.nb_idl.get_active_local_lbs(router_list) + for lb in lbs: + self._expose_ovn_lb_vip(lb) + # if vip-fip expose fip too + if lb.external_ids.get(constants.OVN_LB_VIP_FIP_EXT_ID_KEY): + self._expose_ovn_lb_fip(lb) + + def _withdraw_lbs(self, router_list): + lbs = self.nb_idl.get_active_local_lbs(router_list) + for lb in lbs: + self._withdraw_ovn_lb_vip(lb) + # if vip-fip withdraw fip too + if lb.external_ids.get(constants.OVN_LB_VIP_FIP_EXT_ID_KEY): + self._withdraw_ovn_lb_fip(lb) + @lockutils.synchronized('nbbgp') def expose_ip(self, ips, ips_info): '''Advertice BGP route by adding IP to device. @@ -339,20 +360,28 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase): logical_switch = ips_info.get('logical_switch') if not logical_switch: return False - localnet, bridge_device, bridge_vlan = self._get_ls_localnet_info( - logical_switch) - if not bridge_device: - # This means it is not a provider network - self.ovn_tenant_ls[logical_switch] = True - return False - if bridge_device not in self.ovn_bridge_mappings.values(): - # This node is not properly configured, no need to expose it - return False - if not self.ovn_provider_ls.get(logical_switch): - self.ovn_provider_ls[logical_switch] = { - 'bridge_device': bridge_device, - 'bridge_vlan': bridge_vlan, - 'localnet': localnet} + + bridge_info = self.ovn_provider_ls.get(logical_switch) + if bridge_info: + # already known provider ls + bridge_device = bridge_info['bridge_device'] + bridge_vlan = bridge_info['bridge_vlan'] + localnet = bridge_info['localnet'] + else: + localnet, bridge_device, bridge_vlan = self._get_ls_localnet_info( + logical_switch) + if not bridge_device: + # This means it is not a provider network + self.ovn_tenant_ls[logical_switch] = True + return False + if bridge_device not in self.ovn_bridge_mappings.values(): + # This node is not properly configured, no need to expose it + return False + if not self.ovn_provider_ls.get(logical_switch): + self.ovn_provider_ls[logical_switch] = { + 'bridge_device': bridge_device, + 'bridge_vlan': bridge_vlan, + 'localnet': localnet} mac = ips_info.get('mac') return self._expose_ip(ips, mac, logical_switch, bridge_device, bridge_vlan, port_type=ips_info['type'], @@ -379,6 +408,7 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase): self.ovn_local_cr_lrps[router] = { 'bridge_device': bridge_device, 'bridge_vlan': bridge_vlan, + 'provider_switch': logical_switch, 'ips': ips, } # Expose associated subnets @@ -393,6 +423,9 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase): constants.OVN_LS_NAME_EXT_ID_KEY), 'address_scopes': driver_utils.get_addr_scopes(port)} self._expose_subnet(ips, subnet_info) + + # add missing routes/ips for OVN loadbalancers + self._expose_lbs([router]) else: if not self._expose_provider_port(ips, mac, logical_switch, bridge_device, bridge_vlan, @@ -452,6 +485,10 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase): constants.OVN_LS_NAME_EXT_ID_KEY), 'address_scopes': driver_utils.get_addr_scopes(port)} self._withdraw_subnet(ips, subnet_info) + + # withdraw routes/ips for OVN loadbalancers + self._withdraw_lbs([ips_info['router']]) + try: del self.ovn_local_cr_lrps[ips_info['router']] except KeyError: @@ -497,19 +534,26 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase): return self._expose_fip(ip, mac, logical_switch, row) def _expose_fip(self, ip, mac, logical_switch, row): - localnet, bridge_device, bridge_vlan = self._get_ls_localnet_info( - logical_switch) - if not bridge_device: - # This means it is not a provider network - return False - if bridge_device not in self.ovn_bridge_mappings.values(): - # This node is not properly configured, no need to expose it - return False - if not self.ovn_provider_ls.get(logical_switch): - self.ovn_provider_ls[logical_switch] = { - 'bridge_device': bridge_device, - 'bridge_vlan': bridge_vlan, - 'localnet': localnet} + bridge_info = self.ovn_provider_ls.get(logical_switch) + if bridge_info: + # already known provider ls + bridge_device = bridge_info['bridge_device'] + bridge_vlan = bridge_info['bridge_vlan'] + localnet = bridge_info['localnet'] + else: + localnet, bridge_device, bridge_vlan = self._get_ls_localnet_info( + logical_switch) + if not bridge_device: + # This means it is not a provider network + return False + if bridge_device not in self.ovn_bridge_mappings.values(): + # This node is not properly configured, no need to expose it + return False + if not self.ovn_provider_ls.get(logical_switch): + self.ovn_provider_ls[logical_switch] = { + 'bridge_device': bridge_device, + 'bridge_vlan': bridge_vlan, + 'localnet': localnet} tenant_logical_switch = row.external_ids.get( constants.OVN_LS_NAME_EXT_ID_KEY) if not tenant_logical_switch: @@ -556,6 +600,10 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase): def expose_remote_ip(self, ips, ips_info): self._expose_remote_ip(ips, ips_info) + @lockutils.synchronized('nbbgp') + def withdraw_remote_ip(self, ips, ips_info): + self._withdraw_remote_ip(ips, ips_info) + def _expose_remote_ip(self, ips, ips_info): ips_to_expose = ips if not CONF.expose_tenant_networks: @@ -574,10 +622,6 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase): LOG.debug("Added BGP route for tenant IP(s) %s on chassis %s", ips_to_expose, self.chassis) - @lockutils.synchronized('nbbgp') - def withdraw_remote_ip(self, ips, ips_info): - self._withdraw_remote_ip(ips, ips_info) - def _withdraw_remote_ip(self, ips, ips_info): ips_to_withdraw = ips if not CONF.expose_tenant_networks: @@ -737,6 +781,95 @@ class NBOVNBGPDriver(driver_api.AgentDriverBase): pass return True + @lockutils.synchronized('nbbgp') + def expose_ovn_lb_vip(self, lb): + self._expose_ovn_lb_vip(lb) + + def _expose_ovn_lb_vip(self, lb): + vip_port = lb.external_ids.get(constants.OVN_LB_VIP_PORT_EXT_ID_KEY) + vip_ip = lb.external_ids.get(constants.OVN_LB_VIP_IP_EXT_ID_KEY) + vip_router = lb.external_ids[ + constants.OVN_LB_LR_REF_EXT_ID_KEY].replace('neutron-', "", 1) + vip_lsp = self.nb_idl.lsp_get(vip_port).execute(check_error=True) + if not vip_lsp: + LOG.debug("Something went wrong, VIP port %s not found", vip_port) + return + vip_net = vip_lsp.external_ids.get(constants.OVN_LS_NAME_EXT_ID_KEY) + if vip_net in self.ovn_local_lrps.keys(): + # It is a VIP on a tenant network + # NOTE: the LB is exposed through the cr-lrp, so we add the + # vip_router instead of the logical switch + ips_info = {'logical_switch': vip_router} + self._expose_remote_ip([vip_ip], ips_info) + else: + # It is a VIP on a provider network + localnet, bridge_device, bridge_vlan = self._get_ls_localnet_info( + vip_net) + self._expose_provider_port([vip_ip], None, vip_net, bridge_device, + bridge_vlan, localnet) + + @lockutils.synchronized('nbbgp') + def withdraw_ovn_lb_vip(self, lb): + self._withdraw_ovn_lb_vip(lb) + + def _withdraw_ovn_lb_vip(self, lb): + vip_ip = lb.external_ids.get(constants.OVN_LB_VIP_IP_EXT_ID_KEY) + vip_router = lb.external_ids[ + constants.OVN_LB_LR_REF_EXT_ID_KEY].replace('neutron-', "", 1) + + cr_lrp_info = self.ovn_local_cr_lrps.get(vip_router) + if not cr_lrp_info: + return + provider_ls = cr_lrp_info['provider_switch'] + if self._exposed_ips.get(provider_ls, {}).get(vip_ip): + # VIP is on provider network + self._withdraw_provider_port([vip_ip], + cr_lrp_info['provider_switch'], + cr_lrp_info['bridge_device'], + cr_lrp_info['bridge_vlan']) + else: + # VIP is on tenant network + ips_info = {'logical_switch': vip_router} + self._withdraw_remote_ip([vip_ip], ips_info) + + @lockutils.synchronized('nbbgp') + def expose_ovn_lb_fip(self, lb): + self._expose_ovn_lb_fip(lb) + + def _expose_ovn_lb_fip(self, lb): + vip_port = lb.external_ids.get(constants.OVN_LB_VIP_PORT_EXT_ID_KEY) + vip_lsp = self.nb_idl.lsp_get(vip_port).execute(check_error=True) + if not vip_lsp: + LOG.debug("Something went wrong, VIP port %s not found", vip_port) + return + + external_ip, external_mac, ls_name = ( + self.get_port_external_ip_and_ls(vip_lsp.name)) + if not external_ip or not ls_name: + LOG.debug("Something went wrong, no NAT entry for the VIP %s", + vip_port) + return + self._expose_fip(external_ip, external_mac, ls_name, vip_lsp) + + @lockutils.synchronized('nbbgp') + def withdraw_ovn_lb_fip(self, lb): + self._withdraw_ovn_lb_fip(lb) + + def _withdraw_ovn_lb_fip(self, lb): + vip_fip = lb.external_ids.get(constants.OVN_LB_VIP_FIP_EXT_ID_KEY) + # OVN loadbalancers ARPs are replied by router port + vip_router = lb.external_ids.get( + constants.OVN_LB_LR_REF_EXT_ID_KEY, "").replace('neutron-', "", 1) + if not vip_router: + return + cr_lrp_info = self.ovn_local_cr_lrps.get(vip_router) + if not cr_lrp_info: + return + self._withdraw_provider_port([vip_fip], + cr_lrp_info['provider_switch'], + cr_lrp_info['bridge_device'], + cr_lrp_info['bridge_vlan']) + def _address_scope_allowed(self, ip, address_scopes): if not self.allowed_address_scopes: # No address scopes to filter on => announce everything diff --git a/ovn_bgp_agent/drivers/openstack/utils/ovn.py b/ovn_bgp_agent/drivers/openstack/utils/ovn.py index e6f1608e..c8001ead 100644 --- a/ovn_bgp_agent/drivers/openstack/utils/ovn.py +++ b/ovn_bgp_agent/drivers/openstack/utils/ovn.py @@ -467,6 +467,16 @@ class OvsdbNbOvnIdl(nb_impl_idl.OvnNbApiIdlImpl, Backend): ports.extend(cmd.execute(check_error=True)) return ports + def get_active_local_lbs(self, local_gateway_ports): + lbs = [] + cmd = self.db_find_rows('Load_Balancer', ('vips', '!=', {})) + for row in cmd.execute(check_error=True): + if (row.vips and row.external_ids[ + constants.OVN_LB_LR_REF_EXT_ID_KEY].replace( + 'neutron-', "", 1) in local_gateway_ports): + lbs.append(row) + return lbs + # FIXME(ltomasbo): This can be removed once ovsdbapp version is >=2.3.0 def ls_get_localnet_ports(self, logical_switch, if_exists=True): return LSGetLocalnetPortsCommand(self, logical_switch, diff --git a/ovn_bgp_agent/drivers/openstack/watchers/base_watcher.py b/ovn_bgp_agent/drivers/openstack/watchers/base_watcher.py index a6ef5feb..29d1ecb0 100644 --- a/ovn_bgp_agent/drivers/openstack/watchers/base_watcher.py +++ b/ovn_bgp_agent/drivers/openstack/watchers/base_watcher.py @@ -42,14 +42,27 @@ class PortBindingChassisEvent(Event): return len(mac.strip().split(' ')) > 1 -class OVNLBMemberEvent(Event): +class OVNLBEvent(Event): def __init__(self, bgp_agent, events): self.agent = bgp_agent table = 'Load_Balancer' - super(OVNLBMemberEvent, self).__init__( + super(OVNLBEvent, self).__init__( events, table, None) self.event_name = self.__class__.__name__ + def _get_router(self, row): + try: + return row.external_ids[ + constants.OVN_LB_LR_REF_EXT_ID_KEY].replace('neutron-', "", 1) + except (AttributeError, KeyError): + return + + def _get_vip_fip(self, row): + try: + return row.external_ids[constants.OVN_LB_VIP_FIP_EXT_ID_KEY] + except (AttributeError, KeyError): + return + class LSPChassisEvent(Event): def __init__(self, bgp_agent, events): @@ -74,10 +87,10 @@ class LSPChassisEvent(Event): return None, None def _get_network(self, row): - if (hasattr(row, 'external_ids') and - row.external_ids.get(constants.OVN_LS_NAME_EXT_ID_KEY)): + try: return row.external_ids[constants.OVN_LS_NAME_EXT_ID_KEY] - return None + except (AttributeError, KeyError): + return class LRPChassisEvent(Event): @@ -89,7 +102,7 @@ class LRPChassisEvent(Event): self.event_name = self.__class__.__name__ def _get_network(self, row): - if (hasattr(row, 'external_ids') and - row.external_ids.get(constants.OVN_LS_NAME_EXT_ID_KEY)): + try: return row.external_ids[constants.OVN_LS_NAME_EXT_ID_KEY] - return None + except (AttributeError, KeyError): + return diff --git a/ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py b/ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py index ca6c83c3..6dde91a7 100644 --- a/ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py +++ b/ovn_bgp_agent/drivers/openstack/watchers/bgp_watcher.py @@ -372,7 +372,7 @@ class OVNLBVIPPortEvent(base_watcher.PortBindingChassisEvent): self.agent.expose_ovn_lb(ovn_lb_ip, row) -class OVNLBMemberCreateEvent(base_watcher.OVNLBMemberEvent): +class OVNLBMemberCreateEvent(base_watcher.OVNLBEvent): def __init__(self, bgp_agent): events = (self.ROW_CREATE,) super(OVNLBMemberCreateEvent, self).__init__( @@ -445,7 +445,7 @@ class OVNLBMemberCreateEvent(base_watcher.OVNLBMemberEvent): vip_ip, row.name, associated_cr_lrp_port) -class OVNLBMemberDeleteEvent(base_watcher.OVNLBMemberEvent): +class OVNLBMemberDeleteEvent(base_watcher.OVNLBEvent): def __init__(self, bgp_agent): events = (self.ROW_DELETE,) super(OVNLBMemberDeleteEvent, self).__init__( diff --git a/ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py b/ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py index abff211e..c298eebf 100644 --- a/ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py +++ b/ovn_bgp_agent/drivers/openstack/watchers/nb_bgp_watcher.py @@ -616,3 +616,122 @@ class LogicalSwitchPortTenantDeleteEvent(base_watcher.LSPChassisEvent): 'logical_switch': self._get_network(row) } self.agent.withdraw_remote_ip(ips, ips_info) + + +class OVNLBCreateEvent(base_watcher.OVNLBEvent): + def __init__(self, bgp_agent): + events = (self.ROW_UPDATE,) + super(OVNLBCreateEvent, self).__init__( + bgp_agent, events) + + def match_fn(self, event, row, old): + # The ovn lb balancers are exposed through the cr-lrp, so if the + # local agent does not have the matching router there is no need + # to process the event + try: + if not row.vips: + return False + lb_router = self._get_router(row) + if lb_router not in self.agent.ovn_local_cr_lrps.keys(): + return False + + # Only expose if there is a modification in the VIPS + # And only expose if it is the first item on VIPs + if hasattr(old, 'vips'): + if not old.vips and row.vips: + return True + + if hasattr(old, 'external_ids'): + # Check if the lb_router was added + old_lb_router = self._get_router(old) + if lb_router != old_lb_router: + return True + # Also check if there is a vip_fip addition to expose the FIP + vip_fip = self._get_vip_fip(row) + if not vip_fip: + return False + old_vip_fip = self._get_vip_fip(old) + if vip_fip != old_vip_fip: + return True + except (IndexError, AttributeError): + return False + return False + + def _run(self, event, row, old): + vip_fip = self._get_vip_fip(row) + old_vip_fip = self._get_vip_fip(old) + with _SYNC_STATE_LOCK.read_lock(): + if hasattr(old, 'external_ids'): + if vip_fip and vip_fip != old_vip_fip: + self.agent.expose_ovn_lb_fip(row) + else: + self.agent.expose_ovn_lb_vip(row) + + +class OVNLBDeleteEvent(base_watcher.OVNLBEvent): + def __init__(self, bgp_agent): + events = (self.ROW_DELETE, self.ROW_UPDATE) + super(OVNLBDeleteEvent, self).__init__( + bgp_agent, events) + + def match_fn(self, event, row, old): + # The ovn lb balancers are exposed through the cr-lrp, so if the + # local agent does not have the matching router there is no need + # to process the event + try: + if event == self.ROW_DELETE: + if not row.vips: + return False + lb_router = self._get_router(row) + if lb_router in self.agent.ovn_local_cr_lrps.keys(): + return True + return False + + # ROW UPDATE EVENT + lb_router = self._get_router(row) + old_external_ids = False + if hasattr(old, 'external_ids'): + old_external_ids = True + old_lb_router = self._get_router(old) + if not old_lb_router: + return False + if old_lb_router not in self.agent.ovn_local_cr_lrps.keys(): + return False + if old_lb_router != lb_router: + # Router should not be removed, but if that is the case we + # should remove the loadbalancer + return True + # Also check if the vip_fip is removed to withdraw the FIP + vip_fip = self._get_vip_fip(row) + old_vip_fip = self._get_vip_fip(old) + if old_vip_fip and old_vip_fip != vip_fip: + return True + + # Withdraw IP if VIPs is removed + if hasattr(old, 'vips'): + if old.vips and not row.vips: + if old_external_ids: + old_lb_router = self._get_router(old) + return (old_lb_router in + self.agent.ovn_local_cr_lrps.keys()) + else: + return (lb_router in + self.agent.ovn_local_cr_lrps.keys()) + except (IndexError, AttributeError): + return False + return False + + def _run(self, event, row, old): + vip_fip = self._get_vip_fip(row) + old_vip_fip = self._get_vip_fip(old) + with _SYNC_STATE_LOCK.read_lock(): + if event == self.ROW_DELETE: + self.agent.withdraw_ovn_lb_vip(row) + if vip_fip: + self.agent.withdraw_ovn_lb_fip(row) + else: + if not vip_fip and vip_fip != old_vip_fip: + self.agent.withdraw_ovn_lb_fip(old) + + if hasattr(old, 'vips'): + self.agent.withdraw_ovn_lb_vip(row) diff --git a/ovn_bgp_agent/tests/unit/drivers/openstack/test_nb_ovn_bgp_driver.py b/ovn_bgp_agent/tests/unit/drivers/openstack/test_nb_ovn_bgp_driver.py index e1433e92..75a51695 100644 --- a/ovn_bgp_agent/tests/unit/drivers/openstack/test_nb_ovn_bgp_driver.py +++ b/ovn_bgp_agent/tests/unit/drivers/openstack/test_nb_ovn_bgp_driver.py @@ -59,7 +59,8 @@ class TestNBOVNBGPDriver(test_base.TestCase): self.router1_info = {'bridge_device': self.bridge, 'bridge_vlan': 100, - 'ips': ['172.24.4.11']} + 'ips': ['172.24.4.11'], + 'provider_switch': 'provider-ls'} self.nb_bgp_driver.ovn_local_cr_lrps = { 'router1': self.router1_info} self.ovn_routing_tables = { @@ -165,16 +166,25 @@ class TestNBOVNBGPDriver(test_base.TestCase): port1 = fakes.create_object({ 'name': 'port-1', 'type': constants.OVN_CHASSISREDIRECT_VIF_PORT_TYPE}) + lb1 = fakes.create_object({ + 'name': 'lb1', + 'external_ids': {constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fake-fip'} + }) self.nb_idl.get_active_cr_lrp_on_chassis.return_value = [crlrp_port] self.nb_idl.get_active_local_lrps.return_value = [lrp0] self.nb_idl.get_active_lsp_on_chassis.return_value = [ port0, port1] + self.nb_idl.get_active_local_lbs.return_value = [lb1] mock_ensure_crlrp_exposed = mock.patch.object( self.nb_bgp_driver, '_ensure_crlrp_exposed').start() mock_expose_subnet = mock.patch.object( self.nb_bgp_driver, '_expose_subnet').start() mock_ensure_lsp_exposed = mock.patch.object( self.nb_bgp_driver, '_ensure_lsp_exposed').start() + mock_expose_ovn_lb_vip = mock.patch.object( + self.nb_bgp_driver, '_expose_ovn_lb_vip').start() + mock_expose_ovn_lb_fip = mock.patch.object( + self.nb_bgp_driver, '_expose_ovn_lb_fip').start() mock_routing_bridge.return_value = ['fake-route'] mock_nic_address.return_value = self.mac mock_get_patch_ports.return_value = [1, 2] @@ -211,6 +221,8 @@ class TestNBOVNBGPDriver(test_base.TestCase): 'network': 'network1', 'address_scopes': {4: None, 6: None}}) mock_ensure_lsp_exposed.assert_called_once_with(port0) + mock_expose_ovn_lb_vip.assert_called_once_with(lb1) + mock_expose_ovn_lb_fip.assert_called_once_with(lb1) mock_del_exposed_ips.assert_called_once_with( ips, CONF.bgp_nic) mock_del_ip_rules.assert_called_once_with(fake_ip_rules) @@ -443,13 +455,24 @@ class TestNBOVNBGPDriver(test_base.TestCase): self.nb_bgp_driver.ovn_bridge_mappings = {'fake-localnet': 'br-ex'} mock_expose_subnet = mock.patch.object( self.nb_bgp_driver, '_expose_subnet').start() - lrp0 = fakes.create_object({ - 'name': 'lrp_port', - 'external_ids': { - constants.OVN_CIDRS_EXT_ID_KEY: "10.0.0.1/24", - constants.OVN_LS_NAME_EXT_ID_KEY: 'network1', - constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'}}) - self.nb_idl.get_active_local_lrps.return_value = [lrp0] + + if (ips_info.get('router') and + ips_info['type'] == constants.OVN_CR_LRP_PORT_TYPE): + lrp0 = fakes.create_object({ + 'name': 'lrp_port', + 'external_ids': { + constants.OVN_CIDRS_EXT_ID_KEY: "10.0.0.1/24", + constants.OVN_LS_NAME_EXT_ID_KEY: 'network1', + constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'}}) + self.nb_idl.get_active_local_lrps.return_value = [lrp0] + lb1 = fakes.create_object({ + 'name': 'lb1', 'external_ids': { + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fake-fip'}}) + self.nb_idl.get_active_local_lbs.return_value = [lb1] + mock_expose_ovn_lb_vip = mock.patch.object( + self.nb_bgp_driver, '_expose_ovn_lb_vip').start() + mock_expose_ovn_lb_fip = mock.patch.object( + self.nb_bgp_driver, '_expose_ovn_lb_fip').start() self.nb_bgp_driver.expose_ip(ips, ips_info) @@ -480,6 +503,8 @@ class TestNBOVNBGPDriver(test_base.TestCase): ["10.0.0.1/24"], {'associated_router': 'router1', 'network': 'network1', 'address_scopes': {4: None, 6: None}}) + mock_expose_ovn_lb_vip.assert_called_once_with(lb1) + mock_expose_ovn_lb_fip.assert_called_once_with(lb1) def test_expose_ip(self): ips = [self.ipv4, self.ipv6] @@ -542,13 +567,23 @@ class TestNBOVNBGPDriver(test_base.TestCase): mock_withdraw_subnet = mock.patch.object( self.nb_bgp_driver, '_withdraw_subnet').start() - lrp0 = fakes.create_object({ - 'name': 'lrp_port', - 'external_ids': { - constants.OVN_CIDRS_EXT_ID_KEY: "10.0.0.1/24", - constants.OVN_LS_NAME_EXT_ID_KEY: 'network1', - constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'}}) - self.nb_idl.get_active_local_lrps.return_value = [lrp0] + if (ips_info.get('router') and + ips_info['type'] == constants.OVN_CR_LRP_PORT_TYPE): + lrp0 = fakes.create_object({ + 'name': 'lrp_port', + 'external_ids': { + constants.OVN_CIDRS_EXT_ID_KEY: "10.0.0.1/24", + constants.OVN_LS_NAME_EXT_ID_KEY: 'network1', + constants.OVN_DEVICE_ID_EXT_ID_KEY: 'router1'}}) + self.nb_idl.get_active_local_lrps.return_value = [lrp0] + lb1 = fakes.create_object({ + 'name': 'lb1', 'external_ids': { + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fake-fip'}}) + self.nb_idl.get_active_local_lbs.return_value = [lb1] + mock_withdraw_ovn_lb_vip = mock.patch.object( + self.nb_bgp_driver, '_withdraw_ovn_lb_vip').start() + mock_withdraw_ovn_lb_fip = mock.patch.object( + self.nb_bgp_driver, '_withdraw_ovn_lb_fip').start() self.nb_bgp_driver.withdraw_ip(ips, ips_info) @@ -578,6 +613,8 @@ class TestNBOVNBGPDriver(test_base.TestCase): ["10.0.0.1/24"], {'associated_router': 'router1', 'network': 'network1', 'address_scopes': {4: None, 6: None}}) + mock_withdraw_ovn_lb_vip.assert_called_once_with(lb1) + mock_withdraw_ovn_lb_fip.assert_called_once_with(lb1) def test_withdraw_ip(self): ips = [self.ipv4, self.ipv6] @@ -1113,3 +1150,243 @@ class TestNBOVNBGPDriver(test_base.TestCase): mock.ANY, ips[1], self.router1_info['bridge_device'], self.router1_info['bridge_vlan'], mock.ANY, self.router1_info['ips']) + + def test_expose_ovn_lb_vip_tenant(self): + self.nb_bgp_driver.ovn_local_lrps = {'net1': ['ip1']} + lb = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_PORT_EXT_ID_KEY: 'vip_port', + constants.OVN_LB_VIP_IP_EXT_ID_KEY: 'vip'}, + vips={'vip': 'member', 'fip': 'member'}) + + vip_lsp = utils.create_row( + external_ids={ + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1' + }) + self.nb_idl.lsp_get.return_value.execute.return_value = vip_lsp + mock_expose_remote_ip = mock.patch.object( + self.nb_bgp_driver, '_expose_remote_ip').start() + mock_expose_provider_port = mock.patch.object( + self.nb_bgp_driver, '_expose_provider_port').start() + + self.nb_bgp_driver.expose_ovn_lb_vip(lb) + + mock_expose_remote_ip.assert_called_once_with( + ['vip'], {'logical_switch': 'router1'} + ) + mock_expose_provider_port.assert_not_called() + + def test_expose_ovn_lb_vip_provider(self): + self.nb_bgp_driver.ovn_local_lrps = {'net1': ['ip1']} + lb = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_PORT_EXT_ID_KEY: 'vip_port', + constants.OVN_LB_VIP_IP_EXT_ID_KEY: 'vip'}, + vips={'vip': 'member', 'fip': 'member'}) + + vip_lsp = utils.create_row( + external_ids={ + constants.OVN_LS_NAME_EXT_ID_KEY: 'net2' + }) + self.nb_idl.lsp_get.return_value.execute.return_value = vip_lsp + mock_expose_remote_ip = mock.patch.object( + self.nb_bgp_driver, '_expose_remote_ip').start() + mock_expose_provider_port = mock.patch.object( + self.nb_bgp_driver, '_expose_provider_port').start() + mock_get_ls_localnet_info = mock.patch.object( + self.nb_bgp_driver, '_get_ls_localnet_info').start() + mock_get_ls_localnet_info.return_value = (None, None, None) + + self.nb_bgp_driver.expose_ovn_lb_vip(lb) + + mock_expose_remote_ip.assert_not_called() + mock_get_ls_localnet_info.assert_called_once_with('net2') + mock_expose_provider_port.assert_called_once_with( + ['vip'], None, 'net2', mock.ANY, mock.ANY, mock.ANY) + + def test_expose_ovn_lb_vip_no_vip(self): + self.nb_bgp_driver.ovn_local_lrps = {'net1': ['ip1']} + lb = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_PORT_EXT_ID_KEY: 'vip_port', + constants.OVN_LB_VIP_IP_EXT_ID_KEY: 'vip'}, + vips={'vip': 'member', 'fip': 'member'}) + + self.nb_idl.lsp_get.return_value.execute.return_value = None + mock_expose_remote_ip = mock.patch.object( + self.nb_bgp_driver, '_expose_remote_ip').start() + mock_expose_provider_port = mock.patch.object( + self.nb_bgp_driver, '_expose_provider_port').start() + + self.nb_bgp_driver.expose_ovn_lb_vip(lb) + + mock_expose_remote_ip.assert_not_called() + mock_expose_provider_port.assert_not_called() + + def test_withdraw_ovn_lb_vip_tenant(self): + lb = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_PORT_EXT_ID_KEY: 'vip_port', + constants.OVN_LB_VIP_IP_EXT_ID_KEY: 'vip'}, + vips={'vip': 'member', 'fip': 'member'}) + mock_withdraw_remote_ip = mock.patch.object( + self.nb_bgp_driver, '_withdraw_remote_ip').start() + mock_withdraw_provider_port = mock.patch.object( + self.nb_bgp_driver, '_withdraw_provider_port').start() + + self.nb_bgp_driver.withdraw_ovn_lb_vip(lb) + + mock_withdraw_provider_port.assert_not_called() + mock_withdraw_remote_ip.assert_called_once_with( + ['vip'], {'logical_switch': 'router1'}) + + def test_withdraw_ovn_lb_vip_provider(self): + self.nb_bgp_driver._exposed_ips = { + 'provider-ls': {'vip': {'bridge_device': self.bridge, + 'bridge_vlan': None}}} + lb = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_PORT_EXT_ID_KEY: 'vip_port', + constants.OVN_LB_VIP_IP_EXT_ID_KEY: 'vip'}, + vips={'vip': 'member', 'fip': 'member'}) + mock_withdraw_remote_ip = mock.patch.object( + self.nb_bgp_driver, '_withdraw_remote_ip').start() + mock_withdraw_provider_port = mock.patch.object( + self.nb_bgp_driver, '_withdraw_provider_port').start() + + self.nb_bgp_driver.withdraw_ovn_lb_vip(lb) + + mock_withdraw_provider_port.assert_called_once_with( + ['vip'], + self.router1_info['provider_switch'], + self.router1_info['bridge_device'], + self.router1_info['bridge_vlan']) + mock_withdraw_remote_ip.assert_not_called() + + def test_withdraw_ovn_lb_vip_no_router(self): + lb = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router2', + constants.OVN_LB_VIP_PORT_EXT_ID_KEY: 'vip_port', + constants.OVN_LB_VIP_IP_EXT_ID_KEY: 'vip'}, + vips={'vip': 'member', 'fip': 'member'}) + mock_withdraw_remote_ip = mock.patch.object( + self.nb_bgp_driver, '_withdraw_remote_ip').start() + mock_withdraw_provider_port = mock.patch.object( + self.nb_bgp_driver, '_withdraw_provider_port').start() + + self.nb_bgp_driver.withdraw_ovn_lb_vip(lb) + + mock_withdraw_remote_ip.assert_not_called() + mock_withdraw_provider_port.assert_not_called() + + def test_expose_ovn_lb_fip(self): + lb = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_PORT_EXT_ID_KEY: 'vip_port', + constants.OVN_LB_VIP_IP_EXT_ID_KEY: 'vip'}, + vips={'vip': 'member', 'fip': 'member'}) + vip_lsp = utils.create_row( + name='vip-port-name', + external_ids={ + constants.OVN_LS_NAME_EXT_ID_KEY: 'net2' + }) + self.nb_idl.lsp_get.return_value.execute.return_value = vip_lsp + mock_get_port_external_ip_and_ls = mock.patch.object( + self.nb_bgp_driver, 'get_port_external_ip_and_ls').start() + mock_get_port_external_ip_and_ls.return_value = ('fip', + 'fip-mac', + 'provider-ls') + mock_expose_fip = mock.patch.object( + self.nb_bgp_driver, '_expose_fip').start() + + self.nb_bgp_driver.expose_ovn_lb_fip(lb) + mock_expose_fip.assert_called_once_with( + 'fip', 'fip-mac', 'provider-ls', vip_lsp) + + def test_expose_ovn_lb_fip_no_vip_port(self): + lb = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_PORT_EXT_ID_KEY: 'vip_port', + constants.OVN_LB_VIP_IP_EXT_ID_KEY: 'vip'}, + vips={'vip': 'member', 'fip': 'member'}) + self.nb_idl.lsp_get.return_value.execute.return_value = None + mock_expose_fip = mock.patch.object( + self.nb_bgp_driver, '_expose_fip').start() + + self.nb_bgp_driver.expose_ovn_lb_fip(lb) + mock_expose_fip.assert_not_called() + + def test_expose_ovn_lb_fip_no_external_ip(self): + lb = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_PORT_EXT_ID_KEY: 'vip_port', + constants.OVN_LB_VIP_IP_EXT_ID_KEY: 'vip'}, + vips={'vip': 'member', 'fip': 'member'}) + vip_lsp = utils.create_row( + name='vip-port-name', + external_ids={ + constants.OVN_LS_NAME_EXT_ID_KEY: 'net2' + }) + self.nb_idl.lsp_get.return_value.execute.return_value = vip_lsp + mock_get_port_external_ip_and_ls = mock.patch.object( + self.nb_bgp_driver, 'get_port_external_ip_and_ls').start() + mock_get_port_external_ip_and_ls.return_value = (None, None, None) + mock_expose_fip = mock.patch.object( + self.nb_bgp_driver, '_expose_fip').start() + + self.nb_bgp_driver.expose_ovn_lb_fip(lb) + mock_expose_fip.assert_not_called() + + def test_withdraw_ovn_lb_fip(self): + lb = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_PORT_EXT_ID_KEY: 'vip_port', + constants.OVN_LB_VIP_IP_EXT_ID_KEY: 'vip', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'vip-fip'}, + vips={'vip': 'member', 'fip': 'member'}) + mock_withdraw_provider_port = mock.patch.object( + self.nb_bgp_driver, '_withdraw_provider_port').start() + + self.nb_bgp_driver.withdraw_ovn_lb_fip(lb) + mock_withdraw_provider_port.assert_called_once_with( + ['vip-fip'], + self.router1_info['provider_switch'], + self.router1_info['bridge_device'], + self.router1_info['bridge_vlan']) + + def test_withdraw_ovn_lb_fip_no_vip_router(self): + lb = utils.create_row( + external_ids={ + constants.OVN_LB_VIP_PORT_EXT_ID_KEY: 'vip_port', + constants.OVN_LB_VIP_IP_EXT_ID_KEY: 'vip', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'vip-fip'}, + vips={'vip': 'member', 'fip': 'member'}) + mock_withdraw_provider_port = mock.patch.object( + self.nb_bgp_driver, '_withdraw_provider_port').start() + + self.nb_bgp_driver.withdraw_ovn_lb_fip(lb) + mock_withdraw_provider_port.assert_not_called() + + def test_withdraw_ovn_lb_fip_no_cr_lrp(self): + lb = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router2', + constants.OVN_LB_VIP_PORT_EXT_ID_KEY: 'vip_port', + constants.OVN_LB_VIP_IP_EXT_ID_KEY: 'vip', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'vip-fip'}, + vips={'vip': 'member', 'fip': 'member'}) + mock_withdraw_provider_port = mock.patch.object( + self.nb_bgp_driver, '_withdraw_provider_port').start() + + self.nb_bgp_driver.withdraw_ovn_lb_fip(lb) + mock_withdraw_provider_port.assert_not_called() diff --git a/ovn_bgp_agent/tests/unit/drivers/openstack/utils/test_ovn.py b/ovn_bgp_agent/tests/unit/drivers/openstack/utils/test_ovn.py index 908aa7a8..df1a4715 100644 --- a/ovn_bgp_agent/tests/unit/drivers/openstack/utils/test_ovn.py +++ b/ovn_bgp_agent/tests/unit/drivers/openstack/utils/test_ovn.py @@ -203,6 +203,24 @@ class TestOvsdbNbOvnIdl(test_base.TestCase): self.nb_idl.db_find_rows.assert_has_calls(expected_calls) + def test_get_active_local_lbs(self): + local_gateway_ports = ['router1'] + lb1 = fakes.create_object({ + 'vips': {'vip': 'member1,member2'}, + 'external_ids': { + constants.OVN_LB_LR_REF_EXT_ID_KEY: "neutron-router1"}}) + lb2 = fakes.create_object({ + 'vips': {'vip': 'member1,member2'}, + 'external_ids': { + constants.OVN_LB_LR_REF_EXT_ID_KEY: "neutron-router2"}}) + self.nb_idl.db_find_rows.return_value.execute.return_value = [lb1, lb2] + + ret = self.nb_idl.get_active_local_lbs(local_gateway_ports) + + self.assertEqual([lb1], ret) + self.nb_idl.db_find_rows.assert_called_once_with( + 'Load_Balancer', ('vips', '!=', {})) + class TestOvsdbSbOvnIdl(test_base.TestCase): diff --git a/ovn_bgp_agent/tests/unit/drivers/openstack/watchers/test_base_watcher.py b/ovn_bgp_agent/tests/unit/drivers/openstack/watchers/test_base_watcher.py index 9c66ac8b..d96650d0 100644 --- a/ovn_bgp_agent/tests/unit/drivers/openstack/watchers/test_base_watcher.py +++ b/ovn_bgp_agent/tests/unit/drivers/openstack/watchers/test_base_watcher.py @@ -44,6 +44,33 @@ class TestPortBindingChassisEvent(test_base.TestCase): 'aa:bb:cc:dd:ee:ff 10.10.1.16 10.10.1.17 10.10.1.18')) +class FakeOVNLBEvent(base_watcher.OVNLBEvent): + def run(self): + pass + + +class TestOVNLBEvent(test_base.TestCase): + + def setUp(self): + super(TestOVNLBEvent, self).setUp() + self.ovnlb_event = FakeOVNLBEvent( + mock.Mock(), [mock.Mock()]) + + def test__get_router(self): + row = utils.create_row( + external_ids={constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-net'}) + self.assertEqual('net', self.ovnlb_event._get_router(row)) + row = utils.create_row(external_ids={}) + self.assertEqual(None, self.ovnlb_event._get_router(row)) + + def test__get_vip_fip(self): + row = utils.create_row( + external_ids={constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip'}) + self.assertEqual('fip', self.ovnlb_event._get_vip_fip(row)) + row = utils.create_row(external_ids={}) + self.assertEqual(None, self.ovnlb_event._get_vip_fip(row)) + + class FakeLSPChassisEvent(base_watcher.LSPChassisEvent): def run(self): pass diff --git a/ovn_bgp_agent/tests/unit/drivers/openstack/watchers/test_nb_bgp_watcher.py b/ovn_bgp_agent/tests/unit/drivers/openstack/watchers/test_nb_bgp_watcher.py index 4f2c8a2c..82d61c85 100644 --- a/ovn_bgp_agent/tests/unit/drivers/openstack/watchers/test_nb_bgp_watcher.py +++ b/ovn_bgp_agent/tests/unit/drivers/openstack/watchers/test_nb_bgp_watcher.py @@ -1098,3 +1098,291 @@ class TestLogicalSwitchPortTenantDeleteEvent(test_base.TestCase): up=[True]) self.event.run(None, row, mock.Mock()) self.agent.withdraw_remote_ip.assert_not_called() + + +class TestOVNLBCreateEvent(test_base.TestCase): + def setUp(self): + super(TestOVNLBCreateEvent, self).setUp() + self.chassis = 'fake-chassis' + self.chassis_id = 'fake-chassis-id' + self.agent = mock.Mock(chassis=self.chassis, + chassis_id=self.chassis_id) + self.agent.ovn_local_cr_lrps = { + 'router1': {'bridge_device': 'br-ex', + 'bridge_vlan': None, + 'ips': ['172.24.16.2']}} + self.event = nb_bgp_watcher.OVNLBCreateEvent( + self.agent) + + def test_match_fn(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + old = utils.create_row(vips={}) + self.assertTrue(self.event.match_fn(mock.Mock(), row, old)) + + def test_match_fn_router_added(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + old = utils.create_row(external_ids={}) + self.assertTrue(self.event.match_fn(mock.Mock(), row, old)) + + def test_match_fn_fip_added(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + old = utils.create_row(external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1'}) + self.assertTrue(self.event.match_fn(mock.Mock(), row, old)) + + def test_match_fn_no_vips(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={}) + self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock())) + + def test_match_fn_no_local_crlrp(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router2', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + self.assertFalse(self.event.match_fn(mock.Mock(), row, mock.Mock())) + + def test_run_vip(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + old = utils.create_row(vips={}) + + self.event.run(None, row, old) + + self.agent.expose_ovn_lb_vip.assert_called_once_with(row) + self.agent.expose_ovn_lb_fip.assert_not_called() + + def test_run_vip_added_extra_ext_id_info(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1', + 'other': 'info'}, + vips={'vip': 'member', 'fip': 'member'}) + old = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}) + + self.event.run(None, row, old) + + self.agent.expose_ovn_lb_vip.assert_not_called() + self.agent.expose_ovn_lb_fip.assert_not_called() + + def test_run_fip(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + old = utils.create_row(external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1'}) + + self.event.run(None, row, old) + + self.agent.expose_ovn_lb_vip.assert_not_called() + self.agent.expose_ovn_lb_fip.assert_called_once_with(row) + + +class TestOVNLBDeleteEvent(test_base.TestCase): + def setUp(self): + super(TestOVNLBDeleteEvent, self).setUp() + self.chassis = 'fake-chassis' + self.chassis_id = 'fake-chassis-id' + self.agent = mock.Mock(chassis=self.chassis, + chassis_id=self.chassis_id) + self.agent.ovn_local_cr_lrps = { + 'router1': {'bridge_device': 'br-ex', + 'bridge_vlan': None, + 'ips': ['172.24.16.2']}} + self.event = nb_bgp_watcher.OVNLBDeleteEvent( + self.agent) + + def test_match_fn(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={}) + old = utils.create_row(vips={'vip': 'member', 'fip': 'member'}) + self.assertTrue(self.event.match_fn(mock.Mock(), row, old)) + + def test_match_fn_delete(self): + event = self.event.ROW_DELETE + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + self.assertTrue(self.event.match_fn(event, row, mock.Mock())) + + def test_match_fn_delete_no_vips(self): + event = self.event.ROW_DELETE + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={}) + self.assertFalse(self.event.match_fn(event, row, mock.Mock())) + + def test_match_fn_delete_no_local_router(self): + event = self.event.ROW_DELETE + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router2', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + self.assertFalse(self.event.match_fn(event, row, mock.Mock())) + + def test_match_fn_router_deleted(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + old = utils.create_row(external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1' + }) + self.assertTrue(self.event.match_fn(mock.Mock(), row, old)) + + def test_match_fn_no_old_router(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + old = utils.create_row(external_ids={}) + self.assertFalse(self.event.match_fn(mock.Mock(), row, old)) + + def test_match_fn_old_router_non_local(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + old = utils.create_row(external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router2', + }) + self.assertFalse(self.event.match_fn(mock.Mock(), row, old)) + + def test_match_fn_fip_deleted(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + old = utils.create_row(external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip'}) + self.assertTrue(self.event.match_fn(mock.Mock(), row, old)) + + def test_match_fn_vip_deleted_with_ext_id_update(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={}) + old = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1', + 'other': 'info'}, + vips={'vip': 'member', 'fip': 'member'}) + self.assertTrue(self.event.match_fn(mock.Mock(), row, old)) + + def test_run_vip(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={}) + old = utils.create_row(vips={'vip': 'member'}) + + self.event.run(None, row, old) + + self.agent.withdraw_ovn_lb_vip.assert_called_once_with(row) + self.agent.withdraw_ovn_lb_fip.assert_not_called() + + def test_run_vip_delete(self): + event = self.event.ROW_DELETE + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={}) + + self.event.run(event, row, None) + + self.agent.withdraw_ovn_lb_vip.assert_called_once_with(row) + self.agent.withdraw_ovn_lb_fip.assert_called_once_with(row) + + def test_run_vip_deleted_extra_ext_id_info(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + old = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1', + 'other': 'info'}) + + self.event.run(None, row, old) + + self.agent.withdraw_ovn_lb_vip.assert_not_called() + self.agent.withdraw_ovn_lb_fip.assert_not_called() + + def test_run_fip(self): + row = utils.create_row( + external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LS_NAME_EXT_ID_KEY: 'net1'}, + vips={'vip': 'member', 'fip': 'member'}) + old = utils.create_row(external_ids={ + constants.OVN_LB_LR_REF_EXT_ID_KEY: 'neutron-router1', + constants.OVN_LB_VIP_FIP_EXT_ID_KEY: 'fip'}) + + self.event.run(None, row, old) + + self.agent.withdraw_ovn_lb_vip.assert_not_called() + self.agent.withdraw_ovn_lb_fip.assert_called_once_with(old)