This patch enables BGPSpeaker to store EVPN routes into the VRF
tables and to provide the API for advertising routes.

Usage example:

  speaker = BGPSpeaker(as_number=65001,
                       router_id='172.17.0.1')

  speaker.neighbor_add(address='172.17.0.2', remote_as=65002,
                       enable_evpn=True)

  speaker.vrf_add(route_dist='65001:100',
                  import_rts=['65001:100'],
                  export_rts=['65001:100'],
                  route_family=RF_L2_EVPN)

  speaker.evpn_prefix_add(route_type=EVPN_MAC_IP_ADV_ROUTE,
                          route_dist='65001:100',
                          esi=0,
                          ethernet_tag_id=200,
                          mac_addr='aa:bb:cc:dd:ee:ff',
                          ip_addr='10.0.0.1',
                          next_hop='172.19.0.1')

Signed-off-by: IWASE Yusuke <iwase.yusu...@gmail.com>
---
 ryu/services/protocols/bgp/api/prefix.py           |  48 ++++-
 ryu/services/protocols/bgp/bgpspeaker.py           |  27 +--
 .../protocols/bgp/core_managers/table_manager.py   | 211 +++++++++------------
 ryu/services/protocols/bgp/info_base/evpn.py       |  45 +----
 ryu/services/protocols/bgp/info_base/vpn.py        |  20 +-
 ryu/services/protocols/bgp/info_base/vrf.py        |  72 ++++---
 ryu/services/protocols/bgp/info_base/vrfevpn.py    |  58 ++++++
 .../protocols/bgp/operator/commands/show/vrf.py    |   9 +-
 .../protocols/bgp/operator/internal_api.py         |   2 +-
 ryu/services/protocols/bgp/rtconf/vrfs.py          |  30 +--
 10 files changed, 289 insertions(+), 233 deletions(-)
 create mode 100644 ryu/services/protocols/bgp/info_base/vrfevpn.py

diff --git a/ryu/services/protocols/bgp/api/prefix.py 
b/ryu/services/protocols/bgp/api/prefix.py
index f6cddc3..3340112 100644
--- a/ryu/services/protocols/bgp/api/prefix.py
+++ b/ryu/services/protocols/bgp/api/prefix.py
@@ -40,6 +40,7 @@ from ryu.services.protocols.bgp.rtconf.base import 
ConfigValueError
 from ryu.services.protocols.bgp.rtconf.base import RuntimeConfigError
 from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF
 from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_IPV4
+from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_L2_EVPN
 from ryu.services.protocols.bgp.utils import validation
 
 
@@ -125,7 +126,7 @@ def add_local(route_dist, prefix, next_hop, 
route_family=VRF_RF_IPV4):
     try:
         # Create new path and insert into appropriate VRF table.
         tm = CORE_MANAGER.get_core_service().table_manager
-        label = tm.add_to_vrf(route_dist, prefix, next_hop, route_family)
+        label = tm.update_vrf_table(route_dist, prefix, next_hop, route_family)
         # Currently we only allocate one label per local_prefix,
         # so we share first label from the list.
         if label:
@@ -147,8 +148,9 @@ def delete_local(route_dist, prefix, 
route_family=VRF_RF_IPV4):
     """
     try:
         tm = CORE_MANAGER.get_core_service().table_manager
-        tm.remove_from_vrf(route_dist, prefix, route_family)
-        # Send success response to ApgwAgent.
+        tm.update_vrf_table(route_dist, prefix,
+                            route_family=route_family, is_withdraw=True)
+        # Send success response.
         return [{ROUTE_DISTINGUISHER: route_dist, PREFIX: prefix,
                  VRF_RF: route_family}]
     except BgpCoreError as e:
@@ -165,9 +167,26 @@ def delete_local(route_dist, prefix, 
route_family=VRF_RF_IPV4):
                        opt_args=[EVPN_ESI, EVPN_ETHERNET_TAG_ID, MAC_ADDR,
                                  IP_ADDR])
 def add_evpn_local(route_type, route_dist, next_hop, **kwargs):
-    tm = CORE_MANAGER.get_core_service().table_manager
-    tm.add_to_global_evpn_table(route_type, route_dist, next_hop, **kwargs)
-    return True
+    """Adds EVPN route from VRF identified by *route_dist*.
+    """
+    try:
+        # Create new path and insert into appropriate VRF table.
+        tm = CORE_MANAGER.get_core_service().table_manager
+        label = tm.update_vrf_table(route_dist, next_hop=next_hop,
+                                    route_family=VRF_RF_L2_EVPN,
+                                    route_type=route_type, **kwargs)
+        # Currently we only allocate one label per local route,
+        # so we share first label from the list.
+        if label:
+            label = label[0]
+
+        # Send success response with new label.
+        return [{EVPN_ROUTE_TYPE: route_type,
+                 ROUTE_DISTINGUISHER: route_dist,
+                 VRF_RF: VRF_RF_L2_EVPN,
+                 VPN_LABEL: label}.update(kwargs)]
+    except BgpCoreError as e:
+        raise PrefixError(desc=e)
 
 
 @RegisterWithArgChecks(name='evpn_prefix.delete_local',
@@ -175,7 +194,16 @@ def add_evpn_local(route_type, route_dist, next_hop, 
**kwargs):
                        opt_args=[EVPN_ESI, EVPN_ETHERNET_TAG_ID, MAC_ADDR,
                                  IP_ADDR])
 def delete_evpn_local(route_type, route_dist, **kwargs):
-    tm = CORE_MANAGER.get_core_service().table_manager
-    tm.add_to_global_evpn_table(route_type, route_dist, is_withdraw=True,
-                                **kwargs)
-    return True
+    """Deletes/withdraws EVPN route from VRF identified by *route_dist*.
+    """
+    try:
+        tm = CORE_MANAGER.get_core_service().table_manager
+        tm.update_vrf_table(route_dist,
+                            route_family=VRF_RF_L2_EVPN,
+                            route_type=route_type, is_withdraw=True, **kwargs)
+        # Send success response.
+        return [{EVPN_ROUTE_TYPE: route_type,
+                 ROUTE_DISTINGUISHER: route_dist,
+                 VRF_RF: VRF_RF_L2_EVPN}.update(kwargs)]
+    except BgpCoreError as e:
+        raise PrefixError(desc=e)
diff --git a/ryu/services/protocols/bgp/bgpspeaker.py 
b/ryu/services/protocols/bgp/bgpspeaker.py
index ce72e0f..168220b 100644
--- a/ryu/services/protocols/bgp/bgpspeaker.py
+++ b/ryu/services/protocols/bgp/bgpspeaker.py
@@ -70,6 +70,7 @@ from ryu.services.protocols.bgp.rtconf.neighbors import 
IS_NEXT_HOP_SELF
 from ryu.services.protocols.bgp.rtconf.neighbors import CONNECT_MODE
 from ryu.services.protocols.bgp.rtconf.neighbors import LOCAL_ADDRESS
 from ryu.services.protocols.bgp.rtconf.neighbors import LOCAL_PORT
+from ryu.services.protocols.bgp.rtconf.vrfs import SUPPORTED_VRF_RF
 from ryu.services.protocols.bgp.info_base.base import Filter
 from ryu.services.protocols.bgp.info_base.ipv4 import Ipv4Path
 from ryu.services.protocols.bgp.info_base.ipv6 import Ipv6Path
@@ -80,6 +81,7 @@ from ryu.services.protocols.bgp.info_base.vpnv6 import 
Vpnv6Path
 NEIGHBOR_CONF_MED = 'multi_exit_disc'
 RF_VPN_V4 = vrfs.VRF_RF_IPV4
 RF_VPN_V6 = vrfs.VRF_RF_IPV6
+RF_L2_EVPN = vrfs.VRF_RF_L2_EVPN
 
 
 class EventPrefix(object):
@@ -588,29 +590,30 @@ class BGPSpeaker(object):
         This parameter must be a list of string.
 
         ``route_family`` specifies route family of the VRF.
-        This parameter must be RF_VPN_V4 or RF_VPN_V6.
+        This parameter must be RF_VPN_V4, RF_VPN_V6 or RF_L2_EVPN.
         """
 
