diff --git a/protocols/bgp/api/src/main/java/org/onosproject/bgp/controller/BgpController.java b/protocols/bgp/api/src/main/java/org/onosproject/bgp/controller/BgpController.java index 4620d18861..8030a57bf9 100644 --- a/protocols/bgp/api/src/main/java/org/onosproject/bgp/controller/BgpController.java +++ b/protocols/bgp/api/src/main/java/org/onosproject/bgp/controller/BgpController.java @@ -221,4 +221,9 @@ public interface BgpController { */ Set routeListener(); + /** + * Helper function to notify the controller if the topology has changed. + * Controller will decide if route-refresh needs to be triggered + */ + void notifyTopologyChange(); } diff --git a/protocols/bgp/api/src/main/java/org/onosproject/bgp/controller/BgpPeer.java b/protocols/bgp/api/src/main/java/org/onosproject/bgp/controller/BgpPeer.java index ab26173060..5e4e5323da 100644 --- a/protocols/bgp/api/src/main/java/org/onosproject/bgp/controller/BgpPeer.java +++ b/protocols/bgp/api/src/main/java/org/onosproject/bgp/controller/BgpPeer.java @@ -155,4 +155,9 @@ public interface BgpPeer { void updateEvpnNlri(FlowSpecOperation operType, IpAddress nextHop, List extcommunity, List evpnNlris); + + /** + * Send the Route Refresh message to the connected BGP peer. + */ + void sendRouteRefreshMessage(); } diff --git a/protocols/bgp/bgpio/src/main/java/org/onosproject/bgpio/util/Constants.java b/protocols/bgp/bgpio/src/main/java/org/onosproject/bgpio/util/Constants.java index 4b6c6ebcd2..7d62818e2b 100644 --- a/protocols/bgp/bgpio/src/main/java/org/onosproject/bgpio/util/Constants.java +++ b/protocols/bgp/bgpio/src/main/java/org/onosproject/bgpio/util/Constants.java @@ -22,6 +22,8 @@ public final class Constants { private Constants() { } + public static final byte EMPTY = 0x00; //Empty byte + public static final short TYPE_AND_LEN = 4; public static final short TYPE_AND_LEN_AS_SHORT = 4; public static final short TYPE_AND_LEN_AS_BYTE = 3; diff --git a/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpControllerImpl.java b/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpControllerImpl.java index e23b8386a4..9a2df47ce9 100644 --- a/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpControllerImpl.java +++ b/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpControllerImpl.java @@ -46,9 +46,16 @@ import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import static org.onlab.util.Tools.groupedThreads; + @Component(immediate = true, service = BgpController.class) public class BgpControllerImpl implements BgpController { @@ -72,6 +79,19 @@ public class BgpControllerImpl implements BgpController { private Map> closedSessionExceptionMap = new TreeMap<>(); protected Set bgpRouteListener = new CopyOnWriteArraySet<>(); + //IDs for timers + private static final int PERIODIC_TIMER = 1001; + private static final int WARMUP_TIMER = 1002; + private static final int COOLDOWN_TIMER = 1003; + + private static final int POOL_SIZE = 3; //Current pool size is 3 + private ScheduledExecutorService executor; + private ScheduledFuture cooldownFuture; + private ScheduledFuture periodicFuture; + private ScheduledFuture warmupFuture; + + private AtomicBoolean hasTopologyChanged = new AtomicBoolean(false); + @Override public void activeSessionExceptionAdd(String peerId, String exception) { if (peerId != null) { @@ -131,6 +151,9 @@ public class BgpControllerImpl implements BgpController { @Activate public void activate() { this.ctrl.start(); + executor = Executors.newScheduledThreadPool( + POOL_SIZE, + groupedThreads("onos/apps/bgpcontroller", "bgp-rr-timer")); log.info("Started"); } @@ -141,6 +164,7 @@ public class BgpControllerImpl implements BgpController { // Close all connected peers closeConnectedPeers(); this.ctrl.stop(); + executor.shutdown(); log.info("Stopped"); } @@ -265,6 +289,17 @@ public class BgpControllerImpl implements BgpController { } else { this.log.debug("Added Peer {}", bgpId.toString()); connectedPeers.put(bgpId, bgpPeer); + + //If all timers are stopped, start periodic timer + this.log.info("Start periodic timer"); + if (bgpconfig.isRouteRefreshEnabled() + && (periodicFuture == null || periodicFuture.isCancelled()) + && (cooldownFuture == null || cooldownFuture.isCancelled()) + && (warmupFuture == null || warmupFuture.isCancelled())) { + periodicFuture = executor.schedule(periodicTimerTask, + bgpconfig.getRouteRefreshPeriodicTimer(), TimeUnit.SECONDS); + } + return true; } } @@ -383,4 +418,143 @@ public class BgpControllerImpl implements BgpController { public Set prefixListener() { return bgpPrefixListener; } + + @Override + public void notifyTopologyChange() { + log.info("Topology change received"); + + hasTopologyChanged.set(true); + + //If cooldown timer is running, do nothing further because routeRefresh will be sent when it expires + if (cooldownFuture != null && !cooldownFuture.isCancelled()) { + log.debug("Do nothing : Cooldown timer running"); + return; + } + + //If warmup timer is running, refresh it. If not, start it + if (warmupFuture != null && !warmupFuture.isCancelled()) { + warmupFuture.cancel(true); + warmupFuture = null; + + warmupFuture = executor.schedule(warmupTimerTask, + bgpconfig.getRouteRefreshWarmupTimer(), TimeUnit.SECONDS); + + log.debug("Warmup timer running. Re-started warmup timer"); + return; + } else { + warmupFuture = executor.schedule(warmupTimerTask, + bgpconfig.getRouteRefreshWarmupTimer(), TimeUnit.SECONDS); + log.debug("Warmup timer started"); + return; + } + } + + protected void resetTimers() { + if (periodicFuture != null && !periodicFuture.isCancelled()) { + periodicFuture.cancel(true); + periodicFuture = null; + } + + if (warmupFuture != null && !warmupFuture.isCancelled()) { + warmupFuture.cancel(true); + warmupFuture = null; + } + + if (cooldownFuture != null && !cooldownFuture.isCancelled()) { + cooldownFuture.cancel(true); + cooldownFuture = null; + } + } + + protected synchronized void timerCallback(int timerId) { + switch (timerId) { + case PERIODIC_TIMER: + //Cancel periodic timer and run cooldown timer + periodicFuture.cancel(true); + periodicFuture = null; + + sendRouteRefreshToPeers(); + + //Cancel warmup timer if it is running + if (warmupFuture != null && !warmupFuture.isCancelled()) { + warmupFuture.cancel(true); + warmupFuture = null; + } + + cooldownFuture = executor.schedule(cooldownTimerTask, + bgpconfig.getRouteRefreshCooldownTimer(), TimeUnit.SECONDS); + log.debug("Cooldown timer started"); + break; + case WARMUP_TIMER: + //Send route refresh and start cooldown timer + warmupFuture.cancel(true); + warmupFuture = null; + + sendRouteRefreshToPeers(); + + cooldownFuture = executor.schedule(cooldownTimerTask, + bgpconfig.getRouteRefreshCooldownTimer(), TimeUnit.SECONDS); + //Cancel periodic timer, if it is running + if (periodicFuture != null && !periodicFuture.isCancelled()) { + periodicFuture.cancel(true); + periodicFuture = null; + } + log.debug("Cooldown timer started"); + break; + case COOLDOWN_TIMER: + //If hasTopologyChanged is true, we need to restart cooldown timer. + //Otherwise, start periodic timer + boolean hasTopologyChangedValue = hasTopologyChanged.get(); + + cooldownFuture.cancel(true); + cooldownFuture = null; + + if (hasTopologyChangedValue) { + sendRouteRefreshToPeers(); + cooldownFuture = executor.schedule(cooldownTimerTask, + bgpconfig.getRouteRefreshCooldownTimer(), TimeUnit.SECONDS); + log.debug("Cooldown timer started"); + } else { + periodicFuture = executor.schedule(periodicTimerTask, + bgpconfig.getRouteRefreshPeriodicTimer(), TimeUnit.SECONDS); + log.debug("Periodic timer started"); + } + break; + default: + log.error("Invalid timerId in callback"); + } + + } + + private synchronized void sendRouteRefreshToPeers() { + //Iterate over peers and send route refresh + connectedPeers.forEach((k, v) -> v.sendRouteRefreshMessage()); + + //Refresh hasTopologyChanged variable + hasTopologyChanged.set(false); + } + + private Runnable periodicTimerTask = new Runnable() { + @Override + public void run() { + log.debug("Periodic Timer Expired"); + timerCallback(PERIODIC_TIMER); + } + }; + + private Runnable cooldownTimerTask = new Runnable() { + @Override + public void run() { + log.info("Cooldown Timer Expired"); + timerCallback(COOLDOWN_TIMER); + } + }; + + private Runnable warmupTimerTask = new Runnable() { + @Override + public void run() { + log.debug("Warmup Timer Expired"); + timerCallback(WARMUP_TIMER); + } + }; } diff --git a/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpLocalRibImpl.java b/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpLocalRibImpl.java index edbe75c164..abea91c6ea 100644 --- a/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpLocalRibImpl.java +++ b/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpLocalRibImpl.java @@ -153,7 +153,8 @@ public class BgpLocalRibImpl implements BgpLocalRib { for (BgpNodeListener l : bgpController.listener()) { l.addNode((BgpNodeLSNlriVer4) nlri, details); } - log.debug("Local RIB ad node: {}", detailsLocRib.toString()); + bgpController.notifyTopologyChange(); + log.debug("Local RIB add node: {}", detailsLocRib.toString()); } } else if (nlri instanceof BgpLinkLsNlriVer4) { BgpLinkLSIdentifier linkLsIdentifier = ((BgpLinkLsNlriVer4) nlri).getLinkIdentifier(); @@ -173,6 +174,7 @@ public class BgpLocalRibImpl implements BgpLocalRib { for (BgpLinkListener l : bgpController.linkListener()) { l.addLink((BgpLinkLsNlriVer4) nlri, details); } + bgpController.notifyTopologyChange(); log.debug("Local RIB add link: {}", detailsLocRib.toString()); } } else if (nlri instanceof BgpPrefixIPv4LSNlriVer4) { @@ -314,6 +316,7 @@ public class BgpLocalRibImpl implements BgpLocalRib { l.deleteNode((BgpNodeLSNlriVer4) nlri); } nodeTree.remove(nodeLsIdentifier); + bgpController.notifyTopologyChange(); } } @@ -379,7 +382,7 @@ public class BgpLocalRibImpl implements BgpLocalRib { l.deleteLink((BgpLinkLsNlriVer4) nlri); } linkTree.remove(linkLsIdentifier); - + bgpController.notifyTopologyChange(); } } diff --git a/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpPeerImpl.java b/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpPeerImpl.java index 4dc8feebe8..d53bbc2315 100644 --- a/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpPeerImpl.java +++ b/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpPeerImpl.java @@ -46,6 +46,7 @@ import org.onosproject.bgpio.types.Med; import org.onosproject.bgpio.types.MpReachNlri; import org.onosproject.bgpio.types.MpUnReachNlri; import org.onosproject.bgpio.types.MultiProtocolExtnCapabilityTlv; +import org.onosproject.bgpio.types.RouteRefreshCapabilityTlv; import org.onosproject.bgpio.types.Origin; import org.onosproject.bgpio.types.RpdCapabilityTlv; import org.onosproject.bgpio.types.attr.WideCommunity; @@ -160,6 +161,19 @@ public class BgpPeerImpl implements BgpPeer { return false; } + private final boolean isRouteRefreshSupported() { + List capabilities = sessionInfo.remoteBgpCapability(); + + for (BgpValueType currentCapability : capabilities) { + if (currentCapability instanceof RouteRefreshCapabilityTlv) { + //Presence of Reoute Refresh capability TLV means route refresh is supported + log.debug("Route Refresh is supported by peer"); + return true; + } + } + return false; + } + /** * Send flow specification update message to peer. * @@ -319,6 +333,24 @@ public class BgpPeerImpl implements BgpPeer { channel.write(Collections.singletonList(msg)); } + @Override + public void sendRouteRefreshMessage() { + if (!isRouteRefreshSupported()) { + log.debug("Route Refresh not supported by peer, so cannot send message"); + return; + } + + BgpMessage msg = Controller.getBgpMessageFactory4() + .routeRefreshMsgBuilder() + .addAfiSafiValue(Constants.AFI_IPV6_UNICAST, Constants.EMPTY, Constants.SAFI_UNICAST) + .addAfiSafiValue(Constants.AFI_VALUE, Constants.EMPTY, Constants.SAFI_VALUE) + .build(); + + channel.write(Collections.singletonList(msg)); + + log.info("Route Refresh sent to {}", channelId); + } + @Override public void buildAdjRibIn(List pathAttr) throws BgpParseException { ListIterator iterator = pathAttr.listIterator(); diff --git a/providers/bgp/topology/src/test/java/org/onosproject/provider/bgp/topology/impl/BgpControllerAdapter.java b/providers/bgp/topology/src/test/java/org/onosproject/provider/bgp/topology/impl/BgpControllerAdapter.java index c1b53ed328..8ba85bf921 100644 --- a/providers/bgp/topology/src/test/java/org/onosproject/provider/bgp/topology/impl/BgpControllerAdapter.java +++ b/providers/bgp/topology/src/test/java/org/onosproject/provider/bgp/topology/impl/BgpControllerAdapter.java @@ -181,4 +181,9 @@ public class BgpControllerAdapter implements BgpController { public void removePrefixListener(BgpPrefixListener listener) { // TODO Auto-generated method stub } + + @Override + public void notifyTopologyChange() { + // TODO Auto-generated method stub + } }