# Copyright (C) 2014 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.
"""This module offers a class to enable your code to speak BGP protocol.

"""

import netaddr
from os_ken.lib import hub
from os_ken.lib import ip
from os_ken.lib.packet.bgp import (
    BGPFlowSpecTrafficActionCommunity,
    BGPFlowSpecVlanActionCommunity,
    BGPFlowSpecTPIDActionCommunity,
)

from os_ken.services.protocols.bgp.core_manager import CORE_MANAGER
from os_ken.services.protocols.bgp.signals.emit import BgpSignalBus
from os_ken.services.protocols.bgp.api.base import call
from os_ken.services.protocols.bgp.api.base import PREFIX
from os_ken.services.protocols.bgp.api.base import EVPN_ROUTE_TYPE
from os_ken.services.protocols.bgp.api.base import EVPN_ESI
from os_ken.services.protocols.bgp.api.base import EVPN_ETHERNET_TAG_ID
from os_ken.services.protocols.bgp.api.base import REDUNDANCY_MODE
from os_ken.services.protocols.bgp.api.base import IP_ADDR
from os_ken.services.protocols.bgp.api.base import MAC_ADDR
from os_ken.services.protocols.bgp.api.base import NEXT_HOP
from os_ken.services.protocols.bgp.api.base import IP_PREFIX
from os_ken.services.protocols.bgp.api.base import GW_IP_ADDR
from os_ken.services.protocols.bgp.api.base import ROUTE_DISTINGUISHER
from os_ken.services.protocols.bgp.api.base import ROUTE_FAMILY
from os_ken.services.protocols.bgp.api.base import EVPN_VNI
from os_ken.services.protocols.bgp.api.base import TUNNEL_TYPE
from os_ken.services.protocols.bgp.api.base import PMSI_TUNNEL_TYPE
from os_ken.services.protocols.bgp.api.base import MAC_MOBILITY
from os_ken.services.protocols.bgp.api.base import TUNNEL_ENDPOINT_IP
from os_ken.services.protocols.bgp.api.prefix import EVPN_MAX_ET
from os_ken.services.protocols.bgp.api.prefix import ESI_TYPE_LACP
from os_ken.services.protocols.bgp.api.prefix import ESI_TYPE_L2_BRIDGE
from os_ken.services.protocols.bgp.api.prefix import ESI_TYPE_MAC_BASED
from os_ken.services.protocols.bgp.api.prefix import EVPN_ETH_AUTO_DISCOVERY
from os_ken.services.protocols.bgp.api.prefix import EVPN_MAC_IP_ADV_ROUTE
from os_ken.services.protocols.bgp.api.prefix import EVPN_MULTICAST_ETAG_ROUTE
from os_ken.services.protocols.bgp.api.prefix import EVPN_ETH_SEGMENT
from os_ken.services.protocols.bgp.api.prefix import EVPN_IP_PREFIX_ROUTE
from os_ken.services.protocols.bgp.api.prefix import REDUNDANCY_MODE_ALL_ACTIVE
from os_ken.services.protocols.bgp.api.prefix import REDUNDANCY_MODE_SINGLE_ACTIVE
from os_ken.services.protocols.bgp.api.prefix import TUNNEL_TYPE_VXLAN
from os_ken.services.protocols.bgp.api.prefix import TUNNEL_TYPE_NVGRE
from os_ken.services.protocols.bgp.api.prefix import (
    PMSI_TYPE_NO_TUNNEL_INFO,
    PMSI_TYPE_INGRESS_REP)
from os_ken.services.protocols.bgp.api.prefix import (
    FLOWSPEC_FAMILY,
    FLOWSPEC_FAMILY_IPV4,
    FLOWSPEC_FAMILY_VPNV4,
    FLOWSPEC_FAMILY_IPV6,
    FLOWSPEC_FAMILY_VPNV6,
    FLOWSPEC_FAMILY_L2VPN,
    FLOWSPEC_RULES,
    FLOWSPEC_ACTIONS)
from os_ken.services.protocols.bgp.model import ReceivedRoute
from os_ken.services.protocols.bgp.rtconf.common import LOCAL_AS
from os_ken.services.protocols.bgp.rtconf.common import ROUTER_ID
from os_ken.services.protocols.bgp.rtconf.common import CLUSTER_ID
from os_ken.services.protocols.bgp.rtconf.common import BGP_SERVER_HOSTS
from os_ken.services.protocols.bgp.rtconf.common import BGP_SERVER_PORT
from os_ken.services.protocols.bgp.rtconf.common import DEFAULT_BGP_SERVER_HOSTS
from os_ken.services.protocols.bgp.rtconf.common import DEFAULT_BGP_SERVER_PORT
from os_ken.services.protocols.bgp.rtconf.common import (
    DEFAULT_REFRESH_MAX_EOR_TIME, DEFAULT_REFRESH_STALEPATH_TIME)
