Add support for reconfiguring interfaces in SDN-IP.

Change-Id: I2ea85d85432e661c3cbdca5e5a8b16678a242369
diff --git a/apps/sdnip/pom.xml b/apps/sdnip/pom.xml
index 2c42034..0dd4638 100644
--- a/apps/sdnip/pom.xml
+++ b/apps/sdnip/pom.xml
@@ -58,6 +58,14 @@
 
         <dependency>
             <groupId>org.onosproject</groupId>
+            <artifactId>onos-incubator-api</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+            <classifier>tests</classifier>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
             <artifactId>onos-app-routing</artifactId>
             <version>${project.version}</version>
         </dependency>
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/PeerConnectivityManager.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/PeerConnectivityManager.java
index b558c79..df9aee0 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/PeerConnectivityManager.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/PeerConnectivityManager.java
@@ -23,6 +23,8 @@
 import org.onlab.packet.TpPort;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceEvent;
+import org.onosproject.incubator.net.intf.InterfaceListener;
 import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.config.NetworkConfigEvent;
@@ -44,9 +46,11 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -77,14 +81,17 @@
     private final InternalNetworkConfigListener configListener
             = new InternalNetworkConfigListener();
 
+    private final InternalInterfaceListener interfaceListener
+            = new InternalInterfaceListener();
+
     /**
      * Creates a new PeerConnectivityManager.
      *
      * @param appId              the application ID
      * @param intentSynchronizer the intent synchronizer
-     * @param configService      the SDN-IP config service
-     * @param interfaceService   the interface service
+     * @param configService      the network config service
      * @param routerAppId        application ID
+     * @param interfaceService   the interface service
      */
     public PeerConnectivityManager(ApplicationId appId,
                                    IntentSynchronizationService intentSynchronizer,
@@ -105,6 +112,7 @@
      */
     public void start() {
         configService.addListener(configListener);
+        interfaceService.addListener(interfaceListener);
         setUpConnectivity();
     }
 
@@ -113,6 +121,7 @@
      */
     public void stop() {
         configService.removeListener(configListener);
+        interfaceService.removeListener(interfaceListener);
     }
 
     /**
@@ -122,14 +131,18 @@
     private void setUpConnectivity() {
         BgpConfig config = configService.getConfig(routerAppId, RoutingService.CONFIG_CLASS);
 
+        Set<BgpConfig.BgpSpeakerConfig> bgpSpeakers;
+
         if (config == null) {
-            log.warn("No BgpConfig found");
-            return;
+            log.warn("No BGP config available");
+            bgpSpeakers = Collections.emptySet();
+        } else {
+            bgpSpeakers = config.bgpSpeakers();
         }
 
         Map<Key, PointToPointIntent> existingIntents = new HashMap<>(peerIntents);
 
-        for (BgpConfig.BgpSpeakerConfig bgpSpeaker : config.bgpSpeakers()) {
+        for (BgpConfig.BgpSpeakerConfig bgpSpeaker : bgpSpeakers) {
             log.debug("Start to set up BGP paths for BGP speaker: {}",
                     bgpSpeaker);
 
@@ -409,4 +422,19 @@
         }
     }
 
+    private class InternalInterfaceListener implements InterfaceListener {
+        @Override
+        public void event(InterfaceEvent event) {
+            switch (event.type()) {
+            case INTERFACE_ADDED:
+            case INTERFACE_UPDATED:
+            case INTERFACE_REMOVED:
+                setUpConnectivity();
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
 }
diff --git a/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java b/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java
index a68d76c..d41d3a7 100644
--- a/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java
+++ b/apps/sdnip/src/main/java/org/onosproject/sdnip/SdnIpFib.java
@@ -17,6 +17,7 @@
 package org.onosproject.sdnip;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -30,6 +31,8 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceEvent;
+import org.onosproject.incubator.net.intf.InterfaceListener;
 import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.flow.DefaultTrafficSelector;
@@ -75,6 +78,7 @@
     protected RoutingService routingService;
 
     private final InternalFibListener fibListener = new InternalFibListener();
+    private final InternalInterfaceListener interfaceListener = new InternalInterfaceListener();
 
     private static final int PRIORITY_OFFSET = 100;
     private static final int PRIORITY_MULTIPLIER = 5;
@@ -90,12 +94,15 @@
     public void activate() {
         appId = coreService.getAppId(SdnIp.SDN_IP_APP);
 
+        interfaceService.addListener(interfaceListener);
+
         routingService.addFibListener(fibListener);
         routingService.start();
     }
 
     @Deactivate
     public void deactivate() {
+        interfaceService.removeListener(interfaceListener);
         // TODO remove listener
         routingService.stop();
     }
@@ -240,6 +247,51 @@
                 .build();
     }
 
+    private void updateInterface(Interface intf) {
+        synchronized (this) {
+            for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry : routeIntents.entrySet()) {
+                MultiPointToSinglePointIntent intent = entry.getValue();
+                Set<ConnectPoint> ingress = Sets.newHashSet(intent.ingressPoints());
+                ingress.add(intf.connectPoint());
+
+                MultiPointToSinglePointIntent newIntent =
+                        MultiPointToSinglePointIntent.builder(intent)
+                                .ingressPoints(ingress)
+                                .build();
+
+                routeIntents.put(entry.getKey(), newIntent);
+                intentSynchronizer.submit(newIntent);
+            }
+        }
+    }
+
+    private void removeInterface(Interface intf) {
+        synchronized (this) {
+            for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry : routeIntents.entrySet()) {
+                MultiPointToSinglePointIntent intent = entry.getValue();
+                if (intent.egressPoint().equals(intf.connectPoint())) {
+                    // This intent just lost its head. Remove it and let
+                    // higher layer routing reroute.
+                    intentSynchronizer.withdraw(routeIntents.remove(entry.getKey()));
+                } else {
+                    if (intent.ingressPoints().contains(intf.connectPoint())) {
+
+                        Set<ConnectPoint> ingress = Sets.newHashSet(intent.ingressPoints());
+                        ingress.remove(intf.connectPoint());
+
+                        MultiPointToSinglePointIntent newIntent =
+                                MultiPointToSinglePointIntent.builder(intent)
+                                .ingressPoints(ingress)
+                                .build();
+
+                        routeIntents.put(entry.getKey(), newIntent);
+                        intentSynchronizer.submit(newIntent);
+                    }
+                }
+            }
+        }
+    }
+
     private class InternalFibListener implements FibListener {
         @Override
         public void update(Collection<FibUpdate> updates, Collection<FibUpdate> withdraws) {
@@ -247,4 +299,23 @@
         }
     }
 
+    private class InternalInterfaceListener implements InterfaceListener {
+
+        @Override
+        public void event(InterfaceEvent event) {
+            switch (event.type()) {
+            case INTERFACE_ADDED:
+                updateInterface(event.subject());
+                break;
+            case INTERFACE_UPDATED:
+                break;
+            case INTERFACE_REMOVED:
+                removeInterface(event.subject());
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
 }
diff --git a/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java b/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
index 48f097e..54cb16b 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/PeerConnectivityManagerTest.java
@@ -17,7 +17,6 @@
 
 import com.google.common.collect.Sets;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.onlab.junit.TestUtils.TestUtilsException;
 import org.onlab.packet.Ethernet;
@@ -30,6 +29,7 @@
 import org.onosproject.TestApplicationId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceListener;
 import org.onosproject.incubator.net.intf.InterfaceService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
@@ -48,14 +48,10 @@
 import org.onosproject.routing.IntentSynchronizationService;
 import org.onosproject.routing.config.BgpConfig;
 import org.onosproject.routing.config.BgpPeer;
-import org.onosproject.routing.config.BgpSpeaker;
-import org.onosproject.routing.config.InterfaceAddress;
-import org.onosproject.routing.config.RoutingConfigurationService;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -81,7 +77,6 @@
 
     private PeerConnectivityManager peerConnectivityManager;
     private IntentSynchronizationService intentSynchronizer;
-    private RoutingConfigurationService routingConfig;
     private InterfaceService interfaceService;
     private NetworkConfigService networkConfigService;
 
@@ -104,8 +99,6 @@
     // Interfaces connected to BGP speakers
     private final ConnectPoint s1Eth100 =
             new ConnectPoint(deviceId1, PortNumber.portNumber(100));
-    private final ConnectPoint s2Eth100 =
-            new ConnectPoint(deviceId2, PortNumber.portNumber(100));
 
     // Interfaces connected to BGP peers
     private final ConnectPoint s1Eth1 =
@@ -119,8 +112,10 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        routingConfig = createMock(RoutingConfigurationService.class);
+
         interfaceService = createMock(InterfaceService.class);
+        interfaceService.addListener(anyObject(InterfaceListener.class));
+        expectLastCall().anyTimes();
         networkConfigService = createMock(NetworkConfigService.class);
         networkConfigService.addListener(anyObject(NetworkConfigListener.class));
         expectLastCall().anyTimes();
@@ -172,7 +167,7 @@
         InterfaceIpAddress ia1 =
             new InterfaceIpAddress(IpAddress.valueOf("192.168.10.101"),
                                    IpPrefix.valueOf("192.168.10.0/24"));
-        Interface intfsw1eth1 = new Interface(s1Eth1,
+        Interface intfsw1eth1 = new Interface(interfaceSw1Eth1, s1Eth1,
                 Collections.singletonList(ia1),
                 MacAddress.valueOf("00:00:00:00:00:01"),
                 VlanId.NONE);
@@ -182,7 +177,7 @@
         InterfaceIpAddress ia2 =
             new InterfaceIpAddress(IpAddress.valueOf("192.168.20.101"),
                                    IpPrefix.valueOf("192.168.20.0/24"));
-        Interface intfsw2eth1 = new Interface(s2Eth1,
+        Interface intfsw2eth1 = new Interface(interfaceSw2Eth1, s2Eth1,
                 Collections.singletonList(ia2),
                 MacAddress.valueOf("00:00:00:00:00:02"),
                 VlanId.NONE);
@@ -192,7 +187,7 @@
         InterfaceIpAddress ia3 =
                 new InterfaceIpAddress(IpAddress.valueOf("192.168.30.101"),
                         IpPrefix.valueOf("192.168.30.0/24"));
-        Interface intfsw2eth1intf2 = new Interface(s2Eth1,
+        Interface intfsw2eth1intf2 = new Interface(interfaceSw2Eth1intf2, s2Eth1,
                 Collections.singletonList(ia3),
                 MacAddress.valueOf("00:00:00:00:00:03"),
                 VlanId.NONE);
@@ -430,13 +425,11 @@
      * @throws TestUtilsException if exceptions when using TestUtils
      */
     private void initPeerConnectivity() throws TestUtilsException {
-        expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
         expect(bgpConfig.bgpSpeakers()).andReturn(bgpSpeakers).anyTimes();
         replay(bgpConfig);
         expect(networkConfigService.getConfig(APPID, BgpConfig.class))
                 .andReturn(bgpConfig).anyTimes();
         replay(networkConfigService);
-        replay(routingConfig);
         replay(interfaceService);
 
         intentSynchronizer = createMock(IntentSynchronizationService.class);
@@ -478,6 +471,8 @@
     @Test
     public void testNullInterfaces() {
         reset(interfaceService);
+        interfaceService.addListener(anyObject(InterfaceListener.class));
+        expectLastCall().anyTimes();
 
         expect(interfaceService.getInterfaces()).andReturn(
                 Sets.newHashSet()).anyTimes();
@@ -511,13 +506,9 @@
      */
     @Test
     public void testNullBgpSpeakers() {
-        reset(routingConfig);
         reset(bgpConfig);
-
         expect(bgpConfig.bgpSpeakers()).andReturn(Collections.emptySet()).anyTimes();
         replay(bgpConfig);
-        expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
-        replay(routingConfig);
 
         reset(intentSynchronizer);
         replay(intentSynchronizer);
@@ -537,21 +528,4 @@
         testConnectionSetup();
     }
 
-    /**
-     * Tests a corner case, when there is no Interface configured for one BGP
-     * speaker.
-     */
-    @Ignore
-    @Test
-    public void testNoSpeakerInterface() {
-        BgpSpeaker bgpSpeaker100 = new BgpSpeaker(
-                "bgpSpeaker100",
-                "00:00:00:00:00:00:01:00", 100,
-                "00:00:00:00:01:00");
-        List<InterfaceAddress> interfaceAddresses100 = new LinkedList<>();
-        interfaceAddresses100.add(new InterfaceAddress(dpid1, 1, "192.168.10.201"));
-        interfaceAddresses100.add(new InterfaceAddress(dpid2, 1, "192.168.20.201"));
-        bgpSpeaker100.setInterfaceAddresses(interfaceAddresses100);
-        testConnectionSetup();
-    }
 }
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 3fe048b..c23737c 100644
--- a/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java
+++ b/apps/sdnip/src/test/java/org/onosproject/sdnip/SdnIpFibTest.java
@@ -31,7 +31,10 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreServiceAdapter;
 import org.onosproject.incubator.net.intf.Interface;
+import org.onosproject.incubator.net.intf.InterfaceEvent;
+import org.onosproject.incubator.net.intf.InterfaceListener;
 import org.onosproject.incubator.net.intf.InterfaceService;
+import org.onosproject.incubator.net.intf.InterfaceServiceAdapter;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
@@ -48,18 +51,16 @@
 import org.onosproject.routing.FibUpdate;
 import org.onosproject.routing.IntentSynchronizationService;
 import org.onosproject.routing.RoutingServiceAdapter;
-import org.onosproject.routing.config.BgpPeer;
-import org.onosproject.routing.config.RoutingConfigurationService;
 
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
+import static org.easymock.EasyMock.anyObject;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.reset;
 import static org.easymock.EasyMock.verify;
@@ -70,7 +71,6 @@
  */
 public class SdnIpFibTest extends AbstractIntentTest {
 
-    private RoutingConfigurationService routingConfig;
     private InterfaceService interfaceService;
 
     private static final ConnectPoint SW1_ETH1 = new ConnectPoint(
@@ -89,6 +89,10 @@
             DeviceId.deviceId("of:0000000000000004"),
             PortNumber.portNumber(1));
 
+    private static final ConnectPoint SW5_ETH1 = new ConnectPoint(
+            DeviceId.deviceId("of:0000000000000005"),
+            PortNumber.portNumber(1));
+
     private SdnIpFib sdnipFib;
     private IntentSynchronizationService intentSynchronizer;
     private final Set<Interface> interfaces = Sets.newHashSet();
@@ -96,19 +100,19 @@
     private static final ApplicationId APPID = TestApplicationId.create("SDNIP");
 
     private FibListener fibListener;
+    private InterfaceListener interfaceListener;
 
     @Before
     public void setUp() throws Exception {
         super.setUp();
 
-        routingConfig = createMock(RoutingConfigurationService.class);
         interfaceService = createMock(InterfaceService.class);
+        interfaceService.addListener(anyObject(InterfaceListener.class));
+        expectLastCall().andDelegateTo(new InterfaceServiceDelegate());
 
         // These will set expectations on routingConfig and interfaceService
         setUpInterfaceService();
-        setUpBgpPeers();
 
-        replay(routingConfig);
         replay(interfaceService);
 
         intentSynchronizer = createMock(IntentSynchronizationService.class);
@@ -123,33 +127,6 @@
     }
 
     /**
-     * Sets up BGP peers in external networks.
-     */
-    private void setUpBgpPeers() {
-
-        Map<IpAddress, BgpPeer> peers = new HashMap<>();
-
-        String peerSw1Eth1 = "192.168.10.1";
-        peers.put(IpAddress.valueOf(peerSw1Eth1),
-                new BgpPeer("00:00:00:00:00:00:00:01", 1, peerSw1Eth1));
-
-        // Two BGP peers are connected to switch 2 port 1.
-        String peer1Sw2Eth1 = "192.168.20.1";
-        peers.put(IpAddress.valueOf(peer1Sw2Eth1),
-                new BgpPeer("00:00:00:00:00:00:00:02", 1, peer1Sw2Eth1));
-
-        String peer2Sw2Eth1 = "192.168.20.2";
-        peers.put(IpAddress.valueOf(peer2Sw2Eth1),
-                new BgpPeer("00:00:00:00:00:00:00:02", 1, peer2Sw2Eth1));
-
-        String peer1Sw4Eth1 = "192.168.40.1";
-        peers.put(IpAddress.valueOf(peer1Sw4Eth1),
-                new BgpPeer("00:00:00:00:00:00:00:04", 1, peer1Sw4Eth1));
-
-        expect(routingConfig.getBgpPeers()).andReturn(peers).anyTimes();
-    }
-
-    /**
      * Sets up InterfaceService.
      */
     private void setUpInterfaceService() {
@@ -157,7 +134,7 @@
         interfaceIpAddresses1.add(new InterfaceIpAddress(
                 IpAddress.valueOf("192.168.10.101"),
                 IpPrefix.valueOf("192.168.10.0/24")));
-        Interface sw1Eth1 = new Interface(SW1_ETH1,
+        Interface sw1Eth1 = new Interface("sw1-eth1", SW1_ETH1,
                 interfaceIpAddresses1, MacAddress.valueOf("00:00:00:00:00:01"),
                 VlanId.NONE);
         interfaces.add(sw1Eth1);
@@ -166,7 +143,7 @@
         interfaceIpAddresses2.add(
                 new InterfaceIpAddress(IpAddress.valueOf("192.168.20.101"),
                         IpPrefix.valueOf("192.168.20.0/24")));
-        Interface sw2Eth1 = new Interface(SW2_ETH1,
+        Interface sw2Eth1 = new Interface("sw2-eth1", SW2_ETH1,
                 interfaceIpAddresses2, MacAddress.valueOf("00:00:00:00:00:02"),
                 VlanId.NONE);
         interfaces.add(sw2Eth1);
@@ -175,7 +152,7 @@
         interfaceIpAddresses3.add(
                 new InterfaceIpAddress(IpAddress.valueOf("192.168.30.101"),
                         IpPrefix.valueOf("192.168.30.0/24")));
-        Interface sw3Eth1 = new Interface(SW3_ETH1,
+        Interface sw3Eth1 = new Interface("sw3-eth1", SW3_ETH1,
                 interfaceIpAddresses3, MacAddress.valueOf("00:00:00:00:00:03"),
                 VlanId.NONE);
         interfaces.add(sw3Eth1);
@@ -183,7 +160,7 @@
         InterfaceIpAddress interfaceIpAddress4 =
                 new InterfaceIpAddress(IpAddress.valueOf("192.168.40.101"),
                         IpPrefix.valueOf("192.168.40.0/24"));
-        Interface sw4Eth1 = new Interface(SW4_ETH1,
+        Interface sw4Eth1 = new Interface("sw4-eth1", SW4_ETH1,
                 Lists.newArrayList(interfaceIpAddress4),
                 MacAddress.valueOf("00:00:00:00:00:04"),
                 VlanId.vlanId((short) 1));
@@ -428,6 +405,100 @@
         verify(intentSynchronizer);
     }
 
+    @Test
+    public void testAddInterface() {
+        testFibAdd();
+
+        IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
+
+        // Construct the existing MultiPointToSinglePoint intent
+        TrafficSelector.Builder selectorBuilder =
+                DefaultTrafficSelector.builder();
+        selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(prefix);
+
+        TrafficTreatment.Builder treatmentBuilder =
+                DefaultTrafficTreatment.builder();
+        treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:01"));
+
+        Set<ConnectPoint> ingressPoints = new HashSet<>();
+        ingressPoints.add(SW2_ETH1);
+        ingressPoints.add(SW3_ETH1);
+        ingressPoints.add(SW4_ETH1);
+        ingressPoints.add(SW5_ETH1);
+
+        MultiPointToSinglePointIntent addedIntent =
+                MultiPointToSinglePointIntent.builder()
+                        .appId(APPID)
+                        .key(Key.of(prefix.toString(), APPID))
+                        .selector(selectorBuilder.build())
+                        .treatment(treatmentBuilder.build())
+                        .ingressPoints(ingressPoints)
+                        .egressPoint(SW1_ETH1)
+                        .constraints(SdnIpFib.CONSTRAINTS)
+                        .build();
+
+        reset(intentSynchronizer);
+
+        intentSynchronizer.submit(eqExceptId(addedIntent));
+        expectLastCall().once();
+
+        replay(intentSynchronizer);
+
+        Interface intf = new Interface("newintf", SW5_ETH1,
+                Collections.singletonList(InterfaceIpAddress.valueOf("192.168.50.101/24")),
+                MacAddress.valueOf("00:00:00:00:00:02"), VlanId.NONE);
+        InterfaceEvent intfEvent = new InterfaceEvent(InterfaceEvent.Type.INTERFACE_ADDED, intf);
+        interfaceListener.event(intfEvent);
+
+        verify(intentSynchronizer);
+    }
+
+    @Test
+    public void testRemoveInterface() {
+        testFibAdd();
+
+        IpPrefix prefix = Ip4Prefix.valueOf("1.1.1.0/24");
+
+        // Construct the existing MultiPointToSinglePoint intent
+        TrafficSelector.Builder selectorBuilder =
+                DefaultTrafficSelector.builder();
+        selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(prefix);
+
+        TrafficTreatment.Builder treatmentBuilder =
+                DefaultTrafficTreatment.builder();
+        treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:01"));
+
+        Set<ConnectPoint> ingressPoints = new HashSet<>();
+        ingressPoints.add(SW2_ETH1);
+        ingressPoints.add(SW3_ETH1);
+
+        MultiPointToSinglePointIntent addedIntent =
+                MultiPointToSinglePointIntent.builder()
+                        .appId(APPID)
+                        .key(Key.of(prefix.toString(), APPID))
+                        .selector(selectorBuilder.build())
+                        .treatment(treatmentBuilder.build())
+                        .ingressPoints(ingressPoints)
+                        .egressPoint(SW1_ETH1)
+                        .constraints(SdnIpFib.CONSTRAINTS)
+                        .build();
+
+        reset(intentSynchronizer);
+
+        intentSynchronizer.submit(eqExceptId(addedIntent));
+        expectLastCall().once();
+
+        replay(intentSynchronizer);
+
+        Interface intf = new Interface("newintf", SW4_ETH1,
+                Collections.singletonList(InterfaceIpAddress.valueOf("192.168.50.101/24")),
+                MacAddress.valueOf("00:00:00:00:00:02"), VlanId.NONE);
+        InterfaceEvent intfEvent = new InterfaceEvent(InterfaceEvent.Type.INTERFACE_REMOVED, intf);
+        interfaceListener.event(intfEvent);
+
+        verify(intentSynchronizer);
+    }
+
     private class TestCoreService extends CoreServiceAdapter {
         @Override
         public ApplicationId getAppId(String name) {
@@ -442,4 +513,12 @@
             SdnIpFibTest.this.fibListener = fibListener;
         }
     }
+
+    private class InterfaceServiceDelegate extends InterfaceServiceAdapter {
+
+        @Override
+        public void addListener(InterfaceListener listener) {
+            SdnIpFibTest.this.interfaceListener = listener;
+        }
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/intent/ConnectivityIntent.java b/core/api/src/main/java/org/onosproject/net/intent/ConnectivityIntent.java
index 7caee3e..13f07a2 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/ConnectivityIntent.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/ConnectivityIntent.java
@@ -96,6 +96,26 @@
         protected TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
         protected List<Constraint> constraints = ImmutableList.of();
 
+        /**
+         * Creates a new empty builder.
+         */
+        protected Builder() {
+        }
+
+        /**
+         * Creates a new builder pre-populated with the information in the given
+         * intent.
+         *
+         * @param intent initial intent
+         */
+        protected Builder(ConnectivityIntent intent) {
+            super(intent);
+
+            this.selector(intent.selector())
+                    .treatment(intent.treatment())
+                    .constraints(intent.constraints());
+        }
+
         @Override
         public Builder appId(ApplicationId appId) {
             return (Builder) super.appId(appId);
@@ -111,7 +131,6 @@
             return (Builder) super.priority(priority);
         }
 
-
         /**
          * Sets the traffic selector for the intent that will be built.
          *
@@ -146,7 +165,6 @@
         }
     }
 
-
     /**
      * Returns the match specifying the type of traffic.
      *
diff --git a/core/api/src/main/java/org/onosproject/net/intent/Intent.java b/core/api/src/main/java/org/onosproject/net/intent/Intent.java
index 077fd89..d27d057 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/Intent.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/Intent.java
@@ -15,14 +15,14 @@
  */
 package org.onosproject.net.intent;
 
-import java.util.Collection;
-import java.util.Objects;
-
 import com.google.common.annotations.Beta;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.IdGenerator;
 import org.onosproject.net.NetworkResource;
 
+import java.util.Collection;
+import java.util.Objects;
+
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
@@ -91,6 +91,24 @@
         protected int priority = Intent.DEFAULT_INTENT_PRIORITY;
 
         /**
+         * Creates a new empty builder.
+         */
+        protected Builder() {
+        }
+
+        /**
+         * Creates a new builder pre-populated with the information in the given
+         * intent.
+         *
+         * @param intent initial intent
+         */
+        protected Builder(Intent intent) {
+            this.appId(intent.appId())
+                    .key(intent.key())
+                    .priority(intent.priority());
+        }
+
+        /**
          * Sets the application id for the intent that will be built.
          *
          * @param appId application id to use for built intent
diff --git a/core/api/src/main/java/org/onosproject/net/intent/MultiPointToSinglePointIntent.java b/core/api/src/main/java/org/onosproject/net/intent/MultiPointToSinglePointIntent.java
index ac6061c..44d407e 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/MultiPointToSinglePointIntent.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/MultiPointToSinglePointIntent.java
@@ -100,6 +100,17 @@
     }
 
     /**
+     * Creates a new builder pre-populated with the information in the given
+     * intent.
+     *
+     * @param intent initial intent
+     * @return intent builder
+     */
+    public static Builder builder(MultiPointToSinglePointIntent intent) {
+        return new Builder(intent);
+    }
+
+    /**
      * Builder of a multi point to single point intent.
      */
     public static final class Builder extends ConnectivityIntent.Builder {
@@ -110,6 +121,19 @@
             // Hide constructor
         }
 
+        /**
+         * Creates a new builder pre-populated with information from the given
+         * intent.
+         *
+         * @param intent initial intent
+         */
+        protected Builder(MultiPointToSinglePointIntent intent) {
+            super(intent);
+
+            this.ingressPoints(intent.ingressPoints())
+                    .egressPoint(intent.egressPoint());
+        }
+
         @Override
         public Builder appId(ApplicationId appId) {
             return (Builder) super.appId(appId);
diff --git a/incubator/api/src/test/java/org/onosproject/incubator/net/intf/InterfaceServiceAdapter.java b/incubator/api/src/test/java/org/onosproject/incubator/net/intf/InterfaceServiceAdapter.java
new file mode 100644
index 0000000..235a964
--- /dev/null
+++ b/incubator/api/src/test/java/org/onosproject/incubator/net/intf/InterfaceServiceAdapter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+package org.onosproject.incubator.net.intf;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+
+import java.util.Set;
+
+/**
+ * Interface service adapter class for tests.
+ */
+public class InterfaceServiceAdapter implements InterfaceService {
+    @Override
+    public Set<Interface> getInterfaces() {
+        return null;
+    }
+
+    @Override
+    public Interface getInterfaceByName(ConnectPoint connectPoint, String name) {
+        return null;
+    }
+
+    @Override
+    public Set<Interface> getInterfacesByPort(ConnectPoint port) {
+        return null;
+    }
+
+    @Override
+    public Set<Interface> getInterfacesByIp(IpAddress ip) {
+        return null;
+    }
+
+    @Override
+    public Set<Interface> getInterfacesByVlan(VlanId vlan) {
+        return null;
+    }
+
+    @Override
+    public Interface getMatchingInterface(IpAddress ip) {
+        return null;
+    }
+
+    @Override
+    public void addListener(InterfaceListener listener) {
+
+    }
+
+    @Override
+    public void removeListener(InterfaceListener listener) {
+
+    }
+}