--- /dev/null
+# 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.
+
+"""
+ Public API for BGPSpeaker.
+
+ This API can be used by various services like RPC, CLI, IoC, etc.
+"""
+from __future__ import absolute_import
+
+import logging
+import traceback
+
+from ryu.services.protocols.bgp.base import add_bgp_error_metadata
+from ryu.services.protocols.bgp.base import API_ERROR_CODE
+from ryu.services.protocols.bgp.base import BGPSException
+from ryu.services.protocols.bgp.core_manager import CORE_MANAGER
+from ryu.services.protocols.bgp.rtconf.base import get_validator
+from ryu.services.protocols.bgp.rtconf.base import MissingRequiredConf
+from ryu.services.protocols.bgp.rtconf.base import RuntimeConfigError
+
+
+LOG = logging.getLogger('bgpspeaker.api.base')
+
+# Various constants used in API calls
+ROUTE_DISTINGUISHER = 'route_dist'
+PREFIX = 'prefix'
+NEXT_HOP = 'next_hop'
+VPN_LABEL = 'label'
+API_SYM = 'name'
+ORIGIN_RD = 'origin_rd'
+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'
+GW_IP_ADDR = 'gw_ip_addr'
+MPLS_LABELS = 'mpls_labels'
+TUNNEL_TYPE = 'tunnel_type'
+EVPN_VNI = 'vni'
+PMSI_TUNNEL_TYPE = 'pmsi_tunnel_type'
+FLOWSPEC_FAMILY = 'flowspec_family'
+FLOWSPEC_RULES = 'rules'
+FLOWSPEC_ACTIONS = 'actions'
+
+# API call registry
+_CALL_REGISTRY = {}
+
+
+@add_bgp_error_metadata(code=API_ERROR_CODE,
+ sub_code=1,
+ def_desc='Unknown API error.')
+class ApiException(BGPSException):
+ pass
+
+
+@add_bgp_error_metadata(code=API_ERROR_CODE,
+ sub_code=2,
+ def_desc='API symbol or method is not known.')
+class MethodNotFound(ApiException):
+ pass
+
+
+@add_bgp_error_metadata(code=API_ERROR_CODE,
+ sub_code=3,
+ def_desc='Error related to BGPS core not starting.')
+class CoreNotStarted(ApiException):
+ pass
+
+
+def register(**kwargs):
+ """Decorator for registering API function.
+
+ Does not do any check or validation.
+ """
+ def decorator(func):
+ _CALL_REGISTRY[kwargs.get(API_SYM, func.__name__)] = func
+ return func
+
+ return decorator
+
+
+class RegisterWithArgChecks(object):
+ """Decorator for registering API functions.
+
+ Does some argument checking and validation of required arguments.
+ """
+
+ def __init__(self, name, req_args=None, opt_args=None):
+ self._name = name
+ if not req_args:
+ req_args = []
+ self._req_args = req_args
+ if not opt_args:
+ opt_args = []
+ self._opt_args = opt_args
+ self._all_args = (set(self._req_args) | set(self._opt_args))
+
+ def __call__(self, func):
+ """Wraps given function and registers it as API.
+
+ Returns original function.
+ """
+ def wrapped_fun(**kwargs):
+ """Wraps a function to do validation before calling actual func.
+
+ Wraps a function to take key-value args. only. Checks if:
+ 1) all required argument of wrapped function are provided
+ 2) no extra/un-known arguments are passed
+ 3) checks if validator for required arguments is available
+ 4) validates required arguments
+ 5) if validator for optional arguments is registered,
+ validates optional arguments.
+ Raises exception if no validator can be found for required args.
+ """
+ # Check if we are missing arguments.
+ if not kwargs and len(self._req_args) > 0:
+ raise MissingRequiredConf(desc='Missing all required '
+ 'attributes.')
+
+ # Check if we have unknown arguments.
+ given_args = set(kwargs.keys())
+ unknown_attrs = given_args - set(self._all_args)
+ if unknown_attrs:
+ raise RuntimeConfigError(desc=('Unknown attributes %r' %
+ unknown_attrs))
+
+ # Check if required arguments are missing
+ missing_req_args = set(self._req_args) - given_args
+ if missing_req_args:
+ conf_name = ', '.join(missing_req_args)
+ raise MissingRequiredConf(conf_name=conf_name)
+
+ #
+ # Prepare to call wrapped function.
+ #
+ # Collect required arguments in the order asked and validate it.
+ req_values = []
+ for req_arg in self._req_args:
+ req_value = kwargs.get(req_arg)
+ # Validate required value.
+ validator = get_validator(req_arg)
+ if not validator:
+ raise ValueError('No validator registered for function=%s'
+ ' and arg=%s' % (func, req_arg))
+ validator(req_value)
+ req_values.append(req_value)
+
+ # Collect optional arguments.
+ opt_items = {}
+ for opt_arg, opt_value in kwargs.items():
+ if opt_arg in self._opt_args:
+ # Validate optional value.
+ # Note: If no validator registered for optional value,
+ # skips validation.
+ validator = get_validator(opt_arg)
+ if validator:
+ validator(opt_value)
+ opt_items[opt_arg] = opt_value
+
+ # Call actual function
+ return func(*req_values, **opt_items)
+
+ # Register wrapped function
+ _CALL_REGISTRY[self._name] = wrapped_fun
+ return func
+
+
+def is_call_registered(call_name):
+ return call_name in _CALL_REGISTRY
+
+
+def get_call(call_name):
+ return _CALL_REGISTRY.get(call_name)
+
+
+def call(symbol, **kwargs):
+ """Calls/executes BGPS public API identified by given symbol and passes
+ given kwargs as param.
+ """
+ LOG.info("API method %s called with args: %s", symbol, str(kwargs))
+
+ # TODO(PH, JK) improve the way api function modules are loaded
+ from . import all # noqa
+ if not is_call_registered(symbol):
+ message = 'Did not find any method registered by symbol %s' % symbol
+ raise MethodNotFound(message)
+
+ if not symbol.startswith('core') and not CORE_MANAGER.started:
+ raise CoreNotStarted(desc='CoreManager is not active.')
+
+ call = get_call(symbol)
+ try:
+ return call(**kwargs)
+ except BGPSException as r:
+ LOG.error(traceback.format_exc())
+ raise r
+ except Exception as e:
+ LOG.error(traceback.format_exc())
+ raise ApiException(desc=str(e))