<commit_before># vim: tabstop=4 shiftwidth=4 softtabstop=4
# encoding: utf-8

# Copyright 2014 Orange
#
# 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.
import logging
import socket

import re

from abc import ABCMeta, abstractmethod

from collections import defaultdict

from copy import copy

from threading import Thread
from threading import Lock

from netaddr.ip import IPNetwork
import netaddr

from bagpipe.bgp.common import utils
from bagpipe.bgp.common import logDecorator
from bagpipe.bgp.common.looking_glass import LookingGlassLocalLogger, LGMap

from bagpipe.bgp.engine.tracker_worker import TrackerWorker, \
    compareECMP, compareNoECMP

from bagpipe.bgp.engine import RouteEntry

from exabgp.reactor.protocol import AFI, SAFI

from exabgp.bgp.message.update.attribute.community.extended.encapsulation \
    import Encapsulation
from exabgp.bgp.message.update.attribute.attribute import Attribute
from exabgp.bgp.message.update.nlri.qualifier.rd import RouteDistinguisher

from exabgp.bgp.message.open.asn import ASN

from exabgp.bgp.message.update.attribute.community.extended.communities \
    import ExtendedCommunities
from exabgp.bgp.message.update.attribute.community.extended \
    import TrafficRedirect

from bagpipe.bgp.engine.flowspec import FlowRouteFactory

from exabgp.bgp.message.update.nlri.flow import (
    FlowSourcePort, FlowDestinationPort, FlowIPProtocol, Flow4Source,
    Flow6Source, Flow4Destination, Flow6Destination, NumericOperator)

from exabgp.protocol import Protocol

from bagpipe.bgp.rest_api import APIException

log = logging.getLogger(__name__)


