dragonflow/dragonflow/controller/apps/l3_base.py

789 lines
33 KiB
Python

# Copyright (c) 2017 Huawei Tech. Co., Ltd. .
# All Rights Reserved.
#
# 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 copy
import netaddr
from neutron_lib import constants as common_const
from oslo_log import log
from ryu.lib import mac as ryu_mac_lib
from ryu.lib.packet import ethernet
from ryu.lib.packet import icmp
from ryu.lib.packet import packet
from ryu.lib.packet import tcp
from ryu.lib.packet import udp
from ryu.ofproto import ether
from dragonflow.common import exceptions
from dragonflow.common import utils as df_utils
from dragonflow import conf as cfg
from dragonflow.controller.common import arp_responder
from dragonflow.controller.common import constants as const
from dragonflow.controller.common import icmp_error_generator
from dragonflow.controller.common import icmp_responder
from dragonflow.controller import df_base_app
from dragonflow.db.models import constants as model_constants
from dragonflow.db.models import host_route
from dragonflow.db.models import l2
from dragonflow.db.models import l3
ROUTE_TO_ADD = 'route_to_add'
ROUTE_ADDED = 'route_added'
LOG = log.getLogger(__name__)
class L3AppMixin(object):
def __init__(self, *args, **kwargs):
super(L3AppMixin, self).__init__()
self.route_cache = {}
self.conf = cfg.CONF.df_l3_app
self.ttl_invalid_handler_rate_limit = df_utils.RateLimiter(
max_rate=self.conf.router_ttl_invalid_max_rate,
time_unit=1)
self.port_icmp_unreach_respond_rate_limit = df_utils.RateLimiter(
max_rate=self.conf.router_port_unreach_max_rate,
time_unit=1)
self.api.register_table_handler(const.L3_LOOKUP_TABLE,
self.packet_in_handler)
def switch_features_handler(self, ev):
self.route_cache.clear()
def _handle_ttl_expired(self, msg):
"""
This callback is called when the OVS switch reduced a packet's TTL
to 0.
Create an ICMP error packet, and return it.
:param msg: Packet in message
:type msg: ryu.ofproto.ofproto_v<version>_parser.OFPPacketIn
"""
if self.ttl_invalid_handler_rate_limit():
LOG.warning("Get more than %(rate)s TTL invalid packets per "
"second at table %(table)s",
{'rate': self.conf.router_ttl_invalid_max_rate,
'table': const.L3_LOOKUP_TABLE})
return
LOG.debug("Get an invalid TTL packet at table %s",
const.L3_LOOKUP_TABLE)
pkt = packet.Packet(msg.data)
e_pkt = pkt.get_protocol(ethernet.ethernet)
router_key = msg.match.get('reg5')
lrouter = self.db_store.get_one(
l3.LogicalRouter(unique_key=router_key),
index=l3.LogicalRouter.get_index('unique_key'),
)
router_port_ip = None
for port in lrouter.ports:
if port.lswitch.unique_key == msg.match.get('metadata'):
router_port_ip = port.network.ip
break
if router_port_ip:
icmp_ttl_pkt = icmp_error_generator.generate(
icmp.ICMP_TIME_EXCEEDED, icmp.ICMP_TTL_EXPIRED_CODE,
msg.data, str(router_port_ip), pkt)
unique_key = msg.match.get('reg6')
self.dispatch_packet(icmp_ttl_pkt, unique_key)
else:
LOG.warning("The invalid TTL packet's destination mac %s "
"can't be recognized.", e_pkt.dst)
def _handle_invalid_dest(self, msg):
"""
Handle a packet sent to the router interface (by IP). Since only ping
and ARP are supported, everything else is responded to with a
Destination Unreachable message.
:param msg: Packet in message
:type msg: ryu.ofproto.ofproto_v<version>_parser.OFPPacketIn
"""
# If the destination is router interface, the unique key of router
# interface will be set to reg7 before sending to local controller.
# Code will hit here only when the router interface is not
# concrete.
if self.port_icmp_unreach_respond_rate_limit():
LOG.warning(
"Get more than %(rate)s packets to router port "
"per second at table %(table)s",
{'rate': self.conf.router_port_unreach_max_rate,
'table': const.L3_LOOKUP_TABLE})
return
# Response icmp unreachable to udp or tcp.
pkt = packet.Packet(msg.data)
tcp_pkt = pkt.get_protocol(tcp.tcp)
udp_pkt = pkt.get_protocol(udp.udp)
if tcp_pkt or udp_pkt:
icmp_dst_unreach = icmp_error_generator.generate(
icmp.ICMP_DEST_UNREACH, icmp.ICMP_PORT_UNREACH_CODE,
msg.data, pkt=pkt)
unique_key = msg.match.get('reg6')
self.dispatch_packet(icmp_dst_unreach, unique_key)
def router_function_packet_in_handler(self, msg):
"""React to packet as what a normal router will do.
TTL invalid and router port response will be handled in this method.
Return True if the packet is handled, so there is no need for further
handle.
:param msg: Packet in message
:type msg: ryu.ofproto.ofproto_v<version>_parser.OFPPacketIn
"""
if msg.reason == self.ofproto.OFPR_INVALID_TTL:
self._handle_ttl_expired(msg)
elif msg.match.get('reg7'):
self._handle_invalid_dest(msg)
else:
return False
return True
@df_base_app.register_event(l3.LogicalRouter,
model_constants.EVENT_CREATED)
def router_created(self, router):
for new_port in router.ports:
self._add_new_router_port(router, new_port)
for route in router.routes:
self._add_router_extra_route(router, route)
@df_base_app.register_event(l3.LogicalRouter,
model_constants.EVENT_UPDATED)
def router_updated(self, router, original_router=None):
self._update_router_interfaces(original_router, router)
self._update_router_attributes(original_router, router)
@df_base_app.register_event(l3.LogicalRouter,
model_constants.EVENT_DELETED)
def router_deleted(self, router):
for port in router.ports:
self._delete_router_port(router, port)
for route in router.routes:
self._delete_router_extra_route(router, route)
self.route_cache.pop(router.id, None)
def _update_router_interfaces(self, old_router, new_router):
"""
A router has been updated. Delete old router ports, and create new
router ports
:param old_router: The old router instance
:type old_router: LogicalRouter model
:param new_router: The new router instance
:type new_router: LogicalRouter model
"""
old_ports = old_router.ports
new_ports = new_router.ports
for old_port in old_ports:
if old_port not in new_ports:
self._delete_router_port(new_router, old_port)
for new_port in new_ports:
if new_port not in old_ports:
self._add_new_router_port(new_router, new_port)
def _update_router_attributes(self, old_router, new_router):
"""
A router has been updated. Update the followin attributes:
* extra routes
:param old_router: The old router instance
:type old_router: LogicalRouter model
:param new_router: The new router instance
:type new_router: LogicalRouter model
"""
old_routes = old_router.routes
new_routes = new_router.routes
for old_route in old_routes:
if old_route not in new_routes:
self._delete_router_extra_route(new_router, old_route)
for new_route in new_routes:
if new_route not in old_routes:
self._add_router_extra_route(new_router, new_route)
def _get_port_by_lswitch_and_ip(self, ip, lswitch_id):
"""
Return the logical port with the given IP that's attached to the given
Logical Switch.
:param ip: The port's IP
:type ip: netaddr.IPAddress (or representation thererof)
:param lswitch_id: The Logical Switch's ID
:type lswitch_id: String
:return: LogicalPort or None
"""
ip_lswitch_idx = l2.LogicalPort.get_index('ip,lswitch')
ports = self.db_store.get_all(l2.LogicalPort(lswitch=lswitch_id,
ips=[ip]),
index=ip_lswitch_idx)
return next(ports, None)
def _get_gateway_port_by_ip(self, router, ip):
"""
Return the router port that has the given IP. Raise an exception if
no such router port exists
:param router: The router
:type router: LogicalRouter
:param ip: The IP to search
:type ip: netaddr.IPAddress (or representation thereof)
:return: LogicalPort or None
:raises: exceptions.DBStoreRecordNotFound
"""
for port in router.ports:
if ip in port.network:
return port
# Code is not expected to hit here as neutron will prevent from adding
# unreachable route.
raise exceptions.DBStoreRecordNotFound(
record='RouterPort(router=%s, ip=%s)' % (router.name, ip))
def _add_router_extra_route(self, router, route):
"""Add extra route to router."""
LOG.debug('Add extra route %(route)s to router %(router)s',
{'route': route, 'router': router})
router_port = self._get_gateway_port_by_ip(router, route.nexthop)
lport = self._get_port_by_lswitch_and_ip(route.nexthop,
router_port.lswitch.id)
router_id = router.id
if not lport:
LOG.debug("lport with IP %s doesn't exist, skip adding "
"extra route.", route.nexthop)
self._add_to_route_cache(ROUTE_TO_ADD, router_id, route)
return
self._add_extra_route_to_router(router.unique_key,
router_port.mac,
lport.unique_key,
lport.mac, route)
self._add_to_route_cache(ROUTE_ADDED, router_id, route)
def _delete_router_extra_route(self, router, route):
"""Delete extra route from router."""
LOG.debug('Delete extra route %(route)s from router %(router)s',
{'route': route, 'router': router})
router_port = self._get_gateway_port_by_ip(router, route.nexthop)
router_unique_key = router.unique_key
router_if_mac = router_port.mac
# Delete the openflow for extra route anyway.
self._delete_extra_route_from_router(router_unique_key,
router_if_mac, route)
self._del_from_route_cache(ROUTE_ADDED, router.id, route)
self._del_from_route_cache(ROUTE_TO_ADD, router.id, route)
def _add_extra_route_to_router(self, router_unique_key, router_if_mac,
lport_unique_key, lport_mac, route):
"""Add extra route to router.
@param router_unique_key: The unique_key of router where the extra
route belongs to
@param router_if_mac: The mac address of related router port
@param lport_unique_key: The unique_key of lport whick will act as
nexthop.
@param lport_mac: The mac address of lport which will act as nexthop
@param route: The extra route dict
"""
LOG.info('Add extra route %s to router', route)
ofproto = self.ofproto
parser = self.parser
# Install openflow entry for extra route, only packets come from
# the same subnet as nexthop port can use extra route.
# Match: ip, reg5=router_unique_key, dl_dst=router_if_mac,
# nw_dst=destination,
# Actions:ttl-1, mod_dl_src=router_if_mac, mod_dl_dst=lport_mac,
# load_reg7=next_hop_port_key,
# goto: egress_table
match = self._generate_extra_route_match(router_unique_key,
router_if_mac,
route.destination)
actions = [
parser.OFPActionDecNwTtl(),
parser.OFPActionSetField(eth_src=router_if_mac),
parser.OFPActionSetField(eth_dst=lport_mac),
parser.OFPActionSetField(reg7=lport_unique_key),
]
action_inst = parser.OFPInstructionActions(
ofproto.OFPIT_APPLY_ACTIONS, actions)
goto_inst = parser.OFPInstructionGotoTable(const.EGRESS_TABLE)
inst = [action_inst, goto_inst]
self.mod_flow(
inst=inst,
table_id=const.L3_LOOKUP_TABLE,
priority=const.PRIORITY_VERY_HIGH,
match=match)
def _delete_extra_route_from_router(self, router_unique_key,
router_if_mac, route):
"""Delete extra route from router.
@param router_unique_key: The unique_key of router where the extra
route belongs to
@param router_if_mac: The mac address of related router port
@param route: The extra route dict
"""
LOG.info('Delete extra route %s from router', route)
ofproto = self.ofproto
# Remove openflow entry for extra route
# Match: ip, reg5=router_unique_key, dl_dst=router_if_mac,
# nw_dst=destination
match = self._generate_extra_route_match(router_unique_key,
router_if_mac,
route.destination)
self.mod_flow(
command=ofproto.OFPFC_DELETE_STRICT,
table_id=const.L3_LOOKUP_TABLE,
priority=const.PRIORITY_VERY_HIGH,
match=match)
def _generate_extra_route_match(self, router_unique_key, router_if_mac,
destination):
"""
Create an OpenFlow Match object for the extra route on the router
:param router_unique_key: The Unique Key of the router
:type router_unique_key: Integer
:param router_if_mac: The MAC address of the router
:type router_if_mac: netaddr.EUI (or representation thereof)
:param destination: The destination network
:type destination: netaddr.IPNetwork (or string represenation)
:return: OFPMatch object
"""
dst_network = destination.network
dst_netmask = destination.netmask
if destination.version == common_const.IP_VERSION_4:
match = self.parser.OFPMatch(eth_type=ether.ETH_TYPE_IP,
reg5=router_unique_key,
eth_dst=router_if_mac,
ipv4_dst=(dst_network, dst_netmask))
else:
match = self.parser.OFPMatch(eth_type=ether.ETH_TYPE_IPV6,
reg5=router_unique_key,
eth_dst=router_if_mac,
ipv6_dst=(dst_network, dst_netmask))
return match
# route cache got following structure
# {router: {ROUTE_ADDED: set(route), ROUTE_TO_ADD: set(route)}
def _add_to_route_cache(self, key, router_id, route):
"""
Update the route_cache dictionary.
The route_cache dictionary contains two sets per router:
1. Routes that were added
2. Routes that are pending (e.g. the relevant port isn't online yet)
Add the route 'route' to the set 'key' in route_cache for the router
given by router_id.
:param key: The type of route (added, pending)
:type key: one of: ROUTE_ADDED, ROUTE_TO_ADD
:param router_id: The ID of the router owning the route
:type router_id: String
:param route: The route
:type route: HostRoute model instance
"""
cached_routes = self.route_cache.get(router_id)
if cached_routes is None:
cached_routes = {ROUTE_ADDED: set(), ROUTE_TO_ADD: set()}
self.route_cache[router_id] = cached_routes
routes = cached_routes.get(key)
routes.add((str(route.destination), str(route.nexthop)))
def _del_from_route_cache(self, key, router_id, route):
"""
Delete the given route (route) of the given type (key) from the
route_cache for the given router (router_id). See method
#_add_to_route_cache for more info about route_cache.
:param key: The type of route (added, pending)
:type key: one of: ROUTE_ADDED, ROUTE_TO_ADD
:param router_id: The ID of the router owning the route
:type router_id: String
:param route: The route
:type route: HostRoute model instance
"""
cached_routes = self.route_cache.get(router_id)
if cached_routes is None:
return
routes = cached_routes.get(key)
routes.discard((str(route.destination), str(route.nexthop)))
def _change_route_cache_status(self, router_id, from_part, to_part, route):
"""Change the status of extra route in cache of app.
See method #_add_to_route_cache for more info about route_cache.
:param router_id: The ID of the router owning the route
:type router_id: String
:param from_part: The old type of route (added, pending)
:type from_part: one of: ROUTE_ADDED, ROUTE_TO_ADD
:param to_part: The new type of route (added, pending)
:type to_part: one of: ROUTE_ADDED, ROUTE_TO_ADD
:param route: The route
:type route: HostRoute model instance
"""
self._del_from_route_cache(from_part, router_id, route)
self._add_to_route_cache(to_part, router_id, route)
def _get_router_interface_match(self, router_unique_key, rif_ip):
"""
Create an OpenFlow Match object for the router interface on the router.
Used to either pass the packet to the router (concrete router port)
or send to controller (distributed router port)
:param router_unique_key: The Unique Key of the router
:type router_unique_key: Integer
:param rif_ip: The IP of the router interface
:type rif_ip: netaddr.IPAddress (or representation)
:return: OFPMatch object
"""
if netaddr.IPAddress(rif_ip).version == common_const.IP_VERSION_4:
return self.parser.OFPMatch(eth_type=ether.ETH_TYPE_IP,
reg5=router_unique_key,
ipv4_dst=rif_ip)
return self.parser.OFPMatch(eth_type=ether.ETH_TYPE_IPV6,
reg5=router_unique_key,
ipv6_dst=rif_ip)
def _get_router_route_match(self, router_unique_key, destination):
"""
Create an OpenFlow Match object for a routing entry on the router
:param router_unique_key: The Unique Key of the router
:type router_unique_key: Integer
:param destination: The destination network
:type destination: netaddr.IPNetwork (or string represenation)
:return: OFPMatch object
"""
dst_network = destination.network
dst_netmask = destination.netmask
parser = self.parser
if netaddr.IPAddress(dst_network).version == common_const.IP_VERSION_4:
match = parser.OFPMatch(eth_type=ether.ETH_TYPE_IP,
reg5=router_unique_key,
ipv4_dst=(dst_network, dst_netmask))
else:
match = parser.OFPMatch(eth_type=ether.ETH_TYPE_IPV6,
reg5=router_unique_key,
ipv6_dst=(dst_network, dst_netmask))
return match
def _add_new_router_port(self, router, router_port):
"""
Handle the creation of a new router interface on the router.
* Match L2 address and update reg5
* Install ARP and ICMP responders
* Match packets with router as dst
* Add flows for new route entries
:param router: The router on which the interface is added
:type router: LogicalRouter model object
:param router_port: The router interface being added
:type router_port: RouterInterface model object
"""
LOG.info("Adding new logical router interface = %r",
router_port)
local_network_id = router_port.lswitch.unique_key
parser = self.parser
ofproto = self.ofproto
mac = router_port.mac
router_unique_key = router.unique_key
dst_ip = router_port.network.ip
is_ipv4 = (netaddr.IPAddress(dst_ip).version ==
common_const.IP_VERSION_4)
# Add rule for making packets go from L2_LOOKUP_TABLE
# to L3_LOOKUP_TABLE
match = parser.OFPMatch()
match.set_metadata(local_network_id)
match.set_dl_dst(ryu_mac_lib.haddr_to_bin(mac))
actions = [parser.OFPActionSetField(reg5=router_unique_key)]
action_inst = parser.OFPInstructionActions(
ofproto.OFPIT_APPLY_ACTIONS, actions)
goto_inst = parser.OFPInstructionGotoTable(const.L3_LOOKUP_TABLE)
inst = [action_inst, goto_inst]
self.mod_flow(
inst=inst,
table_id=const.L2_LOOKUP_TABLE,
priority=const.PRIORITY_HIGH,
match=match)
# Add router ARP & ICMP responder for IPv4 Addresses
if is_ipv4:
arp_responder.ArpResponder(self,
local_network_id,
dst_ip, mac).add()
icmp_responder.ICMPResponder(self,
dst_ip,
router_key=router_unique_key).add()
# If router interface is not concrete, send to local controller. local
# controller will create icmp unreachable message. A virtual router
# interface will not be in local cache, as it doesn't have chassis
# information.
lport = self.db_store.get_one(l2.LogicalPort(id=router_port.id))
if not lport:
match = self._get_router_interface_match(router_unique_key, dst_ip)
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
action_inst = parser.OFPInstructionActions(
ofproto.OFPIT_APPLY_ACTIONS, actions)
self.mod_flow(
inst=[action_inst],
table_id=const.L3_LOOKUP_TABLE,
priority=const.PRIORITY_HIGH,
match=match)
else:
self._add_concrete_router_interface(lport, router=router)
# Add rule for routing packets to subnet of this router port
match = self._get_router_route_match(router_unique_key,
router_port.network)
self._add_subnet_send_to_route(match, local_network_id, router_port)
def _delete_router_port(self, router, router_port):
"""
Handle the removal of a router interface from a router. Undoes the
actions in #_add_new_router_port.
:param router: The router on which the interface is removed
:type router: LogicalRouter model object
:param router_port: The router interface being removed
:type router_port: RouterInterface model object
"""
LOG.info("Removing logical router interface = %s",
router_port)
local_network_id = router_port.lswitch.unique_key
parser = self.parser
ofproto = self.ofproto
router_unique_key = router.unique_key
ip = router_port.network.ip
mac = router_port.mac
# Delete rule for making packets go from L2_LOOKUP_TABLE
# to L3_LOOKUP_TABLE
match = parser.OFPMatch()
match.set_metadata(local_network_id)
match.set_dl_dst(ryu_mac_lib.haddr_to_bin(mac))
self.mod_flow(
table_id=const.L2_LOOKUP_TABLE,
command=ofproto.OFPFC_DELETE,
priority=const.PRIORITY_HIGH,
match=match)
# Delete ARP & ICMP responder for router interface
if ip.version == common_const.IP_VERSION_4:
arp_responder.ArpResponder(self, local_network_id, ip).remove()
icmp_responder.ICMPResponder(self, ip,
router_key=router_unique_key).remove()
# Delete rule for packets whose destination is router interface.
match = self._get_router_interface_match(router_unique_key, ip)
self.mod_flow(
table_id=const.L3_LOOKUP_TABLE,
command=ofproto.OFPFC_DELETE,
priority=const.PRIORITY_HIGH,
match=match)
# Delete rule for routing packets to subnet of this router port
match = self._get_router_route_match(router_unique_key,
router_port.network)
self.mod_flow(
table_id=const.L3_LOOKUP_TABLE,
command=ofproto.OFPFC_DELETE,
priority=const.PRIORITY_MEDIUM,
match=match)
@df_base_app.register_event(l2.LogicalPort, l2.EVENT_BIND_LOCAL)
@df_base_app.register_event(l2.LogicalPort, l2.EVENT_BIND_REMOTE)
def _add_port_event_handler(self, lport):
LOG.debug('add %(locality)s port: %(lport)s',
{'lport': lport,
'locality': 'local' if lport.is_local else 'remote'})
if lport.device_owner == common_const.DEVICE_OWNER_ROUTER_INTF:
self._add_concrete_router_interface(lport)
else:
self._add_port(lport)
def _add_concrete_router_interface(self, lport, router=None):
"""
The router interace is concrete, direct the packets to the real
port of router interface. The flow here will overwrite
the flow that packet-in the packets to local controller.
If the router is not given (or is None), try to get it from the
port's owner.
:param lport: The router interface's concrete port
:type lport: LogicalPort model object
:param router: The owning router
:type lport: LogicalRouter or None
"""
router = router or self.db_store.get_one(
l3.LogicalRouter(id=lport.device_id))
if not router:
return
router_unique_key = router.unique_key
port_unique_key = lport.unique_key
match = self._get_router_interface_match(router_unique_key, lport.ip)
actions = [self.parser.OFPActionSetField(reg7=port_unique_key)]
action_inst = self.parser.OFPInstructionActions(
self.ofproto.OFPIT_APPLY_ACTIONS, actions)
goto_inst = self.parser.OFPInstructionGotoTable(
const.EGRESS_TABLE)
inst = [action_inst, goto_inst]
self.mod_flow(
inst=inst,
table_id=const.L3_LOOKUP_TABLE,
priority=const.PRIORITY_HIGH,
match=match)
def _get_router_by_lswitch_and_port_ip(self, lswitch_id, port_ip):
"""Find and return the logical router that lport connects to.
@param lswitch_id: The lswitch id of lport
@param port_ip: The ip of lport
@return Router and the router port that is the gateway of lport
"""
for router in self.db_store.get_all(l3.LogicalRouter):
for port in router.ports:
if (lswitch_id == port.lswitch.id and
netaddr.IPAddress(port_ip) in port.network):
return router, port
return None, None
def _reprocess_to_add_route(self, lport):
"""Add extra routes for lport.
@param lport: The lport related to extra routes.
"""
LOG.debug("Reprocess to add extra routes that use lport %s "
"as nexthop", lport)
lswitch_id = lport.lswitch.id
port_ip = lport.ip
router, router_if = self._get_router_by_lswitch_and_port_ip(
lswitch_id, port_ip)
if not router:
LOG.debug("No router for lport %s, skip adding extra route",
lport)
return
router_id = router.id
cached_routes = self.route_cache.get(router_id)
if not cached_routes or not cached_routes.get(ROUTE_TO_ADD):
LOG.debug("No extra routes need to be processed for logical "
"router %s", router)
return
# Make a copy here, or else _change_route_cache_status will delete
# elements in routes inside the iteration.
routes = copy.deepcopy(cached_routes.get(ROUTE_TO_ADD))
for route in routes:
if str(port_ip) != route[1]:
continue
route = host_route.HostRoute(destination=route[0],
nexthop=route[1])
self._add_extra_route_to_router(router.unique_key,
router_if.mac,
lport.unique_key,
lport.mac,
route)
self._change_route_cache_status(router_id,
from_part=ROUTE_TO_ADD,
to_part=ROUTE_ADDED,
route=route)
def _reprocess_to_delete_route(self, lport):
"""Delete extra routes for lport.
@param lport: The lport related to extra routes.
"""
LOG.debug("Reprocess to delete extra routes that use lport %s "
"as nexthop", lport)
lswitch_id = lport.lswitch.id
port_ip = lport.ip
router, router_if = self._get_router_by_lswitch_and_port_ip(
lswitch_id, port_ip)
if not router:
LOG.debug("No router for lport %s, skip adding extra route",
lport)
return
router_id = router.id
cached_routes = self.route_cache.get(router_id)
if not cached_routes or not cached_routes.get(ROUTE_ADDED):
LOG.debug("No extra routes need to be processed for logical "
"router %s", router)
return
# Make a copy here, or else _change_route_cache_status will delete
# elements in routes inside the iteration.
routes = copy.deepcopy(cached_routes.get(ROUTE_ADDED))
for route in routes:
if str(port_ip) != route[1]:
continue
route = host_route.HostRoute(destination=route[0],
nexthop=route[1])
self._delete_extra_route_from_router(router.unique_key,
router_if.mac,
route)
self._change_route_cache_status(router_id,
from_part=ROUTE_ADDED,
to_part=ROUTE_TO_ADD,
route=route)
def _add_port(self, lport):
"""Add port which is not a router interface."""
self._reprocess_to_add_route(lport)
@df_base_app.register_event(l2.LogicalPort, l2.EVENT_UNBIND_LOCAL)
@df_base_app.register_event(l2.LogicalPort, l2.EVENT_UNBIND_REMOTE)
def _remove_port_event_handler(self, lport):
LOG.debug('remove %(locality)s port: %(lport)s',
{'lport': lport,
'locality': 'local' if lport.is_local else 'remote'})
# Let the router update process to delete flows for concrete
# router port, if there is any.
if lport.device_owner != common_const.DEVICE_OWNER_ROUTER_INTF:
self._remove_port(lport)
def _remove_port(self, lport):
"""Remove port which is not a router interface."""
self._reprocess_to_delete_route(lport)
@df_base_app.register_event(l2.LogicalPort, l2.EVENT_LOCAL_UPDATED)
@df_base_app.register_event(l2.LogicalPort, l2.EVENT_REMOTE_UPDATED)
def _update_port_event_handler(self, lport, orig_lport):
LOG.debug('remove %(locality)s port: %(lport)s',
{'lport': lport,
'locality': 'local' if lport.is_local else 'remote'})
if lport.device_owner != common_const.DEVICE_OWNER_ROUTER_INTF:
self._update_port(lport, orig_lport)
def _update_port(self, lport, orig_lport):
pass