diff --git a/asr1k_neutron_l3/common/config.py b/asr1k_neutron_l3/common/config.py
index 36505909..691a578d 100644
--- a/asr1k_neutron_l3/common/config.py
+++ b/asr1k_neutron_l3/common/config.py
@@ -91,6 +91,10 @@
cfg.BoolOpt('enable_fwaas_cleaning', default=True, help="Run FWaaS cleaning sync to remove stale FWaaS ACLs, "
"Class Maps and Service Policies"),
cfg.IntOpt('fwaas_cleaning_interval', default=300, help="Interval for FWaaS cleaning"),
+ cfg.BoolOpt('advertise_bgp_ipv4_routes_via_redistribute', default=False,
+ help=('Advertise BGP routes (BGPVPN/DAPNets) on IPv4 via redistribute static/connected + route-map '
+ 'instead of using network statements. This avoids the global config lock, that occurs on current '
+ 'firmwares (at least 17.15) when the BGP tree is modified.')),
]
ASR1K_L2_OPTS = [
diff --git a/asr1k_neutron_l3/models/netconf_yang/bgp.py b/asr1k_neutron_l3/models/netconf_yang/bgp.py
index 1ba1b48d..0e32e2d4 100644
--- a/asr1k_neutron_l3/models/netconf_yang/bgp.py
+++ b/asr1k_neutron_l3/models/netconf_yang/bgp.py
@@ -37,6 +37,7 @@ class BGPConstants(object):
VRF = "vrf"
REDISTRIBUTE = "redistribute"
REDISTRIBUTE_VRF = "redistribute-vrf"
+ REDISTRIBUTE_V6 = "redistribute-v6"
CONNECTED = "connected"
STATIC = "static"
UNICAST = "unicast"
@@ -45,6 +46,7 @@ class BGPConstants(object):
NUMBER = "number"
MASK = "mask"
ROUTE_MAP = "route-map"
+ DEFAULT = "default"
class AddressFamilyBase(NyBase):
@@ -106,6 +108,18 @@ def _wrapper_preamble(self, dict, context):
result = {BGPConstants.ROUTER: result}
return result
+ @classmethod
+ def _get(cls, **kwargs):
+ # make sure the result has a vrf associated
+ # due to the way we filter for our AF (with the ASN included) we always get a result, which
+ # prompts the original _get() method to always return a class, which results in problems when
+ # we want an empty result in case it's not there (should_be_none=True), therefore we filter out
+ # pseudoempty results here
+ result = super()._get(**kwargs)
+ if result and getattr(result, "vrf", None) is None:
+ return None
+ return result
+
def __init__(self, **kwargs):
super().__init__(**kwargs)
@@ -167,26 +181,38 @@ def __parameters__(cls):
{'key': 'vrf', 'yang-key': 'name'},
{'key': 'networks', 'yang-path': 'ipv4-unicast/network', 'yang-key': BGPConstants.WITH_MASK,
'type': [NetworkV4], 'default': []},
+ {'key': 'redistribute_connected_with_rm', 'yang-key': 'route-map',
+ 'yang-path': 'ipv4-unicast/redistribute-vrf/connected'},
+ {'key': 'redistribute_static_with_rm', 'yang-key': 'route-map',
+ 'yang-path': 'ipv4-unicast/redistribute-vrf/static/default'},
]
def to_dict(self, context):
if self.vrf is None:
return {}
- vrf = {
- BGPConstants.NAME: self.vrf,
- BGPConstants.IPV4_UNICAST: {
- xml_utils.OPERATION: NC_OPERATION.PUT,
- BGPConstants.NETWORK: {
- BGPConstants.WITH_MASK: [
- net.to_dict(context) for net in sorted(self.networks, key=lambda x: (x.number, x.mask))
- ],
- },
- }
+ ipv4_unicast = {
+ xml_utils.OPERATION: NC_OPERATION.PUT,
+ BGPConstants.NETWORK: {
+ BGPConstants.WITH_MASK: [
+ net.to_dict(context) for net in sorted(self.networks, key=lambda x: (x.number, x.mask))
+ ],
+ },
}
+ if self.redistribute_connected_with_rm or self.redistribute_static_with_rm:
+ redist = {}
+ if self.redistribute_connected_with_rm:
+ redist[BGPConstants.CONNECTED] = {BGPConstants.ROUTE_MAP: self.redistribute_connected_with_rm}
+ if self.redistribute_static_with_rm:
+ redist[BGPConstants.STATIC] = {
+ BGPConstants.DEFAULT: {BGPConstants.ROUTE_MAP: self.redistribute_static_with_rm}}
+ ipv4_unicast[BGPConstants.REDISTRIBUTE_VRF] = redist
result = {
- BGPConstants.VRF: vrf,
+ BGPConstants.VRF: {
+ BGPConstants.NAME: self.vrf,
+ BGPConstants.IPV4_UNICAST: ipv4_unicast,
+ }
}
return result
@@ -224,24 +250,35 @@ def __parameters__(cls):
{'key': 'vrf', 'yang-key': 'name'},
{'key': 'networks', 'yang-path': BGPConstants.IPV6_UNICAST, 'yang-key': BGPConstants.NETWORK,
'type': [NetworkV6], 'default': []},
+ {'key': 'redistribute_connected_with_rm', 'yang-key': 'route-map',
+ 'yang-path': 'ipv6-unicast/redistribute-v6/connected'},
+ {'key': 'redistribute_static_with_rm', 'yang-key': 'route-map',
+ 'yang-path': 'ipv6-unicast/redistribute-v6/static'},
]
def to_dict(self, context):
if self.vrf is None:
return {}
- vrf = {
- BGPConstants.NAME: self.vrf,
- BGPConstants.IPV6_UNICAST: {
- xml_utils.OPERATION: NC_OPERATION.PUT,
- BGPConstants.NETWORK: [
- net.to_dict(context) for net in sorted(self.networks, key=lambda x: x.number)
- ],
- }
+ ipv6_unicast = {
+ xml_utils.OPERATION: NC_OPERATION.PUT,
+ BGPConstants.NETWORK: [
+ net.to_dict(context) for net in sorted(self.networks, key=lambda x: x.number)
+ ],
}
+ if self.redistribute_connected_with_rm or self.redistribute_static_with_rm:
+ redist = {}
+ if self.redistribute_connected_with_rm:
+ redist[BGPConstants.CONNECTED] = {BGPConstants.ROUTE_MAP: self.redistribute_connected_with_rm}
+ if self.redistribute_static_with_rm:
+ redist[BGPConstants.STATIC] = {BGPConstants.ROUTE_MAP: self.redistribute_static_with_rm}
+ ipv6_unicast[BGPConstants.REDISTRIBUTE_V6] = redist
result = {
- BGPConstants.VRF: vrf,
+ BGPConstants.VRF: {
+ BGPConstants.NAME: self.vrf,
+ BGPConstants.IPV6_UNICAST: ipv6_unicast,
+ }
}
return result
diff --git a/asr1k_neutron_l3/models/netconf_yang/l3_interface.py b/asr1k_neutron_l3/models/netconf_yang/l3_interface.py
index db680d31..93e2aced 100644
--- a/asr1k_neutron_l3/models/netconf_yang/l3_interface.py
+++ b/asr1k_neutron_l3/models/netconf_yang/l3_interface.py
@@ -463,3 +463,9 @@ def __parameters__(cls):
def to_dict(self, context):
return {L3Constants.PREFIX: self.prefix.lower()}
+
+ @property
+ def address(self):
+ if self.prefix:
+ return self.prefix.split("/")[0]
+ return None
diff --git a/asr1k_neutron_l3/models/netconf_yang/prefix.py b/asr1k_neutron_l3/models/netconf_yang/prefix.py
index 4c497f08..76929f57 100644
--- a/asr1k_neutron_l3/models/netconf_yang/prefix.py
+++ b/asr1k_neutron_l3/models/netconf_yang/prefix.py
@@ -68,6 +68,14 @@ class PrefixBase(NyBase):
LIST_KEY = None
ITEM_KEY = PrefixConstants.PREFIX_LISTS
+ KNOWN_PREFIXES = [
+ "ext-", "snat-", "route-",
+ "routable4-", "routable6-",
+ "routable-extraroutes4-", "routable-extraroutes6-",
+ "internal4-", "internal6-",
+ "internal-extraroutes4-", "internal-extraroutes6-",
+ ]
+
@classmethod
def __parameters__(cls):
return [
diff --git a/asr1k_neutron_l3/models/netconf_yang/route_map.py b/asr1k_neutron_l3/models/netconf_yang/route_map.py
index 63c02d43..a9123cf7 100644
--- a/asr1k_neutron_l3/models/netconf_yang/route_map.py
+++ b/asr1k_neutron_l3/models/netconf_yang/route_map.py
@@ -68,6 +68,11 @@ class RouteMap(NyBase):
LIST_KEY = None
ITEM_KEY = RouteMapConstants.ROUTE_MAP
+ KNOWN_PREFIXES = [
+ "exp-", "pbr-",
+ "bgp-redistribute4-", "bgp-redistribute6-",
+ ]
+
@classmethod
def __parameters__(cls):
return [
@@ -81,8 +86,11 @@ def __init__(self, **kwargs):
@property
def neutron_router_id(self):
- if self.name is not None and (self.name.startswith('exp-') or self.name.startswith('pbr-')):
- return utils.vrf_id_to_uuid(self.name[4:])
+ if self.name:
+ for prefix in self.KNOWN_PREFIXES:
+ if self.name.startswith(prefix):
+ return utils.vrf_id_to_uuid(self.name[len(prefix):])
+ return None
def to_dict(self, context):
result = OrderedDict()
@@ -195,7 +203,7 @@ def to_dict(self, context):
entry[RouteMapConstants.IP] = {
RouteMapConstants.ADDRESS: {RouteMapConstants.PREFIX_LIST: self.prefix_list}}
if self.prefix_list_v6:
- entry[RouteMapConstants.IP] = {
+ entry[RouteMapConstants.IPV6] = {
RouteMapConstants.ADDRESS: {RouteMapConstants.PREFIX_LIST: self.prefix_list_v6}}
if self.access_list is not None:
diff --git a/asr1k_neutron_l3/models/neutron/l3/bgp.py b/asr1k_neutron_l3/models/neutron/l3/bgp.py
index 2688cc4a..3221ae33 100644
--- a/asr1k_neutron_l3/models/neutron/l3/bgp.py
+++ b/asr1k_neutron_l3/models/neutron/l3/bgp.py
@@ -25,14 +25,16 @@ class AddressFamilyBase(base.Base):
YANG_BGP_CLASS = None
YANG_BGP_NETWORK_CLASS = None
- def __init__(self, vrf, asn=None, has_routable_interface=False, rt_export=[],
- connected_cidrs=[], routable_networks=[], extra_routes=[]):
+ def __init__(self, vrf, asn=None, has_routable_interface=False, enable_af=True, rt_export=[],
+ redistribute_map=None, connected_cidrs=[], routable_networks=[], extra_routes=[]):
super().__init__()
self.vrf = utils.uuid_to_vrf_id(vrf)
+ self.enable_af = enable_af
self.has_routable_interface = has_routable_interface
self.asn = asn
self.enable_bgp = False
self.rt_export = rt_export
+ self.redistribute_map = redistribute_map
self.routable_networks = routable_networks
self.networks = set()
@@ -49,11 +51,13 @@ def __init__(self, vrf, asn=None, has_routable_interface=False, rt_export=[],
self.networks.add(net)
self.networks = list(self.networks)
- if self.has_routable_interface or len(self.rt_export) > 0:
+ if self.enable_af and (self.has_routable_interface or len(self.rt_export) > 0):
self.enable_bgp = True
self._rest_definition = self.YANG_BGP_CLASS(vrf=self.vrf, asn=self.asn, enable_bgp=self.enable_bgp,
- networks=self.networks)
+ networks=self.networks,
+ redistribute_static_with_rm=redistribute_map,
+ redistribute_connected_with_rm=redistribute_map)
def get(self):
return self.YANG_BGP_CLASS.get(self.vrf, asn=self.asn, enable_bgp=self.enable_bgp)
diff --git a/asr1k_neutron_l3/models/neutron/l3/interface.py b/asr1k_neutron_l3/models/neutron/l3/interface.py
index 72fa9c23..670ffcd6 100644
--- a/asr1k_neutron_l3/models/neutron/l3/interface.py
+++ b/asr1k_neutron_l3/models/neutron/l3/interface.py
@@ -52,59 +52,115 @@ def all_interfaces(self):
return result + self.internal_interfaces + self.orphaned_interfaces
+ def get_internal_cidrs(self, ip_version):
+ cidrs = []
+ for interface in self.internal_interfaces:
+ for subnet in interface.subnets:
+ if subnet.get('cidr') and utils.get_ip_version(subnet['cidr']) == ip_version:
+ cidrs.append(subnet['cidr'])
+ cidrs.sort()
+ return cidrs
+
+ def get_internal_cidrs_v4(self):
+ return self.get_internal_cidrs(4)
+
+ def get_internal_cidrs_v6(self):
+ return self.get_internal_cidrs(6)
+
+ def get_external_cidrs(self, ip_version):
+ if not self.gateway_interface:
+ return []
+
+ return [subnet['cidr']
+ for subnet in self.gateway_interface.subnets
+ if subnet.get('cidr') and utils.get_ip_version(subnet['cidr']) == ip_version]
+
+ def get_external_cidrs_v4(self):
+ return self.get_external_cidrs(4)
+
+ def get_external_cidrs_v6(self):
+ return self.get_external_cidrs(6)
+
+ def get_routable_networks(self, ip_version):
+ if not self.gateway_interface:
+ return []
+
+ # routable networks are networks for which the subnet's subnet pool address scope
+ # matches the address scope of the external gateway
+ scope_key = {4: "address_scope_v4", 6: "address_scope_v6"}[ip_version]
+
+ gw_scope_id = getattr(self.gateway_interface, scope_key, None)
+ if not gw_scope_id:
+ return []
+
+ subnets = []
+ for interface in self.internal_interfaces:
+ for subnet in interface.subnets:
+ if subnet['address_scope_id'] == gw_scope_id and utils.get_ip_version(subnet['cidr']) == ip_version:
+ subnets.append(subnet['cidr'])
+
+ return subnets
+
+ def get_routable_networks_v4(self):
+ return self.get_routable_networks(4)
+
+ def get_routable_networks_v6(self):
+ return self.get_routable_networks(6)
+
class Interface(base.Base):
def __init__(self, router_id, router_port, extra_atts):
- super(Interface, self).__init__()
+ super().__init__()
- self.router_id = router_id
self.router_port = router_port
- self.extra_atts = extra_atts
self.id = self.router_port.get('id')
- self.bridge_domain = utils.to_bridge_domain(extra_atts.get('second_dot1q'))
+ self.router_id = router_id
self.vrf = utils.uuid_to_vrf_id(self.router_id)
- self._primary_subnet_id = None
- self.ip_address = self._ip_address()
- self.has_stateful_firewall = False
- self.ipv6_addresses = self._ipv6_addresses()
+ self.extra_atts = extra_atts
+ self.bridge_domain = utils.to_bridge_domain(extra_atts.get('second_dot1q'))
- self.secondary_ip_addresses = []
- self.primary_subnet = self._primary_subnet()
- self.primary_gateway_ip = None
- if self.primary_subnet is not None:
- self.primary_gateway_ip = self.primary_subnet.get('gateway_ip')
+ self._primary_v4_subnet_id = None
+ self._primary_v6_subnet_id = None
+ self.gateway_ip_v4 = None
+ self.gateway_ip_v6 = None
+ self.address_scope_v4 = router_port.get('address_scopes', {}).get('4')
+ self.address_scope_v6 = router_port.get('address_scopes', {}).get('6')
+ self.secondary_ip_addresses = [] # NOTE(seba): I don't think this is being used
+ self.ipv4_address = self._ipv4_address()
+ self.ipv6_addresses = self._ipv6_addresses()
+ self._set_gateway_ips()
self.mac_address = utils.to_cisco_mac(self.router_port.get('mac_address'))
self.mtu = self.router_port.get('mtu')
- self.address_scope = router_port.get('address_scopes', {}).get('4')
+
+ self.has_stateful_firewall = False
def add_secondary_ip_address(self, ip_address, netmask):
self.secondary_ip_addresses.append(BDSecondaryIpAddress(address=ip_address,
mask=utils.to_netmask(netmask)))
- def _ip_address(self):
+ def _ipv4_address(self):
for n_fixed_ip in self.router_port.get('fixed_ips', []):
if utils.get_ip_version(n_fixed_ip['ip_address']) == 4:
- self._primary_subnet_id = n_fixed_ip.get('subnet_id')
-
+ self._primary_v4_subnet_id = n_fixed_ip.get('subnet_id')
return BDPrimaryIpAddress(address=n_fixed_ip.get('ip_address'),
mask=utils.to_netmask(n_fixed_ip.get('prefixlen')))
return None
def _ipv6_addresses(self):
ipv6_addrs = []
- for ip in self.router_port.get('fixed_ips', []):
- if utils.get_ip_version(ip['ip_address']) == 6:
- if self._primary_subnet_id is None:
- self._primary_subnet_id = ip.get('subnet_id')
- ipv6_addrs.append(BDIpv6Address(prefix=f"{ip['ip_address']}/{ip['prefixlen']}"))
+ for n_fixed_ip in self.router_port.get('fixed_ips', []):
+ if utils.get_ip_version(n_fixed_ip['ip_address']) == 6:
+ self._primary_v6_subnet_id = n_fixed_ip.get('subnet_id')
+ ipv6_addrs.append(BDIpv6Address(prefix=f"{n_fixed_ip['ip_address']}/{n_fixed_ip['prefixlen']}"))
return ipv6_addrs
- def _primary_subnet(self):
+ def _set_gateway_ips(self):
for subnet in self.router_port.get('subnets', []):
- if subnet.get('id') == self._primary_subnet_id:
- return subnet
- return None
+ if subnet['id'] == self._primary_v4_subnet_id:
+ self.gateway_ip_v4 = subnet['gateway_ip']
+ elif subnet['id'] == self._primary_v6_subnet_id:
+ self.gateway_ip_v6 = subnet['gateway_ip']
@property
def subnets(self):
@@ -126,33 +182,23 @@ def delete(self):
vbi = BDInterface(name=self.bridge_domain, vrf=self.vrf)
return vbi.delete()
- def disable_nat(self):
- vbi = BDInterface(name=self.bridge_domain)
- return vbi.disable_nat()
-
- def enable_nat(self):
- vbi = BDInterface(name=self.bridge_domain)
- return vbi.enable_nat()
-
class GatewayInterface(Interface):
def __init__(self, router_id, router_port, extra_atts, dynamic_nat_pool):
self.dynamic_nat_pool = dynamic_nat_pool
- super(GatewayInterface, self).__init__(router_id, router_port, extra_atts)
-
- self.nat_address = self._nat_address()
+ super().__init__(router_id, router_port, extra_atts)
# annotate details about the router to the interface description so this can be picked up by SNMP
@property
def _rest_definition(self):
description = (f'type:gw;router:{self.router_id};network:{self.router_port["network_id"]};'
- f'subnet:{self._primary_subnet_id}')
+ f'subnet:{self._primary_v4_subnet_id or self._primary_v6_subnet_id}')
interface_args = dict(name=self.bridge_domain, description=description,
mac_address=self.mac_address, mtu=self.mtu, vrf=self.vrf,
- ip_address=self.ip_address, ipv6_addresses=self.ipv6_addresses,
+ ip_address=self.ipv4_address, ipv6_addresses=self.ipv6_addresses,
secondary_ip_addresses=self.secondary_ip_addresses, nat_outside=True,
redundancy_group=None, route_map='EXT-TOS', access_group_out='EXT-TOS',
ntp_disable=True, arp_timeout=cfg.CONF.asr1k_l3.external_iface_arp_timeout)
@@ -165,9 +211,10 @@ def _rest_definition(self):
return BDInterface(**interface_args)
- def _ip_address(self):
+ def _ipv4_address(self):
+ # NOTE(seba): this method is only here to find the IPs not part of the dynamic nat pool
if self.dynamic_nat_pool is None or not self.router_port.get('fixed_ips'):
- return super()._ip_address()
+ return super()._ipv4_address()
ips, _ = self.dynamic_nat_pool.split("/")
start_ip, end_ip = ips.split("-")
@@ -185,24 +232,15 @@ def _ip_address(self):
self.vrf, self.dynamic_nat_pool)
return None
- self._primary_subnet_id = n_fixed_ip.get('subnet_id')
+ self._primary_v4_subnet_id = n_fixed_ip.get('subnet_id')
return BDPrimaryIpAddress(address=n_fixed_ip['ip_address'],
mask=utils.to_netmask(n_fixed_ip.get('prefixlen')))
- def _nat_address(self):
- ips = self.router_port.get('fixed_ips')
- if bool(ips):
- for ip in ips:
- address = ip.get('ip_address')
-
- if address != self.ip_address.address:
- return address
-
class InternalInterface(Interface):
def __init__(self, router_id, router_port, extra_atts, ingress_acl=None, egress_acl=None):
- super(InternalInterface, self).__init__(router_id, router_port, extra_atts)
+ super().__init__(router_id, router_port, extra_atts)
self.ingress_acl = ingress_acl
self.egress_acl = egress_acl
@@ -210,11 +248,12 @@ def __init__(self, router_id, router_port, extra_atts, ingress_acl=None, egress_
def _rest_definition(self):
# annotate details about the router to the interface description so this can be picked up by SNMP
description = (f'type:internal;project:{self.router_port["project_id"]};router:{self.router_id};'
- f'network:{self.router_port["network_id"]};subnet:{self._primary_subnet_id}')
+ f'network:{self.router_port["network_id"]};'
+ f'subnet:{self._primary_v4_subnet_id or self._primary_v6_subnet_id}')
interface_args = dict(name=self.bridge_domain, description=description,
mac_address=self.mac_address, mtu=self.mtu, vrf=self.vrf,
- ip_address=self.ip_address, ipv6_addresses=self.ipv6_addresses,
+ ip_address=self.ipv4_address, ipv6_addresses=self.ipv6_addresses,
secondary_ip_addresses=self.secondary_ip_addresses,
nat_inside=True, redundancy_group=None, route_map="pbr-{}".format(self.vrf),
ntp_disable=True,
@@ -233,7 +272,7 @@ def _rest_definition(self):
class OrphanedInterface(Interface):
def __init__(self, router_id, router_port, extra_atts):
- super(OrphanedInterface, self).__init__(router_id, router_port, extra_atts)
+ super().__init__(router_id, router_port, extra_atts)
def update(self):
return self.delete()
diff --git a/asr1k_neutron_l3/models/neutron/l3/nat.py b/asr1k_neutron_l3/models/neutron/l3/nat.py
index ac70983b..71f9247e 100644
--- a/asr1k_neutron_l3/models/neutron/l3/nat.py
+++ b/asr1k_neutron_l3/models/neutron/l3/nat.py
@@ -53,11 +53,9 @@ def _rest_definition(self):
class DynamicNAT(BaseNAT):
- def __init__(self, router_id, gateway_interface=None, interfaces=[], redundancy=None, mapping_id=None,
+ def __init__(self, router_id, gateway_interface=None, redundancy=None, mapping_id=None,
mode=asr1k_constants.SNAT_MODE_POOL, bridge_domain=None):
- super(DynamicNAT, self).__init__(router_id, gateway_interface, redundancy, mapping_id)
-
- self.interfaces = interfaces
+ super().__init__(router_id, gateway_interface, redundancy, mapping_id)
self.specific_acl = True
self.mode = mode
@@ -145,7 +143,7 @@ def __init__(self, router_id, floating_ip, gateway_interface, redundancy=None, m
self.floating_ip = floating_ip
self.local_ip = floating_ip.get("fixed_ip_address")
self.global_ip = floating_ip.get("floating_ip_address")
- self.global_ip_mask = gateway_interface.ip_address.mask
+ self.global_ip_mask = gateway_interface.ipv4_address.mask
self.bridge_domain = gateway_interface.bridge_domain
self.id = "{},{}".format(self.local_ip, self.global_ip)
self.mapping_id = utils.uuid_to_mapping_id(self.floating_ip.get('id'))
@@ -168,7 +166,7 @@ def get(self):
class ArpEntry(BaseNAT):
def __init__(self, router_id, ip, gateway_interface):
- super(ArpEntry, self).__init__(router_id, gateway_interface)
+ super().__init__(router_id, gateway_interface)
self.ip = ip
self.id = self.ip
diff --git a/asr1k_neutron_l3/models/neutron/l3/prefix.py b/asr1k_neutron_l3/models/neutron/l3/prefix.py
index a94ec260..69c3f3c8 100644
--- a/asr1k_neutron_l3/models/neutron/l3/prefix.py
+++ b/asr1k_neutron_l3/models/neutron/l3/prefix.py
@@ -13,8 +13,6 @@
# 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 operator import itemgetter, attrgetter
-
from asr1k_neutron_l3.models.neutron.l3 import base
from asr1k_neutron_l3.common import utils
@@ -22,48 +20,43 @@
class BasePrefix(base.Base):
- IP_VERSION = None
-
- def __init__(self, name_prefix, router_id, gateway_interface, internal_interfaces):
+ def __init__(self, name_prefix, router_id, prefixes, add_deny_if_empty=False):
self.vrf = utils.uuid_to_vrf_id(router_id)
- self.internal_interfaces = internal_interfaces
- self.gateway_interface = gateway_interface
- self.gateway_address_scope = None
- self.has_prefixes = False
+ self.prefixes = prefixes
+ self._rest_definition = self.PREFIX_MODEL(name=f"{name_prefix}-{self.vrf}")
- if self.gateway_interface is not None:
- self.gateway_address_scope = self.gateway_interface.address_scope
+ for n, pfx in enumerate(prefixes, 1):
+ self._rest_definition.add_seq(
+ self._make_seq(n * 10, pfx)
+ )
- self._rest_definition = self.PREFIX_MODEL(name="{}-{}".format(name_prefix, self.vrf))
+ if add_deny_if_empty and not prefixes:
+ self._rest_definition.add_seq(
+ # use 4242 as a likely not-used seq no
+ self._make_seq(4242, self.DEFAULT, action="deny")
+ )
def diff(self, should_be_none=False):
- return super().diff(should_be_none=not self.has_prefixes)
+ return super().diff(should_be_none=not self.prefixes)
+
+ def _make_seq(self, no, cidr, action="permit"):
+ return prefix.PrefixSeq(no=no, action=action, ip=cidr)
class BasePrefixV4(BasePrefix):
PREFIX_MODEL = prefix.PrefixV4
- IP_VERSION = 4
+ DEFAULT = "0.0.0.0/0"
class BasePrefixV6(BasePrefix):
PREFIX_MODEL = prefix.PrefixV6
- IP_VERSION = 6
+ DEFAULT = "::/0"
+# ext prefix, containing externally routed prefixes
class ExtPrefixMixIn:
- def __init__(self, router_id=None, gateway_interface=None, internal_interfaces=None):
- super().__init__(name_prefix='ext', router_id=router_id, gateway_interface=gateway_interface,
- internal_interfaces=internal_interfaces)
-
- if self.gateway_interface is not None:
- i = 1
- for subnet in sorted(self.gateway_interface.subnets, key=itemgetter('id')):
- if utils.get_ip_version(subnet.get('cidr')) != self.IP_VERSION:
- continue
- self.has_prefixes = True
- self._rest_definition.add_seq(
- prefix.PrefixSeq(no=i * 10, action="permit", ip=subnet.get('cidr')))
- i += 1
+ def __init__(self, *args, **kwargs):
+ super().__init__(name_prefix='ext', *args, **kwargs)
class ExtPrefixV4(ExtPrefixMixIn, BasePrefixV4):
@@ -74,35 +67,25 @@ class ExtPrefixV6(ExtPrefixMixIn, BasePrefixV6):
pass
+# snat prefixes, list containing everything that should not be snatted
class SnatPrefix(BasePrefixV4):
- def __init__(self, router_id=None, gateway_interface=None, internal_interfaces=None):
- super(SnatPrefix, self).__init__(name_prefix='snat', router_id=router_id, gateway_interface=gateway_interface,
- internal_interfaces=internal_interfaces)
-
- i = 1
- for interface in sorted(self.internal_interfaces, key=attrgetter('id')):
- for subnet in sorted(interface.subnets, key=itemgetter('id')):
- if utils.get_ip_version(subnet.get('cidr')) != self.IP_VERSION:
- continue
- self.has_prefixes = True
- self._rest_definition.add_seq(
- prefix.PrefixSeq(no=i * 10, action="permit", ip=subnet.get('cidr')))
- i += 1
-
-
-class RoutePrefix(BasePrefixV4):
- def __init__(self, router_id=None, gateway_interface=None, internal_interfaces=None):
- super(RoutePrefix, self).__init__(name_prefix='route', router_id=router_id, gateway_interface=gateway_interface,
- internal_interfaces=internal_interfaces)
-
- i = 1
- for interface in sorted(self.internal_interfaces, key=attrgetter('id')):
- for subnet in sorted(interface.subnets, key=itemgetter('id')):
- if utils.get_ip_version(subnet.get('cidr')) != self.IP_VERSION:
- continue
- self.has_prefixes = True
- cidr = subnet.get('cidr')
- permit_ge = utils.prefix_from_cidr(cidr) + 1
- self._rest_definition.add_seq(
- prefix.PrefixSeq(no=i * 10, action="permit", ip=cidr, ge=permit_ge))
- i += 1
+ def __init__(self, *args, **kwargs):
+ super().__init__(name_prefix='snat', *args, **kwargs)
+
+
+# everything that should be routed
+class RoutePrefixMixin:
+ def __init__(self, *args, **kwargs):
+ super().__init__(name_prefix='route', *args, **kwargs)
+
+ def _make_seq(self, no, cidr, action="permit"):
+ permit_ge = utils.prefix_from_cidr(cidr) + 1
+ return prefix.PrefixSeq(no=no, action=action, ip=cidr, ge=permit_ge)
+
+
+class RoutePrefixV4(RoutePrefixMixin, BasePrefixV4):
+ pass
+
+
+class RoutePrefixV6(RoutePrefixMixin, BasePrefixV6):
+ pass
diff --git a/asr1k_neutron_l3/models/neutron/l3/route_map.py b/asr1k_neutron_l3/models/neutron/l3/route_map.py
index ca4ab1a9..61d9d9e3 100644
--- a/asr1k_neutron_l3/models/neutron/l3/route_map.py
+++ b/asr1k_neutron_l3/models/neutron/l3/route_map.py
@@ -63,7 +63,7 @@ def get(self):
class PBRRouteMap(base.Base):
- def __init__(self, name, gateway_interface=None):
+ def __init__(self, name, has_gateway_interface=False):
super(PBRRouteMap, self).__init__()
self.vrf = utils.uuid_to_vrf_id(name)
@@ -71,9 +71,56 @@ def __init__(self, name, gateway_interface=None):
sequences = []
- if gateway_interface is not None:
+ if has_gateway_interface:
sequences.append(route_map.MapSequence(seq_no=15,
operation='permit',
ip_precedence='routine'))
self._rest_definition = route_map.RouteMap(name=self.name, seq=sequences)
+
+
+class RedistRouteMapBase(base.Base):
+ def __init__(self, router_id, routable_rt, extraroutes_rt, enabled):
+ super().__init__()
+
+ self.vrf = utils.uuid_to_vrf_id(router_id)
+ self.name = f"bgp-redistribute{self.IP_VERSION}-{self.vrf}"
+ self.enabled = enabled
+ routable_rt_list = [routable_rt] if routable_rt else None
+ extraroutes_rt_list = [extraroutes_rt] if extraroutes_rt else None
+
+ sequences = [
+ route_map.MapSequence(
+ seq_no=10, operation='permit', asn=routable_rt_list,
+ **{self.PREFIX_LIST_PARAM: f"routable{self.IP_VERSION}-{self.vrf}"}),
+ route_map.MapSequence(
+ seq_no=20, operation='permit', asn=extraroutes_rt_list,
+ **{self.PREFIX_LIST_PARAM: f"routable-extraroutes{self.IP_VERSION}-{self.vrf}"}),
+ route_map.MapSequence(
+ seq_no=30, operation='permit',
+ **{self.PREFIX_LIST_PARAM: f"internal{self.IP_VERSION}-{self.vrf}"}),
+ route_map.MapSequence(
+ seq_no=40, operation='permit',
+ **{self.PREFIX_LIST_PARAM: f"internal-extraroutes{self.IP_VERSION}-{self.vrf}"}),
+ ]
+
+ self._rest_definition = route_map.RouteMap(name=self.name, seq=sequences)
+
+ def diff(self, should_be_none=False):
+ return super().diff(should_be_none=not self.enabled)
+
+ def update(self):
+ if self.enabled:
+ return super().update()
+ else:
+ return self.delete()
+
+
+class Redist4RouteMap(RedistRouteMapBase):
+ IP_VERSION = "4"
+ PREFIX_LIST_PARAM = "prefix_list"
+
+
+class Redist6RouteMap(RedistRouteMapBase):
+ IP_VERSION = "6"
+ PREFIX_LIST_PARAM = "prefix_list_v6"
diff --git a/asr1k_neutron_l3/models/neutron/l3/router.py b/asr1k_neutron_l3/models/neutron/l3/router.py
index be0c1028..3ab41642 100644
--- a/asr1k_neutron_l3/models/neutron/l3/router.py
+++ b/asr1k_neutron_l3/models/neutron/l3/router.py
@@ -17,7 +17,7 @@
import os
from collections import defaultdict
-
+from oslo_config import cfg
from oslo_log import log as logging
from asr1k_neutron_l3.common import asr1k_constants as constants, utils
@@ -52,52 +52,50 @@ def __init__(self, router_info):
self.status = self.router_info.get('status')
self.gateway_interface = None
- self.router_id = self.router_info.get('id')
+ self.router_id = self.router_info['id']
self.interfaces = self._build_interfaces()
self.routes = self._build_routes()
self.enable_snat = False
self.routable_interface = False
if router_info.get('external_gateway_info') is not None:
self.enable_snat = router_info.get('external_gateway_info', {}).get('enable_snat', False)
- self.routable_interface = len(self.address_scope_matches()) > 0
+ self.routable_interface = bool(self.interfaces.get_routable_networks_v4() or
+ self.interfaces.get_routable_networks_v6())
description = self.router_info.get('description')
-
- if description is None or len(description) == 0:
- description = "Router {}".format(self.router_id)
+ if not description:
+ description = f"Router {self.router_id}"
# TODO : get rt's from config for router
address_scope_config = router_info.get(constants.ADDRESS_SCOPE_CONFIG, {})
- rt = None
- global_vrf_id = None
+ self.rt = None
+ self.global_vrf_id = None
if self.gateway_interface is not None:
- if self.gateway_interface.address_scope in address_scope_config:
- rt = address_scope_config[self.gateway_interface.address_scope]
- global_vrf_id = self._to_global_vrf_id(rt)
- elif self.gateway_interface.address_scope is not None:
+ # We excpect that the v4 and v6 adress scope is always from the same cloud VRF
+ gw_address_scope = self.gateway_interface.address_scope_v4 or self.gateway_interface.address_scope_v6
+ if gw_address_scope in address_scope_config:
+ self.rt = address_scope_config[gw_address_scope]
+ self.global_vrf_id = self._to_global_vrf_id(self.rt)
+ elif gw_address_scope:
LOG.error("Router %s has a gateway interface, but no address scope was found in config "
"(address scope of router: %s, available scopes: %s)",
- self.router_id, self.gateway_interface.address_scope, list(address_scope_config.keys()))
+ self.router_id, gw_address_scope, list(address_scope_config.keys()))
if not self.router_atts.get('rd'):
LOG.error("Router %s has no rd attached, configuration is likely to fail!",
- self.router_info.get('id'))
+ self.router_id)
- self.vrf = vrf.Vrf(self.router_info.get('id'), description=description, asn=self.config.asr1k_l3.fabric_asn,
+ self.vrf = vrf.Vrf(self.router_id, description=description, asn=self.config.asr1k_l3.fabric_asn,
rd=self.router_atts.get('rd'), routable_interface=self.routable_interface,
- rt_import=self.rt_import, rt_export=self.rt_export, global_vrf_id=global_vrf_id,
+ rt_import=self.rt_import, rt_export=self.rt_export, global_vrf_id=self.global_vrf_id,
enable_ipv6=self.enable_ipv6)
self.fwaas_conf, self.fwaas_external_policies = self._build_fwaas_conf()
+ self.route_maps = self._build_route_maps()
self.nat_acl = self._build_nat_acl()
- self.route_map = route_map.RouteMap(self.router_info.get('id'), rt=rt,
- routable_interface=self.routable_interface, enable_ipv6=self.enable_ipv6)
-
- self.pbr_route_map = route_map.PBRRouteMap(self.router_info.get('id'), gateway_interface=self.gateway_interface)
-
self.bgp_address_family = self._build_bgp_address_family()
self.dynamic_nat = self._build_dynamic_nat()
@@ -118,26 +116,12 @@ def _to_global_vrf_id(self, rt):
@property
def enable_ipv4(self):
- return any(iface.ip_address for iface in self.interfaces.all_interfaces)
+ return any(iface.ipv4_address for iface in self.interfaces.all_interfaces)
@property
def enable_ipv6(self):
return any(iface.ipv6_addresses for iface in self.interfaces.all_interfaces)
- def address_scope_matches(self):
- result = []
- if self.gateway_interface is not None:
- for interface in self.interfaces.internal_interfaces:
- if self.gateway_interface.address_scope is not None:
- if interface.address_scope == self.gateway_interface.address_scope:
- result.append(interface)
- result = sorted(result, key=lambda _iface: _iface.id)
- return result
-
- def get_routable_networks(self):
- return [iface.primary_subnet['cidr'] for iface in self.address_scope_matches()
- if iface.primary_subnet and 'cidr' in iface.primary_subnet]
-
def _get_fwaas_acls_by_port(self):
"""
This method retrieves the ACLs associated with each port for the router.
@@ -206,34 +190,31 @@ def _build_routes(self):
if self._route_has_connected_interface(r):
routes[ip_net.version].append(r)
+ # handle default routes
if self.gateway_interface is not None:
- if self.gateway_interface.primary_gateway_ip and not primary_overridden[4]:
+ if self.gateway_interface.gateway_ip_v4 and not primary_overridden[4]:
primary_route = route.RouteV4(self.router_id, "0.0.0.0/0",
- self.gateway_interface.primary_gateway_ip)
+ self.gateway_interface.gateway_ip_v4)
if self._route_has_connected_interface(primary_route):
routes[4].append(primary_route)
- if self.gateway_interface.ipv6_addresses and not primary_overridden[6]:
+ if self.gateway_interface.gateway_ip_v6 and not primary_overridden[6]:
primary_route = route.RouteV6(self.router_id, "::/0",
- sorted(self.gateway_interface.ipv6_addresses)[0])
+ self.gateway_interface.gateway_ip_v6)
if self._route_has_connected_interface(primary_route):
routes[6].append(primary_route)
return routes
def _build_nat_acl(self):
- acl = access_list.AccessList("NAT-{}".format(utils.uuid_to_vrf_id(self.router_id)))
+ acl = access_list.AccessList(f"NAT-{utils.uuid_to_vrf_id(self.router_id)}")
# Check address scope and deny any where internal interface matches external
- for interface in self.address_scope_matches():
- subnet = interface.primary_subnet
-
- # FIXME: filter for ipv6?
- if subnet is not None and subnet.get('cidr') is not None:
- ip, netmask = utils.from_cidr(subnet.get('cidr'))
- wildcard = utils.to_wildcard_mask(netmask)
- rule = access_list.Rule(action='deny', source=ip, source_mask=wildcard)
- acl.append_rule(rule)
+ for cidr in self.interfaces.get_routable_networks_v4():
+ ip, netmask = utils.from_cidr(cidr)
+ wildcard = utils.to_wildcard_mask(netmask)
+ rule = access_list.Rule(action='deny', source=ip, source_mask=wildcard)
+ acl.append_rule(rule)
if not self.enable_snat:
acl.append_rule(access_list.Rule(action='deny'))
@@ -242,59 +223,53 @@ def _build_nat_acl(self):
return acl
def _route_has_connected_interface(self, l3_route):
- gw_port = self.router_info.get('gw_port', None)
- if gw_port is not None:
-
- for subnet in gw_port.get('subnets'):
- if subnet and subnet.get('cidr') and utils.ip_in_network(l3_route.nexthop, subnet['cidr']):
- return True
-
- int_ports = self.router_info.get('_interfaces', [])
-
- for int_port in int_ports:
- for subnet in int_port.get('subnets', []):
- if subnet and subnet.get('cidr') and utils.ip_in_network(l3_route.nexthop, subnet['cidr']):
- return True
-
- return False
+ all_cidrs = (
+ self.interfaces.get_external_cidrs_v4() +
+ self.interfaces.get_external_cidrs_v6() +
+ self.interfaces.get_internal_cidrs_v4() +
+ self.interfaces.get_internal_cidrs_v6()
+ )
+ return any(utils.ip_in_network(l3_route.nexthop, cidr) for cidr in all_cidrs)
def _build_bgp_address_family(self):
- networks_v4 = [iface.primary_subnet['cidr'] for iface in self.interfaces.internal_interfaces
- if iface.primary_subnet and 'cidr' in iface.primary_subnet and
- utils.get_ip_version(iface.primary_subnet['cidr']) == 4]
- networks_v6 = [addr.prefix for iface in self.interfaces.internal_interfaces
- for addr in iface.ipv6_addresses]
- routable_networks_v4 = [iface.primary_subnet['cidr'] for iface in self.address_scope_matches()
- if iface.primary_subnet and 'cidr' in iface.primary_subnet and
- utils.get_ip_version(iface.primary_subnet['cidr']) == 4]
- routable_networks_v6 = [addr.prefix for iface in self.address_scope_matches()
- for addr in iface.ipv6_addresses]
-
- extra_routes_v4 = []
- extra_routes_v6 = []
- if self.router_info["bgpvpn_advertise_extra_routes"]:
- extra_routes_v4 = [x.cidr for x in self.routes[4].routes if x.cidr != "0.0.0.0/0"]
- extra_routes_v6 = [x.cidr for x in self.routes[6].routes if x.cidr != "::/0"]
-
- def_args = {
- "vrf": utils.uuid_to_vrf_id(self.router_id),
- "asn": self.config.asr1k_l3.fabric_asn,
- "has_routable_interface": self.routable_interface,
- "rt_export": self.rt_export,
- }
- return {
- 4: bgp.AddressFamilyV4(connected_cidrs=networks_v4, extra_routes=extra_routes_v4,
- routable_networks=routable_networks_v4, **def_args),
- 6: bgp.AddressFamilyV6(connected_cidrs=networks_v6, extra_routes=extra_routes_v6,
- routable_networks=routable_networks_v6, **def_args),
- }
+ bgp_afs = {}
+ for ip_version, BGPAddressFamily in ((4, bgp.AddressFamilyV4), (6, bgp.AddressFamilyV6)):
+ if ip_version == 6 or ip_version == 4 and cfg.CONF.asr1k_l3.advertise_bgp_ipv4_routes_via_redistribute:
+ # use redistribute static/connected
+ networks = []
+ routable_networks = []
+ has_routable_interface = bool(self.interfaces.get_routable_networks(ip_version))
+ redistribute_map = f"bgp-redistribute{ip_version}-{utils.uuid_to_vrf_id(self.router_id)}"
+ else:
+ # use network statements
+ networks = self.interfaces.get_internal_cidrs(ip_version)
+ routable_networks = self.interfaces.get_routable_networks(ip_version)
+ has_routable_interface = bool(routable_networks)
+ redistribute_map = None
+
+ enable_af = self.enable_ipv4 if ip_version == 4 else self.enable_ipv6
+
+ extra_routes = []
+ if self.router_info["bgpvpn_advertise_extra_routes"]:
+ extra_routes = [x.cidr for x in self.routes[ip_version].routes if x.cidr not in ("0.0.0.0/0", "::/0")]
+
+ bgp_afs[ip_version] = BGPAddressFamily(
+ vrf=utils.uuid_to_vrf_id(self.router_id),
+ asn=self.config.asr1k_l3.fabric_asn, rt_export=self.rt_export,
+ connected_cidrs=networks, extra_routes=extra_routes,
+ routable_networks=routable_networks,
+ has_routable_interface=has_routable_interface,
+ redistribute_map=redistribute_map,
+ enable_af=enable_af,
+ )
+ return bgp_afs
def _build_dynamic_nat(self):
pool_nat = nat.DynamicNAT(self.router_id, gateway_interface=self.gateway_interface,
- interfaces=self.interfaces, mode=constants.SNAT_MODE_POOL,
+ mode=constants.SNAT_MODE_POOL,
mapping_id=utils.uuid_to_mapping_id(self.router_id))
interface_nat = nat.DynamicNAT(self.router_id, gateway_interface=self.gateway_interface,
- interfaces=self.interfaces, mode=constants.SNAT_MODE_INTERFACE)
+ mode=constants.SNAT_MODE_INTERFACE)
return {constants.SNAT_MODE_POOL: pool_nat, constants.SNAT_MODE_INTERFACE: interface_nat}
@@ -336,18 +311,77 @@ def _build_prefix_lists(self):
result = []
# external interface
- result.append(prefix.ExtPrefixV4(router_id=self.router_id, gateway_interface=self.gateway_interface))
- result.append(prefix.ExtPrefixV6(router_id=self.router_id, gateway_interface=self.gateway_interface))
+ result.append(prefix.ExtPrefixV4(router_id=self.router_id, prefixes=self.interfaces.get_external_cidrs_v4()))
+ result.append(prefix.ExtPrefixV6(router_id=self.router_id, prefixes=self.interfaces.get_external_cidrs_v6()))
+
+ result.append(prefix.SnatPrefix(router_id=self.router_id, prefixes=self.interfaces.get_routable_networks_v4()))
+
+ result.append(prefix.RoutePrefixV4(router_id=self.router_id,
+ prefixes=self.interfaces.get_routable_networks_v4()))
+ result.append(prefix.RoutePrefixV6(router_id=self.router_id,
+ prefixes=self.interfaces.get_routable_networks_v6()))
+
+ # the new prefix lists
+ # routable -> all dapnets
+ # routable-extraroutes -> all extraroutes part of a dapnets
+ # internal -> everything internal
+ # extraroutes -> extraroutes that are not routable
+ for ip_version, PrefixClass in ((4, prefix.BasePrefixV4), (6, prefix.BasePrefixV6)):
+ if ip_version == 4 and not cfg.CONF.asr1k_l3.advertise_bgp_ipv4_routes_via_redistribute:
+ # we don't need the lists for v4 if they're not being used
+ # to avoid potential config locks, we won't configure them
+ continue
+
+ af_enabled = self.enable_ipv4 if ip_version == 4 else self.enable_ipv6
+ routable_networks = self.interfaces.get_routable_networks(ip_version)
+ internal_networks = self.interfaces.get_internal_cidrs(ip_version)
+ routable_extraroutes = []
+ internal_extraroutes = []
+ for extraroute in self.routes[ip_version].routes:
+ net = extraroute.cidr
+ if net in ("0.0.0.0/0", "::/0"):
+ continue
+
+ if any(utils.network_in_network(net, routable_network) for routable_network in routable_networks):
+ routable_extraroutes.append(net)
+ else:
+ internal_extraroutes.append(net)
+
+ result.extend([
+ PrefixClass(f"routable{ip_version}", self.router_id, routable_networks, add_deny_if_empty=af_enabled),
+ PrefixClass(f"routable-extraroutes{ip_version}", self.router_id, routable_extraroutes,
+ add_deny_if_empty=af_enabled),
+ PrefixClass(f"internal{ip_version}", self.router_id, internal_networks, add_deny_if_empty=af_enabled),
+ PrefixClass(f"internal-extraroutes{ip_version}", self.router_id, internal_extraroutes,
+ add_deny_if_empty=af_enabled),
+ ])
- no_snat_interfaces = self.address_scope_matches()
+ return result
- result.append(prefix.SnatPrefix(router_id=self.router_id, gateway_interface=self.gateway_interface,
- internal_interfaces=no_snat_interfaces))
+ def _build_route_maps(self):
+ extraroutes_rt = None
+ if self.rt and ':' in self.rt:
+ # 65126:106 --> 65126:1106
+ asn, sf = self.rt.split(":", 1)
+ extraroutes_rt = f"{asn}:1{sf}"
- result.append(prefix.RoutePrefix(router_id=self.router_id, gateway_interface=self.gateway_interface,
- internal_interfaces=no_snat_interfaces))
+ route_maps = [
+ # exp route-map
+ route_map.RouteMap(self.router_id, rt=self.rt,
+ routable_interface=self.routable_interface, enable_ipv6=self.enable_ipv6),
- return result
+ # pbr route-map
+ route_map.PBRRouteMap(self.router_id,
+ has_gateway_interface=bool(self.gateway_interface)),
+
+ # redistribute routemaps
+ route_map.Redist4RouteMap(self.router_id, self.rt, extraroutes_rt,
+ enabled=(self.enable_ipv4 and
+ cfg.CONF.asr1k_l3.advertise_bgp_ipv4_routes_via_redistribute)),
+ route_map.Redist6RouteMap(self.router_id, self.rt, extraroutes_rt, enabled=self.enable_ipv6),
+ ]
+
+ return route_maps
def _build_fwaas_conf(self):
router_info = self.router_info
@@ -420,18 +454,12 @@ def _update(self):
for prefix_list in self.prefix_lists:
results.append(prefix_list.update())
- results.append(self.route_map.update())
+ for rm in self.route_maps:
+ results.append(rm.update())
results.append(self.vrf.update())
- if self.gateway_interface is not None:
- results.append(self.pbr_route_map.update())
- else:
- results.append(self.pbr_route_map.delete())
-
- # results.append(self.bgp_address_family.update())
-
for ip_version in (4, 6):
- if (self.routable_interface or len(self.rt_export) > 0) and self.bgp_address_family[ip_version].networks:
+ if self.bgp_address_family[ip_version].enable_bgp:
results.append(self.bgp_address_family[ip_version].update())
else:
results.append(self.bgp_address_family[ip_version].delete())
@@ -501,7 +529,8 @@ def _delete(self):
results.append(prefix.ExtPrefixV6(router_id=self.router_id).delete())
results.append(prefix.RoutePrefix(router_id=self.router_id).delete())
- results.append(self.route_map.delete())
+ for rm in self.route_maps:
+ results.append(rm.delete())
results.append(self.floating_ips.delete())
results.append(self.arp_entries.delete())
results.append(self.routes[4].delete())
@@ -511,7 +540,6 @@ def _delete(self):
results.append(self.dynamic_nat.get(key).delete())
results.append(self.nat_pool.delete())
- results.append(self.pbr_route_map.delete())
results.append(self.nat_acl.delete())
results.append(self.bgp_address_family[4].delete())
results.append(self.bgp_address_family[6].delete())
@@ -551,14 +579,10 @@ def diff(self):
diff_results['prefix_list'] = []
diff_results['prefix_list'].append(prefix_diff.to_dict())
- rm_diff = self.route_map.diff()
- if not rm_diff.valid:
- diff_results['route_map'] = rm_diff.to_dict()
-
- if self.gateway_interface:
- pbr_rm_diff = self.pbr_route_map.diff()
- if not pbr_rm_diff.valid:
- diff_results['pbr_route_map'] = pbr_rm_diff.to_dict()
+ for rm in self.route_maps:
+ rm_diff = rm.diff()
+ if not rm_diff.valid:
+ diff_results[f'route_map-{rm.name}'] = rm_diff.to_dict()
for ip_version in (4, 6):
route_diff = self.routes[ip_version].diff()
diff --git a/asr1k_neutron_l3/models/neutron/l3/vrf.py b/asr1k_neutron_l3/models/neutron/l3/vrf.py
index d28aa3fc..3c19928f 100644
--- a/asr1k_neutron_l3/models/neutron/l3/vrf.py
+++ b/asr1k_neutron_l3/models/neutron/l3/vrf.py
@@ -34,6 +34,7 @@ def __init__(self, name, description=None, asn=None, rd=None, routable_interface
self.asn = asn
self.rd = utils.to_rd(self.asn, rd)
+ # FIXME: hmmm might also need to be AF aware, but... when do we have a routable interface?
if self.routable_interface:
self.map = f"{cfg.CONF.asr1k_l3.dapnet_rm_prefix}{global_vrf_id:02d}"
else:
@@ -45,7 +46,7 @@ def __init__(self, name, description=None, asn=None, rd=None, routable_interface
self.map_v6 = None
self.enable_ipv6 = enable_ipv6
if enable_ipv6:
- self.map_v6 = f"exp-v6-{self.name}"
+ self.map_v6 = f"bgp-redistribute6-{self.name}"
self._rest_definition = vrf.VrfDefinition(name=self.name, description=self.description,
rd=self.rd, map=self.map, map_v6=self.map_v6,
diff --git a/asr1k_neutron_l3/tests/unit/models/netconf_yang/test_parsing.py b/asr1k_neutron_l3/tests/unit/models/netconf_yang/test_parsing.py
index 293fb3b3..5a04c010 100644
--- a/asr1k_neutron_l3/tests/unit/models/netconf_yang/test_parsing.py
+++ b/asr1k_neutron_l3/tests/unit/models/netconf_yang/test_parsing.py
@@ -218,6 +218,95 @@ def test_bgp_parsing(self):
parsed_netmasks.add((network['number'], network['mask']))
self.assertEqual(orig_netmasks, parsed_netmasks)
+ def test_bgp_redistribute_parsing(self):
+ xml_v4_redist_with_rm = """
+
+
+
+
+
+ 65148
+
+
+
+ unicast
+
+ seagull-vrf
+
+
+
+ test123
+
+
+
+ test456
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+
+ xml_v6_redist_with_rm = """
+
+
+
+
+
+ 65148
+
+
+
+ unicast
+
+ seagull-vrf
+
+
+
+ test123
+
+
+ test456
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ context = FakeASR1KContext()
+ bgp_af4 = bgp.AddressFamilyV4.from_xml(xml_v4_redist_with_rm, context)
+ self.assertEqual(bgp_af4.redistribute_connected_with_rm, "test123")
+ self.assertEqual(bgp_af4.redistribute_static_with_rm, "test456")
+
+ bgp_af6 = bgp.AddressFamilyV6.from_xml(xml_v6_redist_with_rm, context)
+ self.assertEqual(bgp_af6.redistribute_connected_with_rm, "test123")
+ self.assertEqual(bgp_af6.redistribute_static_with_rm, "test456")
+
+ # back to xml
+ bgp_af4_dict = bgp_af4.to_dict(context)
+ self.assertEqual("test123", bgp_af4_dict['vrf']['ipv4-unicast']['redistribute-vrf']['connected']['route-map'])
+ self.assertEqual("test456",
+ bgp_af4_dict['vrf']['ipv4-unicast']['redistribute-vrf']['static']['default']['route-map'])
+
+ bgp_af6_dict = bgp_af6.to_dict(context)
+ self.assertEqual("test123", bgp_af6_dict['vrf']['ipv6-unicast']['redistribute-v6']['connected']['route-map'])
+ self.assertEqual("test456", bgp_af6_dict['vrf']['ipv6-unicast']['redistribute-v6']['static']['route-map'])
+
def test_static_nat_parsing(self):
xml = """