-        assert route_family in (RF_VPN_V4, RF_VPN_V6),\
-            'route_family must be RF_VPN_V4 or RF_VPN_V6'
+        assert route_family in SUPPORTED_VRF_RF,\
+            'route_family must be RF_VPN_V4, RF_VPN_V6 or RF_L2_EVPN'
+
+        vrf = {
+            vrfs.ROUTE_DISTINGUISHER: route_dist,
+            vrfs.IMPORT_RTS: import_rts,
+            vrfs.EXPORT_RTS: export_rts,
+            vrfs.SITE_OF_ORIGINS: site_of_origins,
+            vrfs.VRF_RF: route_family,
+        }
 
-        vrf = {}
-        vrf[vrfs.ROUTE_DISTINGUISHER] = route_dist
-        vrf[vrfs.IMPORT_RTS] = import_rts
-        vrf[vrfs.EXPORT_RTS] = export_rts
-        vrf[vrfs.SITE_OF_ORIGINS] = site_of_origins
-        vrf[vrfs.VRF_RF] = route_family
         call('vrf.create', **vrf)
 
     def vrf_del(self, route_dist):
         """ This method deletes the existing vrf.
 
         ``route_dist`` specifies a route distinguisher value.
-
         """
 
-        vrf = {}
-        vrf[vrfs.ROUTE_DISTINGUISHER] = route_dist
+        vrf = {vrfs.ROUTE_DISTINGUISHER: route_dist}
+
         call('vrf.delete', **vrf)
 
     def vrfs_get(self, format='json'):
diff --git a/ryu/services/protocols/bgp/core_managers/table_manager.py 
b/ryu/services/protocols/bgp/core_managers/table_manager.py
index ec71d3f..49d8f2a 100644
--- a/ryu/services/protocols/bgp/core_managers/table_manager.py
+++ b/ryu/services/protocols/bgp/core_managers/table_manager.py
@@ -15,11 +15,12 @@ from ryu.services.protocols.bgp.info_base.vpnv6 import 
Vpnv6Path
 from ryu.services.protocols.bgp.info_base.vpnv6 import Vpnv6Table
 from ryu.services.protocols.bgp.info_base.vrf4 import Vrf4Table
 from ryu.services.protocols.bgp.info_base.vrf6 import Vrf6Table
-from ryu.services.protocols.bgp.info_base.evpn import EvpnPath
+from ryu.services.protocols.bgp.info_base.vrfevpn import VrfEvpnTable
 from ryu.services.protocols.bgp.info_base.evpn import EvpnTable
 from ryu.services.protocols.bgp.rtconf import vrfs
 from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_IPV4
 from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_IPV6
+from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_L2_EVPN
 from ryu.services.protocols.bgp.rtconf.vrfs import SUPPORTED_VRF_RF
 
 from ryu.lib import type_desc
@@ -102,24 +103,25 @@ class TableCoreManager(object):
         LOG.debug('VRF with RD %s marked for removal', vrf_conf.route_dist)
 
     def import_all_vpn_paths_to_vrf(self, vrf_table, import_rts=None):
-        """Imports Vpnv4/6 paths from Global/VPN table into given Vrfv4/6
-         table.
+        """Imports VPNv4/6 or EVPN paths from Global/VPN table into given
+        VRFv4/6  or VRFEVPN table.
         :param vrf_table: Vrf table to which we import
         :type vrf_table: VrfTable
         :param import_rts: import RTs to override default import_rts of
          vrf table for this import
         :type import_rts: set of strings
 
