This patch supports Ethernet Auto-discovery Route and
Ethernet Segment Route in BGPSpeaker.

Signed-off-by: Shinpei Muraoka <shinpei.mura...@gmail.com>
---
 ryu/lib/packet/bgp.py                              |   6 +
 ryu/services/protocols/bgp/api/base.py             |   1 +
 ryu/services/protocols/bgp/api/prefix.py           |  60 ++++++-
 ryu/services/protocols/bgp/bgp_sample_conf.py      |  28 ++-
 ryu/services/protocols/bgp/bgpspeaker.py           |  70 ++++++--
 .../protocols/bgp/core_managers/table_manager.py   |  29 +++-
 ryu/services/protocols/bgp/info_base/vrf.py        |  79 ++++++---
 ryu/services/protocols/bgp/utils/validation.py     |   5 +-
 .../bgp/core_managers/test_table_manager.py        |  31 +++-
 .../unit/services/protocols/bgp/test_bgpspeaker.py | 190 +++++++++++++++++++++
 10 files changed, 451 insertions(+), 48 deletions(-)

diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py
index 4a52fd8..3878796 100644
--- a/ryu/lib/packet/bgp.py
+++ b/ryu/lib/packet/bgp.py
@@ -1310,6 +1310,9 @@ class EvpnNLRI(StringifyMixin, _TypeDisp):
 
     ROUTE_TYPE_NAME = None  # must be defined in subclass
 
+    # Reserved value for Ethernet Tag ID.
+    MAX_ET = 0xFFFFFFFF
+
     # Dictionary of ROUTE_TYPE_NAME to subclass.
     # e.g.)
     #   _NAMES = {'eth_ad': EvpnEthernetAutoDiscoveryNLRI, ...}
@@ -3075,6 +3078,9 @@ class 
BGPEvpnEsiLabelExtendedCommunity(_ExtendedCommunity):
     _VALUE_PACK_STR = '!BB2x3s'
     _VALUE_FIELDS = ['subtype', 'flags']
 
+    # Classification for Flags.
+    SINGLE_ACTIVE_BIT = 1 << 0
+
     def __init__(self, label=None, mpls_label=None, vni=None, **kwargs):
         super(BGPEvpnEsiLabelExtendedCommunity, self).__init__()
         self.do_init(BGPEvpnEsiLabelExtendedCommunity, self, kwargs)
diff --git a/ryu/services/protocols/bgp/api/base.py 
b/ryu/services/protocols/bgp/api/base.py
index 3dd252f..125fee9 100644
--- a/ryu/services/protocols/bgp/api/base.py
+++ b/ryu/services/protocols/bgp/api/base.py
@@ -45,6 +45,7 @@ ROUTE_FAMILY = 'route_family'
 EVPN_ROUTE_TYPE = 'route_type'
 EVPN_ESI = 'esi'
 EVPN_ETHERNET_TAG_ID = 'ethernet_tag_id'
+REDUNDANCY_MODE = 'redundancy_mode'
 MAC_ADDR = 'mac_addr'
 IP_ADDR = 'ip_addr'
 IP_PREFIX = 'ip_prefix'
