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 = """