From: Carl Baldwin Date: Tue, 10 Feb 2015 05:17:51 +0000 (+0000) Subject: Move internal port processing to router classes X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=db80198a1b0025e425db098933edbf4da1a331ce;p=openstack-build%2Fneutron-build.git Move internal port processing to router classes Change-Id: I38b18f6d24c788c96b2d13ccca5d2e9a7a4fcdeb Partially-Implements: bp/restructure-l3-agent --- diff --git a/neutron/agent/l3/agent.py b/neutron/agent/l3/agent.py index 0e4a6ce3b..99998d667 100644 --- a/neutron/agent/l3/agent.py +++ b/neutron/agent/l3/agent.py @@ -24,7 +24,6 @@ from oslo_utils import timeutils from neutron.agent.l3 import dvr from neutron.agent.l3 import dvr_router -from neutron.agent.l3 import dvr_snat_ns from neutron.agent.l3 import event_observers from neutron.agent.l3 import ha from neutron.agent.l3 import ha_router @@ -323,68 +322,6 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, self.event_observers.notify( adv_svc.AdvancedService.after_router_removed, ri) - def _set_subnet_info(self, port): - ips = port['fixed_ips'] - if not ips: - raise Exception(_("Router port %s has no IP address") % port['id']) - if len(ips) > 1: - LOG.error(_LE("Ignoring multiple IPs on router port %s"), - port['id']) - prefixlen = netaddr.IPNetwork(port['subnet']['cidr']).prefixlen - port['ip_cidr'] = "%s/%s" % (ips[0]['ip_address'], prefixlen) - - def _get_existing_devices(self, ri): - ip_wrapper = ip_lib.IPWrapper(namespace=ri.ns_name) - ip_devs = ip_wrapper.get_devices(exclude_loopback=True) - return [ip_dev.name for ip_dev in ip_devs] - - def _process_internal_ports(self, ri): - internal_ports = ri.router.get(l3_constants.INTERFACE_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']]) - new_ports = [p for p in internal_ports if - p['id'] in current_port_ids and - p['id'] not in existing_port_ids] - old_ports = [p for p in ri.internal_ports if - p['id'] not in current_port_ids] - - new_ipv6_port = False - old_ipv6_port = False - for p in new_ports: - self._set_subnet_info(p) - self.internal_network_added(ri, p) - ri.internal_ports.append(p) - if ri.router['distributed']: - ri._set_subnet_arp_info(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) - ri.internal_ports.remove(p) - if (not old_ipv6_port and - netaddr.IPNetwork(p['subnet']['cidr']).version == 6): - old_ipv6_port = True - - # Enable RA - if new_ipv6_port or old_ipv6_port: - ri.radvd.enable(internal_ports) - - existing_devices = self._get_existing_devices(ri) - current_internal_devs = set([n for n in existing_devices - if n.startswith(INTERNAL_DEV_PREFIX)]) - current_port_devs = set([ri.get_internal_device_name(id) for - id in current_port_ids]) - stale_devs = current_internal_devs - current_port_devs - for stale_dev in stale_devs: - LOG.debug('Deleting stale internal router device: %s', - stale_dev) - self.driver.unplug(stale_dev, - namespace=ri.ns_name, - prefix=INTERNAL_DEV_PREFIX) - def _process_external_gateway(self, ri): ex_gw_port = ri.get_ex_gw_port() ex_gw_port_id = (ex_gw_port and ex_gw_port['id'] or @@ -404,7 +341,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, port2_filtered = _get_filtered_dict(port2, keys_to_ignore) return port1_filtered == port2_filtered - self._set_subnet_info(ex_gw_port) + ri._set_subnet_info(ex_gw_port) if not ri.ex_gw_port: self.external_gateway_added(ri, ex_gw_port, interface_name) elif not _gateway_ports_equal(ex_gw_port, ri.ex_gw_port): @@ -412,7 +349,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, elif not ex_gw_port and ri.ex_gw_port: self.external_gateway_removed(ri, ri.ex_gw_port, interface_name) - existing_devices = self._get_existing_devices(ri) + existing_devices = ri._get_existing_devices() stale_devs = [dev for dev in existing_devices if dev.startswith(EXTERNAL_DEV_PREFIX) and dev != interface_name] @@ -426,7 +363,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, # Process SNAT rules for external gateway if (not ri.router['distributed'] or - ex_gw_port and self.get_gw_port_host(ri.router) == self.host): + ex_gw_port and ri.get_gw_port_host() == self.host): ri.perform_snat_action(self._handle_router_snat_rules, interface_name) @@ -486,7 +423,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, if ri.router.get('distributed') and ex_gw_port: ri.fip_ns = self.get_fip_ns(ex_gw_port['network_id']) ri.fip_ns.scan_fip_ports(ri) - self._process_internal_ports(ri) + ri._process_internal_ports() self._process_external(ri) # Process static routes for router ri.routes_updated() @@ -536,8 +473,8 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, def create_dvr_fip_interfaces(self, ri, ex_gw_port): floating_ips = ri.get_floating_ips() - fip_agent_port = self.get_floating_agent_gw_interface( - ri, ex_gw_port['network_id']) + fip_agent_port = ri.get_floating_agent_gw_interface( + ex_gw_port['network_id']) LOG.debug("FloatingIP agent gateway port received from the plugin: " "%s", fip_agent_port) if floating_ips: @@ -546,7 +483,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, if 'subnet' not in fip_agent_port: LOG.error(_LE('Missing subnet/agent_gateway_port')) else: - self._set_subnet_info(fip_agent_port) + ri._set_subnet_info(fip_agent_port) ri.fip_ns.create_gateway_port(fip_agent_port) if ri.fip_ns.agent_gateway_port and floating_ips: @@ -568,27 +505,21 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, def get_external_device_name(self, port_id): return (EXTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] - def get_floating_agent_gw_interface(self, ri, ext_net_id): - """Filter Floating Agent GW port for the external network.""" - fip_ports = ri.router.get(l3_constants.FLOATINGIP_AGENT_INTF_KEY, []) - return next( - (p for p in fip_ports if p['network_id'] == ext_net_id), None) - def external_gateway_added(self, ri, ex_gw_port, interface_name): if ri.router['distributed']: ip_wrapr = ip_lib.IPWrapper(namespace=ri.ns_name) ip_wrapr.netns.execute(['sysctl', '-w', 'net.ipv4.conf.all.send_redirects=0']) - snat_ports = self.get_snat_interfaces(ri) + snat_ports = ri.get_snat_interfaces() for p in ri.internal_ports: - gateway = self._map_internal_interfaces(ri, p, snat_ports) + gateway = ri._map_internal_interfaces(p, snat_ports) id_name = ri.get_internal_device_name(p['id']) if gateway: - self._snat_redirect_add(ri, gateway['fixed_ips'][0] - ['ip_address'], p, id_name) + ri._snat_redirect_add( + gateway['fixed_ips'][0]['ip_address'], p, id_name) if (self.conf.agent_mode == l3_constants.L3_AGENT_MODE_DVR_SNAT and - self.get_gw_port_host(ri.router) == self.host): + ri.get_gw_port_host() == self.host): self._create_dvr_gateway(ri, ex_gw_port, interface_name, snat_ports) for port in snat_ports: @@ -617,7 +548,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, preserve_ips = [] if ri.router['distributed']: if (self.conf.agent_mode == l3_constants.L3_AGENT_MODE_DVR_SNAT and - self.get_gw_port_host(ri.router) == self.host): + ri.get_gw_port_host() == self.host): ns_name = ri.snat_namespace.name else: # no centralized SNAT gateway for this node/agent @@ -665,16 +596,16 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, to_fip_interface_name = ( self._get_external_device_interface_name(ri, ex_gw_port)) ri.process_floating_ip_addresses(to_fip_interface_name) - snat_ports = self.get_snat_interfaces(ri) + snat_ports = ri.get_snat_interfaces() for p in ri.internal_ports: gateway = self._map_internal_interfaces(ri, p, snat_ports) internal_interface = ri.get_internal_device_name(p['id']) - self._snat_redirect_remove(ri, gateway['fixed_ips'][0] - ['ip_address'], - p, internal_interface) + ri._snat_redirect_remove(gateway['fixed_ips'][0]['ip_address'], + p, + internal_interface) if (self.conf.agent_mode == l3_constants.L3_AGENT_MODE_DVR_SNAT - and self.get_gw_port_host(ri.router) == self.host): + and ri.get_gw_port_host() == self.host): ns_name = ri.snat_namespace.name else: # not hosting agent - no work to do @@ -702,89 +633,6 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, (interface_name, ex_gw_ip))] return rules - def _internal_network_added(self, ns_name, network_id, port_id, - internal_cidr, mac_address, - interface_name, prefix, is_ha=False): - if not ip_lib.device_exists(interface_name, namespace=ns_name): - self.driver.plug(network_id, port_id, interface_name, mac_address, - namespace=ns_name, - prefix=prefix) - - if not is_ha: - self.driver.init_l3(interface_name, [internal_cidr], - namespace=ns_name) - ip_address = internal_cidr.split('/')[0] - ip_lib.send_gratuitous_arp(ns_name, - interface_name, - ip_address, - self.conf.send_arp_for_ha) - - 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'] - - interface_name = ri.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, - ri.is_ha) - - if ri.is_ha: - ri._ha_disable_addressing_on_interface(interface_name) - ri._add_vip(internal_cidr, interface_name) - - ex_gw_port = ri.get_ex_gw_port() - if ri.router['distributed'] and ex_gw_port: - snat_ports = self.get_snat_interfaces(ri) - sn_port = self._map_internal_interfaces(ri, port, snat_ports) - if sn_port: - self._snat_redirect_add(ri, sn_port['fixed_ips'][0] - ['ip_address'], port, interface_name) - if (self.conf.agent_mode == l3_constants.L3_AGENT_MODE_DVR_SNAT - and self.get_gw_port_host(ri.router) == self.host): - ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name( - ri.router['id']) - self._set_subnet_info(sn_port) - interface_name = ( - self.get_snat_int_device_name(sn_port['id'])) - self._internal_network_added(ns_name, - sn_port['network_id'], - sn_port['id'], - sn_port['ip_cidr'], - sn_port['mac_address'], - interface_name, - dvr.SNAT_INT_DEV_PREFIX) - - def internal_network_removed(self, ri, port): - port_id = port['id'] - interface_name = ri.get_internal_device_name(port_id) - if ri.router['distributed'] and ri.ex_gw_port: - sn_port = self._map_internal_interfaces(ri, port, ri.snat_ports) - if sn_port: - self._snat_redirect_remove(ri, sn_port['fixed_ips'][0] - ['ip_address'], port, - interface_name) - if (self.conf.agent_mode == l3_constants.L3_AGENT_MODE_DVR_SNAT - and ri.ex_gw_port['binding:host_id'] == self.host): - snat_interface = ( - self.get_snat_int_device_name(sn_port['id']) - ) - ns_name = ri.snat_namespace.name - prefix = dvr.SNAT_INT_DEV_PREFIX - if ip_lib.device_exists(snat_interface, - namespace=ns_name): - self.driver.unplug(snat_interface, namespace=ns_name, - prefix=prefix) - - if ip_lib.device_exists(interface_name, namespace=ri.ns_name): - if ri.is_ha: - ri._clear_vips(interface_name) - self.driver.unplug(interface_name, namespace=ri.ns_name, - prefix=INTERNAL_DEV_PREFIX) - def router_deleted(self, context, router_id): """Deal with router deletion RPC message.""" LOG.debug('Got router deleted notification for %s', router_id) diff --git a/neutron/agent/l3/dvr.py b/neutron/agent/l3/dvr.py index 2496d1917..d6829b63e 100644 --- a/neutron/agent/l3/dvr.py +++ b/neutron/agent/l3/dvr.py @@ -12,26 +12,19 @@ # License for the specific language governing permissions and limitations # under the License. -import binascii import weakref -import netaddr from oslo_log import log as logging from neutron.agent.l3 import dvr_fip_ns from neutron.agent.l3 import dvr_snat_ns -from neutron.agent.linux import ip_lib from neutron.agent.linux import iptables_manager -from neutron.common import constants as l3_constants -from neutron.i18n import _LE LOG = logging.getLogger(__name__) # TODO(Carl) Following constants retained to increase SNR during refactoring SNAT_INT_DEV_PREFIX = dvr_snat_ns.SNAT_INT_DEV_PREFIX SNAT_NS_PREFIX = dvr_snat_ns.SNAT_NS_PREFIX -# xor-folding mask used for IPv6 rule index -MASK_30 = 0x3fffffff class AgentMixin(object): @@ -65,52 +58,6 @@ class AgentMixin(object): def get_ports_by_subnet(self, subnet_id): return self.plugin_rpc.get_ports_by_subnet(self.context, subnet_id) - def get_snat_int_device_name(self, port_id): - return (SNAT_INT_DEV_PREFIX + - port_id)[:self.driver.DEV_NAME_LEN] - - def get_snat_interfaces(self, ri): - return ri.router.get(l3_constants.SNAT_ROUTER_INTF_KEY, []) - - def get_gw_port_host(self, router): - host = router.get('gw_port_host') - if not host: - LOG.debug("gw_port_host missing from router: %s", - router['id']) - return host - - def _get_snat_idx(self, ip_cidr): - """Generate index for DVR snat rules and route tables. - - The index value has to be 32 bits or less but more than the system - generated entries i.e. 32768. For IPv4 use the numeric value of the - cidr. For IPv6 generate a crc32 bit hash and xor-fold to 30 bits. - Use the freed range to extend smaller values so that they become - greater than system generated entries. - """ - net = netaddr.IPNetwork(ip_cidr) - if net.version == 6: - # the crc32 & 0xffffffff is for Python 2.6 and 3.0 compatibility - snat_idx = binascii.crc32(ip_cidr) & 0xffffffff - # xor-fold the hash to reserve upper range to extend smaller values - snat_idx = (snat_idx >> 30) ^ (snat_idx & MASK_30) - if snat_idx < 32768: - snat_idx = snat_idx + MASK_30 - else: - snat_idx = net.value - return snat_idx - - 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(_LE('DVR: no map match_port found!')) - def _create_dvr_gateway(self, ri, ex_gw_port, gw_interface_name, snat_ports): """Create SNAT namespace.""" @@ -118,12 +65,13 @@ class AgentMixin(object): # 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) + ri._set_subnet_info(port) + interface_name = ri.get_snat_int_device_name(port['id']) + # TODO(Carl) calling private method on router. Will fix soon. + ri._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, snat_ns.name, preserve_ips=[]) ri.snat_iptables_manager = iptables_manager.IptablesManager( @@ -132,33 +80,6 @@ class AgentMixin(object): # kicks the FW Agent to add rules for the snat namespace self.process_router_add(ri) - def _snat_redirect_add(self, ri, gateway, sn_port, sn_int): - """Adds rules and routes for SNAT redirection.""" - try: - ip_cidr = sn_port['ip_cidr'] - snat_idx = self._get_snat_idx(ip_cidr) - ns_ipr = ip_lib.IPRule(namespace=ri.ns_name) - ns_ipd = ip_lib.IPDevice(sn_int, namespace=ri.ns_name) - ns_ipwrapr = ip_lib.IPWrapper(namespace=ri.ns_name) - ns_ipd.route.add_gateway(gateway, table=snat_idx) - ns_ipr.rule.add(ip_cidr, snat_idx, snat_idx) - ns_ipwrapr.netns.execute(['sysctl', '-w', 'net.ipv4.conf.%s.' - 'send_redirects=0' % sn_int]) - except Exception: - LOG.exception(_LE('DVR: error adding redirection logic')) - - def _snat_redirect_remove(self, ri, gateway, sn_port, sn_int): - """Removes rules and routes for SNAT redirection.""" - try: - ip_cidr = sn_port['ip_cidr'] - snat_idx = self._get_snat_idx(ip_cidr) - ns_ipr = ip_lib.IPRule(namespace=ri.ns_name) - ns_ipd = ip_lib.IPDevice(sn_int, namespace=ri.ns_name) - ns_ipd.route.delete_gateway(gateway, table=snat_idx) - ns_ipr.rule.delete(ip_cidr, snat_idx, snat_idx) - except Exception: - LOG.exception(_LE('DVR: removed snat failed')) - def add_arp_entry(self, context, payload): """Add arp entry into router namespace. Called from RPC.""" router_id = payload['router_id'] diff --git a/neutron/agent/l3/dvr_router.py b/neutron/agent/l3/dvr_router.py index 0e1de86db..6af1e67ad 100644 --- a/neutron/agent/l3/dvr_router.py +++ b/neutron/agent/l3/dvr_router.py @@ -12,6 +12,9 @@ # License for the specific language governing permissions and limitations # under the License. +import binascii +import netaddr + from oslo_log import log as logging from oslo_utils import excutils @@ -24,6 +27,8 @@ from neutron.common import utils as common_utils from neutron.i18n import _LE LOG = logging.getLogger(__name__) +# xor-folding mask used for IPv6 rule index +MASK_30 = 0x3fffffff class DvrRouter(router.RouterInfo): @@ -45,6 +50,13 @@ class DvrRouter(router.RouterInfo): floating_ips = super(DvrRouter, self).get_floating_ips() return [i for i in floating_ips if i['host'] == self.host] + def get_snat_interfaces(self): + return self.router.get(l3_constants.SNAT_ROUTER_INTF_KEY, []) + + def get_snat_int_device_name(self, port_id): + long_name = dvr_snat_ns.SNAT_INT_DEV_PREFIX + port_id + return long_name[:self.driver.DEV_NAME_LEN] + def _handle_fip_nat_rules(self, interface_name, action): """Configures NAT rules for Floating IPs for DVR. @@ -139,7 +151,6 @@ class DvrRouter(router.RouterInfo): return l3_constants.FLOATINGIP_STATUS_ERROR # Special Handling for DVR - update FIP namespace - # and ri.namespace to handle DVR based FIP ip_cidr = common_utils.ip_to_cidr(fip['floating_ip_address']) self.floating_ip_added_dist(fip, ip_cidr) return l3_constants.FLOATINGIP_STATUS_ACTIVE @@ -212,3 +223,144 @@ class DvrRouter(router.RouterInfo): p['mac_address'], subnet_id, 'add') + + def _map_internal_interfaces(self, 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(_LE('DVR: no map match_port found!')) + + @staticmethod + def _get_snat_idx(ip_cidr): + """Generate index for DVR snat rules and route tables. + + The index value has to be 32 bits or less but more than the system + generated entries i.e. 32768. For IPv4 use the numeric value of the + cidr. For IPv6 generate a crc32 bit hash and xor-fold to 30 bits. + Use the freed range to extend smaller values so that they become + greater than system generated entries. + """ + net = netaddr.IPNetwork(ip_cidr) + if net.version == 6: + # the crc32 & 0xffffffff is for Python 2.6 and 3.0 compatibility + snat_idx = binascii.crc32(ip_cidr) & 0xffffffff + # xor-fold the hash to reserve upper range to extend smaller values + snat_idx = (snat_idx >> 30) ^ (snat_idx & MASK_30) + if snat_idx < 32768: + snat_idx = snat_idx + MASK_30 + else: + snat_idx = net.value + return snat_idx + + def _snat_redirect_add(self, gateway, sn_port, sn_int): + """Adds rules and routes for SNAT redirection.""" + try: + ip_cidr = sn_port['ip_cidr'] + snat_idx = self._get_snat_idx(ip_cidr) + ns_ipr = ip_lib.IPRule(namespace=self.ns_name) + ns_ipd = ip_lib.IPDevice(sn_int, namespace=self.ns_name) + ns_ipwrapr = ip_lib.IPWrapper(namespace=self.ns_name) + ns_ipd.route.add_gateway(gateway, table=snat_idx) + ns_ipr.rule.add(ip_cidr, snat_idx, snat_idx) + ns_ipwrapr.netns.execute(['sysctl', '-w', 'net.ipv4.conf.%s.' + 'send_redirects=0' % sn_int]) + except Exception: + LOG.exception(_LE('DVR: error adding redirection logic')) + + def _snat_redirect_remove(self, gateway, sn_port, sn_int): + """Removes rules and routes for SNAT redirection.""" + try: + ip_cidr = sn_port['ip_cidr'] + snat_idx = self._get_snat_idx(ip_cidr) + ns_ipr = ip_lib.IPRule(namespace=self.ns_name) + ns_ipd = ip_lib.IPDevice(sn_int, namespace=self.ns_name) + ns_ipd.route.delete_gateway(gateway, table=snat_idx) + ns_ipr.rule.delete(ip_cidr, snat_idx, snat_idx) + except Exception: + LOG.exception(_LE('DVR: removed snat failed')) + + def get_gw_port_host(self): + host = self.router.get('gw_port_host') + if not host: + LOG.debug("gw_port_host missing from router: %s", + self.router['id']) + return host + + def internal_network_added(self, port): + super(DvrRouter, self).internal_network_added(port) + + ex_gw_port = self.get_ex_gw_port() + if not ex_gw_port: + return + + snat_ports = self.get_snat_interfaces() + sn_port = self._map_internal_interfaces(port, snat_ports) + if not sn_port: + return + + interface_name = self.get_internal_device_name(port['id']) + self._snat_redirect_add(sn_port['fixed_ips'][0]['ip_address'], + port, + interface_name) + + # TODO(Carl) This is a sign that dvr needs two router classes. + is_this_snat_host = (self.agent_conf.agent_mode == 'dvr_snat' and + self.get_gw_port_host() == self.host) + if not is_this_snat_host: + return + + ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(self.router['id']) + self._set_subnet_info(sn_port) + interface_name = self.get_snat_int_device_name(sn_port['id']) + self._internal_network_added( + ns_name, + sn_port['network_id'], + sn_port['id'], + sn_port['ip_cidr'], + sn_port['mac_address'], + interface_name, + dvr_snat_ns.SNAT_INT_DEV_PREFIX) + + self._set_subnet_arp_info(port) + + def _dvr_internal_network_removed(self, port): + if not self.ex_gw_port: + return + + sn_port = self._map_internal_interfaces(port, self.snat_ports) + if not sn_port: + return + + # DVR handling code for SNAT + interface_name = self.get_internal_device_name(port['id']) + self._snat_redirect_remove(sn_port['fixed_ips'][0]['ip_address'], + port, + interface_name) + + is_this_snat_host = (self.agent_conf.agent_mode == 'dvr_snat' and + self.ex_gw_port['binding:host_id'] == self.host) + if not is_this_snat_host: + return + + snat_interface = ( + self.get_snat_int_device_name(sn_port['id'])) + ns_name = self.snat_namespace.name + prefix = dvr_snat_ns.SNAT_INT_DEV_PREFIX + if ip_lib.device_exists(snat_interface, namespace=ns_name): + self.driver.unplug(snat_interface, namespace=ns_name, + prefix=prefix) + + def internal_network_removed(self, port): + self._dvr_internal_network_removed(port) + super(DvrRouter, self).internal_network_removed(port) + + def get_floating_agent_gw_interface(self, ext_net_id): + """Filter Floating Agent GW port for the external network.""" + fip_ports = self.router.get(l3_constants.FLOATINGIP_AGENT_INTF_KEY, []) + return next( + (p for p in fip_ports if p['network_id'] == ext_net_id), None) diff --git a/neutron/agent/l3/ha.py b/neutron/agent/l3/ha.py index cf693e396..0b4e87127 100644 --- a/neutron/agent/l3/ha.py +++ b/neutron/agent/l3/ha.py @@ -61,7 +61,7 @@ class AgentMixin(object): ri.router_id) return - self._set_subnet_info(ha_port) + ri._set_subnet_info(ha_port) ri.ha_network_added(ha_port['network_id'], ha_port['id'], ha_port['ip_cidr'], diff --git a/neutron/agent/l3/ha_router.py b/neutron/agent/l3/ha_router.py index 83a25ee67..36bf0431e 100644 --- a/neutron/agent/l3/ha_router.py +++ b/neutron/agent/l3/ha_router.py @@ -251,3 +251,24 @@ class HaRouter(router.RouterInfo): def remove_floating_ip(self, device, ip_cidr): self._remove_vip(ip_cidr) + + def internal_network_added(self, port): + port_id = port['id'] + interface_name = self.get_internal_device_name(port_id) + + if not ip_lib.device_exists(interface_name, namespace=self.ns_name): + self.driver.plug(port['network_id'], + port_id, + interface_name, + port['mac_address'], + namespace=self.ns_name, + prefix=router.INTERNAL_DEV_PREFIX) + + self._ha_disable_addressing_on_interface(interface_name) + self._add_vip(port['ip_cidr'], interface_name) + + def internal_network_removed(self, port): + super(HaRouter, self).internal_network_removed(port) + + interface_name = self.get_internal_device_name(port['id']) + self._clear_vips(interface_name) diff --git a/neutron/agent/l3/router_info.py b/neutron/agent/l3/router_info.py index ab44bf449..43cd6ef83 100644 --- a/neutron/agent/l3/router_info.py +++ b/neutron/agent/l3/router_info.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +import netaddr + from oslo_log import log as logging from neutron.agent.l3 import namespaces @@ -20,10 +22,10 @@ from neutron.agent.linux import iptables_manager from neutron.common import constants as l3_constants from neutron.common import exceptions as n_exc from neutron.common import utils as common_utils -from neutron.i18n import _LW +from neutron.i18n import _LE, _LW LOG = logging.getLogger(__name__) -INTERNAL_DEV_PREFIX = 'qr-' +INTERNAL_DEV_PREFIX = namespaces.INTERNAL_DEV_PREFIX class RouterInfo(object): @@ -86,6 +88,16 @@ class RouterInfo(object): def get_internal_device_name(self, port_id): return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] + def _set_subnet_info(self, port): + ips = port['fixed_ips'] + if not ips: + raise Exception(_("Router port %s has no IP address") % port['id']) + if len(ips) > 1: + LOG.error(_LE("Ignoring multiple IPs on router port %s"), + port['id']) + prefixlen = netaddr.IPNetwork(port['subnet']['cidr']).prefixlen + port['ip_cidr'] = "%s/%s" % (ips[0]['ip_address'], prefixlen) + def perform_snat_action(self, snat_callback, *args): # Process SNAT rules for attached subnets if self._snat_action: @@ -245,3 +257,95 @@ class RouterInfo(object): self.radvd.disable() if self.router_namespace: self.router_namespace.delete() + + 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, + namespace=ns_name): + self.driver.plug(network_id, port_id, interface_name, mac_address, + namespace=ns_name, + prefix=prefix) + + self.driver.init_l3(interface_name, [internal_cidr], + namespace=ns_name) + ip_address = internal_cidr.split('/')[0] + ip_lib.send_gratuitous_arp(ns_name, + interface_name, + ip_address, + self.agent_conf.send_arp_for_ha) + + def internal_network_added(self, port): + network_id = port['network_id'] + port_id = port['id'] + internal_cidr = port['ip_cidr'] + mac_address = port['mac_address'] + + interface_name = self.get_internal_device_name(port_id) + + self._internal_network_added(self.ns_name, + network_id, + port_id, + internal_cidr, + mac_address, + interface_name, + INTERNAL_DEV_PREFIX) + + def internal_network_removed(self, port): + interface_name = self.get_internal_device_name(port['id']) + + if ip_lib.device_exists(interface_name, namespace=self.ns_name): + self.driver.unplug(interface_name, namespace=self.ns_name, + prefix=INTERNAL_DEV_PREFIX) + + def _get_existing_devices(self): + ip_wrapper = ip_lib.IPWrapper(namespace=self.ns_name) + ip_devs = ip_wrapper.get_devices(exclude_loopback=True) + return [ip_dev.name for ip_dev in ip_devs] + + def _process_internal_ports(self): + existing_port_ids = set(p['id'] for p in self.internal_ports) + + internal_ports = self.router.get(l3_constants.INTERFACE_KEY, []) + current_port_ids = set(p['id'] for p in internal_ports + if p['admin_state_up']) + + new_port_ids = current_port_ids - existing_port_ids + new_ports = [p for p in internal_ports if p['id'] in new_port_ids] + old_ports = [p for p in self.internal_ports + if p['id'] not in current_port_ids] + + new_ipv6_port = False + old_ipv6_port = False + for p in new_ports: + self._set_subnet_info(p) + self.internal_network_added(p) + self.internal_ports.append(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(p) + self.internal_ports.remove(p) + if (not old_ipv6_port and + netaddr.IPNetwork(p['subnet']['cidr']).version == 6): + old_ipv6_port = True + + # Enable RA + if new_ipv6_port or old_ipv6_port: + self.radvd.enable(internal_ports) + + existing_devices = self._get_existing_devices() + current_internal_devs = set(n for n in existing_devices + if n.startswith( + INTERNAL_DEV_PREFIX)) + current_port_devs = set(self.get_internal_device_name(port_id) + for port_id in current_port_ids) + stale_devs = current_internal_devs - current_port_devs + for stale_dev in stale_devs: + LOG.debug('Deleting stale internal router device: %s', + stale_dev) + self.driver.unplug(stale_dev, + namespace=self.ns_name, + prefix=INTERNAL_DEV_PREFIX) diff --git a/neutron/tests/unit/agent/l3/test_dvr_router.py b/neutron/tests/unit/agent/l3/test_dvr_router.py index 974852f3f..ef3dd5f71 100644 --- a/neutron/tests/unit/agent/l3/test_dvr_router.py +++ b/neutron/tests/unit/agent/l3/test_dvr_router.py @@ -184,3 +184,25 @@ class TestDvrRouterOperations(base.BaseTestCase): router_ports = [port] ri.router.get.return_value = router_ports self.assertEqual(None, ri._get_internal_port(mock.sentinel.subnet_id2)) + + def test__get_snat_idx_ipv4(self): + ip_cidr = '101.12.13.00/24' + ri = self._create_router(mock.MagicMock()) + snat_idx = ri._get_snat_idx(ip_cidr) + # 0x650C0D00 is numerical value of 101.12.13.00 + self.assertEqual(0x650C0D00, snat_idx) + + def test__get_snat_idx_ipv6(self): + ip_cidr = '2620:0:a03:e100::/64' + ri = self._create_router(mock.MagicMock()) + snat_idx = ri._get_snat_idx(ip_cidr) + # 0x3D345705 is 30 bit xor folded crc32 of the ip_cidr + self.assertEqual(0x3D345705, snat_idx) + + def test__get_snat_idx_ipv6_below_32768(self): + ip_cidr = 'd488::/30' + # crc32 of this ip_cidr is 0x1BD7 + ri = self._create_router(mock.MagicMock()) + snat_idx = ri._get_snat_idx(ip_cidr) + # 0x1BD7 + 0x3FFFFFFF = 0x40001BD6 + self.assertEqual(0x40001BD6, snat_idx) diff --git a/neutron/tests/unit/test_l3_agent.py b/neutron/tests/unit/test_l3_agent.py index 8a9415a23..fe41f7736 100644 --- a/neutron/tests/unit/test_l3_agent.py +++ b/neutron/tests/unit/test_l3_agent.py @@ -279,23 +279,6 @@ class BasicRouterOperationsFramework(base.BaseTestCase): self.ri_kwargs = {'agent_conf': self.conf, 'interface_driver': self.mock_driver} - def _prepare_internal_network_data(self): - port_id = _uuid() - router_id = _uuid() - network_id = _uuid() - router = prepare_router_data(num_internal_ports=2) - router_id = router['id'] - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - ri = l3router.RouterInfo(router_id, router, - **self.ri_kwargs) - 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} - - return agent, ri, port - def _process_router_instance_for_agent(self, agent, ri, router): ri.router = router if not ri.radvd: @@ -361,26 +344,41 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): l3_agent.L3NATAgent(HOSTNAME, self.conf) def _test_internal_network_action(self, action): - agent, ri, port = self._prepare_internal_network_data() + router = prepare_router_data(num_internal_ports=2) + router_id = router['id'] + ri = l3router.RouterInfo(router_id, router, **self.ri_kwargs) + port = {'network_id': _uuid(), + 'id': _uuid(), + 'ip_cidr': '99.0.1.9/24', + 'mac_address': 'ca:fe:de:ad:be:ef'} + interface_name = ri.get_internal_device_name(port['id']) if action == 'add': self.device_exists.return_value = False - agent.internal_network_added(ri, port) + ri.internal_network_added(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.ns_name, interface_name, '99.0.1.9', mock.ANY) elif action == 'remove': self.device_exists.return_value = True - agent.internal_network_removed(ri, port) + ri.internal_network_removed(port) self.assertEqual(self.mock_driver.unplug.call_count, 1) else: raise Exception("Invalid action %s" % action) def _test_internal_network_action_dist(self, action): - agent, ri, port = self._prepare_internal_network_data() - ri.router['distributed'] = True + router = prepare_router_data(num_internal_ports=2) + router_id = router['id'] + agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) + ri = dvr_router.DvrRouter( + agent, HOSTNAME, router_id, router, **self.ri_kwargs) + port = {'network_id': _uuid(), + 'id': _uuid(), + 'ip_cidr': '99.0.1.9/24', + 'mac_address': 'ca:fe:de:ad:be:ef'} + ri.router['gw_port_host'] = HOSTNAME agent.host = HOSTNAME agent.conf.agent_mode = 'dvr_snat' @@ -408,29 +406,30 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): if action == 'add': self.device_exists.return_value = False - agent._map_internal_interfaces = mock.Mock(return_value=sn_port) - agent._snat_redirect_add = mock.Mock() - agent._set_subnet_info = mock.Mock() - agent._internal_network_added = mock.Mock() - agent.internal_network_added(ri, port) - self.assertEqual(agent._snat_redirect_add.call_count, 1) - self.assertEqual(agent._set_subnet_info.call_count, 1) - self.assertEqual(agent._internal_network_added.call_count, 2) - agent._internal_network_added.assert_called_with( + ri._map_internal_interfaces = mock.Mock(return_value=sn_port) + ri._snat_redirect_add = mock.Mock() + ri._set_subnet_info = mock.Mock() + ri._set_subnet_arp_info = mock.Mock() + ri._internal_network_added = mock.Mock() + ri.internal_network_added(port) + self.assertEqual(ri._snat_redirect_add.call_count, 1) + self.assertEqual(ri._set_subnet_info.call_count, 1) + self.assertEqual(ri._internal_network_added.call_count, 2) + ri._set_subnet_arp_info.assert_called_once_with(port) + ri._internal_network_added.assert_called_with( dvr_snat_ns.SnatNamespace.get_snat_ns_name(ri.router['id']), sn_port['network_id'], sn_port['id'], sn_port['ip_cidr'], sn_port['mac_address'], - agent.get_snat_int_device_name(sn_port['id']), + ri.get_snat_int_device_name(sn_port['id']), dvr_snat_ns.SNAT_INT_DEV_PREFIX) elif action == 'remove': self.device_exists.return_value = False - agent._map_internal_interfaces = mock.Mock(return_value=sn_port) - agent._snat_redirect_remove = mock.Mock() - agent.internal_network_removed(ri, port) - agent._snat_redirect_remove.assert_called_with( - ri, + ri._map_internal_interfaces = mock.Mock(return_value=sn_port) + ri._snat_redirect_remove = mock.Mock() + ri.internal_network_removed(port) + ri._snat_redirect_remove.assert_called_with( sn_port['fixed_ips'][0]['ip_address'], port, ri.get_internal_device_name(port['id'])) @@ -456,12 +455,12 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): agent.conf.agent_mode = 'dvr_snat' agent.host = HOSTNAME agent._create_dvr_gateway = mock.Mock() - agent.get_snat_interfaces = mock.Mock(return_value=self.snat_ports) ri = dvr_router.DvrRouter(agent, HOSTNAME, router['id'], router, **self.ri_kwargs) + ri.get_snat_interfaces = mock.Mock(return_value=self.snat_ports) ri.create_snat_namespace() ri.fip_ns = agent.get_fip_ns(ex_net_id) ri.internal_ports = self.snat_ports @@ -509,7 +508,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): elif action == 'remove': self.device_exists.return_value = True agent._map_internal_interfaces = mock.Mock(return_value=sn_port) - agent._snat_redirect_remove = mock.Mock() + ri._snat_redirect_remove = mock.Mock() agent.external_gateway_removed(ri, ex_gw_port, interface_name) if not router.get('distributed'): self.mock_driver.unplug.assert_called_once_with( @@ -518,8 +517,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): namespace=mock.ANY, prefix=mock.ANY) else: - agent._snat_redirect_remove.assert_called_with( - ri, + ri._snat_redirect_remove.assert_called_with( sn_port['fixed_ips'][0]['ip_address'], sn_port, ri.get_internal_device_name(sn_port['id'])) @@ -643,32 +641,13 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): else: self.assertIn(r.rule, expected_rules) - def test__get_snat_idx_ipv4(self): - ip_cidr = '101.12.13.00/24' - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - snat_idx = agent._get_snat_idx(ip_cidr) - # 0x650C0D00 is numerical value of 101.12.13.00 - self.assertEqual(0x650C0D00, snat_idx) - - def test__get_snat_idx_ipv6(self): - ip_cidr = '2620:0:a03:e100::/64' - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - snat_idx = agent._get_snat_idx(ip_cidr) - # 0x3D345705 is 30 bit xor folded crc32 of the ip_cidr - self.assertEqual(0x3D345705, snat_idx) - - def test__get_snat_idx_ipv6_below_32768(self): - ip_cidr = 'd488::/30' - # crc32 of this ip_cidr is 0x1BD7 - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - snat_idx = agent._get_snat_idx(ip_cidr) - # 0x1BD7 + 0x3FFFFFFF = 0x40001BD6 - self.assertEqual(0x40001BD6, snat_idx) - def test__map_internal_interfaces(self): - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) router = prepare_router_data(num_internal_ports=4) - ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs) + ri = dvr_router.DvrRouter(mock.sentinel.agent, + HOSTNAME, + router['id'], + router, + **self.ri_kwargs) test_port = { 'mac_address': '00:12:23:34:45:56', 'fixed_ips': [{'subnet_id': _get_subnet_id( @@ -676,15 +655,11 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): '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]) + res_port = ri._map_internal_interfaces(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]) + res_ip = ri._map_internal_interfaces(internal_ports[0], [test_port]) self.assertNotEqual(test_port, res_ip) self.assertIsNone(res_ip) @@ -907,7 +882,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): agent, HOSTNAME, router['id'], router, **self.ri_kwargs) self.assertEqual( agent_gateway_port[0], - agent.get_floating_agent_gw_interface(ri, fake_network_id)) + ri.get_floating_agent_gw_interface(fake_network_id)) @mock.patch.object(lla.LinkLocalAllocator, '_write') def test_create_dvr_fip_interfaces(self, lla_write): @@ -945,9 +920,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): with contextlib.nested(mock.patch.object(ri, 'get_floating_ips'), mock.patch.object( - agent, 'get_floating_agent_gw_interface'), + ri, 'get_floating_agent_gw_interface'), mock.patch.object( - agent, '_set_subnet_info') + ri, '_set_subnet_info') ) as (fips, fip_gw_port, sub_info): @@ -1205,7 +1180,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs) agent.external_gateway_added = mock.Mock() with mock.patch.object( - l3_agent.L3NATAgent, + ri, 'internal_network_added') as internal_network_added: # raise RuntimeError to simulate that an unexpected exception # occurs @@ -1233,7 +1208,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): agent.process_router(ri) with mock.patch.object( - l3_agent.L3NATAgent, + ri, 'internal_network_removed') as internal_net_removed: # raise RuntimeError to simulate that an unexpected exception # occurs @@ -1384,9 +1359,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): self.assertEqual(len(internal_ports), 1) internal_port = internal_ports[0] - with contextlib.nested(mock.patch.object(l3_agent.L3NATAgent, + with contextlib.nested(mock.patch.object(ri, 'internal_network_removed'), - mock.patch.object(l3_agent.L3NATAgent, + mock.patch.object(ri, 'internal_network_added'), mock.patch.object(l3_agent.L3NATAgent, 'external_gateway_removed'), @@ -1402,8 +1377,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): self.assertEqual(external_gateway_added.call_count, 1) self.assertFalse(external_gateway_removed.called) self.assertFalse(internal_network_removed.called) - internal_network_added.assert_called_once_with( - ri, internal_port) + internal_network_added.assert_called_once_with(internal_port) self.assertEqual(self.mock_driver.unplug.call_count, len(stale_devnames)) calls = [mock.call(stale_devname, @@ -1767,7 +1741,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): 'mac_address': 'ca:fe:de:ad:be:ef', 'ip_cidr': '20.0.0.30/24'} - interface_name = agent.get_snat_int_device_name(port_id) + interface_name = ri.get_snat_int_device_name(port_id) self.device_exists.return_value = False agent._create_dvr_gateway(ri, dvr_gw_port, interface_name,