class TrafficClassifier(object):

    def __init__(self, sourcePrefix=None, destinationPrefix=None,
                 sourcePort=None, destinationPort=None, protocol='tcp'):
        self.sourcePrefix = sourcePrefix
        self.destinationPrefix = destinationPrefix

        if sourcePort:
            if ":" in sourcePort:
                self.sourcePort = tuple(
                    [int(p) for p in sourcePort.split(":")]
                )
            else:
                self.sourcePort = int(sourcePort)
        else:
            self.sourcePort = sourcePort

        if destinationPort:
            if ":" in destinationPort:
                self.destinationPort = tuple(
                    [int(p) for p in destinationPort.split(":")]
                )
            else:
                self.destinationPort = int(destinationPort)
        else:
            self.destinationPort = destinationPort

        self.protocol = protocol

    def __repr__(self):
        return "traffic-classifier:%s" % str(self)

    def __str__(self):
        return "%s-%s,%s-%s,%s" % (self.sourcePrefix or "*",
                                   self.sourcePort or "*",
                                   self.destinationPrefix or "*",
                                   self.destinationPort or "*",
                                   self.protocol)

    def __eq__(self, other):
        return (isinstance(other, self.__class__) and
                self.__dict__ == other.__dict__)

    def __hash__(self):
        return hash((self.sourcePrefix, self.sourcePort,
                     self.destinationPrefix, self.destinationPort,
                     self.protocol))

    def _interpretPortRule(self, rule):
        if len(rule) == 1:
            op = rule[0].operations
            port = rule[0].value
            if op == '>=':
                port_range = (int(port), 65535)
            elif op == '<=':
                port_range = (0, int(port))
            elif op == '>':
                port_range = (int(port)+1, 65535)
            elif op == '<':
                port_range = (0, int(port)-1)
            else:
                port_range = int(port)
        else:
            port_range = ()
            for index in range(len(rule)):
                op = rule[index].operations
                port = rule[index].value
                if op == '>=' or op == '<=':
                    port_range += int(port)
                elif op == '>':
                    port_range += int(port)+1
                elif op == '<':
                    port_range += int(port)-1

        return port_range

    @logDecorator.log
    def _constructPortRules(self, port_range, flow_object):
        log.info("Construct port rules with %s (type %s) on %s object",
                 port_range, type(port_range), flow_object)
        port_rules = list()

        # Check if port range or port number
        if type(port_range) == tuple:
            port_min, port_max = port_range

            if int(port_min) == 0:
                # Operator <
                port_rules.append(flow_object(NumericOperator.LT,
                                              port_max))
            elif int(port_max) == 65535:
                # Operator >
                port_rules.append(flow_object(NumericOperator.GT,
                                              port_min))
            else:
                # Operator >=
                gt_eq_op = (NumericOperator.GT | NumericOperator.EQ)
                # Operator &<=
                lt_eq_op = (NumericOperator.AND | NumericOperator.LT |
                            NumericOperator.EQ)
                port_rules.append(flow_object(gt_eq_op, port_min))
                port_rules.append(flow_object(lt_eq_op, port_max))
        else:
            port_rules.append(flow_object(NumericOperator.EQ,
                                          port_range))

        return port_rules

    def _getSourcePrefix(self, rule):
        self.sourcePrefix = rule[0].cidr.prefix()

    def _getDestinationPrefix(self, rule):
        self.destinationPrefix = rule[0].cidr.prefix()

    def _parseAnyPort(self, rule):
        self.sourcePort = self._interpretPortRule(rule)
        self.destinationPort = list(self.sourcePort)

    def _parseSourcePort(self, rule):
        self.sourcePort = self._interpretPortRule(rule)

    def _parseDestinationPort(self, rule):
        self.destinationPort = self._interpretPortRule(rule)

    def _getProtocol(self, rule):
        self.protocol = Protocol.names[rule[0].value]

    def mapTrafficClassifier2RedirectRules(self):
        rules = list()
        if self.sourcePrefix:
            ip, mask = self.sourcePrefix.split('/')
            if IPNetwork(self.sourcePrefix).version == 4:
                rules.append(Flow4Source(socket.inet_pton(socket.AF_INET, ip),
                                         int(mask)))
            elif IPNetwork(self.sourcePrefix).version == 6:
                rules.append(Flow6Source(socket.inet_pton(socket.AF_INET6, ip),
                                         int(mask), 0))  # TODO: IPv6 offset ??

        if self.destinationPrefix:
            ip, mask = self.destinationPrefix.split('/')
            if IPNetwork(self.destinationPrefix).version == 4:
                rules.append(
                    Flow4Destination(socket.inet_pton(socket.AF_INET, ip),
                                     int(mask))
                )
            elif IPNetwork(self.destinationPrefix).version == 6:
                rules.append(
                    Flow6Destination(socket.inet_pton(socket.AF_INET6, ip),
                                     int(mask), 0)
                )  # TODO: IPv6 offset ??

        if self.sourcePort:
            rules += self._constructPortRules(self.sourcePort,
                                              FlowSourcePort)

        if self.destinationPort:
            rules += self._constructPortRules(self.destinationPort,
                                              FlowDestinationPort)

        if self.protocol:
            rules.append(FlowIPProtocol(NumericOperator.EQ,
                                        Protocol.codes[self.protocol]))

        return rules

    def mapRedirectRules2TrafficClassifier(self, rules):
        components = {1: self._getDestinationPrefix,  # FlowDestinationPrefix
                      2: self._getSourcePrefix,  # FlowSourcePrefix
                      3: self._getProtocol,  # FlowIPProtocol
                      4: self._parseAnyPort,  # FlowAnyPort
                      5: self._parseDestinationPort,  # FLowDestinationPort
                      6: self._parseSourcePort}  # FlowSourcePort

        for ID, rule in rules.iteritems():
            components[ID](rule)


