def _workerCleanup(self, worker): ''' Consider all routes announced by this worker as withdrawn. Consider this worker unsubscribed from all of its current subscriptions. ''' log.info("Cleanup for worker %s", worker.name) # synthesize withdraw events for all routes from this worker if worker in self._source2entries: entries = self._source2entries[worker] log.info( " Preparing to withdraw %d routes that were advertised " "by worker", len(entries)) for entry in entries: log.info(" Enqueue event to Withdraw route %s", entry) self.enqueue(RouteEvent(RouteEvent.WITHDRAW, entry)) del self._source2entries[worker] else: log.info("(we had no trace of %s in _source2entries)", worker) # remove worker from all of its subscriptions if worker in self._worker2matches: for match in self._worker2matches[worker]: assert (match in self._match2workersAndEntries) self._match2workers(match).remove(worker) del self._worker2matches[worker]
def _routeEventUnsubscribe(self, unsubscription): self.routeTableManager.enqueue(unsubscription) if (self.config['enable_rtc'] and not isinstance(unsubscription.worker, BGPPeerWorker)): wasLastWorkerForSubscription = \ self._trackedSubscriptionsRemoveWorker(unsubscription) # FIXME: not excellent to hardcode this here if ((unsubscription.safi in (SAFI.mpls_vpn, SAFI.evpn)) and wasLastWorkerForSubscription): # synthesize a withdraw RouteEvent for a RouteTarget constraint # route routeEvent = RouteEvent( RouteEvent.WITHDRAW, self._subscription2RTCRouteEntry(unsubscription), self) log.debug( "Based on unsubscription => synthesized withdraw" " for RTC %s", routeEvent) self.routeTableManager.enqueue(routeEvent) else: log.debug( "No need to synthesize an RTC route " "(wasLastWorkerForSubscription:%s) ", wasLastWorkerForSubscription)
def _routeEventSubscribe(self, subscription): self.routeTableManager.enqueue(subscription) # synthesize a RouteEvent for a RouteTarget constraint route if (self.config['enable_rtc'] and not isinstance(subscription.worker, BGPPeerWorker)): firstWorkerForSubscription = self._trackedSubscriptionsAddWorker( subscription) # FIXME: not excellent to hardcode this here if ((subscription.safi in (SAFI.mpls_vpn, SAFI.evpn)) and firstWorkerForSubscription): routeEvent = RouteEvent( RouteEvent.ADVERTISE, self._subscription2RTCRouteEntry(subscription), self) log.debug("Based on subscription => synthesized RTC %s", routeEvent) self.routeTableManager.enqueue(routeEvent) else: log.debug( "No need to synthesize an RTC route " "(firstWorkerForSubscription:%s) ", firstWorkerForSubscription)
def _readvertiseStop(self, nlri): self.log.debug("Stop re-advertising %s from VRF", nlri.prefix) for label in self._getLocalLabels(): self.log.debug("Stop re-advertising %s from VRF, with label %s", nlri.prefix, label) routeEntry = self._routeForReAdvertisement(nlri.prefix, label) self._pushEvent(RouteEvent(RouteEvent.WITHDRAW, routeEntry)) self.readvertised.remove(nlri.prefix)
def _workerUnsubscribes(self, sub): assert (isinstance(sub.worker, Worker)) # self._dumpState() match = Match(sub.afi, sub.safi, sub.routeTarget) # update _worker2matches if sub.worker not in self._worker2matches: log.warning("worker %s unsubs'd from %s but wasn't tracked yet", sub.worker, match) else: try: self._worker2matches[sub.worker].remove(match) except KeyError: log.warning( "worker %s unsubs' from %s but this match was" "not tracked for this worker (should not happen," " this is a bug)", sub.worker, match) # synthesize withdraw events for entry in self._match2entries(match, emptyListIfNone=True): intersect = set( self._matchesFor(entry.afi, entry.safi, entry.routeTargets)).intersection( self._worker2matches[sub.worker]) if len(intersect) > 0: log.debug( "Will not synthesize withdraw event for %s, because" " worker subscribed to %s", entry, intersect) else: log.debug("Found a entry for this match: %s", entry) event = RouteEvent(RouteEvent.WITHDRAW, entry) (shouldDispatch, reason) = self._shouldDispatch(event, sub.worker) if shouldDispatch: log.info("Dispatching re-synthesized event for %s", entry) sub.worker.enqueue(event) else: log.info( "%s => not dispatching re-synthesized event for %s", reason, entry) # update _match2workersAndEntries if match not in self._match2workersAndEntries: log.warning( "worker %s unsubscribed from %s but we had no such" " subscription yet", sub.worker, match) else: try: self._match2workers(match).remove(sub.worker) except KeyError: log.warning( "worker %s unsubscribed from %s but was not" " subscribed yet", sub.worker, match) self._checkMatch2workersAndEntriesCleanup(match)
def _newRouteEvent(self, eventType, nlri, rts, source, nh, lp=0, replacedRouteEntry=None, afi=AFI(AFI.ipv4), safi=SAFI(SAFI.mpls_vpn)): attributes = Attributes() attributes.add(NextHop(nh)) attributes.add(LocalPreference(lp)) routeEvent = RouteEvent(eventType, RouteEntry( afi, safi, rts, nlri, attributes, source), source) routeEvent.setReplacedRoute(replacedRouteEntry) self.eventTargetWorker.enqueue(routeEvent) log.info("Emitting event to %s: %s", self.eventTargetWorker, routeEvent) self._wait() return routeEvent
def vifUnplugged(self, macAddress, ipAddressPrefix, advertiseSubnet): label = self.macAddress2LocalPortData[macAddress]['label'] for prefix in self.readvertised: self.log.debug("Stop re-advertising %s with this port as next hop", prefix) routeEntry = self._routeForReAdvertisement(prefix, label) self._pushEvent(RouteEvent(RouteEvent.WITHDRAW, routeEntry)) VPNInstance.vifUnplugged(self, macAddress, ipAddressPrefix, advertiseSubnet)
def _readvertise(self, nlri): self.log.debug("Start re-advertising %s from VRF", nlri.prefix) for label in self._getLocalLabels(): self.log.debug("Start re-advertising %s from VRF, with label %s", nlri.prefix, label) # need a distinct RD for each route... routeEntry = self._routeForReAdvertisement(nlri.prefix, label) self._pushEvent(RouteEvent(RouteEvent.ADVERTISE, routeEntry)) self.readvertised.add(nlri.prefix)
def _processReceivedRoute(self, route): self.log.info("Received route: %s", route) rts = [] if AttributeID.EXTENDED_COMMUNITY in route.attributes: rts = [ ecom for ecom in route.attributes[ AttributeID.EXTENDED_COMMUNITY].communities if isinstance(ecom, RouteTarget) ] if not rts: raise Exception("Unable to find any Route Targets" "in the received route") routeEntry = self._newRouteEntry(route.nlri.afi, route.nlri.safi, rts, route.nlri, route.attributes) if route.action == "announce": self._pushEvent(RouteEvent(RouteEvent.ADVERTISE, routeEntry)) else: self._pushEvent(RouteEvent(RouteEvent.WITHDRAW, routeEntry)) # TODO(tmmorin): move RTC code out-of the peer-specific code if (route.nlri.afi, route.nlri.safi) == (AFI(AFI.ipv4), SAFI(SAFI.rtc)): self.log.info("Received an RTC route") if route.nlri.route_target is None: self.log.info("Received RTC is a wildcard") # the semantic of RTC routes does not distinguish between AFI/SAFIs # if our peer subscribed to a Route Target, it means that we needs # to send him all routes of any AFI/SAFI carrying this RouteTarget. for (afi, safi) in self._activeFamilies: if (afi, safi) != (AFI(AFI.ipv4), SAFI(SAFI.rtc)): if route.action == "announce": self._subscribe(afi, safi, route.nlri.route_target) else: # withdraw self._unsubscribe(afi, safi, route.nlri.route_target)
def _workerSubscribes(self, sub): # TODO: this function currently will not consider whether or not # is already subscribed to set of route events, before considering # a subscription. In particular, multiple identical subscriptions # will lead to this code resyntethizing events at each call. # # Ideally, the code should detect that the worker is already subscribed # and skip the subscription. *But* such a change should not be done # until the code in ExaBGPPeerWorker is updated to support this. assert (isinstance(sub.worker, Worker)) log.info("workerSubscribes: %s", sub) worker = sub.worker # self._dumpState() match = Match(sub.afi, sub.safi, sub.routeTarget) # update match2worker self._match2workers(match, createIfNone=True).add(worker) # update worker2matches if worker not in self._worker2matches: self._worker2matches[worker] = set() # re-synthesize events for entry in self._match2entries(match): log.debug("Found a entry for this match: %s", entry) event = RouteEvent(RouteEvent.ADVERTISE, entry) (shouldDispatch, reason) = self._shouldDispatch(event, worker) if shouldDispatch: # check if the entry carries a routeTarget to which the worker # was already subscribed for rt in entry.routeTargets: if Match(entry.afi, entry.safi, rt) in self._worker2matches[worker]: (shouldDispatch, reason) = ( False, "worker already had a subscription for this route") break if shouldDispatch: log.info("Dispatching re-synthesized event for %s", entry) worker.enqueue(event) else: log.info("%s => not dispatching re-synthesized event for %s", reason, entry) # update worker2matches self._worker2matches[worker].add(match)
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) # Unregister from BGP with these route targets for rt in removed_import_rt: self._unsubscribe(self.afi, self.safi, 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 for routeEntry in self.getWorkerRouteEntries(): self.log.info("Re-advertising route %s with updated RTs (%s)", routeEntry.nlri, newExportRTs) updatedAttributes = copy(routeEntry.attributes) del updatedAttributes[AttributeID.EXTENDED_COMMUNITY] updatedAttributes.add(self._genExtendedCommunities()) updatedRouteEntry = self._newRouteEntry( routeEntry.afi, routeEntry.safi, self.exportRTs, routeEntry.nlri, updatedAttributes) self.log.debug(" updated route: %s", updatedRouteEntry) self._pushEvent( RouteEvent(RouteEvent.ADVERTISE, updatedRouteEntry))
def __init__(self, *args, **kwargs): VPNInstance.__init__(self, *args, **kwargs) self.gwPort = None # Advertise route to receive multi-destination traffic self.log.info("Generating BGP route for broadcast/multicast traffic") etag = None label = LabelStackEntry(self.instanceLabel) route = Route( EVPNMulticast( RouteDistinguisher(RouteDistinguisher.TYPE_IP_LOC, None, self.bgpManager.getLocalAddress(), self.instanceId), etag, self.bgpManager.getLocalAddress())) route.attributes.add(self._genExtendedCommunities()) # add PMSI Tunnel Attribute route pmsi_tunnel_attribute = PMSITunnelIngressReplication( self.dataplaneDriver.getLocalAddress(), label) route.attributes.add(pmsi_tunnel_attribute) nh = Inet( 1, socket.inet_pton(socket.AF_INET, self.dataplaneDriver.getLocalAddress())) route.attributes.add(NextHop(nh)) self.multicastRouteEntry = self._newRouteEntry(self.afi, self.safi, self.exportRTs, route.nlri, route.attributes) self._pushEvent( RouteEvent(RouteEvent.ADVERTISE, self.multicastRouteEntry))
def testF1_testEmptyRT(self): # worker advertises a route with no RT w1 = self._newworker("Worker1", Worker) subscribe = Subscription(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), None, w1) self.routeTableManager.enqueue(subscribe) w2 = self._newworker("Worker2", Worker) routeEvent = RouteEvent( RouteEvent.ADVERTISE, RouteEntry(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), None, NLRI1, Attributes(), w2), w2) self.routeTableManager.enqueue(routeEvent) self._wait() self.assertEqual( 1, w1.enqueue.call_count, "1 route advertised should be synthesized to Worker1")
def vifUnplugged(self, macAddress, ipAddressPrefix, advertiseSubnet=False): # Verify port and endpoint (MAC address, IP address) tuple consistency portData = self.macAddress2LocalPortData.get(macAddress) if (not portData or self.ipAddress2MacAddress.get(ipAddressPrefix) != macAddress): self.log.error( "vifUnplugged called for endpoint (%s, %s), but no " "consistent informations or was not plugged yet", macAddress, ipAddressPrefix) raise APIException("No consistent endpoint (%s, %s) informations " "or was not plugged yet, cannot unplug" % (macAddress, ipAddressPrefix)) # Finding label and local port informations label = portData.get('label') localPort = portData.get('port_info') if (not label or not localPort): self.log.error( "vifUnplugged called for endpoint (%s, %s), but " "port data (%s, %s) is incomplete", macAddress, ipAddressPrefix, label, localPort) raise Exception("Inconsistent informations for port, bug ?") if localPort['linuxif'] in self.localPort2Endpoints: # Parse address/mask (ipPrefix, prefixLen) = self._parseIPAddressPrefix(ipAddressPrefix) lastEndpoint = len( self.localPort2Endpoints[localPort['linuxif']]) <= 1 if not advertiseSubnet: self.log.debug("Will advertise as /32 instead of /%d" % prefixLen) prefixLen = 32 self.log.info( "Synthesizing and withdrawing BGP route for VIF %s " "endpoint (%s, %s/%d)", localPort['linuxif'], macAddress, ipPrefix, prefixLen) routeEntry = self.synthesizeVifBGPRoute(macAddress, ipPrefix, prefixLen, label) self._pushEvent(RouteEvent(RouteEvent.WITHDRAW, routeEntry)) # Unplug endpoint from data plane self.dataplane.vifUnplugged(macAddress, ipPrefix, localPort, label, lastEndpoint) # Forget data for this port if last endpoint if lastEndpoint: # Free label to the allocator self.labelAllocator.release(label) del self.localPort2Endpoints[localPort['linuxif']] else: self.localPort2Endpoints[localPort['linuxif']].remove({ 'mac': macAddress, 'ip': ipAddressPrefix }) if not lastEndpoint: if not any([ endpoint['mac'] == macAddress for endpoint in self.localPort2Endpoints[localPort['linuxif']] ]): del self.macAddress2LocalPortData[macAddress] else: del self.macAddress2LocalPortData[macAddress] del self.ipAddress2MacAddress[ipAddressPrefix] else: self.log.error( "vifUnplugged called for endpoint {%s, %s}, but" " port data is incomplete", macAddress, ipAddressPrefix) raise Exception("BGP component bug, check its logs")
def vifPlugged(self, macAddress, ipAddressPrefix, localPort, advertiseSubnet=False): # Check if this port has already been plugged # - Verify port informations consistency if macAddress in self.macAddress2LocalPortData: self.log.debug("MAC address already plugged, checking port " "consistency") portData = self.macAddress2LocalPortData[macAddress] if (portData.get("port_info") != localPort): raise APIException("Port information is not consistent. MAC " "address cannot be bound to two different" "ports. Previous plug for port %s " "(%s != %s)" % (localPort['linuxif'], portData.get("port_info"), localPort)) # - Verify (MAC address, IP address) tuple consistency if ipAddressPrefix in self.ipAddress2MacAddress: if self.ipAddress2MacAddress.get(ipAddressPrefix) != macAddress: raise APIException("Inconsistent endpoint info: %s already " "bound to a MAC address different from %s" % (ipAddressPrefix, macAddress)) else: return # Else, plug port on dataplane try: # Parse address/mask (ipPrefix, prefixLen) = self._parseIPAddressPrefix(ipAddressPrefix) self.log.debug("Plugging port (%s)", ipPrefix) portData = self.macAddress2LocalPortData.get(macAddress, dict()) if not portData: portData['label'] = self.labelAllocator.getNewLabel( "Incoming traffic for %s %d, interface %s, endpoint %s/%s" % (self.instanceType, self.instanceId, localPort['linuxif'], macAddress, ipAddressPrefix)) portData["port_info"] = localPort # Call driver to setup the dataplane for incoming traffic self.dataplane.vifPlugged(macAddress, ipPrefix, localPort, portData['label']) if not advertiseSubnet: self.log.debug("Will advertise as /32 instead of /%d" % prefixLen) prefixLen = 32 self.log.info( "Synthesizing and advertising BGP route for VIF %s " "endpoint (%s, %s/%d)", localPort['linuxif'], macAddress, ipPrefix, prefixLen) routeEntry = self.synthesizeVifBGPRoute(macAddress, ipPrefix, prefixLen, portData['label']) self._pushEvent(RouteEvent(RouteEvent.ADVERTISE, routeEntry)) if localPort['linuxif'] not in self.localPort2Endpoints: self.localPort2Endpoints[localPort['linuxif']] = list() self.localPort2Endpoints[localPort['linuxif']].append({ 'mac': macAddress, 'ip': ipAddressPrefix, }) self.macAddress2LocalPortData[macAddress] = portData self.ipAddress2MacAddress[ipAddressPrefix] = macAddress except Exception as e: self.log.error("Error in vifPlugged: %s", e) if localPort['linuxif'] in self.localPort2Endpoints: if len(self.localPort2Endpoints[localPort['linuxif']]) > 1: self.localPort2Endpoints[localPort['linuxif']].remove({ 'mac': macAddress, 'ip': ipAddressPrefix }) else: del self.localPort2Endpoints[localPort['linuxif']] if macAddress in self.macAddress2LocalPortData: del self.macAddress2LocalPortData[macAddress] if ipAddressPrefix in self.ipAddress2MacAddress: del self.ipAddress2MacAddress[ipAddressPrefix] raise
def _receiveRouteEvent(self, routeEvent): log.info("receive: %s", routeEvent) entry = routeEvent.routeEntry log.debug("Try to find a entry from same peer with same nlri") try: replacedEntry = self._source_nlri2entry[(entry.source, entry.nlri)] except KeyError: replacedEntry = None log.debug(" Result: %s", replacedEntry) # replacedEntry should be non-empty for a withdraw if replacedEntry is None and (routeEvent.type == RouteEvent.WITHDRAW): log.warning("WITHDRAW but found no route that we could remove: %s", routeEvent.routeEntry) return # Propagate events to interested workers... if routeEvent.type == RouteEvent.ADVERTISE: if replacedEntry == routeEvent.routeEntry: log.warning("The route advertized is the same as the one " "previously advertized by the source, ignoring") return # propagate event to interested worker # and include the info on the route are replaced by this # route, if any routeEvent.setReplacedRoute(replacedEntry) workersAlreadyNotified = self._propagateRouteEvent(routeEvent) else: # WITHDRAW workersAlreadyNotified = None # Synthesize and dispatch a withdraw event for the route entry that # was withdrawn or replaced, except, in the case of a replaced route, # to workers that had the ADVERTISE event if replacedEntry is not None: log.debug("Synthesizing a withdraw event for replaced route %s", replacedEntry) removalEvent = RouteEvent(RouteEvent.WITHDRAW, replacedEntry, routeEvent.source) self._propagateRouteEvent(removalEvent, workersAlreadyNotified) # Update match2entries and source2entries for the # replacedRoute for match in self._matchesFor(replacedEntry.afi, replacedEntry.safi, replacedEntry.routeTargets): try: self._match2entries(match).discard(replacedEntry) except KeyError: log.error( "Trying to remove a route from a match, but" " match %s not found - not supposed to happen" " (route: %s)", match, replacedEntry) self._checkMatch2workersAndEntriesCleanup(match) self._source2entriesRemoveEntry(replacedEntry) if routeEvent.type == RouteEvent.ADVERTISE: # Update match2entries and source2entries for the newly # advertized route for match in self._matchesFor(entry.afi, entry.safi, entry.routeTargets): self._match2entries(match, createIfNone=True).add(entry) self._source2entriesAddEntry(entry) # Update _source_nlri2entry self._source_nlri2entry[(entry.source, entry.nlri)] = entry else: # WITHDRAW # Update _source_nlri2entry try: del self._source_nlri2entry[(entry.source, entry.nlri)] except KeyError: log.error("Withdraw, but nothing removed in " "_sourcenlri2entryRemove")