-
         Checks if we have any path RT common with VRF table's import RT.
         """
-        rfs = (Vrf4Table.ROUTE_FAMILY, Vrf6Table.ROUTE_FAMILY)
-        assert vrf_table.route_family in rfs, 'Invalid VRF table.'
-
         if vrf_table.route_family == Vrf4Table.ROUTE_FAMILY:
             vpn_table = self.get_vpn4_table()
-        else:
+        elif vrf_table.route_family == Vrf6Table.ROUTE_FAMILY:
             vpn_table = self.get_vpn6_table()
+        elif vrf_table.route_family == VrfEvpnTable.ROUTE_FAMILY:
+            vpn_table = self.get_evpn_table()
+        else:
+            raise ValueError('Invalid VRF table route family: %s' %
+                             vrf_table.route_family)
 
         vrf_table.import_vpn_paths_from_table(vpn_table, import_rts)
 
@@ -320,8 +322,8 @@ class TableCoreManager(object):
             for path in dest.known_path_list:
                 if path.source is None:
                     vrf_table.insert_vrf_path(
-                        path.nlri,
-                        path.nexthop,
+                        nlri=path.nlri,
+                        next_hop=path.nexthop,
                         gen_lbl=True
                     )
         LOG.debug('Re-installed NC paths with current policy for table %s.',
@@ -364,26 +366,24 @@ class TableCoreManager(object):
         importing/installing of paths from global tables.
         Returns created table.
         """
-
         route_family = vrf_conf.route_family
-        assert route_family in (VRF_RF_IPV4, VRF_RF_IPV6)
-        vrf_table = None
-        if route_family == VRF_RF_IPV4:
-            vrf_table = Vrf4Table(
-                vrf_conf, self._core_service, self._signal_bus
-            )
-            table_id = (vrf_conf.route_dist, route_family)
-            self._tables[table_id] = vrf_table
 
+        if route_family == VRF_RF_IPV4:
+            vrf_table = Vrf4Table
         elif route_family == VRF_RF_IPV6:
-            vrf_table = Vrf6Table(
-                vrf_conf, self._core_service, self._signal_bus
-            )
-            table_id = (vrf_conf.route_dist, route_family)
-            self._tables[table_id] = vrf_table
+            vrf_table = Vrf6Table
+        elif route_family == VRF_RF_L2_EVPN:
+            vrf_table = VrfEvpnTable
+        else:
+            raise ValueError('Unsupported route family for VRF: %s' %
+                             route_family)
+
+        vrf_table = vrf_table(vrf_conf, self._core_service, self._signal_bus)
+        table_id = (vrf_conf.route_dist, route_family)
+        self._tables[table_id] = vrf_table
 
         assert vrf_table is not None
-        LOG.debug('Added new VrfTable with rd: %s and add_fmly: %s',
+        LOG.debug('Added new VrfTable with route_dist:%s and route_family:%s',
                   vrf_conf.route_dist, route_family)
 
         import_rts = vrf_conf.import_rts
@@ -435,13 +435,11 @@ class TableCoreManager(object):
                   uninteresting_dest_count)
 
     def import_single_vpn_path_to_all_vrfs(self, vpn_path, path_rts=None):
-        """Imports *vpnv4_path* to qualifying VRF tables.
+        """Imports *vpn_path* to qualifying VRF tables.
 
         Import RTs of VRF table is matched with RTs from *vpn4_path* and if we
         have any common RTs we import the path into VRF.
         """
-        assert (vpn_path.route_family in
-                (Vpnv4Path.ROUTE_FAMILY, Vpnv6Path.ROUTE_FAMILY))
         LOG.debug('Importing path %s to qualifying VRFs', vpn_path)
 
         # If this path has no RTs we are done.
@@ -453,9 +451,16 @@ class TableCoreManager(object):
         interested_tables = set()
 
         # Get route family of VRF to when this VPN Path can be imported to
-        route_family = RF_IPv4_UC
-        if vpn_path.route_family != RF_IPv4_VPN:
+        if vpn_path.route_family == RF_IPv4_VPN:
+            route_family = RF_IPv4_UC
+        elif vpn_path.route_family == RF_IPv6_VPN:
             route_family = RF_IPv6_UC
+        elif vpn_path.route_family == RF_L2_EVPN:
+            route_family = RF_L2_EVPN
+        else:
+            raise ValueError('Unsupported route family for VRF: %s' %
+                             vpn_path.route_family)
+
         for rt in path_rts:
             rt_rf_id = rt + ':' + str(route_family)
             vrf_rt_tables = self._tables_for_rt.get(rt_rf_id)
@@ -478,44 +483,70 @@ class TableCoreManager(object):
             # If we do not have any VRF with import RT that match with path RT
             LOG.debug('No VRF table found that imports RTs: %s', path_rts)
 
-    def add_to_vrf(self, route_dist, prefix, next_hop, route_family):
-        """Adds `prefix` to VRF identified by `route_dist` with given
-         `next_hop`.
+    def update_vrf_table(self, route_dist, prefix=None, next_hop=None,
+                         route_family=None, route_type=None,
+                         is_withdraw=False, **kwargs):
+        """Update a BGP route in the VRF table identified by `route_dist`
+        with the given `next_hop`.
+
+        If `is_withdraw` is False, which is the default, add a BGP route
+        to the VRF table identified by `route_dist` with the given
+        `next_hop`.
+        If `is_withdraw` is True, remove a BGP route from the VRF table
+        and the given `next_hop` is ignored.
+
+        If `route_family` is VRF_RF_L2_EVPN, `route_type` and `kwargs`
+        are required to construct EVPN NLRI and `prefix` is ignored.
 
         Returns assigned VPN label.
         """
         from ryu.services.protocols.bgp.core import BgpCoreError
 
