second try
[vsorcdistro/.git] / ryu / ryu / services / protocols / bgp / api / base.py
1 # Copyright (C) 2014 Nippon Telegraph and Telephone Corporation.
2 #
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
6 #
7 #    http://www.apache.org/licenses/LICENSE-2.0
8 #
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
12 # implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 """
17  Public API for BGPSpeaker.
18
19  This API can be used by various services like RPC, CLI, IoC, etc.
20 """
21 from __future__ import absolute_import
22
23 import logging
24 import traceback
25
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
33
34
35 LOG = logging.getLogger('bgpspeaker.api.base')
36
37 # Various constants used in API calls
38 ROUTE_DISTINGUISHER = 'route_dist'
39 PREFIX = 'prefix'
40 NEXT_HOP = 'next_hop'
41 VPN_LABEL = 'label'
42 API_SYM = 'name'
43 ORIGIN_RD = 'origin_rd'
44 ROUTE_FAMILY = 'route_family'
45 EVPN_ROUTE_TYPE = 'route_type'
46 EVPN_ESI = 'esi'
47 EVPN_ETHERNET_TAG_ID = 'ethernet_tag_id'
48 REDUNDANCY_MODE = 'redundancy_mode'
49 MAC_ADDR = 'mac_addr'
50 IP_ADDR = 'ip_addr'
51 IP_PREFIX = 'ip_prefix'
52 GW_IP_ADDR = 'gw_ip_addr'
53 MPLS_LABELS = 'mpls_labels'
54 TUNNEL_TYPE = 'tunnel_type'
55 EVPN_VNI = 'vni'
56 PMSI_TUNNEL_TYPE = 'pmsi_tunnel_type'
57 FLOWSPEC_FAMILY = 'flowspec_family'
58 FLOWSPEC_RULES = 'rules'
59 FLOWSPEC_ACTIONS = 'actions'
60
61 # API call registry
62 _CALL_REGISTRY = {}
63
64
65 @add_bgp_error_metadata(code=API_ERROR_CODE,
66                         sub_code=1,
67                         def_desc='Unknown API error.')
68 class ApiException(BGPSException):
69     pass
70
71
72 @add_bgp_error_metadata(code=API_ERROR_CODE,
73                         sub_code=2,
74                         def_desc='API symbol or method is not known.')
75 class MethodNotFound(ApiException):
76     pass
77
78
79 @add_bgp_error_metadata(code=API_ERROR_CODE,
80                         sub_code=3,
81                         def_desc='Error related to BGPS core not starting.')
82 class CoreNotStarted(ApiException):
83     pass
84
85
86 def register(**kwargs):
87     """Decorator for registering API function.
88
89     Does not do any check or validation.
90     """
91     def decorator(func):
92         _CALL_REGISTRY[kwargs.get(API_SYM, func.__name__)] = func
93         return func
94
95     return decorator
96
97
98 class RegisterWithArgChecks(object):
99     """Decorator for registering API functions.
100
101     Does some argument checking and validation of required arguments.
102     """
103
104     def __init__(self, name, req_args=None, opt_args=None):
105         self._name = name
106         if not req_args:
107             req_args = []
108         self._req_args = req_args
109         if not opt_args:
110             opt_args = []
111         self._opt_args = opt_args
112         self._all_args = (set(self._req_args) | set(self._opt_args))
113
114     def __call__(self, func):
115         """Wraps given function and registers it as API.
116
117             Returns original function.
118         """
119         def wrapped_fun(**kwargs):
120             """Wraps a function to do validation before calling actual func.
121
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.
130             """
131             # Check if we are missing arguments.
132             if not kwargs and len(self._req_args) > 0:
133                 raise MissingRequiredConf(desc='Missing all required '
134                                           'attributes.')
135
136             # Check if we have unknown arguments.
137             given_args = set(kwargs.keys())
138             unknown_attrs = given_args - set(self._all_args)
139             if unknown_attrs:
140                 raise RuntimeConfigError(desc=('Unknown attributes %r' %
141                                                unknown_attrs))
142
143             # Check if required arguments are missing
144             missing_req_args = set(self._req_args) - given_args
145             if missing_req_args:
146                 conf_name = ', '.join(missing_req_args)
147                 raise MissingRequiredConf(conf_name=conf_name)
148
149             #
150             # Prepare to call wrapped function.
151             #
152             # Collect required arguments in the order asked and validate it.
153             req_values = []
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)
158                 if not validator:
159                     raise ValueError('No validator registered for function=%s'
160                                      ' and arg=%s' % (func, req_arg))
161                 validator(req_value)
162                 req_values.append(req_value)
163
164             # Collect optional arguments.
165             opt_items = {}
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,
170                     # skips validation.
171                     validator = get_validator(opt_arg)
172                     if validator:
173                         validator(opt_value)
174                     opt_items[opt_arg] = opt_value
175
176             # Call actual function
177             return func(*req_values, **opt_items)
178
179         # Register wrapped function
180         _CALL_REGISTRY[self._name] = wrapped_fun
181         return func
182
183
184 def is_call_registered(call_name):
185     return call_name in _CALL_REGISTRY
186
187
188 def get_call(call_name):
189     return _CALL_REGISTRY.get(call_name)
190
191
192 def call(symbol, **kwargs):
193     """Calls/executes BGPS public API identified by given symbol and passes
194     given kwargs as param.
195     """
196     LOG.info("API method %s called with args: %s", symbol, str(kwargs))
197
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)
203
204     if not symbol.startswith('core') and not CORE_MANAGER.started:
205         raise CoreNotStarted(desc='CoreManager is not active.')
206
207     call = get_call(symbol)
208     try:
209         return call(**kwargs)
210     except BGPSException as r:
211         LOG.error(traceback.format_exc())
212         raise r
213     except Exception as e:
214         LOG.error(traceback.format_exc())
215         raise ApiException(desc=str(e))