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 Running or runtime configuration base classes.
18"""
19from abc import ABCMeta
20from abc import abstractmethod
21import functools
22import numbers
23import logging
24import uuid
25
26import six
27
28from ryu.services.protocols.bgp.base import add_bgp_error_metadata
29from ryu.services.protocols.bgp.base import BGPSException
30from ryu.services.protocols.bgp.base import get_validator
31from ryu.services.protocols.bgp.base import RUNTIME_CONF_ERROR_CODE
32from ryu.services.protocols.bgp.base import validate
33from ryu.services.protocols.bgp.utils import validation
34from ryu.services.protocols.bgp.utils.validation import is_valid_asn
35
36LOG = logging.getLogger('bgpspeaker.rtconf.base')
37
38#
39# Nested settings.
40#
41CAP_REFRESH = 'cap_refresh'
42CAP_ENHANCED_REFRESH = 'cap_enhanced_refresh'
43CAP_FOUR_OCTET_AS_NUMBER = 'cap_four_octet_as_number'
44CAP_MBGP_IPV4 = 'cap_mbgp_ipv4'
45CAP_MBGP_IPV6 = 'cap_mbgp_ipv6'
46CAP_MBGP_VPNV4 = 'cap_mbgp_vpnv4'
47CAP_MBGP_VPNV6 = 'cap_mbgp_vpnv6'
48CAP_MBGP_EVPN = 'cap_mbgp_evpn'
49CAP_MBGP_IPV4FS = 'cap_mbgp_ipv4fs'
50CAP_MBGP_IPV6FS = 'cap_mbgp_ipv6fs'
51CAP_MBGP_VPNV4FS = 'cap_mbgp_vpnv4fs'
52CAP_MBGP_VPNV6FS = 'cap_mbgp_vpnv6fs'
53CAP_MBGP_L2VPNFS = 'cap_mbgp_l2vpnfs'
54CAP_RTC = 'cap_rtc'
55RTC_AS = 'rtc_as'
56HOLD_TIME = 'hold_time'
57
58# To control how many prefixes can be received from a neighbor.
59# 0 value indicates no limit and other related options will be ignored.
60# Current behavior is to log that limit has reached.
61MAX_PREFIXES = 'max_prefixes'
62
63# Has same meaning as: http://www.juniper.net/techpubs/software/junos/junos94
64# /swconfig-routing/disabling-suppression-of-route-
65# advertisements.html#id-13255463
66ADVERTISE_PEER_AS = 'advertise_peer_as'
67
68# MED - MULTI_EXIT_DISC
69MULTI_EXIT_DISC = 'multi_exit_disc'
70
71# Extended community attribute route origin.
72SITE_OF_ORIGINS = 'site_of_origins'
73
74# Constants related to errors.
75CONF_NAME = 'conf_name'
76CONF_VALUE = 'conf_value'
77
78# Max. value  limits
79MAX_NUM_IMPORT_RT = 1000
80MAX_NUM_EXPORT_RT = 250
81MAX_NUM_SOO = 10
82
83
84# =============================================================================
85# Runtime configuration errors or exceptions.
86# =============================================================================
87
88@add_bgp_error_metadata(code=RUNTIME_CONF_ERROR_CODE, sub_code=1,
89                        def_desc='Error with runtime-configuration.')
90class RuntimeConfigError(BGPSException):
91    """Base class for all runtime configuration errors.
92    """
93    pass
94
95
96@add_bgp_error_metadata(code=RUNTIME_CONF_ERROR_CODE, sub_code=2,
97                        def_desc='Missing required configuration.')
98class MissingRequiredConf(RuntimeConfigError):
99    """Exception raised when trying to configure with missing required
100    settings.
101    """
102
103    def __init__(self, **kwargs):
104        conf_name = kwargs.get('conf_name')
105        if conf_name:
106            super(MissingRequiredConf, self).__init__(
107                desc='Missing required configuration: %s' % conf_name)
108        else:
109            super(MissingRequiredConf, self).__init__(desc=kwargs.get('desc'))
110
111
112@add_bgp_error_metadata(code=RUNTIME_CONF_ERROR_CODE, sub_code=3,
113                        def_desc='Incorrect Type for configuration.')
114class ConfigTypeError(RuntimeConfigError):
115    """Exception raised when configuration value type miss-match happens.
116    """
117
118    def __init__(self, **kwargs):
119        conf_name = kwargs.get(CONF_NAME)
120        conf_value = kwargs.get(CONF_VALUE)
121        if conf_name and conf_value:
122            super(ConfigTypeError, self).__init__(
123                desc='Incorrect Type %s for configuration: %s' %
124                (conf_value, conf_name))
125        elif conf_name:
126            super(ConfigTypeError, self).__init__(
127                desc='Incorrect Type for configuration: %s' % conf_name)
128        else:
129            super(ConfigTypeError, self).__init__(desc=kwargs.get('desc'))
130
131
132@add_bgp_error_metadata(code=RUNTIME_CONF_ERROR_CODE, sub_code=4,
133                        def_desc='Incorrect Value for configuration.')
134class ConfigValueError(RuntimeConfigError):
135    """Exception raised when configuration value is of correct type but
136    incorrect value.
137    """
138
139    def __init__(self, **kwargs):
140        conf_name = kwargs.get(CONF_NAME)
141        conf_value = kwargs.get(CONF_VALUE)
142        if conf_name and conf_value:
143            super(ConfigValueError, self).__init__(
144                desc='Incorrect Value %s for configuration: %s' %
145                (conf_value, conf_name))
146        elif conf_name:
147            super(ConfigValueError, self).__init__(
148                desc='Incorrect Value for configuration: %s' % conf_name)
149        else:
150            super(ConfigValueError, self).__init__(desc=kwargs.get('desc'))
151
152
153# =============================================================================
154# Configuration base classes.
155# =============================================================================
156
157@six.add_metaclass(ABCMeta)
158class BaseConf(object):
159    """Base class for a set of configuration values.
160
161    Configurations can be required or optional. Also acts as a container of
162    configuration change listeners.
163    """
164
165    def __init__(self, **kwargs):
166        self._req_settings = self.get_req_settings()
167        self._opt_settings = self.get_opt_settings()
168        self._valid_evts = self.get_valid_evts()
169        self._listeners = {}
170        self._settings = {}
171
172        # validate required and unknown settings
173        self._validate_req_unknown_settings(**kwargs)
174
175        # Initialize configuration settings.
176        self._init_req_settings(**kwargs)
177        self._init_opt_settings(**kwargs)
178
179    @property
180    def settings(self):
181        """Returns a copy of current settings."""
182        return self._settings.copy()
183
184    @classmethod
185    def get_valid_evts(cls):
186        return set()
187
188    @classmethod
189    def get_req_settings(cls):
190        return set()
191
192    @classmethod
193    def get_opt_settings(cls):
194        return set()
195
196    @abstractmethod
197    def _init_opt_settings(self, **kwargs):
198        """Sub-classes should override this method to initialize optional
199         settings.
200        """
201        pass
202
203    @abstractmethod
204    def update(self, **kwargs):
205        # Validate given values
206        self._validate_req_unknown_settings(**kwargs)
207
208    def _validate_req_unknown_settings(self, **kwargs):
209        """Checks if required settings are present.
210
211        Also checks if unknown requirements are present.
212        """
213        # Validate given configuration.
214        self._all_attrs = (self._req_settings | self._opt_settings)
215        if not kwargs and len(self._req_settings) > 0:
216            raise MissingRequiredConf(desc='Missing all required attributes.')
217
218        given_attrs = frozenset(kwargs.keys())
219        unknown_attrs = given_attrs - self._all_attrs
220        if unknown_attrs:
221            raise RuntimeConfigError(desc=(
222                'Unknown attributes: %s' %
223                ', '.join([str(i) for i in unknown_attrs])))
224        missing_req_settings = self._req_settings - given_attrs
225        if missing_req_settings:
226            raise MissingRequiredConf(conf_name=list(missing_req_settings))
227
228    def _init_req_settings(self, **kwargs):
229        for req_attr in self._req_settings:
230            req_attr_value = kwargs.get(req_attr)
231            if req_attr_value is None:
232                raise MissingRequiredConf(conf_name=req_attr_value)
233            # Validate attribute value
234            req_attr_value = get_validator(req_attr)(req_attr_value)
235            self._settings[req_attr] = req_attr_value
236
237    def add_listener(self, evt, callback):
238        #   if (evt not in self.get_valid_evts()):
239        #       raise RuntimeConfigError(desc=('Unknown event %s' % evt))
240
241        listeners = self._listeners.get(evt, None)
242        if not listeners:
243            listeners = set()
244            self._listeners[evt] = listeners
245        listeners.update([callback])
246
247    def remove_listener(self, evt, callback):
248        if evt in self.get_valid_evts():
249            listeners = self._listeners.get(evt, None)
250            if listeners and (callback in listeners):
251                listeners.remove(callback)
252                return True
253
254        return False
255
256    def _notify_listeners(self, evt, value):
257        listeners = self._listeners.get(evt, [])
258        for callback in listeners:
259            callback(ConfEvent(self, evt, value))
260
261    def __repr__(self):
262        return '%s(%r)' % (self.__class__, self._settings)
263
264
265class ConfWithId(BaseConf):
266    """Configuration settings related to identity."""
267    # Config./resource identifier.
268    ID = 'id'
269    # Config./resource name.
270    NAME = 'name'
271    # Config./resource description.
272    DESCRIPTION = 'description'
273
274    UPDATE_NAME_EVT = 'update_name_evt'
275    UPDATE_DESCRIPTION_EVT = 'update_description_evt'
276
277    VALID_EVT = frozenset([UPDATE_NAME_EVT, UPDATE_DESCRIPTION_EVT])
278    OPTIONAL_SETTINGS = frozenset([ID, NAME, DESCRIPTION])
279
280    def __init__(self, **kwargs):
281        super(ConfWithId, self).__init__(**kwargs)
282
283    @classmethod
284    def get_opt_settings(cls):
285        self_confs = super(ConfWithId, cls).get_opt_settings()
286        self_confs.update(ConfWithId.OPTIONAL_SETTINGS)
287        return self_confs
288
289    @classmethod
290    def get_req_settings(cls):
291        self_confs = super(ConfWithId, cls).get_req_settings()
292        return self_confs
293
294    @classmethod
295    def get_valid_evts(cls):
296        self_valid_evts = super(ConfWithId, cls).get_valid_evts()
297        self_valid_evts.update(ConfWithId.VALID_EVT)
298        return self_valid_evts
299
300    def _init_opt_settings(self, **kwargs):
301        super(ConfWithId, self)._init_opt_settings(**kwargs)
302        self._settings[ConfWithId.ID] = \
303            compute_optional_conf(ConfWithId.ID, str(uuid.uuid4()), **kwargs)
304        self._settings[ConfWithId.NAME] = \
305            compute_optional_conf(ConfWithId.NAME, str(self), **kwargs)
306        self._settings[ConfWithId.DESCRIPTION] = \
307            compute_optional_conf(ConfWithId.DESCRIPTION, str(self), **kwargs)
308
309    @property
310    def id(self):
311        return self._settings[ConfWithId.ID]
312
313    @property
314    def name(self):
315        return self._settings[ConfWithId.NAME]
316
317    @name.setter
318    def name(self, new_name):
319        old_name = self.name
320        if not new_name:
321            new_name = repr(self)
322        else:
323            get_validator(ConfWithId.NAME)(new_name)
324
325        if old_name != new_name:
326            self._settings[ConfWithId.NAME] = new_name
327            self._notify_listeners(ConfWithId.UPDATE_NAME_EVT,
328                                   (old_name, self.name))
329
330    @property
331    def description(self):
332        return self._settings[ConfWithId.DESCRIPTION]
333
334    @description.setter
335    def description(self, new_description):
336        old_desc = self.description
337        if not new_description:
338            new_description = str(self)
339        else:
340            get_validator(ConfWithId.DESCRIPTION)(new_description)
341
342        if old_desc != new_description:
343            self._settings[ConfWithId.DESCRIPTION] = new_description
344            self._notify_listeners(ConfWithId.UPDATE_DESCRIPTION_EVT,
345                                   (old_desc, self.description))
346
347    def update(self, **kwargs):
348        # Update inherited configurations
349        super(ConfWithId, self).update(**kwargs)
350        self.name = compute_optional_conf(ConfWithId.NAME,
351                                          str(self),
352                                          **kwargs)
353        self.description = compute_optional_conf(ConfWithId.DESCRIPTION,
354                                                 str(self),
355                                                 **kwargs)
356
357
358class ConfWithStats(BaseConf):
359    """Configuration settings related to statistics collection."""
360
361    # Enable or disable statistics logging.
362    STATS_LOG_ENABLED = 'statistics_log_enabled'
363    DEFAULT_STATS_LOG_ENABLED = False
364
365    # Statistics logging time.
366    STATS_TIME = 'statistics_interval'
367    DEFAULT_STATS_TIME = 60
368
369    UPDATE_STATS_LOG_ENABLED_EVT = 'update_stats_log_enabled_evt'
370    UPDATE_STATS_TIME_EVT = 'update_stats_time_evt'
371
372    VALID_EVT = frozenset([UPDATE_STATS_LOG_ENABLED_EVT,
373                           UPDATE_STATS_TIME_EVT])
374    OPTIONAL_SETTINGS = frozenset([STATS_LOG_ENABLED, STATS_TIME])
375
376    def __init__(self, **kwargs):
377        super(ConfWithStats, self).__init__(**kwargs)
378
379    def _init_opt_settings(self, **kwargs):
380        super(ConfWithStats, self)._init_opt_settings(**kwargs)
381        self._settings[ConfWithStats.STATS_LOG_ENABLED] = \
382            compute_optional_conf(ConfWithStats.STATS_LOG_ENABLED,
383                                  ConfWithStats.DEFAULT_STATS_LOG_ENABLED,
384                                  **kwargs)
385        self._settings[ConfWithStats.STATS_TIME] = \
386            compute_optional_conf(ConfWithStats.STATS_TIME,
387                                  ConfWithStats.DEFAULT_STATS_TIME,
388                                  **kwargs)
389
390    @property
391    def stats_log_enabled(self):
392        return self._settings[ConfWithStats.STATS_LOG_ENABLED]
393
394    @stats_log_enabled.setter
395    def stats_log_enabled(self, enabled):
396        get_validator(ConfWithStats.STATS_LOG_ENABLED)(enabled)
397        if enabled != self.stats_log_enabled:
398            self._settings[ConfWithStats.STATS_LOG_ENABLED] = enabled
399            self._notify_listeners(ConfWithStats.UPDATE_STATS_LOG_ENABLED_EVT,
400                                   enabled)
401
402    @property
403    def stats_time(self):
404        return self._settings[ConfWithStats.STATS_TIME]
405
406    @stats_time.setter
407    def stats_time(self, stats_time):
408        get_validator(ConfWithStats.STATS_TIME)(stats_time)
409        if stats_time != self.stats_time:
410            self._settings[ConfWithStats.STATS_TIME] = stats_time
411            self._notify_listeners(ConfWithStats.UPDATE_STATS_TIME_EVT,
412                                   stats_time)
413
414    @classmethod
415    def get_opt_settings(cls):
416        confs = super(ConfWithStats, cls).get_opt_settings()
417        confs.update(ConfWithStats.OPTIONAL_SETTINGS)
418        return confs
419
420    @classmethod
421    def get_valid_evts(cls):
422        valid_evts = super(ConfWithStats, cls).get_valid_evts()
423        valid_evts.update(ConfWithStats.VALID_EVT)
424        return valid_evts
425
426    def update(self, **kwargs):
427        # Update inherited configurations
428        super(ConfWithStats, self).update(**kwargs)
429        self.stats_log_enabled = \
430            compute_optional_conf(ConfWithStats.STATS_LOG_ENABLED,
431                                  ConfWithStats.DEFAULT_STATS_LOG_ENABLED,
432                                  **kwargs)
433        self.stats_time = \
434            compute_optional_conf(ConfWithStats.STATS_TIME,
435                                  ConfWithStats.DEFAULT_STATS_TIME,
436                                  **kwargs)
437
438
439@six.add_metaclass(ABCMeta)
440class BaseConfListener(object):
441    """Base class of all configuration listeners."""
442
443    def __init__(self, base_conf):
444        pass
445    # TODO(PH): re-vist later and check if we need this check
446#         if not isinstance(base_conf, BaseConf):
447#             raise TypeError('Currently we only support listening to '
448#                             'instances of BaseConf')
449
450
451class ConfWithIdListener(BaseConfListener):
452
453    def __init__(self, conf_with_id):
454        assert conf_with_id
455        super(ConfWithIdListener, self).__init__(conf_with_id)
456        conf_with_id.add_listener(ConfWithId.UPDATE_NAME_EVT,
457                                  self.on_chg_name_conf_with_id)
458        conf_with_id.add_listener(ConfWithId.UPDATE_DESCRIPTION_EVT,
459                                  self.on_chg_desc_conf_with_id)
460
461    def on_chg_name_conf_with_id(self, conf_evt):
462        # Note did not makes this method abstract as this is not important
463        # event.
464        raise NotImplementedError()
465
466    def on_chg_desc_conf_with_id(self, conf_evt):
467        # Note did not makes this method abstract as this is not important
468        # event.
469        raise NotImplementedError()
470
471
472class ConfWithStatsListener(BaseConfListener):
473
474    def __init__(self, conf_with_stats):
475        assert conf_with_stats
476        super(ConfWithStatsListener, self).__init__(conf_with_stats)
477        conf_with_stats.add_listener(
478            ConfWithStats.UPDATE_STATS_LOG_ENABLED_EVT,
479            self.on_chg_stats_enabled_conf_with_stats)
480
481        conf_with_stats.add_listener(ConfWithStats.UPDATE_STATS_TIME_EVT,
482                                     self.on_chg_stats_time_conf_with_stats)
483
484    @abstractmethod
485    def on_chg_stats_time_conf_with_stats(self, conf_evt):
486        raise NotImplementedError()
487
488    @abstractmethod
489    def on_chg_stats_enabled_conf_with_stats(self, conf_evt):
490        raise NotImplementedError()
491
492
493@functools.total_ordering
494class ConfEvent(object):
495    """Encapsulates configuration settings change/update event."""
496
497    def __init__(self, evt_src, evt_name, evt_value):
498        """Creates an instance using given parameters.
499
500        Parameters:
501            -`evt_src`: (BaseConf) source of the event
502            -`evt_name`: (str) name of event, has to be one of the valid
503            event of `evt_src`
504            - `evt_value`: (tuple) event context that helps event handler
505        """
506        if evt_name not in evt_src.get_valid_evts():
507            raise ValueError('Event %s is not a valid event for type %s.' %
508                             (evt_name, type(evt_src)))
509        self._src = evt_src
510        self._name = evt_name
511        self._value = evt_value
512
513    @property
514    def src(self):
515        return self._src
516
517    @property
518    def name(self):
519        return self._name
520
521    @property
522    def value(self):
523        return self._value
524
525    def __repr__(self):
526        return '<ConfEvent(%s, %s, %s)>' % (self.src, self.name, self.value)
527
528    def __str__(self):
529        return ('ConfEvent(src=%s, name=%s, value=%s)' %
530                (self.src, self.name, self.value))
531
532    def __lt__(self, other):
533        return ((self.src, self.name, self.value) <
534                (other.src, other.name, other.value))
535
536    def __eq__(self, other):
537        return ((self.src, self.name, self.value) ==
538                (other.src, other.name, other.value))
539
540
541# =============================================================================
542# Runtime configuration setting validators and their registry.
543# =============================================================================
544
545@validate(name=ConfWithId.ID)
546def validate_conf_id(identifier):
547    if not isinstance(identifier, str):
548        raise ConfigTypeError(conf_name=ConfWithId.ID, conf_value=identifier)
549    if len(identifier) > 128:
550        raise ConfigValueError(conf_name=ConfWithId.ID, conf_value=identifier)
551    return identifier
552
553
554@validate(name=ConfWithId.NAME)
555def validate_conf_name(name):
556    if not isinstance(name, str):
557        raise ConfigTypeError(conf_name=ConfWithId.NAME, conf_value=name)
558    if len(name) > 128:
559        raise ConfigValueError(conf_name=ConfWithId.NAME, conf_value=name)
560    return name
561
562
563@validate(name=ConfWithId.DESCRIPTION)
564def validate_conf_desc(description):
565    if not isinstance(description, str):
566        raise ConfigTypeError(conf_name=ConfWithId.DESCRIPTION,
567                              conf_value=description)
568    return description
569
570
571@validate(name=ConfWithStats.STATS_LOG_ENABLED)
572def validate_stats_log_enabled(stats_log_enabled):
573    if not isinstance(stats_log_enabled, bool):
574        raise ConfigTypeError(desc='Statistics log enabled settings can only'
575                              ' be boolean type.')
576    return stats_log_enabled
577
578
579@validate(name=ConfWithStats.STATS_TIME)
580def validate_stats_time(stats_time):
581    if not isinstance(stats_time, numbers.Integral):
582        raise ConfigTypeError(desc='Statistics log timer value has to be of '
583                              'integral type but got: %r' % stats_time)
584    if stats_time < 10:
585        raise ConfigValueError(desc='Statistics log timer cannot be set to '
586                               'less then 10 sec, given timer value %s.' %
587                               stats_time)
588    return stats_time
589
590
591@validate(name=CAP_REFRESH)
592def validate_cap_refresh(crefresh):
593    if not isinstance(crefresh, bool):
594        raise ConfigTypeError(desc='Invalid Refresh capability settings: %s. '
595                              'Boolean value expected' % crefresh)
596    return crefresh
597
598
599@validate(name=CAP_ENHANCED_REFRESH)
600def validate_cap_enhanced_refresh(cer):
601    if not isinstance(cer, bool):
602        raise ConfigTypeError(desc='Invalid Enhanced Refresh capability '
603                              'settings: %s. Boolean value expected' % cer)
604    return cer
605
606
607@validate(name=CAP_FOUR_OCTET_AS_NUMBER)
608def validate_cap_four_octet_as_number(cfoan):
609    if not isinstance(cfoan, bool):
610        raise ConfigTypeError(desc='Invalid Four-Octet AS Number capability '
611                              'settings: %s boolean value expected' % cfoan)
612    return cfoan
613
614
615@validate(name=CAP_MBGP_IPV4)
616def validate_cap_mbgp_ipv4(cmv4):
617    if not isinstance(cmv4, bool):
618        raise ConfigTypeError(desc='Invalid MP-BGP IPv4 capability '
619                              'settings: %s. Boolean value expected' % cmv4)
620
621    return cmv4
622
623
624@validate(name=CAP_MBGP_IPV6)
625def validate_cap_mbgp_ipv6(cmv6):
626    if not isinstance(cmv6, bool):
627        raise ConfigTypeError(desc='Invalid MP-BGP IPv6 capability '
628                              'settings: %s. Boolean value expected' % cmv6)
629
630    return cmv6
631
632
633@validate(name=CAP_MBGP_VPNV4)
634def validate_cap_mbgp_vpnv4(cmv4):
635    if not isinstance(cmv4, bool):
636        raise ConfigTypeError(desc='Invalid MP-BGP VPNv4 capability '
637                              'settings: %s. Boolean value expected' % cmv4)
638
639    return cmv4
640
641
642@validate(name=CAP_MBGP_VPNV6)
643def validate_cap_mbgp_vpnv6(cmv6):
644    if not isinstance(cmv6, bool):
645        raise ConfigTypeError(desc='Invalid MP-BGP VPNv6 capability '
646                              'settings: %s. Boolean value expected' % cmv6)
647
648    return cmv6
649
650
651@validate(name=CAP_MBGP_EVPN)
652def validate_cap_mbgp_evpn(cmevpn):
653    if not isinstance(cmevpn, bool):
654        raise ConfigTypeError(desc='Invalid Ethernet VPN capability '
655                              'settings: %s. Boolean value expected' % cmevpn)
656    return cmevpn
657
658
659@validate(name=CAP_MBGP_IPV4FS)
660def validate_cap_mbgp_ipv4fs(cmv4fs):
661    if not isinstance(cmv4fs, bool):
662        raise ConfigTypeError(desc='Invalid MP-BGP '
663                              'IPv4 Flow Specification capability '
664                              'settings: %s. Boolean value expected' % cmv4fs)
665    return cmv4fs
666
667
668@validate(name=CAP_MBGP_IPV6FS)
669def validate_cap_mbgp_ipv6fs(cmv6fs):
670    if not isinstance(cmv6fs, bool):
671        raise ConfigTypeError(desc='Invalid MP-BGP '
672                              'IPv6 Flow Specification capability '
673                              'settings: %s. Boolean value expected' % cmv6fs)
674    return cmv6fs
675
676
677@validate(name=CAP_MBGP_VPNV4FS)
678def validate_cap_mbgp_vpnv4fs(cmv4fs):
679    if not isinstance(cmv4fs, bool):
680        raise ConfigTypeError(desc='Invalid MP-BGP '
681                              'VPNv4 Flow Specification capability '
682                              'settings: %s. Boolean value expected' % cmv4fs)
683    return cmv4fs
684
685
686@validate(name=CAP_MBGP_VPNV6FS)
687def validate_cap_mbgp_vpnv66fs(cmv6fs):
688    if not isinstance(cmv6fs, bool):
689        raise ConfigTypeError(desc='Invalid MP-BGP '
690                              'VPNv6 Flow Specification capability '
691                              'settings: %s. Boolean value expected' % cmv6fs)
692    return cmv6fs
693
694
695@validate(name=CAP_MBGP_L2VPNFS)
696def validate_cap_mbgp_l2vpnfs(cml2fs):
697    if not isinstance(cml2fs, bool):
698        raise ConfigTypeError(desc='Invalid MP-BGP '
699                              'L2VPN Flow Specification capability '
700                              'settings: %s. Boolean value expected' % cml2fs)
701    return cml2fs
702
703
704@validate(name=CAP_RTC)
705def validate_cap_rtc(cap_rtc):
706    if not isinstance(cap_rtc, bool):
707        raise ConfigTypeError(desc='Invalid type for specifying RTC '
708                              'capability. Expected boolean got: %s' %
709                              type(cap_rtc))
710    return cap_rtc
711
712
713@validate(name=RTC_AS)
714def validate_cap_rtc_as(rtc_as):
715    if not is_valid_asn(rtc_as):
716        raise ConfigValueError(desc='Invalid RTC AS configuration value: %s'
717                               % rtc_as)
718    return rtc_as
719
720
721@validate(name=HOLD_TIME)
722def validate_hold_time(hold_time):
723    if ((hold_time is None) or (not isinstance(hold_time, int)) or
724            hold_time < 10):
725        raise ConfigValueError(desc='Invalid hold_time configuration value %s'
726                               % hold_time)
727
728    return hold_time
729
730
731@validate(name=MULTI_EXIT_DISC)
732def validate_med(med):
733    if med is not None and not validation.is_valid_med(med):
734        raise ConfigValueError(desc='Invalid multi-exit-discriminatory (med)'
735                               ' value: %s.' % med)
736    return med
737
738
739@validate(name=SITE_OF_ORIGINS)
740def validate_soo_list(soo_list):
741    if not isinstance(soo_list, list):
742        raise ConfigTypeError(conf_name=SITE_OF_ORIGINS, conf_value=soo_list)
743    if len(soo_list) > MAX_NUM_SOO:
744        raise ConfigValueError(desc='Max. SOO is limited to %s' %
745                               MAX_NUM_SOO)
746    if not all(validation.is_valid_ext_comm_attr(attr) for attr in soo_list):
747        raise ConfigValueError(conf_name=SITE_OF_ORIGINS,
748                               conf_value=soo_list)
749    # Check if we have duplicates
750    unique_rts = set(soo_list)
751    if len(unique_rts) != len(soo_list):
752        raise ConfigValueError(desc='Duplicate value provided in %s' %
753                               soo_list)
754    return soo_list
755
756
757@validate(name=MAX_PREFIXES)
758def validate_max_prefixes(max_prefixes):
759    if not isinstance(max_prefixes, six.integer_types):
760        raise ConfigTypeError(desc='Max. prefixes value should be of type '
761                              'int or long but found %s' % type(max_prefixes))
762    if max_prefixes < 0:
763        raise ConfigValueError(desc='Invalid max. prefixes value: %s' %
764                               max_prefixes)
765    return max_prefixes
766
767
768@validate(name=ADVERTISE_PEER_AS)
769def validate_advertise_peer_as(advertise_peer_as):
770    if not isinstance(advertise_peer_as, bool):
771        raise ConfigTypeError(desc='Invalid type for advertise-peer-as, '
772                              'expected bool got %s' %
773                              type(advertise_peer_as))
774    return advertise_peer_as
775
776
777# =============================================================================
778# Other utils.
779# =============================================================================
780
781def compute_optional_conf(conf_name, default_value, **all_config):
782    """Returns *conf_name* settings if provided in *all_config*, else returns
783     *default_value*.
784
785    Validates *conf_name* value if provided.
786    """
787    conf_value = all_config.get(conf_name)
788    if conf_value is not None:
789        # Validate configuration value.
790        conf_value = get_validator(conf_name)(conf_value)
791    else:
792        conf_value = default_value
793    return conf_value
794