Carry previous next hop information in RouteEvent

Change-Id: Ib66a9191892c0e62b54fddcbfdc5645f305e9fcf
diff --git a/apps/routing/src/test/java/org/onosproject/routing/impl/SingleSwitchFibInstallerTest.java b/apps/routing/src/test/java/org/onosproject/routing/impl/SingleSwitchFibInstallerTest.java
index 2b60f91..1e6efb5 100644
--- a/apps/routing/src/test/java/org/onosproject/routing/impl/SingleSwitchFibInstallerTest.java
+++ b/apps/routing/src/test/java/org/onosproject/routing/impl/SingleSwitchFibInstallerTest.java
@@ -335,6 +335,7 @@
         testRouteAdd();
         reset(flowObjectiveService);
 
+        ResolvedRoute oldRoute = new ResolvedRoute(PREFIX1, NEXT_HOP1, MAC1, SW1_ETH1);
         ResolvedRoute route = new ResolvedRoute(PREFIX1, NEXT_HOP2, MAC2, SW1_ETH2);
 
         // Create the next objective
@@ -348,7 +349,7 @@
         setUpFlowObjectiveService();
 
         // Send in the update event
-        routeListener.event(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED, route));
+        routeListener.event(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED, route, oldRoute));
 
         verify(flowObjectiveService);
     }
@@ -365,7 +366,7 @@
         testRouteAdd();
 
         // Construct the existing route
-        ResolvedRoute route = new ResolvedRoute(PREFIX1, null, null, null);
+        ResolvedRoute route = new ResolvedRoute(PREFIX1, NEXT_HOP1, MAC1, SW1_ETH1);
 
         // Create the flow objective
         reset(flowObjectiveService);
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java
index a764c83..31e1cde 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java
@@ -244,6 +244,7 @@
         testRouteAddToNoVlan();
 
         // Build the new route entries for prefix1 and prefix2
+        ResolvedRoute oldRoutePrefixOne = new ResolvedRoute(PREFIX1, IP3, MAC3, SW3_ETH1);
         ResolvedRoute routePrefixOne = new ResolvedRoute(PREFIX1, IP1, MAC1, SW1_ETH1);
 
         // Create the new expected intents
@@ -258,7 +259,7 @@
 
         // Send in the update events
         routeListener.event(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED,
-                                           routePrefixOne));
+                                           routePrefixOne, oldRoutePrefixOne));
 
         verify(intentSynchronizer);
     }
@@ -278,6 +279,7 @@
         testRouteAddToVlan();
 
         // Build the new route entries for prefix1 and prefix2
+        ResolvedRoute oldRoutePrefix = new ResolvedRoute(PREFIX2, IP1, MAC1, SW1_ETH1);
         ResolvedRoute routePrefix = new ResolvedRoute(PREFIX2, IP3, MAC3, SW3_ETH1);
 
         // Create the new expected intents
@@ -293,7 +295,7 @@
 
         // Send in the update events
         routeListener.event(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED,
-                                           routePrefix));
+                                           routePrefix, oldRoutePrefix));
 
         verify(intentSynchronizer);
     }
@@ -310,7 +312,7 @@
         testRouteAddToNoVlan();
 
         // Construct the existing route entry
-        ResolvedRoute route = new ResolvedRoute(PREFIX1, null, null, null);
+        ResolvedRoute route = new ResolvedRoute(PREFIX1, IP3, MAC3, SW3_ETH1);
 
         // Create existing intent
         MultiPointToSinglePointIntent removedIntent =
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteEvent.java b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteEvent.java
index 1889a53..fc5199d 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteEvent.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteEvent.java
@@ -16,15 +16,20 @@
 
 package org.onosproject.incubator.net.routing;
 
+import org.joda.time.LocalDateTime;
 import org.onosproject.event.AbstractEvent;
 
 import java.util.Objects;
 
