Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use route-maps instead of network for BGP prefixes #114

Draft
wants to merge 2 commits into
base: stable/yoga-m3
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions asr1k_neutron_l3/common/config.py
Original file line number Diff line number Diff line change
@@ -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,
66 changes: 35 additions & 31 deletions asr1k_neutron_l3/models/netconf_yang/bgp.py
Original file line number Diff line number Diff line change
@@ -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()
10 changes: 4 additions & 6 deletions asr1k_neutron_l3/models/netconf_yang/prefix.py
Original file line number Diff line number Diff line change
@@ -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:
35 changes: 24 additions & 11 deletions asr1k_neutron_l3/models/netconf_yang/route_map.py
Original file line number Diff line number Diff line change
@@ -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
6 changes: 4 additions & 2 deletions asr1k_neutron_l3/models/neutron/l3/bgp.py
Original file line number Diff line number Diff line change
@@ -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)
26 changes: 25 additions & 1 deletion asr1k_neutron_l3/models/neutron/l3/prefix.py
Original file line number Diff line number Diff line change
@@ -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"
28 changes: 27 additions & 1 deletion asr1k_neutron_l3/models/neutron/l3/route_map.py
Original file line number Diff line number Diff line change
@@ -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)
44 changes: 37 additions & 7 deletions asr1k_neutron_l3/models/neutron/l3/router.py
Original file line number Diff line number Diff line change
@@ -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()
39 changes: 38 additions & 1 deletion asr1k_neutron_l3/tests/unit/models/netconf_yang/test_parsing.py
Original file line number Diff line number Diff line change
@@ -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):
</with-mask>
</network>
<redistribute-vrf>
<connected/>
<connected>
<route-map>stonechat</route-map>
</connected>
<static/>
</redistribute-vrf>
</ipv4-unicast>
@@ -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 = """
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"
message-id="urn:uuid:9caf3918-3eb9-4d0e-a8a5-5ec268e3bf97">
<data>
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<route-map>
<name>RM-DAP-EXTRA-ROUTES</name>
<route-map-without-order-seq xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-route-map">
<seq_no>10</seq_no>
<operation>permit</operation>
<set>
<community>
<community-well-known>
<community-list>65126</community-list>
<community-list>4268097541</community-list>
</community-well-known>
</community>
</set>
</route-map-without-order-seq>
</route-map>
</native>
</data>
</rpc-reply>"""
context = FakeASR1KContext()
rm = RouteMap.from_xml(xml, context)

self.assertEqual(["65126", "4268097541"], rm.seq[0].community_list)