-        assert route_dist and prefix and next_hop
-        if route_family not in (VRF_RF_IPV4, VRF_RF_IPV6):
-            raise ValueError('Given route_family %s is not supported.' %
-                             route_family)
+        assert route_dist
+
+        if is_withdraw:
+            gen_lbl = False
+            next_hop = None
+        else:
+            gen_lbl = True
+            if not (is_valid_ipv4(next_hop) or is_valid_ipv6(next_hop)):
+                raise BgpCoreError(
+                    desc='Invalid IPv4/IPv6 nexthop: %s' % next_hop)
+
+        vrf_table = self._tables.get((route_dist, route_family))
+        if vrf_table is None:
+            raise BgpCoreError(
+                desc='VRF table  does not exist: route_dist=%s, '
+                     'route_family=%s' % (route_dist, route_family))
 
-        vrf_table = None
-        table_id = (route_dist, route_family)
         if route_family == VRF_RF_IPV4:
-            vrf_table = self._tables.get(table_id)
-            if vrf_table is None:
-                raise BgpCoreError(desc='VRF table for RD: %s does not '
-                                        'exist.' % route_dist)
-            if not is_valid_ipv4_prefix(prefix) or not is_valid_ipv4(next_hop):
-                raise BgpCoreError(desc='Invalid Ipv4 prefix or nexthop.')
+            if not is_valid_ipv4_prefix(prefix):
+                raise BgpCoreError(desc='Invalid IPv4 prefix: %s' % prefix)
             ip, masklen = prefix.split('/')
             prefix = IPAddrPrefix(int(masklen), ip)
         elif route_family == VRF_RF_IPV6:
-            vrf_table = self._tables.get(table_id)
-            if vrf_table is None:
-                raise BgpCoreError(desc='VRF table for RD: %s does not '
-                                        'exist.' % route_dist)
-            if not is_valid_ipv6_prefix(prefix) or not is_valid_ipv6(next_hop):
-                raise BgpCoreError(desc='Invalid Ipv6 prefix or nexthop.')
+            if not is_valid_ipv6_prefix(prefix):
+                raise BgpCoreError(desc='Invalid IPv6 prefix: %s' % prefix)
             ip6, masklen = prefix.split('/')
             prefix = IP6AddrPrefix(int(masklen), ip6)
+        elif route_family == VRF_RF_L2_EVPN:
+            assert route_type
+            subclass = EvpnNLRI._lookup_type_name(route_type)
+            kwargs['route_dist'] = route_dist
+            esi = kwargs.get('esi', None)
+            if esi is not None:
+                # Note: Currently, we support arbitrary 9-octet ESI value only.
+                kwargs['esi'] = EvpnArbitraryEsi(type_desc.Int9.from_user(esi))
+            prefix = subclass(**kwargs)
+        else:
+            raise BgpCoreError(
+                desc='Unsupported route family %s' % route_family)
 
+        # We do not check if we have a path to given prefix, we issue
+        # withdrawal. Hence multiple withdrawals have not side effect.
         return vrf_table.insert_vrf_path(
-            prefix, next_hop=next_hop,
-            gen_lbl=True
-        )
+            nlri=prefix, next_hop=next_hop, gen_lbl=gen_lbl,
+            is_withdraw=is_withdraw)
 
     def add_to_global_table(self, prefix, nexthop=None,
                             is_withdraw=False):
@@ -550,78 +581,6 @@ class TableCoreManager(object):
         # add to global ipv4 table and propagates to neighbors
         self.learn_path(new_path)
 
-    def add_to_global_evpn_table(self, route_type, route_dist, next_hop=None,
-                                 is_withdraw=False, **kwargs):
-        """Adds BGP EVPN Route to global EVPN Table with given `next_hop`.
-
-        If `is_withdraw` is set to `True`, removes the given route from
-        global EVPN Table.
-        """
-
-        # construct EVPN NLRI instance
-        subclass = EvpnNLRI._lookup_type_name(route_type)
-        kwargs['route_dist'] = route_dist
-        esi = kwargs.get('esi', None)
-        if esi is not None:
-            # Note: Currently, we support arbitrary 9-octet ESI value only.
-            kwargs['esi'] = EvpnArbitraryEsi(type_desc.Int9.from_user(esi))
-        nlri = subclass(**kwargs)
-
-        # set mandatory path attributes
-        origin = BGPPathAttributeOrigin(BGP_ATTR_ORIGIN_IGP)
-        aspath = BGPPathAttributeAsPath([[]])
-        pathattrs = OrderedDict()
-        pathattrs[BGP_ATTR_TYPE_ORIGIN] = origin
-        pathattrs[BGP_ATTR_TYPE_AS_PATH] = aspath
-
-        # set the default next_hop address
-        if next_hop is None:
-            next_hop = '0.0.0.0'
-
-        new_path = EvpnPath(source=None, nlri=nlri, src_ver_num=1,
-                            pattrs=pathattrs, nexthop=next_hop,
-                            is_withdraw=is_withdraw)
-
-        # add to global EVPN table and propagates to neighbors
-        self.learn_path(new_path)
-
-    def remove_from_vrf(self, route_dist, prefix, route_family):
-        """Removes `prefix` from VRF identified by `route_dist`.
-
-        Returns assigned VPN label.
-        """
-        from ryu.services.protocols.bgp.core import BgpCoreError
-        # Validate given
-        if route_family not in (VRF_RF_IPV4, VRF_RF_IPV6):
-            raise BgpCoreError(desc='Unsupported route family %s' %
-                                    route_family)
-        val_ipv4 = route_family == VRF_RF_IPV4\
-            and is_valid_ipv4_prefix(prefix)
-        val_ipv6 = route_family == VRF_RF_IPV6\
-            and is_valid_ipv6_prefix(prefix)
-
-        if not val_ipv4 and not val_ipv6:
-            raise BgpCoreError(desc='Invalid prefix or nexthop.')
-
-        table_id = (route_dist, route_family)
-        if route_family == VRF_RF_IPV4:
-            vrf_table = self._tables.get(table_id)
-            if not vrf_table:
-                raise BgpCoreError(desc='Vrf for route distinguisher %s does '
-                                        'not exist.' % route_dist)
-            ip, masklen = prefix.split('/')
-            prefix = IPAddrPrefix(int(masklen), ip)
-        else:
-            vrf_table = self._tables.get(table_id)
-            if not vrf_table:
-                raise BgpCoreError(desc='Vrf for route distinguisher %s does '
-                                        'not exist.' % route_dist)
-            ip6, masklen = prefix.split('/')
-            prefix = IP6AddrPrefix(int(masklen), ip6)
-            # We do not check if we have a path to given prefix, we issue
-        # withdrawal. Hence multiple withdrawals have not side effect.
-        return vrf_table.insert_vrf_path(prefix, is_withdraw=True)
-
     def clean_stale_routes(self, peer, route_family=None):
         """Removes old routes from `peer` from `route_family` table.
 
diff --git a/ryu/services/protocols/bgp/info_base/evpn.py 
b/ryu/services/protocols/bgp/info_base/evpn.py
index 1a2c6f6..c5f49a4 100644
--- a/ryu/services/protocols/bgp/info_base/evpn.py
+++ b/ryu/services/protocols/bgp/info_base/evpn.py
@@ -22,32 +22,22 @@ import logging
 from ryu.lib.packet.bgp import EvpnNLRI
 from ryu.lib.packet.bgp import RF_L2_EVPN
 
-from ryu.services.protocols.bgp.info_base.base import Path
-from ryu.services.protocols.bgp.info_base.base import Table
-from ryu.services.protocols.bgp.info_base.base import Destination
-from ryu.services.protocols.bgp.info_base.base import NonVrfPathProcessingMixin
+from ryu.services.protocols.bgp.info_base.vpn import VpnDest
+from ryu.services.protocols.bgp.info_base.vpn import VpnPath
+from ryu.services.protocols.bgp.info_base.vpn import VpnTable
 
 LOG = logging.getLogger('bgpspeaker.info_base.evpn')
 
 
-class EvpnDest(Destination, NonVrfPathProcessingMixin):
+class EvpnDest(VpnDest):
     """EVPN Destination
 