diff --git a/ryu/services/protocols/bgp/api/prefix.py 
b/ryu/services/protocols/bgp/api/prefix.py
index 3cc61af..e175f17 100644
--- a/ryu/services/protocols/bgp/api/prefix.py
+++ b/ryu/services/protocols/bgp/api/prefix.py
@@ -18,13 +18,18 @@
 """
 import logging
 
+from ryu.lib.packet.bgp import EvpnEsi
+from ryu.lib.packet.bgp import EvpnNLRI
+from ryu.lib.packet.bgp import EvpnEthernetAutoDiscoveryNLRI
 from ryu.lib.packet.bgp import EvpnMacIPAdvertisementNLRI
 from ryu.lib.packet.bgp import EvpnInclusiveMulticastEthernetTagNLRI
+from ryu.lib.packet.bgp import EvpnEthernetSegmentNLRI
 from ryu.lib.packet.bgp import EvpnIpPrefixNLRI
 from ryu.lib.packet.bgp import BGPPathAttributePmsiTunnel
 from ryu.services.protocols.bgp.api.base import EVPN_ROUTE_TYPE
 from ryu.services.protocols.bgp.api.base import EVPN_ESI
 from ryu.services.protocols.bgp.api.base import EVPN_ETHERNET_TAG_ID
+from ryu.services.protocols.bgp.api.base import REDUNDANCY_MODE
 from ryu.services.protocols.bgp.api.base import MAC_ADDR
 from ryu.services.protocols.bgp.api.base import IP_ADDR
 from ryu.services.protocols.bgp.api.base import IP_PREFIX
@@ -53,16 +58,49 @@ from ryu.services.protocols.bgp.utils import validation
 
 LOG = logging.getLogger('bgpspeaker.api.prefix')
 
+# Maximum value of the Ethernet Tag ID
+EVPN_MAX_ET = EvpnNLRI.MAX_ET
+
+# ESI Types
+ESI_TYPE_ARBITRARY = EvpnEsi.ARBITRARY
+ESI_TYPE_LACP = EvpnEsi.LACP
+ESI_TYPE_L2_BRIDGE = EvpnEsi.L2_BRIDGE
+ESI_TYPE_MAC_BASED = EvpnEsi.MAC_BASED
+ESI_TYPE_ROUTER_ID = EvpnEsi.ROUTER_ID
+ESI_TYPE_AS_BASED = EvpnEsi.AS_BASED
+SUPPORTED_ESI_TYPES = [
+    ESI_TYPE_ARBITRARY,
+    ESI_TYPE_LACP,
+    ESI_TYPE_L2_BRIDGE,
+    ESI_TYPE_MAC_BASED,
+    ESI_TYPE_ROUTER_ID,
+    ESI_TYPE_AS_BASED,
+]
+
 # Constants used in API calls for EVPN
+EVPN_ETH_AUTO_DISCOVERY = EvpnEthernetAutoDiscoveryNLRI.ROUTE_TYPE_NAME
 EVPN_MAC_IP_ADV_ROUTE = EvpnMacIPAdvertisementNLRI.ROUTE_TYPE_NAME
-EVPN_MULTICAST_ETAG_ROUTE = 
EvpnInclusiveMulticastEthernetTagNLRI.ROUTE_TYPE_NAME
+EVPN_MULTICAST_ETAG_ROUTE = (
+    EvpnInclusiveMulticastEthernetTagNLRI.ROUTE_TYPE_NAME)
+EVPN_ETH_SEGMENT = EvpnEthernetSegmentNLRI.ROUTE_TYPE_NAME
 EVPN_IP_PREFIX_ROUTE = EvpnIpPrefixNLRI.ROUTE_TYPE_NAME
 SUPPORTED_EVPN_ROUTE_TYPES = [
+    EVPN_ETH_AUTO_DISCOVERY,
     EVPN_MAC_IP_ADV_ROUTE,
     EVPN_MULTICAST_ETAG_ROUTE,
+    EVPN_ETH_SEGMENT,
     EVPN_IP_PREFIX_ROUTE,
 ]
 
+# Constants for ESI Label extended community
+REDUNDANCY_MODE_ALL_ACTIVE = 'all_active'
+REDUNDANCY_MODE_SINGLE_ACTIVE = 'single_active'
+REDUNDANCY_MODE_TYPES = [
+    None,
+    REDUNDANCY_MODE_ALL_ACTIVE,
+    REDUNDANCY_MODE_SINGLE_ACTIVE,
+]
+
 # Constants for BGP Tunnel Encapsulation Attribute
 TUNNEL_TYPE_VXLAN = 'vxlan'
 TUNNEL_TYPE_NVGRE = 'nvgre'
@@ -134,6 +172,13 @@ def is_valid_ethernet_tag_id(ethernet_tag_id):
                                conf_value=ethernet_tag_id)
 
 
+@validate(name=REDUNDANCY_MODE)
+def is_valid_redundancy_mode(redundancy_mode):
+    if redundancy_mode not in REDUNDANCY_MODE_TYPES:
+        raise ConfigValueError(conf_name=REDUNDANCY_MODE,
+                               conf_value=redundancy_mode)
+
+
 @validate(name=MAC_ADDR)
 def is_valid_mac_addr(addr):
     if not validation.is_valid_mac(addr):
@@ -241,12 +286,19 @@ def delete_local(route_dist, prefix, 
route_family=VRF_RF_IPV4):
 @RegisterWithArgChecks(name='evpn_prefix.add_local',
                        req_args=[EVPN_ROUTE_TYPE, ROUTE_DISTINGUISHER,
                                  NEXT_HOP],
-                       opt_args=[EVPN_ESI, EVPN_ETHERNET_TAG_ID, MAC_ADDR,
-                                 IP_ADDR, IP_PREFIX, GW_IP_ADDR, EVPN_VNI,
-                                 TUNNEL_TYPE, PMSI_TUNNEL_TYPE])
+                       opt_args=[EVPN_ESI, EVPN_ETHERNET_TAG_ID,
+                                 REDUNDANCY_MODE, MAC_ADDR, IP_ADDR, IP_PREFIX,
+                                 GW_IP_ADDR, EVPN_VNI, TUNNEL_TYPE,
+                                 PMSI_TUNNEL_TYPE])
 def add_evpn_local(route_type, route_dist, next_hop, **kwargs):
     """Adds EVPN route from VRF identified by *route_dist*.
     """
+
+    if(route_type in [EVPN_ETH_AUTO_DISCOVERY, EVPN_ETH_SEGMENT]
+       and kwargs['esi'] == 0):
+        raise ConfigValueError(conf_name=EVPN_ESI,
+                               conf_value=kwargs['esi'])
+
     try:
         # Create new path and insert into appropriate VRF table.
         tm = CORE_MANAGER.get_core_service().table_manager
diff --git a/ryu/services/protocols/bgp/bgp_sample_conf.py 
b/ryu/services/protocols/bgp/bgp_sample_conf.py
index de8caf0..9b9564c 100644
--- a/ryu/services/protocols/bgp/bgp_sample_conf.py
+++ b/ryu/services/protocols/bgp/bgp_sample_conf.py
@@ -3,11 +3,16 @@ import os
 from ryu.services.protocols.bgp.bgpspeaker import RF_VPN_V4
 from ryu.services.protocols.bgp.bgpspeaker import RF_VPN_V6
 from ryu.services.protocols.bgp.bgpspeaker import RF_L2_EVPN
+from ryu.services.protocols.bgp.bgpspeaker import EVPN_MAX_ET
+from ryu.services.protocols.bgp.bgpspeaker import ESI_TYPE_LACP
+from ryu.services.protocols.bgp.bgpspeaker import ESI_TYPE_MAC_BASED
+from ryu.services.protocols.bgp.bgpspeaker import EVPN_ETH_AUTO_DISCOVERY
 from ryu.services.protocols.bgp.bgpspeaker import EVPN_MAC_IP_ADV_ROUTE
 from ryu.services.protocols.bgp.bgpspeaker import TUNNEL_TYPE_VXLAN
 from ryu.services.protocols.bgp.bgpspeaker import EVPN_MULTICAST_ETAG_ROUTE
+from ryu.services.protocols.bgp.bgpspeaker import EVPN_ETH_SEGMENT
 from ryu.services.protocols.bgp.bgpspeaker import EVPN_IP_PREFIX_ROUTE
-
+from ryu.services.protocols.bgp.bgpspeaker import REDUNDANCY_MODE_SINGLE_ACTIVE
 
 # =============================================================================
 # BGP configuration.
@@ -92,6 +97,17 @@ BGP = {
         },
         # Example of EVPN prefix
         {
+            'route_type': EVPN_ETH_AUTO_DISCOVERY,
+            'route_dist': '65001:200',
+            'esi': {
+                'type': ESI_TYPE_LACP,
+                'mac_addr': 'aa:bb:cc:dd:ee:ff',
+                'port_key': 100,
+            },
+            'ethernet_tag_id': EVPN_MAX_ET,
+            'redundancy_mode': REDUNDANCY_MODE_SINGLE_ACTIVE,
+        },
+        {
             'route_type': EVPN_MAC_IP_ADV_ROUTE,
             'route_dist': '65001:200',
             'esi': 0,
@@ -110,6 +126,16 @@ BGP = {
             'ip_addr': '10.40.1.1',
         },
         {
+            'route_type': EVPN_ETH_SEGMENT,
+            'route_dist': '65001:200',
+            'esi': {
+                'type': ESI_TYPE_MAC_BASED,
+                'mac_addr': 'aa:bb:cc:dd:ee:ff',
+                'local_disc': 100,
+            },
+            'ip_addr': '172.17.0.1',
+        },
+        {
             'route_type': EVPN_IP_PREFIX_ROUTE,
             'route_dist': '65001:200',
             'esi': 0,
diff --git a/ryu/services/protocols/bgp/bgpspeaker.py 
b/ryu/services/protocols/bgp/bgpspeaker.py
index c56f8dd..6e90ced 100644
--- a/ryu/services/protocols/bgp/bgpspeaker.py
+++ b/ryu/services/protocols/bgp/bgpspeaker.py
@@ -26,6 +26,7 @@ from ryu.services.protocols.bgp.api.base import PREFIX
 from ryu.services.protocols.bgp.api.base import EVPN_ROUTE_TYPE
 from ryu.services.protocols.bgp.api.base import EVPN_ESI
 from ryu.services.protocols.bgp.api.base import EVPN_ETHERNET_TAG_ID
+from ryu.services.protocols.bgp.api.base import REDUNDANCY_MODE
 from ryu.services.protocols.bgp.api.base import IP_ADDR
 from ryu.services.protocols.bgp.api.base import MAC_ADDR
 from ryu.services.protocols.bgp.api.base import NEXT_HOP
@@ -36,9 +37,17 @@ from ryu.services.protocols.bgp.api.base import ROUTE_FAMILY
 from ryu.services.protocols.bgp.api.base import EVPN_VNI
 from ryu.services.protocols.bgp.api.base import TUNNEL_TYPE
 from ryu.services.protocols.bgp.api.base import PMSI_TUNNEL_TYPE
+from ryu.services.protocols.bgp.api.prefix import EVPN_MAX_ET
+from ryu.services.protocols.bgp.api.prefix import ESI_TYPE_LACP
+from ryu.services.protocols.bgp.api.prefix import ESI_TYPE_L2_BRIDGE
+from ryu.services.protocols.bgp.api.prefix import ESI_TYPE_MAC_BASED
+from ryu.services.protocols.bgp.api.prefix import EVPN_ETH_AUTO_DISCOVERY
 from ryu.services.protocols.bgp.api.prefix import EVPN_MAC_IP_ADV_ROUTE
 from ryu.services.protocols.bgp.api.prefix import EVPN_MULTICAST_ETAG_ROUTE
+from ryu.services.protocols.bgp.api.prefix import EVPN_ETH_SEGMENT
 from ryu.services.protocols.bgp.api.prefix import EVPN_IP_PREFIX_ROUTE
+from ryu.services.protocols.bgp.api.prefix import REDUNDANCY_MODE_ALL_ACTIVE
+from ryu.services.protocols.bgp.api.prefix import REDUNDANCY_MODE_SINGLE_ACTIVE
 from ryu.services.protocols.bgp.api.prefix import TUNNEL_TYPE_VXLAN
 from ryu.services.protocols.bgp.api.prefix import TUNNEL_TYPE_NVGRE
 from ryu.services.protocols.bgp.api.prefix import (
@@ -538,18 +547,25 @@ class BGPSpeaker(object):
     def evpn_prefix_add(self, route_type, route_dist, esi=0,
                         ethernet_tag_id=None, mac_addr=None, ip_addr=None,
                         ip_prefix=None, gw_ip_addr=None, vni=None,
-                        next_hop=None, tunnel_type=None,
-                        pmsi_tunnel_type=None):
+                        next_hop=None, tunnel_type=None, pmsi_tunnel_type=None,
+                        redundancy_mode=None):
         """ This method adds a new EVPN route to be advertised.
 
         ``route_type`` specifies one of the EVPN route type name. The
