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 Running or runtime configuration related to Virtual Routing and Forwarding
23 from ryu.lib.packet.bgp import RF_IPv4_UC
24 from ryu.lib.packet.bgp import RF_IPv6_UC
25 from ryu.lib.packet.bgp import RF_L2_EVPN
26 from ryu.lib.packet.bgp import RF_IPv4_FLOWSPEC
27 from ryu.lib.packet.bgp import RF_IPv6_FLOWSPEC
28 from ryu.lib.packet.bgp import RF_L2VPN_FLOWSPEC
30 from ryu.services.protocols.bgp.utils import validation
31 from ryu.services.protocols.bgp.base import get_validator
32 from ryu.services.protocols.bgp.rtconf.base import BaseConf
33 from ryu.services.protocols.bgp.rtconf.base import BaseConfListener
34 from ryu.services.protocols.bgp.rtconf.base import ConfigTypeError
35 from ryu.services.protocols.bgp.rtconf.base import ConfigValueError
36 from ryu.services.protocols.bgp.rtconf.base import ConfWithId
37 from ryu.services.protocols.bgp.rtconf.base import ConfWithIdListener
38 from ryu.services.protocols.bgp.rtconf.base import ConfWithStats
39 from ryu.services.protocols.bgp.rtconf.base import ConfWithStatsListener
40 from ryu.services.protocols.bgp.rtconf.base import MAX_NUM_EXPORT_RT
41 from ryu.services.protocols.bgp.rtconf.base import MAX_NUM_IMPORT_RT
42 from ryu.services.protocols.bgp.rtconf.base import MULTI_EXIT_DISC
43 from ryu.services.protocols.bgp.rtconf.base import RuntimeConfigError
44 from ryu.services.protocols.bgp.rtconf.base import SITE_OF_ORIGINS
45 from ryu.services.protocols.bgp.rtconf.base import validate
46 from ryu.services.protocols.bgp.rtconf.base import validate_med
47 from ryu.services.protocols.bgp.rtconf.base import validate_soo_list
50 LOG = logging.getLogger('bgpspeaker.rtconf.vrfs')
52 # Configuration setting names.
53 ROUTE_DISTINGUISHER = 'route_dist'
54 IMPORT_RTS = 'import_rts'
55 EXPORT_RTS = 'export_rts'
58 VRF_RF = 'route_family'
59 IMPORT_MAPS = 'import_maps'
61 # Supported VRF route-families
64 VRF_RF_L2_EVPN = 'evpn'
65 VRF_RF_IPV4_FLOWSPEC = 'ipv4fs'
66 VRF_RF_IPV6_FLOWSPEC = 'ipv6fs'
67 VRF_RF_L2VPN_FLOWSPEC = 'l2vpnfs'
74 VRF_RF_L2VPN_FLOWSPEC,
78 # Default configuration values.
79 DEFAULT_VRF_NAME = 'no-vrf-name'
80 DEFAULT_VRF_DESC = 'no-vrf-desc'
83 @validate(name=IMPORT_RTS)
84 def validate_import_rts(import_rts):
85 if not isinstance(import_rts, list):
86 raise ConfigTypeError(conf_name=IMPORT_RTS, conf_value=import_rts)
87 if not (len(import_rts) <= MAX_NUM_IMPORT_RT):
88 raise ConfigValueError(desc='Max. import RT is limited to %s' %
90 if not all(validation.is_valid_ext_comm_attr(rt) for rt in import_rts):
91 raise ConfigValueError(conf_name=IMPORT_RTS, conf_value=import_rts)
92 # Check if we have duplicates
93 unique_rts = set(import_rts)
94 if len(unique_rts) != len(import_rts):
95 raise ConfigValueError(desc='Duplicate value provided %s' % import_rts)
100 @validate(name=EXPORT_RTS)
101 def validate_export_rts(export_rts):
102 if not isinstance(export_rts, list):
103 raise ConfigTypeError(conf_name=EXPORT_RTS, conf_value=export_rts)
104 if not (len(export_rts) <= MAX_NUM_EXPORT_RT):
105 raise ConfigValueError(desc='Max. import RT is limited to %s' %
108 if not all(validation.is_valid_ext_comm_attr(rt) for rt in export_rts):
109 raise ConfigValueError(conf_name=EXPORT_RTS, conf_value=export_rts)
110 # Check if we have duplicates
111 unique_rts = set(export_rts)
112 if len(unique_rts) != len(export_rts):
113 raise ConfigValueError(desc='Duplicate value provided in %s' %
118 @validate(name=ROUTE_DISTINGUISHER)
119 def validate_rd(route_dist):
120 if not validation.is_valid_route_dist(route_dist):
121 raise ConfigValueError(conf_name=ROUTE_DISTINGUISHER,
122 conf_value=route_dist)
126 @validate(name=VRF_RF)
127 def validate_vrf_rf(vrf_rf):
128 if vrf_rf not in SUPPORTED_VRF_RF:
129 raise ConfigValueError(desc='Give VRF route family %s is not '
130 'supported.' % vrf_rf)
134 class VrfConf(ConfWithId, ConfWithStats):
135 """Class that encapsulates configurations for one VRF."""
137 VRF_CHG_EVT = 'vrf_chg_evt'
139 VALID_EVT = frozenset([VRF_CHG_EVT])
141 REQUIRED_SETTINGS = frozenset([ROUTE_DISTINGUISHER,
145 OPTIONAL_SETTINGS = frozenset(
146 [VRF_NAME, MULTI_EXIT_DISC, SITE_OF_ORIGINS, VRF_RF, IMPORT_MAPS]
149 def __init__(self, **kwargs):
150 """Create an instance of VRF runtime configuration."""
151 super(VrfConf, self).__init__(**kwargs)
153 def _init_opt_settings(self, **kwargs):
154 super(VrfConf, self)._init_opt_settings(**kwargs)
155 # We do not have valid default MED value.
156 # If no MED attribute is provided then we do not have to use MED.
157 # If MED attribute is provided we have to validate it and use it.
158 med = kwargs.pop(MULTI_EXIT_DISC, None)
159 if med and validate_med(med):
160 self._settings[MULTI_EXIT_DISC] = med
162 # We do not have valid default SOO value.
163 # If no SOO attribute is provided then we do not have to use SOO.
164 # If SOO attribute is provided we have to validate it and use it.
165 soos = kwargs.pop(SITE_OF_ORIGINS, None)
166 if soos and validate_soo_list(soos):
167 self._settings[SITE_OF_ORIGINS] = soos
169 # Current we we only support VRF for IPv4 and IPv6 with default IPv4
170 vrf_rf = kwargs.pop(VRF_RF, VRF_RF_IPV4)
171 if vrf_rf and validate_vrf_rf(vrf_rf):
172 self._settings[VRF_RF] = vrf_rf
174 import_maps = kwargs.pop(IMPORT_MAPS, [])
175 self._settings[IMPORT_MAPS] = import_maps
177 # =========================================================================
178 # Required attributes
179 # =========================================================================
182 def route_dist(self):
183 return self._settings[ROUTE_DISTINGUISHER]
185 # =========================================================================
186 # Optional attributes with valid defaults.
187 # =========================================================================
190 def import_rts(self):
191 return list(self._settings[IMPORT_RTS])
194 def export_rts(self):
195 return list(self._settings[EXPORT_RTS])
199 soos = self._settings.get(SITE_OF_ORIGINS)
207 def multi_exit_disc(self):
208 """Returns configured value of MED, else None.
210 This configuration does not have default value.
212 return self._settings.get(MULTI_EXIT_DISC)
215 def route_family(self):
216 """Returns configured route family for this VRF
218 This configuration does not change.
220 return self._settings.get(VRF_RF)
224 return VrfConf.create_rd_rf_id(self.route_dist, self.route_family)
227 def import_maps(self):
228 return self._settings.get(IMPORT_MAPS)
231 def create_rd_rf_id(route_dist, route_family):
232 return route_dist, route_family
235 def vrf_rf_2_rf(vrf_rf):
236 if vrf_rf == VRF_RF_IPV4:
238 elif vrf_rf == VRF_RF_IPV6:
240 elif vrf_rf == VRF_RF_L2_EVPN:
242 elif vrf_rf == VRF_RF_IPV4_FLOWSPEC:
243 return RF_IPv4_FLOWSPEC
244 elif vrf_rf == VRF_RF_IPV6_FLOWSPEC:
245 return RF_IPv6_FLOWSPEC
246 elif vrf_rf == VRF_RF_L2VPN_FLOWSPEC:
247 return RF_L2VPN_FLOWSPEC
249 raise ValueError('Unsupported VRF route family given %s' % vrf_rf)
252 def rf_2_vrf_rf(route_family):
253 if route_family == RF_IPv4_UC:
255 elif route_family == RF_IPv6_UC:
257 elif route_family == RF_L2_EVPN:
258 return VRF_RF_L2_EVPN
259 elif route_family == RF_IPv4_FLOWSPEC:
260 return VRF_RF_IPV4_FLOWSPEC
261 elif route_family == RF_IPv6_FLOWSPEC:
262 return VRF_RF_IPV6_FLOWSPEC
263 elif route_family == RF_L2VPN_FLOWSPEC:
264 return VRF_RF_L2VPN_FLOWSPEC
266 raise ValueError('No supported mapping for route family '
267 'to vrf_route_family exists for %s' %
272 """Returns a copy of current settings.
274 As some of the attributes are themselves containers, we clone the
275 settings to provide clones for those containers as well.
278 cloned_setting = self._settings.copy()
279 # Don't want clone to link to same RT containers
280 cloned_setting[IMPORT_RTS] = self.import_rts
281 cloned_setting[EXPORT_RTS] = self.export_rts
282 cloned_setting[SITE_OF_ORIGINS] = self.soo_list
283 return cloned_setting
286 def get_opt_settings(cls):
287 self_confs = super(VrfConf, cls).get_opt_settings()
288 self_confs.update(VrfConf.OPTIONAL_SETTINGS)
292 def get_req_settings(cls):
293 self_confs = super(VrfConf, cls).get_req_settings()
294 self_confs.update(VrfConf.REQUIRED_SETTINGS)
298 def get_valid_evts(cls):
299 self_valid_evts = super(VrfConf, cls).get_valid_evts()
300 self_valid_evts.update(VrfConf.VALID_EVT)
301 return self_valid_evts
303 def update(self, **kwargs):
304 """Updates this `VrfConf` settings.
306 Notifies listeners if any settings changed. Returns `True` if update
307 was successful. This vrfs' route family, id and route dist settings
308 cannot be updated/changed.
310 # Update inherited configurations
311 super(VrfConf, self).update(**kwargs)
312 vrf_id = kwargs.get(ConfWithId.ID)
313 vrf_rd = kwargs.get(ROUTE_DISTINGUISHER)
314 vrf_rf = kwargs.get(VRF_RF)
315 if (vrf_id != self.id or
316 vrf_rd != self.route_dist or
317 vrf_rf != self.route_family):
318 raise ConfigValueError(desc='id/route-distinguisher/route-family'
319 ' do not match configured value.')
321 # Validate and update individual settings
322 new_imp_rts, old_imp_rts = \
323 self._update_import_rts(**kwargs)
324 export_rts_changed = self._update_export_rts(**kwargs)
325 soos_list_changed = self._update_soo_list(**kwargs)
326 med_changed = self._update_med(**kwargs)
327 re_export_needed = (export_rts_changed or
330 import_maps = kwargs.get(IMPORT_MAPS, [])
331 re_import_needed = self._update_importmaps(import_maps)
333 # If we did have any change in value of any settings, we notify
335 if (new_imp_rts is not None or
336 old_imp_rts is not None or
337 re_export_needed or re_import_needed):
345 self._notify_listeners(VrfConf.VRF_CHG_EVT, evt_value)
348 def _update_import_rts(self, **kwargs):
349 import_rts = kwargs.get(IMPORT_RTS)
350 get_validator(IMPORT_RTS)(import_rts)
351 curr_import_rts = set(self._settings[IMPORT_RTS])
353 import_rts = set(import_rts)
354 if not import_rts.symmetric_difference(curr_import_rts):
357 # Get the difference between current and new RTs
358 new_import_rts = import_rts - curr_import_rts
359 old_import_rts = curr_import_rts - import_rts
361 # Update current RTs and notify listeners.
362 self._settings[IMPORT_RTS] = import_rts
363 return new_import_rts, old_import_rts
365 def _update_export_rts(self, **kwargs):
366 export_rts = kwargs.get(EXPORT_RTS)
367 get_validator(EXPORT_RTS)(export_rts)
368 curr_export_rts = set(self._settings[EXPORT_RTS])
370 if curr_export_rts.symmetric_difference(export_rts):
371 # Update current RTs and notify listeners.
372 self._settings[EXPORT_RTS] = list(export_rts)
377 def _update_soo_list(self, **kwargs):
378 soo_list = kwargs.get(SITE_OF_ORIGINS, [])
379 get_validator(SITE_OF_ORIGINS)(soo_list)
380 curr_soos = set(self.soo_list)
382 # If given list is different from existing settings, we update it
383 if curr_soos.symmetric_difference(soo_list):
384 self._settings[SITE_OF_ORIGINS] = soo_list[:]
389 def _update_med(self, **kwargs):
390 multi_exit_disc = kwargs.get(MULTI_EXIT_DISC, None)
392 get_validator(MULTI_EXIT_DISC)(multi_exit_disc)
394 if multi_exit_disc != self.multi_exit_disc:
395 self._settings[MULTI_EXIT_DISC] = multi_exit_disc
400 def _update_importmaps(self, import_maps):
401 if set(self._settings[IMPORT_MAPS]).symmetric_difference(import_maps):
402 self._settings[IMPORT_MAPS] = import_maps
408 return ('<%s(route_dist: %r, import_rts: %r, export_rts: %r, '
409 'soo_list: %r)>' % (self.__class__.__name__,
410 self.route_dist, self.import_rts,
411 self.export_rts, self.soo_list))
414 return 'VrfConf-%s' % self.route_dist
417 class VrfsConf(BaseConf):
418 """Container for all VRF configurations."""
420 ADD_VRF_CONF_EVT, REMOVE_VRF_CONF_EVT = range(2)
422 VALID_EVT = frozenset([ADD_VRF_CONF_EVT, REMOVE_VRF_CONF_EVT])
425 super(VrfsConf, self).__init__()
426 self._vrfs_by_rd_rf = {}
427 self._vrfs_by_id = {}
429 def _init_opt_settings(self, **kwargs):
434 """Returns a list of configured `VrfConf`s
436 return list(self._vrfs_by_rd_rf.values())
439 def vrf_interested_rts(self):
440 interested_rts = set()
441 for vrf_conf in self._vrfs_by_id.values():
442 interested_rts.update(vrf_conf.import_rts)
443 return interested_rts
445 def update(self, **kwargs):
446 raise NotImplementedError('Use either add/remove_vrf_conf'
449 def add_vrf_conf(self, vrf_conf):
450 if vrf_conf.rd_rf_id in self._vrfs_by_rd_rf.keys():
451 raise RuntimeConfigError(
452 desc='VrfConf with rd_rf %s already exists'
453 % str(vrf_conf.rd_rf_id)
455 if vrf_conf.id in self._vrfs_by_id:
456 raise RuntimeConfigError(
457 desc='VrfConf with id %s already exists' % str(vrf_conf.id)
460 self._vrfs_by_rd_rf[vrf_conf.rd_rf_id] = vrf_conf
461 self._vrfs_by_id[vrf_conf.id] = vrf_conf
462 self._notify_listeners(VrfsConf.ADD_VRF_CONF_EVT, vrf_conf)
464 def remove_vrf_conf(self, route_dist=None, vrf_id=None,
466 """Removes any matching `VrfConf` for given `route_dist` or `vrf_id`
469 - `route_dist`: (str) route distinguisher of a configured VRF
470 - `vrf_id`: (str) vrf ID
471 - `vrf_rf`: (str) route family of the VRF configuration
472 If only `route_dist` is given, removes `VrfConf`s for all supported
473 address families for this `route_dist`. If `vrf_rf` is given, than only
474 removes `VrfConf` for that specific route family. If only `vrf_id` is
475 given, matching `VrfConf` will be removed.
477 if route_dist is None and vrf_id is None:
478 raise RuntimeConfigError(desc='To delete supply route_dist or id.')
480 # By default we remove all VRFs for given Id or RD
481 vrf_rfs = SUPPORTED_VRF_RF
482 # If asked to delete specific route family vrf conf.
486 # For all vrf route family asked to be deleted, we collect all deleted
488 removed_vrf_confs = []
489 for route_family in vrf_rfs:
490 if route_dist is not None:
491 rd_rf_id = VrfConf.create_rd_rf_id(route_dist, route_family)
492 vrf_conf = self._vrfs_by_rd_rf.pop(rd_rf_id, None)
494 self._vrfs_by_id.pop(vrf_conf.id, None)
495 removed_vrf_confs.append(vrf_conf)
497 vrf_conf = self._vrfs_by_id.pop(vrf_id, None)
499 self._vrfs_by_rd_rf.pop(vrf_conf.rd_rd_id, None)
500 removed_vrf_confs.append(vrf_conf)
502 # We do not raise any exception if we cannot find asked VRF.
503 for vrf_conf in removed_vrf_confs:
504 self._notify_listeners(VrfsConf.REMOVE_VRF_CONF_EVT, vrf_conf)
505 return removed_vrf_confs
507 def get_vrf_conf(self, route_dist, vrf_rf, vrf_id=None):
508 if route_dist is None and vrf_id is None:
509 raise RuntimeConfigError(desc='To get VRF supply route_dist '
511 if route_dist is not None and vrf_id is not None:
512 vrf1 = self._vrfs_by_id.get(vrf_id)
513 rd_rf_id = VrfConf.create_rd_rf_id(route_dist, vrf_rf)
514 vrf2 = self._vrfs_by_rd_rf.get(rd_rf_id)
516 raise RuntimeConfigError(desc='Given VRF ID (%s) and RD (%s)'
517 ' are not of same VRF.' %
518 (vrf_id, route_dist))
520 elif route_dist is not None:
521 rd_rf_id = VrfConf.create_rd_rf_id(route_dist, vrf_rf)
522 vrf = self._vrfs_by_rd_rf.get(rd_rf_id)
524 vrf = self._vrfs_by_id.get(vrf_id)
528 def vrfs_by_rd_rf_id(self):
529 return dict(self._vrfs_by_rd_rf)
532 def get_valid_evts(cls):
533 self_valid_evts = super(VrfsConf, cls).get_valid_evts()
534 self_valid_evts.update(VrfsConf.VALID_EVT)
535 return self_valid_evts
538 return '<%s(%r)>' % (self.__class__.__name__, self._vrfs_by_id)
542 return [vrf.settings for vrf in self._vrfs_by_id.values()]
545 class VrfConfListener(ConfWithIdListener, ConfWithStatsListener):
546 """Base listener for various VRF configuration change event."""
548 def __init__(self, vrf_conf):
549 super(VrfConfListener, self).__init__(vrf_conf)
550 vrf_conf.add_listener(VrfConf.VRF_CHG_EVT, self.on_chg_vrf_conf)
552 def on_chg_vrf_conf(self, evt):
553 raise NotImplementedError('This method should be overridden')
556 class VrfsConfListener(BaseConfListener):
557 """Base listener for VRF container change events."""
559 def __init__(self, vrfs_conf):
560 super(VrfsConfListener, self).__init__(vrfs_conf)
561 vrfs_conf.add_listener(VrfsConf.ADD_VRF_CONF_EVT, self.on_add_vrf_conf)
562 vrfs_conf.add_listener(VrfsConf.REMOVE_VRF_CONF_EVT,
563 self.on_remove_vrf_conf)
566 def on_add_vrf_conf(self, evt):
567 raise NotImplementedError('This method should be overridden')
570 def on_remove_vrf_conf(self, evt):
571 raise NotImplementedError('This method should be overridden')