-    Store EVPN paths.
+    Store EVPN Paths.
     """
     ROUTE_FAMILY = RF_L2_EVPN
 
-    def _best_path_lost(self):
-        old_best_path = self._best_path
-        NonVrfPathProcessingMixin._best_path_lost(self)
-        self._core_service._signal_bus.best_path_changed(old_best_path, True)
 
-    def _new_best_path(self, best_path):
-        NonVrfPathProcessingMixin._new_best_path(self, best_path)
-        self._core_service._signal_bus.best_path_changed(best_path, False)
-
-
-class EvpnTable(Table):
+class EvpnTable(VpnTable):
     """Global table to store EVPN routing information.
 
     Uses `EvpnDest` to store destination information for each known EVPN
@@ -56,25 +46,8 @@ class EvpnTable(Table):
     ROUTE_FAMILY = RF_L2_EVPN
     VPN_DEST_CLASS = EvpnDest
 
-    def __init__(self, core_service, signal_bus):
-        super(EvpnTable, self).__init__(None, core_service, signal_bus)
-
-    def _table_key(self, nlri):
-        """Return a key that will uniquely identify this NLRI inside
-        this table.
-        """
-        return nlri.formatted_nlri_str
-
-    def _create_dest(self, nlri):
-        return self.VPN_DEST_CLASS(self, nlri)
-
-    def __str__(self):
-        return '%s(scope_id: %s, rf: %s)' % (
-            self.__class__.__name__, self.scope_id, self.route_family
-        )
-
 
-class EvpnPath(Path):
+class EvpnPath(VpnPath):
     """Represents a way of reaching an EVPN destination."""
     ROUTE_FAMILY = RF_L2_EVPN
     VRF_PATH_CLASS = None  # defined in init - anti cyclic import hack
@@ -82,5 +55,5 @@ class EvpnPath(Path):
 
     def __init__(self, *args, **kwargs):
         super(EvpnPath, self).__init__(*args, **kwargs)
-        # TODO:
-        # To support the VRF table for BGP EVPN routes.
+        from ryu.services.protocols.bgp.info_base.vrfevpn import VrfEvpnPath
+        self.VRF_PATH_CLASS = VrfEvpnPath
diff --git a/ryu/services/protocols/bgp/info_base/vpn.py 
b/ryu/services/protocols/bgp/info_base/vpn.py
index 0f59107..46cf47f 100644
--- a/ryu/services/protocols/bgp/info_base/vpn.py
+++ b/ryu/services/protocols/bgp/info_base/vpn.py
@@ -21,6 +21,7 @@ import abc
 import logging
 import six
 
+from ryu.lib.packet.bgp import RF_L2_EVPN
 from ryu.services.protocols.bgp.info_base.base import Destination
 from ryu.services.protocols.bgp.info_base.base import NonVrfPathProcessingMixin
 from ryu.services.protocols.bgp.info_base.base import Path
@@ -63,23 +64,30 @@ class VpnPath(Path):
     NLRI_CLASS = None
 
     def clone_to_vrf(self, is_withdraw=False):