+import static com.google.common.base.MoreObjects.toStringHelper;
+
 /**
  * Describes an event about a route.
  */
 public class RouteEvent extends AbstractEvent<RouteEvent.Type, ResolvedRoute> {
 
+    private final ResolvedRoute prevSubject;
+
     /**
      * Route event type.
      */
@@ -32,28 +37,38 @@
 
         /**
          * Route is new.
+         * <p>
+         * The subject of this event should be the route being added.
+         * The prevSubject of this event should be null.
          */
         ROUTE_ADDED,
 
         /**
          * Route has updated information.
+         * <p>
+         * The subject of this event should be the new route.
+         * The prevSubject of this event should be the old route.
          */
         ROUTE_UPDATED,
 
         /**
          * Route was removed.
+         * <p>
+         * The subject of this event should be the route being removed.
+         * The prevSubject of this event should be null.
          */
         ROUTE_REMOVED
     }
 
     /**
-     * Creates a new route event.
+     * Creates a new route event without specifying previous subject.
      *
      * @param type event type
      * @param subject event subject
      */
     public RouteEvent(Type type, ResolvedRoute subject) {
         super(type, subject);
+        this.prevSubject = null;
     }
 
     /**
@@ -65,11 +80,33 @@
      */
     protected RouteEvent(Type type, ResolvedRoute subject, long time) {
         super(type, subject, time);
+        this.prevSubject = null;
+    }
+
+    /**
+     * Creates a new route event with previous subject.
+     *
+     * @param type event type
+     * @param subject event subject
+     * @param prevSubject previous subject
+     */
+    public RouteEvent(Type type, ResolvedRoute subject, ResolvedRoute prevSubject) {
+        super(type, subject);
+        this.prevSubject = prevSubject;
+    }
+
+    /**
+     * Returns the previous subject of the event.
+     *
+     * @return previous subject to which this event pertains
+     */
+    public ResolvedRoute prevSubject() {
+        return prevSubject;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(subject(), type());
+        return Objects.hash(subject(), type(), prevSubject());
     }
 
     @Override
@@ -85,6 +122,17 @@
         RouteEvent that = (RouteEvent) other;
 
         return Objects.equals(this.subject(), that.subject()) &&