class VPNInstance(TrackerWorker, Thread, LookingGlassLocalLogger):
    __metaclass__ = ABCMeta

    afi = None
    safi = None

    @logDecorator.log
    def __init__(self, vpnManager, dataplaneDriver,
                 externalInstanceId, instanceId, importRTs, exportRTs,
                 gatewayIP, mask, readvertise, attractTraffic, **kwargs):

        self.vpnManager = vpnManager

        self.instanceType = self.__class__.__name__
        self.instanceId = instanceId

        Thread.__init__(self)
        self.setDaemon(True)

        if dataplaneDriver.ecmpSupport:
            compareRoutes = compareECMP
        else:
            compareRoutes = compareNoECMP

        TrackerWorker.__init__(self, self.vpnManager.bgpManager, "%s-%d" %
                               (self.instanceType, self.instanceId),
                               compareRoutes)

        LookingGlassLocalLogger.__init__(self,
                                         "%s-%d" % (self.instanceType,
                                                    self.instanceId))
        self.lock = Lock()

        self.importRTs = importRTs
        self.exportRTs = exportRTs
        self.externalInstanceId = externalInstanceId
        self.gatewayIP = gatewayIP
        self.mask = mask

        self.afi = self.__class__.afi
        self.safi = self.__class__.safi
        assert(isinstance(self.afi, AFI))
        assert(isinstance(self.safi, SAFI))

        self.dataplaneDriver = dataplaneDriver

        self.instanceLabel = self.vpnManager.labelAllocator.getNewLabel(
            "Incoming traffic for %s %d" % (self.instanceType,
                                            self.instanceId))

        self.localPortData = dict()

        # One local port -> List of endpoints (MAC and IP addresses tuple)
        self.localPort2Endpoints = dict()
        # One MAC address -> One local port
        self.macAddress2LocalPortData = dict()
        # One IP address ->  One MAC address
        self.ipAddress2MacAddress = dict()

        # Redirected instances list from which traffic is attracted (based on
        # FlowSpec 5-tuple classification)
        self.redirectedInstances = set()

        self.dataplane = self.dataplaneDriver.initializeDataplaneInstance(
            self.instanceId, self.externalInstanceId,
            self.gatewayIP, self.mask, self.instanceLabel, **kwargs)

        for rt in self.importRTs:
            self._subscribe(self.afi, self.safi, rt)
            # Subscribe to FlowSpec routes
            # FIXME(tmorin): this maybe isn't applicable yet to E-VPN yet
            self._subscribe(self.afi, SAFI(SAFI.flow_vpn), rt)

        if readvertise:
            self.readvertise = True
            try:
                self.readvertiseToRTs = readvertise['to_rt']
            except KeyError:
                raise APIException("'readvertise' specified with no 'to_rt'")
            self.readvertiseFromRTs = readvertise.get('from_rt', [])
            self.log.debug("readvertise enabled, from RT:%s, to %s",
                           self.readvertiseFromRTs, self.readvertiseToRTs)
            for rt in self.readvertiseFromRTs:
                self._subscribe(self.afi, self.safi, rt)
        else:
            self.log.debug("readvertise not enabled")
            self.readvertise = False

        if self.readvertise and attractTraffic:
            if len(self.readvertiseToRTs) != 1:
                raise APIException("attract_traffic requires exactly one RT"
                                   " to be provided in readvertise/to_rt")
            self.attractTraffic = True
            self.attractRTs = attractTraffic['redirect_rts']
            try:
                self.attractClassifier = attractTraffic['classifier']
            except KeyError:
                raise APIException("'attractTraffic' specified with no "
                                   "'classifier'")
            self.log.debug("Attract traffic enabled with RT: %s and classifier:"
                           " %s", self.attractRTs, self.attractClassifier)
        else:
            self.log.debug("attract traffic not enabled")
            self.attractTraffic = False

    @utils.synchronized
    def stop(self):
        self._stop()

    @logDecorator.log
    def _stop(self):
        # cleanup BGP subscriptions
        for rt in self.importRTs:
            self._unsubscribe(self.afi, self.safi, rt)
            self._unsubscribe(self.afi, SAFI(SAFI.flow_vpn), rt)

        self.dataplane.cleanup()

        self.vpnManager.labelAllocator.release(self.instanceLabel)

        # this makes sure that the thread will be stopped, and any remaining
        # routes/subscriptions are released:
        TrackerWorker.stop(self)

    @utils.synchronized
    @logDecorator.log
    def stopIfEmpty(self):
        self.log.debug("localPort2Endpoints: %s", self.localPort2Endpoints)
        if self.isEmpty():
            self._stop()
            return True

        return False

    def isEmpty(self):
        return (not self.localPort2Endpoints)

    def hasEnpoint(self, linuxif):
        return (self.localPort2Endpoints.get(linuxif) is not None)

    @logDecorator.log
    def updateRouteTargets(self, newImportRTs, newExportRTs):
        added_import_rt = set(newImportRTs) - set(self.importRTs)
        removed_import_rt = set(self.importRTs) - set(newImportRTs)

        self.log.debug("%s %d - Added Import RTs: %s",
                       self.instanceType, self.instanceId, added_import_rt)
        self.log.debug("%s %d - Removed Import RTs: %s",
                       self.instanceType, self.instanceId, removed_import_rt)

        # Register to BGP with these route targets
        for rt in added_import_rt:
            self._subscribe(self.afi, self.safi, rt)
            self._subscribe(self.afi, SAFI(SAFI.flow_vpn), rt)

        # Unregister from BGP with these route targets
        for rt in removed_import_rt:
            self._unsubscribe(self.afi, self.safi, rt)
            self._unsubscribe(self.afi, SAFI(SAFI.flow_vpn), rt)

        # Update import and export route targets
        self.importRTs = newImportRTs

        # Re-advertise all routes with new export RTs
        self.log.debug("Exports RTs: %s -> %s", self.exportRTs, newExportRTs)
        if frozenset(newExportRTs) != frozenset(self.exportRTs):
            self.log.debug("Will re-export routes with new RTs")
            self.exportRTs = newExportRTs
            # FIXME: we should only update the routes that
            # are routes of ports plugged to the VPN instance,
            # not all routes which would wrongly include
            # routes that we re-advertise between RTs
            for routeEntry in self.getRouteEntries():
                self.log.info("Re-advertising route %s with updated RTs (%s)",
                              routeEntry.nlri, newExportRTs)

                updatedRouteEntry = RouteEntry(routeEntry.nlri, None,
                                               copy(routeEntry.attributes))
                # reset the routeTargets
                # will RTs originally present in routeEntry.attributes
                updatedRouteEntry.setRouteTargets(self.exportRTs)

                self.log.debug("   updated route: %s", updatedRouteEntry)

                self._advertiseRoute(updatedRouteEntry)

    def _parseIPAddressPrefix(self, ipAddressPrefix):
        ipAddress = ""
        mask = 0
        try:
            net = IPNetwork(ipAddressPrefix)
            (ipAddress, mask) = (str(net.ip), net.prefixlen)
        except netaddr.core.AddrFormatError as e:
            raise APIException("Bogus IP prefix: %s" % ipAddressPrefix)

        return (ipAddress, mask)

    def _genEncapExtendedCommunities(self):
        ecommunities = ExtendedCommunities()
        for encap in self.dataplaneDriver.supportedEncaps():
            if not isinstance(encap, Encapsulation):
                raise Exception("dataplaneDriver.supportedEncaps() should "
                                "return a list of Encapsulation objects (%s)",
                                type(encap))

            if encap != Encapsulation(Encapsulation.Type.DEFAULT):
                ecommunities.communities.append(encap)
        # FIXME: si DEFAULT + xxx => adv MPLS
        return ecommunities

    def _getRDFromInstanceId(self):
        return RouteDistinguisher.fromElements(
            self.bgpManager.getLocalAddress(),
            self.instanceId)

    @abstractmethod
    def generateVifBGPRoute(self, macAddress, ipPrefix, prefixLen, label):
        '''
        returns a RouteEntry
        '''
        pass<commit_msg>minor source cleanups<commit_after># vim: tabstop=4 shiftwidth=4 softtabstop=4