-        supported route types are EVPN_MAC_IP_ADV_ROUTE,
-        EVPN_MULTICAST_ETAG_ROUTE and EVPN_IP_PREFIX_ROUTE.
+        supported route types are EVPN_ETH_AUTO_DISCOVERY,
+        EVPN_MAC_IP_ADV_ROUTE, EVPN_MULTICAST_ETAG_ROUTE, EVPN_ETH_SEGMENT
+        and EVPN_IP_PREFIX_ROUTE.
 
         ``route_dist`` specifies a route distinguisher value.
 
-        ``esi`` is an integer value to specify the Ethernet Segment
-        Identifier. 0 is the default and denotes a single-homed site.
+        ``esi`` is an value to specify the Ethernet Segment Identifier.
+         0 is the default and denotes a single-homed site.
+         If you want to advertise esi other than 0,
+         it must be set as a dictionary type.
+         The following keys and values must be set.
+          - type: specifies one of the ESI type.
+         The remaining keys and values are the same as the argument of
+         the class corresponding to esi_type.
 
         ``ethernet_tag_id`` specifies the Ethernet Tag ID.
 
@@ -576,6 +592,10 @@ class BGPSpeaker(object):
          used to encode the multicast tunnel identifier.
         This field is advertised only if route_type is
         EVPN_MULTICAST_ETAG_ROUTE.
