From: Carl Baldwin Date: Wed, 7 Jan 2015 19:25:41 +0000 (+0000) Subject: Encapsulate DVR Fip namespace X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=59795ab607579f2c764fecb33f538dae0124671e;p=openstack-build%2Fneutron-build.git Encapsulate DVR Fip namespace The floating ip namespace is an important concept that links a DVR enabled L3 agent to DVR routers. The namespace is shared by all of the dvr routers on a compute host connected to the same network. It is managed by the dvr enabled agent. Hence, it should be known to the agent and made available to the DVR routers as well. This calls for a proper encapsulation of this functionality in a class which can be accessed by both the agent and the routers. In trying to tease floating ip functionality out of the L3 agent and in to the new router classes, I found it very difficult to do because the logic around the floating ip namespace has not yet been properly encapsulated. Change-Id: I0913cfae391164146bbb50ed42311506d6559e3f Partially-Implements: bp/restructure-l3-agent --- diff --git a/neutron/agent/l3/agent.py b/neutron/agent/l3/agent.py index 6020ce6a9..ab0b84cfb 100644 --- a/neutron/agent/l3/agent.py +++ b/neutron/agent/l3/agent.py @@ -23,6 +23,7 @@ from oslo.utils import timeutils from neutron.agent.common import config from neutron.agent.l3 import dvr +from neutron.agent.l3 import dvr_fip_ns from neutron.agent.l3 import dvr_router from neutron.agent.l3 import event_observers from neutron.agent.l3 import ha @@ -274,7 +275,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, def _destroy_namespace(self, ns): if ns.startswith(NS_PREFIX): self._destroy_router_namespace(ns) - elif ns.startswith(dvr.FIP_NS_PREFIX): + elif ns.startswith(dvr_fip_ns.FIP_NS_PREFIX): self._destroy_fip_namespace(ns) elif ns.startswith(dvr.SNAT_NS_PREFIX): self._destroy_snat_namespace(ns) @@ -294,7 +295,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, # device is on default bridge self.driver.unplug(d.name, namespace=ns, prefix=INTERNAL_DEV_PREFIX) - elif d.name.startswith(dvr.ROUTER_2_FIP_DEV_PREFIX): + elif d.name.startswith(dvr_fip_ns.ROUTER_2_FIP_DEV_PREFIX): ns_ip.del_veth(d.name) elif d.name.startswith(EXTERNAL_DEV_PREFIX): self.driver.unplug(d.name, @@ -588,7 +589,10 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, # TODO(mrsmith) - we shouldn't need to check here if 'distributed' not in ri.router: ri.router['distributed'] = False - self.scan_fip_ports(ri) + ex_gw_port = self._get_ex_gw_port(ri) + 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) self._process_external(ri) # Process static routes for router @@ -598,7 +602,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, self._process_ha_router(ri) # Update ex_gw_port and enable_snat on the router info cache - ri.ex_gw_port = self._get_ex_gw_port(ri) + ri.ex_gw_port = ex_gw_port ri.snat_ports = ri.router.get(l3_constants.SNAT_ROUTER_INTF_KEY, []) ri.enable_snat = ri.router.get('enable_snat') @@ -660,25 +664,32 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, def create_dvr_fip_interfaces(self, ri, ex_gw_port): floating_ips = self.get_floating_ips(ri) if floating_ips: - is_first = self._fip_ns_subscribe(ri.router_id) + is_first = ri.fip_ns.subscribe(ri.router_id) if is_first: - self._create_agent_gateway_port(ri, floating_ips[0] - ['floating_network_id']) - - if self.agent_gateway_port and floating_ips: - fip_net_id = floating_ips[0]['floating_network_id'] - self.create_rtr_2_fip_link(ri, fip_net_id) + agent_gateway_port = ( + self.plugin_rpc.get_agent_gateway_port( + self.context, ex_gw_port['network_id'])) + if 'subnet' not in agent_gateway_port: + LOG.error(_LE('Missing subnet/agent_gateway_port')) + else: + self._set_subnet_info(agent_gateway_port) + ri.fip_ns.create_gateway_port(agent_gateway_port) + + if ri.fip_ns.agent_gateway_port and floating_ips: + if ri.dist_fip_count == 0: + ri.fip_ns.create_rtr_2_fip_link(ri) + + # kicks the FW Agent to add rules for the IR namespace if + # configured + self.process_router_add(ri) def _get_external_device_interface_name(self, ri, ex_gw_port): if ri.router['distributed']: - fip_int = self.get_fip_int_device_name(ri.router_id) - # TODO(mrsmith) refactor for multiple ext nets - fip_ns = self.get_fip_ns_name(str(self._fetch_external_net_id())) - + fip_int = ri.fip_ns.get_int_device_name(ri.router_id) if ip_lib.device_exists(fip_int, root_helper=self.root_helper, - namespace=fip_ns): - return self.get_rtr_int_device_name(ri.router_id) + namespace=ri.fip_ns.get_name()): + return ri.fip_ns.get_rtr_ext_device_name(ri.router_id) else: return self.get_external_device_name(ex_gw_port['id']) diff --git a/neutron/agent/l3/dvr.py b/neutron/agent/l3/dvr.py index ab35d9920..fceb3f573 100644 --- a/neutron/agent/l3/dvr.py +++ b/neutron/agent/l3/dvr.py @@ -14,30 +14,18 @@ import binascii import netaddr -import os -from neutron.agent.l3 import link_local_allocator as lla +from neutron.agent.l3 import dvr_fip_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.common import utils as common_utils from neutron.i18n import _LE from neutron.openstack.common import log as logging LOG = logging.getLogger(__name__) SNAT_INT_DEV_PREFIX = 'sg-' -FIP_EXT_DEV_PREFIX = 'fg-' -FIP_NS_PREFIX = 'fip-' SNAT_NS_PREFIX = 'snat-' -FIP_2_ROUTER_DEV_PREFIX = 'fpr-' -ROUTER_2_FIP_DEV_PREFIX = 'rfp-' -FIP_LL_SUBNET = '169.254.30.0/23' -# Rule priority range for FIPs -FIP_PR_START = 32768 -FIP_PR_END = FIP_PR_START + 40000 -# Route Table index for FIPs -FIP_RT_TBL = 16 # xor-folding mask used for IPv6 rule index MASK_30 = 0x3fffffff @@ -45,24 +33,23 @@ MASK_30 = 0x3fffffff class AgentMixin(object): def __init__(self, host): # dvr data - self.agent_gateway_port = None - self.fip_ns_subscribers = set() - self.local_subnets = lla.LinkLocalAllocator( - os.path.join(self.conf.state_path, 'fip-linklocal-networks'), - FIP_LL_SUBNET) - self.fip_priorities = set(range(FIP_PR_START, - FIP_PR_END)) - + self._fip_namespaces = {} super(AgentMixin, self).__init__(host) - def _fip_ns_subscribe(self, router_id): - is_first = (len(self.fip_ns_subscribers) == 0) - self.fip_ns_subscribers.add(router_id) - return is_first + def get_fip_ns(self, ext_net_id): + # TODO(Carl) is this necessary? Code that this replaced was careful to + # convert these to string like this so I preserved that. + ext_net_id = str(ext_net_id) + + if ext_net_id not in self._fip_namespaces: + fip_ns = dvr_fip_ns.FipNamespace(ext_net_id, + self.conf, + self.driver, + self.root_helper, + self.use_ipv6) + self._fip_namespaces[ext_net_id] = fip_ns - def _fip_ns_unsubscribe(self, router_id): - self.fip_ns_subscribers.discard(router_id) - return len(self.fip_ns_subscribers) == 0 + return self._fip_namespaces[ext_net_id] def _destroy_snat_namespace(self, ns): ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=ns) @@ -79,24 +66,10 @@ class AgentMixin(object): 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 - ns_ip.del_veth(d.name) - elif d.name.startswith(FIP_EXT_DEV_PREFIX): - # single port from FIP NS to br-ext - # TODO(carl) Where does the port get deleted? - 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 + ex_net_id = ns[len(dvr_fip_ns.FIP_NS_PREFIX):] + fip_ns = self.get_fip_ns(ex_net_id) + del self._fip_namespaces[ex_net_id] + fip_ns.destroy() def _set_subnet_arp_info(self, ri, port): """Set ARP info retrieved from Plugin for existing ports.""" @@ -123,43 +96,10 @@ class AgentMixin(object): if f['subnet_id'] == subnet_id: return port - def scan_fip_ports(self, ri): - # don't scan if not dvr or count is not None - if not ri.router.get('distributed') or ri.dist_fip_count is not None: - return - - # scan system for any existing fip ports - ri.dist_fip_count = 0 - rtr_2_fip_interface = self.get_rtr_int_device_name(ri.router_id) - if ip_lib.device_exists(rtr_2_fip_interface, - root_helper=self.root_helper, - namespace=ri.ns_name): - device = ip_lib.IPDevice(rtr_2_fip_interface, self.root_helper, - namespace=ri.ns_name) - existing_cidrs = [addr['cidr'] for addr in device.addr.list()] - fip_cidrs = [c for c in existing_cidrs if - common_utils.is_cidr_host(c)] - ri.dist_fip_count = len(fip_cidrs) - - 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) @@ -205,35 +145,6 @@ class AgentMixin(object): else: LOG.error(_LE('DVR: no map match_port found!')) - 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 _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 _create_dvr_gateway(self, ri, ex_gw_port, gw_interface_name, snat_ports): """Create SNAT namespace.""" @@ -257,116 +168,23 @@ class AgentMixin(object): # kicks the FW Agent to add rules for the snat namespace self.process_router_add(ri) - 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] - ip_lib.send_gratuitous_arp(ns_name, - interface_name, - ip_address, - self.conf.send_arp_for_ha, - self.root_helper) - - 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 _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(_LE('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) - ri.fip_iptables_manager = iptables_manager.IptablesManager( - root_helper=self.root_helper, namespace=fip_ns_name, - use_ipv6=self.use_ipv6) - # no connection tracking needed in fip namespace - ri.fip_iptables_manager.ipv4['raw'].add_rule('PREROUTING', - '-j CT --notrack') - ri.fip_iptables_manager.apply() - 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_fip_subnet is None: - ri.rtr_fip_subnet = self.local_subnets.allocate(ri.router_id) - rtr_2_fip, fip_2_rtr = ri.rtr_fip_subnet.get_pair() - ip_wrapper = ip_lib.IPWrapper(self.root_helper, - namespace=ri.ns_name) - if not ip_lib.device_exists(rtr_2_fip_name, 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(str(rtr_2_fip), - rtr_2_fip_name, ri.ns_name) - self.internal_ns_interface_added(str(fip_2_rtr), - 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(str(fip_2_rtr.ip), table=FIP_RT_TBL) - #setup the NAT rules and chains - self._handle_router_fip_nat_rules(ri, rtr_2_fip_name, 'add_rules') - # kicks the FW Agent to add rules for the IR namespace if configured - self.process_router_add(ri) - def floating_ip_added_dist(self, ri, fip, fip_cidr): """Add floating IP to FIP namespace.""" floating_ip = fip['floating_ip_address'] fixed_ip = fip['fixed_ip_address'] - rule_pr = self.fip_priorities.pop() + rule_pr = ri.fip_ns.allocate_rule_priority() ri.floating_ips_dict[floating_ip] = rule_pr - fip_2_rtr_name = self.get_fip_int_device_name(ri.router_id) + fip_2_rtr_name = ri.fip_ns.get_int_device_name(ri.router_id) ip_rule = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name) - ip_rule.add(fixed_ip, FIP_RT_TBL, rule_pr) + ip_rule.add(fixed_ip, dvr_fip_ns.FIP_RT_TBL, rule_pr) #Add routing rule in fip namespace - fip_ns_name = self.get_fip_ns_name(str(fip['floating_network_id'])) + fip_ns_name = ri.fip_ns.get_name() rtr_2_fip, _ = ri.rtr_fip_subnet.get_pair() device = ip_lib.IPDevice(fip_2_rtr_name, self.root_helper, namespace=fip_ns_name) device.route.add_route(fip_cidr, str(rtr_2_fip.ip)) interface_name = ( - self.get_fip_ext_device_name(self.agent_gateway_port['id'])) + ri.fip_ns.get_ext_device_name(ri.fip_ns.agent_gateway_port['id'])) ip_lib.send_garp_for_proxyarp(fip_ns_name, interface_name, floating_ip, @@ -378,17 +196,17 @@ class AgentMixin(object): 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) + rtr_2_fip_name = ri.fip_ns.get_rtr_ext_device_name(ri.router_id) + fip_2_rtr_name = ri.fip_ns.get_int_device_name(ri.router_id) if ri.rtr_fip_subnet is None: ri.rtr_fip_subnet = self.local_subnets.allocate(ri.router_id) rtr_2_fip, fip_2_rtr = ri.rtr_fip_subnet.get_pair() - fip_ns_name = self.get_fip_ns_name(str(self._fetch_external_net_id())) + fip_ns_name = ri.fip_ns.get_name() if floating_ip in ri.floating_ips_dict: rule_pr = ri.floating_ips_dict[floating_ip] ip_rule = ip_lib.IpRule(self.root_helper, namespace=ri.ns_name) - ip_rule.delete(floating_ip, FIP_RT_TBL, rule_pr) - self.fip_priorities.add(rule_pr) + ip_rule.delete(floating_ip, dvr_fip_ns.FIP_RT_TBL, rule_pr) + ri.fip_ns.deallocate_rule_priority(rule_pr) #TODO(rajeev): Handle else case - exception/log? device = ip_lib.IPDevice(fip_2_rtr_name, self.root_helper, @@ -403,11 +221,11 @@ class AgentMixin(object): namespace=ri.ns_name) ns_ip = ip_lib.IPWrapper(self.root_helper, namespace=fip_ns_name) device.route.delete_gateway(str(fip_2_rtr.ip), - table=FIP_RT_TBL) - self.local_subnets.release(ri.router_id) + table=dvr_fip_ns.FIP_RT_TBL) + ri.fip_ns.local_subnets.release(ri.router_id) ri.rtr_fip_subnet = None ns_ip.del_veth(fip_2_rtr_name) - is_last = self._fip_ns_unsubscribe(ri.router_id) + is_last = ri.fip_ns.unsubscribe(ri.router_id) # clean up fip-namespace if this is the last FIP if is_last: self._destroy_fip_namespace(fip_ns_name) diff --git a/neutron/agent/l3/dvr_fip_ns.py b/neutron/agent/l3/dvr_fip_ns.py new file mode 100644 index 000000000..52bd59b3f --- /dev/null +++ b/neutron/agent/l3/dvr_fip_ns.py @@ -0,0 +1,238 @@ +# Copyright (c) 2015 Openstack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import netaddr +import os + +from neutron.agent.l3 import link_local_allocator as lla +from neutron.agent.linux import ip_lib +from neutron.agent.linux import iptables_manager +from neutron.common import utils as common_utils +from neutron.i18n import _LE +from neutron.openstack.common import log as logging + +LOG = logging.getLogger(__name__) + +FIP_NS_PREFIX = 'fip-' +FIP_EXT_DEV_PREFIX = 'fg-' +FIP_2_ROUTER_DEV_PREFIX = 'fpr-' +ROUTER_2_FIP_DEV_PREFIX = 'rfp-' +# Route Table index for FIPs +FIP_RT_TBL = 16 +FIP_LL_SUBNET = '169.254.30.0/23' +# Rule priority range for FIPs +FIP_PR_START = 32768 +FIP_PR_END = FIP_PR_START + 40000 + + +class FipNamespace(object): + def __init__(self, ext_net_id, agent_conf, driver, root_helper, use_ipv6): + self._ext_net_id = ext_net_id + self.agent_conf = agent_conf + self.driver = driver + self.root_helper = root_helper + self.use_ipv6 = use_ipv6 + self.agent_gateway_port = None + self._subscribers = set() + self._rule_priorities = set(range(FIP_PR_START, FIP_PR_END)) + self._iptables_manager = iptables_manager.IptablesManager( + root_helper=self.root_helper, + namespace=self.get_name(), + use_ipv6=self.use_ipv6) + path = os.path.join(agent_conf.state_path, 'fip-linklocal-networks') + self.local_subnets = lla.LinkLocalAllocator(path, FIP_LL_SUBNET) + + def get_name(self): + return (FIP_NS_PREFIX + self._ext_net_id) + + def get_ext_device_name(self, port_id): + return (FIP_EXT_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] + + def get_int_device_name(self, router_id): + return (FIP_2_ROUTER_DEV_PREFIX + router_id)[:self.driver.DEV_NAME_LEN] + + def get_rtr_ext_device_name(self, router_id): + return (ROUTER_2_FIP_DEV_PREFIX + router_id)[:self.driver.DEV_NAME_LEN] + + def has_subscribers(self): + return len(self._subscribers) != 0 + + def subscribe(self, router_id): + is_first = not self.has_subscribers() + self._subscribers.add(router_id) + return is_first + + def unsubscribe(self, router_id): + self._subscribers.discard(router_id) + return not self.has_subscribers() + + def allocate_rule_priority(self): + return self._rule_priorities.pop() + + def deallocate_rule_priority(self, rule_pr): + self._rule_priorities.add(rule_pr) + + def _gateway_added(self, ex_gw_port, interface_name): + """Add Floating IP gateway port.""" + ns_name = self.get_name() + 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.agent_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 = str(netaddr.IPNetwork(ex_gw_port['ip_cidr']).ip) + ip_lib.send_gratuitous_arp(ns_name, + interface_name, + ip_address, + self.agent_conf.send_arp_for_ha, + self.root_helper) + + 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] + # TODO(Carl) mlavelle's work has self.ip_wrapper + ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=ns_name) + ip_wrapper.netns.execute(cmd, check_exit_code=False) + + def create(self): + # TODO(Carl) Get this functionality from mlavelle's namespace baseclass + ip_wrapper_root = ip_lib.IPWrapper(self.root_helper) + ip_wrapper = ip_wrapper_root.ensure_namespace(self.get_name()) + ip_wrapper.netns.execute(['sysctl', '-w', 'net.ipv4.ip_forward=1']) + if self.use_ipv6: + ip_wrapper.netns.execute(['sysctl', '-w', + 'net.ipv6.conf.all.forwarding=1']) + + # no connection tracking needed in fip namespace + self._iptables_manager.ipv4['raw'].add_rule('PREROUTING', + '-j CT --notrack') + self._iptables_manager.apply() + + def destroy(self): + ns = self.get_name() + # TODO(carl) Reconcile this with mlavelle's namespace work + # TODO(carl) mlavelle's work has self.ip_wrapper + ip_wrapper = ip_lib.IPWrapper(self.root_helper, namespace=ns) + for d in ip_wrapper.get_devices(exclude_loopback=True): + if d.name.startswith(FIP_2_ROUTER_DEV_PREFIX): + # internal link between IRs and FIP NS + ip_wrapper.del_veth(d.name) + elif d.name.startswith(FIP_EXT_DEV_PREFIX): + # single port from FIP NS to br-ext + # TODO(carl) Where does the port get deleted? + LOG.debug('DVR: unplug: %s', d.name) + ext_net_bridge = self.agent_conf.external_network_bridge + self.driver.unplug(d.name, + bridge=ext_net_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.agent_conf.router_delete_namespaces: + try: + ip_wrapper.netns.delete(ns) + except RuntimeError: + LOG.exception(_LE('Failed trying to delete namespace: %s'), ns) + + self.agent_gateway_port = None + + def create_gateway_port(self, agent_gateway_port): + """Create Floating IP gateway port. + + Request port creation from Plugin then creates + Floating IP namespace and adds gateway port. + """ + self.agent_gateway_port = agent_gateway_port + + # add fip-namespace and agent_gateway_port + self.create() + + iface_name = self.get_ext_device_name(agent_gateway_port['id']) + self._gateway_added(agent_gateway_port, iface_name) + + 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 create_rtr_2_fip_link(self, ri): + """Create interface between router and Floating IP namespace.""" + rtr_2_fip_name = self.get_rtr_ext_device_name(ri.router_id) + fip_2_rtr_name = self.get_int_device_name(ri.router_id) + fip_ns_name = self.get_name() + + # add link local IP to interface + if ri.rtr_fip_subnet is None: + ri.rtr_fip_subnet = self.local_subnets.allocate(ri.router_id) + rtr_2_fip, fip_2_rtr = ri.rtr_fip_subnet.get_pair() + ip_wrapper = ip_lib.IPWrapper(self.root_helper, + namespace=ri.ns_name) + device_exists = ip_lib.device_exists(rtr_2_fip_name, + self.root_helper, + namespace=ri.ns_name) + if not device_exists: + int_dev = ip_wrapper.add_veth(rtr_2_fip_name, + fip_2_rtr_name, + fip_ns_name) + self._internal_ns_interface_added(str(rtr_2_fip), + rtr_2_fip_name, + ri.ns_name) + self._internal_ns_interface_added(str(fip_2_rtr), + 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(str(fip_2_rtr.ip), table=FIP_RT_TBL) + #setup the NAT rules and chains + ri._handle_fip_nat_rules(rtr_2_fip_name, 'add_rules') + + def scan_fip_ports(self, ri): + # don't scan if not dvr or count is not None + if ri.dist_fip_count is not None: + return + + # scan system for any existing fip ports + ri.dist_fip_count = 0 + rtr_2_fip_interface = self.get_rtr_ext_device_name(ri.router_id) + if ip_lib.device_exists(rtr_2_fip_interface, + root_helper=self.root_helper, + namespace=ri.ns_name): + device = ip_lib.IPDevice(rtr_2_fip_interface, + self.root_helper, + namespace=ri.ns_name) + existing_cidrs = [addr['cidr'] for addr in device.addr.list()] + fip_cidrs = [c for c in existing_cidrs if + common_utils.is_cidr_host(c)] + ri.dist_fip_count = len(fip_cidrs) diff --git a/neutron/agent/l3/dvr_router.py b/neutron/agent/l3/dvr_router.py index 6822a15aa..8ad32da9a 100644 --- a/neutron/agent/l3/dvr_router.py +++ b/neutron/agent/l3/dvr_router.py @@ -24,3 +24,26 @@ class DvrRouter(router.RouterInfo): # Linklocal subnet for router and floating IP namespace link self.rtr_fip_subnet = None self.dist_fip_count = None + + def _handle_fip_nat_rules(self, 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. + """ + self.iptables_manager.ipv4['nat'].empty_chain('POSTROUTING') + self.iptables_manager.ipv4['nat'].empty_chain('snat') + + # Add back the jump to float-snat + self.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}) + self.iptables_manager.ipv4['nat'].add_rule(*rule) + self.iptables_manager.apply() diff --git a/neutron/tests/functional/agent/test_l3_agent.py b/neutron/tests/functional/agent/test_l3_agent.py index 03bfaf052..837bb57a4 100755 --- a/neutron/tests/functional/agent/test_l3_agent.py +++ b/neutron/tests/functional/agent/test_l3_agent.py @@ -681,17 +681,16 @@ class TestDvrRouter(L3AgentTestFramework): self.assertTrue(floating_ips) external_port = self.agent._get_ex_gw_port(router) - fip_ns_name = ( - self.agent.get_fip_ns_name(floating_ips[0]['floating_network_id']) - ) + fip_ns = self.agent.get_fip_ns(floating_ips[0]['floating_network_id']) + fip_ns_name = fip_ns.get_name() fg_port_created_succesfully = ip_lib.device_exists_with_ip_mac( - self.agent.get_fip_ext_device_name(external_port['id']), + fip_ns.get_ext_device_name(external_port['id']), external_port['ip_cidr'], external_port['mac_address'], fip_ns_name, self.root_helper) self.assertTrue(fg_port_created_succesfully) # Check fpr-router device has been created - device_name = self.agent.get_fip_int_device_name(router.router_id) + device_name = fip_ns.get_int_device_name(router.router_id) fpr_router_device_created_succesfully = ip_lib.device_exists( device_name, self.root_helper, fip_ns_name) self.assertTrue(fpr_router_device_created_succesfully) @@ -699,6 +698,6 @@ class TestDvrRouter(L3AgentTestFramework): # In the router namespace # Check rfp- is created correctly for fip in floating_ips: - device_name = self.agent.get_rtr_int_device_name(router.router_id) + device_name = fip_ns.get_rtr_ext_device_name(router.router_id) self.assertTrue(ip_lib.device_exists( device_name, self.root_helper, router.ns_name)) diff --git a/neutron/tests/unit/agent/test_dvr_fip_ns.py b/neutron/tests/unit/agent/test_dvr_fip_ns.py new file mode 100644 index 000000000..9f4bc78db --- /dev/null +++ b/neutron/tests/unit/agent/test_dvr_fip_ns.py @@ -0,0 +1,188 @@ +# Copyright (c) 2015 Openstack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock + +from neutron.agent.l3 import dvr_fip_ns +from neutron.agent.l3 import link_local_allocator as lla +from neutron.agent.linux import ip_lib +from neutron.openstack.common import uuidutils +from neutron.tests import base + +_uuid = uuidutils.generate_uuid + + +class TestDvrFipNs(base.BaseTestCase): + def setUp(self): + super(TestDvrFipNs, self).setUp() + self.conf = mock.Mock() + self.conf.state_path = '/tmp' + self.driver = mock.Mock() + self.driver.DEV_NAME_LEN = 14 + self.net_id = _uuid() + self.fip_ns = dvr_fip_ns.FipNamespace(self.net_id, + self.conf, + self.driver, + mock.sentinel.root_helper, + use_ipv6=True) + + def test_subscribe(self): + is_first = self.fip_ns.subscribe(mock.sentinel.router_id) + self.assertTrue(is_first) + + def test_subscribe_not_first(self): + self.fip_ns.subscribe(mock.sentinel.router_id) + is_first = self.fip_ns.subscribe(mock.sentinel.router_id2) + self.assertFalse(is_first) + + def test_unsubscribe(self): + self.fip_ns.subscribe(mock.sentinel.router_id) + is_last = self.fip_ns.unsubscribe(mock.sentinel.router_id) + self.assertTrue(is_last) + + def test_unsubscribe_not_last(self): + self.fip_ns.subscribe(mock.sentinel.router_id) + self.fip_ns.subscribe(mock.sentinel.router_id2) + is_last = self.fip_ns.unsubscribe(mock.sentinel.router_id2) + self.assertFalse(is_last) + + def test_allocate_rule_priority(self): + pr = self.fip_ns.allocate_rule_priority() + self.assertNotIn(pr, self.fip_ns._rule_priorities) + + def test_deallocate_rule_priority(self): + pr = self.fip_ns.allocate_rule_priority() + self.fip_ns.deallocate_rule_priority(pr) + self.assertIn(pr, self.fip_ns._rule_priorities) + + @mock.patch.object(ip_lib, 'IPWrapper') + @mock.patch.object(ip_lib, 'IPDevice') + @mock.patch.object(ip_lib, 'send_gratuitous_arp') + @mock.patch.object(ip_lib, 'device_exists') + def test_gateway_added(self, device_exists, send_arp, IPDevice, IPWrapper): + 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': self.net_id, + 'mac_address': 'ca:fe:de:ad:be:ef', + 'ip_cidr': '20.0.0.30/24'} + + device_exists.return_value = False + self.fip_ns._gateway_added(agent_gw_port, + mock.sentinel.interface_name) + self.assertEqual(self.driver.plug.call_count, 1) + self.assertEqual(self.driver.init_l3.call_count, 1) + send_arp.assert_called_once_with(self.fip_ns.get_name(), + mock.sentinel.interface_name, + '20.0.0.30', + mock.ANY, mock.ANY) + + @mock.patch.object(ip_lib, 'IPWrapper') + def test_destroy(self, IPWrapper): + ip_wrapper = IPWrapper() + dev1 = mock.Mock() + dev1.name = 'fpr-aaaa' + dev2 = mock.Mock() + dev2.name = 'fg-aaaa' + ip_wrapper.get_devices.return_value = [dev1, dev2] + + self.fip_ns.destroy() + + ext_net_bridge = self.conf.external_network_bridge + ns_name = self.fip_ns.get_name() + self.driver.unplug.assert_called_once_with('fg-aaaa', + bridge=ext_net_bridge, + prefix='fg-', + namespace=ns_name) + ip_wrapper.del_veth.assert_called_once_with('fpr-aaaa') + + @mock.patch.object(ip_lib, 'IPWrapper') + @mock.patch.object(ip_lib, 'IPDevice') + @mock.patch.object(ip_lib, 'device_exists') + def test_create_rtr_2_fip_link(self, device_exists, IPDevice, IPWrapper): + ri = mock.Mock() + ri.router_id = _uuid() + ri.rtr_fip_subnet = None + ri.ns_name = mock.sentinel.router_ns + + rtr_2_fip_name = self.fip_ns.get_rtr_ext_device_name(ri.router_id) + fip_2_rtr_name = self.fip_ns.get_int_device_name(ri.router_id) + fip_ns_name = self.fip_ns.get_name() + + self.fip_ns.local_subnets = allocator = mock.Mock() + pair = lla.LinkLocalAddressPair('169.254.31.28/31') + allocator.allocate.return_value = pair + device_exists.return_value = False + self.fip_ns.create_rtr_2_fip_link(ri) + + ip_wrapper = IPWrapper() + ip_wrapper.add_veth.assert_called_with(rtr_2_fip_name, + fip_2_rtr_name, + fip_ns_name) + + device = IPDevice() + device.route.add_gateway.assert_called_once_with( + '169.254.31.29', table=16) + + @mock.patch.object(ip_lib, 'IPWrapper') + @mock.patch.object(ip_lib, 'IPDevice') + @mock.patch.object(ip_lib, 'device_exists') + def test_create_rtr_2_fip_link_already_exists(self, + device_exists, + IPDevice, + IPWrapper): + ri = mock.Mock() + ri.router_id = _uuid() + ri.rtr_fip_subnet = None + device_exists.return_value = True + + self.fip_ns.local_subnets = allocator = mock.Mock() + pair = lla.LinkLocalAddressPair('169.254.31.28/31') + allocator.allocate.return_value = pair + self.fip_ns.create_rtr_2_fip_link(ri) + + ip_wrapper = IPWrapper() + self.assertFalse(ip_wrapper.add_veth.called) + + @mock.patch.object(ip_lib, 'IPDevice') + def _test_scan_fip_ports(self, ri, ip_list, IPDevice): + IPDevice.return_value = device = mock.Mock() + device.addr.list.return_value = ip_list + self.fip_ns.get_rtr_ext_device_name = mock.Mock( + return_value=mock.sentinel.rtr_ext_device_name) + self.fip_ns.scan_fip_ports(ri) + + @mock.patch.object(ip_lib, 'device_exists') + def test_scan_fip_ports_restart_fips(self, device_exists): + device_exists.return_value = True + ri = mock.Mock() + ri.dist_fip_count = None + ip_list = [{'cidr': '111.2.3.4/32'}, {'cidr': '111.2.3.5/32'}] + self._test_scan_fip_ports(ri, ip_list) + self.assertEqual(2, ri.dist_fip_count) + + @mock.patch.object(ip_lib, 'device_exists') + def test_scan_fip_ports_restart_none(self, device_exists): + device_exists.return_value = True + ri = mock.Mock() + ri.dist_fip_count = None + self._test_scan_fip_ports(ri, []) + self.assertEqual(0, ri.dist_fip_count) + + def test_scan_fip_ports_restart_zero(self): + ri = mock.Mock() + ri.dist_fip_count = 0 + self._test_scan_fip_ports(ri, None) + self.assertEqual(0, ri.dist_fip_count) diff --git a/neutron/tests/unit/test_l3_agent.py b/neutron/tests/unit/test_l3_agent.py index a1cb633ae..6f7876363 100644 --- a/neutron/tests/unit/test_l3_agent.py +++ b/neutron/tests/unit/test_l3_agent.py @@ -26,6 +26,7 @@ from neutron.agent.common import config as agent_config from neutron.agent.l3 import agent as l3_agent from neutron.agent.l3 import config as l3_config from neutron.agent.l3 import dvr +from neutron.agent.l3 import dvr_fip_ns from neutron.agent.l3 import dvr_router from neutron.agent.l3 import ha from neutron.agent.l3 import link_local_allocator as lla @@ -415,19 +416,21 @@ class TestBasicRouterOperations(base.BaseTestCase): ri = l3router.RouterInfo(router['id'], router, ns_name=agent.get_ns_name(router['id']), **self.ri_kwargs) + ex_net_id = _uuid() # Special setup for dvr routers if router.get('distributed'): 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.fip_ns = agent.get_fip_ns(ex_net_id) ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30', 'subnet_id': _uuid()}], 'subnet': {'gateway_ip': '20.0.0.1'}, 'extra_subnets': [{'cidr': '172.16.0.0/24'}], 'id': _uuid(), - 'network_id': _uuid(), + 'network_id': ex_net_id, 'mac_address': 'ca:fe:de:ad:be:ef', 'ip_cidr': '20.0.0.30/24'} interface_name = agent.get_external_device_name(ex_gw_port['id']) @@ -825,39 +828,6 @@ class TestBasicRouterOperations(base.BaseTestCase): 4, '1.5.25.15', '00:44:33:22:11:55') agent.router_deleted(None, router['id']) - @mock.patch('neutron.agent.linux.ip_lib.IPDevice') - def _test_scan_fip_ports(self, ri, ip_list, IPDevice): - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - self.device_exists.return_value = True - IPDevice.return_value = device = mock.Mock() - device.addr.list.return_value = ip_list - agent.scan_fip_ports(ri) - - def test_scan_fip_ports_restart_fips(self): - router = prepare_router_data() - ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs) - ri.router['distributed'] = True - ip_list = [{'cidr': '111.2.3.4/32'}, {'cidr': '111.2.3.5/32'}] - self._test_scan_fip_ports(ri, ip_list) - self.assertEqual(ri.dist_fip_count, 2) - - def test_scan_fip_ports_restart_none(self): - router = prepare_router_data() - ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs) - ri.router['distributed'] = True - ip_list = [] - self._test_scan_fip_ports(ri, ip_list) - self.assertEqual(ri.dist_fip_count, 0) - - def test_scan_fip_ports_restart_zero(self): - router = prepare_router_data() - ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs) - ri.router['distributed'] = True - ri.dist_fip_count = 0 - ip_list = None - self._test_scan_fip_ports(ri, ip_list) - self.assertEqual(ri.dist_fip_count, 0) - def test_process_cent_router(self): router = prepare_router_data() ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs) @@ -961,10 +931,11 @@ class TestBasicRouterOperations(base.BaseTestCase): IPDevice.return_value = device = mock.Mock() device.addr.list.return_value = [] ri.iptables_manager.ipv4['nat'] = mock.MagicMock() - ex_gw_port = {'id': _uuid()} + ex_gw_port = {'id': _uuid(), 'network_id': mock.sentinel.ext_net_id} with mock.patch.object(lla.LinkLocalAllocator, '_write'): if ri.router['distributed']: + ri.fip_ns = agent.get_fip_ns(ex_gw_port['network_id']) agent.create_dvr_fip_interfaces(ri, ex_gw_port) fip_statuses = agent.process_router_floating_ip_addresses( ri, ex_gw_port) @@ -1015,13 +986,13 @@ class TestBasicRouterOperations(base.BaseTestCase): 'host': HOSTNAME, 'floating_ip_address': '15.1.2.3', 'fixed_ip_address': '192.168.0.1', - 'floating_network_id': _uuid(), + 'floating_network_id': mock.sentinel.ext_net_id, 'port_id': _uuid()}, {'id': _uuid(), 'host': 'some-other-host', 'floating_ip_address': '15.1.2.4', 'fixed_ip_address': '192.168.0.10', - 'floating_network_id': _uuid(), + 'floating_network_id': mock.sentinel.ext_net_id, 'port_id': _uuid()}]} router = prepare_router_data(enable_snat=True) @@ -1032,7 +1003,8 @@ class TestBasicRouterOperations(base.BaseTestCase): ri.dist_fip_count = 0 agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) agent.host = HOSTNAME - agent.agent_gateway_port = ( + fip_ns = agent.get_fip_ns(mock.sentinel.ext_net_id) + fip_ns.agent_gateway_port = ( {'fixed_ips': [{'ip_address': '20.0.0.30', 'subnet_id': _uuid()}], 'subnet': {'gateway_ip': '20.0.0.1'}, @@ -1140,7 +1112,7 @@ class TestBasicRouterOperations(base.BaseTestCase): agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) fip_statuses = agent.process_router_floating_ip_addresses( - ri, {'id': _uuid()}) + ri, {'id': _uuid(), 'network_id': mock.sentinel.ext_net_id}) self.assertIsNone(fip_statuses.get(fip_id)) @@ -1617,23 +1589,6 @@ class TestBasicRouterOperations(base.BaseTestCase): agent.router_added_to_agent(None, [FAKE_ID]) self.assertEqual(1, agent._queue.add.call_count) - def test_destroy_fip_namespace(self): - namespaces = ['qrouter-foo', 'qrouter-bar'] - - self.mock_ip.get_namespaces.return_value = namespaces - self.mock_ip.get_devices.return_value = [FakeDev('fpr-aaaa'), - FakeDev('fg-aaaa')] - - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - - agent._destroy_fip_namespace(namespaces[0]) - self.mock_driver.unplug.assert_called_once_with('fg-aaaa', - bridge='br-ex', - prefix='fg-', - namespace='qrouter' - '-foo') - self.mock_ip.del_veth.assert_called_once_with('fpr-aaaa') - def test_destroy_namespace(self): namespace = 'qrouter-bar' @@ -1910,81 +1865,16 @@ class TestBasicRouterOperations(base.BaseTestCase): 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', - mock.ANY, mock.ANY) - 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 = 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 = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs) - - 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'])) - - with mock.patch.object(lla.LinkLocalAllocator, '_write'): - self.device_exists.return_value = False - 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( - '169.254.31.29', table=16) - - # TODO(mrsmith): test _create_agent_gateway_port - - def test_create_rtr_2_fip_link_already_exists(self): - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - router = prepare_router_data() - - ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs) - self.device_exists.return_value = True - with mock.patch.object(lla.LinkLocalAllocator, '_write'): - agent.create_rtr_2_fip_link(ri, {}) - self.assertFalse(self.mock_ip.add_veth.called) - def test_floating_ip_added_dist(self): agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) router = prepare_router_data() ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs) + ext_net_id = _uuid() 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(), + 'network_id': ext_net_id, 'mac_address': 'ca:fe:de:ad:be:ef', 'ip_cidr': '20.0.0.30/24'} @@ -1992,9 +1882,10 @@ class TestBasicRouterOperations(base.BaseTestCase): 'host': HOSTNAME, 'floating_ip_address': '15.1.2.3', 'fixed_ip_address': '192.168.0.1', - 'floating_network_id': _uuid(), + 'floating_network_id': ext_net_id, 'port_id': _uuid()} - agent.agent_gateway_port = agent_gw_port + ri.fip_ns = agent.get_fip_ns(ext_net_id) + ri.fip_ns.agent_gateway_port = agent_gw_port ri.rtr_fip_subnet = lla.LinkLocalAddressPair('169.254.30.42/31') ri.dist_fip_count = 0 ip_cidr = common_utils.ip_to_cidr(fip['floating_ip_address']) @@ -2002,9 +1893,8 @@ class TestBasicRouterOperations(base.BaseTestCase): self.mock_rule.add.assert_called_with('192.168.0.1', 16, FIP_PRI) # TODO(mrsmith): add more asserts - @mock.patch.object(l3_agent.L3NATAgent, '_fip_ns_unsubscribe') @mock.patch.object(lla.LinkLocalAllocator, '_write') - def test_floating_ip_removed_dist(self, write, unsubscribe): + def test_floating_ip_removed_dist(self, write): agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) router = prepare_router_data() agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30', @@ -2018,7 +1908,8 @@ class TestBasicRouterOperations(base.BaseTestCase): ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs) ri.dist_fip_count = 2 - agent.fip_ns_subscribers.add(ri.router_id) + ri.fip_ns = agent.get_fip_ns(agent._fetch_external_net_id()) + ri.fip_ns.unsubscribe = mock.Mock() ri.floating_ips_dict['11.22.33.44'] = FIP_PRI ri.fip_2_rtr = '11.22.33.42' ri.rtr_2_fip = '11.22.33.40' @@ -2030,21 +1921,20 @@ class TestBasicRouterOperations(base.BaseTestCase): self.mock_rule.delete.assert_called_with(floating_ip, 16, FIP_PRI) self.mock_ip_dev.route.delete_route.assert_called_with(fip_cidr, str(s.ip)) - self.assertFalse(unsubscribe.called, '_fip_ns_unsubscribe called!') + self.assertFalse(ri.fip_ns.unsubscribe.called) with mock.patch.object(agent, '_destroy_fip_namespace') as f: ri.dist_fip_count = 1 - fip_ns_name = agent.get_fip_ns_name( - str(agent._fetch_external_net_id())) - ri.rtr_fip_subnet = agent.local_subnets.allocate(ri.router_id) + fip_ns_name = ri.fip_ns.get_name() + ri.rtr_fip_subnet = ri.fip_ns.local_subnets.allocate(ri.router_id) _, fip_to_rtr = ri.rtr_fip_subnet.get_pair() agent.floating_ip_removed_dist(ri, fip_cidr) self.mock_ip.del_veth.assert_called_once_with( - agent.get_fip_int_device_name(router['id'])) + ri.fip_ns.get_int_device_name(router['id'])) self.mock_ip_dev.route.delete_gateway.assert_called_once_with( str(fip_to_rtr.ip), table=16) f.assert_called_once_with(fip_ns_name) - unsubscribe.assert_called_once_with(ri.router_id) + ri.fip_ns.unsubscribe.assert_called_once_with(ri.router_id) def test_get_service_plugin_list(self): service_plugins = [p_const.L3_ROUTER_NAT] @@ -2080,74 +1970,41 @@ class TestBasicRouterOperations(base.BaseTestCase): self.assertRaises(messaging.MessagingTimeout, l3_agent.L3NATAgent, HOSTNAME, self.conf) - def test__fip_ns_subscribe_is_first_true(self): - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - router_id = _uuid() - is_first = agent._fip_ns_subscribe(router_id) - self.assertTrue(is_first) - self.assertEqual(len(agent.fip_ns_subscribers), 1) - - def test__fip_ns_subscribe_is_first_false(self): - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - router_id = _uuid() - router2_id = _uuid() - agent._fip_ns_subscribe(router_id) - is_first = agent._fip_ns_subscribe(router2_id) - self.assertFalse(is_first) - self.assertEqual(len(agent.fip_ns_subscribers), 2) - - def test__fip_ns_unsubscribe_is_last_true(self): - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - router_id = _uuid() - agent.fip_ns_subscribers.add(router_id) - is_last = agent._fip_ns_unsubscribe(router_id) - self.assertTrue(is_last) - self.assertEqual(len(agent.fip_ns_subscribers), 0) - - def test__fip_ns_unsubscribe_is_last_false(self): - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - router_id = _uuid() - router2_id = _uuid() - agent.fip_ns_subscribers.add(router_id) - agent.fip_ns_subscribers.add(router2_id) - is_last = agent._fip_ns_unsubscribe(router_id) - self.assertFalse(is_last) - self.assertEqual(len(agent.fip_ns_subscribers), 1) - def test_external_gateway_removed_ext_gw_port_and_fip(self): self.conf.set_override('state_path', '/tmp') self.conf.set_override('router_delete_namespaces', True) agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) agent.conf.agent_mode = 'dvr' - 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'} - external_net_id = _uuid() - agent._fetch_external_net_id = mock.Mock(return_value=external_net_id) - router = prepare_router_data(num_internal_ports=2) router['distributed'] = True router['gw_port_host'] = HOSTNAME + external_net_id = router['gw_port']['network_id'] ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs) + ri.fip_ns = agent.get_fip_ns(external_net_id) + ri.fip_ns.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': external_net_id, + 'mac_address': 'ca:fe:de:ad:be:ef', + 'ip_cidr': '20.0.0.30/24'} + agent._fetch_external_net_id = mock.Mock(return_value=external_net_id) + vm_floating_ip = '19.4.4.2' ri.floating_ips_dict[vm_floating_ip] = FIP_PRI ri.dist_fip_count = 1 ri.ex_gw_port = ri.router['gw_port'] del ri.router['gw_port'] - ri.rtr_fip_subnet = agent.local_subnets.allocate(ri.router_id) + ri.rtr_fip_subnet = ri.fip_ns.local_subnets.allocate(ri.router_id) _, fip_to_rtr = ri.rtr_fip_subnet.get_pair() nat = ri.iptables_manager.ipv4['nat'] nat.clear_rules_by_tag = mock.Mock() nat.add_rule = mock.Mock() self.mock_ip.get_devices.return_value = [ - FakeDev(agent.get_fip_ext_device_name(_uuid()))] + FakeDev(ri.fip_ns.get_ext_device_name(_uuid()))] self.mock_ip_dev.addr.list.return_value = [ {'cidr': vm_floating_ip + '/32'}, {'cidr': '19.4.4.1/24'}] @@ -2158,16 +2015,15 @@ class TestBasicRouterOperations(base.BaseTestCase): agent.get_external_device_name(ri.ex_gw_port['id'])) self.mock_ip.del_veth.assert_called_once_with( - agent.get_fip_int_device_name(ri.router['id'])) + ri.fip_ns.get_int_device_name(ri.router['id'])) self.mock_ip_dev.route.delete_gateway.assert_called_once_with( - str(fip_to_rtr.ip), table=dvr.FIP_RT_TBL) + str(fip_to_rtr.ip), table=dvr_fip_ns.FIP_RT_TBL) self.assertEqual(ri.dist_fip_count, 0) - self.assertEqual(len(agent.fip_ns_subscribers), 0) + self.assertFalse(ri.fip_ns.has_subscribers()) self.assertEqual(self.mock_driver.unplug.call_count, 1) - self.assertIsNone(agent.agent_gateway_port) - self.mock_ip.netns.delete.assert_called_once_with( - agent.get_fip_ns_name(external_net_id)) + self.assertIsNone(ri.fip_ns.agent_gateway_port) + self.mock_ip.netns.delete.assert_called_once_with(ri.fip_ns.get_name()) self.assertFalse(nat.add_rule.called) nat.clear_rules_by_tag.assert_called_once_with('floating_ip')