# encoding: utf-8

# Copyright 2014 Orange
#
# 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.
import logging
import socket

from abc import ABCMeta, abstractmethod

from collections import defaultdict

from copy import copy

from threading import Thread
from threading import Lock

from netaddr.ip import IPNetwork
import netaddr

from bagpipe.bgp.common import utils
from bagpipe.bgp.common import logDecorator
from bagpipe.bgp.common.looking_glass import LookingGlassLocalLogger, LGMap

from bagpipe.bgp.engine.tracker_worker import TrackerWorker, \
    compareECMP, compareNoECMP

from bagpipe.bgp.engine import RouteEntry

from exabgp.reactor.protocol import AFI, SAFI

from exabgp.bgp.message.update.attribute.community.extended.encapsulation \
    import Encapsulation
from exabgp.bgp.message.update.nlri.qualifier.rd import RouteDistinguisher

from exabgp.bgp.message.open.asn import ASN

from exabgp.bgp.message.update.attribute.community.extended.communities \
    import ExtendedCommunities
from exabgp.bgp.message.update.attribute.community.extended \
    import TrafficRedirect

from bagpipe.bgp.engine.flowspec import FlowRouteFactory

from exabgp.bgp.message.update.nlri.flow import (
    FlowSourcePort, FlowDestinationPort, FlowIPProtocol, Flow4Source,
    Flow6Source, Flow4Destination, Flow6Destination, NumericOperator)

