def test_8_route_entry_set_rts(self): atts = exa.Attributes() ecoms = exa.ExtendedCommunities() ecoms.communities.append(exa.RouteTarget(64512, 1)) ecoms.communities.append(exa.RouteTarget(64512, 2)) ecoms.communities.append( exa.Encapsulation(exa.Encapsulation.Type.VXLAN)) atts.add(exa.LocalPreference(20)) atts.add(ecoms) entry = engine.RouteEntry(base.NLRI1, None, atts) # check that the route_entry object has the RTs we wanted self.assertIn(exa.RouteTarget(64512, 1), entry.route_targets) self.assertIn(exa.RouteTarget(64512, 2), entry.route_targets) # modify the route targets entry.set_route_targets( [exa.RouteTarget(64512, 3), exa.RouteTarget(64512, 1)]) # check that the new RTs have replaced the old ones self.assertIn(exa.RouteTarget(64512, 1), entry.route_targets) self.assertIn(exa.RouteTarget(64512, 3), entry.route_targets) self.assertNotIn(exa.RouteTarget(64512, 2), entry.route_targets) # also need to check the RTs in the attributes ecoms = entry.attributes[ exa.Attribute.CODE.EXTENDED_COMMUNITY].communities self.assertIn(exa.RouteTarget(64512, 1), ecoms) self.assertIn(exa.RouteTarget(64512, 3), ecoms) self.assertNotIn(exa.RouteTarget(64512, 2), ecoms) # check that other communities were preserved self.assertIn(exa.Encapsulation(exa.Encapsulation.Type.VXLAN), ecoms)
def test_6_same_nlri_same_attributes_order_multivalued(self): # Two routes with same NLRI but and same attributes should # hash to the same values and be equal, *even if* for a said # multivalued attributes, like extended community, the values # appear in a distinct order atts1 = exa.Attributes() ecoms1 = exa.ExtendedCommunities() ecoms1.communities.append(exa.RouteTarget(64512, 1)) ecoms1.communities.append( exa.Encapsulation(exa.Encapsulation.Type.VXLAN)) ecoms1.communities.append(exa.RouteTarget(64512, 2)) atts1.add(ecoms1) atts2 = exa.Attributes() ecoms2 = exa.ExtendedCommunities() ecoms2.communities.append(exa.RouteTarget(64512, 2)) ecoms2.communities.append(exa.RouteTarget(64512, 1)) ecoms2.communities.append( exa.Encapsulation(exa.Encapsulation.Type.VXLAN)) atts2.add(ecoms2) entry1 = engine.RouteEntry(base.NLRI1, None, atts1) entry2 = engine.RouteEntry(base.NLRI1, None, atts2) self.assertEqual(hash(entry1), hash(entry2)) self.assertEqual(entry1, entry2)
def test_10_ecoms(self): ecoms1 = exa.ExtendedCommunities() ecoms1.communities.append( exa.Encapsulation(exa.Encapsulation.Type.VXLAN)) atts1 = exa.Attributes() atts1.add(ecoms1) ecoms2 = exa.ExtendedCommunities() ecoms2.communities.append( exa.Encapsulation(exa.Encapsulation.Type.VXLAN)) ecoms2.communities.append(exa.RouteTarget(64512, 1)) atts2 = exa.Attributes() atts2.add(ecoms2) self.assertFalse(atts1.sameValuesAs(atts2)) self.assertFalse(atts2.sameValuesAs(atts1))
def _check_encaps(self, route): '''check that encaps of a route returns a list of encaps supported by both the dataplane driver and the advertized route (based on BGP Encapsulation community) logs a warning if there is no common encap ''' adv_encaps = None try: adv_encaps = route.ecoms(exa.Encapsulation) self.log.debug("Advertized Encaps: %s", adv_encaps) except KeyError: self.log.debug("no encap advertized, let's use default") if not adv_encaps: adv_encaps = [exa.Encapsulation(exa.Encapsulation.Type.DEFAULT)] good_encaps = set(adv_encaps) & set(self.dp_driver.supported_encaps()) if not good_encaps: self.log.warning( "No encap supported by dataplane driver for route" " %s, advertized: %s, dataplane supports: {%s}", route, adv_encaps, ", ".join([ repr(encap) for encap in self.dp_driver.supported_encaps() ])) return good_encaps
class DummyDataplaneDriver(dp_drivers.DummyDataplaneDriver): type = constants.EVPN dataplane_instance_class = DummyVPNInstanceDataplane encaps = [exa.Encapsulation(exa.Encapsulation.Type.VXLAN)] def __init__(self, *args, **kwargs): dp_drivers.DummyDataplaneDriver.__init__(self, *args, **kwargs)
def test_9_route_entry_rts_as_init_param(self): atts = exa.Attributes() ecoms = exa.ExtendedCommunities() ecoms.communities.append( exa.Encapsulation(exa.Encapsulation.Type.VXLAN)) atts.add(exa.LocalPreference(20)) atts.add(ecoms) rts = [exa.RouteTarget(64512, 1), exa.RouteTarget(64512, 2)] entry = engine.RouteEntry(base.NLRI1, rts, atts) self.assertIn(exa.RouteTarget(64512, 1), entry.route_targets) self.assertIn(exa.RouteTarget(64512, 2), entry.route_targets) ecoms = entry.attributes[ exa.Attribute.CODE.EXTENDED_COMMUNITY].communities self.assertIn(exa.RouteTarget(64512, 1), ecoms) self.assertIn(exa.RouteTarget(64512, 2), ecoms) self.assertIn(exa.Encapsulation(exa.Encapsulation.Type.VXLAN), ecoms)
def _gen_encap_extended_communities(self): ecommunities = exa.extcoms.ExtendedCommunities() for encap in self.dp_driver.supported_encaps(): if not isinstance(encap, exa.Encapsulation): raise Exception( "dp_driver.supported_encaps() should " "return a list of Encapsulation objects (%s)", type(encap)) if encap != exa.Encapsulation(exa.Encapsulation.Type.DEFAULT): ecommunities.communities.append(encap) # FIXME: si DEFAULT + xxx => adv MPLS return ecommunities
def __init__(self, *args, **kwargs): vpn_instance.VPNInstance.__init__(self, *args, **kwargs) self.gw_port = None encaps = self.dp_driver.supported_encaps() if (exa.Encapsulation(exa.Encapsulation.Type.VXLAN) in encaps and any([ exa.Encapsulation(exa.Encapsulation.Type.MPLS) in encaps, exa.Encapsulation(exa.Encapsulation.Type.GRE) in encaps, exa.Encapsulation(exa.Encapsulation.Type.MPLS_UDP) in encaps ])): raise Exception("The dataplane can't support both an MPLS encap " "and a VXLAN encapsulation") # Advertise route to receive multi-destination traffic self.log.info("Generating BGP route for broadcast/multicast traffic") nlri = exa.EVPNMulticast( self.instance_rd, exa.EthernetTag(), exa.IP.create(self.bgp_manager.get_local_address()), None, exa.IP.create(self.bgp_manager.get_local_address())) attributes = exa.Attributes() attributes.add(self._gen_encap_extended_communities()) # add PMSI Tunnel Attribute route attributes.add( exa.PMSIIngressReplication(self.dp_driver.get_local_address(), raw_label=self.instance_label)) self.multicast_route_entry = engine.RouteEntry(nlri, self.export_rts, attributes) self._advertise_route(self.multicast_route_entry)
class LinuxVXLANDataplaneDriver(dp_drivers.DataplaneDriver): """E-VPN Dataplane driver relying on Linux kernel linuxbridge VXLAN""" dataplane_instance_class = LinuxVXLANEVIDataplane type = consts.EVPN required_kernel = "3.11.0" encaps = [exa.Encapsulation(exa.Encapsulation.Type.VXLAN)] driver_opts = [ cfg.IntOpt("vxlan_dst_port", default="0", help=("UDP port toward which send VXLAN traffic (defaults " "to standard IANA-allocated port)")), ] def __init__(self): lg.LookingGlassLocalLogger.__init__(self, __name__) self.log.info("Initializing %s", self.__class__.__name__) dp_drivers.DataplaneDriver.__init__(self) def initialize(self): self.log.info("Really initializing %s", self.__class__.__name__) self._run_command("modprobe vxlan", run_as_root=True) def reset_state(self): self.log.debug("Resetting %s dataplane", self.__class__.__name__) # delete all EVPN bridges cmd = "brctl show | tail -n +2 | awk '{print $1}'| grep '%s'" for bridge in self._run_command(cmd % BRIDGE_NAME_PREFIX, run_as_root=True, raise_on_error=False, acceptable_return_codes=[0, 1], shell=True)[0]: self._run_command("ip link set %s down" % bridge, run_as_root=True) self._run_command("brctl delbr %s" % bridge, run_as_root=True) # delete all VXLAN interfaces cmd = "ip link show | awk '{print $2}' | tr -d ':' | grep '%s'" for interface in self._run_command(cmd % VXLAN_INTERFACE_PREFIX, run_as_root=True, raise_on_error=False, acceptable_return_codes=[0, 1], shell=True)[0]: self._run_command("ip link set %s down" % interface, run_as_root=True) self._run_command("ip link delete %s" % interface, run_as_root=True)
class OVSDataplaneDriver(dp_drivers.DataplaneDriver): dataplane_instance_class = OVSEVIDataplane type = consts.EVPN ecmp_support = False encaps = [exa.Encapsulation(exa.Encapsulation.Type.VXLAN)] driver_opts = [ cfg.StrOpt("ovs_bridge", default="br-tun", help=("Name of the OVS bridge to use, this has to be the " "same as the tunneling bridge of the Neutron OVS " "agent, usually br-tun")), ] def __init__(self, *args, **kwargs): super(OVSDataplaneDriver, self).__init__(*args, **kwargs) config.set_default_root_helper() self.bridge = dataplane_utils.OVSBridgeWithGroups( br_tun.OVSTunnelBridge(self.config.ovs_bridge) ) self.tunnel_mgr = TunnelManager(self.bridge, self.get_local_address()) def needs_cleanup_assist(self): return True def reset_state(self): # cleanup is taken care of by OVS Neutron Agent pass # Looking glass #### def get_lg_local_info(self, path_prefix): return { "tunnels": self.tunnel_mgr.infos(), }
class DataplaneDriver(lg.LookingGlassLocalLogger, utils.ClassReprMixin, metaclass=abc.ABCMeta): '''Dataplane driver The initialisation workflow is the following: - on startup, the selected dataplane driver is loaded (its __init__ is called) - on the first attachment of an interface that matches the driver's type the following happens: * the driver reset_state() method is called * the driver initialize() method is called The cleanup workflow (what happens when bagpipe-bgp-cleanup is called) is the following: - the dataplane driver is loaded (its __init__ is called) - reset_state() method is called As a consequence, whether some init action should go in __init__ or initialize depends on what reset_state does: - __init__ should not do things that reset_state will revert - initialize can do things that reset_state will revert ''' type = None dataplane_instance_class = object # has to be overridden by subclasses encaps = [ exa.Encapsulation(exa.Encapsulation.Type.DEFAULT), exa.Encapsulation(exa.Encapsulation.Type.MPLS) ] ecmp_support = False required_kernel = None driver_opts = [] @log_decorator.log def __init__(self): lg.LookingGlassLocalLogger.__init__(self) cfg.CONF.register_opts(self.driver_opts, constants.config_group(self.type)) self.config = cfg.CONF.get(constants.config_group(self.type)) assert issubclass(self.dataplane_instance_class, VPNInstanceDataplane) self.local_address = self.config.get("dataplane_local_address") if self.local_address is None: self.local_address = cfg.CONF.BGP.local_address self.log.info("Will use %s as local_address", self.local_address) # Linux kernel version check o = self._run_command("uname -r") self.kernel_release = o[0][0].split("-")[0] if self.required_kernel: if (version.StrictVersion(self.kernel_release) < version.StrictVersion(self.required_kernel)): self.log.warning( "%s requires at least Linux kernel %s" " (you are running %s)", self.__class__.__name__, self.required_kernel, self.kernel_release) # Flag to trigger cleanup all dataplane states on first call to # vif_plugged self.first_init = True @abc.abstractmethod def reset_state(self): '''dataplane cleanup hook (abstract) This method is called: - right before the work on the first attachment begins (initialize() is called afterwards) - by bagpipe-bgp-cleanup ''' pass def initialize(self): '''dataplane initialization hook This is called after reset_state (which, e.g. cleans up the stuff possibly left-out by a previous run). Things that are reverted by reset_state() should go here. ''' pass @log_decorator.log_info def initialize_dataplane_instance(self, instance_id, external_instance_id, gateway_ip, mask, instance_label, **kwargs): '''per-VPN dataplane instanciation returns a VPNInstanceDataplane subclass after calling reset_state on the dataplane driver, if this is the first call to initialize_dataplane_instance ''' if self.first_init: self.log.info("First VPN instance init, reinitializing dataplane" " state") try: self.reset_state() except Exception as e: self.log.error("Exception while resetting state: %s", e) try: self.initialize() except Exception as e: self.log.error( "Exception while initializing dataplane" " state: %s", e) raise self.first_init = False else: self.log.debug("(not reinitializing dataplane state)") return self.dataplane_instance_class(self, instance_id, external_instance_id, gateway_ip, mask, instance_label, **kwargs) def get_local_address(self): return self.local_address def supported_encaps(self): return self.__class__.encaps def needs_cleanup_assist(self): '''Control per-route cleanup events A dataplane driver not able to cleanup all the states for a given VPN instance can return True here to receive artifical dataplane removal calls, such as remove_dataplane_for_remote_endpoint, for each state previously setup ''' return False def validate_directions(self, direction): # by default, assume a driver only supports plugging in both directions # if a driver does not support forwarding only the traffic to the port, # (and hence omit forwarding traffic from the port), it should raise an # exception if directions is TO_PORT # if a driver does not support forwarding only the traffic from the # port (and hence omit forwarding traffic to the port), it should raise # an exception if directions is FROM_PORT if (direction is not None) and (direction != constants.BOTH): self.log.warning("Unsupported direction: %s", direction) raise exc.APIException("Unsupported direction: %s" % direction) def _run_command(self, command, run_as_root=False, *args, **kwargs): return run_command.run_command(self.log, command, run_as_root, *args, **kwargs) def get_lg_map(self): encaps = [] for encap in self.supported_encaps(): encaps.append(repr(encap)) return { "name": (lg.VALUE, self.__class__.__name__), "local_address": (lg.VALUE, self.local_address), "supported_encaps": (lg.VALUE, encaps), "config": (lg.VALUE, utils.osloconfig_json_serialize(self.config)), "kernel_release": (lg.VALUE, self.kernel_release) }
def _vxlan_dp_driver(self): return (exa.Encapsulation(exa.Encapsulation.Type.VXLAN) in self.dp_driver.supported_encaps())
class DataplaneDriver(lg.LookingGlassLocalLogger): type = None dataplane_instance_class = object # has to be overridden by subclasses encaps = [ exa.Encapsulation(exa.Encapsulation.Type.DEFAULT), exa.Encapsulation(exa.Encapsulation.Type.MPLS) ] makebefore4break_support = False ecmp_support = False required_kernel = None driver_opts = [] @log_decorator.log def __init__(self): lg.LookingGlassLocalLogger.__init__(self) cfg.CONF.register_opts(self.driver_opts, constants.config_group(self.type)) self.config = cfg.CONF.get(constants.config_group(self.type)) assert issubclass(self.dataplane_instance_class, VPNInstanceDataplane) self.local_address = self.config.get("dataplane_local_address") if self.local_address is None: self.local_address = cfg.CONF.BGP.local_address self.log.info("Will use %s as local_address", self.local_address) # Linux kernel version check o = self._run_command("uname -r") self.kernel_release = o[0][0].split("-")[0] if self.required_kernel: if (version.StrictVersion(self.kernel_release) < version.StrictVersion(self.required_kernel)): self.log.warning( "%s requires at least Linux kernel %s" " (you are running %s)", self.__class__.__name__, self.required_kernel, self.kernel_release) # Flag to trigger cleanup all dataplane states on first call to # vif_plugged self.first_init = True @abc.abstractmethod def reset_state(self): pass @abc.abstractmethod def initialize(self): '''dataplane initialization hook (abstract) This is called after reset_state (which, e.g. cleans up the stuff possibly left-out by a previous failed run). All init things that should not be cleaned up go here. ''' pass @log_decorator.log_info def initialize_dataplane_instance(self, instance_id, external_instance_id, gateway_ip, mask, instance_label, **kwargs): '''per-VPN dataplane instanciation returns a VPNInstanceDataplane subclass after calling reset_state on the dataplane driver, if this is the first call to initialize_dataplane_instance ''' if self.first_init: self.log.info("First VPN instance init, reinitializing dataplane" " state") try: self.reset_state() except Exception as e: self.log.error("Exception while resetting state: %s", e) try: self.initialize() except Exception as e: self.log.error( "Exception while initializing dataplane" " state: %s", e) raise self.first_init = False else: self.log.debug("(not reinitializing dataplane state)") return self.dataplane_instance_class(self, instance_id, external_instance_id, gateway_ip, mask, instance_label, **kwargs) def get_local_address(self): return self.local_address def supported_encaps(self): return self.__class__.encaps def _run_command(self, command, run_as_root=False, *args, **kwargs): return run_command.run_command(self.log, command, run_as_root, *args, **kwargs) def get_lg_map(self): encaps = [] for encap in self.supported_encaps(): encaps.append(repr(encap)) return { "name": (lg.VALUE, self.__class__.__name__), "local_address": (lg.VALUE, self.local_address), "supported_encaps": (lg.VALUE, encaps), "config": (lg.VALUE, utils.osloconfig_json_serialize(self.config)), "kernel_release": (lg.VALUE, self.kernel_release) }
def supported_encaps(self): yield exa.Encapsulation(exa.Encapsulation.Type.MPLS) # we also accept route with no encap specified yield exa.Encapsulation(exa.Encapsulation.Type.DEFAULT)