from os_ken.services.protocols.bgp.rtconf.common import DEFAULT_LABEL_RANGE
from os_ken.services.protocols.bgp.rtconf.common import REFRESH_MAX_EOR_TIME
from os_ken.services.protocols.bgp.rtconf.common import REFRESH_STALEPATH_TIME
from os_ken.services.protocols.bgp.rtconf.common import LABEL_RANGE
from os_ken.services.protocols.bgp.rtconf.common import ALLOW_LOCAL_AS_IN_COUNT
from os_ken.services.protocols.bgp.rtconf.common import LOCAL_PREF
from os_ken.services.protocols.bgp.rtconf.common import DEFAULT_LOCAL_PREF
from os_ken.services.protocols.bgp.rtconf import neighbors
from os_ken.services.protocols.bgp.rtconf import vrfs
from os_ken.services.protocols.bgp.rtconf.base import CAP_MBGP_IPV4
from os_ken.services.protocols.bgp.rtconf.base import CAP_MBGP_IPV6
from os_ken.services.protocols.bgp.rtconf.base import CAP_MBGP_VPNV4
from os_ken.services.protocols.bgp.rtconf.base import CAP_MBGP_VPNV6
from os_ken.services.protocols.bgp.rtconf.base import CAP_MBGP_EVPN
from os_ken.services.protocols.bgp.rtconf.base import CAP_MBGP_IPV4FS
from os_ken.services.protocols.bgp.rtconf.base import CAP_MBGP_IPV6FS
from os_ken.services.protocols.bgp.rtconf.base import CAP_MBGP_VPNV4FS
from os_ken.services.protocols.bgp.rtconf.base import CAP_MBGP_VPNV6FS
from os_ken.services.protocols.bgp.rtconf.base import CAP_MBGP_L2VPNFS
from os_ken.services.protocols.bgp.rtconf.base import CAP_ENHANCED_REFRESH
from os_ken.services.protocols.bgp.rtconf.base import CAP_FOUR_OCTET_AS_NUMBER
from os_ken.services.protocols.bgp.rtconf.base import MULTI_EXIT_DISC
from os_ken.services.protocols.bgp.rtconf.base import SITE_OF_ORIGINS
from os_ken.services.protocols.bgp.rtconf.neighbors import (
    DEFAULT_CAP_MBGP_IPV4,
    DEFAULT_CAP_MBGP_IPV6,
    DEFAULT_CAP_MBGP_VPNV4,
    DEFAULT_CAP_MBGP_VPNV6,
    DEFAULT_CAP_MBGP_EVPN,
    DEFAULT_CAP_MBGP_IPV4FS,
    DEFAULT_CAP_MBGP_IPV6FS,
    DEFAULT_CAP_MBGP_VPNV4FS,
    DEFAULT_CAP_MBGP_VPNV6FS,
    DEFAULT_CAP_MBGP_L2VPNFS,
    DEFAULT_CAP_ENHANCED_REFRESH,
    DEFAULT_CAP_FOUR_OCTET_AS_NUMBER,
    DEFAULT_CONNECT_MODE,
    REMOTE_PORT,
    DEFAULT_BGP_PORT,
    PEER_NEXT_HOP,
    PASSWORD,
    DEFAULT_IS_ROUTE_SERVER_CLIENT,
    IS_ROUTE_SERVER_CLIENT,
    DEFAULT_IS_ROUTE_REFLECTOR_CLIENT,
    IS_ROUTE_REFLECTOR_CLIENT,
    DEFAULT_IS_NEXT_HOP_SELF,
    IS_NEXT_HOP_SELF,
    CONNECT_MODE,
    LOCAL_ADDRESS,
    LOCAL_PORT,
)
from os_ken.services.protocols.bgp.rtconf.vrfs import SUPPORTED_VRF_RF
from os_ken.services.protocols.bgp.info_base.base import Filter
from os_ken.services.protocols.bgp.info_base.ipv4 import Ipv4Path
from os_ken.services.protocols.bgp.info_base.ipv6 import Ipv6Path
from os_ken.services.protocols.bgp.info_base.vpnv4 import Vpnv4Path
from os_ken.services.protocols.bgp.info_base.vpnv6 import Vpnv6Path
from os_ken.services.protocols.bgp.info_base.evpn import EvpnPath


NEIGHBOR_CONF_MED = MULTI_EXIT_DISC  # for backward compatibility
RF_VPN_V4 = vrfs.VRF_RF_IPV4
RF_VPN_V6 = vrfs.VRF_RF_IPV6
RF_L2_EVPN = vrfs.VRF_RF_L2_EVPN
RF_VPNV4_FLOWSPEC = vrfs.VRF_RF_IPV4_FLOWSPEC
RF_VPNV6_FLOWSPEC = vrfs.VRF_RF_IPV6_FLOWSPEC
RF_L2VPN_FLOWSPEC = vrfs.VRF_RF_L2VPN_FLOWSPEC

# Constants for the Traffic Filtering Actions of Flow Specification.
FLOWSPEC_TA_SAMPLE = BGPFlowSpecTrafficActionCommunity.SAMPLE
FLOWSPEC_TA_TERMINAL = BGPFlowSpecTrafficActionCommunity.TERMINAL

# Constants for the VLAN Actions of Flow Specification.
FLOWSPEC_VLAN_POP = BGPFlowSpecVlanActionCommunity.POP
FLOWSPEC_VLAN_PUSH = BGPFlowSpecVlanActionCommunity.PUSH
FLOWSPEC_VLAN_SWAP = BGPFlowSpecVlanActionCommunity.SWAP
FLOWSPEC_VLAN_RW_INNER = BGPFlowSpecVlanActionCommunity.REWRITE_INNER
FLOWSPEC_VLAN_RW_OUTER = BGPFlowSpecVlanActionCommunity.REWRITE_OUTER

# Constants for the TPID Actions of Flow Specification.
FLOWSPEC_TPID_TI = BGPFlowSpecTPIDActionCommunity.TI
FLOWSPEC_TPID_TO = BGPFlowSpecTPIDActionCommunity.TO


class EventPrefix(object):
    """
    Used to pass an update on any best remote path to
    best_path_change_handler.

    ================ ======================================================
    Attribute        Description
    ================ ======================================================
    remote_as        The AS number of a peer that caused this change
    route_dist       None in the case of IPv4 or IPv6 family
    prefix           A prefix was changed
    nexthop          The nexthop of the changed prefix
    label            MPLS label for VPNv4, VPNv6 or EVPN prefix
    path             An instance of ``info_base.base.Path`` subclass
    is_withdraw      True if this prefix has gone otherwise False
    ================ ======================================================
    """

    def __init__(self, path, is_withdraw):
        self.path = path
        self.is_withdraw = is_withdraw

    @property
    def remote_as(self):
        return self.path.source.remote_as

    @property
    def route_dist(self):
        if (isinstance(self.path, Vpnv4Path)
                or isinstance(self.path, Vpnv6Path)
                or isinstance(self.path, EvpnPath)):
            return self.path.nlri.route_dist
        else:
            return None

    @property
    def prefix(self):
        if isinstance(self.path, Ipv4Path) or isinstance(self.path, Ipv6Path):
            return self.path.nlri.addr + '/' + str(self.path.nlri.length)
        elif (isinstance(self.path, Vpnv4Path)
              or isinstance(self.path, Vpnv6Path)
              or isinstance(self.path, EvpnPath)):
            return self.path.nlri.prefix
        else:
            return None

    @property
    def nexthop(self):
        return self.path.nexthop

    @property
    def label(self):
        if (isinstance(self.path, Vpnv4Path)
                or isinstance(self.path, Vpnv6Path)
                or isinstance(self.path, EvpnPath)):
            return getattr(self.path.nlri, 'label_list', None)
        else:
            return None