from exabgp.protocol import Protocol

from bagpipe.bgp.rest_api import APIException

log = logging.getLogger(__name__)


class TrafficClassifier(object):

    def __init__(self, sourcePrefix=None, destinationPrefix=None,
                 sourcePort=None, destinationPort=None, protocol='tcp'):
        self.sourcePrefix = sourcePrefix
        self.destinationPrefix = destinationPrefix

        if sourcePort:
            if ":" in sourcePort:
                self.sourcePort = tuple(
                    [int(p) for p in sourcePort.split(":")]
                )
            else:
                self.sourcePort = int(sourcePort)
        else:
            self.sourcePort = sourcePort

        if destinationPort:
            if ":" in destinationPort:
                self.destinationPort = tuple(
                    [int(p) for p in destinationPort.split(":")]
                )
            else:
                self.destinationPort = int(destinationPort)
        else:
            self.destinationPort = destinationPort

        self.protocol = protocol

    def __repr__(self):
        return "traffic-classifier:%s" % str(self)

    def __str__(self):
        return "%s-%s,%s-%s,%s" % (self.sourcePrefix or "*",
                                   self.sourcePort or "*",
                                   self.destinationPrefix or "*",
                                   self.destinationPort or "*",
                                   self.protocol)

    def __eq__(self, other):
        return (isinstance(other, self.__class__) and
                self.__dict__ == other.__dict__)

    def __hash__(self):
        return hash((self.sourcePrefix, self.sourcePort,
                     self.destinationPrefix, self.destinationPort,
                     self.protocol))

    def _interpretPortRule(self, rule):
        if len(rule) == 1:
            op = rule[0].operations
            port = rule[0].value
            if op == '>=':
                port_range = (int(port), 65535)
            elif op == '<=':
                port_range = (0, int(port))
            elif op == '>':
                port_range = (int(port)+1, 65535)
            elif op == '<':
                port_range = (0, int(port)-1)
            else:
                port_range = int(port)
        else:
            port_range = ()
            for index in range(len(rule)):
                op = rule[index].operations
                port = rule[index].value
                if op == '>=' or op == '<=':
                    port_range += int(port)
                elif op == '>':
                    port_range += int(port)+1
                elif op == '<':
                    port_range += int(port)-1

        return port_range

    @logDecorator.log
    def _constructPortRules(self, port_range, flow_object):
        log.info("Construct port rules with %s (type %s) on %s object",
                 port_range, type(port_range), flow_object)
        port_rules = list()

        # Check if port range or port number
        if type(port_range) == tuple:
            port_min, port_max = port_range

            if int(port_min) == 0:
                # Operator <
                port_rules.append(flow_object(NumericOperator.LT,
                                              port_max))
            elif int(port_max) == 65535:
                # Operator >
                port_rules.append(flow_object(NumericOperator.GT,
                                              port_min))
            else:
                # Operator >=
                gt_eq_op = (NumericOperator.GT | NumericOperator.EQ)
                # Operator &<=
                lt_eq_op = (NumericOperator.AND | NumericOperator.LT |
                            NumericOperator.EQ)
                port_rules.append(flow_object(gt_eq_op, port_min))
                port_rules.append(flow_object(lt_eq_op, port_max))
        else:
            port_rules.append(flow_object(NumericOperator.EQ,
                                          port_range))

        return port_rules

    def _getSourcePrefix(self, rule):
        self.sourcePrefix = rule[0].cidr.prefix()

    def _getDestinationPrefix(self, rule):
        self.destinationPrefix = rule[0].cidr.prefix()

    def _parseAnyPort(self, rule):
        self.sourcePort = self._interpretPortRule(rule)
        self.destinationPort = list(self.sourcePort)

    def _parseSourcePort(self, rule):
        self.sourcePort = self._interpretPortRule(rule)

    def _parseDestinationPort(self, rule):
        self.destinationPort = self._interpretPortRule(rule)

    def _getProtocol(self, rule):
        self.protocol = Protocol.names[rule[0].value]

    def mapTrafficClassifier2RedirectRules(self):
        rules = list()
        if self.sourcePrefix:
            ip, mask = self.sourcePrefix.split('/')
            if IPNetwork(self.sourcePrefix).version == 4:
                rules.append(Flow4Source(socket.inet_pton(socket.AF_INET, ip),
                                         int(mask)))
            elif IPNetwork(self.sourcePrefix).version == 6:
                rules.append(Flow6Source(socket.inet_pton(socket.AF_INET6, ip),
                                         int(mask), 0))  # TODO: IPv6 offset ??

        if self.destinationPrefix:
            ip, mask = self.destinationPrefix.split('/')
            if IPNetwork(self.destinationPrefix).version == 4:
                rules.append(
                    Flow4Destination(socket.inet_pton(socket.AF_INET, ip),
                                     int(mask))
                )
            elif IPNetwork(self.destinationPrefix).version == 6:
                rules.append(
                    Flow6Destination(socket.inet_pton(socket.AF_INET6, ip),
                                     int(mask), 0)
                )  # TODO: IPv6 offset ??

        if self.sourcePort:
            rules += self._constructPortRules(self.sourcePort,
                                              FlowSourcePort)

        if self.destinationPort:
            rules += self._constructPortRules(self.destinationPort,
                                              FlowDestinationPort)

        if self.protocol:
            rules.append(FlowIPProtocol(NumericOperator.EQ,
                                        Protocol.codes[self.protocol]))

        return rules

    def mapRedirectRules2TrafficClassifier(self, rules):
        components = {1: self._getDestinationPrefix,  # FlowDestinationPrefix
                      2: self._getSourcePrefix,  # FlowSourcePrefix
                      3: self._getProtocol,  # FlowIPProtocol
                      4: self._parseAnyPort,  # FlowAnyPort
                      5: self._parseDestinationPort,  # FLowDestinationPort
                      6: self._parseSourcePort}  # FlowSourcePort

        for ID, rule in rules.iteritems():
            components[ID](rule)


