Refactor the SDN-IP BGP code so the BGP routes are withdrawn if
the onos-app-sdnip feature is uninstalled.

Change-Id: I49c40ee172a06d5809da69f736648fa639745975
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
index 3ae5b82..6316b59 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
@@ -167,8 +167,33 @@
      * Shuts the router down.
      */
     public void shutdown() {
+        // Stop all threads
         bgpUpdatesExecutor.shutdownNow();
         bgpIntentsSynchronizerExecutor.shutdownNow();
+
+        synchronized (this) {
+            // Cleanup all local state
+            bgpRoutes = new ConcurrentInvertedRadixTree<>(
+                new DefaultByteArrayNodeFactory());
+            routeUpdates.clear();
+            routesWaitingOnArp.clear();
+            pushedRouteIntents.clear();
+
+            //
+            // Withdraw all SDN-IP intents
+            //
+            if (!isElectedLeader) {
+                return;         // Nothing to do: not the leader anymore
+            }
+            log.debug("Withdrawing all SDN-IP Route Intents...");
+            for (Intent intent : intentService.getIntents()) {
+                if (!(intent instanceof MultiPointToSinglePointIntent)
+                        || !intent.appId().equals(appId)) {
+                    continue;
+                }
+                intentService.withdraw(intent);
+            }
+        }
     }
 
     //@Override TODO hook this up to something
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
index e0a710a..8b620a9 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSession.java
@@ -237,6 +237,18 @@
     }
 
     @Override
+    public void channelOpen(ChannelHandlerContext ctx,
+                            ChannelStateEvent channelEvent) {
+        bgpSessionManager.addSessionChannel(channelEvent.getChannel());
+    }
+
+    @Override
+    public void channelClosed(ChannelHandlerContext ctx,
+                            ChannelStateEvent channelEvent) {
+        bgpSessionManager.removeSessionChannel(channelEvent.getChannel());
+    }
+
+    @Override
     public void channelConnected(ChannelHandlerContext ctx,
                                  ChannelStateEvent channelEvent) {
         localAddress = ctx.getChannel().getLocalAddress();
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
index 38fad6c..b6dfd34 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/bgp/BgpSessionManager.java
@@ -32,6 +32,8 @@
 import org.jboss.netty.channel.ChannelPipeline;
 import org.jboss.netty.channel.ChannelPipelineFactory;
 import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.group.ChannelGroup;
+import org.jboss.netty.channel.group.DefaultChannelGroup;
 import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
 import org.onlab.onos.sdnip.RouteListener;
 import org.onlab.onos.sdnip.RouteUpdate;
@@ -46,7 +48,10 @@
 public class BgpSessionManager {
     private static final Logger log =
         LoggerFactory.getLogger(BgpSessionManager.class);
+    boolean isShutdown = true;
     private Channel serverChannel;     // Listener for incoming BGP connections
+    private ServerBootstrap serverBootstrap;
+    private ChannelGroup allChannels = new DefaultChannelGroup();
     private ConcurrentMap<SocketAddress, BgpSession> bgpSessions =
         new ConcurrentHashMap<>();
     private Ip4Address myBgpId;        // Same BGP ID for all peers
@@ -85,6 +90,24 @@
     }
 
     /**
+     * Adds the channel for a BGP session.
+     *
+     * @param channel the channel to add
+     */
+    void addSessionChannel(Channel channel) {
+        allChannels.add(channel);
+    }
+
+    /**
+     * Removes the channel for a BGP session.
+     *
+     * @param channel the channel to remove
+     */
+    void removeSessionChannel(Channel channel) {
+        allChannels.remove(channel);
+    }
+
+    /**
      * Processes the connection from a BGP peer.
      *
      * @param bgpSession the BGP session for the peer
@@ -160,6 +183,7 @@
      */
     public void startUp(int listenPortNumber) {
         log.debug("BGP Session Manager startUp()");
+        isShutdown = false;
 
         ChannelFactory channelFactory =
             new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
@@ -183,13 +207,14 @@
         InetSocketAddress listenAddress =
             new InetSocketAddress(listenPortNumber);
 
-        ServerBootstrap serverBootstrap = new ServerBootstrap(channelFactory);
+        serverBootstrap = new ServerBootstrap(channelFactory);
         // serverBootstrap.setOptions("reuseAddr", true);
         serverBootstrap.setOption("child.keepAlive", true);
         serverBootstrap.setOption("child.tcpNoDelay", true);
         serverBootstrap.setPipelineFactory(pipelineFactory);
         try {
             serverChannel = serverBootstrap.bind(listenAddress);
+            allChannels.add(serverChannel);
         } catch (ChannelException e) {
             log.debug("Exception binding to BGP port {}: ",
                       listenAddress.getPort(), e);
@@ -200,10 +225,9 @@
      * Shuts down the BGP Session Manager operation.
      */
     public void shutDown() {
-        // TODO: Complete the implementation: remove routes, etc.
-        if (serverChannel != null) {
-            serverChannel.close();
-        }
+        isShutdown = true;
+        allChannels.close().awaitUninterruptibly();
+        serverBootstrap.releaseExternalResources();
     }
 
     /**
@@ -223,6 +247,9 @@
         synchronized void routeUpdates(BgpSession bgpSession,
                         Collection<BgpRouteEntry> addedBgpRouteEntries,
                         Collection<BgpRouteEntry> deletedBgpRouteEntries) {
+            if (isShutdown) {
+                return;         // Ignore any leftover updates if shutdown
+            }
             // Process the deleted route entries
             for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
                 processDeletedRoute(bgpSession, bgpRouteEntry);