+
+        ``redundancy_mode`` specifies a redundancy mode type.
+        The supported redundancy mode types are REDUNDANCY_MODE_ALL_ACTIVE
+        and REDUNDANCY_MODE_SINGLE_ACTIVE.
         """
         func_name = 'evpn_prefix.add_local'
 
@@ -593,7 +613,16 @@ class BGPSpeaker(object):
             kwargs[TUNNEL_TYPE] = tunnel_type
 
         # Set route type specific arguments
-        if route_type == EVPN_MAC_IP_ADV_ROUTE:
+        if route_type == EVPN_ETH_AUTO_DISCOVERY:
+            # REDUNDANCY_MODE is parameter for extended community
+            kwargs.update({
+                EVPN_ESI: esi,
+                EVPN_ETHERNET_TAG_ID: ethernet_tag_id,
+                REDUNDANCY_MODE: redundancy_mode,
+            })
+            if vni is not None:
+                kwargs[EVPN_VNI] = vni
+        elif route_type == EVPN_MAC_IP_ADV_ROUTE:
             kwargs.update({
                 EVPN_ESI: esi,
                 EVPN_ETHERNET_TAG_ID: ethernet_tag_id,
@@ -617,6 +646,11 @@ class BGPSpeaker(object):
             elif pmsi_tunnel_type is not None:
                 raise ValueError('Unsupported PMSI tunnel type: %s' %
                                  pmsi_tunnel_type)
+        elif route_type == EVPN_ETH_SEGMENT:
+            kwargs.update({
+                EVPN_ESI: esi,
+                IP_ADDR: ip_addr,
+            })
         elif route_type == EVPN_IP_PREFIX_ROUTE:
             kwargs.update({
                 EVPN_ESI: esi,
@@ -641,8 +675,14 @@ class BGPSpeaker(object):
 
         ``route_dist`` specifies a route distinguisher value.
 
-        ``esi`` is an integer value to specify the Ethernet Segment
-        Identifier. 0 is the default and denotes a single-homed site.
+        ``esi`` is an value to specify the Ethernet Segment Identifier.
+         0 is the default and denotes a single-homed site.
+         If you want to advertise esi other than 0,
+         it must be set as a dictionary type.
+         The following keys and values must be set.
+          - type: specifies one of the ESI type.
+         The remaining keys and values are the same as the argument of
+         the class corresponding to esi_type.
 
         ``ethernet_tag_id`` specifies the Ethernet Tag ID.
 
@@ -659,7 +699,12 @@ class BGPSpeaker(object):
                   ROUTE_DISTINGUISHER: route_dist}
 
         # Set route type specific arguments
-        if route_type == EVPN_MAC_IP_ADV_ROUTE:
+        if route_type == EVPN_ETH_AUTO_DISCOVERY:
+            kwargs.update({
+                EVPN_ESI: esi,
+                EVPN_ETHERNET_TAG_ID: ethernet_tag_id,
+            })
+        elif route_type == EVPN_MAC_IP_ADV_ROUTE:
             kwargs.update({
                 EVPN_ESI: esi,
                 EVPN_ETHERNET_TAG_ID: ethernet_tag_id,
@@ -671,6 +716,11 @@ class BGPSpeaker(object):
                 EVPN_ETHERNET_TAG_ID: ethernet_tag_id,
                 IP_ADDR: ip_addr,
             })
+        elif route_type == EVPN_ETH_SEGMENT:
+            kwargs.update({
+                EVPN_ESI: esi,
+                IP_ADDR: ip_addr,
+            })
         elif route_type == EVPN_IP_PREFIX_ROUTE:
             kwargs.update({
                 EVPN_ETHERNET_TAG_ID: ethernet_tag_id,
diff --git a/ryu/services/protocols/bgp/core_managers/table_manager.py 
b/ryu/services/protocols/bgp/core_managers/table_manager.py
index be3e8bc..0e084b8 100644
--- a/ryu/services/protocols/bgp/core_managers/table_manager.py
+++ b/ryu/services/protocols/bgp/core_managers/table_manager.py
@@ -32,6 +32,7 @@ from ryu.lib.packet.bgp import BGPPathAttributeAsPath
 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_ORIGIN
 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH
 from ryu.lib.packet.bgp import BGP_ATTR_ORIGIN_IGP
+from ryu.lib.packet.bgp import EvpnEsi
 from ryu.lib.packet.bgp import EvpnArbitraryEsi
 from ryu.lib.packet.bgp import EvpnNLRI
 from ryu.lib.packet.bgp import EvpnMacIPAdvertisementNLRI
@@ -483,7 +484,8 @@ class TableCoreManager(object):
 
     def update_vrf_table(self, route_dist, prefix=None, next_hop=None,
                          route_family=None, route_type=None, tunnel_type=None,
-                         is_withdraw=False, pmsi_tunnel_type=None, **kwargs):
+                         is_withdraw=False, redundancy_mode=None,
+                         pmsi_tunnel_type=None, **kwargs):
         """Update a BGP route in the VRF table identified by `route_dist`
         with the given `next_hop`.
 
