diff --git a/etc/l3_agent.ini b/etc/l3_agent.ini index e6903988d4..c8eafc841b 100644 --- a/etc/l3_agent.ini +++ b/etc/l3_agent.ini @@ -77,3 +77,14 @@ # Timeout for ovs-vsctl commands. # If the timeout expires, ovs commands will fail with ALARMCLOCK error. # ovs_vsctl_timeout = 10 + +# The working mode for the agent. Allowed values are: +# - legacy: this preserves the existing behavior where the L3 agent is +# deployed on a centralized networking node to provide L3 services +# like DNAT, and SNAT. Use this mode if you do not want to adopt DVR. +# - dvr: this mode enables DVR functionality, and must be used for an L3 +# agent that runs on a compute host. +# - dvr_snat: this enables centralized SNAT support in conjunction with +# DVR. This mode must be used for an L3 agent running on a centralized +# node (or in single-host deployments, e.g. devstack). +# agent_mode = legacy diff --git a/neutron/agent/l3_agent.py b/neutron/agent/l3_agent.py index d0a59a92b7..9d7ec12195 100644 --- a/neutron/agent/l3_agent.py +++ b/neutron/agent/l3_agent.py @@ -28,7 +28,6 @@ from neutron.agent.linux import external_process from neutron.agent.linux import interface from neutron.agent.linux import ip_lib from neutron.agent.linux import iptables_manager -from neutron.agent.linux import ovs_lib # noqa from neutron.agent.linux import ra from neutron.agent import rpc as agent_rpc from neutron.common import config as common_config @@ -53,6 +52,18 @@ LOG = logging.getLogger(__name__) NS_PREFIX = 'qrouter-' INTERNAL_DEV_PREFIX = 'qr-' EXTERNAL_DEV_PREFIX = 'qg-' +SNAT_INT_DEV_PREFIX = 'sg-' +FIP_NS_PREFIX = 'fip-' +SNAT_NS_PREFIX = 'snat-' +FIP_2_ROUTER_DEV_PREFIX = 'fpr-' +ROUTER_2_FIP_DEV_PREFIX = 'rfp-' +FIP_EXT_DEV_PREFIX = 'fg-' +FIP_LL_PREFIX = '169.254.30.' +# Route Table index for FIPs +FIP_RT_TBL = 16 +# Rule priority range for FIPs +FIP_PR_START = 32768 +FIP_PR_END = FIP_PR_START + 40000 RPC_LOOP_INTERVAL = 1 FLOATING_IP_CIDR_SUFFIX = '/32' # Lower value is higher priority @@ -67,6 +78,10 @@ class L3PluginApi(n_rpc.RpcProxy): API version history: 1.0 - Initial version. 1.1 - Floating IP operational status updates + 1.2 - DVR support: new L3 plugin methods added. + - get_ports_by_subnet + - get_agent_gateway_port + Needed by the agent when operating in DVR/DVR_SNAT mode """ @@ -105,6 +120,22 @@ class L3PluginApi(n_rpc.RpcProxy): topic=self.topic, version='1.1') + def get_ports_by_subnet(self, context, subnet_id): + """Retrieve ports by subnet id.""" + return self.call(context, + self.make_msg('get_ports_by_subnet', host=self.host, + subnet_id=subnet_id), + topic=self.topic, + version='1.2') + + def get_agent_gateway_port(self, context, fip_net): + """Get or create an agent_gateway_port.""" + return self.call(context, + self.make_msg('get_agent_gateway_port', + network_id=fip_net, host=self.host), + topic=self.topic, + version='1.2') + class RouterInfo(object): @@ -114,7 +145,9 @@ class RouterInfo(object): self._snat_enabled = None self._snat_action = None self.internal_ports = [] + self.snat_ports = [] self.floating_ips = set() + self.floating_ips_dict = {} self.root_helper = root_helper self.use_namespaces = use_namespaces # Invoke the setter for establishing initial SNAT action @@ -125,6 +158,12 @@ class RouterInfo(object): #FIXME(danwent): use_ipv6=True, namespace=self.ns_name) self.routes = [] + # DVR Data + # Linklocal router to floating IP addr + self.rtr_2_fip = None + # Linklocal floating to router IP addr + self.fip_2_rtr = None + self.dist_fip_count = 0 @property def router(self): @@ -301,10 +340,27 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): It was previously a list of routers in dict format. It is now a list of router IDs only. Per rpc versioning rules, it is backwards compatible. + 1.2 - DVR support: new L3 agent methods added. + - add_arp_entry + - del_arp_entry + Needed by the L3 service when dealing with DVR """ - RPC_API_VERSION = '1.1' + RPC_API_VERSION = '1.2' OPTS = [ + cfg.StrOpt('agent_mode', default='legacy', + help=_("The working mode for the agent. Allowed modes are: " + "'legacy' - this preserves the existing behavior " + "where the L3 agent is deployed on a centralized " + "networking node to provide L3 services like DNAT, " + "and SNAT. Use this mode if you do not want to " + "adopt DVR. 'dvr' - this mode enables DVR " + "functionality and must be used for an L3 agent " + "that runs on a compute host. 'dvr_snat' - this " + "enables centralized SNAT support in conjunction " + "with DVR. This mode must be used for an L3 agent " + "running on a centralized node (or in single-host " + "deployments, e.g. devstack)")), cfg.StrOpt('external_network_bridge', default='br-ex', help=_("Name of bridge used for external network " "traffic.")), @@ -366,6 +422,12 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): self._clean_stale_namespaces = self.conf.use_namespaces + # dvr data + self.agent_gateway_port = None + self.agent_fip_count = 0 + self.local_ips = set(range(2, 251)) + self.fip_priorities = set(range(FIP_PR_START, FIP_PR_END)) + self._queue = RouterProcessingQueue() super(L3NATAgent, self).__init__(conf=self.conf) @@ -425,41 +487,89 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): one attempt will be made to delete them. """ for ns in router_namespaces: - if self.conf.enable_metadata_proxy: - self._destroy_metadata_proxy(ns[len(NS_PREFIX):], ns) - ra.disable_ipv6_ra(ns[len(NS_PREFIX):], ns, self.root_helper) try: - self._destroy_router_namespace(ns) + self._destroy_namespace(ns) except RuntimeError: LOG.exception(_('Failed to destroy stale router namespace ' '%s'), ns) self._clean_stale_namespaces = False - def _destroy_router_namespace(self, namespace): - ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=namespace) + def _destroy_namespace(self, ns): + if ns.startswith(NS_PREFIX): + if self.conf.enable_metadata_proxy: + self._destroy_metadata_proxy(ns[len(NS_PREFIX):], ns) + self._destroy_router_namespace(ns) + elif ns.startswith(FIP_NS_PREFIX): + self._destroy_fip_namespace(ns) + elif ns.startswith(SNAT_NS_PREFIX): + self._destroy_snat_namespace(ns) + + def _delete_namespace(self, ns_ip, ns): + try: + ns_ip.netns.delete(ns) + except RuntimeError: + msg = _('Failed trying to delete namespace: %s') % ns + LOG.exception(msg) + + def _destroy_snat_namespace(self, ns): + ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=ns) + # delete internal interfaces + for d in ns_ip.get_devices(exclude_loopback=True): + if d.name.startswith(SNAT_INT_DEV_PREFIX): + LOG.debug('Unplugging DVR device %s', d.name) + self.driver.unplug(d.name, namespace=ns, + prefix=SNAT_INT_DEV_PREFIX) + + # TODO(mrsmith): delete ext-gw-port + LOG.debug('DVR: destroy snat ns: %s', ns) + if self.conf.router_delete_namespaces: + self._delete_namespace(ns_ip, ns) + + def _destroy_fip_namespace(self, ns): + ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=ns) + for d in ns_ip.get_devices(exclude_loopback=True): + if d.name.startswith(FIP_2_ROUTER_DEV_PREFIX): + # internal link between IRs and FIP NS + # TODO(mrsmith): remove IR interfaces (IP pool?) + pass + elif d.name.startswith(FIP_EXT_DEV_PREFIX): + # single port from FIP NS to br-ext + # TODO(mrsmith): remove br-ext interface + LOG.debug('DVR: unplug: %s', d.name) + self.driver.unplug(d.name, + bridge=self.conf.external_network_bridge, + namespace=ns, + prefix=FIP_EXT_DEV_PREFIX) + LOG.debug('DVR: destroy fip ns: %s', ns) + # TODO(mrsmith): add LOG warn if fip count != 0 + if self.conf.router_delete_namespaces: + self._delete_namespace(ns_ip, ns) + self.agent_gateway_port = None + + def _destroy_router_namespace(self, ns): + ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=ns) for d in ns_ip.get_devices(exclude_loopback=True): if d.name.startswith(INTERNAL_DEV_PREFIX): # device is on default bridge - self.driver.unplug(d.name, namespace=namespace, + self.driver.unplug(d.name, namespace=ns, prefix=INTERNAL_DEV_PREFIX) elif d.name.startswith(EXTERNAL_DEV_PREFIX): self.driver.unplug(d.name, bridge=self.conf.external_network_bridge, - namespace=namespace, + namespace=ns, prefix=EXTERNAL_DEV_PREFIX) if self.conf.router_delete_namespaces: - try: - ns_ip.netns.delete(namespace) - except RuntimeError: - msg = _('Failed trying to delete namespace: %s') - LOG.exception(msg % namespace) + self._delete_namespace(ns_ip, ns) + + def _create_namespace(self, name): + ip_wrapper_root = ip_lib.IPWrapper(self.root_helper) + ip_wrapper = ip_wrapper_root.ensure_namespace(name) + ip_wrapper.netns.execute(['sysctl', '-w', 'net.ipv4.ip_forward=1']) def _create_router_namespace(self, ri): - ip_wrapper_root = ip_lib.IPWrapper(self.root_helper) - ip_wrapper = ip_wrapper_root.ensure_namespace(ri.ns_name) - ip_wrapper.netns.execute(['sysctl', '-w', 'net.ipv4.ip_forward=1']) + self._create_namespace(ri.ns_name) def _fetch_external_net_id(self, force=False): """Find UUID of single external network for this agent.""" @@ -553,6 +663,24 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): ns_name) pm.disable() + def _set_subnet_arp_info(self, ri, port): + """Set ARP info retrieved from Plugin for existing ports.""" + if 'id' not in port['subnet'] or not ri.router['distributed']: + return + subnet_id = port['subnet']['id'] + subnet_ports = ( + self.plugin_rpc.get_ports_by_subnet(self.context, + subnet_id)) + + for p in subnet_ports: + if (p['device_owner'] not in ( + l3_constants.DEVICE_OWNER_ROUTER_INTF, + l3_constants.DEVICE_OWNER_DVR_INTERFACE)): + for fixed_ip in p['fixed_ips']: + self._update_arp_entry(ri, fixed_ip['ip_address'], + p['mac_address'], + subnet_id, 'add') + def _set_subnet_info(self, port): ips = port['fixed_ips'] if not ips: @@ -570,9 +698,13 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): return [ip_dev.name for ip_dev in ip_devs] def process_router(self, ri): + # TODO(mrsmith) - we shouldn't need to check here + if 'distributed' not in ri.router: + ri.router['distributed'] = False ri.iptables_manager.defer_apply_on() ex_gw_port = self._get_ex_gw_port(ri) internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, []) + snat_ports = ri.router.get(l3_constants.SNAT_ROUTER_INTF_KEY, []) existing_port_ids = set([p['id'] for p in ri.internal_ports]) current_port_ids = set([p['id'] for p in internal_ports if p['admin_state_up']]) @@ -586,15 +718,15 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): old_ipv6_port = False for p in new_ports: self._set_subnet_info(p) - self.internal_network_added(ri, p['network_id'], p['id'], - p['ip_cidr'], p['mac_address']) + self.internal_network_added(ri, p) ri.internal_ports.append(p) + self._set_subnet_arp_info(ri, p) if (not new_ipv6_port and netaddr.IPNetwork(p['subnet']['cidr']).version == 6): new_ipv6_port = True for p in old_ports: - self.internal_network_removed(ri, p['id'], p['ip_cidr']) + self.internal_network_removed(ri, p) ri.internal_ports.remove(p) if (not old_ipv6_port and netaddr.IPNetwork(p['subnet']['cidr']).version == 6): @@ -653,8 +785,10 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): # Process static routes for router self.routes_updated(ri) # Process SNAT rules for external gateway - ri.perform_snat_action(self._handle_router_snat_rules, - internal_cidrs, interface_name) + if (not ri.router['distributed'] or + ex_gw_port and ri.router['gw_port_host'] == self.host): + ri.perform_snat_action(self._handle_router_snat_rules, + internal_cidrs, interface_name) # Process SNAT/DNAT rules for floating IPs fip_statuses = {} @@ -684,6 +818,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): # Update ex_gw_port and enable_snat on the router info cache ri.ex_gw_port = ex_gw_port + ri.snat_ports = snat_ports ri.enable_snat = ri.router.get('enable_snat') def _handle_router_snat_rules(self, ri, ex_gw_port, internal_cidrs, @@ -692,13 +827,19 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): # This is safe because if use_namespaces is set as False # then the agent can only configure one router, otherwise # each router's SNAT rules will be in their own namespace - ri.iptables_manager.ipv4['nat'].empty_chain('POSTROUTING') - ri.iptables_manager.ipv4['nat'].empty_chain('snat') + if ri.router['distributed']: + iptables_manager = ri.snat_iptables_manager + else: + iptables_manager = ri.iptables_manager - # Add back the jump to float-snat - ri.iptables_manager.ipv4['nat'].add_rule('snat', '-j $float-snat') + iptables_manager.ipv4['nat'].empty_chain('POSTROUTING') + iptables_manager.ipv4['nat'].empty_chain('snat') - # And add them back if the action if add_rules + if not ri.router['distributed']: + # Add back the jump to float-snat + iptables_manager.ipv4['nat'].add_rule('snat', '-j $float-snat') + + # And add them back if the action is add_rules if action == 'add_rules' and ex_gw_port: # ex_gw_port should not be None in this case # NAT rules are added only if ex_gw_port has an IPv4 address @@ -709,8 +850,31 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): internal_cidrs, interface_name) for rule in rules: - ri.iptables_manager.ipv4['nat'].add_rule(*rule) + iptables_manager.ipv4['nat'].add_rule(*rule) break + iptables_manager.apply() + + def _handle_router_fip_nat_rules(self, ri, interface_name, action): + """Configures NAT rules for Floating IPs for DVR. + + Remove all the rules. This is safe because if + use_namespaces is set as False then the agent can + only configure one router, otherwise each router's + NAT rules will be in their own namespace. + """ + ri.iptables_manager.ipv4['nat'].empty_chain('POSTROUTING') + ri.iptables_manager.ipv4['nat'].empty_chain('snat') + + # Add back the jump to float-snat + ri.iptables_manager.ipv4['nat'].add_rule('snat', '-j $float-snat') + + # And add them back if the action is add_rules + if action == 'add_rules' and interface_name: + rule = ('POSTROUTING', '! -i %(interface_name)s ' + '! -o %(interface_name)s -m conntrack ! ' + '--ctstate DNAT -j ACCEPT' % + {'interface_name': interface_name}) + ri.iptables_manager.ipv4['nat'].add_rule(*rule) ri.iptables_manager.apply() def process_router_floating_ip_nat_rules(self, ri): @@ -721,8 +885,9 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): # Clear out all iptables rules for floating ips ri.iptables_manager.ipv4['nat'].clear_rules_by_tag('floating_ip') + floating_ips = self.get_floating_ips(ri) # Loop once to ensure that floating ips are configured. - for fip in ri.router.get(l3_constants.FLOATINGIP_KEY, []): + for fip in floating_ips: # Rebuild iptables rules for the floating ip. fixed = fip['fixed_ip_address'] fip_ip = fip['floating_ip_address'] @@ -739,14 +904,33 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): those that should not longer be configured. """ fip_statuses = {} - interface_name = self.get_external_device_name(ex_gw_port['id']) + + floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, []) + if ri.router['distributed']: + # filter out only FIPs for this host/agent + floating_ips = [i for i in floating_ips if i['host'] == self.host] + if floating_ips and self.agent_gateway_port is None: + self._create_agent_gateway_port(ri, floating_ips[0] + ['floating_network_id']) + + if self.agent_gateway_port: + if floating_ips and ri.dist_fip_count == 0: + self.create_rtr_2_fip_link(ri, floating_ips[0] + ['floating_network_id']) + interface_name = self.get_rtr_int_device_name(ri.router_id) + else: + # there are no fips or agent port, no work to do + return fip_statuses + else: + interface_name = self.get_external_device_name(ex_gw_port['id']) + device = ip_lib.IPDevice(interface_name, self.root_helper, namespace=ri.ns_name) existing_cidrs = set([addr['cidr'] for addr in device.addr.list()]) new_cidrs = set() # Loop once to ensure that floating ips are configured. - for fip in ri.router.get(l3_constants.FLOATINGIP_KEY, []): + for fip in floating_ips: fip_ip = fip['floating_ip_address'] ip_cidr = str(fip_ip) + FLOATING_IP_CIDR_SUFFIX @@ -765,10 +949,15 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): LOG.warn(_("Unable to configure IP address for " "floating IP: %s"), fip['id']) continue - # As GARP is processed in a distinct thread the call below - # won't raise an exception to be handled. - self._send_gratuitous_arp_packet( - ri, interface_name, fip_ip) + if ri.router['distributed']: + # Special Handling for DVR - update FIP namespace + # and ri.namespace to handle DVR based FIP + self.floating_ip_added_dist(ri, fip) + else: + # As GARP is processed in a distinct thread the call below + # won't raise an exception to be handled. + self._send_gratuitous_arp_packet( + ri.ns_name, interface_name, fip_ip) fip_statuses[fip['id']] = ( l3_constants.FLOATINGIP_STATUS_ACTIVE) @@ -777,26 +966,48 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): if ip_cidr.endswith(FLOATING_IP_CIDR_SUFFIX): net = netaddr.IPNetwork(ip_cidr) device.addr.delete(net.version, ip_cidr) + if ri.router['distributed']: + self.floating_ip_removed_dist(ri, ip_cidr) return fip_statuses def _get_ex_gw_port(self, ri): return ri.router.get('gw_port') - def _arping(self, ri, interface_name, ip_address): + def _arping(self, ns_name, interface_name, ip_address, distributed=False): + if distributed: + device = ip_lib.IPDevice(interface_name, self.root_helper, + namespace=ns_name) + ip_cidr = str(ip_address) + FLOATING_IP_CIDR_SUFFIX + net = netaddr.IPNetwork(ip_cidr) + device.addr.add(net.version, ip_cidr, str(net.broadcast)) + arping_cmd = ['arping', '-A', '-I', interface_name, '-c', self.conf.send_arp_for_ha, ip_address] try: ip_wrapper = ip_lib.IPWrapper(self.root_helper, - namespace=ri.ns_name) + namespace=ns_name) ip_wrapper.netns.execute(arping_cmd, check_exit_code=True) except Exception as e: LOG.error(_("Failed sending gratuitous ARP: %s"), str(e)) + if distributed: + device.addr.delete(net.version, ip_cidr) - def _send_gratuitous_arp_packet(self, ri, interface_name, ip_address): + def _send_gratuitous_arp_packet(self, ns_name, interface_name, ip_address, + distributed=False): if self.conf.send_arp_for_ha > 0: - eventlet.spawn_n(self._arping, ri, interface_name, ip_address) + eventlet.spawn_n(self._arping, ns_name, interface_name, ip_address, + distributed) + + def get_internal_port(self, ri, subnet_id): + """Return internal router port based on subnet_id.""" + router_ports = ri.router.get(l3_constants.INTERFACE_KEY, []) + for port in router_ports: + fips = port['fixed_ips'] + for f in fips: + if f['subnet_id'] == subnet_id: + return port def get_internal_device_name(self, port_id): return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] @@ -804,38 +1015,184 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): def get_external_device_name(self, port_id): return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] + def get_fip_ext_device_name(self, port_id): + return (FIP_EXT_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] + + def get_rtr_int_device_name(self, router_id): + return (ROUTER_2_FIP_DEV_PREFIX + router_id)[:self.driver.DEV_NAME_LEN] + + def get_fip_int_device_name(self, router_id): + return (FIP_2_ROUTER_DEV_PREFIX + router_id)[:self.driver.DEV_NAME_LEN] + + def get_snat_int_device_name(self, port_id): + return (SNAT_INT_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] + + def get_fip_ns_name(self, ext_net_id): + return (FIP_NS_PREFIX + ext_net_id) + + def get_snat_ns_name(self, router_id): + return (SNAT_NS_PREFIX + router_id) + + def get_snat_interfaces(self, ri): + return ri.router.get(l3_constants.SNAT_ROUTER_INTF_KEY, []) + + def get_floating_ips(self, ri): + """Filter Floating IPs to be hosted on this agent.""" + floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, []) + if ri.router['distributed']: + floating_ips = [i for i in floating_ips if i['host'] == self.host] + return floating_ips + + def _map_internal_interfaces(self, ri, int_port, snat_ports): + """Return the SNAT port for the given internal interface port.""" + fixed_ip = int_port['fixed_ips'][0] + subnet_id = fixed_ip['subnet_id'] + match_port = [p for p in snat_ports if + p['fixed_ips'][0]['subnet_id'] == subnet_id] + if match_port: + return match_port[0] + else: + LOG.error(_('DVR: no map match_port found!')) + + def _create_dvr_gateway(self, ri, ex_gw_port, gw_interface_name, + internal_cidrs, snat_ports): + """Create SNAT namespace.""" + snat_ns_name = self.get_snat_ns_name(ri.router['id']) + self._create_namespace(snat_ns_name) + # connect snat_ports to br_int from SNAT namespace + for port in snat_ports: + # create interface_name + self._set_subnet_info(port) + interface_name = self.get_snat_int_device_name(port['id']) + self._internal_network_added(snat_ns_name, port['network_id'], + port['id'], port['ip_cidr'], + port['mac_address'], interface_name, + SNAT_INT_DEV_PREFIX) + self._external_gateway_added(ri, ex_gw_port, gw_interface_name, + internal_cidrs, snat_ns_name, + preserve_ips=[]) + ri.snat_iptables_manager = ( + iptables_manager.IptablesManager( + root_helper=self.root_helper, namespace=snat_ns_name + ) + ) + def external_gateway_added(self, ri, ex_gw_port, interface_name, internal_cidrs): + if ri.router['distributed']: + ip_wrapr = ip_lib.IPWrapper(self.root_helper, namespace=ri.ns_name) + ip_wrapr.netns.execute(['sysctl', '-w', + 'net.ipv4.conf.all.send_redirects=0']) + snat_ports = self.get_snat_interfaces(ri) + for p in ri.internal_ports: + gateway = self._map_internal_interfaces(ri, p, snat_ports) + id_name = self.get_internal_device_name(p['id']) + if gateway: + self._snat_redirect_add(ri, gateway['fixed_ips'][0] + ['ip_address'], p, id_name) - self.driver.plug(ex_gw_port['network_id'], - ex_gw_port['id'], interface_name, - ex_gw_port['mac_address'], - bridge=self.conf.external_network_bridge, - namespace=ri.ns_name, - prefix=EXTERNAL_DEV_PREFIX) + if self.conf.agent_mode == 'dvr_snat' and ( + ri.router['gw_port_host'] == self.host): + if snat_ports: + self._create_dvr_gateway(ri, ex_gw_port, + interface_name, + internal_cidrs, snat_ports) + for port in snat_ports: + for ip in port['fixed_ips']: + self._update_arp_entry(ri, ip['ip_address'], + port['mac_address'], + ip['subnet_id'], 'add') + return # Compute a list of addresses this router is supposed to have. # This avoids unnecessarily removing those addresses and # causing a momentarily network outage. - floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, []) + floating_ips = self.get_floating_ips(ri) preserve_ips = [ip['floating_ip_address'] + FLOATING_IP_CIDR_SUFFIX for ip in floating_ips] + self._external_gateway_added(ri, ex_gw_port, interface_name, + internal_cidrs, ri.ns_name, + preserve_ips) + + def _external_gateway_added(self, ri, ex_gw_port, interface_name, + internal_cidrs, ns_name, preserve_ips): + if not ip_lib.device_exists(interface_name, + root_helper=self.root_helper, + namespace=ns_name): + self.driver.plug(ex_gw_port['network_id'], + ex_gw_port['id'], interface_name, + ex_gw_port['mac_address'], + bridge=self.conf.external_network_bridge, + namespace=ns_name, + prefix=EXTERNAL_DEV_PREFIX) + self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']], - namespace=ri.ns_name, + namespace=ns_name, gateway=ex_gw_port['subnet'].get('gateway_ip'), extra_subnets=ex_gw_port.get('extra_subnets', []), preserve_ips=preserve_ips) ip_address = ex_gw_port['ip_cidr'].split('/')[0] - self._send_gratuitous_arp_packet(ri, interface_name, ip_address) + self._send_gratuitous_arp_packet(ns_name, + interface_name, ip_address) + + def agent_gateway_added(self, ns_name, ex_gw_port, + interface_name): + """Add Floating IP gateway port to FIP namespace.""" + if not ip_lib.device_exists(interface_name, + root_helper=self.root_helper, + namespace=ns_name): + self.driver.plug(ex_gw_port['network_id'], + ex_gw_port['id'], interface_name, + ex_gw_port['mac_address'], + bridge=self.conf.external_network_bridge, + namespace=ns_name, + prefix=FIP_EXT_DEV_PREFIX) + + self.driver.init_l3(interface_name, [ex_gw_port['ip_cidr']], + namespace=ns_name) + ip_address = ex_gw_port['ip_cidr'].split('/')[0] + self._send_gratuitous_arp_packet(ns_name, interface_name, ip_address) + + gw_ip = ex_gw_port['subnet']['gateway_ip'] + if gw_ip: + ipd = ip_lib.IPDevice(interface_name, self.root_helper, + namespace=ns_name) + ipd.route.add_gateway(gw_ip) + + cmd = ['sysctl', '-w', 'net.ipv4.conf.%s.proxy_arp=1' % interface_name] + ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=ns_name) + ip_wrapper.netns.execute(cmd, check_exit_code=False) + + def internal_ns_interface_added(self, ip_cidr, + interface_name, ns_name): + ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=ns_name) + ip_wrapper.netns.execute(['ip', 'addr', 'add', + ip_cidr, 'dev', interface_name]) def external_gateway_removed(self, ri, ex_gw_port, interface_name, internal_cidrs): + if ri.router['distributed']: + for p in ri.internal_ports: + internal_interface = self.get_internal_device_name(p['id']) + self._snat_redirect_remove(ri, p, internal_interface) + + if self.conf.agent_mode == 'dvr_snat' and ( + ex_gw_port['binding:host_id'] == self.host): + ns_name = self.get_snat_ns_name(ri.router['id']) + else: + # not hosting agent - no work to do + LOG.debug('DVR: CSNAT not hosted: %s', ex_gw_port) + return + else: + ns_name = ri.ns_name self.driver.unplug(interface_name, bridge=self.conf.external_network_bridge, - namespace=ri.ns_name, + namespace=ns_name, prefix=EXTERNAL_DEV_PREFIX) + if ri.router['distributed']: + self._destroy_snat_namespace(ns_name) def metadata_filter_rules(self): rules = [] @@ -863,23 +1220,100 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): rules.extend(self.internal_network_nat_rules(ex_gw_ip, cidr)) return rules - def internal_network_added(self, ri, network_id, port_id, - internal_cidr, mac_address): - interface_name = self.get_internal_device_name(port_id) + def _snat_redirect_add(self, ri, gateway, sn_port, sn_int): + """Adds rules and routes for SNAT redirection.""" + try: + snat_idx = netaddr.IPNetwork(sn_port['ip_cidr']).value + ns_ipr = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name) + ns_ipd = ip_lib.IPDevice(sn_int, self.root_helper, + namespace=ri.ns_name) + ns_ipd.route.add_gateway(gateway, table=snat_idx) + ns_ipr.add_rule_from(sn_port['ip_cidr'], snat_idx, snat_idx) + ns_ipr.netns.execute(['sysctl', '-w', 'net.ipv4.conf.%s.' + 'send_redirects=0' % sn_int]) + except Exception: + LOG.exception(_('DVR: error adding redirection logic')) + + def _snat_redirect_remove(self, ri, sn_port, sn_int): + """Removes rules and routes for SNAT redirection.""" + try: + snat_idx = netaddr.IPNetwork(sn_port['ip_cidr']).value + ns_ipr = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name) + ns_ipd = ip_lib.IPDevice(sn_int, self.root_helper, + namespace=ri.ns_name) + ns_ipd.route.delete_gateway(table=snat_idx) + ns_ipr.delete_rule_priority(snat_idx) + except Exception: + LOG.exception(_('DVR: removed snat failed')) + + def _internal_network_added(self, ns_name, network_id, port_id, + internal_cidr, mac_address, + interface_name, prefix): if not ip_lib.device_exists(interface_name, root_helper=self.root_helper, - namespace=ri.ns_name): + namespace=ns_name): self.driver.plug(network_id, port_id, interface_name, mac_address, - namespace=ri.ns_name, - prefix=INTERNAL_DEV_PREFIX) + namespace=ns_name, + prefix=prefix) self.driver.init_l3(interface_name, [internal_cidr], - namespace=ri.ns_name) + namespace=ns_name) ip_address = internal_cidr.split('/')[0] - self._send_gratuitous_arp_packet(ri, interface_name, ip_address) + self._send_gratuitous_arp_packet(ns_name, interface_name, ip_address) + + def internal_network_added(self, ri, port): + network_id = port['network_id'] + port_id = port['id'] + internal_cidr = port['ip_cidr'] + mac_address = port['mac_address'] - def internal_network_removed(self, ri, port_id, internal_cidr): interface_name = self.get_internal_device_name(port_id) + + self._internal_network_added(ri.ns_name, network_id, port_id, + internal_cidr, mac_address, + interface_name, INTERNAL_DEV_PREFIX) + + ex_gw_port = self._get_ex_gw_port(ri) + if ri.router['distributed'] and ex_gw_port: + snat_ports = self.get_snat_interfaces(ri) + snat_ip = self._map_internal_interfaces(ri, port, snat_ports) + if snat_ip: + self._snat_redirect_add(ri, snat_ip['fixed_ips'][0] + ['ip_address'], port, interface_name) + if self.conf.agent_mode == 'dvr_snat' and ( + ri.router['gw_port_host'] == self.host): + ns_name = self.get_snat_ns_name(ri.router['id']) + for port in snat_ports: + self._set_subnet_info(port) + interface_name = self.get_snat_int_device_name(port['id']) + self._internal_network_added(ns_name, port['network_id'], + port['id'], internal_cidr, + port['mac_address'], + interface_name, + SNAT_INT_DEV_PREFIX) + + def internal_network_removed(self, ri, port): + port_id = port['id'] + interface_name = self.get_internal_device_name(port_id) + if ri.router['distributed'] and ri.ex_gw_port: + # DVR handling code for SNAT + self._snat_redirect_remove(ri, port, interface_name) + if self.conf.agent_mode == 'dvr_snat' and ( + ri.ex_gw_port['binding:host_id'] == self.host): + snat_port = self._map_internal_interfaces(ri, port, + ri.snat_ports) + if snat_port: + snat_interface = ( + self.get_snat_int_device_name(snat_port['id']) + ) + ns_name = self.get_snat_ns_name(ri.router['id']) + prefix = SNAT_INT_DEV_PREFIX + if ip_lib.device_exists(snat_interface, + root_helper=self.root_helper, + namespace=ns_name): + self.driver.unplug(snat_interface, namespace=ns_name, + prefix=prefix) + if ip_lib.device_exists(interface_name, root_helper=self.root_helper, namespace=ri.ns_name): @@ -891,6 +1325,118 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): (internal_cidr, ex_gw_ip))] return rules + def _create_agent_gateway_port(self, ri, network_id): + """Create Floating IP gateway port. + + Request port creation from Plugin then creates + Floating IP namespace and adds gateway port. + """ + self.agent_gateway_port = ( + self.plugin_rpc.get_agent_gateway_port( + self.context, network_id)) + if 'subnet' not in self.agent_gateway_port: + LOG.error(_('Missing subnet/agent_gateway_port')) + return + self._set_subnet_info(self.agent_gateway_port) + + # add fip-namespace and agent_gateway_port + fip_ns_name = ( + self.get_fip_ns_name(str(network_id))) + self._create_namespace(fip_ns_name) + interface_name = ( + self.get_fip_ext_device_name(self.agent_gateway_port['id'])) + self.agent_gateway_added(fip_ns_name, self.agent_gateway_port, + interface_name) + + def create_rtr_2_fip_link(self, ri, network_id): + """Create interface between router and Floating IP namespace.""" + rtr_2_fip_name = self.get_rtr_int_device_name(ri.router_id) + fip_2_rtr_name = self.get_fip_int_device_name(ri.router_id) + fip_ns_name = self.get_fip_ns_name(str(network_id)) + + # add link local IP to interface + if ri.rtr_2_fip is None: + ri.rtr_2_fip = FIP_LL_PREFIX + str(self.local_ips.pop()) + if ri.fip_2_rtr is None: + ri.fip_2_rtr = FIP_LL_PREFIX + str(self.local_ips.pop()) + ip_wrapper = ip_lib.IPWrapper(self.root_helper, + namespace=ri.ns_name) + int_dev = ip_wrapper.add_veth(rtr_2_fip_name, + fip_2_rtr_name, fip_ns_name) + self.internal_ns_interface_added(ri.rtr_2_fip + '/31', + rtr_2_fip_name, ri.ns_name) + self.internal_ns_interface_added(ri.fip_2_rtr + '/31', + fip_2_rtr_name, fip_ns_name) + int_dev[0].link.set_up() + int_dev[1].link.set_up() + # add default route for the link local interface + device = ip_lib.IPDevice(rtr_2_fip_name, self.root_helper, + namespace=ri.ns_name) + device.route.add_gateway(ri.fip_2_rtr, table=FIP_RT_TBL) + #setup the NAT rules and chains + self._handle_router_fip_nat_rules(ri, rtr_2_fip_name, 'add_rules') + + def floating_ip_added_dist(self, ri, fip): + """Add floating IP to FIP namespace.""" + floating_ip = fip['floating_ip_address'] + fixed_ip = fip['fixed_ip_address'] + rule_pr = self.fip_priorities.pop() + ri.floating_ips_dict[floating_ip] = rule_pr + fip_2_rtr_name = self.get_fip_int_device_name(ri.router_id) + ip_rule = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name) + ip_rule.add_rule_from(fixed_ip, FIP_RT_TBL, rule_pr) + + #Add routing rule in fip namespace + fip_cidr = str(floating_ip) + FLOATING_IP_CIDR_SUFFIX + fip_ns_name = self.get_fip_ns_name(str(fip['floating_network_id'])) + device = ip_lib.IPDevice(fip_2_rtr_name, self.root_helper, + namespace=fip_ns_name) + device.route.add_route(fip_cidr, ri.rtr_2_fip) + interface_name = ( + self.get_fip_ext_device_name(self.agent_gateway_port['id'])) + self._send_gratuitous_arp_packet(fip_ns_name, + interface_name, floating_ip, + distributed=True) + # update internal structures + self.agent_fip_count = self.agent_fip_count + 1 + ri.dist_fip_count = ri.dist_fip_count + 1 + + def floating_ip_removed_dist(self, ri, fip_cidr): + """Remove floating IP from FIP namespace.""" + floating_ip = fip_cidr.split('/')[0] + rtr_2_fip_name = self.get_rtr_int_device_name(ri.router_id) + fip_2_rtr_name = self.get_fip_int_device_name(ri.router_id) + fip_ns_name = self.get_fip_ns_name(str(self._fetch_external_net_id())) + ip_rule_rtr = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name) + if floating_ip in ri.floating_ips_dict: + rule_pr = ri.floating_ips_dict[floating_ip] + #TODO(rajeev): Handle else case - exception/log? + else: + rule_pr = None + + ip_rule_rtr.delete_rule_priority(rule_pr) + self.fip_priorities.add(rule_pr) + device = ip_lib.IPDevice(fip_2_rtr_name, self.root_helper, + namespace=fip_ns_name) + + device.route.delete_route(fip_cidr, ri.rtr_2_fip) + # check if this is the last FIP for this router + ri.dist_fip_count = ri.dist_fip_count - 1 + if ri.dist_fip_count == 0: + #remove default route entry + device = ip_lib.IPDevice(rtr_2_fip_name, self.root_helper, + namespace=ri.ns_name) + device.route.delete_gateway(ri.fip_2_rtr, table=FIP_RT_TBL) + self.local_ips.add(ri.rtr_2_fip.rsplit('.', 1)[1]) + ri.rtr_2_fip = None + self.local_ips.add(ri.fip_2_rtr.rsplit('.', 1)[1]) + ri.fip_2_rtr = None + # TODO(mrsmith): remove interface + # clean up fip-namespace if this is the last FIP + self.agent_fip_count = self.agent_fip_count - 1 + if self.agent_fip_count == 0: + self._destroy_fip_namespace(fip_ns_name) + def floating_forward_rules(self, floating_ip, fixed_ip): return [('PREROUTING', '-d %s -j DNAT --to %s' % (floating_ip, fixed_ip)), @@ -905,6 +1451,46 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager): update = RouterUpdate(router_id, PRIORITY_RPC, action=DELETE_ROUTER) self._queue.add(update) + def _update_arp_entry(self, ri, ip, mac, subnet_id, operation): + """Add or delete arp entry into router namespace.""" + port = self.get_internal_port(ri, subnet_id) + if 'id' in port: + ip_cidr = str(ip) + '/32' + try: + # TODO(mrsmith): optimize the calls below for bulk calls + net = netaddr.IPNetwork(ip_cidr) + interface_name = self.get_internal_device_name(port['id']) + device = ip_lib.IPDevice(interface_name, self.root_helper, + namespace=ri.ns_name) + if operation == 'add': + device.neigh.add(net.version, ip, mac) + elif operation == 'delete': + device.neigh.delete(net.version, ip, mac) + except Exception: + LOG.exception(_("DVR: Failed updating arp entry")) + self.fullsync = True + + def add_arp_entry(self, context, payload): + """Add arp entry into router namespace. Called from RPC.""" + arp_table = payload['arp_table'] + router_id = payload['router_id'] + ip = arp_table['ip_address'] + mac = arp_table['mac_address'] + subnet_id = arp_table['subnet_id'] + ri = self.router_info.get(router_id) + self._update_arp_entry(ri, ip, mac, subnet_id, 'add') + + def del_arp_entry(self, context, payload): + """Delete arp entry from router namespace. Called from RPC.""" + arp_table = payload['arp_table'] + router_id = payload['router_id'] + ip = arp_table['ip_address'] + mac = arp_table['mac_address'] + subnet_id = arp_table['subnet_id'] + ri = self.router_info.get(router_id) + if ri: + self._update_arp_entry(ri, ip, mac, subnet_id, 'delete') + def routers_updated(self, context, routers): """Deal with routers modification and creation RPC message.""" LOG.debug(_('Got routers updated notification :%s'), routers) @@ -1115,6 +1701,7 @@ class L3NATAgentWithStateReport(L3NATAgent): 'host': host, 'topic': topics.L3_AGENT, 'configurations': { + 'agent_mode': self.conf.agent_mode, 'use_namespaces': self.conf.use_namespaces, 'router_id': self.conf.router_id, 'handle_internal_only_routers': diff --git a/neutron/tests/unit/services/vpn/test_vpn_agent.py b/neutron/tests/unit/services/vpn/test_vpn_agent.py index 09f5594714..b998c948d3 100644 --- a/neutron/tests/unit/services/vpn/test_vpn_agent.py +++ b/neutron/tests/unit/services/vpn/test_vpn_agent.py @@ -92,7 +92,7 @@ class TestVPNAgent(base.BaseTestCase): def test_get_namespace(self): router_id = _uuid() ri = l3_agent.RouterInfo(router_id, self.conf.root_helper, - self.conf.use_namespaces, None) + self.conf.use_namespaces, {}) self.agent.router_info = {router_id: ri} namespace = self.agent.get_namespace(router_id) self.assertTrue(namespace.endswith(router_id)) @@ -101,7 +101,7 @@ class TestVPNAgent(base.BaseTestCase): def test_add_nat_rule(self): router_id = _uuid() ri = l3_agent.RouterInfo(router_id, self.conf.root_helper, - self.conf.use_namespaces, None) + self.conf.use_namespaces, {}) iptables = mock.Mock() ri.iptables_manager.ipv4['nat'] = iptables self.agent.router_info = {router_id: ri} @@ -121,7 +121,7 @@ class TestVPNAgent(base.BaseTestCase): def test_remove_rule(self): router_id = _uuid() ri = l3_agent.RouterInfo(router_id, self.conf.root_helper, - self.conf.use_namespaces, None) + self.conf.use_namespaces, {}) iptables = mock.Mock() ri.iptables_manager.ipv4['nat'] = iptables self.agent.router_info = {router_id: ri} @@ -140,7 +140,7 @@ class TestVPNAgent(base.BaseTestCase): def test_iptables_apply(self): router_id = _uuid() ri = l3_agent.RouterInfo(router_id, self.conf.root_helper, - self.conf.use_namespaces, None) + self.conf.use_namespaces, {}) iptables = mock.Mock() ri.iptables_manager = iptables self.agent.router_info = {router_id: ri} @@ -168,12 +168,13 @@ class TestVPNAgent(base.BaseTestCase): 'neutron.agent.linux.iptables_manager.IptablesManager').start() router_id = _uuid() ri = l3_agent.RouterInfo(router_id, self.conf.root_helper, - self.conf.use_namespaces, None) + self.conf.use_namespaces, {}) ri.router = { 'id': _uuid(), 'admin_state_up': True, 'routes': [], - 'external_gateway_info': {}} + 'external_gateway_info': {}, + 'distributed': False} device = mock.Mock() self.agent.router_info = {router_id: ri} self.agent.devices = [device] diff --git a/neutron/tests/unit/test_l3_agent.py b/neutron/tests/unit/test_l3_agent.py index 132ff1dd43..78f0ff2d43 100644 --- a/neutron/tests/unit/test_l3_agent.py +++ b/neutron/tests/unit/test_l3_agent.py @@ -37,6 +37,7 @@ _uuid = uuidutils.generate_uuid HOSTNAME = 'myhost' FAKE_ID = _uuid() FAKE_ID_2 = _uuid() +FIP_PRI = 32768 class TestExclusiveRouterProcessor(base.BaseTestCase): @@ -165,6 +166,14 @@ class TestBasicRouterOperations(base.BaseTestCase): self.mock_ip = mock.MagicMock() ip_cls.return_value = self.mock_ip + ip_rule = mock.patch('neutron.agent.linux.ip_lib.IpRule').start() + self.mock_rule = mock.MagicMock() + ip_rule.return_value = self.mock_rule + + ip_dev = mock.patch('neutron.agent.linux.ip_lib.IPDevice').start() + self.mock_ip_dev = mock.MagicMock() + ip_dev.return_value = self.mock_ip_dev + self.l3pluginApi_cls_p = mock.patch( 'neutron.agent.l3_agent.L3PluginApi') l3pluginApi_cls = self.l3pluginApi_cls_p.start() @@ -175,6 +184,8 @@ class TestBasicRouterOperations(base.BaseTestCase): 'neutron.openstack.common.loopingcall.FixedIntervalLoopingCall') self.looping_call_p.start() + self.subnet_id_list = [] + def test__sync_routers_task_raise_exception(self): agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) self.plugin_api.get_routers.side_effect = Exception() @@ -192,7 +203,7 @@ class TestBasicRouterOperations(base.BaseTestCase): def test_router_info_create(self): id = _uuid() ri = l3_agent.RouterInfo(id, self.conf.root_helper, - self.conf.use_namespaces, None) + self.conf.use_namespaces, {}) self.assertTrue(ri.ns_name.endswith(id)) @@ -221,24 +232,28 @@ class TestBasicRouterOperations(base.BaseTestCase): port_id = _uuid() router_id = _uuid() network_id = _uuid() + router = self._prepare_router_data(num_internal_ports=2) + router_id = router['id'] ri = l3_agent.RouterInfo(router_id, self.conf.root_helper, - self.conf.use_namespaces, None) + self.conf.use_namespaces, router=router) agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) cidr = '99.0.1.9/24' mac = 'ca:fe:de:ad:be:ef' + port = {'network_id': network_id, + 'id': port_id, 'ip_cidr': cidr, + 'mac_address': mac} interface_name = agent.get_internal_device_name(port_id) if action == 'add': self.device_exists.return_value = False - agent.internal_network_added(ri, network_id, - port_id, cidr, mac) + agent.internal_network_added(ri, port) self.assertEqual(self.mock_driver.plug.call_count, 1) self.assertEqual(self.mock_driver.init_l3.call_count, 1) - self.send_arp.assert_called_once_with(ri, interface_name, + self.send_arp.assert_called_once_with(ri.ns_name, interface_name, '99.0.1.9') elif action == 'remove': self.device_exists.return_value = True - agent.internal_network_removed(ri, port_id, cidr) + agent.internal_network_removed(ri, port) self.assertEqual(self.mock_driver.unplug.call_count, 1) else: raise Exception("Invalid action %s" % action) @@ -250,9 +265,9 @@ class TestBasicRouterOperations(base.BaseTestCase): self._test_internal_network_action('remove') def _test_external_gateway_action(self, action): - router_id = _uuid() - ri = l3_agent.RouterInfo(router_id, self.conf.root_helper, - self.conf.use_namespaces, None) + router = self._prepare_router_data(num_internal_ports=2) + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) internal_cidrs = ['100.0.1.0/24', '200.74.0.0/16'] ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30', @@ -267,17 +282,19 @@ class TestBasicRouterOperations(base.BaseTestCase): if action == 'add': self.device_exists.return_value = False - ri.router = mock.Mock() - ri.router.get.return_value = [{'floating_ip_address': - '192.168.1.34'}] + fake_fip = {'floatingips': [{'id': _uuid(), + 'floating_ip_address': '192.168.1.34', + 'fixed_ip_address': '192.168.0.1', + 'port_id': _uuid()}]} + router[l3_constants.FLOATINGIP_KEY] = fake_fip['floatingips'] agent.external_gateway_added(ri, ex_gw_port, interface_name, internal_cidrs) self.assertEqual(self.mock_driver.plug.call_count, 1) self.assertEqual(self.mock_driver.init_l3.call_count, 1) - self.send_arp.assert_called_once_with(ri, interface_name, + self.send_arp.assert_called_once_with(ri.ns_name, interface_name, '20.0.0.30') kwargs = {'preserve_ips': ['192.168.1.34/32'], - 'namespace': 'qrouter-' + router_id, + 'namespace': 'qrouter-' + router['id'], 'gateway': '20.0.0.1', 'extra_subnets': [{'cidr': '172.16.0.0/24'}]} self.mock_driver.init_l3.assert_called_with(interface_name, @@ -301,7 +318,7 @@ class TestBasicRouterOperations(base.BaseTestCase): router_id = _uuid() ri = l3_agent.RouterInfo(router_id, self.conf.root_helper, - self.conf.use_namespaces, None) + self.conf.use_namespaces, {}) agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) floating_ip = '20.0.0.101' interface_name = agent.get_external_device_name(router_id) @@ -335,7 +352,7 @@ class TestBasicRouterOperations(base.BaseTestCase): router_id = _uuid() ri = l3_agent.RouterInfo(router_id, self.conf.root_helper, self.conf.use_namespaces, - None) + {}) agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) fake_route1 = {'destination': '135.207.0.0/16', @@ -383,7 +400,7 @@ class TestBasicRouterOperations(base.BaseTestCase): ri = l3_agent.RouterInfo(router_id, self.conf.root_helper, self.conf.use_namespaces, - None) + {}) ri.router = {} fake_old_routes = [] @@ -446,7 +463,8 @@ class TestBasicRouterOperations(base.BaseTestCase): self.assertIn(r.rule, expected_rules) @staticmethod - def _router_append_interface(router, count=1, ip_version=4, + def _router_append_interface(router, subnet_id_list=[], count=1, + ip_version=4, ra_mode=None, addr_mode=None): if ip_version == 4: ip_pool = '35.4.%i.4' @@ -465,12 +483,17 @@ class TestBasicRouterOperations(base.BaseTestCase): for p in interfaces]) for i in range(current, current + count): + if subnet_id_list: + subnet_id_list.append(_uuid()) + subnet_id = subnet_id_list[i] + else: + subnet_id = _uuid() interfaces.append( {'id': _uuid(), 'network_id': _uuid(), 'admin_state_up': True, 'fixed_ips': [{'ip_address': ip_pool % i, - 'subnet_id': _uuid()}], + 'subnet_id': subnet_id}], 'mac_address': 'ca:fe:de:ad:be:ef', 'subnet': {'cidr': cidr_pool % i, 'gateway_ip': gw_pool % i, @@ -497,20 +520,159 @@ class TestBasicRouterOperations(base.BaseTestCase): 'subnet_id': _uuid()}], 'subnet': {'cidr': cidr, 'gateway_ip': gateway_ip}} + int_ports = [] + self.subnet_id_list = [] + for i in range(num_internal_ports): + self.subnet_id_list.append(_uuid()) + subnet_id = self.subnet_id_list[i] + int_ports.append({'id': _uuid(), + 'network_id': _uuid(), + 'admin_state_up': True, + 'fixed_ips': [{'ip_address': '35.4.%s.4' % i, + 'subnet_id': subnet_id}], + 'mac_address': 'ca:fe:de:ad:be:ef', + 'subnet': {'cidr': '35.4.%s.0/24' % i, + 'gateway_ip': '35.4.%s.1' % i}}) router = { 'id': router_id, + 'distributed': False, l3_constants.INTERFACE_KEY: [], 'routes': [], 'gw_port': ex_gw_port} - self._router_append_interface(router, count=num_internal_ports, + self._router_append_interface(router, self.subnet_id_list, + count=num_internal_ports, ip_version=ip_version) if enable_snat is not None: router['enable_snat'] = enable_snat return router - def test_process_router(self): + def test__map_internal_interfaces(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router = self._prepare_router_data(num_internal_ports=4) + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) + test_port = {'mac_address': '00:12:23:34:45:56', + 'fixed_ips': [{'subnet_id': self.subnet_id_list[0], + 'ip_address': '101.12.13.14'}]} + internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, []) + # test valid case + res_port = agent._map_internal_interfaces(ri, + internal_ports[0], + [test_port]) + self.assertEqual(test_port, res_port) + # test invalid case + test_port['fixed_ips'][0]['subnet_id'] = 1234 + res_ip = agent._map_internal_interfaces(ri, + internal_ports[0], + [test_port]) + self.assertNotEqual(test_port, res_ip) + self.assertIsNone(res_ip) + + def test_get_internal_port(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router = self._prepare_router_data(num_internal_ports=4) + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) + + # Test Basic cases + port = agent.get_internal_port(ri, self.subnet_id_list[0]) + fips = port.get('fixed_ips', []) + subnet_id = fips[0]['subnet_id'] + self.assertEqual(self.subnet_id_list[0], subnet_id) + port = agent.get_internal_port(ri, self.subnet_id_list[1]) + fips = port.get('fixed_ips', []) + subnet_id = fips[0]['subnet_id'] + self.assertEqual(self.subnet_id_list[1], subnet_id) + port = agent.get_internal_port(ri, self.subnet_id_list[3]) + fips = port.get('fixed_ips', []) + subnet_id = fips[0]['subnet_id'] + self.assertEqual(self.subnet_id_list[3], subnet_id) + + # Test miss cases + no_port = agent.get_internal_port(ri, FAKE_ID) + self.assertIsNone(no_port) + port = agent.get_internal_port(ri, self.subnet_id_list[0]) + fips = port.get('fixed_ips', []) + subnet_id = fips[0]['subnet_id'] + self.assertNotEqual(self.subnet_id_list[3], subnet_id) + + def test__set_subnet_arp_info(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router = self._prepare_router_data(num_internal_ports=2) + router['distributed'] = True + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) + ports = ri.router.get(l3_constants.INTERFACE_KEY, []) + test_ports = [{'mac_address': '00:11:22:33:44:55', + 'device_owner': 'network:dhcp', + 'subnet_id': self.subnet_id_list[0], + 'fixed_ips': [{'ip_address': '1.2.3.4'}]}] + + self.plugin_api.get_ports_by_subnet.return_value = test_ports + + # Test basic case + ports[0]['subnet']['id'] = self.subnet_id_list[0] + agent._set_subnet_arp_info(ri, ports[0]) + self.mock_ip_dev.neigh.add.assert_called_once_with( + 4, '1.2.3.4', '00:11:22:33:44:55') + + # Test negative case + router['distributed'] = False + agent._set_subnet_arp_info(ri, ports[0]) + self.mock_ip_dev.neigh.add.never_called() + + def test_add_arp_entry(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router = self._prepare_router_data(num_internal_ports=2) + arp_table = {'ip_address': '1.7.23.11', + 'mac_address': '00:11:22:33:44:55', + 'subnet_id': self.subnet_id_list[0]} + + payload = {'arp_table': arp_table, 'router_id': router['id']} + agent._router_added(router['id'], router) + agent.add_arp_entry(None, payload) + agent.router_deleted(None, router['id']) + self.mock_ip_dev.neigh.add.assert_called_once_with( + 4, '1.7.23.11', '00:11:22:33:44:55') + + def test_del_arp_entry(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router = self._prepare_router_data(num_internal_ports=2) + arp_table = {'ip_address': '1.5.25.15', + 'mac_address': '00:44:33:22:11:55', + 'subnet_id': self.subnet_id_list[0]} + + payload = {'arp_table': arp_table, 'router_id': router['id']} + agent._router_added(router['id'], router) + # first add the entry + agent.add_arp_entry(None, payload) + # now delete it + agent.del_arp_entry(None, payload) + self.mock_ip_dev.neigh.delete.assert_called_once_with( + 4, '1.5.25.15', '00:44:33:22:11:55') + agent.router_deleted(None, router['id']) + + def test_process_cent_router(self): + router = self._prepare_router_data() + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) + self._test_process_router(ri) + + def test_process_dist_router(self): + router = self._prepare_router_data() + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) + ri.router['distributed'] = True + ri.router['_snat_router_interfaces'] = [{ + 'fixed_ips': [{'subnet_id': self.subnet_id_list[0], + 'ip_address': '1.2.3.4'}]}] + ri.router['gw_port_host'] = None + self._test_process_router(ri) + + def _test_process_router(self, ri): + router = ri.router agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) fake_fip_id = 'fake_fip_id' agent.process_router_floating_ip_addresses = mock.Mock() @@ -518,14 +680,11 @@ class TestBasicRouterOperations(base.BaseTestCase): agent.process_router_floating_ip_addresses.return_value = { fake_fip_id: 'ACTIVE'} agent.external_gateway_added = mock.Mock() - router = self._prepare_router_data() fake_floatingips1 = {'floatingips': [ {'id': fake_fip_id, 'floating_ip_address': '8.8.8.8', 'fixed_ip_address': '7.7.7.7', 'port_id': _uuid()}]} - ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, - self.conf.use_namespaces, router=router) agent.process_router(ri) ex_gw_port = agent._get_ex_gw_port(ri) agent.process_router_floating_ip_addresses.assert_called_with( @@ -566,21 +725,13 @@ class TestBasicRouterOperations(base.BaseTestCase): self.assertFalse(agent.process_router_floating_ip_nat_rules.called) @mock.patch('neutron.agent.linux.ip_lib.IPDevice') - def test_process_router_floating_ip_addresses_add(self, IPDevice): - fip_id = _uuid() - fip = { - 'id': fip_id, 'port_id': _uuid(), - 'floating_ip_address': '15.1.2.3', - 'fixed_ip_address': '192.168.0.1' - } - + def _test_process_router_floating_ip_addresses_add(self, ri, + agent, IPDevice): + floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, []) + fip_id = floating_ips[0]['id'] IPDevice.return_value = device = mock.Mock() device.addr.list.return_value = [] - - ri = mock.MagicMock() - ri.router.get.return_value = [fip] - - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + ri.iptables_manager.ipv4['nat'] = mock.MagicMock() fip_statuses = agent.process_router_floating_ip_addresses( ri, {'id': _uuid()}) @@ -597,6 +748,7 @@ class TestBasicRouterOperations(base.BaseTestCase): ri = mock.MagicMock() ri.router.get.return_value = [fip] + ri.router['distributed'].__nonzero__ = lambda self: False agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) @@ -608,6 +760,50 @@ class TestBasicRouterOperations(base.BaseTestCase): for chain, rule in rules: nat.add_rule.assert_any_call(chain, rule, tag='floating_ip') + def test_process_router_cent_floating_ip_add(self): + fake_floatingips = {'floatingips': [ + {'id': _uuid(), + 'floating_ip_address': '15.1.2.3', + 'fixed_ip_address': '192.168.0.1', + 'port_id': _uuid()}]} + + router = self._prepare_router_data(enable_snat=True) + router[l3_constants.FLOATINGIP_KEY] = fake_floatingips['floatingips'] + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) + ri.iptables_manager.ipv4['nat'] = mock.MagicMock() + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + self._test_process_router_floating_ip_addresses_add(ri, agent) + + def test_process_router_dist_floating_ip_add(self): + fake_floatingips = {'floatingips': [ + {'id': _uuid(), + 'host': HOSTNAME, + 'floating_ip_address': '15.1.2.3', + 'fixed_ip_address': '192.168.0.1', + 'floating_network_id': _uuid(), + 'port_id': _uuid()}]} + + router = self._prepare_router_data(enable_snat=True) + router[l3_constants.FLOATINGIP_KEY] = fake_floatingips['floatingips'] + router['distributed'] = True + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) + ri.iptables_manager.ipv4['nat'] = mock.MagicMock() + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + agent.host = HOSTNAME + agent.agent_gateway_port = ( + {'fixed_ips': [{'ip_address': '20.0.0.30', + 'subnet_id': _uuid()}], + 'subnet': {'gateway_ip': '20.0.0.1'}, + 'id': _uuid(), + 'network_id': _uuid(), + 'mac_address': 'ca:fe:de:ad:be:ef', + 'ip_cidr': '20.0.0.30/24'} + ) + self._test_process_router_floating_ip_addresses_add(ri, agent) + + # TODO(mrsmith): refactor for DVR cases @mock.patch('neutron.agent.linux.ip_lib.IPDevice') def test_process_router_floating_ip_addresses_remove(self, IPDevice): IPDevice.return_value = device = mock.Mock() @@ -615,6 +811,7 @@ class TestBasicRouterOperations(base.BaseTestCase): ri = mock.MagicMock() ri.router.get.return_value = [] + ri.router['distributed'].__nonzero__ = lambda self: False agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) @@ -647,6 +844,7 @@ class TestBasicRouterOperations(base.BaseTestCase): IPDevice.return_value = device = mock.Mock() device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}] ri = mock.MagicMock() + ri.router['distributed'].__nonzero__ = lambda self: False ri.router.get.return_value = [fip] @@ -693,6 +891,7 @@ class TestBasicRouterOperations(base.BaseTestCase): } ri = mock.MagicMock() ri.router.get.return_value = [fip] + ri.router['distributed'].__nonzero__ = lambda self: False agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) @@ -1035,6 +1234,7 @@ class TestBasicRouterOperations(base.BaseTestCase): agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) ri = mock.MagicMock() port = {'fixed_ips': [{'ip_address': '192.168.1.4'}]} + ri.router = {'distributed': False} agent._handle_router_snat_rules(ri, port, [], "iface", "add_rules") @@ -1051,9 +1251,10 @@ class TestBasicRouterOperations(base.BaseTestCase): def test_handle_router_snat_rules_add_rules(self): agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) ri = l3_agent.RouterInfo(_uuid(), self.conf.root_helper, - self.conf.use_namespaces, None) + self.conf.use_namespaces, {}) ex_gw_port = {'fixed_ips': [{'ip_address': '192.168.1.4'}]} internal_cidrs = ['10.0.0.0/24'] + ri.router = {'distributed': False} agent._handle_router_snat_rules(ri, ex_gw_port, internal_cidrs, "iface", "add_rules") @@ -1116,11 +1317,7 @@ class TestBasicRouterOperations(base.BaseTestCase): self.assertFalse(external_gateway_removed.called) self.assertFalse(internal_network_removed.called) internal_network_added.assert_called_once_with( - ri, - internal_port['network_id'], - internal_port['id'], - internal_port['ip_cidr'], - internal_port['mac_address']) + ri, internal_port) self.assertEqual(self.mock_driver.unplug.call_count, len(stale_devnames)) calls = [mock.call(stale_devname, @@ -1193,11 +1390,32 @@ class TestBasicRouterOperations(base.BaseTestCase): 'enable_snat': True, 'routes': [], 'gw_port': ex_gw_port} + router['distributed'] = False agent._router_added(router['id'], router) agent.router_deleted(None, router['id']) agent._process_router_delete() self.assertFalse(list(agent.removed_routers)) + def test_destroy_fip_namespace(self): + class FakeDev(object): + def __init__(self, name): + self.name = name + + namespaces = ['qrouter-foo', 'qrouter-bar'] + + self.mock_ip.get_namespaces.return_value = namespaces + self.mock_ip.get_devices.return_value = [FakeDev('fr-aaaa'), + FakeDev('fg-aaaa')] + + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + + agent._destroy_fip_namespace(namespaces[0]) + # TODO(mrsmith): update for fr interface + self.assertEqual(self.mock_driver.unplug.call_count, 1) + self.mock_driver.unplug.assert_called_with('fg-aaaa', bridge='br-ex', + prefix='fg-', + namespace='qrouter-foo') + def test_destroy_router_namespace_skips_ns_removal(self): agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) agent._destroy_router_namespace("fakens") @@ -1216,7 +1434,8 @@ class TestBasicRouterOperations(base.BaseTestCase): router_id = _uuid() router = {'id': _uuid(), 'external_gateway_info': {}, - 'routes': []} + 'routes': [], + 'distributed': False} with mock.patch.object( agent, '_destroy_metadata_proxy') as destroy_proxy: with mock.patch.object( @@ -1439,7 +1658,8 @@ class TestBasicRouterOperations(base.BaseTestCase): self.conf.set_override('router_id', None) stale_namespaces = [l3_agent.NS_PREFIX + 'cccc', l3_agent.NS_PREFIX + 'eeeee'] - router_list = [{'id': 'foo'}, {'id': 'aaaa'}] + router_list = [{'id': 'foo', 'distributed': False}, + {'id': 'aaaa', 'distributed': False}] other_namespaces = ['qdhcp-aabbcc', 'unknown'] self._cleanup_namespace_test(stale_namespaces, @@ -1451,13 +1671,166 @@ class TestBasicRouterOperations(base.BaseTestCase): stale_namespaces = [l3_agent.NS_PREFIX + 'cccc', l3_agent.NS_PREFIX + 'eeeee', l3_agent.NS_PREFIX + self.conf.router_id] - router_list = [{'id': 'foo'}, {'id': 'aaaa'}] + router_list = [{'id': 'foo', 'distributed': False}, + {'id': 'aaaa', 'distributed': False}] other_namespaces = ['qdhcp-aabbcc', 'unknown'] self._cleanup_namespace_test(stale_namespaces, router_list, other_namespaces) + def test_create_dvr_gateway(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router = self._prepare_router_data() + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) + + port_id = _uuid() + dvr_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30', + 'subnet_id': _uuid()}], + 'subnet': {'gateway_ip': '20.0.0.1'}, + 'id': port_id, + 'network_id': _uuid(), + 'mac_address': 'ca:fe:de:ad:be:ef', + 'ip_cidr': '20.0.0.30/24'} + + snat_ports = [{'subnet': {'cidr': '152.2.0.0/16', + 'gateway_ip': '152.2.0.1', + 'id': _uuid()}, + 'network_id': _uuid(), + 'device_owner': 'network:router_centralized_snat', + 'ip_cidr': '152.2.0.13/16', + 'mac_address': 'fa:16:3e:80:8d:80', + 'fixed_ips': [{'subnet_id': _uuid(), + 'ip_address': '152.2.0.13'}], + 'id': _uuid(), 'device_id': _uuid()}, + {'subnet': {'cidr': '152.10.0.0/16', + 'gateway_ip': '152.10.0.1', + 'id': _uuid()}, + 'network_id': _uuid(), + 'device_owner': 'network:router_centralized_snat', + 'ip_cidr': '152.10.0.13/16', + 'mac_address': 'fa:16:3e:80:8d:80', + 'fixed_ips': [{'subnet_id': _uuid(), + 'ip_address': '152.10.0.13'}], + 'id': _uuid(), 'device_id': _uuid()}] + + interface_name = agent.get_snat_int_device_name(port_id) + internal_cidrs = None + self.device_exists.return_value = False + + agent._create_dvr_gateway(ri, dvr_gw_port, interface_name, + internal_cidrs, snat_ports) + + # check 2 internal ports are plugged + # check 1 ext-gw-port is plugged + self.assertEqual(self.mock_driver.plug.call_count, 3) + self.assertEqual(self.mock_driver.init_l3.call_count, 3) + + def test_agent_gateway_added(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + network_id = _uuid() + port_id = _uuid() + agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30', + 'subnet_id': _uuid()}], + 'subnet': {'gateway_ip': '20.0.0.1'}, + 'id': port_id, + 'network_id': network_id, + 'mac_address': 'ca:fe:de:ad:be:ef', + 'ip_cidr': '20.0.0.30/24'} + fip_ns_name = ( + agent.get_fip_ns_name(str(network_id))) + interface_name = ( + agent.get_fip_ext_device_name(port_id)) + + self.device_exists.return_value = False + agent.agent_gateway_added(fip_ns_name, agent_gw_port, + interface_name) + self.assertEqual(self.mock_driver.plug.call_count, 1) + self.assertEqual(self.mock_driver.init_l3.call_count, 1) + if self.conf.use_namespaces: + self.send_arp.assert_called_once_with(fip_ns_name, interface_name, + '20.0.0.30') + else: + self.utils_exec.assert_any_call( + check_exit_code=True, root_helper=self.conf.root_helper) + + def test_create_rtr_2_fip_link(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router = self._prepare_router_data() + fip = {'id': _uuid(), + 'host': HOSTNAME, + 'floating_ip_address': '15.1.2.3', + 'fixed_ip_address': '192.168.0.1', + 'floating_network_id': _uuid(), + 'port_id': _uuid()} + + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) + + rtr_2_fip_name = agent.get_rtr_int_device_name(ri.router_id) + fip_2_rtr_name = agent.get_fip_int_device_name(ri.router_id) + fip_ns_name = agent.get_fip_ns_name(str(fip['floating_network_id'])) + + agent.create_rtr_2_fip_link(ri, fip['floating_network_id']) + self.mock_ip.add_veth.assert_called_with(rtr_2_fip_name, + fip_2_rtr_name, fip_ns_name) + # TODO(mrsmith): add more aasserts - + self.mock_ip_dev.route.add_gateway.assert_called_once_with( + ri.fip_2_rtr, table=16) + + # TODO(mrsmith): test _create_agent_gateway_port + + def test_floating_ip_added_dist(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router = self._prepare_router_data() + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) + agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30', + 'subnet_id': _uuid()}], + 'subnet': {'gateway_ip': '20.0.0.1'}, + 'id': _uuid(), + 'network_id': _uuid(), + 'mac_address': 'ca:fe:de:ad:be:ef', + 'ip_cidr': '20.0.0.30/24'} + + fip = {'id': _uuid(), + 'host': HOSTNAME, + 'floating_ip_address': '15.1.2.3', + 'fixed_ip_address': '192.168.0.1', + 'floating_network_id': _uuid(), + 'port_id': _uuid()} + agent.agent_gateway_port = agent_gw_port + agent.floating_ip_added_dist(ri, fip) + self.mock_rule.add_rule_from.assert_called_with('192.168.0.1', + 16, FIP_PRI) + # TODO(mrsmith): add more asserts + + def test_floating_ip_removed_dist(self): + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + router = self._prepare_router_data() + agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30', + 'subnet_id': _uuid()}], + 'subnet': {'gateway_ip': '20.0.0.1'}, + 'id': _uuid(), + 'network_id': _uuid(), + 'mac_address': 'ca:fe:de:ad:be:ef', + 'ip_cidr': '20.0.0.30/24'} + + fip_cidr = '11.22.33.44/24' + + ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper, + self.conf.use_namespaces, router=router) + ri.dist_fip_count = 2 + ri.floating_ips_dict['11.22.33.44'] = FIP_PRI + agent.agent_gateway_port = agent_gw_port + agent.floating_ip_removed_dist(ri, fip_cidr) + self.mock_rule.delete_rule_priority.assert_called_with(FIP_PRI) + self.mock_ip_dev.route.delete_route.assert_called_with(fip_cidr, + ri.rtr_2_fip) + # TODO(mrsmith): test ri.dist_fip_count == 0 + # TODO(mrsmith): test agent_fip_count == 0 case + class TestL3AgentEventHandler(base.BaseTestCase):