-        vrf_nlri = self.NLRI_CLASS(self._nlri.prefix)
+        if self.ROUTE_FAMILY == RF_L2_EVPN:
+            nlri_cls = self.NLRI_CLASS._lookup_type(self._nlri.type)
+            kwargs = dict(self._nlri.__dict__)
+            kwargs.pop('type', None)
+            vrf_nlri = nlri_cls(**kwargs)
+        else:  # self.ROUTE_FAMILY in [RF_IPv4_VPN, RF_IPv46_VPN]
+            vrf_nlri = self.NLRI_CLASS(self._nlri.prefix)
 
         pathattrs = None
         if not is_withdraw:
             pathattrs = self.pathattr_map
 
         vrf_path = self.VRF_PATH_CLASS(
-            self.VRF_PATH_CLASS.create_puid(
+            puid=self.VRF_PATH_CLASS.create_puid(
                 self._nlri.route_dist,
-                self._nlri.prefix
-            ),
-            self.source, vrf_nlri,
-            self.source_version_num,
+                self._nlri.prefix),
+            source=self.source,
+            nlri=vrf_nlri,
+            src_ver_num=self.source_version_num,
             pattrs=pathattrs,
             nexthop=self.nexthop,
             is_withdraw=is_withdraw,
             label_list=self._nlri.label_list)
+
         return vrf_path
 
 
diff --git a/ryu/services/protocols/bgp/info_base/vrf.py 
b/ryu/services/protocols/bgp/info_base/vrf.py
index c3f6603..ca6fdac 100644
--- a/ryu/services/protocols/bgp/info_base/vrf.py
+++ b/ryu/services/protocols/bgp/info_base/vrf.py
@@ -30,6 +30,7 @@ from ryu.lib.packet.bgp import BGPPathAttributeAsPath
 from ryu.lib.packet.bgp import BGPPathAttributeExtendedCommunities
 from ryu.lib.packet.bgp import BGPTwoOctetAsSpecificExtendedCommunity
 from ryu.lib.packet.bgp import BGPPathAttributeMultiExitDisc
+from ryu.lib.packet.bgp import RF_L2_EVPN
 
 from ryu.services.protocols.bgp.base import OrderedDict
 from ryu.services.protocols.bgp.constants import VPN_TABLE
@@ -87,7 +88,10 @@ class VrfTable(Table):
         """Return a key that will uniquely identify this NLRI inside
         this table.
         """
-        return str(nlri)
+        # Note: We use `prefix` representation of the NLRI, because
+        # BGP route can be identified without the route distinguisher
+        # value in the VRF space.
+        return nlri.prefix
 
     def _create_dest(self, nlri):
         return self.VRF_DEST_CLASS(self, nlri)
@@ -134,7 +138,8 @@ class VrfTable(Table):
                 self.import_vpn_path(vpn_path)
 
     def import_vpn_path(self, vpn_path):