-                Objects.equals(this.type(), that.type());
+                Objects.equals(this.type(), that.type()) &&
+                Objects.equals(this.prevSubject(), that.prevSubject());
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("time", new LocalDateTime(time()))
+                .add("type", type())
+                .add("subject", subject())
+                .add("prevSubject", prevSubject())
+                .toString();
     }
 }
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteStore.java b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteStore.java
index 9eac65c..22a6285 100644
--- a/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteStore.java
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteStore.java
@@ -74,7 +74,7 @@
     Collection<Route> getRoutesForNextHop(IpAddress ip);
 
     /**
-     * Updates a next hop IP and MAC in the store.
+     * Updates a next hop information in the store.
      *
      * @param ip IP address
      * @param nextHopData Information of the next hop
@@ -82,7 +82,7 @@
     void updateNextHop(IpAddress ip, NextHopData nextHopData);
 
     /**
-     * Removes a next hop IP and MAC from the store.
+     * Removes a next hop information from the store.
      *
      * @param ip IP address
      * @param nextHopData Information of the next hop
@@ -90,7 +90,7 @@
     void removeNextHop(IpAddress ip, NextHopData nextHopData);
 
     /**
-     * Returns the MAC address of the given next hop.
+     * Returns the information of the given next hop.
      *
      * @param ip next hop IP
      * @return Information of the next hop
diff --git a/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/RouteManager.java b/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/RouteManager.java
index 4c0f59e..6669281 100644
--- a/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/RouteManager.java
+++ b/incubator/net/src/main/java/org/onosproject/incubator/net/routing/impl/RouteManager.java
@@ -120,7 +120,7 @@
                 if (routes != null) {
                     routes.forEach(route -> {
                         NextHopData nextHopData = routeStore.getNextHop(route.nextHop());
-                            l.post(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED,
+                            l.post(new RouteEvent(RouteEvent.Type.ROUTE_ADDED,
                                     new ResolvedRoute(route, nextHopData.mac(),
                                             nextHopData.location())));
                     });
diff --git a/incubator/net/src/test/java/org/onosproject/incubator/net/routing/impl/RouteManagerTest.java b/incubator/net/src/test/java/org/onosproject/incubator/net/routing/impl/RouteManagerTest.java
index 86ba6f6..ba73cde 100644
--- a/incubator/net/src/test/java/org/onosproject/incubator/net/routing/impl/RouteManagerTest.java
+++ b/incubator/net/src/test/java/org/onosproject/incubator/net/routing/impl/RouteManagerTest.java
@@ -215,23 +215,26 @@
     public void testRouteUpdate() {
         Route route = new Route(Route.Source.STATIC, V4_PREFIX1, V4_NEXT_HOP1);
         Route updatedRoute = new Route(Route.Source.STATIC, V4_PREFIX1, V4_NEXT_HOP2);
+        ResolvedRoute resolvedRoute = new ResolvedRoute(updatedRoute, MAC1, CP1);
         ResolvedRoute updatedResolvedRoute = new ResolvedRoute(updatedRoute, MAC2, CP1);
 
-        verifyRouteRemoveThenAdd(route, updatedRoute, updatedResolvedRoute);
+        verifyRouteRemoveThenAdd(route, updatedRoute, resolvedRoute, updatedResolvedRoute);
 
         // Different prefix pointing to the same next hop.
         // In this case we expect to receive a ROUTE_UPDATED event.
         route = new Route(Route.Source.STATIC, V4_PREFIX2, V4_NEXT_HOP1);
         updatedRoute = new Route(Route.Source.STATIC, V4_PREFIX2, V4_NEXT_HOP2);
+        resolvedRoute = new ResolvedRoute(route, MAC1, CP1);
         updatedResolvedRoute = new ResolvedRoute(updatedRoute, MAC2, CP1);
 
-        verifyRouteUpdated(route, updatedRoute, updatedResolvedRoute);
+        verifyRouteUpdated(route, updatedRoute, resolvedRoute, updatedResolvedRoute);
 
         route = new Route(Route.Source.STATIC, V6_PREFIX1, V6_NEXT_HOP1);
         updatedRoute = new Route(Route.Source.STATIC, V6_PREFIX1, V6_NEXT_HOP2);
+        resolvedRoute = new ResolvedRoute(route, MAC3, CP1);
         updatedResolvedRoute = new ResolvedRoute(updatedRoute, MAC4, CP1);
 
-        verifyRouteRemoveThenAdd(route, updatedRoute, updatedResolvedRoute);
+        verifyRouteRemoveThenAdd(route, updatedRoute, resolvedRoute, updatedResolvedRoute);
     }
 
     /**
@@ -240,16 +243,18 @@
      *
      * @param original original route
      * @param updated updated route
+     * @param resolvedRoute resolved route before update
      * @param updatedResolvedRoute resolved route that is expected to be sent to
      *                             the route listener
      */
     private void verifyRouteRemoveThenAdd(Route original, Route updated,
+                                          ResolvedRoute resolvedRoute,
                                           ResolvedRoute updatedResolvedRoute) {
         // First add the original route
         addRoute(original);
 
         routeListener.event(new RouteEvent(RouteEvent.Type.ROUTE_REMOVED,
-                new ResolvedRoute(original, null, null)));
+                new ResolvedRoute(original, resolvedRoute.nextHopMac(), resolvedRoute.location())));
         expectLastCall().once();
         routeListener.event(new RouteEvent(RouteEvent.Type.ROUTE_ADDED, updatedResolvedRoute));
         expectLastCall().once();
@@ -267,15 +272,18 @@
      *
      * @param original original route
      * @param updated updated route
+     * @param resolvedRoute resolved route before update
      * @param updatedResolvedRoute resolved route that is expected to be sent to
      *                             the route listener
      */
     private void verifyRouteUpdated(Route original, Route updated,
+                                    ResolvedRoute resolvedRoute,
                                     ResolvedRoute updatedResolvedRoute) {
         // First add the original route
         addRoute(original);
 
-        routeListener.event(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED, updatedResolvedRoute));
+        routeListener.event(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED,
+                updatedResolvedRoute, resolvedRoute));
         expectLastCall().once();
 
         replay(routeListener);
@@ -291,12 +299,14 @@
     @Test
     public void testRouteDelete() {
         Route route = new Route(Route.Source.STATIC, V4_PREFIX1, V4_NEXT_HOP1);
+        ResolvedRoute removedResolvedRoute = new ResolvedRoute(route, MAC1, CP1);
 
-        verifyDelete(route);
+        verifyDelete(route, removedResolvedRoute);
 
         route = new Route(Route.Source.STATIC, V6_PREFIX1, V6_NEXT_HOP1);
+        removedResolvedRoute = new ResolvedRoute(route, MAC3, CP1);
 
-        verifyDelete(route);
+        verifyDelete(route, removedResolvedRoute);
     }
 
     /**
@@ -304,12 +314,13 @@
      * the route listener.
      *
      * @param route route to delete
+     * @param removedResolvedRoute the resolved route being removed
      */
