File: base.py

package info (click to toggle)
python-os-ken 3.1.1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 21,320 kB
  • sloc: python: 100,703; erlang: 14,517; ansic: 594; sh: 338; makefile: 136
file content (222 lines) | stat: -rw-r--r-- 7,459 bytes parent folder | download | duplicates (3)
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))