diff --git a/asr1k_neutron_l3/common/config.py b/asr1k_neutron_l3/common/config.py
index b6ffdc0e..ca13adf5 100644
--- a/asr1k_neutron_l3/common/config.py
+++ b/asr1k_neutron_l3/common/config.py
@@ -83,6 +83,11 @@
help="Route-Map to apply to all DAPNet BGP network statements"),
cfg.StrOpt('dapnet_extra_routes_rm', default='RM-DAP-EXTRA-ROUTES',
help="Route-Map to apply to all BGP network statements for extra routes that are contained in a DAPNet"),
+ # FIXME: move defaults away from here
+ cfg.ListOpt('dapn_routable_nets_communities', default=["65126"],
+ help="Communities to assign to DAPNets (via redistribute statement)"),
+ cfg.ListOpt('dapn_extra_routes_communities', default=["65126", "4268097541"],
+ help="Communities to assign to DAPNet extraroutes (via redistribute statement)"),
cfg.IntOpt('external_iface_arp_timeout', default=1800,
help="Set ARP timeout for the external interface of a router. Set to 0 to not set this attribute"),
cfg.IntOpt('internal_iface_arp_timeout', default=0,
diff --git a/asr1k_neutron_l3/models/netconf_yang/bgp.py b/asr1k_neutron_l3/models/netconf_yang/bgp.py
index 7ed4705b..c4ac523d 100644
--- a/asr1k_neutron_l3/models/netconf_yang/bgp.py
+++ b/asr1k_neutron_l3/models/netconf_yang/bgp.py
@@ -38,6 +38,7 @@ class BGPConstants(object):
REDISTRIBUTE_VRF = "redistribute-vrf"
CONNECTED = "connected"
STATIC = "static"
+ DEFAULT = "default"
UNICAST = "unicast"
NETWORK = "network"
WITH_MASK = "with-mask"
@@ -82,14 +83,14 @@ def __parameters__(cls):
return [
{'key': 'asn', 'id': True, 'yang-key': 'id'},
{'key': 'vrf', 'yang-key': 'name'},
- {'key': 'connected', 'yang-path': 'ipv4-unicast/redistribute', 'default': False,
- 'yang-type': YANG_TYPE.EMPTY},
- {'key': 'static', 'yang-path': 'ipv4-unicast/redistribute', 'default': False,
- 'yang-type': YANG_TYPE.EMPTY},
{'key': 'connected', 'yang-path': 'ipv4-unicast/redistribute-vrf', 'default': False,
'yang-type': YANG_TYPE.EMPTY},
+ {'key': 'connected_with_rm', 'yang-key': 'route-map',
+ 'yang-path': 'ipv4-unicast/redistribute-vrf/connected'},
{'key': 'static', 'yang-path': 'ipv4-unicast/redistribute-vrf', 'default': False,
'yang-type': YANG_TYPE.EMPTY},
+ {'key': 'static_with_rm', 'yang-key': 'route-map',
+ 'yang-path': 'ipv4-unicast/redistribute-vrf/default/static'},
{'key': 'networks_v4', 'yang-path': 'ipv4-unicast/network', 'yang-key': BGPConstants.WITH_MASK,
'type': [Network], 'default': []},
]
@@ -152,34 +153,37 @@ def __init__(self, **kwargs):
self.asn = kwargs.get("asn", None)
def to_dict(self, context):
- result = OrderedDict()
- if self.vrf is not None:
- vrf = OrderedDict()
- vrf[BGPConstants.NAME] = self.vrf
- vrf[BGPConstants.IPV4_UNICAST] = {
- xml_utils.OPERATION: NC_OPERATION.PUT,
- }
+ if self.vrf is None:
+ return {}
- REDIST_CONST = BGPConstants.REDISTRIBUTE_VRF if context.version_min_17_3 else BGPConstants.REDISTRIBUTE
-
- # redistribute connected/static is only used with 16.9
- if self.from_device or not context.version_min_17_3:
- if self.connected or self.static:
- vrf[BGPConstants.IPV4_UNICAST][REDIST_CONST] = {}
- if self.connected:
- vrf[BGPConstants.IPV4_UNICAST][REDIST_CONST][BGPConstants.CONNECTED] = ''
- if self.static:
- vrf[BGPConstants.IPV4_UNICAST][REDIST_CONST][BGPConstants.STATIC] = ''
-
- # networks are currently only announced with 17.4
- if self.networks_v4 and (self.from_device or context.version_min_17_3):
- vrf[BGPConstants.IPV4_UNICAST][BGPConstants.NETWORK] = {
- BGPConstants.WITH_MASK: [
- net.to_dict(context) for net in sorted(self.networks_v4, key=lambda x: (x.number, x.mask))
- ]
- }
- result[BGPConstants.VRF] = vrf
- return dict(result)
+ result = {}
+
+ vrf = {}
+ vrf[BGPConstants.NAME] = self.vrf
+ vrf[BGPConstants.IPV4_UNICAST] = {
+ xml_utils.OPERATION: NC_OPERATION.PUT,
+ }
+
+ if self.connected or self.connected_with_rm or self.static or self.static_with_rm:
+ redist = vrf[BGPConstants.IPV4_UNICAST].setdefault(BGPConstants.REDISTRIBUTE_VRF, {})
+ if self.connected_with_rm:
+ redist[BGPConstants.CONNECTED] = {BGPConstants.ROUTE_MAP: self.connected_with_rm}
+ elif self.connected:
+ redist[BGPConstants.CONNECTED] = ''
+
+ if self.static_with_rm:
+ redist[BGPConstants.STATIC] = {BGPConstants.DEFAULT: {BGPConstants.ROUTE_MAP: self.static_with_rm}}
+ elif self.static:
+ redist[BGPConstants.STATIC] = ''
+
+ if self.networks_v4:
+ vrf[BGPConstants.IPV4_UNICAST][BGPConstants.NETWORK] = {
+ BGPConstants.WITH_MASK: [
+ net.to_dict(context) for net in sorted(self.networks_v4, key=lambda x: (x.number, x.mask))
+ ]
+ }
+ result[BGPConstants.VRF] = vrf
+ return result
def to_delete_dict(self, context):
result = OrderedDict()
diff --git a/asr1k_neutron_l3/models/netconf_yang/prefix.py b/asr1k_neutron_l3/models/netconf_yang/prefix.py
index 6bd9381f..a770a83f 100644
--- a/asr1k_neutron_l3/models/netconf_yang/prefix.py
+++ b/asr1k_neutron_l3/models/netconf_yang/prefix.py
@@ -92,12 +92,10 @@ def __init__(self, **kwargs):
@property
def neutron_router_id(self):
- if self.name is not None and self.name.startswith('ext-'):
- return utils.vrf_id_to_uuid(self.name[4:])
- elif self.name is not None and self.name.startswith('snat-'):
- return utils.vrf_id_to_uuid(self.name[5:])
- elif self.name is not None and self.name.startswith('route-'):
- return utils.vrf_id_to_uuid(self.name[6:])
+ for prefix in 'ext-', 'snat-', 'route-', 'BGPVPN-', 'ROUTABLEINT-', 'ROUTABLEEXTRA-':
+ if self.name and self.name.startswith(prefix):
+ return utils.vrf_id_to_uuid(self.name[len(prefix):])
+ return None
def add_seq(self, seq):
if seq.no is None:
diff --git a/asr1k_neutron_l3/models/netconf_yang/route_map.py b/asr1k_neutron_l3/models/netconf_yang/route_map.py
index 2f520347..24fc26ca 100644
--- a/asr1k_neutron_l3/models/netconf_yang/route_map.py
+++ b/asr1k_neutron_l3/models/netconf_yang/route_map.py
@@ -29,6 +29,9 @@ class RouteMapConstants(object):
OPERATION = "operation"
SET = "set"
EXTCOMMUNITY = 'extcommunity'
+ COMMUNITY = 'community'
+ COMMUNITY_WELL_KNOWN = 'community-well-known'
+ COMMUNITY_LIST = 'community-list'
RT = 'rt'
RANGE = 'range'
ADDITIVE = 'additive'
@@ -128,6 +131,8 @@ def __parameters__(cls):
{'key': 'prefix_list', 'yang-key': 'prefix-list', 'yang-path': 'match/ip/address'},
{'key': 'access_list', 'yang-key': 'access-list', 'yang-path': 'match/ip/address'},
{'key': 'ip_precedence', 'yang-path': 'set/ip/precedence', 'yang-key': 'precedence-fields'},
+ {'key': 'community_list', 'yang-key': 'community-list', 'yang-path': 'set/community/community-well-known',
+ 'default': []},
{'key': 'drop_on_17_3', 'default': False},
]
@@ -158,24 +163,26 @@ def to_dict(self, context):
seq[RouteMapConstants.OPERATION] = self.operation
if self.asn:
- seq[RouteMapConstants.SET] = {
- RouteMapConstants.EXTCOMMUNITY: {RouteMapConstants.RT: {RouteMapConstants.ASN: self.asn}}}
+ seq.setdefault(RouteMapConstants.SET, {})
+ seq[RouteMapConstants.SET][RouteMapConstants.EXTCOMMUNITY] = {
+ RouteMapConstants.RT: {RouteMapConstants.ASN: self.asn},
+ }
if self.next_hop is not None:
+ seq.setdefault(RouteMapConstants.SET, {})
if context.version_min_17_3:
- seq[RouteMapConstants.SET] = {
- RouteMapConstants.IP: {RouteMapConstants.NEXT_HOP: {RouteMapConstants.ADDRESS: [self.next_hop]}}
+ seq[RouteMapConstants.SET][RouteMapConstants.IP] = {
+ RouteMapConstants.NEXT_HOP: {RouteMapConstants.ADDRESS: [self.next_hop]},
}
if self.force:
# it looks like the force flag is now part of the address list in 17.3+
seq[RouteMapConstants.SET][RouteMapConstants.IP][RouteMapConstants.NEXT_HOP][
RouteMapConstants.ADDRESS].append(RouteMapConstants.FORCE)
else:
- seq[RouteMapConstants.SET] = {
- RouteMapConstants.IP: {
- RouteMapConstants.NEXT_HOP: {
- RouteMapConstants.NEXT_HOP_ADDR: {
- RouteMapConstants.ADDRESS: self.next_hop}}}}
+ seq[RouteMapConstants.SET][RouteMapConstants.IP] = {
+ RouteMapConstants.NEXT_HOP: {
+ RouteMapConstants.NEXT_HOP_ADDR: {
+ RouteMapConstants.ADDRESS: self.next_hop}}}
if self.force:
seq[RouteMapConstants.SET][RouteMapConstants.IP][RouteMapConstants.NEXT_HOP][
RouteMapConstants.NEXT_HOP_ADDR][RouteMapConstants.FORCE] = ""
@@ -188,13 +195,19 @@ def to_dict(self, context):
seq[RouteMapConstants.MATCH] = {
RouteMapConstants.IP: {RouteMapConstants.ADDRESS: {RouteMapConstants.ACCESS_LIST: self.access_list}}}
if self.ip_precedence:
- if RouteMapConstants.SET not in seq:
- seq[RouteMapConstants.SET] = {}
+ seq.setdefault(RouteMapConstants.SET, {})
if RouteMapConstants.IP not in seq[RouteMapConstants.SET]:
seq[RouteMapConstants.SET][RouteMapConstants.IP] = {}
seq[RouteMapConstants.SET][RouteMapConstants.IP][RouteMapConstants.PRECEDENCE] = {
RouteMapConstants.PRECEDENCE_FIELDS: self.ip_precedence,
}
+ if self.community_list:
+ seq.setdefault(RouteMapConstants.SET, {})
+ seq[RouteMapConstants.SET][RouteMapConstants.COMMUNITY] = {
+ RouteMapConstants.COMMUNITY_WELL_KNOWN: {
+ RouteMapConstants.COMMUNITY_LIST: self.community_list,
+ },
+ }
seq[xml_utils.NS] = xml_utils.NS_CISCO_ROUTE_MAP
return seq
diff --git a/asr1k_neutron_l3/models/neutron/l3/bgp.py b/asr1k_neutron_l3/models/neutron/l3/bgp.py
index 8521320a..d309157f 100644
--- a/asr1k_neutron_l3/models/neutron/l3/bgp.py
+++ b/asr1k_neutron_l3/models/neutron/l3/bgp.py
@@ -23,7 +23,8 @@
class AddressFamily(base.Base):
def __init__(self, vrf, asn=None, routable_interface=False, rt_export=[],
- connected_cidrs=[], routable_networks=[], extra_routes=[]):
+ connected_cidrs=[], routable_networks=[], extra_routes=[],
+ redist_rm=None):
super(AddressFamily, self).__init__()
self.vrf = utils.uuid_to_vrf_id(vrf)
self.routable_interface = routable_interface
@@ -50,7 +51,8 @@ def __init__(self, vrf, asn=None, routable_interface=False, rt_export=[],
self.enable_bgp = True
self._rest_definition = bgp.AddressFamily(vrf=self.vrf, asn=self.asn, enable_bgp=self.enable_bgp,
- static=True, connected=True, networks_v4=self.networks_v4)
+ networks_v4=self.networks_v4,
+ static_with_rm=redist_rm, connected_with_rm=redist_rm)
def get(self):
return bgp.AddressFamily.get(self.vrf, asn=self.asn, enable_bgp=self.enable_bgp)
diff --git a/asr1k_neutron_l3/models/neutron/l3/prefix.py b/asr1k_neutron_l3/models/neutron/l3/prefix.py
index 9d887a5e..27a888dd 100644
--- a/asr1k_neutron_l3/models/neutron/l3/prefix.py
+++ b/asr1k_neutron_l3/models/neutron/l3/prefix.py
@@ -24,6 +24,7 @@
class BasePrefix(base.Base):
def __init__(self, name_prefix, router_id, gateway_interface, internal_interfaces):
self.vrf = utils.uuid_to_vrf_id(router_id)
+ self.list_name = f"{name_prefix}-{self.vrf}"
self.internal_interfaces = internal_interfaces
self.gateway_interface = gateway_interface
self.gateway_address_scope = None
@@ -32,7 +33,7 @@ def __init__(self, name_prefix, router_id, gateway_interface, internal_interface
if self.gateway_interface is not None:
self.gateway_address_scope = self.gateway_interface.address_scope
- self._rest_definition = prefix.Prefix(name="{}-{}".format(name_prefix, self.vrf))
+ self._rest_definition = prefix.Prefix(name=self.list_name)
class ExtPrefix(BasePrefix):
@@ -74,3 +75,26 @@ def __init__(self, router_id=None, gateway_interface=None, internal_interfaces=N
permit_ge = utils.prefix_from_cidr(cidr) + 1
self._rest_definition.add_seq(prefix.PrefixSeq(no=i * 10, permit_ip=cidr, permit_ge=permit_ge))
i += 1
+
+
+class BasePrefixWithPrefixes(BasePrefix):
+ PREFIX_NAME = None
+
+ def __init__(self, router_id, prefixes):
+ super().__init__(self.PREFIX_NAME, router_id, None, None)
+
+ self.has_prefixes = bool(prefixes)
+ for n, cidr in enumerate(sorted(prefixes), 1):
+ self._rest_definition.add_seq(prefix.PrefixSeq(no=n * 10, permit_ip=cidr))
+
+
+class BgpvpnPrefixes(BasePrefixWithPrefixes):
+ PREFIX_NAME = "BGPVPN"
+
+
+class RoutableInternalPrefixes(BasePrefixWithPrefixes):
+ PREFIX_NAME = "ROUTABLEINT"
+
+
+class RoutableExtraPrefixes(BasePrefixWithPrefixes):
+ PREFIX_NAME = "ROUTABLEEXTRA"
diff --git a/asr1k_neutron_l3/models/neutron/l3/route_map.py b/asr1k_neutron_l3/models/neutron/l3/route_map.py
index 45e8bf8a..5d368321 100644
--- a/asr1k_neutron_l3/models/neutron/l3/route_map.py
+++ b/asr1k_neutron_l3/models/neutron/l3/route_map.py
@@ -13,9 +13,11 @@
# 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 oslo_config import cfg
+
from asr1k_neutron_l3.common import utils
from asr1k_neutron_l3.models.netconf_yang import route_map
-from asr1k_neutron_l3.models.neutron.l3 import base
+from asr1k_neutron_l3.models.neutron.l3 import base, prefix
class RouteMap(base.Base):
@@ -82,3 +84,27 @@ def __init__(self, name, gateway_interface=None):
ip_precedence='routine'))
self._rest_definition = route_map.RouteMap(name=self.name, seq=sequences)
+
+
+class BgpvpnRedistRouteMap(base.Base):
+ def __init__(self, router_id):
+ super().__init__()
+
+ self.vrf = utils.uuid_to_vrf_id(router_id)
+ self.name = "BGPVPNREDIST-{}".format(self.vrf)
+
+ sequences = [
+ route_map.MapSequence(seq_no=10,
+ operation='permit',
+ community_list=cfg.CONF.asr1k_l3.dapn_routable_nets_communities,
+ access_list=f"{prefix.RoutableInternalPrefixes.PREFIX_NAME}-{self.vrf}"),
+ route_map.MapSequence(seq_no=20,
+ operation='permit',
+ community_list=cfg.CONF.asr1k_l3.dapn_extra_routes_communities,
+ access_list=f"{prefix.RoutableExtraPrefixes.PREFIX_NAME}-{self.vrf}"),
+ route_map.MapSequence(seq_no=30,
+ operation='permit',
+ access_list=f"{prefix.BgpvpnPrefixes.PREFIX_NAME}-{self.vrf}"),
+ ]
+
+ self._rest_definition = route_map.RouteMap(name=self.name, seq=sequences)
diff --git a/asr1k_neutron_l3/models/neutron/l3/router.py b/asr1k_neutron_l3/models/neutron/l3/router.py
index 603f0296..bf50c322 100644
--- a/asr1k_neutron_l3/models/neutron/l3/router.py
+++ b/asr1k_neutron_l3/models/neutron/l3/router.py
@@ -94,6 +94,7 @@ def __init__(self, router_info):
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.bgpvpn_extras = self._build_bgpvpn_extras()
self.dynamic_nat = self._build_dynamic_nat()
self.nat_pool = self._build_nat_pool()
@@ -222,16 +223,37 @@ def _route_has_connected_interface(self, l3_route):
return False
def _build_bgp_address_family(self):
- connected_cidrs = self.get_internal_cidrs()
- extra_routes = list()
- if self.router_info["bgpvpn_advertise_extra_routes"]:
- extra_routes = [x.cidr for x in self.routes.routes if x.cidr != "0.0.0.0/0"]
-
return bgp.AddressFamily(self.router_info.get('id'), asn=self.config.asr1k_l3.fabric_asn,
routable_interface=self.routable_interface,
- rt_export=self.rt_export, connected_cidrs=connected_cidrs,
+ rt_export=self.rt_export, connected_cidrs=[],
routable_networks=self.get_routable_networks(),
- extra_routes=extra_routes)
+ extra_routes=[],
+ redist_rm="BGPVPNREDIST-{}".format(utils.uuid_to_vrf_id(self.router_id)))
+
+ def _build_bgpvpn_extras(self):
+ extra_routes = []
+ if self.router_info["bgpvpn_advertise_extra_routes"]:
+ extra_routes = [x.cidr for x in self.routes.routes if x.cidr != "0.0.0.0/0"]
+
+ routable_internal = []
+ routable_extra = []
+ bgpvpn_cidrs = []
+ for cidr in self.get_internal_cidrs() + extra_routes:
+ if any(utils.network_in_network(cidr, rn) for rn in self.get_routable_networks()):
+ if cidr in extra_routes:
+ routable_extra.append(cidr)
+ else:
+ routable_internal.append(cidr)
+ else:
+ bgpvpn_cidrs.append(cidr)
+
+ return [
+ prefix.RoutableInternalPrefixes(self.router_id, routable_internal),
+ prefix.RoutableExtraPrefixes(self.router_id, routable_extra),
+ prefix.BgpvpnPrefixes(self.router_id, bgpvpn_cidrs),
+
+ route_map.BgpvpnRedistRouteMap(self.router_id),
+ ]
def _build_dynamic_nat(self):
pool_nat = nat.DynamicNAT(self.router_id, gateway_interface=self.gateway_interface,
@@ -347,8 +369,10 @@ def _update(self):
if self.routable_interface or len(self.rt_export) > 0:
results.append(self.bgp_address_family.update())
+ results.extend(obj.update() for obj in self.bgpvpn_extras)
else:
results.append(self.bgp_address_family.delete())
+ results.extend(obj.delete() for obj in self.bgpvpn_extras)
if self.nat_acl:
results.append(self.nat_acl.update())
@@ -416,6 +440,7 @@ def _delete(self):
results.append(self.nat_acl.delete())
results.append(self.pbr_acl.delete())
results.append(self.bgp_address_family.delete())
+ results.extend(obj.delete() for obj in self.bgpvpn_extras)
for interface in self.interfaces.all_interfaces:
results.append(interface.delete())
@@ -437,6 +462,11 @@ def diff(self):
if not bgp_diff.valid:
diff_results['bgp'] = bgp_diff.to_dict()
+ for obj in self.bgpvpn_extras:
+ obj_diff = obj.diff()
+ if not obj_diff.valid:
+ diff_results.setdefault('bgpvpn_extras', []).append(obj_diff.to_dict())
+
for prefix_list in self.prefix_lists:
if prefix_list.internal_interfaces is not None and len(prefix_list.internal_interfaces) > 0:
prefix_diff = prefix_list.diff()
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 a6788793..4f819564 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
@@ -20,6 +20,7 @@
from asr1k_neutron_l3.models.netconf_yang.l2_interface import BridgeDomain
from asr1k_neutron_l3.models.netconf_yang.vrf import VrfDefinition
from asr1k_neutron_l3.models.netconf_yang.nat import StaticNatList
+from asr1k_neutron_l3.models.netconf_yang.route_map import RouteMap
class ParsingTest(base.BaseTestCase):
@@ -246,7 +247,9 @@ def test_bgp_parsing(self):
-
+
+ stonechat
+
@@ -266,6 +269,10 @@ def test_bgp_parsing(self):
context = FakeASR1KContext()
bgp_af = bgp.AddressFamily.from_xml(bgp_xml, context)
+ self.assertTrue(bgp_af.connected)
+ self.assertEqual("stonechat", bgp_af.connected_with_rm)
+ self.assertTrue(bgp_af.static)
+ self.assertIsNone(bgp_af.static_with_rm)
parsed_cidrs = {net.cidr for net in bgp_af.networks_v4}
self.assertEqual(orig_cidrs, parsed_cidrs)
for network in bgp_af.networks_v4:
@@ -408,3 +415,33 @@ def test_parse_nat_garp_flag(self):
snl = StaticNatList.from_xml(xml, context)
nat = snl.static_nats[0]
self.assertEqual('6657', nat.garp_bdvif_iface)
+
+ def test_parse_route_map_community_set(self):
+ xml = """
+
+
+
+
+ RM-DAP-EXTRA-ROUTES
+
+ 10
+ permit
+
+
+
+ 65126
+ 4268097541
+
+
+
+
+
+
+
+
+"""
+ context = FakeASR1KContext()
+ rm = RouteMap.from_xml(xml, context)
+
+ self.assertEqual(["65126", "4268097541"], rm.seq[0].community_list)