@@ -496,6 +498,8 @@ class TableCoreManager(object):
         If `route_family` is VRF_RF_L2_EVPN, `route_type` and `kwargs`
         are required to construct EVPN NLRI and `prefix` is ignored.
 
+        ``redundancy_mode`` specifies a redundancy mode type.
+
 `       `pmsi_tunnel_type` specifies the type of the PMSI tunnel attribute
          used to encode the multicast tunnel identifier.
         This field is advertised only if route_type is
@@ -522,6 +526,8 @@ class TableCoreManager(object):
                 desc='VRF table  does not exist: route_dist=%s, '
                      'route_family=%s' % (route_dist, route_family))
 
+        vni = kwargs.get('vni', None)
+
         if route_family == VRF_RF_IPV4:
             if not is_valid_ipv4_prefix(prefix):
                 raise BgpCoreError(desc='Invalid IPv4 prefix: %s' % prefix)
@@ -541,14 +547,20 @@ class TableCoreManager(object):
             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))
-            if 'vni' in kwargs:
-                # Disable to generate MPLS labels, because encapsulation type
-                # is not MPLS.
+                if isinstance(esi, dict):
+                    esi_type = esi.get('type', 0)
+                    esi_class = EvpnEsi._lookup_type(esi_type)
+                    kwargs['esi'] = esi_class.from_jsondict(esi)
+                else:  # isinstance(esi, numbers.Integral)
+                    kwargs['esi'] = EvpnArbitraryEsi(
+                        type_desc.Int9.from_user(esi))
+            if vni is not None:
+                # Disable to generate MPLS labels,
+                # because encapsulation type is not MPLS.
                 from ryu.services.protocols.bgp.api.prefix import (
                     TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE)
-                assert tunnel_type in [TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE]
+                assert tunnel_type in [
+                    None, TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE]
                 gen_lbl = False
             prefix = subclass(**kwargs)
         else:
@@ -559,7 +571,8 @@ class TableCoreManager(object):
         # withdrawal. Hence multiple withdrawals have not side effect.
         return vrf_table.insert_vrf_path(
             nlri=prefix, next_hop=next_hop, gen_lbl=gen_lbl,
-            is_withdraw=is_withdraw, tunnel_type=tunnel_type,
+            is_withdraw=is_withdraw, redundancy_mode=redundancy_mode,
+            vni=vni, tunnel_type=tunnel_type,
             pmsi_tunnel_type=pmsi_tunnel_type)
 
     def update_global_table(self, prefix, next_hop=None, is_withdraw=False):
diff --git a/ryu/services/protocols/bgp/info_base/vrf.py 
b/ryu/services/protocols/bgp/info_base/vrf.py
index f6b50bc..c4e41ef 100644
--- a/ryu/services/protocols/bgp/info_base/vrf.py
+++ b/ryu/services/protocols/bgp/info_base/vrf.py
@@ -28,10 +28,13 @@ from ryu.lib.packet.bgp import 
BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE
 from ryu.lib.packet.bgp import BGP_ATTR_TYPE_MULTI_EXIT_DISC
 from ryu.lib.packet.bgp import BGPPathAttributeOrigin
 from ryu.lib.packet.bgp import BGPPathAttributeAsPath
+from ryu.lib.packet.bgp import EvpnEthernetSegmentNLRI
 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 BGPEncapsulationExtendedCommunity
+from ryu.lib.packet.bgp import BGPEvpnEsiLabelExtendedCommunity
+from ryu.lib.packet.bgp import BGPEvpnEsImportRTExtendedCommunity
 from ryu.lib.packet.bgp import BGPPathAttributePmsiTunnel
 from ryu.lib.packet.bgp import PmsiTunnelIdIngressReplication
 from ryu.lib.packet.bgp import RF_L2_EVPN
@@ -221,6 +224,28 @@ class VrfTable(Table):
         label_list = []
         vrf_conf = self.vrf_conf
         if not is_withdraw:
+            table_manager = self._core_service.table_manager
+            if gen_lbl and next_hop:
+                # Label per next_hop demands we use a different label
+                # per next_hop. Here connected interfaces are advertised per
+                # VRF.
+                label_key = (vrf_conf.route_dist, next_hop)
+                nh_label = table_manager.get_nexthop_label(label_key)
+                if not nh_label:
+                    nh_label = table_manager.get_next_vpnv4_label()
+                    table_manager.set_nexthop_label(label_key, nh_label)
+                label_list.append(nh_label)
+
+            elif gen_lbl:
+                # If we do not have next_hop, get a new label.
+                label_list.append(table_manager.get_next_vpnv4_label())
+
+            # Set MPLS labels with the generated labels
+            if gen_lbl and isinstance(nlri, EvpnMacIPAdvertisementNLRI):
+                nlri.mpls_labels = label_list[:2]
+            elif gen_lbl and isinstance(nlri, EvpnIpPrefixNLRI):
+                nlri.mpls_label = label_list[0]
+
             # Create a dictionary for path-attrs.
             pattrs = OrderedDict()
 
@@ -232,6 +257,15 @@ class VrfTable(Table):
                 EXPECTED_ORIGIN)
             pattrs[BGP_ATTR_TYPE_AS_PATH] = BGPPathAttributeAsPath([])
             communities = []
+
+            # Set ES-Import Route Target
+            if isinstance(nlri, EvpnEthernetSegmentNLRI):
+                subtype = 2
+                es_import = nlri.esi.mac_addr
+                communities.append(BGPEvpnEsImportRTExtendedCommunity(
+                                   subtype=subtype,
+                                   es_import=es_import))
+
             for rt in vrf_conf.export_rts:
                 as_num, local_admin = rt.split(':')
                 subtype = 2
@@ -253,28 +287,35 @@ class VrfTable(Table):
                 communities.append(
                     BGPEncapsulationExtendedCommunity.from_str(tunnel_type))
 
+            # Set ESI Label Extended Community
+            redundancy_mode = kwargs.get('redundancy_mode', None)
+            if redundancy_mode is not None:
+                subtype = 1
+                flags = 0
+
+                from ryu.services.protocols.bgp.api.prefix import (
+                    REDUNDANCY_MODE_SINGLE_ACTIVE)
+                if redundancy_mode == REDUNDANCY_MODE_SINGLE_ACTIVE:
+                    flags |= BGPEvpnEsiLabelExtendedCommunity.SINGLE_ACTIVE_BIT
+
+                vni = kwargs.get('vni', None)
+                if vni is not None:
+                    communities.append(BGPEvpnEsiLabelExtendedCommunity(
+                        subtype=subtype,
+                        flags=flags,
+                        vni=vni))
+                else:
+                    communities.append(BGPEvpnEsiLabelExtendedCommunity(
+                                       subtype=subtype,
+                                       flags=flags,
+                                       mpls_label=label_list[0]))
+
             pattrs[BGP_ATTR_TYPE_EXTENDED_COMMUNITIES] = \
                 BGPPathAttributeExtendedCommunities(communities=communities)
             if vrf_conf.multi_exit_disc:
                 pattrs[BGP_ATTR_TYPE_MULTI_EXIT_DISC] = \
                     BGPPathAttributeMultiExitDisc(vrf_conf.multi_exit_disc)
 
-            table_manager = self._core_service.table_manager
-            if gen_lbl and next_hop:
-                # Label per next_hop demands we use a different label
-                # per next_hop. Here connected interfaces are advertised per
-                # VRF.
-                label_key = (vrf_conf.route_dist, next_hop)
-                nh_label = table_manager.get_nexthop_label(label_key)
-                if not nh_label:
-                    nh_label = table_manager.get_next_vpnv4_label()
-                    table_manager.set_nexthop_label(label_key, nh_label)
-                label_list.append(nh_label)
-
-            elif gen_lbl:
-                # If we do not have next_hop, get a new label.
-                label_list.append(table_manager.get_next_vpnv4_label())
-
             # Set PMSI Tunnel Attribute
             pmsi_tunnel_type = kwargs.get('pmsi_tunnel_type', None)
             if pmsi_tunnel_type is not None:
@@ -290,12 +331,6 @@ class VrfTable(Table):
                                                tunnel_type=pmsi_tunnel_type,
                                                tunnel_id=tunnel_id)
 
-            # Set MPLS labels with the generated labels
-            if gen_lbl and isinstance(nlri, EvpnMacIPAdvertisementNLRI):
-                nlri.mpls_labels = label_list[:2]
-            elif gen_lbl and isinstance(nlri, EvpnIpPrefixNLRI):
-                nlri.mpls_label = label_list[0]
-
         puid = self.VRF_PATH_CLASS.create_puid(
             vrf_conf.route_dist, nlri.prefix)
 
diff --git a/ryu/services/protocols/bgp/utils/validation.py 
b/ryu/services/protocols/bgp/utils/validation.py
index ff6ed50..35dc4c7 100644
--- a/ryu/services/protocols/bgp/utils/validation.py
+++ b/ryu/services/protocols/bgp/utils/validation.py
@@ -240,8 +240,9 @@ def is_valid_ext_comm_attr(attr):
 
 def is_valid_esi(esi):
     """Returns True if the given EVPN Ethernet SegmentEthernet ID is valid."""
-    # Note: Currently, only integer type value is supported
-    return isinstance(esi, numbers.Integral)
+    if isinstance(esi, numbers.Integral):
+        return 0 <= esi <= 0xffffffffffffffffff
+    return isinstance(esi, dict)
 
 
 def is_valid_ethernet_tag_id(etag_id):
diff --git 
a/ryu/tests/unit/services/protocols/bgp/core_managers/test_table_manager.py 
b/ryu/tests/unit/services/protocols/bgp/core_managers/test_table_manager.py
index a53e4c4..48c99e9 100644
--- a/ryu/tests/unit/services/protocols/bgp/core_managers/test_table_manager.py
+++ b/ryu/tests/unit/services/protocols/bgp/core_managers/test_table_manager.py
@@ -31,8 +31,12 @@ from ryu.lib.packet.bgp import BGP_ATTR_TYPE_AS_PATH
 from ryu.lib.packet.bgp import IPAddrPrefix
 from ryu.lib.packet.bgp import IP6AddrPrefix
 from ryu.lib.packet.bgp import EvpnArbitraryEsi
+from ryu.lib.packet.bgp import EvpnLACPEsi
+from ryu.lib.packet.bgp import EvpnEthernetAutoDiscoveryNLRI
 from ryu.lib.packet.bgp import EvpnMacIPAdvertisementNLRI
 from ryu.lib.packet.bgp import EvpnInclusiveMulticastEthernetTagNLRI
+from ryu.services.protocols.bgp.bgpspeaker import EVPN_MAX_ET
+from ryu.services.protocols.bgp.bgpspeaker import ESI_TYPE_LACP
 from ryu.services.protocols.bgp.core import BgpCoreError
 from ryu.services.protocols.bgp.core_managers import table_manager
 from ryu.services.protocols.bgp.rtconf.vrfs import VRF_RF_IPV4
@@ -115,7 +119,7 @@ class Test_TableCoreManager(unittest.TestCase):
                                     next_hop, route_family, route_type,
                                     **kwargs)
 
-    def test_update_vrf_table_l2_evpn_with_esi(self):
+    def test_update_vrf_table_l2_evpn_with_esi_int(self):
         # Prepare test data
         route_dist = '65000:100'
         prefix_str = None  # should be ignored
@@ -139,6 +143,31 @@ class Test_TableCoreManager(unittest.TestCase):
                                     next_hop, route_family, route_type,
                                     **kwargs)
 
+    def test_update_vrf_table_l2_evpn_with_esi_dict(self):
+        # Prepare test data
+        route_dist = '65000:100'
+        prefix_str = None  # should be ignored
+        kwargs = {
+            'ethernet_tag_id': EVPN_MAX_ET,
+        }
+        esi = EvpnLACPEsi(mac_addr='aa:bb:cc:dd:ee:ff', port_key=100)
+        prefix_inst = EvpnEthernetAutoDiscoveryNLRI(
+            route_dist=route_dist,
+            esi=esi,
+            **kwargs)
+        next_hop = '0.0.0.0'
+        route_family = VRF_RF_L2_EVPN
+        route_type = EvpnEthernetAutoDiscoveryNLRI.ROUTE_TYPE_NAME
+        kwargs['esi'] = {
+            'type': ESI_TYPE_LACP,
+            'mac_addr': 'aa:bb:cc:dd:ee:ff',
+            'port_key': 100,
+        }
+
+        self._test_update_vrf_table(prefix_inst, route_dist, prefix_str,
+                                    next_hop, route_family, route_type,
+                                    **kwargs)
+
     def test_update_vrf_table_l2_evpn_without_esi(self):
         # Prepare test data
         route_dist = '65000:100'
diff --git a/ryu/tests/unit/services/protocols/bgp/test_bgpspeaker.py 
b/ryu/tests/unit/services/protocols/bgp/test_bgpspeaker.py
index d5e4908..da0f482 100644
--- a/ryu/tests/unit/services/protocols/bgp/test_bgpspeaker.py
+++ b/ryu/tests/unit/services/protocols/bgp/test_bgpspeaker.py
@@ -23,6 +23,12 @@ except ImportError:
 from nose.tools import raises
 
 from ryu.services.protocols.bgp import bgpspeaker
+from ryu.services.protocols.bgp.bgpspeaker import EVPN_MAX_ET
+from ryu.services.protocols.bgp.bgpspeaker import ESI_TYPE_LACP
+from ryu.services.protocols.bgp.api.prefix import ESI_TYPE_L2_BRIDGE
+from ryu.services.protocols.bgp.bgpspeaker import ESI_TYPE_MAC_BASED
+from ryu.services.protocols.bgp.api.prefix import REDUNDANCY_MODE_ALL_ACTIVE
+from ryu.services.protocols.bgp.api.prefix import REDUNDANCY_MODE_SINGLE_ACTIVE
 
 
 LOG = logging.getLogger(__name__)
@@ -36,6 +42,86 @@ class Test_BGPSpeaker(unittest.TestCase):
     @mock.patch('ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__',
                 mock.MagicMock(return_value=None))
     @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call')
+    def test_evpn_prefix_add_eth_auto_discovery(self, mock_call):
+        # Prepare test data
+        route_type = bgpspeaker.EVPN_ETH_AUTO_DISCOVERY
+        route_dist = '65000:100'
+        esi = {
+            'type': ESI_TYPE_LACP,
+            'mac_addr': 'aa:bb:cc:dd:ee:ff',
+            'port_key': 100,
+        }
+        ethernet_tag_id = EVPN_MAX_ET
+        redundancy_mode = REDUNDANCY_MODE_ALL_ACTIVE
+        next_hop = '0.0.0.0'
+        expected_kwargs = {
+            'route_type': route_type,
+            'route_dist': route_dist,
+            'esi': esi,
+            'ethernet_tag_id': ethernet_tag_id,
+            'redundancy_mode': redundancy_mode,
+            'next_hop': next_hop,
+        }
+
+        # Test
+        speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1')
+        speaker.evpn_prefix_add(
+            route_type=route_type,
+            route_dist=route_dist,
+            esi=esi,
+            ethernet_tag_id=ethernet_tag_id,
+            redundancy_mode=redundancy_mode,
+        )
+
+        # Check
+        mock_call.assert_called_with(
+            'evpn_prefix.add_local', **expected_kwargs)
+
+    @mock.patch(
+        'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__',
+        mock.MagicMock(return_value=None))
+    @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call')
+    def test_evpn_prefix_add_eth_auto_discovery_vni(self, mock_call):
+        # Prepare test data
+        route_type = bgpspeaker.EVPN_ETH_AUTO_DISCOVERY
+        route_dist = '65000:100'
+        esi = {
+            'type': ESI_TYPE_L2_BRIDGE,
+            'mac_addr': 'aa:bb:cc:dd:ee:ff',
+            'priority': 100,
+        }
+        ethernet_tag_id = EVPN_MAX_ET
+        redundancy_mode = REDUNDANCY_MODE_SINGLE_ACTIVE
+        vni = 500
+        next_hop = '0.0.0.0'
+        expected_kwargs = {
+            'route_type': route_type,
+            'route_dist': route_dist,
+            'esi': esi,
+            'ethernet_tag_id': ethernet_tag_id,
+            'redundancy_mode': redundancy_mode,
+            'vni': vni,
+            'next_hop': next_hop,
+        }
+
+        # Test
+        speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1')
+        speaker.evpn_prefix_add(
+            route_type=route_type,
+            route_dist=route_dist,
+            esi=esi,
+            ethernet_tag_id=ethernet_tag_id,
+            redundancy_mode=redundancy_mode,
+            vni=vni
+        )
+
+        # Check
+        mock_call.assert_called_with(
+            'evpn_prefix.add_local', **expected_kwargs)
+
+    @mock.patch('ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__',
+                mock.MagicMock(return_value=None))
+    @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call')
     def test_evpn_prefix_add_mac_ip_adv(self, mock_call):
         # Prepare test data
         route_type = bgpspeaker.EVPN_MAC_IP_ADV_ROUTE
@@ -195,6 +281,42 @@ class Test_BGPSpeaker(unittest.TestCase):
         'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__',
         mock.MagicMock(return_value=None))
     @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call')
+    def test_evpn_prefix_add_eth_segment(self, mock_call):
+        # Prepare test data
+        route_type = bgpspeaker.EVPN_ETH_SEGMENT
+        route_dist = '65000:100'
+        esi = {
+            'type': ESI_TYPE_MAC_BASED,
+            'mac_addr': 'aa:bb:cc:dd:ee:ff',
+            'local_disc': 100,
+        }
+        ip_addr = '192.168.0.1'
+        next_hop = '0.0.0.0'
+        expected_kwargs = {
+            'route_type': route_type,
+            'route_dist': route_dist,
+            'esi': esi,
+            'ip_addr': ip_addr,
+            'next_hop': next_hop,
+        }
+
+        # Test
+        speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1')
+        speaker.evpn_prefix_add(
+            route_type=route_type,
+            route_dist=route_dist,
+            esi=esi,
+            ip_addr=ip_addr,
+        )
+
+        # Check
+        mock_call.assert_called_with(
+            'evpn_prefix.add_local', **expected_kwargs)
+
+    @mock.patch(
+        'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__',
+        mock.MagicMock(return_value=None))
+    @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call')
     def test_evpn_prefix_add_ip_prefix_route(self, mock_call):
         # Prepare test data
         route_type = bgpspeaker.EVPN_IP_PREFIX_ROUTE
@@ -303,6 +425,40 @@ class Test_BGPSpeaker(unittest.TestCase):
         mock_call.assert_called_with(
             'evpn_prefix.add_local', 'Invalid arguments detected')
 
+    @mock.patch(
+        'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__',
+        mock.MagicMock(return_value=None))
+    @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call')
+    def test_evpn_prefix_del_auto_discovery(self, mock_call):
+        # Prepare test data
+        route_type = bgpspeaker.EVPN_ETH_AUTO_DISCOVERY
+        route_dist = '65000:100'
+        esi = {
+            'type': ESI_TYPE_LACP,
+            'mac_addr': 'aa:bb:cc:dd:ee:ff',
+            'port_key': 100,
+        }
+        ethernet_tag_id = EVPN_MAX_ET
+        expected_kwargs = {
+            'route_type': route_type,
+            'route_dist': route_dist,
+            'esi': esi,
+            'ethernet_tag_id': ethernet_tag_id,
+        }
+
+        # Test
+        speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1')
+        speaker.evpn_prefix_del(
+            route_type=route_type,
+            route_dist=route_dist,
+            esi=esi,
+            ethernet_tag_id=ethernet_tag_id,
+        )
+
+        # Check
+        mock_call.assert_called_with(
+            'evpn_prefix.delete_local', **expected_kwargs)
+
     @mock.patch('ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__',
                 mock.MagicMock(return_value=None))
     @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call')
@@ -405,6 +561,40 @@ class Test_BGPSpeaker(unittest.TestCase):
         'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__',
         mock.MagicMock(return_value=None))
     @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call')
+    def test_evpn_prefix_del_eth_segment(self, mock_call):
+        # Prepare test data
+        route_type = bgpspeaker.EVPN_ETH_SEGMENT
+        route_dist = '65000:100'
+        esi = {
+            'esi_type': ESI_TYPE_MAC_BASED,
+            'mac_addr': 'aa:bb:cc:dd:ee:ff',
+            'local_disc': 100,
+        }
+        ip_addr = '192.168.0.1'
+        expected_kwargs = {
+            'route_type': route_type,
+            'route_dist': route_dist,
+            'esi': esi,
+            'ip_addr': ip_addr,
+        }
+
+        # Test
+        speaker = bgpspeaker.BGPSpeaker(65000, '10.0.0.1')
+        speaker.evpn_prefix_del(
+            route_type=route_type,
+            route_dist=route_dist,
+            esi=esi,
+            ip_addr=ip_addr,
+        )
+
+        # Check
+        mock_call.assert_called_with(
+            'evpn_prefix.delete_local', **expected_kwargs)
+
+    @mock.patch(
+        'ryu.services.protocols.bgp.bgpspeaker.BGPSpeaker.__init__',
+        mock.MagicMock(return_value=None))
+    @mock.patch('ryu.services.protocols.bgp.bgpspeaker.call')
     def test_evpn_prefix_del_ip_prefix_route(self, mock_call):
         # Prepare test data
         route_type = bgpspeaker.EVPN_IP_PREFIX_ROUTE
-- 
2.7.4


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

Reply via email to