mirror of
https://github.com/faucetsdn/ryu.git
synced 2026-05-08 13:56:09 +02:00
bgp/application: Re-implement base BGP application
Currently, options for bgp/application.py is not passed to 'ryu-manager',
bgp/application.py does only start RPC server and can not start other
threads including BGP core and SSH server using bgp_sample_conf.py.
This patch enables bgp/application.py to start BGP threads using
the specified configuration file and reconstructs configuration file
format.
With this patch, BGPSpaker application can be started like:
$ ryu-manager --bgp-app-config-file ryu/services/protocols/bgp/bgp_sample_conf.py
ryu/services/protocols/bgp/application.py
Signed-off-by: IWASE Yusuke <iwase.yusuke0@gmail.com>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
This commit is contained in:
parent
1dfe90d17a
commit
649d31afbe
@ -12,221 +12,213 @@
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""
|
||||
Defines bases classes to create a BGP application.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from logging.config import dictConfig
|
||||
import traceback
|
||||
|
||||
from oslo_config import cfg
|
||||
import os
|
||||
|
||||
from ryu import cfg
|
||||
from ryu.lib import hub
|
||||
from ryu.utils import load_source
|
||||
from ryu.base.app_manager import RyuApp
|
||||
from ryu.services.protocols.bgp.api.base import call
|
||||
from ryu.services.protocols.bgp.base import add_bgp_error_metadata
|
||||
from ryu.services.protocols.bgp.base import BGPSException
|
||||
from ryu.services.protocols.bgp.base import BIN_ERROR
|
||||
from ryu.services.protocols.bgp.core_manager import CORE_MANAGER
|
||||
from ryu.services.protocols.bgp import net_ctrl
|
||||
from ryu.services.protocols.bgp.bgpspeaker import BGPSpeaker
|
||||
from ryu.services.protocols.bgp.net_ctrl import NET_CONTROLLER
|
||||
from ryu.services.protocols.bgp.net_ctrl import NC_RPC_BIND_IP
|
||||
from ryu.services.protocols.bgp.net_ctrl import NC_RPC_BIND_PORT
|
||||
from ryu.services.protocols.bgp.operator.ssh import SSH_CLI_CONTROLLER
|
||||
from ryu.services.protocols.bgp.rtconf.base import RuntimeConfigError
|
||||
from ryu.services.protocols.bgp.rtconf.common import BGP_SERVER_PORT
|
||||
from ryu.services.protocols.bgp.rtconf.common import DEFAULT_BGP_SERVER_PORT
|
||||
from ryu.services.protocols.bgp.rtconf.common import \
|
||||
DEFAULT_REFRESH_MAX_EOR_TIME
|
||||
from ryu.services.protocols.bgp.rtconf.common import \
|
||||
DEFAULT_REFRESH_STALEPATH_TIME
|
||||
from ryu.services.protocols.bgp.rtconf.common import (
|
||||
DEFAULT_REFRESH_MAX_EOR_TIME, DEFAULT_REFRESH_STALEPATH_TIME)
|
||||
from ryu.services.protocols.bgp.rtconf.common import DEFAULT_LABEL_RANGE
|
||||
from ryu.services.protocols.bgp.rtconf.common import LABEL_RANGE
|
||||
from ryu.services.protocols.bgp.rtconf.common import LOCAL_AS
|
||||
from ryu.services.protocols.bgp.rtconf.common import REFRESH_MAX_EOR_TIME
|
||||
from ryu.services.protocols.bgp.rtconf.common import REFRESH_STALEPATH_TIME
|
||||
from ryu.services.protocols.bgp.rtconf.common import ROUTER_ID
|
||||
from ryu.services.protocols.bgp.rtconf import neighbors
|
||||
from ryu.services.protocols.bgp.rtconf import vrfs
|
||||
from ryu.services.protocols.bgp.utils.validation import is_valid_ipv4
|
||||
from ryu.services.protocols.bgp.operator import ssh
|
||||
from ryu.services.protocols.bgp.utils.validation import is_valid_ipv6
|
||||
|
||||
|
||||
LOG = logging.getLogger('bgpspeaker.application')
|
||||
CONF = cfg.CONF
|
||||
|
||||
CONF.register_opts([
|
||||
cfg.IntOpt('bind-port', default=50002, help='rpc-port'),
|
||||
cfg.StrOpt('bind-ip', default='0.0.0.0', help='rpc-bind-ip'),
|
||||
cfg.StrOpt('bgp-config-file', default=None,
|
||||
help='bgp-config-file')
|
||||
])
|
||||
CONF = cfg.CONF['bgp-app']
|
||||
|
||||
|
||||
@add_bgp_error_metadata(code=BIN_ERROR,
|
||||
sub_code=1,
|
||||
def_desc='Unknown bootstrap exception.')
|
||||
class ApplicationException(BGPSException):
|
||||
"""Specific Base exception related to `BSPSpeaker`."""
|
||||
"""
|
||||
Specific Base exception related to `BSPSpeaker`.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def validate_rpc_host(ip):
|
||||
"""
|
||||
Validates the given ip for use as RPC server address.
|
||||
"""
|
||||
if not is_valid_ipv4(ip) and not is_valid_ipv6(ip):
|
||||
raise ApplicationException(
|
||||
desc='Invalid RPC ip address: %s' % ip)
|
||||
return ip
|
||||
|
||||
|
||||
def load_config(config_file):
|
||||
"""
|
||||
Validates the given file for use as the settings file for BGPSpeaker
|
||||
and loads the configuration from the given file as a module instance.
|
||||
"""
|
||||
if not config_file or not os.path.isfile(config_file):
|
||||
raise ApplicationException(
|
||||
desc='Invalid configuration file: %s' % config_file)
|
||||
|
||||
# Loads the configuration from the given file, if available.
|
||||
try:
|
||||
return load_source('bgpspeaker.application.settings', config_file)
|
||||
except Exception as e:
|
||||
raise ApplicationException(desc=str(e))
|
||||
|
||||
|
||||
class RyuBGPSpeaker(RyuApp):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.bind_ip = RyuBGPSpeaker.validate_rpc_ip(CONF.bind_ip)
|
||||
self.bind_port = RyuBGPSpeaker.validate_rpc_port(CONF.bind_port)
|
||||
self.config_file = CONF.bgp_config_file
|
||||
super(RyuBGPSpeaker, self).__init__(*args, **kwargs)
|
||||
self.config_file = CONF.config_file
|
||||
|
||||
# BGPSpeaker instance (not instantiated yet)
|
||||
self.speaker = None
|
||||
|
||||
def start(self):
|
||||
# Only two main green threads are required for APGW bgp-agent.
|
||||
# One for NetworkController, another for BGPS core.
|
||||
|
||||
# If configuration file was provided and loaded successfully. We start
|
||||
# BGPS core using these settings. If no configuration file is provided
|
||||
# or if configuration file is missing minimum required settings BGPS
|
||||
# core is not started.
|
||||
if self.config_file:
|
||||
LOG.debug('Loading config. from settings file.')
|
||||
settings = self.load_config(self.config_file)
|
||||
# Configure log settings, if available.
|
||||
if getattr(settings, 'LOGGING', None):
|
||||
dictConfig(settings.LOGGING)
|
||||
|
||||
if getattr(settings, 'BGP', None):
|
||||
self._start_core(settings)
|
||||
|
||||
if getattr(settings, 'SSH', None) is not None:
|
||||
hub.spawn(ssh.SSH_CLI_CONTROLLER.start, None, **settings.SSH)
|
||||
# Start Network Controller to server RPC peers.
|
||||
t = hub.spawn(net_ctrl.NET_CONTROLLER.start, *[],
|
||||
**{net_ctrl.NC_RPC_BIND_IP: self.bind_ip,
|
||||
net_ctrl.NC_RPC_BIND_PORT: self.bind_port})
|
||||
LOG.debug('Started Network Controller')
|
||||
|
||||
super(RyuBGPSpeaker, self).start()
|
||||
|
||||
return t
|
||||
# If configuration file was provided and loaded successfully, we start
|
||||
# BGPSpeaker using the given settings.
|
||||
# If no configuration file is provided or if any minimum required
|
||||
# setting is missing, BGPSpeaker will not be started.
|
||||
if self.config_file:
|
||||
LOG.debug('Loading config file %s...', self.config_file)
|
||||
settings = load_config(self.config_file)
|
||||
|
||||
@classmethod
|
||||
def validate_rpc_ip(cls, ip):
|
||||
"""Validates given ip for use as rpc host bind address.
|
||||
# Configure logging settings, if available.
|
||||
if hasattr(settings, 'LOGGING'):
|
||||
# Not implemented yet.
|
||||
LOG.debug('Loading LOGGING settings... (NOT implemented yet)')
|
||||
# from logging.config import dictConfig
|
||||
# logging_settings = dictConfig(settings.LOGGING)
|
||||
|
||||
# Configure BGP settings, if available.
|
||||
if hasattr(settings, 'BGP'):
|
||||
LOG.debug('Loading BGP settings...')
|
||||
self._start_speaker(settings.BGP)
|
||||
|
||||
# Configure SSH settings, if available.
|
||||
if hasattr(settings, 'SSH'):
|
||||
LOG.debug('Loading SSH settings...')
|
||||
hub.spawn(SSH_CLI_CONTROLLER.start, **settings.SSH)
|
||||
|
||||
# Start RPC server with the given RPC settings.
|
||||
rpc_settings = {
|
||||
NC_RPC_BIND_PORT: CONF.rpc_port,
|
||||
NC_RPC_BIND_IP: validate_rpc_host(CONF.rpc_host),
|
||||
}
|
||||
return hub.spawn(NET_CONTROLLER.start, **rpc_settings)
|
||||
|
||||
def _start_speaker(self, settings):
|
||||
"""
|
||||
if not is_valid_ipv4(ip):
|
||||
raise ApplicationException(desc='Invalid rpc ip address.')
|
||||
return ip
|
||||
|
||||
@classmethod
|
||||
def validate_rpc_port(cls, port):
|
||||
"""Validates give port for use as rpc server port.
|
||||
Starts BGPSpeaker using the given settings.
|
||||
"""
|
||||
if not port:
|
||||
raise ApplicationException(desc='Invalid rpc port number.')
|
||||
if isinstance(port, str):
|
||||
port = int(port)
|
||||
# Settings for starting BGPSpeaker
|
||||
bgp_settings = {}
|
||||
|
||||
return port
|
||||
|
||||
def load_config(self, config_file):
|
||||
"""Validates give file as settings file for BGPSpeaker.
|
||||
|
||||
Load the configuration from file as settings module.
|
||||
"""
|
||||
if not config_file or not isinstance(config_file, str):
|
||||
raise ApplicationException('Invalid configuration file.')
|
||||
|
||||
# Check if file can be read
|
||||
# Get required settings.
|
||||
try:
|
||||
return load_source('settings', config_file)
|
||||
except Exception as e:
|
||||
raise ApplicationException(desc=str(e))
|
||||
|
||||
def _start_core(self, settings):
|
||||
"""Starts BGPS core using setting and given pool.
|
||||
"""
|
||||
# Get common settings
|
||||
routing_settings = settings.BGP.get('routing')
|
||||
common_settings = {}
|
||||
|
||||
# Get required common settings.
|
||||
try:
|
||||
common_settings[LOCAL_AS] = routing_settings.pop(LOCAL_AS)
|
||||
common_settings[ROUTER_ID] = routing_settings.pop(ROUTER_ID)
|
||||
bgp_settings['as_number'] = settings.get(LOCAL_AS)
|
||||
bgp_settings['router_id'] = settings.get(ROUTER_ID)
|
||||
except KeyError as e:
|
||||
raise ApplicationException(
|
||||
desc='Required minimum configuration missing %s' %
|
||||
e)
|
||||
desc='Required BGP configuration missing: %s' % e)
|
||||
|
||||
# Get optional common settings
|
||||
common_settings[BGP_SERVER_PORT] = \
|
||||
routing_settings.get(BGP_SERVER_PORT, DEFAULT_BGP_SERVER_PORT)
|
||||
common_settings[REFRESH_STALEPATH_TIME] = \
|
||||
routing_settings.get(REFRESH_STALEPATH_TIME,
|
||||
DEFAULT_REFRESH_STALEPATH_TIME)
|
||||
common_settings[REFRESH_MAX_EOR_TIME] = \
|
||||
routing_settings.get(REFRESH_MAX_EOR_TIME,
|
||||
DEFAULT_REFRESH_MAX_EOR_TIME)
|
||||
common_settings[LABEL_RANGE] = \
|
||||
routing_settings.get(LABEL_RANGE, DEFAULT_LABEL_RANGE)
|
||||
# Get optional settings.
|
||||
bgp_settings[BGP_SERVER_PORT] = settings.get(
|
||||
BGP_SERVER_PORT, DEFAULT_BGP_SERVER_PORT)
|
||||
bgp_settings[REFRESH_STALEPATH_TIME] = settings.get(
|
||||
REFRESH_STALEPATH_TIME, DEFAULT_REFRESH_STALEPATH_TIME)
|
||||
bgp_settings[REFRESH_MAX_EOR_TIME] = settings.get(
|
||||
REFRESH_MAX_EOR_TIME, DEFAULT_REFRESH_MAX_EOR_TIME)
|
||||
bgp_settings[LABEL_RANGE] = settings.get(
|
||||
LABEL_RANGE, DEFAULT_LABEL_RANGE)
|
||||
|
||||
# Start BGPS core service
|
||||
waiter = hub.Event()
|
||||
call('core.start', waiter=waiter, **common_settings)
|
||||
waiter.wait()
|
||||
# Create BGPSpeaker instance.
|
||||
LOG.debug('Starting BGPSpeaker...')
|
||||
self.speaker = BGPSpeaker(**bgp_settings)
|
||||
|
||||
LOG.debug('Core started %s', CORE_MANAGER.started)
|
||||
# Core manager started add configured neighbor and vrfs
|
||||
if CORE_MANAGER.started:
|
||||
# Add neighbors.
|
||||
self._add_neighbors(routing_settings)
|
||||
# Add neighbors.
|
||||
LOG.debug('Adding neighbors...')
|
||||
self._add_neighbors(settings.get('neighbors', []))
|
||||
|
||||
# Add Vrfs.
|
||||
self._add_vrfs(routing_settings)
|
||||
# Add VRFs.
|
||||
LOG.debug('Adding VRFs...')
|
||||
self._add_vrfs(settings.get('vrfs', []))
|
||||
|
||||
# Add Networks
|
||||
self._add_networks(routing_settings)
|
||||
# Add Networks
|
||||
LOG.debug('Adding routes...')
|
||||
self._add_routes(settings.get('routes', []))
|
||||
|
||||
def _add_neighbors(self, routing_settings):
|
||||
"""Add bgp peers/neighbors from given settings to BGPS runtime.
|
||||
|
||||
All valid neighbors are loaded. Miss-configured neighbors are ignored
|
||||
and error is logged.
|
||||
def _add_neighbors(self, settings):
|
||||
"""
|
||||
bgp_neighbors = routing_settings.setdefault('bgp_neighbors', {})
|
||||
for ip, bgp_neighbor in bgp_neighbors.items():
|
||||
Add BGP neighbors from the given settings.
|
||||
|
||||
All valid neighbors are loaded.
|
||||
Miss-configured neighbors are ignored and errors are logged.
|
||||
"""
|
||||
for neighbor_settings in settings:
|
||||
LOG.debug('Adding neighbor settings: %s', neighbor_settings)
|
||||
try:
|
||||
bgp_neighbor[neighbors.IP_ADDRESS] = ip
|
||||
call('neighbor.create', **bgp_neighbor)
|
||||
LOG.debug('Added neighbor %s', ip)
|
||||
except RuntimeConfigError as re:
|
||||
LOG.error(re)
|
||||
LOG.error(traceback.format_exc())
|
||||
continue
|
||||
self.speaker.neighbor_add(**neighbor_settings)
|
||||
except RuntimeConfigError as e:
|
||||
LOG.exception(e)
|
||||
|
||||
def _add_vrfs(self, routing_settings):
|
||||
"""Add VRFs from given settings to BGPS runtime.
|
||||
def _add_vrfs(self, settings):
|
||||
"""
|
||||
Add BGP VRFs from the given settings.
|
||||
|
||||
If any of the VRFs are miss-configured errors are logged.
|
||||
All valid VRFs are loaded.
|
||||
Miss-configured VRFs are ignored and errors are logged.
|
||||
"""
|
||||
vpns_conf = routing_settings.setdefault('vpns', {})
|
||||
for vrfname, vrf in vpns_conf.items():
|
||||
for vrf_settings in settings:
|
||||
LOG.debug('Adding VRF settings: %s', vrf_settings)
|
||||
try:
|
||||
vrf[vrfs.VRF_NAME] = vrfname
|
||||
call('vrf.create', **vrf)
|
||||
LOG.debug('Added vrf %s', vrf)
|
||||
self.speaker.vrf_add(**vrf_settings)
|
||||
except RuntimeConfigError as e:
|
||||
LOG.error(e)
|
||||
LOG.exception(e)
|
||||
|
||||
def _add_routes(self, settings):
|
||||
"""
|
||||
Add BGP routes from given settings.
|
||||
|
||||
All valid routes are loaded.
|
||||
Miss-configured routes are ignored and errors are logged.
|
||||
"""
|
||||
for route_settings in settings:
|
||||
if 'prefix' in route_settings:
|
||||
prefix_add = self.speaker.prefix_add
|
||||
elif 'route_type' in route_settings:
|
||||
prefix_add = self.speaker.evpn_prefix_add
|
||||
else:
|
||||
LOG.debug('Skip invalid route settings: %s', route_settings)
|
||||
continue
|
||||
|
||||
def _add_networks(self, routing_settings):
|
||||
"""Add networks from given settings to BGPS runtime.
|
||||
|
||||
If any of the networks are miss-configured errors are logged.
|
||||
All valid networks are loaded.
|
||||
"""
|
||||
networks = routing_settings.setdefault('networks', [])
|
||||
for prefix in networks:
|
||||
LOG.debug('Adding route settings: %s', route_settings)
|
||||
try:
|
||||
call('network.add', prefix=prefix)
|
||||
LOG.debug('Added network %s', prefix)
|
||||
prefix_add(**route_settings)
|
||||
except RuntimeConfigError as e:
|
||||
LOG.error(e)
|
||||
continue
|
||||
LOG.exception(e)
|
||||
|
||||
@ -1,49 +1,98 @@
|
||||
import os
|
||||
|
||||
from ryu.services.protocols.bgp.bgpspeaker import RF_VPN_V4
|
||||
from ryu.services.protocols.bgp.bgpspeaker import RF_L2_EVPN
|
||||
from ryu.services.protocols.bgp.bgpspeaker import EVPN_MAC_IP_ADV_ROUTE
|
||||
from ryu.services.protocols.bgp.bgpspeaker import TUNNEL_TYPE_VXLAN
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# BGP configuration.
|
||||
# =============================================================================
|
||||
BGP = {
|
||||
|
||||
# General BGP configuration.
|
||||
'routing': {
|
||||
# ASN for this BGP instance.
|
||||
'local_as': 64512,
|
||||
# AS number for this BGP instance.
|
||||
'local_as': 65001,
|
||||
|
||||
# BGP Router ID.
|
||||
'router_id': '10.10.0.1',
|
||||
# BGP Router ID.
|
||||
'router_id': '172.17.0.1',
|
||||
|
||||
# We list all BGP neighbors below. We establish EBGP sessions with peer
|
||||
# with different AS number then configured above. We will
|
||||
# establish IBGP session if AS number is same.
|
||||
'bgp_neighbors': {
|
||||
'10.0.0.1': {
|
||||
'remote_as': 64513,
|
||||
'multi_exit_disc': 100
|
||||
},
|
||||
'10.10.0.2': {
|
||||
'remote_as': 64514,
|
||||
},
|
||||
# List of BGP neighbors.
|
||||
# The parameters for each neighbor are the same as the arguments of
|
||||
# BGPSpeaker.neighbor_add() method.
|
||||
'neighbors': [
|
||||
{
|
||||
'address': '172.17.0.2',
|
||||
'remote_as': 65002,
|
||||
'enable_ipv4': True,
|
||||
'enable_vpnv4': True,
|
||||
},
|
||||
{
|
||||
'address': '172.17.0.3',
|
||||
'remote_as': 65000,
|
||||
'enable_evpn': True,
|
||||
},
|
||||
],
|
||||
|
||||
'networks': [
|
||||
'10.20.0.0/24',
|
||||
'10.30.0.0/24',
|
||||
'10.40.0.0/16',
|
||||
'10.50.0.0/16',
|
||||
],
|
||||
},
|
||||
# List of BGP VRF tables.
|
||||
# The parameters for each VRF table are the same as the arguments of
|
||||
# BGPSpeaker.vrf_add() method.
|
||||
'vrfs': [
|
||||
{
|
||||
'route_dist': '65001:100',
|
||||
'import_rts': ['65001:100'],
|
||||
'export_rts': ['65001:100'],
|
||||
'route_family': RF_VPN_V4,
|
||||
},
|
||||
{
|
||||
'route_dist': '65000:200',
|
||||
'import_rts': ['65000:200'],
|
||||
'export_rts': ['65000:200'],
|
||||
'route_family': RF_L2_EVPN,
|
||||
},
|
||||
],
|
||||
|
||||
# List of BGP routes.
|
||||
# The parameters for each route are the same as the arguments of
|
||||
# BGPSpeaker.prefix_add() or BGPSpeaker.evpn_prefix_add() method.
|
||||
'routes': [
|
||||
# Example of IPv4 prefix
|
||||
{
|
||||
'prefix': '10.10.1.0/24',
|
||||
},
|
||||
# Example of VPNv4 prefix
|
||||
{
|
||||
'prefix': '10.20.1.0/24',
|
||||
'next_hop': '172.17.0.1',
|
||||
'route_dist': '65001:100',
|
||||
},
|
||||
# Example of EVPN prefix
|
||||
{
|
||||
'route_type': EVPN_MAC_IP_ADV_ROUTE,
|
||||
'route_dist': '65000:200',
|
||||
'esi': 0,
|
||||
'ethernet_tag_id': 0,
|
||||
'tunnel_type': TUNNEL_TYPE_VXLAN,
|
||||
'vni': 200,
|
||||
'mac_addr': 'aa:bb:cc:dd:ee:ff',
|
||||
'ip_addr': '10.30.1.1',
|
||||
'next_hop': '172.17.0.1',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
# SSH = {
|
||||
# 'ssh_port': 4990,
|
||||
# 'ssh_host': 'localhost',
|
||||
# 'ssh_hostkey': '/etc/ssh_host_rsa_key',
|
||||
# 'ssh_username': 'ryu',
|
||||
# 'ssh_password': 'ryu'
|
||||
# }
|
||||
# =============================================================================
|
||||
# SSH server configuration.
|
||||
# =============================================================================
|
||||
SSH = {
|
||||
'ssh_port': 4990,
|
||||
'ssh_host': 'localhost',
|
||||
# 'ssh_host_key': '/etc/ssh_host_rsa_key',
|
||||
# 'ssh_username': 'ryu',
|
||||
# 'ssh_password': 'ryu',
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Logging configuration.
|
||||
@ -101,7 +150,6 @@ LOGGING = {
|
||||
'loggers': {
|
||||
'bgpspeaker': {
|
||||
'handlers': ['console', 'log_file'],
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': False,
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user