neutron/neutron/agent/l3/dvr_edge_router.py
Brian Haley b79842f289 Start enforcing E125 flake8 directive
Removed E125 (continuation line does not distinguish itself
from next logical line) from the ignore list and fixed all
the indentation issues.  Didn't think it was going to be
close to 100 files when I started.

Change-Id: I0a6f5efec4b7d8d3632dd9dbb43e0ab58af9dff3
2019-07-19 23:39:41 -04:00

377 lines
16 KiB
Python

# 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.
from neutron_lib import constants as lib_constants
from oslo_log import log as logging
from neutron.agent.l3 import dvr_local_router
from neutron.agent.l3 import dvr_snat_ns
from neutron.agent.l3 import router_info as router
from neutron.agent.linux import ip_lib
from neutron.agent.linux import iptables_manager
from neutron.common import utils as common_utils
LOG = logging.getLogger(__name__)
class DvrEdgeRouter(dvr_local_router.DvrLocalRouter):
def __init__(self, host, *args, **kwargs):
super(DvrEdgeRouter, self).__init__(host, *args, **kwargs)
self.snat_namespace = dvr_snat_ns.SnatNamespace(
self.router_id, self.agent_conf, self.driver, self.use_ipv6)
self.snat_iptables_manager = None
def get_gw_ns_name(self):
return self.snat_namespace.name
def external_gateway_added(self, ex_gw_port, interface_name):
super(DvrEdgeRouter, self).external_gateway_added(
ex_gw_port, interface_name)
if self._is_this_snat_host():
self._create_dvr_gateway(ex_gw_port, interface_name)
# NOTE: When a router is created without a gateway the routes get
# added to the router namespace, but if we wanted to populate
# the same routes to the snat namespace after the gateway port
# is added, we need to call routes_updated here.
self.routes_updated([], self.router['routes'])
elif self.snat_namespace.exists():
# This is the case where the snat was moved manually or
# rescheduled to a different agent when the agent was dead.
LOG.debug("SNAT was moved or rescheduled to a different host "
"and does not match with the current host. This is "
"a stale namespace %s and will be cleared from the "
"current dvr_snat host.", self.snat_namespace.name)
self.external_gateway_removed(ex_gw_port, interface_name)
def _list_centralized_floating_ip_cidrs(self):
# Compute a list of addresses this gw is supposed to have.
# This avoids unnecessarily removing those addresses and
# causing a momentarily network outage.
floating_ips = self.get_floating_ips()
return [common_utils.ip_to_cidr(ip['floating_ip_address'])
for ip in floating_ips
if ip.get(lib_constants.DVR_SNAT_BOUND)]
def external_gateway_updated(self, ex_gw_port, interface_name):
if not self._is_this_snat_host():
# no centralized SNAT gateway for this node/agent
LOG.debug("not hosting snat for router: %s", self.router['id'])
if self.snat_namespace.exists():
LOG.debug("SNAT was rescheduled to host %s. Clearing snat "
"namespace.", self.router.get('gw_port_host'))
return self.external_gateway_removed(
ex_gw_port, interface_name)
return
if not self.snat_namespace.exists():
# SNAT might be rescheduled to this agent; need to process like
# newly created gateway
return self.external_gateway_added(ex_gw_port, interface_name)
else:
preserve_ips = self._list_centralized_floating_ip_cidrs()
self._external_gateway_added(ex_gw_port,
interface_name,
self.snat_namespace.name,
preserve_ips)
def _external_gateway_removed(self, ex_gw_port, interface_name):
super(DvrEdgeRouter, self).external_gateway_removed(ex_gw_port,
interface_name)
if not self._is_this_snat_host() and not self.snat_namespace.exists():
# no centralized SNAT gateway for this node/agent
LOG.debug("not hosting snat for router: %s", self.router['id'])
return
self.driver.unplug(interface_name,
namespace=self.snat_namespace.name,
prefix=router.EXTERNAL_DEV_PREFIX)
def external_gateway_removed(self, ex_gw_port, interface_name):
self._external_gateway_removed(ex_gw_port, interface_name)
if self.snat_namespace.exists():
self.snat_namespace.delete()
def internal_network_added(self, port):
super(DvrEdgeRouter, self).internal_network_added(port)
# TODO(gsagie) some of this checks are already implemented
# in the base class, think how to avoid re-doing them
if not self._is_this_snat_host():
return
sn_port = self.get_snat_port_for_internal_port(port)
if not sn_port:
return
ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name(self.router['id'])
interface_name = self._get_snat_int_device_name(sn_port['id'])
self._internal_network_added(
ns_name,
sn_port['network_id'],
sn_port['id'],
sn_port['fixed_ips'],
sn_port['mac_address'],
interface_name,
lib_constants.SNAT_INT_DEV_PREFIX,
mtu=sn_port.get('mtu'))
def _dvr_internal_network_removed(self, port):
super(DvrEdgeRouter, self)._dvr_internal_network_removed(port)
if not self.ex_gw_port:
return
sn_port = self.get_snat_port_for_internal_port(port, self.snat_ports)
if not sn_port:
return
if not self._is_this_snat_host():
return
snat_interface = self._get_snat_int_device_name(sn_port['id'])
ns_name = self.snat_namespace.name
prefix = lib_constants.SNAT_INT_DEV_PREFIX
if ip_lib.device_exists(snat_interface, namespace=ns_name):
self.driver.unplug(snat_interface, namespace=ns_name,
prefix=prefix)
def _plug_snat_port(self, port):
interface_name = self._get_snat_int_device_name(port['id'])
self._internal_network_added(
self.snat_namespace.name, port['network_id'],
port['id'], port['fixed_ips'],
port['mac_address'], interface_name,
lib_constants.SNAT_INT_DEV_PREFIX,
mtu=port.get('mtu'))
def _create_dvr_gateway(self, ex_gw_port, gw_interface_name):
snat_ns = self._create_snat_namespace()
# connect snat_ports to br_int from SNAT namespace
for port in self.get_snat_interfaces():
self._plug_snat_port(port)
self._external_gateway_added(ex_gw_port, gw_interface_name,
snat_ns.name, preserve_ips=[])
self.snat_iptables_manager = iptables_manager.IptablesManager(
namespace=snat_ns.name,
use_ipv6=self.use_ipv6)
self._initialize_address_scope_iptables(self.snat_iptables_manager)
def _create_snat_namespace(self):
"""Create SNAT namespace."""
# TODO(mlavalle): in the near future, this method should contain the
# code in the L3 agent that creates a gateway for a dvr. The first step
# is to move the creation of the snat namespace here
self.snat_namespace.create()
return self.snat_namespace
def _get_snat_int_device_name(self, port_id):
long_name = lib_constants.SNAT_INT_DEV_PREFIX + port_id
return long_name[:self.driver.DEV_NAME_LEN]
def _is_this_snat_host(self):
host = self.router.get('gw_port_host')
if not host:
LOG.debug("gw_port_host missing from router: %s",
self.router['id'])
return host == self.host
def _handle_router_snat_rules(self, ex_gw_port, interface_name):
super(DvrEdgeRouter, self)._handle_router_snat_rules(
ex_gw_port, interface_name)
if not self._is_this_snat_host():
return
if not self.get_ex_gw_port():
return
if not self.snat_iptables_manager:
LOG.debug("DVR router: no snat rules to be handled")
return
with self.snat_iptables_manager.defer_apply():
self._empty_snat_chains(self.snat_iptables_manager)
# NOTE: float-snat should be added for the
# centralized floating-ips supported by the
# snat namespace.
self.snat_iptables_manager.ipv4['nat'].add_rule(
'snat', '-j $float-snat')
self._add_snat_rules(ex_gw_port, self.snat_iptables_manager,
interface_name)
def update_routing_table(self, operation, route):
if self.get_ex_gw_port() and self._is_this_snat_host():
ns_name = self.snat_namespace.name
# NOTE: For now let us apply the static routes both in SNAT
# namespace and Router Namespace, to reduce the complexity.
if self.snat_namespace.exists():
super(DvrEdgeRouter, self)._update_routing_table(
operation, route, namespace=ns_name)
else:
LOG.error("The SNAT namespace %s does not exist for "
"the router.", ns_name)
super(DvrEdgeRouter, self).update_routing_table(operation, route)
def delete(self):
super(DvrEdgeRouter, self).delete()
if self.snat_namespace.exists():
self.snat_namespace.delete()
def process_address_scope(self):
super(DvrEdgeRouter, self).process_address_scope()
if not self._is_this_snat_host():
return
if not self.snat_iptables_manager:
LOG.debug("DVR router: no snat rules to be handled")
return
# Prepare address scope iptables rule for dvr snat interfaces
internal_ports = self.get_snat_interfaces()
ports_scopemark = self._get_port_devicename_scopemark(
internal_ports, self._get_snat_int_device_name)
# Prepare address scope iptables rule for external port
external_port = self.get_ex_gw_port()
if external_port:
external_port_scopemark = self._get_port_devicename_scopemark(
[external_port], self.get_external_device_name)
for ip_version in (lib_constants.IP_VERSION_4,
lib_constants.IP_VERSION_6):
ports_scopemark[ip_version].update(
external_port_scopemark[ip_version])
with self.snat_iptables_manager.defer_apply():
self._add_address_scope_mark(
self.snat_iptables_manager, ports_scopemark)
def _delete_stale_external_devices(self, interface_name):
if not self.snat_namespace.exists():
return
ns_ip = ip_lib.IPWrapper(namespace=self.snat_namespace.name)
for d in ns_ip.get_devices():
if (d.name.startswith(router.EXTERNAL_DEV_PREFIX) and
d.name != interface_name):
LOG.debug('Deleting stale external router device: %s', d.name)
self.driver.unplug(
d.name,
namespace=self.snat_namespace.name,
prefix=router.EXTERNAL_DEV_PREFIX)
def get_snat_external_device_interface_name(self, ex_gw_port):
long_name = router.EXTERNAL_DEV_PREFIX + ex_gw_port['id']
return long_name[:self.driver.DEV_NAME_LEN]
def get_centralized_fip_cidr_set(self):
"""Returns the fip_cidr set for centralized floatingips."""
ex_gw_port = self.get_ex_gw_port()
# Don't look for centralized FIP cidrs if gw_port not exists or
# this is not snat host
if (not ex_gw_port or not self._is_this_snat_host() or
not self.snat_namespace.exists()):
return set()
interface_name = self.get_snat_external_device_interface_name(
ex_gw_port)
return set([addr['cidr'] for addr in ip_lib.get_devices_with_ip(
self.snat_namespace.name,
name=interface_name)])
def get_router_cidrs(self, device):
"""Over-ride the get_router_cidrs function to return the list.
This function is overridden to provide the complete list of
floating_ip cidrs that the router hosts.
This includes the centralized floatingip cidr list and the
regular floatingip cidr list that are bound to fip namespace.
"""
fip_cidrs = super(DvrEdgeRouter, self).get_router_cidrs(device)
centralized_cidrs = self.get_centralized_fip_cidr_set()
return fip_cidrs | centralized_cidrs
def remove_centralized_floatingip(self, fip_cidr):
"""Function to handle the centralized Floatingip remove."""
if not self.get_ex_gw_port():
return
if not self._is_this_snat_host():
return
interface_name = self.get_snat_external_device_interface_name(
self.get_ex_gw_port())
device = ip_lib.IPDevice(
interface_name, namespace=self.snat_namespace.name)
device.delete_addr_and_conntrack_state(fip_cidr)
self.process_floating_ip_nat_rules_for_centralized_floatingip()
def add_centralized_floatingip(self, fip, fip_cidr):
"""Function to handle the centralized Floatingip addition."""
if not self.get_ex_gw_port():
return
if not self._is_this_snat_host():
return
interface_name = self.get_snat_external_device_interface_name(
self.get_ex_gw_port())
try:
ip_lib.add_ip_address(fip_cidr, interface_name,
namespace=self.snat_namespace.name)
except ip_lib.IpAddressAlreadyExists:
pass
except RuntimeError:
LOG.warning("Unable to configure IP address for centralized "
"floating IP: %s", fip['id'])
return lib_constants.FLOATINGIP_STATUS_ERROR
self.process_floating_ip_nat_rules_for_centralized_floatingip()
# Send a GARP message on the external interface for the
# centralized floatingip configured.
ip_lib.send_ip_addr_adv_notif(self.snat_namespace.name,
interface_name,
fip['floating_ip_address'])
return lib_constants.FLOATINGIP_STATUS_ACTIVE
def _centralized_floating_forward_rules(self, floating_ip, fixed_ip):
to_source = '-s %s/32 -j SNAT --to-source %s' % (fixed_ip, floating_ip)
if self.snat_iptables_manager.random_fully:
to_source += ' --random-fully'
return [('PREROUTING', '-d %s/32 -j DNAT --to-destination %s' %
(floating_ip, fixed_ip)),
('OUTPUT', '-d %s/32 -j DNAT --to-destination %s' %
(floating_ip, fixed_ip)),
('float-snat', to_source)]
def _set_floating_ip_nat_rules_for_centralized_floatingip(self, fip):
if fip.get(lib_constants.DVR_SNAT_BOUND):
fixed = fip['fixed_ip_address']
fip_ip = fip['floating_ip_address']
for chain, rule in self._centralized_floating_forward_rules(
fip_ip, fixed):
self.snat_iptables_manager.ipv4['nat'].add_rule(
chain, rule, tag='floating_ip')
def process_floating_ip_nat_rules_for_centralized_floatingip(self):
self.snat_iptables_manager.ipv4['nat'].clear_rules_by_tag(
'floating_ip')
floating_ips = self.get_floating_ips()
for fip in floating_ips:
self._set_floating_ip_nat_rules_for_centralized_floatingip(fip)
self.snat_iptables_manager.apply()
def process_floating_ip_nat_rules(self):
if self._is_this_snat_host():
self.process_floating_ip_nat_rules_for_centralized_floatingip()
# Cover mixed dvr_snat and compute node, aka a dvr_snat node has both
# centralized and distributed floating IPs.
super(DvrEdgeRouter, self).process_floating_ip_nat_rules()