class VPNInstance(TrackerWorker, Thread, LookingGlassLocalLogger):
    __metaclass__ = ABCMeta

    afi = None
    safi = None

    @logDecorator.log
    def __init__(self, vpnManager, dataplaneDriver,
                 externalInstanceId, instanceId, importRTs, exportRTs,
                 gatewayIP, mask, readvertise, attractTraffic, **kwargs):

        self.vpnManager = vpnManager

        self.instanceType = self.__class__.__name__
        self.instanceId = instanceId

        Thread.__init__(self)
        self.setDaemon(True)

        if dataplaneDriver.ecmpSupport:
            compareRoutes = compareECMP
        else:
            compareRoutes = compareNoECMP

        TrackerWorker.__init__(self, self.vpnManager.bgpManager, "%s-%d" %
                               (self.instanceType, self.instanceId),
                               compareRoutes)

        LookingGlassLocalLogger.__init__(self,
                                         "%s-%d" % (self.instanceType,
                                                    self.instanceId))
        self.lock = Lock()

        self.importRTs = importRTs
        self.exportRTs = exportRTs
        self.externalInstanceId = externalInstanceId
        self.gatewayIP = gatewayIP
        self.mask = mask

        self.afi = self.__class__.afi
        self.safi = self.__class__.safi
        assert(isinstance(self.afi, AFI))
        assert(isinstance(self.safi, SAFI))

        self.dataplaneDriver = dataplaneDriver

        self.instanceLabel = self.vpnManager.labelAllocator.getNewLabel(
            "Incoming traffic for %s %d" % (self.instanceType,
                                            self.instanceId))

        self.localPortData = dict()

        # One local port -> List of endpoints (MAC and IP addresses tuple)
        self.localPort2Endpoints = dict()
        # One MAC address -> One local port
        self.macAddress2LocalPortData = dict()
        # One IP address ->  One MAC address
        self.ipAddress2MacAddress = dict()

        # Redirected instances list from which traffic is attracted (based on
        # FlowSpec 5-tuple classification)
        self.redirectedInstances = set()

        self.dataplane = self.dataplaneDriver.initializeDataplaneInstance(
            self.instanceId, self.externalInstanceId,
            self.gatewayIP, self.mask, self.instanceLabel, **kwargs)

        for rt in self.importRTs:
            self._subscribe(self.afi, self.safi, rt)
            # Subscribe to FlowSpec routes
            # FIXME(tmorin): this maybe isn't applicable yet to E-VPN yet
            self._subscribe(self.afi, SAFI(SAFI.flow_vpn), rt)

        if readvertise:
            self.readvertise = True
            try:
                self.readvertiseToRTs = readvertise['to_rt']
            except KeyError:
                raise APIException("'readvertise' specified with no 'to_rt'")
            self.readvertiseFromRTs = readvertise.get('from_rt', [])
            self.log.debug("readvertise enabled, from RT:%s, to %s",
                           self.readvertiseFromRTs, self.readvertiseToRTs)
            for rt in self.readvertiseFromRTs:
                self._subscribe(self.afi, self.safi, rt)
        else:
            self.log.debug("readvertise not enabled")
            self.readvertise = False

        if self.readvertise and attractTraffic:
            if len(self.readvertiseToRTs) != 1:
                raise APIException("attract_traffic requires exactly one RT"
                                   " to be provided in readvertise/to_rt")
            self.attractTraffic = True
            self.attractRTs = attractTraffic['redirect_rts']
            try:
                self.attractClassifier = attractTraffic['classifier']
            except KeyError:
                raise APIException("'attractTraffic' specified with no "
                                   "'classifier'")
            self.log.debug("Attract traffic enabled with RT: %s and classifier:"
                           " %s", self.attractRTs, self.attractClassifier)
        else:
            self.log.debug("attract traffic not enabled")
            self.attractTraffic = False

    @utils.synchronized
    def stop(self):
        self._stop()

    @logDecorator.log
    def _stop(self):
        # cleanup BGP subscriptions
        for rt in self.importRTs:
            self._unsubscribe(self.afi, self.safi, rt)
            self._unsubscribe(self.afi, SAFI(SAFI.flow_vpn), rt)

        self.dataplane.cleanup()

        self.vpnManager.labelAllocator.release(self.instanceLabel)

        # this makes sure that the thread will be stopped, and any remaining
        # routes/subscriptions are released:
        TrackerWorker.stop(self)

    @utils.synchronized
    @logDecorator.log
    def stopIfEmpty(self):
        self.log.debug("localPort2Endpoints: %s", self.localPort2Endpoints)
        if self.isEmpty():
            self._stop()
            return True

        return False

    def isEmpty(self):
        return (not self.localPort2Endpoints)

    def hasEnpoint(self, linuxif):
        return (self.localPort2Endpoints.get(linuxif) is not None)

    @logDecorator.log
    def updateRouteTargets(self, newImportRTs, newExportRTs):
        added_import_rt = set(newImportRTs) - set(self.importRTs)
        removed_import_rt = set(self.importRTs) - set(newImportRTs)

        self.log.debug("%s %d - Added Import RTs: %s",
                       self.instanceType, self.instanceId, added_import_rt)
        self.log.debug("%s %d - Removed Import RTs: %s",
                       self.instanceType, self.instanceId, removed_import_rt)

        # Register to BGP with these route targets
        for rt in added_import_rt:
            self._subscribe(self.afi, self.safi, rt)
            self._subscribe(self.afi, SAFI(SAFI.flow_vpn), rt)

        # Unregister from BGP with these route targets
        for rt in removed_import_rt:
            self._unsubscribe(self.afi, self.safi, rt)
            self._unsubscribe(self.afi, SAFI(SAFI.flow_vpn), rt)

        # Update import and export route targets
        self.importRTs = newImportRTs

        # Re-advertise all routes with new export RTs
        self.log.debug("Exports RTs: %s -> %s", self.exportRTs, newExportRTs)
        if frozenset(newExportRTs) != frozenset(self.exportRTs):
            self.log.debug("Will re-export routes with new RTs")
            self.exportRTs = newExportRTs
            # FIXME: we should only update the routes that
            # are routes of ports plugged to the VPN instance,
            # not all routes which would wrongly include
            # routes that we re-advertise between RTs
            for routeEntry in self.getRouteEntries():
                self.log.info("Re-advertising route %s with updated RTs (%s)",
                              routeEntry.nlri, newExportRTs)

                updatedRouteEntry = RouteEntry(routeEntry.nlri, None,
                                               copy(routeEntry.attributes))
                # reset the routeTargets
                # will RTs originally present in routeEntry.attributes
                updatedRouteEntry.setRouteTargets(self.exportRTs)

                self.log.debug("   updated route: %s", updatedRouteEntry)

                self._advertiseRoute(updatedRouteEntry)

    def _parseIPAddressPrefix(self, ipAddressPrefix):
        ipAddress = ""
        mask = 0
        try:
            net = IPNetwork(ipAddressPrefix)
            (ipAddress, mask) = (str(net.ip), net.prefixlen)
        except netaddr.core.AddrFormatError:
            raise APIException("Bogus IP prefix: %s" % ipAddressPrefix)

        return (ipAddress, mask)

    def _genEncapExtendedCommunities(self):
        ecommunities = ExtendedCommunities()
        for encap in self.dataplaneDriver.supportedEncaps():
            if not isinstance(encap, Encapsulation):
                raise Exception("dataplaneDriver.supportedEncaps() should "
                                "return a list of Encapsulation objects (%s)",
                                type(encap))

            if encap != Encapsulation(Encapsulation.Type.DEFAULT):
                ecommunities.communities.append(encap)
        # FIXME: si DEFAULT + xxx => adv MPLS
        return ecommunities

    def _getRDFromInstanceId(self):
        return RouteDistinguisher.fromElements(
            self.bgpManager.getLocalAddress(),
            self.instanceId)

    @abstractmethod
    def generateVifBGPRoute(self, macAddress, ipPrefix, prefixLen, label):
        '''
        returns a RouteEntry
        '''
        pass
{'fraction_non_alphanumeric': 0.0854624791870012, 'fraction_numerical': 0.0043635528506631455, 'mean_word_length': 1.9065498539841468, 'pattern_counts': {'":': 8, '<': 15, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 64, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1532083', 'n_tokens_mistral': 9495, 'n_tokens_neox': 9123, 'n_words': 2149}