1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
# 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.
"""
import logging
import traceback
from os_ken.services.protocols.bgp.base import add_bgp_error_metadata
from os_ken.services.protocols.bgp.base import API_ERROR_CODE
from os_ken.services.protocols.bgp.base import BGPSException
from os_ken.services.protocols.bgp.core_manager import CORE_MANAGER
from os_ken.services.protocols.bgp.rtconf.base import get_validator
from os_ken.services.protocols.bgp.rtconf.base import MissingRequiredConf
from os_ken.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'
TUNNEL_ENDPOINT_IP = 'tunnel_endpoint_ip'
MAC_MOBILITY = 'mac_mobility'
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.
"""
if 'password' in kwargs:
log_str = str(
dict(kwargs.items() - {'password': kwargs['password']}.items()))
else:
log_str = str(kwargs)
LOG.info(
"API method %s called with args: %s", symbol, log_str)
# 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))
|