-    private void verifyDelete(Route route) {
+    private void verifyDelete(Route route, ResolvedRoute removedResolvedRoute) {
         addRoute(route);
 
         RouteEvent withdrawRouteEvent = new RouteEvent(RouteEvent.Type.ROUTE_REMOVED,
-                new ResolvedRoute(route, null, null));
+                removedResolvedRoute);
 
         reset(routeListener);
         routeListener.event(withdrawRouteEvent);
diff --git a/incubator/store/src/main/java/org/onosproject/incubator/store/routing/impl/LocalRouteStore.java b/incubator/store/src/main/java/org/onosproject/incubator/store/routing/impl/LocalRouteStore.java
index a1e3120..70bca92 100644
--- a/incubator/store/src/main/java/org/onosproject/incubator/store/routing/impl/LocalRouteStore.java
+++ b/incubator/store/src/main/java/org/onosproject/incubator/store/routing/impl/LocalRouteStore.java
@@ -118,15 +118,16 @@
         Collection<Route> routes = getDefaultRouteTable(ip).getRoutesForNextHop(ip);
 
         if (!routes.isEmpty() && !nextHopData.equals(nextHops.get(ip))) {
-            NextHopData oldNextHop = nextHops.put(ip, nextHopData);
+            NextHopData oldNextHopData = nextHops.put(ip, nextHopData);
 
             for (Route route : routes) {
-                if (oldNextHop == null) {
+                if (oldNextHopData == null) {
                     notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_ADDED,
                             new ResolvedRoute(route, nextHopData.mac(), nextHopData.location())));
                 } else {
                     notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED,
-                            new ResolvedRoute(route, nextHopData.mac(), nextHopData.location())));
+                            new ResolvedRoute(route, nextHopData.mac(), nextHopData.location()),
+                            new ResolvedRoute(route, oldNextHopData.mac(), oldNextHopData.location())));
                 }
             }
         }
@@ -138,7 +139,7 @@
             Collection<Route> routes = getDefaultRouteTable(ip).getRoutesForNextHop(ip);
             for (Route route : routes) {
                 notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_REMOVED,
-                        new ResolvedRoute(route, null, null)));
+                        new ResolvedRoute(route, nextHopData.mac(), nextHopData.location())));
             }
         }
     }
@@ -202,6 +203,8 @@
          * @param route route to update
          */
         public void update(Route route) {
+            NextHopData oldNextHopData = null;
+
             synchronized (this) {
                 Route oldRoute = routes.put(route.prefix(), route);
                 routeTable.put(createBinaryString(route.prefix()), route);
@@ -214,7 +217,7 @@
                     reverseIndex.remove(oldRoute.nextHop(), oldRoute);
 
                     if (reverseIndex.get(oldRoute.nextHop()).isEmpty()) {
-                        nextHops.remove(oldRoute.nextHop());
+                        oldNextHopData = nextHops.remove(oldRoute.nextHop());
                     }
                 }
 
@@ -226,14 +229,20 @@
                 NextHopData nextHopData = nextHops.get(route.nextHop());
 
                 if (oldRoute != null && !oldRoute.nextHop().equals(route.nextHop())) {
+                    ResolvedRoute oldResolvedRoute =
+                            new ResolvedRoute(oldRoute,
+                                    (oldNextHopData == null) ? null : oldNextHopData.mac(),
+                                    (oldNextHopData == null) ? null : oldNextHopData.location());
+
                     if (nextHopData == null) {
                         // We don't know the new MAC address yet so delete the route
                         notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_REMOVED,
-                                new ResolvedRoute(oldRoute, null, null)));
+                                oldResolvedRoute));
                     } else {
                         // We know the new MAC address so update the route
                         notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_UPDATED,
-                                new ResolvedRoute(route, nextHopData.mac(), nextHopData.location())));
+                                new ResolvedRoute(route, nextHopData.mac(), nextHopData.location()),
+                                oldResolvedRoute));
                     }
                     return;
                 }
@@ -258,8 +267,10 @@
 
                 if (removed != null) {
                     reverseIndex.remove(removed.nextHop(), removed);
+                    NextHopData oldNextHopData = getNextHop(removed.nextHop());
                     notifyDelegate(new RouteEvent(RouteEvent.Type.ROUTE_REMOVED,
-                            new ResolvedRoute(route, null, null)));
+                            new ResolvedRoute(route, oldNextHopData.mac(),
+                                    oldNextHopData.location())));
                 }
             }
         }