-        """Imports `vpnv(4|6)_path` into `vrf(4|6)_table`.
+        """Imports `vpnv(4|6)_path` into `vrf(4|6)_table` or `evpn_path`
+        into vrfevpn_table`.
 
         :Parameters:
             - `vpn_path`: (Path) VPN path that will be cloned and imported
@@ -148,17 +153,24 @@ class VrfTable(Table):
         source = vpn_path.source
         if not source:
             source = VRF_TABLE
-        ip, masklen = vpn_path.nlri.prefix.split('/')
-        vrf_nlri = self.NLRI_CLASS(length=int(masklen), addr=ip)
 
-        vpn_nlri = vpn_path.nlri
-        puid = self.VRF_PATH_CLASS.create_puid(vpn_nlri.route_dist,
-                                               vpn_nlri.prefix)
+        if self.VPN_ROUTE_FAMILY == RF_L2_EVPN:
+            nlri_cls = self.NLRI_CLASS._lookup_type(vpn_path.nlri.type)
+            kwargs = dict(vpn_path.nlri.__dict__)
+            kwargs.pop('type', None)
+            vrf_nlri = nlri_cls(**kwargs)
+        else:  # self.VPN_ROUTE_FAMILY in [RF_IPv4_VPN, RF_IPv6_VPN]
+            # Copy NLRI instance
+            ip, masklen = vpn_path.nlri.prefix.split('/')
+            vrf_nlri = self.NLRI_CLASS(length=int(masklen), addr=ip)
+
         vrf_path = self.VRF_PATH_CLASS(
-            puid,
-            source,
-            vrf_nlri,
-            vpn_path.source_version_num,
+            puid=self.VRF_PATH_CLASS.create_puid(
+                vpn_path.nlri.route_dist,
+                vpn_path.nlri.prefix),
+            source=source,
+            nlri=vrf_nlri,
+            src_ver_num=vpn_path.source_version_num,
             pattrs=vpn_path.pathattr_map,
             nexthop=vpn_path.nexthop,
             is_withdraw=vpn_path.is_withdraw,
@@ -197,9 +209,9 @@ class VrfTable(Table):
                         changed_dests.append(dest)
         return changed_dests
 
-    def insert_vrf_path(self, ip_nlri, next_hop=None,
+    def insert_vrf_path(self, nlri, next_hop=None,
                         gen_lbl=False, is_withdraw=False):
-        assert ip_nlri
+        assert nlri
         pattrs = None
         label_list = []
         vrf_conf = self.vrf_conf
@@ -253,10 +265,10 @@ class VrfTable(Table):
                 label_list.append(table_manager.get_next_vpnv4_label())
 
         puid = self.VRF_PATH_CLASS.create_puid(
-            vrf_conf.route_dist, ip_nlri.prefix
-        )
+            vrf_conf.route_dist, nlri.prefix)
+
         path = self.VRF_PATH_CLASS(
-            puid, None, ip_nlri, 0, pattrs=pattrs,
+            puid, None, nlri, 0, pattrs=pattrs,
             nexthop=next_hop, label_list=label_list,
             is_withdraw=is_withdraw
         )
@@ -410,7 +422,7 @@ class VrfDest(Destination):
                 # version num. as new_paths are implicit withdrawal of old
                 # paths and when doing RouteRefresh (not EnhancedRouteRefresh)
                 # we get same paths again.
-                if (new_path.puid == path.puid):
+                if new_path.puid == path.puid:
                     old_paths.append(path)
                     break
 
@@ -489,22 +501,30 @@ class VrfPath(Path):
         return clone
 
     def clone_to_vpn(self, route_dist, for_withdrawal=False):
-        ip, masklen = self._nlri.prefix.split('/')
-        vpn_nlri = self.VPN_NLRI_CLASS(length=int(masklen),
-                                       addr=ip,
-                                       labels=self.label_list,
-                                       route_dist=route_dist)
+        if self.ROUTE_FAMILY == RF_L2_EVPN:
+            nlri_cls = self.VPN_NLRI_CLASS._lookup_type(self._nlri.type)
+            kwargs = dict(self._nlri.__dict__)
+            kwargs.pop('type', None)
+            vpn_nlri = nlri_cls(**kwargs)
+        else:  # self.ROUTE_FAMILY in [RF_IPv4_UC, RF_IPv6_UC]
+            ip, masklen = self._nlri.prefix.split('/')
+            vpn_nlri = self.VPN_NLRI_CLASS(length=int(masklen),
+                                           addr=ip,
+                                           labels=self.label_list,
+                                           route_dist=route_dist)
 
         pathattrs = None
         if not for_withdrawal:
             pathattrs = self.pathattr_map
+
         vpnv_path = self.VPN_PATH_CLASS(
-            self.source, vpn_nlri,
-            self.source_version_num,
+            source=self.source,
+            nlri=vpn_nlri,
+            src_ver_num=self.source_version_num,
             pattrs=pathattrs,
             nexthop=self.nexthop,
-            is_withdraw=for_withdrawal
-        )
+            is_withdraw=for_withdrawal)
+
         return vpnv_path
 
     def __eq__(self, b_path):
diff --git a/ryu/services/protocols/bgp/info_base/vrfevpn.py 
b/ryu/services/protocols/bgp/info_base/vrfevpn.py
new file mode 100644
index 0000000..5c3a571
--- /dev/null
+++ b/ryu/services/protocols/bgp/info_base/vrfevpn.py
@@ -0,0 +1,58 @@
+# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+ Defines data types and models required specifically for VRF (for EVPN)
+ support. Represents data structures for VRF not VPN/global.
+"""
+
+import logging
+
+from ryu.lib.packet.bgp import RF_L2_EVPN
+from ryu.lib.packet.bgp import EvpnNLRI
+
+from ryu.services.protocols.bgp.info_base.evpn import EvpnPath
+from ryu.services.protocols.bgp.info_base.vrf import VrfDest
+from ryu.services.protocols.bgp.info_base.vrf import VrfNlriImportMap
+from ryu.services.protocols.bgp.info_base.vrf import VrfPath
+from ryu.services.protocols.bgp.info_base.vrf import VrfTable
+
+LOG = logging.getLogger('bgpspeaker.info_base.vrfevpn')
+
+
+class VrfEvpnPath(VrfPath):
+    """Represents a way of reaching an EVPN destination with a VPN."""
+    ROUTE_FAMILY = RF_L2_EVPN
+    VPN_PATH_CLASS = EvpnPath
+    VPN_NLRI_CLASS = EvpnNLRI
+
+
+class VrfEvpnDest(VrfDest):
+    """Destination for EVPN VRFs."""
+    ROUTE_FAMILY = RF_L2_EVPN
+
+
+class VrfEvpnTable(VrfTable):
+    """Virtual Routing and Forwarding information base for EVPN."""
+    ROUTE_FAMILY = RF_L2_EVPN
+    VPN_ROUTE_FAMILY = RF_L2_EVPN
+    NLRI_CLASS = EvpnNLRI
+    VRF_PATH_CLASS = VrfEvpnPath
+    VRF_DEST_CLASS = VrfEvpnDest
+
+
+class VrfEvpnNlriImportMap(VrfNlriImportMap):
+    VRF_PATH_CLASS = VrfEvpnPath
+    NLRI_CLASS = EvpnNLRI
diff --git a/ryu/services/protocols/bgp/operator/commands/show/vrf.py 
b/ryu/services/protocols/bgp/operator/commands/show/vrf.py
index 8730665..c89421f 100644
--- a/ryu/services/protocols/bgp/operator/commands/show/vrf.py
+++ b/ryu/services/protocols/bgp/operator/commands/show/vrf.py
@@ -15,10 +15,12 @@ from .route_formatter_mixin import RouteFormatterMixin
 
 LOG = logging.getLogger('bgpspeaker.operator.commands.show.vrf')
 
+SUPPORTED_VRF_RF = ('ipv4', 'ipv6', 'evpn')
+
 
 class Routes(Command, RouteFormatterMixin):
     help_msg = 'show routes present for vrf'
-    param_help_msg = '<vpn-name> <route-family>(ipv4, ipv6)'
+    param_help_msg = '<vpn-name> <route-family>%s' % str(SUPPORTED_VRF_RF)
     command = 'routes'
 
     def __init__(self, *args, **kwargs):
@@ -32,8 +34,9 @@ class Routes(Command, RouteFormatterMixin):
             return WrongParamResp()
         vrf_name = params[0]
         vrf_rf = params[1]
-        if vrf_rf not in ('ipv4', 'ipv6'):
-            return WrongParamResp('route-family not one of (ipv4, ipv6)')
+        if vrf_rf not in SUPPORTED_VRF_RF:
+            return WrongParamResp('route-family not one of %s' %
+                                  str(SUPPORTED_VRF_RF))
 
         from ryu.services.protocols.bgp.operator.internal_api import \
             WrongParamError
diff --git a/ryu/services/protocols/bgp/operator/internal_api.py 
b/ryu/services/protocols/bgp/operator/internal_api.py
index c37b1cf..7f9449e 100644
--- a/ryu/services/protocols/bgp/operator/internal_api.py
+++ b/ryu/services/protocols/bgp/operator/internal_api.py
@@ -180,7 +180,7 @@ class InternalApi(object):
                 route_families.extend(SUPPORTED_GLOBAL_RF)
             else:
                 route_family = RouteFamily(afi, safi)
-                if (route_family not in SUPPORTED_GLOBAL_RF):
+                if route_family not in SUPPORTED_GLOBAL_RF:
                     raise WrongParamError('Not supported address-family'
                                           ' %s, %s' % (afi, safi))
                 route_families.append(route_family)
diff --git a/ryu/services/protocols/bgp/rtconf/vrfs.py 
b/ryu/services/protocols/bgp/rtconf/vrfs.py
index ecf6463..1d6581a 100644
--- a/ryu/services/protocols/bgp/rtconf/vrfs.py
+++ b/ryu/services/protocols/bgp/rtconf/vrfs.py
@@ -22,6 +22,7 @@ import logging
 
 from ryu.lib.packet.bgp import RF_IPv4_UC
 from ryu.lib.packet.bgp import RF_IPv6_UC
+from ryu.lib.packet.bgp import RF_L2_EVPN
 
 from ryu.services.protocols.bgp.utils import validation
 from ryu.services.protocols.bgp.base import get_validator
@@ -54,10 +55,11 @@ VRF_DESC = 'vrf_desc'
 VRF_RF = 'route_family'
 IMPORT_MAPS = 'import_maps'
 
-# Two supported VRF route-families
-VRF_RF_IPV6 = 'ipv6'
+# Supported VRF route-families
 VRF_RF_IPV4 = 'ipv4'
-SUPPORTED_VRF_RF = (VRF_RF_IPV4, VRF_RF_IPV6)
+VRF_RF_IPV6 = 'ipv6'
+VRF_RF_L2_EVPN = 'evpn'
+SUPPORTED_VRF_RF = (VRF_RF_IPV4, VRF_RF_IPV6, VRF_RF_L2_EVPN)
 
 
 # Default configuration values.
@@ -77,8 +79,7 @@ def validate_import_rts(import_rts):
     # Check if we have duplicates
     unique_rts = set(import_rts)
     if len(unique_rts) != len(import_rts):
-        raise ConfigValueError(desc='Duplicate value provided %s' %
-                               (import_rts))
+        raise ConfigValueError(desc='Duplicate value provided %s' % import_rts)
 
     return import_rts
 
@@ -97,7 +98,7 @@ def validate_export_rts(export_rts):
     unique_rts = set(export_rts)
     if len(unique_rts) != len(export_rts):
         raise ConfigValueError(desc='Duplicate value provided in %s' %
-                               (export_rts))
+                               export_rts)
     return export_rts
 
 
@@ -223,6 +224,8 @@ class VrfConf(ConfWithId, ConfWithStats):
             return RF_IPv4_UC
         elif vrf_rf == VRF_RF_IPV6:
             return RF_IPv6_UC
+        elif vrf_rf == VRF_RF_L2_EVPN:
+            return RF_L2_EVPN
         else:
             raise ValueError('Unsupported VRF route family given %s' % vrf_rf)
 
@@ -232,6 +235,8 @@ class VrfConf(ConfWithId, ConfWithStats):
             return VRF_RF_IPV4
         elif route_family == RF_IPv6_UC:
             return VRF_RF_IPV6
+        elif route_family == RF_L2_EVPN:
+            return VRF_RF_L2_EVPN
         else:
             raise ValueError('No supported mapping for route family '
                              'to vrf_route_family exists for %s' %
@@ -322,7 +327,7 @@ class VrfConf(ConfWithId, ConfWithStats):
 
         import_rts = set(import_rts)
         if not import_rts.symmetric_difference(curr_import_rts):
-            return (None, None)
+            return None, None
 
         # Get the difference between current and new RTs
         new_import_rts = import_rts - curr_import_rts
@@ -330,7 +335,7 @@ class VrfConf(ConfWithId, ConfWithStats):
 
         # Update current RTs and notify listeners.
         self._settings[IMPORT_RTS] = import_rts
-        return (new_import_rts, old_import_rts)
+        return new_import_rts, old_import_rts
 
     def _update_export_rts(self, **kwargs):
         export_rts = kwargs.get(EXPORT_RTS)
@@ -381,7 +386,7 @@ class VrfConf(ConfWithId, ConfWithStats):
                                     self.export_rts, self.soo_list))
 
     def __str__(self):
-        return ('VrfConf-%s' % (self.route_dist))
+        return 'VrfConf-%s' % self.route_dist
 
 
 class VrfsConf(BaseConf):
@@ -451,7 +456,7 @@ class VrfsConf(BaseConf):
         vrf_rfs = SUPPORTED_VRF_RF
         # If asked to delete specific route family vrf conf.
         if vrf_rf:
-            vrf_rfs = (vrf_rf)
+            vrf_rfs = vrf_rf
 
         # For all vrf route family asked to be deleted, we collect all deleted
         # VrfConfs
@@ -478,7 +483,6 @@ class VrfsConf(BaseConf):
         if route_dist is None and vrf_id is None:
             raise RuntimeConfigError(desc='To get VRF supply route_dist '
                                      'or vrf_id.')
-        vrf = None
         if route_dist is not None and vrf_id is not None:
             vrf1 = self._vrfs_by_id.get(vrf_id)
             rd_rf_id = VrfConf.create_rd_rf_id(route_dist, vrf_rf)
@@ -500,8 +504,8 @@ class VrfsConf(BaseConf):
         return dict(self._vrfs_by_rd_rf)
 
     @classmethod
-    def get_valid_evts(self):
-        self_valid_evts = super(VrfsConf, self).get_valid_evts()
+    def get_valid_evts(cls):
+        self_valid_evts = super(VrfsConf, cls).get_valid_evts()
         self_valid_evts.update(VrfsConf.VALID_EVT)
         return self_valid_evts
 
-- 
2.7.4


------------------------------------------------------------------------------
_______________________________________________
Ryu-devel mailing list
Ryu-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to