From cb932d2243fa1f9fa32bc64b64113990fb2183d8 Mon Sep 17 00:00:00 2001 From: Carl Baldwin Date: Thu, 18 Dec 2014 17:48:19 +0000 Subject: [PATCH] Move Floating IP processing to Router classes Change-Id: Id0822f1c3d586c813a09e4b442a21d3cb52943d0 Partially-Implements: bp/restructure-l3-agent --- neutron/agent/l3/agent.py | 172 +--------- neutron/agent/l3/dvr_router.py | 25 +- neutron/agent/l3/ha_router.py | 14 + neutron/agent/l3/legacy_router.py | 14 +- neutron/agent/l3/router_info.py | 123 ++++++++ .../tests/functional/agent/test_l3_agent.py | 18 +- .../unit/{ => agent/l3}/test_dvr_router.py | 53 +++- neutron/tests/unit/agent/l3/test_ha_router.py | 41 +++ neutron/tests/unit/agent/l3/test_l3_router.py | 222 +++++++++++++ .../tests/unit/agent/l3/test_legacy_router.py | 77 +++++ neutron/tests/unit/test_l3_agent.py | 297 ++++-------------- 11 files changed, 646 insertions(+), 410 deletions(-) rename neutron/tests/unit/{ => agent/l3}/test_dvr_router.py (68%) create mode 100644 neutron/tests/unit/agent/l3/test_ha_router.py create mode 100644 neutron/tests/unit/agent/l3/test_l3_router.py create mode 100644 neutron/tests/unit/agent/l3/test_legacy_router.py diff --git a/neutron/agent/l3/agent.py b/neutron/agent/l3/agent.py index 88fd221b2..659ea8fa9 100644 --- a/neutron/agent/l3/agent.py +++ b/neutron/agent/l3/agent.py @@ -362,6 +362,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, } if router.get('distributed'): + kwargs['host'] = self.host return dvr_router.DvrRouter(*args, **kwargs) if router.get('ha'): @@ -471,7 +472,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, prefix=INTERNAL_DEV_PREFIX) def _process_external_gateway(self, ri): - ex_gw_port = self._get_ex_gw_port(ri) + ex_gw_port = ri.get_ex_gw_port() ex_gw_port_id = (ex_gw_port and ex_gw_port['id'] or ri.ex_gw_port and ri.ex_gw_port['id']) @@ -515,29 +516,6 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, ri.perform_snat_action(self._handle_router_snat_rules, interface_name) - def _put_fips_in_error_state(self, ri): - fip_statuses = {} - for fip in ri.router.get(l3_constants.FLOATINGIP_KEY, []): - fip_statuses[fip['id']] = l3_constants.FLOATINGIP_STATUS_ERROR - return fip_statuses - - def _process_snat_dnat_for_fip(self, ri): - try: - self.process_router_floating_ip_nat_rules(ri) - except Exception: - # TODO(salv-orlando): Less broad catching - raise n_exc.FloatingIpSetupException('L3 agent failure to setup ' - 'NAT for floating IPs') - - def _configure_fip_addresses(self, ri, interface_name): - try: - return self.process_router_floating_ip_addresses( - ri, interface_name) - except Exception: - # TODO(salv-orlando): Less broad catching - raise n_exc.FloatingIpSetupException('L3 agent failure to setup ' - 'floating IPs') - def _update_fip_statuses(self, ri, existing_floating_ips, fip_statuses): # Identify floating IPs which were disabled ri.floating_ips = set(fip_statuses.keys()) @@ -558,7 +536,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, try: with ri.iptables_manager.defer_apply(): self._process_external_gateway(ri) - ex_gw_port = self._get_ex_gw_port(ri) + ex_gw_port = ri.get_ex_gw_port() # TODO(Carl) Return after setting existing_floating_ips and # still call _update_fip_statuses? if not ex_gw_port: @@ -568,19 +546,19 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, existing_floating_ips = ri.floating_ips if ri.router['distributed']: self.create_dvr_fip_interfaces(ri, ex_gw_port) - self._process_snat_dnat_for_fip(ri) + ri.process_snat_dnat_for_fip() # Once NAT rules for floating IPs are safely in place # configure their addresses on the external gateway port interface_name = self._get_external_device_interface_name( ri, ex_gw_port) - fip_statuses = self._configure_fip_addresses(ri, interface_name) + fip_statuses = ri.configure_fip_addresses(interface_name) except (n_exc.FloatingIpSetupException, n_exc.IpTablesApplyException) as e: # All floating IPs must be put in error state LOG.exception(e) - fip_statuses = self._put_fips_in_error_state(ri) + fip_statuses = ri.put_fips_in_error_state() self._update_fip_statuses(ri, existing_floating_ips, fip_statuses) @@ -589,7 +567,7 @@ 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 - ex_gw_port = self._get_ex_gw_port(ri) + ex_gw_port = ri.get_ex_gw_port() 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) @@ -641,28 +619,8 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, break iptables_manager.apply() - def process_router_floating_ip_nat_rules(self, ri): - """Configure NAT rules for the router's floating IPs. - - Configures iptables rules for the floating ips of the given router - """ - # 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 floating_ips: - # Rebuild iptables rules for the floating ip. - fixed = fip['fixed_ip_address'] - fip_ip = fip['floating_ip_address'] - for chain, rule in self.floating_forward_rules(fip_ip, fixed): - ri.iptables_manager.ipv4['nat'].add_rule(chain, rule, - tag='floating_ip') - - ri.iptables_manager.apply() - def create_dvr_fip_interfaces(self, ri, ex_gw_port): - floating_ips = self.get_floating_ips(ri) + floating_ips = ri.get_floating_ips() fip_agent_port = self.get_floating_agent_gw_interface( ri, ex_gw_port['network_id']) LOG.debug("FloatingIP agent gateway port received from the plugin: " @@ -692,91 +650,6 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, else: return self.get_external_device_name(ex_gw_port['id']) - def _add_floating_ip(self, ri, fip, interface_name, device): - fip_ip = fip['floating_ip_address'] - ip_cidr = common_utils.ip_to_cidr(fip_ip) - - if ri.is_ha: - ri._add_vip(ip_cidr, interface_name) - else: - net = netaddr.IPNetwork(ip_cidr) - try: - device.addr.add(net.version, ip_cidr, str(net.broadcast)) - except RuntimeError: - # any exception occurred here should cause the floating IP - # to be set in error state - LOG.warn(_LW("Unable to configure IP address for " - "floating IP: %s"), fip['id']) - return l3_constants.FLOATINGIP_STATUS_ERROR - if ri.router['distributed']: - # Special Handling for DVR - update FIP namespace - # and ri.namespace to handle DVR based FIP - ri.floating_ip_added_dist(fip, ip_cidr) - else: - # As GARP is processed in a distinct thread the call below - # won't raise an exception to be handled. - ip_lib.send_gratuitous_arp(ri.ns_name, - interface_name, - fip_ip, - self.conf.send_arp_for_ha) - return l3_constants.FLOATINGIP_STATUS_ACTIVE - - def _remove_floating_ip(self, ri, device, ip_cidr): - if ri.is_ha: - ri._remove_vip(ip_cidr) - else: - net = netaddr.IPNetwork(ip_cidr) - device.addr.delete(net.version, ip_cidr) - self.driver.delete_conntrack_state(namespace=ri.ns_name, - ip=ip_cidr) - if ri.router['distributed']: - ri.floating_ip_removed_dist(ip_cidr) - - def _get_router_cidrs(self, ri, device): - if ri.is_ha: - return set(ri._ha_get_existing_cidrs(device.name)) - else: - return set([addr['cidr'] for addr in device.addr.list()]) - - def process_router_floating_ip_addresses(self, ri, interface_name): - """Configure IP addresses on router's external gateway interface. - - Ensures addresses for existing floating IPs and cleans up - those that should not longer be configured. - """ - - fip_statuses = {} - if interface_name is None: - LOG.debug('No Interface for floating IPs router: %s', - ri.router['id']) - return fip_statuses - - device = ip_lib.IPDevice(interface_name, namespace=ri.ns_name) - existing_cidrs = self._get_router_cidrs(ri, device) - new_cidrs = set() - - floating_ips = self.get_floating_ips(ri) - # Loop once to ensure that floating ips are configured. - for fip in floating_ips: - fip_ip = fip['floating_ip_address'] - ip_cidr = common_utils.ip_to_cidr(fip_ip) - new_cidrs.add(ip_cidr) - fip_statuses[fip['id']] = l3_constants.FLOATINGIP_STATUS_ACTIVE - if ip_cidr not in existing_cidrs: - fip_statuses[fip['id']] = self._add_floating_ip( - ri, fip, interface_name, device) - - fips_to_remove = ( - ip_cidr for ip_cidr in existing_cidrs - new_cidrs - if common_utils.is_cidr_host(ip_cidr)) - for ip_cidr in fips_to_remove: - self._remove_floating_ip(ri, device, ip_cidr) - - return fip_statuses - - def _get_ex_gw_port(self, ri): - return ri.router.get('gw_port') - def get_internal_device_name(self, port_id): return (INTERNAL_DEV_PREFIX + port_id)[:self.driver.DEV_NAME_LEN] @@ -789,13 +662,6 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, def get_router_id(self, ns_name): return ns_name[len(NS_PREFIX):] - 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 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, []) @@ -829,7 +695,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, # 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 = self.get_floating_ips(ri) + floating_ips = ri.get_floating_ips() preserve_ips = [common_utils.ip_to_cidr(ip['floating_ip_address']) for ip in floating_ips] @@ -852,7 +718,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, return else: ns_name = ri.ns_name - floating_ips = self.get_floating_ips(ri) + floating_ips = ri.get_floating_ips() preserve_ips = [common_utils.ip_to_cidr(ip['floating_ip_address']) for ip in floating_ips] @@ -886,12 +752,12 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, def external_gateway_removed(self, ri, ex_gw_port, interface_name): if ri.router['distributed']: - self.process_router_floating_ip_nat_rules(ri) + # TODO(Carl) Should this be calling process_snat_dnat_for_fip? + ri.process_floating_ip_nat_rules() if ri.fip_ns: to_fip_interface_name = ( self._get_external_device_interface_name(ri, ex_gw_port)) - self.process_router_floating_ip_addresses( - ri, to_fip_interface_name) + ri.process_floating_ip_addresses(to_fip_interface_name) for p in ri.internal_ports: internal_interface = self.get_internal_device_name(p['id']) self._snat_redirect_remove(ri, p, internal_interface) @@ -959,7 +825,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, ri._ha_disable_addressing_on_interface(interface_name) ri._add_vip(internal_cidr, interface_name) - ex_gw_port = self._get_ex_gw_port(ri) + 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) @@ -1007,14 +873,6 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, self.driver.unplug(interface_name, namespace=ri.ns_name, prefix=INTERNAL_DEV_PREFIX) - def floating_forward_rules(self, floating_ip, fixed_ip): - return [('PREROUTING', '-d %s -j DNAT --to %s' % - (floating_ip, fixed_ip)), - ('OUTPUT', '-d %s -j DNAT --to %s' % - (floating_ip, fixed_ip)), - ('float-snat', '-s %s -j SNAT --to %s' % - (fixed_ip, floating_ip))] - def router_deleted(self, context, router_id): """Deal with router deletion RPC message.""" LOG.debug('Got router deleted notification for %s', router_id) @@ -1239,7 +1097,7 @@ class L3NATAgentWithStateReport(L3NATAgent): router_infos = self.router_info.values() num_routers = len(router_infos) for ri in router_infos: - ex_gw_port = self._get_ex_gw_port(ri) + ex_gw_port = ri.get_ex_gw_port() if ex_gw_port: num_ex_gw_ports += 1 num_interfaces += len(ri.router.get(l3_constants.INTERFACE_KEY, diff --git a/neutron/agent/l3/dvr_router.py b/neutron/agent/l3/dvr_router.py index f2adc4759..b85326495 100644 --- a/neutron/agent/l3/dvr_router.py +++ b/neutron/agent/l3/dvr_router.py @@ -15,18 +15,27 @@ from neutron.agent.l3 import dvr_fip_ns from neutron.agent.l3 import router_info as router from neutron.agent.linux import ip_lib +from neutron.common import constants as l3_constants +from neutron.common import utils as common_utils class DvrRouter(router.RouterInfo): - def __init__(self, *args, **kwargs): + def __init__(self, host, *args, **kwargs): super(DvrRouter, self).__init__(*args, **kwargs) + self.host = host + self.floating_ips_dict = {} self.snat_iptables_manager = None # Linklocal subnet for router and floating IP namespace link self.rtr_fip_subnet = None self.dist_fip_count = None + def get_floating_ips(self): + """Filter Floating IPs to be hosted on this agent.""" + floating_ips = super(DvrRouter, self).get_floating_ips() + return [i for i in floating_ips if i['host'] == self.host] + def _handle_fip_nat_rules(self, interface_name, action): """Configures NAT rules for Floating IPs for DVR. @@ -114,3 +123,17 @@ class DvrRouter(router.RouterInfo): # semaphore to sync creation/deletion of this namespace. self.fip_ns.destroy() self.fip_ns = None + + def add_floating_ip(self, fip, interface_name, device): + if not self._add_fip_addr_to_device(fip, device): + 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 + + def remove_floating_ip(self, device, ip_cidr): + super(DvrRouter, self).remove_floating_ip(device, ip_cidr) + self.floating_ip_removed_dist(ip_cidr) diff --git a/neutron/agent/l3/ha_router.py b/neutron/agent/l3/ha_router.py index 10314d757..f4cafb933 100644 --- a/neutron/agent/l3/ha_router.py +++ b/neutron/agent/l3/ha_router.py @@ -20,6 +20,7 @@ from neutron.agent.l3 import router_info as router from neutron.agent.linux import ip_lib from neutron.agent.linux import keepalived from neutron.agent.metadata import driver as metadata_driver +from neutron.common import utils as common_utils from neutron.openstack.common import log as logging LOG = logging.getLogger(__name__) @@ -164,6 +165,9 @@ class HaRouter(router.RouterInfo): instance = self._get_keepalived_instance() return instance.get_existing_vip_ip_addresses(interface_name) + def get_router_cidrs(self, device): + return set(self._ha_get_existing_cidrs(device.name)) + def _ha_external_gateway_removed(self, interface_name): self._clear_vips(interface_name) @@ -238,3 +242,13 @@ class HaRouter(router.RouterInfo): old_gateway_cidr = self.ex_gw_port['ip_cidr'] self._remove_vip(old_gateway_cidr) self._ha_external_gateway_added(ex_gw_port, interface_name) + + def add_floating_ip(self, fip, interface_name, device): + fip_ip = fip['floating_ip_address'] + ip_cidr = common_utils.ip_to_cidr(fip_ip) + self._add_vip(ip_cidr, interface_name) + # TODO(Carl) Should this return status? + # return l3_constants.FLOATINGIP_STATUS_ACTIVE + + def remove_floating_ip(self, device, ip_cidr): + self._remove_vip(ip_cidr) diff --git a/neutron/agent/l3/legacy_router.py b/neutron/agent/l3/legacy_router.py index 31dd0f326..9c7c5bdc7 100644 --- a/neutron/agent/l3/legacy_router.py +++ b/neutron/agent/l3/legacy_router.py @@ -13,7 +13,19 @@ # under the License. from neutron.agent.l3 import router_info as router +from neutron.agent.linux import ip_lib +from neutron.common import constants as l3_constants class LegacyRouter(router.RouterInfo): - pass + def add_floating_ip(self, fip, interface_name, device): + if not self._add_fip_addr_to_device(fip, device): + return l3_constants.FLOATINGIP_STATUS_ERROR + + # As GARP is processed in a distinct thread the call below + # won't raise an exception to be handled. + ip_lib.send_gratuitous_arp(self.ns_name, + interface_name, + fip['floating_ip_address'], + self.agent_conf.send_arp_for_ha) + return l3_constants.FLOATINGIP_STATUS_ACTIVE diff --git a/neutron/agent/l3/router_info.py b/neutron/agent/l3/router_info.py index 3c7a5e7df..464bf6185 100644 --- a/neutron/agent/l3/router_info.py +++ b/neutron/agent/l3/router_info.py @@ -12,9 +12,14 @@ # License for the specific language governing permissions and limitations # under the License. +import netaddr + 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 exceptions as n_exc from neutron.common import utils as common_utils +from neutron.i18n import _LW from neutron.openstack.common import log as logging LOG = logging.getLogger(__name__) @@ -103,3 +108,121 @@ class RouterInfo(object): LOG.debug("Removed route entry is '%s'", route) self._update_routing_table('delete', route) self.routes = new_routes + + def get_ex_gw_port(self): + return self.router.get('gw_port') + + def get_floating_ips(self): + """Filter Floating IPs to be hosted on this agent.""" + return self.router.get(l3_constants.FLOATINGIP_KEY, []) + + def floating_forward_rules(self, floating_ip, fixed_ip): + return [('PREROUTING', '-d %s -j DNAT --to %s' % + (floating_ip, fixed_ip)), + ('OUTPUT', '-d %s -j DNAT --to %s' % + (floating_ip, fixed_ip)), + ('float-snat', '-s %s -j SNAT --to %s' % + (fixed_ip, floating_ip))] + + def process_floating_ip_nat_rules(self): + """Configure NAT rules for the router's floating IPs. + + Configures iptables rules for the floating ips of the given router + """ + # Clear out all iptables rules for floating ips + self.iptables_manager.ipv4['nat'].clear_rules_by_tag('floating_ip') + + floating_ips = self.get_floating_ips() + # Loop once to ensure that floating ips are configured. + for fip in floating_ips: + # Rebuild iptables rules for the floating ip. + fixed = fip['fixed_ip_address'] + fip_ip = fip['floating_ip_address'] + for chain, rule in self.floating_forward_rules(fip_ip, fixed): + self.iptables_manager.ipv4['nat'].add_rule(chain, rule, + tag='floating_ip') + + self.iptables_manager.apply() + + def process_snat_dnat_for_fip(self): + try: + self.process_floating_ip_nat_rules() + except Exception: + # TODO(salv-orlando): Less broad catching + raise n_exc.FloatingIpSetupException( + 'L3 agent failure to setup NAT for floating IPs') + + def _add_fip_addr_to_device(self, fip, device): + """Configures the floating ip address on the device. + """ + try: + ip_cidr = common_utils.ip_to_cidr(fip['floating_ip_address']) + net = netaddr.IPNetwork(ip_cidr) + device.addr.add(net.version, ip_cidr, str(net.broadcast)) + return True + except RuntimeError: + # any exception occurred here should cause the floating IP + # to be set in error state + LOG.warn(_LW("Unable to configure IP address for " + "floating IP: %s"), fip['id']) + + def add_floating_ip(self, fip, interface_name, device): + raise NotImplementedError() + + def remove_floating_ip(self, device, ip_cidr): + net = netaddr.IPNetwork(ip_cidr) + device.addr.delete(net.version, ip_cidr) + self.driver.delete_conntrack_state(namespace=self.ns_name, ip=ip_cidr) + + def get_router_cidrs(self, device): + return set([addr['cidr'] for addr in device.addr.list()]) + + def process_floating_ip_addresses(self, interface_name): + """Configure IP addresses on router's external gateway interface. + + Ensures addresses for existing floating IPs and cleans up + those that should not longer be configured. + """ + + fip_statuses = {} + if interface_name is None: + LOG.debug('No Interface for floating IPs router: %s', + self.router['id']) + return fip_statuses + + device = ip_lib.IPDevice(interface_name, namespace=self.ns_name) + existing_cidrs = self.get_router_cidrs(device) + new_cidrs = set() + + floating_ips = self.get_floating_ips() + # Loop once to ensure that floating ips are configured. + for fip in floating_ips: + fip_ip = fip['floating_ip_address'] + ip_cidr = common_utils.ip_to_cidr(fip_ip) + new_cidrs.add(ip_cidr) + fip_statuses[fip['id']] = l3_constants.FLOATINGIP_STATUS_ACTIVE + if ip_cidr not in existing_cidrs: + fip_statuses[fip['id']] = self.add_floating_ip( + fip, interface_name, device) + + fips_to_remove = ( + ip_cidr for ip_cidr in existing_cidrs - new_cidrs + if common_utils.is_cidr_host(ip_cidr)) + for ip_cidr in fips_to_remove: + self.remove_floating_ip(device, ip_cidr) + + return fip_statuses + + def configure_fip_addresses(self, interface_name): + try: + return self.process_floating_ip_addresses(interface_name) + except Exception: + # TODO(salv-orlando): Less broad catching + raise n_exc.FloatingIpSetupException('L3 agent failure to setup ' + 'floating IPs') + + def put_fips_in_error_state(self): + fip_statuses = {} + for fip in self.router.get(l3_constants.FLOATINGIP_KEY, []): + fip_statuses[fip['id']] = l3_constants.FLOATINGIP_STATUS_ERROR + return fip_statuses diff --git a/neutron/tests/functional/agent/test_l3_agent.py b/neutron/tests/functional/agent/test_l3_agent.py index 5e46b74c1..41235eb45 100755 --- a/neutron/tests/functional/agent/test_l3_agent.py +++ b/neutron/tests/functional/agent/test_l3_agent.py @@ -148,7 +148,7 @@ class L3AgentTestFramework(base.BaseOVSLinuxTestCase): router_id = router.router_id ha_device_name = router.get_ha_device_name(router.ha_port['id']) ha_device_cidr = router.ha_port['ip_cidr'] - external_port = self.agent._get_ex_gw_port(router) + external_port = router.get_ex_gw_port() ex_port_ipv6 = router._get_ipv6_lladdr( external_port['mac_address']) external_device_name = self.agent.get_external_device_name( @@ -161,7 +161,7 @@ class L3AgentTestFramework(base.BaseOVSLinuxTestCase): internal_port['id']) internal_device_cidr = internal_port['ip_cidr'] floating_ip_cidr = common_utils.ip_to_cidr( - self.agent.get_floating_ips(router)[0]['floating_ip_address']) + router.get_floating_ips()[0]['floating_ip_address']) default_gateway_ip = external_port['subnet'].get('gateway_ip') return """vrrp_instance VR_1 { @@ -370,7 +370,7 @@ class L3AgentTestCase(L3AgentTestFramework): self.assertIn(new_fip, new_config) self.assertNotIn(old_gw, new_config) self.assertIn(new_gw, new_config) - external_port = self.agent._get_ex_gw_port(router) + external_port = router.get_ex_gw_port() external_device_name = self.agent.get_external_device_name( external_port['id']) self.assertNotIn('%s/24 dev %s' % @@ -385,7 +385,7 @@ class L3AgentTestCase(L3AgentTestFramework): router = self.manage_router(self.agent, router_info) if enable_ha: - port = self.agent._get_ex_gw_port(router) + port = router.get_ex_gw_port() interface_name = self.agent.get_external_device_name(port['id']) self._assert_no_ip_addresses_on_interface(router, interface_name) helpers.wait_until_true(lambda: router.ha_state == 'master') @@ -425,13 +425,13 @@ class L3AgentTestCase(L3AgentTestFramework): self.assertFalse(router.keepalived_manager.process.active) def _assert_external_device(self, router): - external_port = self.agent._get_ex_gw_port(router) + external_port = router.get_ex_gw_port() self.assertTrue(self.device_exists_with_ip_mac( external_port, self.agent.get_external_device_name, router.ns_name)) def _assert_gateway(self, router): - external_port = self.agent._get_ex_gw_port(router) + external_port = router.get_ex_gw_port() external_device_name = self.agent.get_external_device_name( external_port['id']) external_device = ip_lib.IPDevice(external_device_name, @@ -444,7 +444,7 @@ class L3AgentTestCase(L3AgentTestFramework): def _floating_ips_configured(self, router): floating_ips = router.router[l3_constants.FLOATINGIP_KEY] - external_port = self.agent._get_ex_gw_port(router) + external_port = router.get_ex_gw_port() return len(floating_ips) and all(ip_lib.device_exists_with_ip_mac( self.agent.get_external_device_name(external_port['id']), '%s/32' % fip['floating_ip_address'], @@ -708,7 +708,7 @@ class TestDvrRouter(L3AgentTestFramework): ] def _assert_dvr_external_device(self, router): - external_port = self.agent._get_ex_gw_port(router) + external_port = router.get_ex_gw_port() snat_ns_name = self.agent.get_snat_ns_name(router.router_id) # if the agent is in dvr_snat mode, then we have to check @@ -746,7 +746,7 @@ class TestDvrRouter(L3AgentTestFramework): def _assert_dvr_snat_gateway(self, router): namespace = self.agent.get_snat_ns_name(router.router_id) - external_port = self.agent._get_ex_gw_port(router) + external_port = router.get_ex_gw_port() external_device_name = self.agent.get_external_device_name( external_port['id']) external_device = ip_lib.IPDevice(external_device_name, diff --git a/neutron/tests/unit/test_dvr_router.py b/neutron/tests/unit/agent/l3/test_dvr_router.py similarity index 68% rename from neutron/tests/unit/test_dvr_router.py rename to neutron/tests/unit/agent/l3/test_dvr_router.py index 2f3aa76ce..9c05b6535 100644 --- a/neutron/tests/unit/test_dvr_router.py +++ b/neutron/tests/unit/agent/l3/test_dvr_router.py @@ -17,7 +17,9 @@ import netaddr from neutron.agent.l3 import dvr_router from neutron.agent.l3 import link_local_allocator as lla +from neutron.agent.l3 import router_info from neutron.agent.linux import ip_lib +from neutron.common import constants as l3_constants from neutron.common import utils as common_utils from neutron.openstack.common import uuidutils from neutron.tests import base @@ -33,12 +35,23 @@ class TestDvrRouterOperations(base.BaseTestCase): def _create_router(self, router, **kwargs): agent_conf = mock.Mock() - return dvr_router.DvrRouter(mock.sentinel.router_id, + return dvr_router.DvrRouter(mock.sentinel.myhost, + mock.sentinel.router_id, router, agent_conf, mock.sentinel.interface_driver, **kwargs) + def test_get_floating_ips_dvr(self): + router = mock.MagicMock() + router.get.return_value = [{'host': mock.sentinel.myhost}, + {'host': mock.sentinel.otherhost}] + ri = self._create_router(router) + + fips = ri.get_floating_ips() + + self.assertEqual([{'host': mock.sentinel.myhost}], fips) + @mock.patch.object(ip_lib, 'send_garp_for_proxyarp') @mock.patch.object(ip_lib, 'IPDevice') @mock.patch.object(ip_lib, 'IpRule') @@ -115,3 +128,41 @@ class TestDvrRouterOperations(base.BaseTestCase): mIPDevice().route.delete_gateway.assert_called_once_with( str(fip_to_rtr.ip), table=16) fip_ns.unsubscribe.assert_called_once_with(ri.router_id) + + def _test_add_floating_ip(self, ri, fip, is_failure): + ri._add_fip_addr_to_device = mock.Mock(return_value=is_failure) + ri.floating_ip_added_dist = mock.Mock() + + result = ri.add_floating_ip(fip, + mock.sentinel.interface_name, + mock.sentinel.device) + ri._add_fip_addr_to_device.assert_called_once_with( + fip, mock.sentinel.device) + return result + + def test_add_floating_ip(self): + ri = self._create_router(mock.MagicMock()) + ip = '15.1.2.3' + fip = {'floating_ip_address': ip} + result = self._test_add_floating_ip(ri, fip, True) + ri.floating_ip_added_dist.assert_called_once_with(fip, ip + '/32') + self.assertEqual(l3_constants.FLOATINGIP_STATUS_ACTIVE, result) + + def test_add_floating_ip_error(self): + ri = self._create_router(mock.MagicMock()) + result = self._test_add_floating_ip( + ri, {'floating_ip_address': '15.1.2.3'}, False) + self.assertFalse(ri.floating_ip_added_dist.called) + self.assertEqual(l3_constants.FLOATINGIP_STATUS_ERROR, result) + + @mock.patch.object(router_info.RouterInfo, 'remove_floating_ip') + def test_remove_floating_ip(self, super_remove_floating_ip): + ri = self._create_router(mock.MagicMock()) + ri.floating_ip_removed_dist = mock.Mock() + + ri.remove_floating_ip(mock.sentinel.device, mock.sentinel.ip_cidr) + + super_remove_floating_ip.assert_called_once_with( + mock.sentinel.device, mock.sentinel.ip_cidr) + ri.floating_ip_removed_dist.assert_called_once_with( + mock.sentinel.ip_cidr) diff --git a/neutron/tests/unit/agent/l3/test_ha_router.py b/neutron/tests/unit/agent/l3/test_ha_router.py new file mode 100644 index 000000000..180093697 --- /dev/null +++ b/neutron/tests/unit/agent/l3/test_ha_router.py @@ -0,0 +1,41 @@ +# 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 ha_router +from neutron.tests import base + + +class TestBasicRouterOperations(base.BaseTestCase): + def setUp(self): + super(TestBasicRouterOperations, self).setUp() + + def _create_router(self, router=None, **kwargs): + if not router: + router = mock.MagicMock() + return ha_router.HaRouter(mock.sentinel.router_id, + router, + mock.sentinel.agent_conf, + mock.sentinel.driver, + ns_name=mock.sentinel.namespace, + **kwargs) + + def test_get_router_cidrs_returns_ha_cidrs(self): + ri = self._create_router() + device = mock.MagicMock() + device.name.return_value = 'eth2' + addresses = ['15.1.2.2/24', '15.1.2.3/32'] + ri._ha_get_existing_cidrs = mock.MagicMock(return_value=addresses) + self.assertEqual(set(addresses), ri.get_router_cidrs(device)) diff --git a/neutron/tests/unit/agent/l3/test_l3_router.py b/neutron/tests/unit/agent/l3/test_l3_router.py new file mode 100644 index 000000000..66e8ad8bf --- /dev/null +++ b/neutron/tests/unit/agent/l3/test_l3_router.py @@ -0,0 +1,222 @@ +# 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 router_info +from neutron.agent.linux import ip_lib +from neutron.common import constants as l3_constants +from neutron.common import exceptions as n_exc +from neutron.openstack.common import uuidutils +from neutron.tests import base + +_uuid = uuidutils.generate_uuid + + +class BasicRouterTestCaseFramework(base.BaseTestCase): + def _create_router(self, router=None, **kwargs): + if not router: + router = mock.MagicMock() + return router_info.RouterInfo(mock.sentinel.router_id, + router, + mock.sentinel.agent_conf, + mock.sentinel.interface_driver, + **kwargs) + + +class TestBasicRouterOperations(BasicRouterTestCaseFramework): + + def test_get_floating_ips(self): + router = mock.MagicMock() + router.get.return_value = [mock.sentinel.floating_ip] + ri = self._create_router(router) + + fips = ri.get_floating_ips() + + self.assertEqual([mock.sentinel.floating_ip], fips) + + def test_process_floating_ip_nat_rules(self): + ri = self._create_router() + fips = [{'fixed_ip_address': mock.sentinel.ip, + 'floating_ip_address': mock.sentinel.fip}] + ri.get_floating_ips = mock.Mock(return_value=fips) + ri.iptables_manager = mock.MagicMock() + ipv4_nat = ri.iptables_manager.ipv4['nat'] + ri.floating_forward_rules = mock.Mock( + return_value=[(mock.sentinel.chain, mock.sentinel.rule)]) + + ri.process_floating_ip_nat_rules() + + # Be sure that the rules are cleared first and apply is called last + self.assertEqual(mock.call.clear_rules_by_tag('floating_ip'), + ipv4_nat.mock_calls[0]) + self.assertEqual(mock.call.apply(), ri.iptables_manager.mock_calls[-1]) + + # Be sure that add_rule is called somewhere in the middle + ipv4_nat.add_rule.assert_called_once_with(mock.sentinel.chain, + mock.sentinel.rule, + tag='floating_ip') + + def test_process_floating_ip_nat_rules_removed(self): + ri = self._create_router() + ri.get_floating_ips = mock.Mock(return_value=[]) + ri.iptables_manager = mock.MagicMock() + ipv4_nat = ri.iptables_manager.ipv4['nat'] + + ri.process_floating_ip_nat_rules() + + # Be sure that the rules are cleared first and apply is called last + self.assertEqual(mock.call.clear_rules_by_tag('floating_ip'), + ipv4_nat.mock_calls[0]) + self.assertEqual(mock.call.apply(), ri.iptables_manager.mock_calls[-1]) + + # Be sure that add_rule is called somewhere in the middle + self.assertFalse(ipv4_nat.add_rule.called) + + def _test_add_fip_addr_to_device_error(self, device): + ri = self._create_router() + ip = '15.1.2.3' + + result = ri._add_fip_addr_to_device( + {'id': mock.sentinel.id, 'floating_ip_address': ip}, device) + + device.addr.add.assert_called_with(4, ip + '/32', ip) + return result + + def test__add_fip_addr_to_device(self): + result = self._test_add_fip_addr_to_device_error(mock.Mock()) + self.assertTrue(result) + + def test__add_fip_addr_to_device_error(self): + device = mock.Mock() + device.addr.add.side_effect = RuntimeError + result = self._test_add_fip_addr_to_device_error(device) + self.assertFalse(result) + + def test_process_snat_dnat_for_fip(self): + ri = self._create_router() + ri.process_floating_ip_nat_rules = mock.Mock(side_effect=Exception) + + self.assertRaises(n_exc.FloatingIpSetupException, + ri.process_snat_dnat_for_fip) + + ri.process_floating_ip_nat_rules.assert_called_once_with() + + def test_put_fips_in_error_state(self): + ri = self._create_router() + ri.router = mock.Mock() + ri.router.get.return_value = [{'id': mock.sentinel.id1}, + {'id': mock.sentinel.id2}] + + statuses = ri.put_fips_in_error_state() + + expected = [{mock.sentinel.id1: l3_constants.FLOATINGIP_STATUS_ERROR, + mock.sentinel.id2: l3_constants.FLOATINGIP_STATUS_ERROR}] + self.assertNotEqual(expected, statuses) + + def test_configure_fip_addresses(self): + ri = self._create_router() + ri.process_floating_ip_addresses = mock.Mock( + side_effect=Exception) + + self.assertRaises(n_exc.FloatingIpSetupException, + ri.configure_fip_addresses, + mock.sentinel.interface_name) + + ri.process_floating_ip_addresses.assert_called_once_with( + mock.sentinel.interface_name) + + def test_get_router_cidrs_returns_cidrs(self): + ri = self._create_router() + addresses = ['15.1.2.2/24', '15.1.2.3/32'] + device = mock.MagicMock() + device.addr.list.return_value = [{'cidr': addresses[0]}, + {'cidr': addresses[1]}] + self.assertEqual(set(addresses), ri.get_router_cidrs(device)) + + +@mock.patch.object(ip_lib, 'IPDevice') +class TestFloatingIpWithMockDevice(BasicRouterTestCaseFramework): + + def test_process_floating_ip_addresses_remap(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.2' + } + + IPDevice.return_value = device = mock.Mock() + device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}] + ri = self._create_router() + ri.get_floating_ips = mock.Mock(return_value=[fip]) + + fip_statuses = ri.process_floating_ip_addresses( + mock.sentinel.interface_name) + self.assertEqual({fip_id: l3_constants.FLOATINGIP_STATUS_ACTIVE}, + fip_statuses) + + self.assertFalse(device.addr.add.called) + self.assertFalse(device.addr.delete.called) + + def test_process_router_with_disabled_floating_ip(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.2' + } + + ri = self._create_router() + ri.floating_ips = [fip] + ri.get_floating_ips = mock.Mock(return_value=[]) + + fip_statuses = ri.process_floating_ip_addresses( + mock.sentinel.interface_name) + + self.assertIsNone(fip_statuses.get(fip_id)) + + def test_process_router_floating_ip_with_device_add_error(self, IPDevice): + IPDevice.return_value = device = mock.Mock(side_effect=RuntimeError) + device.addr.list.return_value = [] + fip_id = _uuid() + fip = { + 'id': fip_id, 'port_id': _uuid(), + 'floating_ip_address': '15.1.2.3', + 'fixed_ip_address': '192.168.0.2' + } + ri = self._create_router() + ri.add_floating_ip = mock.Mock( + return_value=l3_constants.FLOATINGIP_STATUS_ERROR) + ri.get_floating_ips = mock.Mock(return_value=[fip]) + + fip_statuses = ri.process_floating_ip_addresses( + mock.sentinel.interface_name) + + self.assertEqual({fip_id: l3_constants.FLOATINGIP_STATUS_ERROR}, + fip_statuses) + + # TODO(mrsmith): refactor for DVR cases + def test_process_floating_ip_addresses_remove(self, IPDevice): + IPDevice.return_value = device = mock.Mock() + device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}] + + ri = self._create_router() + ri.remove_floating_ip = mock.Mock() + ri.router.get = mock.Mock(return_value=[]) + + fip_statuses = ri.process_floating_ip_addresses( + mock.sentinel.interface_name) + self.assertEqual({}, fip_statuses) + ri.remove_floating_ip.assert_called_once_with(device, '15.1.2.3/32') diff --git a/neutron/tests/unit/agent/l3/test_legacy_router.py b/neutron/tests/unit/agent/l3/test_legacy_router.py new file mode 100644 index 000000000..085765659 --- /dev/null +++ b/neutron/tests/unit/agent/l3/test_legacy_router.py @@ -0,0 +1,77 @@ +# 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 legacy_router +from neutron.agent.linux import ip_lib +from neutron.common import constants as l3_constants +from neutron.tests import base + + +class BasicRouterTestCaseFramework(base.BaseTestCase): + def _create_router(self, router=None, **kwargs): + if not router: + router = mock.MagicMock() + self.agent_conf = mock.Mock() + self.driver = mock.Mock() + return legacy_router.LegacyRouter(mock.sentinel.router_id, + router, + self.agent_conf, + self.driver, + ns_name=mock.sentinel.namespace, + **kwargs) + + +class TestBasicRouterOperations(BasicRouterTestCaseFramework): + + def test_remove_floating_ip(self): + ri = self._create_router(mock.MagicMock()) + device = mock.Mock() + cidr = '15.1.2.3/32' + + ri.remove_floating_ip(device, cidr) + + device.addr.delete.assert_called_once_with(4, cidr) + self.driver.delete_conntrack_state.assert_called_once_with( + ip=cidr, + namespace=mock.sentinel.namespace) + + +@mock.patch.object(ip_lib, 'send_gratuitous_arp') +class TestAddFloatingIpWithMockGarp(BasicRouterTestCaseFramework): + def test_add_floating_ip(self, send_gratuitous_arp): + ri = self._create_router() + ri._add_fip_addr_to_device = mock.Mock(return_value=True) + self.agent_conf.send_arp_for_ha = mock.sentinel.arp_count + ri.ns_name = mock.sentinel.ns_name + ip = '15.1.2.3' + result = ri.add_floating_ip({'floating_ip_address': ip}, + mock.sentinel.interface_name, + mock.sentinel.device) + ip_lib.send_gratuitous_arp.assert_called_once_with( + mock.sentinel.ns_name, + mock.sentinel.interface_name, + ip, + mock.sentinel.arp_count) + self.assertEqual(l3_constants.FLOATINGIP_STATUS_ACTIVE, result) + + def test_add_floating_ip_error(self, send_gratuitous_arp): + ri = self._create_router() + ri._add_fip_addr_to_device = mock.Mock(return_value=False) + result = ri.add_floating_ip({'floating_ip_address': '15.1.2.3'}, + mock.sentinel.interface_name, + mock.sentinel.device) + self.assertFalse(ip_lib.send_gratuitous_arp.called) + self.assertEqual(l3_constants.FLOATINGIP_STATUS_ERROR, result) diff --git a/neutron/tests/unit/test_l3_agent.py b/neutron/tests/unit/test_l3_agent.py index e64a1ad6c..892e30c50 100644 --- a/neutron/tests/unit/test_l3_agent.py +++ b/neutron/tests/unit/test_l3_agent.py @@ -26,9 +26,9 @@ 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 legacy_router from neutron.agent.l3 import link_local_allocator as lla from neutron.agent.l3 import router_info as l3router from neutron.agent.linux import external_process @@ -753,7 +753,10 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): def test_process_dist_router(self): router = prepare_router_data() - ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs) + ri = dvr_router.DvrRouter(HOSTNAME, + router['id'], + router, + **self.ri_kwargs) subnet_id = _get_subnet_id(router[l3_constants.INTERFACE_KEY][0]) ri.router['distributed'] = True ri.router['_snat_router_interfaces'] = [{ @@ -768,9 +771,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): agent.host = HOSTNAME fake_fip_id = 'fake_fip_id' agent.create_dvr_fip_interfaces = mock.Mock() - agent.process_router_floating_ip_addresses = mock.Mock() - agent.process_router_floating_ip_nat_rules = mock.Mock() - agent.process_router_floating_ip_addresses.return_value = { + ri.process_floating_ip_addresses = mock.Mock() + ri.process_floating_ip_nat_rules = mock.Mock() + ri.process_floating_ip_addresses.return_value = { fake_fip_id: 'ACTIVE'} agent.external_gateway_added = mock.Mock() agent.external_gateway_updated = mock.Mock() @@ -781,11 +784,10 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): 'port_id': _uuid(), 'host': HOSTNAME}]} agent.process_router(ri) - agent.process_router_floating_ip_addresses.assert_called_with( - ri, mock.ANY) - agent.process_router_floating_ip_addresses.reset_mock() - agent.process_router_floating_ip_nat_rules.assert_called_with(ri) - agent.process_router_floating_ip_nat_rules.reset_mock() + ri.process_floating_ip_addresses.assert_called_with(mock.ANY) + ri.process_floating_ip_addresses.reset_mock() + ri.process_floating_ip_nat_rules.assert_called_with() + ri.process_floating_ip_nat_rules.reset_mock() agent.external_gateway_added.reset_mock() # remap floating IP to a new fixed ip @@ -794,11 +796,10 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): router[l3_constants.FLOATINGIP_KEY] = fake_floatingips2['floatingips'] agent.process_router(ri) - agent.process_router_floating_ip_addresses.assert_called_with( - ri, mock.ANY) - agent.process_router_floating_ip_addresses.reset_mock() - agent.process_router_floating_ip_nat_rules.assert_called_with(ri) - agent.process_router_floating_ip_nat_rules.reset_mock() + ri.process_floating_ip_addresses.assert_called_with(mock.ANY) + ri.process_floating_ip_addresses.reset_mock() + ri.process_floating_ip_nat_rules.assert_called_with() + ri.process_floating_ip_nat_rules.reset_mock() self.assertEqual(agent.external_gateway_added.call_count, 0) self.assertEqual(agent.external_gateway_updated.call_count, 0) agent.external_gateway_added.reset_mock() @@ -812,19 +813,18 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): ri.router['gw_port']['fixed_ips'][0]['ip_address'] = str(old_ip + 1) agent.process_router(ri) - agent.process_router_floating_ip_addresses.reset_mock() - agent.process_router_floating_ip_nat_rules.reset_mock() + ri.process_floating_ip_addresses.reset_mock() + ri.process_floating_ip_nat_rules.reset_mock() self.assertEqual(agent.external_gateway_added.call_count, 0) self.assertEqual(agent.external_gateway_updated.call_count, 1) # remove just the floating ips del router[l3_constants.FLOATINGIP_KEY] agent.process_router(ri) - agent.process_router_floating_ip_addresses.assert_called_with( - ri, mock.ANY) - agent.process_router_floating_ip_addresses.reset_mock() - agent.process_router_floating_ip_nat_rules.assert_called_with(ri) - agent.process_router_floating_ip_nat_rules.reset_mock() + ri.process_floating_ip_addresses.assert_called_with(mock.ANY) + ri.process_floating_ip_addresses.reset_mock() + ri.process_floating_ip_nat_rules.assert_called_with() + ri.process_floating_ip_nat_rules.reset_mock() # now no ports so state is torn down del router[l3_constants.INTERFACE_KEY] @@ -832,51 +832,32 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): agent.process_router(ri) self.assertEqual(self.send_arp.call_count, 1) distributed = ri.router.get('distributed', False) - self.assertEqual(agent.process_router_floating_ip_addresses.called, + self.assertEqual(ri.process_floating_ip_addresses.called, distributed) - self.assertEqual(agent.process_router_floating_ip_nat_rules.called, + self.assertEqual(ri.process_floating_ip_nat_rules.called, distributed) @mock.patch('neutron.agent.linux.ip_lib.IPDevice') - def _test_process_router_floating_ip_addresses_add(self, ri, - agent, IPDevice): - floating_ips = agent.get_floating_ips(ri) + def _test_process_floating_ip_addresses_add(self, ri, agent, IPDevice): + floating_ips = ri.get_floating_ips() fip_id = floating_ips[0]['id'] IPDevice.return_value = device = mock.Mock() device.addr.list.return_value = [] ri.iptables_manager.ipv4['nat'] = mock.MagicMock() ex_gw_port = {'id': _uuid(), 'network_id': mock.sentinel.ext_net_id} + ri.add_floating_ip = mock.Mock( + return_value=l3_constants.FLOATINGIP_STATUS_ACTIVE) 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) + fip_statuses = ri.process_floating_ip_addresses( + mock.sentinel.interface_name) self.assertEqual({fip_id: l3_constants.FLOATINGIP_STATUS_ACTIVE}, fip_statuses) - device.addr.add.assert_called_once_with(4, '15.1.2.3/32', '15.1.2.3') - - def test_process_router_floating_ip_nat_rules_add(self): - fip = { - 'id': _uuid(), 'port_id': _uuid(), - 'floating_ip_address': '15.1.2.3', - 'fixed_ip_address': '192.168.0.1' - } - - ri = mock.MagicMock() - ri.router['distributed'].__nonzero__ = lambda self: False - - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - agent.get_floating_ips = mock.Mock(return_value=[fip]) - - agent.process_router_floating_ip_nat_rules(ri) - - nat = ri.iptables_manager.ipv4['nat'] - nat.clear_rules_by_tag.assert_called_once_with('floating_ip') - rules = agent.floating_forward_rules('15.1.2.3', '192.168.0.1') - for chain, rule in rules: - nat.add_rule.assert_any_call(chain, rule, tag='floating_ip') + ri.add_floating_ip.assert_called_once_with( + floating_ips[0], mock.sentinel.interface_name, device) def test_get_floating_agent_gw_interfaces(self): fake_network_id = _uuid() @@ -895,7 +876,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): router = prepare_router_data(enable_snat=True) router[l3_constants.FLOATINGIP_AGENT_INTF_KEY] = agent_gateway_port router['distributed'] = True - ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs) + ri = dvr_router.DvrRouter( + HOSTNAME, router['id'], router, **self.ri_kwargs) agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) self.assertEqual( agent_gateway_port[0], @@ -925,7 +907,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): router[l3_constants.FLOATINGIP_KEY] = fake_floatingips['floatingips'] router[l3_constants.FLOATINGIP_AGENT_INTF_KEY] = agent_gateway_port router['distributed'] = True - ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs) + ri = dvr_router.DvrRouter( + HOSTNAME, router['id'], router, **self.ri_kwargs) agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) ext_gw_port = ri.router.get('gw_port') @@ -933,7 +916,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): ri.dist_fip_count = 0 ri.fip_ns.subscribe = mock.Mock() - with contextlib.nested(mock.patch.object(agent, + with contextlib.nested(mock.patch.object(ri, 'get_floating_ips'), mock.patch.object( agent, 'get_floating_agent_gw_interface'), @@ -961,10 +944,11 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): router = prepare_router_data(enable_snat=True) router[l3_constants.FLOATINGIP_KEY] = fake_floatingips['floatingips'] - ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs) + ri = legacy_router.LegacyRouter(router['id'], router, **self.ri_kwargs) ri.iptables_manager.ipv4['nat'] = mock.MagicMock() agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - self._test_process_router_floating_ip_addresses_add(ri, agent) + agent.get_external_device_name = mock.Mock(return_value='exgw') + self._test_process_floating_ip_addresses_add(ri, agent) def test_process_router_dist_floating_ip_add(self): fake_floatingips = {'floatingips': [ @@ -984,11 +968,13 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): router = prepare_router_data(enable_snat=True) router[l3_constants.FLOATINGIP_KEY] = fake_floatingips['floatingips'] router['distributed'] = True - ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs) + ri = dvr_router.DvrRouter(HOSTNAME, + router['id'], + router, + **self.ri_kwargs) ri.iptables_manager.ipv4['nat'] = mock.MagicMock() ri.dist_fip_count = 0 agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - agent.host = HOSTNAME fip_ns = agent.get_fip_ns(mock.sentinel.ext_net_id) fip_ns.agent_gateway_port = ( {'fixed_ips': [{'ip_address': '20.0.0.30', @@ -999,131 +985,6 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): '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) - - def test_get_router_cidrs_returns_cidrs(self): - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - ri = mock.MagicMock() - ri.is_ha = False - addresses = ['15.1.2.2/24', '15.1.2.3/32'] - device = mock.MagicMock() - device.addr.list.return_value = [{'cidr': addresses[0]}, - {'cidr': addresses[1]}] - self.assertEqual(set(addresses), agent._get_router_cidrs(ri, device)) - - def test_get_router_cidrs_returns_ha_cidrs(self): - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - ri = mock.MagicMock() - ri.is_ha = True - device = mock.MagicMock() - device.name.return_value = 'eth2' - addresses = ['15.1.2.2/24', '15.1.2.3/32'] - ri._ha_get_existing_cidrs = mock.MagicMock() - ri._ha_get_existing_cidrs.return_value = addresses - self.assertEqual(set(addresses), agent._get_router_cidrs(ri, device)) - - # 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() - device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}] - - ri = mock.MagicMock() - ri.router.get.return_value = [] - type(ri).is_ha = mock.PropertyMock(return_value=False) - ri.router['distributed'].__nonzero__ = lambda self: False - - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - - fip_statuses = agent.process_router_floating_ip_addresses( - ri, {'id': _uuid()}) - self.assertEqual({}, fip_statuses) - device.addr.delete.assert_called_once_with(4, '15.1.2.3/32') - self.mock_driver.delete_conntrack_state.assert_called_once_with( - namespace=ri.ns_name, - ip='15.1.2.3/32') - - def test_process_router_floating_ip_nat_rules_remove(self): - ri = mock.MagicMock() - ri.router.get.return_value = [] - - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - - agent.process_router_floating_ip_nat_rules(ri) - - nat = ri.iptables_manager.ipv4['nat'] - nat = ri.iptables_manager.ipv4['nat`'] - nat.clear_rules_by_tag.assert_called_once_with('floating_ip') - - @mock.patch('neutron.agent.linux.ip_lib.IPDevice') - def test_process_router_floating_ip_addresses_remap(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.2' - } - - 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 - type(ri).is_ha = mock.PropertyMock(return_value=False) - ri.router.get.return_value = [fip] - - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - - fip_statuses = agent.process_router_floating_ip_addresses( - ri, {'id': _uuid()}) - self.assertEqual({fip_id: l3_constants.FLOATINGIP_STATUS_ACTIVE}, - fip_statuses) - - self.assertFalse(device.addr.add.called) - self.assertFalse(device.addr.delete.called) - - @mock.patch('neutron.agent.linux.ip_lib.IPDevice') - def test_process_router_with_disabled_floating_ip(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.2' - } - - ri = mock.MagicMock() - ri.floating_ips = [fip] - ri.router.get.return_value = [] - - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - - fip_statuses = agent.process_router_floating_ip_addresses( - ri, {'id': _uuid(), 'network_id': mock.sentinel.ext_net_id}) - - self.assertIsNone(fip_statuses.get(fip_id)) - - @mock.patch('neutron.agent.linux.ip_lib.IPDevice') - def test_process_router_floating_ip_with_device_add_error(self, IPDevice): - IPDevice.return_value = device = mock.Mock() - device.addr.add.side_effect = RuntimeError() - device.addr.list.return_value = [] - fip_id = _uuid() - fip = { - 'id': fip_id, 'port_id': _uuid(), - 'floating_ip_address': '15.1.2.3', - 'fixed_ip_address': '192.168.0.2' - } - ri = mock.MagicMock() - type(ri).is_ha = mock.PropertyMock(return_value=False) - ri.router.get.return_value = [fip] - ri.router['distributed'].__nonzero__ = lambda self: False - - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - - fip_statuses = agent.process_router_floating_ip_addresses( - ri, {'id': _uuid()}) - - self.assertEqual({fip_id: l3_constants.FLOATINGIP_STATUS_ERROR}, - fip_statuses) def test_process_router_snat_disabled(self): agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) @@ -1386,7 +1247,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): 'fixed_ip_address': '7.7.7.7', 'port_id': router[l3_constants.INTERFACE_KEY][0]['id']}] - ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs) + ri = legacy_router.LegacyRouter(router['id'], + router, + **self.ri_kwargs) agent.external_gateway_added = mock.Mock() agent.process_router(ri) # Assess the call for putting the floating IP up was performed @@ -1405,8 +1268,6 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): def test_process_router_floatingip_exception(self): agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - agent.process_router_floating_ip_addresses = mock.Mock() - agent.process_router_floating_ip_addresses.side_effect = RuntimeError with mock.patch.object( agent.plugin_rpc, 'update_floatingip_statuses') as mock_update_fip_status: @@ -1419,6 +1280,8 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): 'port_id': router[l3_constants.INTERFACE_KEY][0]['id']}] ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs) + ri.process_floating_ip_addresses = mock.Mock( + side_effect=RuntimeError) agent.external_gateway_added = mock.Mock() agent.process_router(ri) # Assess the call for putting the floating IP into Error @@ -1429,6 +1292,7 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): def test_handle_router_snat_rules_distributed_without_snat_manager(self): ri = dvr_router.DvrRouter( + HOSTNAME, 'foo_router_id', {'distributed': True}, **self.ri_kwargs) @@ -1892,7 +1756,9 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): self.mock_driver.unplug.reset_mock() external_net_id = router['gw_port']['network_id'] - ri = dvr_router.DvrRouter(router['id'], router, **self.ri_kwargs) + ri = dvr_router.DvrRouter( + HOSTNAME, router['id'], router, **self.ri_kwargs) + ri.remove_floating_ip = mock.Mock() agent._fetch_external_net_id = mock.Mock(return_value=external_net_id) ri.ex_gw_port = ri.router['gw_port'] del ri.router['gw_port'] @@ -1929,24 +1795,11 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): ri, ri.ex_gw_port, agent.get_external_device_name(ri.ex_gw_port['id'])) - self.assertFalse(nat.add_rule.called) - nat.clear_rules_by_tag.assert_called_once_with('floating_ip') if fip_ns: - self.mock_ip.del_veth.assert_called_once_with( - 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_ns.FIP_RT_TBL) - - self.assertEqual(ri.dist_fip_count, 0) - self.assertFalse(fip_ns.has_subscribers()) - - self.assertIsNone(fip_ns.agent_gateway_port) - self.assertTrue(fip_ns.destroyed) - self.mock_ip.netns.delete.assert_called_once_with( - fip_ns.get_name()) - self.assertEqual(self.mock_driver.unplug.call_count, 1) + ri.remove_floating_ip.assert_called_once_with(self.mock_ip_dev, + '19.4.4.2/32') else: - self.assertFalse(self.mock_driver.unplug.called) + self.assertFalse(ri.remove_floating_ip.called) def test_external_gateway_removed_ext_gw_port_and_fip(self): self._test_external_gateway_removed_ext_gw_port_and_fip(fip_ns=True) @@ -2022,41 +1875,3 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework): if not skip(managed_flag): assertFlag(managed_flag)('AdvManagedFlag on', self.utils_replace_file.call_args[0][1]) - - def test__put_fips_in_error_state(self): - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - ri = mock.Mock() - ri.router.get.return_value = [{'id': mock.sentinel.id1}, - {'id': mock.sentinel.id2}] - - statuses = agent._put_fips_in_error_state(ri) - - expected = [{mock.sentinel.id1: l3_constants.FLOATINGIP_STATUS_ERROR, - mock.sentinel.id2: l3_constants.FLOATINGIP_STATUS_ERROR}] - self.assertNotEqual(expected, statuses) - - def test__process_snat_dnat_for_fip(self): - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - agent.process_router_floating_ip_nat_rules = mock.Mock( - side_effect=Exception) - - self.assertRaises(n_exc.FloatingIpSetupException, - agent._process_snat_dnat_for_fip, - mock.sentinel.ri) - - agent.process_router_floating_ip_nat_rules.assert_called_with( - mock.sentinel.ri) - - def test__configure_fip_addresses(self): - agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) - agent.process_router_floating_ip_addresses = mock.Mock( - side_effect=Exception) - - self.assertRaises(n_exc.FloatingIpSetupException, - agent._configure_fip_addresses, - mock.sentinel.ri, - mock.sentinel.ex_gw_port) - - agent.process_router_floating_ip_addresses.assert_called_with( - mock.sentinel.ri, - mock.sentinel.ex_gw_port) -- 2.45.2