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))
Example #4
0
    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
Example #5
0
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)
Example #7
0
    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
Example #8
0
    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)
Example #9
0
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)
Example #10
0
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(),
        }
Example #11
0
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)
        }
Example #12
0
 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)
        }
Example #14
0
 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)