1 # Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
17 Public API for BGPSpeaker.
19 This API can be used by various services like RPC, CLI, IoC, etc.
21 from __future__ import absolute_import
26 from ryu.services.protocols.bgp.base import add_bgp_error_metadata
27 from ryu.services.protocols.bgp.base import API_ERROR_CODE
28 from ryu.services.protocols.bgp.base import BGPSException
29 from ryu.services.protocols.bgp.core_manager import CORE_MANAGER
30 from ryu.services.protocols.bgp.rtconf.base import get_validator
31 from ryu.services.protocols.bgp.rtconf.base import MissingRequiredConf
32 from ryu.services.protocols.bgp.rtconf.base import RuntimeConfigError
35 LOG = logging.getLogger('bgpspeaker.api.base')
37 # Various constants used in API calls
38 ROUTE_DISTINGUISHER = 'route_dist'
43 ORIGIN_RD = 'origin_rd'
44 ROUTE_FAMILY = 'route_family'
45 EVPN_ROUTE_TYPE = 'route_type'
47 EVPN_ETHERNET_TAG_ID = 'ethernet_tag_id'
48 REDUNDANCY_MODE = 'redundancy_mode'
51 IP_PREFIX = 'ip_prefix'
52 GW_IP_ADDR = 'gw_ip_addr'
53 MPLS_LABELS = 'mpls_labels'
54 TUNNEL_TYPE = 'tunnel_type'
56 PMSI_TUNNEL_TYPE = 'pmsi_tunnel_type'
57 FLOWSPEC_FAMILY = 'flowspec_family'
58 FLOWSPEC_RULES = 'rules'
59 FLOWSPEC_ACTIONS = 'actions'
65 @add_bgp_error_metadata(code=API_ERROR_CODE,
67 def_desc='Unknown API error.')
68 class ApiException(BGPSException):
72 @add_bgp_error_metadata(code=API_ERROR_CODE,
74 def_desc='API symbol or method is not known.')
75 class MethodNotFound(ApiException):
79 @add_bgp_error_metadata(code=API_ERROR_CODE,
81 def_desc='Error related to BGPS core not starting.')
82 class CoreNotStarted(ApiException):
86 def register(**kwargs):
87 """Decorator for registering API function.
89 Does not do any check or validation.
92 _CALL_REGISTRY[kwargs.get(API_SYM, func.__name__)] = func
98 class RegisterWithArgChecks(object):
99 """Decorator for registering API functions.
101 Does some argument checking and validation of required arguments.
104 def __init__(self, name, req_args=None, opt_args=None):
108 self._req_args = req_args
111 self._opt_args = opt_args
112 self._all_args = (set(self._req_args) | set(self._opt_args))
114 def __call__(self, func):
115 """Wraps given function and registers it as API.
117 Returns original function.
119 def wrapped_fun(**kwargs):
120 """Wraps a function to do validation before calling actual func.
122 Wraps a function to take key-value args. only. Checks if:
123 1) all required argument of wrapped function are provided
124 2) no extra/un-known arguments are passed
125 3) checks if validator for required arguments is available
126 4) validates required arguments
127 5) if validator for optional arguments is registered,
128 validates optional arguments.
129 Raises exception if no validator can be found for required args.
131 # Check if we are missing arguments.
132 if not kwargs and len(self._req_args) > 0:
133 raise MissingRequiredConf(desc='Missing all required '
136 # Check if we have unknown arguments.
137 given_args = set(kwargs.keys())
138 unknown_attrs = given_args - set(self._all_args)
140 raise RuntimeConfigError(desc=('Unknown attributes %r' %
143 # Check if required arguments are missing
144 missing_req_args = set(self._req_args) - given_args
146 conf_name = ', '.join(missing_req_args)
147 raise MissingRequiredConf(conf_name=conf_name)
150 # Prepare to call wrapped function.
152 # Collect required arguments in the order asked and validate it.
154 for req_arg in self._req_args:
155 req_value = kwargs.get(req_arg)
156 # Validate required value.
157 validator = get_validator(req_arg)
159 raise ValueError('No validator registered for function=%s'
160 ' and arg=%s' % (func, req_arg))
162 req_values.append(req_value)
164 # Collect optional arguments.
166 for opt_arg, opt_value in kwargs.items():
167 if opt_arg in self._opt_args:
168 # Validate optional value.
169 # Note: If no validator registered for optional value,
171 validator = get_validator(opt_arg)
174 opt_items[opt_arg] = opt_value
176 # Call actual function
177 return func(*req_values, **opt_items)
179 # Register wrapped function
180 _CALL_REGISTRY[self._name] = wrapped_fun
184 def is_call_registered(call_name):
185 return call_name in _CALL_REGISTRY
188 def get_call(call_name):
189 return _CALL_REGISTRY.get(call_name)
192 def call(symbol, **kwargs):
193 """Calls/executes BGPS public API identified by given symbol and passes
194 given kwargs as param.
196 LOG.info("API method %s called with args: %s", symbol, str(kwargs))
198 # TODO(PH, JK) improve the way api function modules are loaded
199 from . import all # noqa
200 if not is_call_registered(symbol):
201 message = 'Did not find any method registered by symbol %s' % symbol
202 raise MethodNotFound(message)
204 if not symbol.startswith('core') and not CORE_MANAGER.started:
205 raise CoreNotStarted(desc='CoreManager is not active.')
207 call = get_call(symbol)
209 return call(**kwargs)
210 except BGPSException as r:
211 LOG.error(traceback.format_exc())
213 except Exception as e:
214 LOG.error(traceback.format_exc())
215 raise ApiException(desc=str(e))