1# Copyright (C) 2014 Nippon Telegraph and Telephone Corporation. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12# implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16""" 17 Defines base data types and models required specifically for VRF support. 18""" 19 20import abc 21import logging 22import six 23 24from ryu.lib.packet.bgp import BGP_ATTR_TYPE_ORIGIN 25from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH 26from ryu.lib.packet.bgp import BGP_ATTR_TYPE_EXTENDED_COMMUNITIES 27from ryu.lib.packet.bgp import BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE 28from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MULTI_EXIT_DISC 29from ryu.lib.packet.bgp import BGPPathAttributeOrigin 30from ryu.lib.packet.bgp import BGPPathAttributeAsPath 31from ryu.lib.packet.bgp import EvpnEthernetSegmentNLRI 32from ryu.lib.packet.bgp import BGPPathAttributeExtendedCommunities 33from ryu.lib.packet.bgp import BGPPathAttributeMultiExitDisc 34from ryu.lib.packet.bgp import BGPEncapsulationExtendedCommunity 35from ryu.lib.packet.bgp import BGPEvpnEsiLabelExtendedCommunity 36from ryu.lib.packet.bgp import BGPEvpnEsImportRTExtendedCommunity 37from ryu.lib.packet.bgp import BGPPathAttributePmsiTunnel 38from ryu.lib.packet.bgp import PmsiTunnelIdIngressReplication 39from ryu.lib.packet.bgp import RF_L2_EVPN 40from ryu.lib.packet.bgp import EvpnMacIPAdvertisementNLRI 41from ryu.lib.packet.bgp import EvpnIpPrefixNLRI 42from ryu.lib.packet.safi import ( 43 IP_FLOWSPEC, 44 VPN_FLOWSPEC, 45) 46 47from ryu.services.protocols.bgp.base import OrderedDict 48from ryu.services.protocols.bgp.constants import VPN_TABLE 49from ryu.services.protocols.bgp.constants import VRF_TABLE 50from ryu.services.protocols.bgp.info_base.base import Destination 51from ryu.services.protocols.bgp.info_base.base import Path 52from ryu.services.protocols.bgp.info_base.base import Table 53from ryu.services.protocols.bgp.utils.bgp import create_rt_extended_community 54from ryu.services.protocols.bgp.utils.stats import LOCAL_ROUTES 55from ryu.services.protocols.bgp.utils.stats import REMOTE_ROUTES 56from ryu.services.protocols.bgp.utils.stats import RESOURCE_ID 57from ryu.services.protocols.bgp.utils.stats import RESOURCE_NAME 58 59LOG = logging.getLogger('bgpspeaker.info_base.vrf') 60 61 62@six.add_metaclass(abc.ABCMeta) 63class VrfTable(Table): 64 """Virtual Routing and Forwarding information base. 65 Keeps destination imported to given vrf in represents. 66 """ 67 68 ROUTE_FAMILY = None 69 VPN_ROUTE_FAMILY = None 70 NLRI_CLASS = None 71 VRF_PATH_CLASS = None 72 VRF_DEST_CLASS = None 73 74 def __init__(self, vrf_conf, core_service, signal_bus): 75 Table.__init__(self, vrf_conf.route_dist, core_service, signal_bus) 76 self._vrf_conf = vrf_conf 77 self._import_maps = [] 78 self.init_import_maps(vrf_conf.import_maps) 79 80 def init_import_maps(self, import_maps): 81 LOG.debug( 82 "Initializing import maps (%s) for %r", import_maps, self 83 ) 84 del self._import_maps[:] 85 importmap_manager = self._core_service.importmap_manager 86 for name in import_maps: 87 import_map = importmap_manager.get_import_map_by_name(name) 88 if import_map is None: 89 raise KeyError('No import map with name %s' % name) 90 self._import_maps.append(import_map) 91 92 @property 93 def import_rts(self): 94 return self._vrf_conf.import_rts 95 96 @property 97 def vrf_conf(self): 98 return self._vrf_conf 99 100 def _table_key(self, nlri): 101 """Return a key that will uniquely identify this NLRI inside 102 this table. 103 """ 104 # Note: We use `prefix` representation of the NLRI, because 105 # BGP route can be identified without the route distinguisher 106 # value in the VRF space. 107 return nlri.prefix 108 109 def _create_dest(self, nlri): 110 return self.VRF_DEST_CLASS(self, nlri) 111 112 def append_import_map(self, import_map): 113 self._import_maps.append(import_map) 114 115 def remove_import_map(self, import_map): 116 self._import_maps.remove(import_map) 117 118 def get_stats_summary_dict(self): 119 """Returns count of local and remote paths.""" 120 121 remote_route_count = 0 122 local_route_count = 0 123 for dest in self.values(): 124 for path in dest.known_path_list: 125 if (hasattr(path.source, 'version_num') or 126 path.source == VPN_TABLE): 127 remote_route_count += 1 128 else: 129 local_route_count += 1 130 return {RESOURCE_ID: self._vrf_conf.id, 131 RESOURCE_NAME: self._vrf_conf.name, 132 REMOTE_ROUTES: remote_route_count, 133 LOCAL_ROUTES: local_route_count} 134 135 def import_vpn_paths_from_table(self, vpn_table, import_rts=None): 136 for vpn_dest in vpn_table.values(): 137 vpn_path = vpn_dest.best_path 138 if not vpn_path: 139 continue 140 141 if import_rts is None: 142 import_rts = set(self.import_rts) 143 else: 144 import_rts = set(import_rts) 145 146 path_rts = vpn_path.get_rts() 147 if import_rts.intersection(path_rts): 148 # TODO(PH): When (re-)implementing extranet, check what should 149 # be the label reported back to NC for local paths coming from 150 # other VRFs. 151 self.import_vpn_path(vpn_path) 152 153 def import_vpn_path(self, vpn_path): 154 """Imports `vpnv(4|6)_path` into `vrf(4|6)_table` or `evpn_path` 155 into vrfevpn_table`. 156 157 :Parameters: 158 - `vpn_path`: (Path) VPN path that will be cloned and imported 159 into VRF. 160 Note: Does not do any checking if this import is valid. 161 """ 162 assert vpn_path.route_family == self.VPN_ROUTE_FAMILY 163 # If source of given vpnv4 path is NC we import it to given VRF 164 # table because of extranet setting. Hence we identify source of 165 # EXTRANET prefixes as VRF_TABLE, else VPN_TABLE. 166 source = vpn_path.source 167 if not source: 168 source = VRF_TABLE 169 170 if self.VPN_ROUTE_FAMILY == RF_L2_EVPN: 171 # Because NLRI class is the same if the route family is EVPN, 172 # we re-use the NLRI instance. 173 vrf_nlri = vpn_path.nlri 174 elif self.ROUTE_FAMILY.safi in [IP_FLOWSPEC, VPN_FLOWSPEC]: 175 vrf_nlri = self.NLRI_CLASS(rules=vpn_path.nlri.rules) 176 else: # self.VPN_ROUTE_FAMILY in [RF_IPv4_VPN, RF_IPv6_VPN] 177 # Copy NLRI instance 178 ip, masklen = vpn_path.nlri.prefix.split('/') 179 vrf_nlri = self.NLRI_CLASS(length=int(masklen), addr=ip) 180 181 vrf_path = self.VRF_PATH_CLASS( 182 puid=self.VRF_PATH_CLASS.create_puid( 183 vpn_path.nlri.route_dist, 184 vpn_path.nlri.prefix), 185 source=source, 186 nlri=vrf_nlri, 187 src_ver_num=vpn_path.source_version_num, 188 pattrs=vpn_path.pathattr_map, 189 nexthop=vpn_path.nexthop, 190 is_withdraw=vpn_path.is_withdraw, 191 label_list=getattr(vpn_path.nlri, 'label_list', None), 192 ) 193 if self._is_vrf_path_already_in_table(vrf_path): 194 return None 195 196 if self._is_vrf_path_filtered_out_by_import_maps(vrf_path): 197 return None 198 else: 199 vrf_dest = self.insert(vrf_path) 200 self._signal_bus.dest_changed(vrf_dest) 201 202 def _is_vrf_path_filtered_out_by_import_maps(self, vrf_path): 203 for import_map in self._import_maps: 204 if import_map.match(vrf_path): 205 return True 206 207 return False 208 209 def _is_vrf_path_already_in_table(self, vrf_path): 210 dest = self._get_dest(vrf_path.nlri) 211 if dest is None: 212 return False 213 return vrf_path in dest.known_path_list 214 215 def apply_import_maps(self): 216 changed_dests = [] 217 for dest in self.values(): 218 assert isinstance(dest, VrfDest) 219 for import_map in self._import_maps: 220 for path in dest.known_path_list: 221 if import_map.match(path): 222 dest.withdraw_path(path) 223 changed_dests.append(dest) 224 return changed_dests 225 226 def insert_vrf_path(self, nlri, next_hop=None, 227 gen_lbl=False, is_withdraw=False, **kwargs): 228 assert nlri 229 pattrs = None 230 label_list = [] 231 vrf_conf = self.vrf_conf 232 if not is_withdraw: 233 table_manager = self._core_service.table_manager 234 if gen_lbl and next_hop: 235 # Label per next_hop demands we use a different label 236 # per next_hop. Here connected interfaces are advertised per 237 # VRF. 238 label_key = (vrf_conf.route_dist, next_hop) 239 nh_label = table_manager.get_nexthop_label(label_key) 240 if not nh_label: 241 nh_label = table_manager.get_next_vpnv4_label() 242 table_manager.set_nexthop_label(label_key, nh_label) 243 label_list.append(nh_label) 244 245 elif gen_lbl: 246 # If we do not have next_hop, get a new label. 247 label_list.append(table_manager.get_next_vpnv4_label()) 248 249 # Set MPLS labels with the generated labels 250 if gen_lbl and isinstance(nlri, EvpnMacIPAdvertisementNLRI): 251 nlri.mpls_labels = label_list[:2] 252 elif gen_lbl and isinstance(nlri, EvpnIpPrefixNLRI): 253 nlri.mpls_label = label_list[0] 254 255 # Create a dictionary for path-attrs. 256 pattrs = OrderedDict() 257 258 # MpReachNlri and/or MpUnReachNlri attribute info. is contained 259 # in the path. Hence we do not add these attributes here. 260 from ryu.services.protocols.bgp.core import EXPECTED_ORIGIN 261 262 pattrs[BGP_ATTR_TYPE_ORIGIN] = BGPPathAttributeOrigin( 263 EXPECTED_ORIGIN) 264 pattrs[BGP_ATTR_TYPE_AS_PATH] = BGPPathAttributeAsPath([]) 265 communities = [] 266 267 # Set ES-Import Route Target 268 if isinstance(nlri, EvpnEthernetSegmentNLRI): 269 subtype = 2 270 es_import = nlri.esi.mac_addr 271 communities.append(BGPEvpnEsImportRTExtendedCommunity( 272 subtype=subtype, 273 es_import=es_import)) 274 275 for rt in vrf_conf.export_rts: 276 communities.append(create_rt_extended_community(rt, 2)) 277 for soo in vrf_conf.soo_list: 278 communities.append(create_rt_extended_community(soo, 3)) 279 280 # Set Tunnel Encapsulation Attribute 281 tunnel_type = kwargs.get('tunnel_type', None) 282 if tunnel_type: 283 communities.append( 284 BGPEncapsulationExtendedCommunity.from_str(tunnel_type)) 285 286 # Set ESI Label Extended Community 287 redundancy_mode = kwargs.get('redundancy_mode', None) 288 if redundancy_mode is not None: 289 subtype = 1 290 flags = 0 291 292 from ryu.services.protocols.bgp.api.prefix import ( 293 REDUNDANCY_MODE_SINGLE_ACTIVE) 294 if redundancy_mode == REDUNDANCY_MODE_SINGLE_ACTIVE: 295 flags |= BGPEvpnEsiLabelExtendedCommunity.SINGLE_ACTIVE_BIT 296 297 vni = kwargs.get('vni', None) 298 if vni is not None: 299 communities.append(BGPEvpnEsiLabelExtendedCommunity( 300 subtype=subtype, 301 flags=flags, 302 vni=vni)) 303 else: 304 communities.append(BGPEvpnEsiLabelExtendedCommunity( 305 subtype=subtype, 306 flags=flags, 307 mpls_label=label_list[0])) 308 309 pattrs[BGP_ATTR_TYPE_EXTENDED_COMMUNITIES] = \ 310 BGPPathAttributeExtendedCommunities(communities=communities) 311 if vrf_conf.multi_exit_disc: 312 pattrs[BGP_ATTR_TYPE_MULTI_EXIT_DISC] = \ 313 BGPPathAttributeMultiExitDisc(vrf_conf.multi_exit_disc) 314 315 # Set PMSI Tunnel Attribute 316 pmsi_tunnel_type = kwargs.get('pmsi_tunnel_type', None) 317 if pmsi_tunnel_type is not None: 318 from ryu.services.protocols.bgp.api.prefix import ( 319 PMSI_TYPE_INGRESS_REP) 320 if pmsi_tunnel_type == PMSI_TYPE_INGRESS_REP: 321 tunnel_id = PmsiTunnelIdIngressReplication( 322 tunnel_endpoint_ip=self._core_service.router_id) 323 else: # pmsi_tunnel_type == PMSI_TYPE_NO_TUNNEL_INFO 324 tunnel_id = None 325 pattrs[BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE] = \ 326 BGPPathAttributePmsiTunnel(pmsi_flags=0, 327 tunnel_type=pmsi_tunnel_type, 328 tunnel_id=tunnel_id, 329 vni=kwargs.get('vni', None)) 330 331 puid = self.VRF_PATH_CLASS.create_puid( 332 vrf_conf.route_dist, nlri.prefix) 333 334 path = self.VRF_PATH_CLASS( 335 puid, None, nlri, 0, pattrs=pattrs, 336 nexthop=next_hop, label_list=label_list, 337 is_withdraw=is_withdraw 338 ) 339 340 # Insert the path into VRF table, get affected destination so that we 341 # can process it further. 342 eff_dest = self.insert(path) 343 # Enqueue the eff_dest for further processing. 344 self._signal_bus.dest_changed(eff_dest) 345 return label_list 346 347 def clean_uninteresting_paths(self, interested_rts=None): 348 if interested_rts is None: 349 interested_rts = set(self.vrf_conf.import_rts) 350 return super(VrfTable, self).clean_uninteresting_paths(interested_rts) 351 352 353@six.add_metaclass(abc.ABCMeta) 354class VrfDest(Destination): 355 """Base class for VRF destination.""" 356 357 def __init__(self, table, nlri): 358 super(VrfDest, self).__init__(table, nlri) 359 self._route_dist = self._table.vrf_conf.route_dist 360 361 @property 362 def nlri_str(self): 363 # Returns `prefix` without the route distinguisher value, because 364 # a destination in VRF space can be identified without the route 365 # distinguisher. 366 return self._nlri.prefix 367 368 def _best_path_lost(self): 369 # Have to send update messages for withdraw of best-path to Network 370 # controller or Global table. 371 old_best_path = self._best_path 372 self._best_path = None 373 374 if old_best_path is None: 375 return 376 377 if old_best_path.source is not None: 378 # Send update-withdraw msg. to Sink. Create withdraw path 379 # out of old best path and queue it into flexinet sinks. 380 old_best_path = old_best_path.clone(for_withdrawal=True) 381 self._core_service.update_flexinet_peers(old_best_path, 382 self._route_dist) 383 else: 384 # Create withdraw-path out of old best path. 385 gpath = old_best_path.clone_to_vpn(self._route_dist, 386 for_withdrawal=True) 387 # Insert withdraw into global table and enqueue the destination 388 # for further processing. 389 tm = self._core_service.table_manager 390 tm.learn_path(gpath) 391 392 def _new_best_path(self, best_path): 393 LOG.debug('New best path selected for destination %s', self) 394 395 old_best_path = self._best_path 396 assert (best_path != old_best_path) 397 self._best_path = best_path 398 # Distribute new best-path to flexinet-peers. 399 if best_path.source is not None: 400 # Since route-refresh just causes the version number to 401 # go up and this changes best-path, we check if new- 402 # best-path is really different than old-best-path that 403 # warrants sending update to flexinet peers. 404 405 def really_diff(): 406 old_labels = old_best_path.label_list 407 new_labels = best_path.label_list 408 return old_best_path.nexthop != best_path.nexthop \ 409 or set(old_labels) != set(new_labels) 410 411 if not old_best_path or (old_best_path and really_diff()): 412 # Create OutgoingRoute and queue it into NC sink. 413 self._core_service.update_flexinet_peers( 414 best_path, self._route_dist 415 ) 416 else: 417 # If NC is source, we create new path and insert into global 418 # table. 419 gpath = best_path.clone_to_vpn(self._route_dist) 420 tm = self._core_service.table_manager 421 tm.learn_path(gpath) 422 LOG.debug('VRF table %s has new best path: %s', 423 self._route_dist, self.best_path) 424 425 def _remove_withdrawals(self): 426 """Removes withdrawn paths. 427 428 Note: 429 We may have disproportionate number of withdraws compared to know paths 430 since not all paths get installed into the table due to bgp policy and 431 we can receive withdraws for such paths and withdrawals may not be 432 stopped by the same policies. 433 """ 434 435 LOG.debug('Removing %s withdrawals', len(self._withdraw_list)) 436 437 # If we have not withdrawals, we have nothing to do. 438 if not self._withdraw_list: 439 return 440 441 # If we have some withdrawals and no know-paths, it means it is safe to 442 # delete these withdraws. 443 if not self._known_path_list: 444 LOG.debug('Found %s withdrawals for path(s) that did not get' 445 ' installed.', len(self._withdraw_list)) 446 del (self._withdraw_list[:]) 447 return 448 449 # If we have some known paths and some withdrawals, we find matches and 450 # delete them first. 451 matches = [] 452 w_matches = [] 453 # Match all withdrawals from destination paths. 454 for withdraw in self._withdraw_list: 455 match = None 456 for path in self._known_path_list: 457 # We have a match if the source are same. 458 if path.puid == withdraw.puid: 459 match = path 460 matches.append(path) 461 w_matches.append(withdraw) 462 # One withdraw can remove only one path. 463 break 464 # We do no have any match for this withdraw. 465 if not match: 466 LOG.debug('No matching path for withdraw found, may be path ' 467 'was not installed into table: %s', 468 withdraw) 469 # If we have partial match. 470 if len(matches) != len(self._withdraw_list): 471 LOG.debug('Did not find match for some withdrawals. Number of ' 472 'matches(%s), number of withdrawals (%s)', 473 len(matches), len(self._withdraw_list)) 474 475 # Clear matching paths and withdrawals. 476 for match in matches: 477 self._known_path_list.remove(match) 478 for w_match in w_matches: 479 self._withdraw_list.remove(w_match) 480 481 def _remove_old_paths(self): 482 """Identifies which of known paths are old and removes them. 483 484 Known paths will no longer have paths whose new version is present in 485 new paths. 486 """ 487 new_paths = self._new_path_list 488 known_paths = self._known_path_list 489 for new_path in new_paths: 490 old_paths = [] 491 for path in known_paths: 492 # Here we just check if source is same and not check if path 493 # version num. as new_paths are implicit withdrawal of old 494 # paths and when doing RouteRefresh (not EnhancedRouteRefresh) 495 # we get same paths again. 496 if new_path.puid == path.puid: 497 old_paths.append(path) 498 break 499 500 for old_path in old_paths: 501 known_paths.remove(old_path) 502 LOG.debug('Implicit withdrawal of old path, since we have' 503 ' learned new path from same source: %s', old_path) 504 505 def _validate_path(self, path): 506 if not path or not hasattr(path, 'label_list'): 507 raise ValueError('Invalid value of path. Expected type ' 508 'with attribute label_list got %s' % path) 509 510 511@six.add_metaclass(abc.ABCMeta) 512class VrfPath(Path): 513 """Represents a way of reaching an IP destination with a VPN. 514 """ 515 __slots__ = ('_label_list', '_puid') 516 517 ROUTE_FAMILY = None 518 VPN_PATH_CLASS = None 519 VPN_NLRI_CLASS = None 520 521 def __init__(self, puid, source, nlri, src_ver_num, 522 pattrs=None, nexthop=None, 523 is_withdraw=False, label_list=None): 524 """Initializes a Vrf path. 525 526 Parameters: 527 - `puid`: (str) path ID, identifies VPN path from which this 528 VRF path was imported. 529 - `label_list`: (list) List of labels for this path. 530 Note: other parameters are as documented in super class. 531 """ 532 if self.ROUTE_FAMILY.safi in [IP_FLOWSPEC, VPN_FLOWSPEC]: 533 nexthop = '0.0.0.0' 534 535 Path.__init__(self, source, nlri, src_ver_num, pattrs, nexthop, 536 is_withdraw) 537 if label_list is None: 538 label_list = [] 539 self._label_list = label_list 540 self._puid = puid 541 542 @property 543 def puid(self): 544 return self._puid 545 546 @property 547 def origin_rd(self): 548 tokens = self.puid.split(':') 549 return tokens[0] + ':' + tokens[1] 550 551 @property 552 def label_list(self): 553 return self._label_list[:] 554 555 @property 556 def nlri_str(self): 557 # Returns `prefix` without the route distinguisher value, because 558 # a destination in VRF space can be identified without the route 559 # distinguisher. 560 return self._nlri.prefix 561 562 @staticmethod 563 def create_puid(route_dist, ip_prefix): 564 assert route_dist and ip_prefix 565 return str(route_dist) + ':' + ip_prefix 566 567 def clone(self, for_withdrawal=False): 568 pathattrs = None 569 if not for_withdrawal: 570 pathattrs = self.pathattr_map 571 572 clone = self.__class__( 573 self.puid, 574 self._source, 575 self.nlri, 576 self.source_version_num, 577 pattrs=pathattrs, 578 nexthop=self.nexthop, 579 is_withdraw=for_withdrawal, 580 label_list=self.label_list 581 ) 582 return clone 583 584 def clone_to_vpn(self, route_dist, for_withdrawal=False): 585 if self.ROUTE_FAMILY == RF_L2_EVPN: 586 # Because NLRI class is the same if the route family is EVPN, 587 # we re-use the NLRI instance. 588 vpn_nlri = self._nlri 589 590 elif self.ROUTE_FAMILY.safi in [IP_FLOWSPEC, VPN_FLOWSPEC]: 591 vpn_nlri = self.VPN_NLRI_CLASS(route_dist=route_dist, 592 rules=self.nlri.rules) 593 594 else: # self.ROUTE_FAMILY in [RF_IPv4_UC, RF_IPv6_UC] 595 ip, masklen = self._nlri.prefix.split('/') 596 vpn_nlri = self.VPN_NLRI_CLASS(length=int(masklen), 597 addr=ip, 598 labels=self.label_list, 599 route_dist=route_dist) 600 601 pathattrs = None 602 if not for_withdrawal: 603 pathattrs = self.pathattr_map 604 605 vpnv_path = self.VPN_PATH_CLASS( 606 source=self.source, 607 nlri=vpn_nlri, 608 src_ver_num=self.source_version_num, 609 pattrs=pathattrs, 610 nexthop=self.nexthop, 611 is_withdraw=for_withdrawal) 612 613 return vpnv_path 614 615 def __eq__(self, b_path): 616 if not isinstance(b_path, self.__class__): 617 return False 618 if not self.route_family == b_path.route_family: 619 return False 620 if not self.puid == b_path.puid: 621 return False 622 if not self.label_list == b_path.label_list: 623 return False 624 if not self.nexthop == b_path.nexthop: 625 return False 626 if not self.pathattr_map == b_path.pathattr_map: 627 return False 628 629 return True 630 631 632class ImportMap(object): 633 def match(self, vrf_path): 634 raise NotImplementedError() 635 636 637class VrfNlriImportMap(ImportMap): 638 VRF_PATH_CLASS = None 639 NLRI_CLASS = None 640 641 def __init__(self, prefix): 642 assert self.VRF_PATH_CLASS is not None 643 assert self.NLRI_CLASS is not None 644 self._nlri = self.NLRI_CLASS(prefix) 645 646 def match(self, vrf_path): 647 if vrf_path.route_family != self.VRF_PATH_CLASS.ROUTE_FAMILY: 648 LOG.error( 649 "vrf_paths route_family does not match importmaps" 650 "route_family. Applied to wrong table?") 651 return False 652 653 return vrf_path.nlri == self._nlri 654 655 656class VrfRtImportMap(ImportMap): 657 def __init__(self, rt): 658 self._rt = rt 659 660 def match(self, vrf_path): 661 extcomm = vrf_path.pathattr_map.get(BGP_ATTR_TYPE_EXTENDED_COMMUNITIES) 662 return extcomm is not None and self._rt in extcomm.rt_list 663