class BGPSpeaker(object):
    """Class to provide the APIs of OSKen BGP Speaker.

    ``as_number`` specifies an Autonomous Number. It must be an integer
    between 1 and 65535.

    ``router_id`` specifies BGP router identifier. It must be the
    string representation of an IPv4 address (e.g. 10.0.0.1).

    ``bgp_server_host`` specifies a list of TCP listen host addresses.

    ``bgp_server_port`` specifies TCP listen port number. 179 is
    used if not specified.

    ``refresh_stalepath_time`` causes the BGP speaker to remove
    stale routes from the BGP table after the timer expires, even
    if the speaker does not receive a Router-Refresh End-of-RIB
    message. This feature is disabled (not implemented yet).

    ``refresh_max_eor_time`` causes the BGP speaker to generate a
    Route-Refresh End-of-RIB message if it was not able to
    generate one due to route flapping. This feature is disabled
    (not implemented yet).

    ``best_path_change_handler``, if specified, is called when any
    best remote path is changed due to an update message or remote
    peer down. The handler is supposed to take one argument, the
    instance of an EventPrefix class instance.

    ``adj_rib_in_change_handler``, if specified, is called when any
    adj-RIB-in path is changed due to an update message or remote
    peer down. The given handler should take three argument, the
    instance of an EventPrefix class instance, str type peer's IP address
    and int type peer's AS number.

    ``peer_down_handler``, if specified, is called when BGP peering
    session goes down.

    ``peer_up_handler``, if specified, is called when BGP peering
    session goes up.

    ``ssh_console`` specifies whether or not SSH CLI need to be started.

    ``ssh_port`` specifies the port number for SSH CLI server.
    The default is bgp.operator.ssh.DEFAULT_SSH_PORT.

    ``ssh_host`` specifies the IP address for SSH CLI server.
    The default is bgp.operator.ssh.DEFAULT_SSH_HOST.

    ``ssh_host_key`` specifies the path to the host key added to
    the keys list used by SSH CLI server.
    The default is bgp.operator.ssh.DEFAULT_SSH_HOST_KEY.

    ``label_range`` specifies the range of MPLS labels generated
    automatically.

    ``allow_local_as_in_count`` maximum number of local AS number
    occurrences in AS_PATH.  This option is useful for e.g.  auto RD/RT
    configurations in leaf/spine architecture with shared AS numbers.
    The default is 0 and means "local AS number is not allowed in
    AS_PATH".  To allow local AS, 3 is recommended (Cisco's default).

    ``cluster_id`` specifies the cluster identifier for Route Reflector.
    It must be the string representation of an IPv4 address.
    If omitted, "router_id" is used for this field.

    ``local_pref`` specifies the default local preference. It must be an
    integer.
    """

    def __init__(self, as_number, router_id,
                 bgp_server_hosts=DEFAULT_BGP_SERVER_HOSTS,
                 bgp_server_port=DEFAULT_BGP_SERVER_PORT,
                 refresh_stalepath_time=DEFAULT_REFRESH_STALEPATH_TIME,
                 refresh_max_eor_time=DEFAULT_REFRESH_MAX_EOR_TIME,
                 best_path_change_handler=None,
                 adj_rib_in_change_handler=None,
                 peer_down_handler=None,
                 peer_up_handler=None,
                 ssh_console=False,
                 ssh_port=None, ssh_host=None, ssh_host_key=None,
                 label_range=DEFAULT_LABEL_RANGE,
                 allow_local_as_in_count=0,
                 cluster_id=None,
                 local_pref=DEFAULT_LOCAL_PREF):
        super(BGPSpeaker, self).__init__()

        settings = {
            LOCAL_AS: as_number,
            ROUTER_ID: router_id,
            BGP_SERVER_HOSTS: bgp_server_hosts,
            BGP_SERVER_PORT: bgp_server_port,
            REFRESH_STALEPATH_TIME: refresh_stalepath_time,
            REFRESH_MAX_EOR_TIME: refresh_max_eor_time,
            LABEL_RANGE: label_range,
            ALLOW_LOCAL_AS_IN_COUNT: allow_local_as_in_count,
            CLUSTER_ID: cluster_id,
            LOCAL_PREF: local_pref,
        }
        self._core_start(settings)
        self._init_signal_listeners()
        self._best_path_change_handler = best_path_change_handler
        self._adj_rib_in_change_handler = adj_rib_in_change_handler
        self._peer_down_handler = peer_down_handler
        self._peer_up_handler = peer_up_handler
        if ssh_console:
            # Note: paramiko used in bgp.operator.ssh is the optional
            # requirements, imports bgp.operator.ssh here.
            from os_ken.services.protocols.bgp.operator import ssh
            ssh_settings = {
                ssh.SSH_PORT: ssh_port or ssh.DEFAULT_SSH_PORT,
                ssh.SSH_HOST: ssh_host or ssh.DEFAULT_SSH_HOST,
                ssh.SSH_HOST_KEY: ssh_host_key or ssh.DEFAULT_SSH_HOST_KEY,
            }
            hub.spawn(ssh.SSH_CLI_CONTROLLER.start, **ssh_settings)

    def _notify_peer_down(self, peer):
        remote_ip = peer.ip_address
        remote_as = peer.remote_as
        if self._peer_down_handler:
            self._peer_down_handler(remote_ip, remote_as)

    def _notify_peer_up(self, peer):
        remote_ip = peer.protocol.recv_open_msg.bgp_identifier
        remote_as = peer.protocol.recv_open_msg.my_as
        if self._peer_up_handler:
            self._peer_up_handler(remote_ip, remote_as)

    def _notify_best_path_changed(self, path, is_withdraw):
        if (not path.source
                or not isinstance(path, (Ipv4Path, Ipv6Path,
                                         Vpnv4Path, Vpnv6Path, EvpnPath))):
            return

        ev = EventPrefix(path, is_withdraw)

        if self._best_path_change_handler:
            self._best_path_change_handler(ev)

    def _notify_adj_rib_in_changed(self, peer, route):
        if not isinstance(route, ReceivedRoute):
            return

        if self._adj_rib_in_change_handler:
            self._adj_rib_in_change_handler(
                EventPrefix(route.path, route.path.is_withdraw),
                peer.ip_address, peer.remote_as)

    def _init_signal_listeners(self):
        CORE_MANAGER.get_core_service()._signal_bus.register_listener(
            BgpSignalBus.BGP_BEST_PATH_CHANGED,
            lambda _, info:
            self._notify_best_path_changed(info['path'],
                                           info['is_withdraw'])
        )
        CORE_MANAGER.get_core_service()._signal_bus.register_listener(
            BgpSignalBus.BGP_ADJ_RIB_IN_CHANGED,
            lambda _, info:
            self._notify_adj_rib_in_changed(info['peer'],
                                            info['received_route'])
        )
        CORE_MANAGER.get_core_service()._signal_bus.register_listener(
            BgpSignalBus.BGP_ADJ_DOWN,
            lambda _, info:
            self._notify_peer_down(info['peer'])
        )
        CORE_MANAGER.get_core_service()._signal_bus.register_listener(
            BgpSignalBus.BGP_ADJ_UP,
            lambda _, info:
            self._notify_peer_up(info['peer'])
        )

    def _core_start(self, settings):
        waiter = hub.Event()
        call('core.start', waiter=waiter, **settings)
        waiter.wait()

    def _serve_forever(self):
        pass

    def shutdown(self):
        """ Shutdown BGP speaker
        """
        call('core.stop')

    def neighbor_add(self, address, remote_as,
                     remote_port=DEFAULT_BGP_PORT,
                     enable_ipv4=DEFAULT_CAP_MBGP_IPV4,
                     enable_ipv6=DEFAULT_CAP_MBGP_IPV6,
                     enable_vpnv4=DEFAULT_CAP_MBGP_VPNV4,
                     enable_vpnv6=DEFAULT_CAP_MBGP_VPNV6,
                     enable_evpn=DEFAULT_CAP_MBGP_EVPN,
                     enable_ipv4fs=DEFAULT_CAP_MBGP_IPV4FS,
                     enable_ipv6fs=DEFAULT_CAP_MBGP_IPV6FS,
                     enable_vpnv4fs=DEFAULT_CAP_MBGP_VPNV4FS,
                     enable_vpnv6fs=DEFAULT_CAP_MBGP_VPNV6FS,
                     enable_l2vpnfs=DEFAULT_CAP_MBGP_L2VPNFS,
                     enable_enhanced_refresh=DEFAULT_CAP_ENHANCED_REFRESH,
                     enable_four_octet_as_number=DEFAULT_CAP_FOUR_OCTET_AS_NUMBER,
                     next_hop=None, password=None, multi_exit_disc=None,
                     site_of_origins=None,
                     is_route_server_client=DEFAULT_IS_ROUTE_SERVER_CLIENT,
                     is_route_reflector_client=DEFAULT_IS_ROUTE_REFLECTOR_CLIENT,
                     is_next_hop_self=DEFAULT_IS_NEXT_HOP_SELF,
                     local_address=None,
                     local_port=None, local_as=None,
                     connect_mode=DEFAULT_CONNECT_MODE):
        """ This method registers a new neighbor. The BGP speaker tries to
        establish a bgp session with the peer (accepts a connection
        from the peer and also tries to connect to it).

        ``address`` specifies the IP address of the peer. It must be
        the string representation of an IP address. Only IPv4 is
        supported now.

        ``remote_as`` specifies the AS number of the peer. It must be
        an integer between 1 and 65535.

        ``remote_port`` specifies the TCP port number of the peer.

        ``enable_ipv4`` enables IPv4 address family for this
        neighbor.

        ``enable_ipv6`` enables IPv6 address family for this
        neighbor.

        ``enable_vpnv4`` enables VPNv4 address family for this
        neighbor.

        ``enable_vpnv6`` enables VPNv6 address family for this
        neighbor.

        ``enable_evpn`` enables Ethernet VPN address family for this
        neighbor.

        ``enable_ipv4fs`` enables IPv4 Flow Specification address family
        for this neighbor.

        ``enable_ipv6fs`` enables IPv6 Flow Specification address family
        for this neighbor.

        ``enable_vpnv4fs`` enables VPNv4 Flow Specification address family
        for this neighbor.

        ``enable_vpnv6fs`` enables VPNv6 Flow Specification address family
        for this neighbor.

        ``enable_l2vpnfs`` enables L2VPN Flow Specification address family
        for this neighbor.

        ``enable_enhanced_refresh`` enables Enhanced Route Refresh for this
        neighbor.

        ``enable_four_octet_as_number`` enables Four-Octet AS Number
        capability for this neighbor.

        ``next_hop`` specifies the next hop IP address. If not
        specified, host's ip address to access to a peer is used.

        ``password`` is used for the MD5 authentication if it's
        specified. By default, the MD5 authentication is disabled.

        ``multi_exit_disc`` specifies multi exit discriminator (MED) value
        as an int type value.
        If omitted, MED is not sent to the neighbor.

        ``site_of_origins`` specifies site_of_origin values.
        This parameter must be a list of string.

        ``is_route_server_client`` specifies whether this neighbor is a
        router server's client or not.

        ``is_route_reflector_client`` specifies whether this neighbor is a
        router reflector's client or not.

        ``is_next_hop_self`` specifies whether the BGP speaker announces
        its own ip address to iBGP neighbor or not as path's next_hop address.

        ``local_address`` specifies Loopback interface address for
        iBGP peering.

        ``local_port`` specifies source TCP port for iBGP peering.

        ``local_as`` specifies local AS number per-peer.
        If omitted, the AS number of BGPSpeaker instance is used.

        ``connect_mode`` specifies how to connect to this neighbor.
        This parameter must be one of the following.

        - CONNECT_MODE_ACTIVE         = 'active'
        - CONNECT_MODE_PASSIVE        = 'passive'
        - CONNECT_MODE_BOTH (default) = 'both'
        """
        bgp_neighbor = {
            neighbors.IP_ADDRESS: address,
            neighbors.REMOTE_AS: remote_as,
            REMOTE_PORT: remote_port,
            PEER_NEXT_HOP: next_hop,
            PASSWORD: password,
            IS_ROUTE_SERVER_CLIENT: is_route_server_client,
            IS_ROUTE_REFLECTOR_CLIENT: is_route_reflector_client,
            IS_NEXT_HOP_SELF: is_next_hop_self,
            CONNECT_MODE: connect_mode,
            CAP_ENHANCED_REFRESH: enable_enhanced_refresh,
            CAP_FOUR_OCTET_AS_NUMBER: enable_four_octet_as_number,
            CAP_MBGP_IPV4: enable_ipv4,
            CAP_MBGP_IPV6: enable_ipv6,
            CAP_MBGP_VPNV4: enable_vpnv4,
            CAP_MBGP_VPNV6: enable_vpnv6,
            CAP_MBGP_EVPN: enable_evpn,
            CAP_MBGP_IPV4FS: enable_ipv4fs,
            CAP_MBGP_IPV6FS: enable_ipv6fs,
            CAP_MBGP_VPNV4FS: enable_vpnv4fs,
            CAP_MBGP_VPNV6FS: enable_vpnv6fs,
            CAP_MBGP_L2VPNFS: enable_l2vpnfs,
        }

        if multi_exit_disc:
            bgp_neighbor[MULTI_EXIT_DISC] = multi_exit_disc

        if site_of_origins:
            bgp_neighbor[SITE_OF_ORIGINS] = site_of_origins

        if local_address:
            bgp_neighbor[LOCAL_ADDRESS] = local_address

        if local_port:
            bgp_neighbor[LOCAL_PORT] = local_port

        if local_as:
            bgp_neighbor[LOCAL_AS] = local_as

        call('neighbor.create', **bgp_neighbor)

    def neighbor_del(self, address):
        """ This method unregister the registered neighbor. If a session with
        the peer exists, the session will be closed.

        ``address`` specifies the IP address of the peer. It must be
        the string representation of an IP address.
        """
        bgp_neighbor = {
            neighbors.IP_ADDRESS: address,
        }

        call('neighbor.delete', **bgp_neighbor)

    def neighbor_reset(self, address):
        """ This method reset the registered neighbor.

        ``address`` specifies the IP address of the peer. It must be
        the string representation of an IP address.
        """
        bgp_neighbor = {
            neighbors.IP_ADDRESS: address,
        }

        call('core.reset_neighbor', **bgp_neighbor)

    def neighbor_update(self, address, conf_type, conf_value):
        """ This method changes the neighbor configuration.

        ``address`` specifies the IP address of the peer.

        ``conf_type`` specifies configuration type which you want to change.
        Currently os_ken.services.protocols.bgp.bgpspeaker.MULTI_EXIT_DISC
        can be specified.

        ``conf_value`` specifies value for the configuration type.
        """

        assert conf_type == MULTI_EXIT_DISC or conf_type == CONNECT_MODE

        func_name = 'neighbor.update'
        attribute_param = {}
        if conf_type == MULTI_EXIT_DISC:
            attribute_param = {neighbors.MULTI_EXIT_DISC: conf_value}
        elif conf_type == CONNECT_MODE:
            attribute_param = {neighbors.CONNECT_MODE: conf_value}

        param = {neighbors.IP_ADDRESS: address,
                 neighbors.CHANGES: attribute_param}

        call(func_name, **param)

    def neighbor_state_get(self, address=None, format='json'):
        """ This method returns the state of peer(s) in a json
        format.

        ``address`` specifies the address of a peer. If not given, the
        state of all the peers return.

        ``format`` specifies the format of the response.
        This parameter must be one of the following.

        - 'json' (default)
        - 'cli'
        """
        show = {
            'params': ['neighbor', 'summary'],
            'format': format,
        }
        if address:
            show['params'].append(address)

        return call('operator.show', **show)

    def prefix_add(self, prefix, next_hop=None, route_dist=None):
        """ This method adds a new prefix to be advertised.

        ``prefix`` must be the string representation of an IP network
        (e.g., 10.1.1.0/24).

        ``next_hop`` specifies the next hop address for this
        prefix. This parameter is necessary for only VPNv4 and VPNv6
        address families.

        ``route_dist`` specifies a route distinguisher value. This
        parameter is necessary for only VPNv4 and VPNv6 address
        families.
        """
        func_name = 'network.add'
        networks = {
            PREFIX: prefix,
        }
        if next_hop:
            networks[NEXT_HOP] = next_hop
        if route_dist:
            func_name = 'prefix.add_local'
            networks[ROUTE_DISTINGUISHER] = route_dist

            rf, p = self._check_rf_and_normalize(prefix)
            networks[ROUTE_FAMILY] = rf
            networks[PREFIX] = p

            if rf == vrfs.VRF_RF_IPV6 and ip.valid_ipv4(next_hop):
                # convert the next_hop to IPv4-Mapped IPv6 Address
                networks[NEXT_HOP] = \
                    str(netaddr.IPAddress(next_hop).ipv6())

        return call(func_name, **networks)

    def prefix_del(self, prefix, route_dist=None):
        """ This method deletes a advertised prefix.

        ``prefix`` must be the string representation of an IP network.

        ``route_dist`` specifies a route distinguisher value.
        """
        func_name = 'network.del'
        networks = {
            PREFIX: prefix,
        }
        if route_dist:
            func_name = 'prefix.delete_local'
            networks[ROUTE_DISTINGUISHER] = route_dist

            rf, p = self._check_rf_and_normalize(prefix)
            networks[ROUTE_FAMILY] = rf
            networks[PREFIX] = p

        call(func_name, **networks)

    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,
                        redundancy_mode=None, tunnel_endpoint_ip=None, mac_mobility=None):
        """ This method adds a new EVPN route to be advertised.

        ``route_type`` specifies one of the EVPN route type name.
        This parameter must be one of the following.

        - EVPN_ETH_AUTO_DISCOVERY   = 'eth_ad'
        - EVPN_MAC_IP_ADV_ROUTE     = 'mac_ip_adv'
        - EVPN_MULTICAST_ETAG_ROUTE = 'multicast_etag'
        - EVPN_ETH_SEGMENT          = 'eth_seg'
        - EVPN_IP_PREFIX_ROUTE      = 'ip_prefix'

        ``route_dist`` specifies a route distinguisher value.

        ``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 dictionary type.
        If esi is dictionary type, 'type' key must be set
        and specifies ESI type.
        For the supported ESI type, see :py:mod:`os_ken.lib.packet.bgp.EvpnEsi`.
        The remaining arguments are the same as that for
        the corresponding class.

        ``ethernet_tag_id`` specifies the Ethernet Tag ID.

        ``mac_addr`` specifies a MAC address to advertise.

        ``ip_addr`` specifies an IPv4 or IPv6 address to advertise.

        ``ip_prefix`` specifies an IPv4 or IPv6 prefix to advertise.

        ``gw_ip_addr`` specifies an IPv4 or IPv6 address of
        gateway to advertise.

        ``vni`` specifies an Virtual Network Identifier for VXLAN
        or Virtual Subnet Identifier for NVGRE.
        If tunnel_type is not TUNNEL_TYPE_VXLAN or TUNNEL_TYPE_NVGRE,
        this field is ignored.

        ``next_hop`` specifies the next hop address for this prefix.

        ``tunnel_type`` specifies the data plane encapsulation type
        to advertise. By the default, this attribute is not advertised.
        The supported encapsulation types are following.

        - TUNNEL_TYPE_VXLAN = 'vxlan'
        - TUNNEL_TYPE_NVGRE = 'nvgre

        ``pmsi_tunnel_type`` specifies the type of the PMSI tunnel attribute
        used to encode the multicast tunnel identifier.
        This attribute is advertised only if route_type is
        EVPN_MULTICAST_ETAG_ROUTE and not advertised by the default.
        This attribute can also carry vni if tunnel_type is specified.
        The supported PMSI tunnel types are following.

        - PMSI_TYPE_NO_TUNNEL_INFO = 0
        - PMSI_TYPE_INGRESS_REP    = 6

        ``redundancy_mode`` specifies a redundancy mode type.
        This attribute is advertised only if route_type is
        EVPN_ETH_AUTO_DISCOVERY and not advertised by the default.
        The supported redundancy mode types are following.

        - REDUNDANCY_MODE_ALL_ACTIVE    = 'all_active'
        - REDUNDANCY_MODE_SINGLE_ACTIVE = 'single_active'

        ``tunnel_endpoint_ip`` specifies a VTEP IP address other than the
        local router ID. This attribute is advertised only if route_type is
        EVPN_MULTICAST_ETAG_ROUTE, and defaults to the local router ID.

        ``mac_mobility`` specifies an optional integer sequence number to use
        in a MAC Mobility extended community field. The special value '-1' can
        be used to set the STATIC flag with a 0-value sequence number.

        """
        func_name = 'evpn_prefix.add_local'

        # Check the default values
        if not next_hop:
            next_hop = '0.0.0.0'

        # Set required arguments
        kwargs = {EVPN_ROUTE_TYPE: route_type,
                  ROUTE_DISTINGUISHER: route_dist,
                  NEXT_HOP: next_hop}

        # Set optional arguments
        if tunnel_type in [TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE]:
            kwargs[TUNNEL_TYPE] = tunnel_type
        elif tunnel_type is not None:
            raise ValueError('Unsupported tunnel type: %s' % tunnel_type)

        # Set route type specific arguments
        if route_type == EVPN_ETH_AUTO_DISCOVERY:
            kwargs.update({
                EVPN_ESI: esi,
                EVPN_ETHERNET_TAG_ID: ethernet_tag_id,
            })
            if vni is not None:
                kwargs[EVPN_VNI] = vni
            # Set Redundancy Mode Attribute arguments
            if redundancy_mode in [
                    REDUNDANCY_MODE_ALL_ACTIVE,
                    REDUNDANCY_MODE_SINGLE_ACTIVE]:
                kwargs[REDUNDANCY_MODE] = redundancy_mode
            elif redundancy_mode is not None:
                raise ValueError('Unsupported Redundancy Mode: %s' %
                                 redundancy_mode)
        elif route_type == EVPN_MAC_IP_ADV_ROUTE:
            kwargs.update({
                EVPN_ESI: esi,
                EVPN_ETHERNET_TAG_ID: ethernet_tag_id,
                MAC_ADDR: mac_addr,
                IP_ADDR: ip_addr,
            })
            # Set tunnel type specific arguments
            if tunnel_type in [TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE]:
                kwargs[EVPN_VNI] = vni

            # Pass on mac_mobility, must be integer value
            if mac_mobility is not None:
                kwargs[MAC_MOBILITY] = int(mac_mobility)
        elif route_type == EVPN_MULTICAST_ETAG_ROUTE:
            kwargs.update({
                EVPN_ETHERNET_TAG_ID: ethernet_tag_id,
                IP_ADDR: ip_addr,
            })
            # Set tunnel type specific arguments
            if tunnel_type in [TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE]:
                kwargs[EVPN_VNI] = vni
            # Set PMSI Tunnel Attribute arguments
            if pmsi_tunnel_type in [
                    PMSI_TYPE_NO_TUNNEL_INFO,
                    PMSI_TYPE_INGRESS_REP]:
                kwargs[PMSI_TUNNEL_TYPE] = pmsi_tunnel_type
            elif pmsi_tunnel_type is not None:
                raise ValueError('Unsupported PMSI tunnel type: %s' %
                                 pmsi_tunnel_type)
            # Set non-default tunnel endpoint IP, if provided
            if tunnel_endpoint_ip is not None:
                kwargs[TUNNEL_ENDPOINT_IP] = tunnel_endpoint_ip
        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,
                EVPN_ETHERNET_TAG_ID: ethernet_tag_id,
                IP_PREFIX: ip_prefix,
                GW_IP_ADDR: gw_ip_addr,
            })
            # Set tunnel type specific arguments
            if tunnel_type in [TUNNEL_TYPE_VXLAN, TUNNEL_TYPE_NVGRE]:
                kwargs[EVPN_VNI] = vni

            # Add mac_mobility
            if mac_mobility is not None:
                kwargs[MAC_MOBILITY] = int(mac_mobility)
        else:
            raise ValueError('Unsupported EVPN route type: %s' % route_type)

        call(func_name, **kwargs)

    def evpn_prefix_del(self, route_type, route_dist, esi=0,
                        ethernet_tag_id=None, mac_addr=None, ip_addr=None,
                        ip_prefix=None):
        """ This method deletes an advertised EVPN route.

        ``route_type`` specifies one of the EVPN route type name.

        ``route_dist`` specifies a route distinguisher value.

        ``esi`` is an value to specify the Ethernet Segment Identifier.

        ``ethernet_tag_id`` specifies the Ethernet Tag ID.

        ``mac_addr`` specifies a MAC address to advertise.

        ``ip_addr`` specifies an IPv4 or IPv6 address to advertise.

        ``ip_prefix`` specifies an IPv4 or IPv6 prefix to advertise.
        """
        func_name = 'evpn_prefix.delete_local'

        # Set required arguments
        kwargs = {EVPN_ROUTE_TYPE: route_type,
                  ROUTE_DISTINGUISHER: route_dist}

        # Set route type specific arguments
        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_ETHERNET_TAG_ID: ethernet_tag_id,
                MAC_ADDR: mac_addr,
                IP_ADDR: ip_addr,
            })
        elif route_type == EVPN_MULTICAST_ETAG_ROUTE:
            kwargs.update({
                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,
                IP_PREFIX: ip_prefix,
            })
        else:
            raise ValueError('Unsupported EVPN route type: %s' % route_type)

        call(func_name, **kwargs)

    def flowspec_prefix_add(self, flowspec_family, rules, route_dist=None,
                            actions=None):
        """ This method adds a new Flow Specification prefix to be advertised.

        ``flowspec_family`` specifies one of the flowspec family name.
        This parameter must be one of the following.

        - FLOWSPEC_FAMILY_IPV4  = 'ipv4fs'
        - FLOWSPEC_FAMILY_IPV6  = 'ipv6fs'
        - FLOWSPEC_FAMILY_VPNV4 = 'vpnv4fs'
        - FLOWSPEC_FAMILY_VPNV6 = 'vpnv6fs'
        - FLOWSPEC_FAMILY_L2VPN = 'l2vpnfs'

        ``rules`` specifies NLRIs of Flow Specification as
        a dictionary type value.
        For the supported NLRI types and arguments,
        see `from_user()` method of the following classes.

        - :py:mod:`os_ken.lib.packet.bgp.FlowSpecIPv4NLRI`
        - :py:mod:`os_ken.lib.packet.bgp.FlowSpecIPv6NLRI`
        - :py:mod:`os_ken.lib.packet.bgp.FlowSpecVPNv4NLRI`
        - :py:mod:`os_ken.lib.packet.bgp.FlowSpecVPNv6NLRI`
        - :py:mod:`os_ken.lib.packet.bgp.FlowSpecL2VPNNLRI`

        ``route_dist`` specifies a route distinguisher value.
        This parameter is required only if flowspec_family is one of the
        following address family.

        - FLOWSPEC_FAMILY_VPNV4 = 'vpnv4fs'
        - FLOWSPEC_FAMILY_VPNV6 = 'vpnv6fs'
        - FLOWSPEC_FAMILY_L2VPN = 'l2vpnfs'

        ``actions`` specifies Traffic Filtering Actions of
        Flow Specification as a dictionary type value.
        The keys are "ACTION_NAME" for each action class and
        values are used for the arguments to that class.
        For the supported "ACTION_NAME" and arguments,
        see the following table.

        =============== ===============================================================
        ACTION_NAME     Action Class
        =============== ===============================================================
        traffic_rate    :py:mod:`os_ken.lib.packet.bgp.BGPFlowSpecTrafficRateCommunity`
        traffic_action  :py:mod:`os_ken.lib.packet.bgp.BGPFlowSpecTrafficActionCommunity`
        redirect        :py:mod:`os_ken.lib.packet.bgp.BGPFlowSpecRedirectCommunity`
        traffic_marking :py:mod:`os_ken.lib.packet.bgp.BGPFlowSpecTrafficMarkingCommunity`
        vlan_action     :py:mod:`os_ken.lib.packet.bgp.BGPFlowSpecVlanActionCommunity`
        tpid_action     :py:mod:`os_ken.lib.packet.bgp.BGPFlowSpecTPIDActionCommunity`
        =============== ===============================================================

        Example(IPv4)::

            >>> speaker = BGPSpeaker(as_number=65001, router_id='172.17.0.1')
            >>> speaker.neighbor_add(address='172.17.0.2',
            ...                      remote_as=65002,
            ...                      enable_ipv4fs=True)
            >>> speaker.flowspec_prefix_add(
            ...     flowspec_family=FLOWSPEC_FAMILY_IPV4,
            ...     rules={
            ...         'dst_prefix': '10.60.1.0/24'
            ...     },
            ...     actions={
            ...         'traffic_marking': {
            ...             'dscp': 24
            ...         }
            ...     }
            ... )

        Example(VPNv4)::

            >>> speaker = BGPSpeaker(as_number=65001, router_id='172.17.0.1')
            >>> speaker.neighbor_add(address='172.17.0.2',
            ...                      remote_as=65002,
            ...                      enable_vpnv4fs=True)
            >>> speaker.vrf_add(route_dist='65001:100',
            ...                 import_rts=['65001:100'],
            ...                 export_rts=['65001:100'],
            ...                 route_family=RF_VPNV4_FLOWSPEC)
            >>> speaker.flowspec_prefix_add(
            ...     flowspec_family=FLOWSPEC_FAMILY_VPNV4,
            ...     route_dist='65000:100',
            ...     rules={
            ...         'dst_prefix': '10.60.1.0/24'
            ...     },
            ...     actions={
            ...         'traffic_marking': {
            ...             'dscp': 24
            ...         }
            ...     }
            ... )
        """
        func_name = 'flowspec.add'

        # Set required arguments
        kwargs = {
            FLOWSPEC_FAMILY: flowspec_family,
            FLOWSPEC_RULES: rules,
            FLOWSPEC_ACTIONS: actions or {},
        }

        if flowspec_family in [FLOWSPEC_FAMILY_VPNV4, FLOWSPEC_FAMILY_VPNV6,
                               FLOWSPEC_FAMILY_L2VPN]:
            func_name = 'flowspec.add_local'
            kwargs.update({ROUTE_DISTINGUISHER: route_dist})

        call(func_name, **kwargs)

    def flowspec_prefix_del(self, flowspec_family, rules, route_dist=None):
        """ This method deletes an advertised Flow Specification route.

        ``flowspec_family`` specifies one of the flowspec family name.

        ``rules`` specifies NLRIs of Flow Specification as
        a dictionary type value.

        ``route_dist`` specifies a route distinguisher value.
        """
        func_name = 'flowspec.del'

        # Set required arguments
        kwargs = {
            FLOWSPEC_FAMILY: flowspec_family,
            FLOWSPEC_RULES: rules,
        }

        if flowspec_family in [FLOWSPEC_FAMILY_VPNV4, FLOWSPEC_FAMILY_VPNV6,
                               FLOWSPEC_FAMILY_L2VPN]:
            func_name = 'flowspec.del_local'
            kwargs.update({ROUTE_DISTINGUISHER: route_dist})

        call(func_name, **kwargs)

    def vrf_add(self, route_dist, import_rts, export_rts, site_of_origins=None,
                route_family=RF_VPN_V4, multi_exit_disc=None):
        """ This method adds a new vrf used for VPN.

        ``route_dist`` specifies a route distinguisher value.

        ``import_rts`` specifies a list of route targets to be imported.

        ``export_rts`` specifies a list of route targets to be exported.

        ``site_of_origins`` specifies site_of_origin values.
        This parameter must be a list of string.

        ``route_family`` specifies route family of the VRF.
        This parameter must be one of the following.

        - RF_VPN_V4 (default) = 'ipv4'
        - RF_VPN_V6           = 'ipv6'
        - RF_L2_EVPN          = 'evpn'
        - RF_VPNV4_FLOWSPEC   = 'ipv4fs'
        - RF_VPNV6_FLOWSPEC   = 'ipv6fs'
        - RF_L2VPN_FLOWSPEC   = 'l2vpnfs'

        ``multi_exit_disc`` specifies multi exit discriminator (MED) value.
        It must be an integer.
        """
        if route_family not in SUPPORTED_VRF_RF:
            raise ValueError('Unsupported route_family: %s' % route_family)

        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,
            vrfs.MULTI_EXIT_DISC: multi_exit_disc,
        }

        call('vrf.create', **vrf)

    def vrf_del(self, route_dist):
        """ This method deletes the existing vrf.

        ``route_dist`` specifies a route distinguisher value.
        """

        vrf = {vrfs.ROUTE_DISTINGUISHER: route_dist}

        call('vrf.delete', **vrf)

    def vrfs_get(self, subcommand='routes', route_dist=None,
                 route_family='all', format='json'):
        """ This method returns the existing vrfs.

        ``subcommand`` specifies one of the following.

        - 'routes': shows routes present for vrf
        - 'summary': shows configuration and summary of vrf

        ``route_dist`` specifies a route distinguisher value.
        If route_family is not 'all', this value must be specified.

        ``route_family`` specifies route family of the VRF.
        This parameter must be one of the following.

        - RF_VPN_V4  = 'ipv4'
        - RF_VPN_V6  = 'ipv6'
        - RF_L2_EVPN = 'evpn'
        - 'all' (default)

        ``format`` specifies the format of the response.
        This parameter must be one of the following.

        - 'json' (default)
        - 'cli'
        """
        show = {
            'format': format,
        }
        if route_family in SUPPORTED_VRF_RF:
            assert route_dist is not None
            show['params'] = ['vrf', subcommand, route_dist, route_family]
        else:
            show['params'] = ['vrf', subcommand, 'all']

        return call('operator.show', **show)

    def rib_get(self, family='all', format='json'):
        """ This method returns the BGP routing information in a json
        format. This will be improved soon.

        ``family`` specifies the address family of the RIB (e.g. 'ipv4').

        ``format`` specifies the format of the response.
        This parameter must be one of the following.

        - 'json' (default)
        - 'cli'
        """
        show = {
            'params': ['rib', family],
            'format': format
        }

        return call('operator.show', **show)

    def neighbor_get(self, route_type, address, format='json'):
        """ This method returns the BGP adj-RIB-in/adj-RIB-out information
        in a json format.

        ``route_type`` This parameter is necessary for only received-routes
        and sent-routes.

        - received-routes : paths received and not withdrawn by given peer
        - sent-routes : paths sent and not withdrawn to given peer

        ``address`` specifies the IP address of the peer. It must be
        the string representation of an IP address.

        ``format`` specifies the format of the response.
        This parameter must be one of the following.

        - 'json' (default)
        - 'cli'
        """
        show = {
            'format': format,
        }
        if route_type == 'sent-routes' or route_type == 'received-routes':
            show['params'] = ['neighbor', route_type, address, 'all']
        else:
            show['params'] = ['neighbor', 'received-routes', address, 'all']

        return call('operator.show', **show)

    def neighbors_get(self, format='json'):
        """ This method returns a list of the BGP neighbors.

        ``format`` specifies the format of the response.
        This parameter must be one of the following.

        - 'json' (default)
        - 'cli'
        """
        show = {
            'params': ['neighbor'],
            'format': format,
        }

        return call('operator.show', **show)

    def _set_filter(self, filter_type, address, filters):
        assert filter_type in ('in', 'out'), (
            "filter type must be 'in' or 'out'")

        assert all(isinstance(f, Filter) for f in filters), (
            'all the items in filters must be an instance of Filter sub-class')

        if filters is None:
            filters = []

        func_name = 'neighbor.' + filter_type + '_filter.set'
        param = {
            neighbors.IP_ADDRESS: address,
        }
        if filter_type == 'in':
            param[neighbors.IN_FILTER] = filters
        else:
            param[neighbors.OUT_FILTER] = filters

        call(func_name, **param)

    def out_filter_set(self, address, filters):
        """ This method sets out-filter to neighbor.

        ``address`` specifies the IP address of the peer.

        ``filters`` specifies a filter list to filter the path advertisement.
        The contents must be an instance of Filter sub-class

        If you want to define out-filter that send only a particular
        prefix to neighbor, filters can be created as follows::

            p = PrefixFilter('10.5.111.0/24',
                             policy=PrefixFilter.POLICY_PERMIT)

            all = PrefixFilter('0.0.0.0/0',
                               policy=PrefixFilter.POLICY_DENY)

            pList = [p, all]

            self.bgpspeaker.out_filter_set(neighbor_address, pList)

        .. Note::

            out-filter evaluates paths in the order of Filter in the pList.
        """

        self._set_filter('out', address, filters)

    def out_filter_get(self, address):
        """ This method gets out-filter setting from the specified neighbor.

        ``address`` specifies the IP address of the peer.

        Returns a list object containing an instance of Filter sub-class
        """

        func_name = 'neighbor.out_filter.get'
        param = {
            neighbors.IP_ADDRESS: address,
        }

        return call(func_name, **param)

    def in_filter_set(self, address, filters):
        """This method sets in-bound filters to a neighbor.

        ``address`` specifies the IP address of the neighbor

        ``filters`` specifies filter list applied before advertised paths are
        imported to the global rib. All the items in the list must be an
        instance of Filter sub-class.
        """

        self._set_filter('in', address, filters)

    def in_filter_get(self, address):
        """This method gets in-bound filters of the specified neighbor.

        ``address`` specifies the IP address of the neighbor.

        Returns a list object containing an instance of Filter sub-class
        """

        func_name = 'neighbor.in_filter.get'
        param = {
            neighbors.IP_ADDRESS: address,
        }

        return call(func_name, **param)

    def bmp_server_add(self, address, port):
        """This method registers a new BMP (BGP monitoring Protocol)
        server. The BGP speaker starts to send BMP messages to the
        server. Currently, only one BMP server can be registered.

        ``address`` specifies the IP address of a BMP server.

        ``port`` specifies the listen port number of a BMP server.
        """

        func_name = 'bmp.start'
        param = {
            'host': address,
            'port': port,
        }

        call(func_name, **param)

    def bmp_server_del(self, address, port):
        """ This method unregister the registered BMP server.

        ``address`` specifies the IP address of a BMP server.

        ``port`` specifies the listen port number of a BMP server.
        """

        func_name = 'bmp.stop'
        param = {
            'host': address,
            'port': port,
        }

        call(func_name, **param)

    def attribute_map_set(self, address, attribute_maps,
                          route_dist=None, route_family=RF_VPN_V4):
        """This method sets attribute mapping to a neighbor.
        attribute mapping can be used when you want to apply
        attribute to BGPUpdate under specific conditions.

        ``address`` specifies the IP address of the neighbor

        ``attribute_maps`` specifies attribute_map list that are used
        before paths are advertised. All the items in the list must
        be an instance of AttributeMap class

        ``route_dist`` specifies route dist in which attribute_maps
        are added.

        ``route_family`` specifies route family of the VRF.
        This parameter must be one of the following.

        - RF_VPN_V4 (default) = 'ipv4'
        - RF_VPN_V6           = 'ipv6'

        We can set AttributeMap to a neighbor as follows::

            pref_filter = PrefixFilter('192.168.103.0/30',
                                       PrefixFilter.POLICY_PERMIT)

            attribute_map = AttributeMap([pref_filter],
                                         AttributeMap.ATTR_LOCAL_PREF, 250)

            speaker.attribute_map_set('192.168.50.102', [attribute_map])
        """

        if route_family not in SUPPORTED_VRF_RF:
            raise ValueError('Unsupported route_family: %s' % route_family)

        func_name = 'neighbor.attribute_map.set'
        param = {
            neighbors.IP_ADDRESS: address,
            neighbors.ATTRIBUTE_MAP: attribute_maps,
        }
        if route_dist is not None:
            param[vrfs.ROUTE_DISTINGUISHER] = route_dist
            param[vrfs.VRF_RF] = route_family

        call(func_name, **param)

    def attribute_map_get(self, address, route_dist=None,
                          route_family=RF_VPN_V4):
        """This method gets in-bound filters of the specified neighbor.

        ``address`` specifies the IP address of the neighbor.

        ``route_dist`` specifies route distinguisher that has attribute_maps.

        ``route_family`` specifies route family of the VRF.
        This parameter must be one of the following.

        - RF_VPN_V4 (default) = 'ipv4'
        - RF_VPN_V6           = 'ipv6'

        Returns a list object containing an instance of AttributeMap
        """

        if route_family not in SUPPORTED_VRF_RF:
            raise ValueError('Unsupported route_family: %s' % route_family)

        func_name = 'neighbor.attribute_map.get'
        param = {
            neighbors.IP_ADDRESS: address,
        }
        if route_dist is not None:
            param[vrfs.ROUTE_DISTINGUISHER] = route_dist
            param[vrfs.VRF_RF] = route_family

        return call(func_name, **param)

    @staticmethod
    def _check_rf_and_normalize(prefix):
        """ check prefix's route_family and if the address is
        IPv6 address, return IPv6 route_family and normalized IPv6 address.
        If the address is IPv4 address, return IPv4 route_family
        and the prefix itself.
        """
        addr, masklen = prefix.split('/')
        if ip.valid_ipv6(addr):
            # normalize IPv6 address
            ipv6_prefix = str(netaddr.IPAddress(addr)) + '/' + masklen
            return vrfs.VRF_RF_IPV6, ipv6_prefix
        else:
            return vrfs.VRF_RF_IPV4, prefix
