Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/PeerConnectivity.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/PeerConnectivityManager.java
similarity index 98%
rename from apps/sdnip/src/main/java/org/onlab/onos/sdnip/PeerConnectivity.java
rename to apps/sdnip/src/main/java/org/onlab/onos/sdnip/PeerConnectivityManager.java
index 31cd971..3917f5a 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/PeerConnectivity.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/PeerConnectivityManager.java
@@ -25,10 +25,10 @@
 /**
  * Manages the connectivity requirements between peers.
  */
-public class PeerConnectivity {
+public class PeerConnectivityManager {
 
     private static final Logger log = LoggerFactory.getLogger(
-            PeerConnectivity.class);
+            PeerConnectivityManager.class);
 
     // TODO these shouldn't be defined here
     private static final short BGP_PORT = 179;
@@ -41,7 +41,7 @@
     // TODO this sucks.
     private int intentId = 0;
 
-    public PeerConnectivity(SdnIpConfigService configInfoService,
+    public PeerConnectivityManager(SdnIpConfigService configInfoService,
             InterfaceService interfaceService, IntentService intentService) {
         this.configInfoService = configInfoService;
         this.interfaceService = interfaceService;
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java
index e7ed601..3cc7708 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java
@@ -36,7 +36,7 @@
     protected HostService hostService;
 
     private SdnIpConfigReader config;
-    private PeerConnectivity peerConnectivity;
+    private PeerConnectivityManager peerConnectivity;
     private Router router;
     private BgpSessionManager bgpSessionManager;
 
@@ -49,7 +49,7 @@
 
         InterfaceService interfaceService = new HostServiceBasedInterfaceService(hostService);
 
-        peerConnectivity = new PeerConnectivity(config, interfaceService, intentService);
+        peerConnectivity = new PeerConnectivityManager(config, interfaceService, intentService);
         peerConnectivity.start();
 
         router = new Router(intentService, hostService, config, interfaceService);
@@ -79,4 +79,8 @@
     public Collection<RouteEntry> getRoutes() {
         return router.getRoutes();
     }
+
+    static String dpidToUri(String dpid) {
+        return "of:" + dpid.replace(":", "");
+    }
 }
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/PeerConnectivityManagerTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/PeerConnectivityManagerTest.java
new file mode 100644
index 0000000..0ecea00
--- /dev/null
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/PeerConnectivityManagerTest.java
@@ -0,0 +1,711 @@
+package org.onlab.onos.sdnip;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reportMatcher;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+
+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 org.easymock.IArgumentMatcher;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.flow.DefaultTrafficSelector;
+import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.intent.IntentId;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.PointToPointIntent;
+import org.onlab.onos.sdnip.bgp.BgpConstants;
+import org.onlab.onos.sdnip.config.BgpPeer;
+import org.onlab.onos.sdnip.config.BgpSpeaker;
+import org.onlab.onos.sdnip.config.Interface;
+import org.onlab.onos.sdnip.config.InterfaceAddress;
+import org.onlab.onos.sdnip.config.SdnIpConfigService;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+
+import com.google.common.collect.Sets;
+
+/**
+ * Unit tests for PeerConnectivityManager interface.
+ */
+public class PeerConnectivityManagerTest {
+
+    private PeerConnectivityManager peerConnectivityManager;
+    private IntentService intentService;
+    private SdnIpConfigService configInfoService;
+    private InterfaceService interfaceService;
+
+    private Map<String, BgpSpeaker> bgpSpeakers;
+    private Map<String, Interface> interfaces;
+    private Map<IpAddress, BgpPeer> peers;
+
+    private Map<String, BgpSpeaker> configuredBgpSpeakers;
+    private Map<String, Interface> configuredInterfaces;
+    private Map<IpAddress, BgpPeer> configuredPeers;
+    private List<PointToPointIntent> intentList;
+
+    private final String dpid1 = "00:00:00:00:00:00:00:01";
+    private final String dpid2 = "00:00:00:00:00:00:00:02";
+
+    private final DeviceId deviceId1 =
+            DeviceId.deviceId(SdnIp.dpidToUri(dpid1));
+    private final DeviceId deviceId2 =
+            DeviceId.deviceId(SdnIp.dpidToUri(dpid2));
+
+    // 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 =
+            new ConnectPoint(deviceId1, PortNumber.portNumber(1));
+    private final ConnectPoint s2Eth1 =
+            new ConnectPoint(deviceId2, PortNumber.portNumber(1));
+
+    // We don't compare the intent ID so all expected intents can use the same ID
+    private final IntentId testIntentId = new IntentId(0);
+
+    private final TrafficTreatment noTreatment =
+            DefaultTrafficTreatment.builder().build();
+
+    @Before
+    public void setUp() throws Exception {
+        bgpSpeakers = Collections.unmodifiableMap(setUpBgpSpeakers());
+        interfaces = Collections.unmodifiableMap(setUpInterfaces());
+        peers = Collections.unmodifiableMap(setUpPeers());
+
+        initPeerConnectivity();
+        intentList = setUpIntentList();
+    }
+
+    /**
+     * Sets up BGP speakers.
+     *
+     * @return configured BGP speakers as a map from speaker name to speaker
+     */
+    private Map<String, BgpSpeaker> setUpBgpSpeakers() {
+
+        configuredBgpSpeakers = new HashMap<>();
+
+        BgpSpeaker bgpSpeaker1 = new BgpSpeaker(
+                "bgpSpeaker1",
+                "00:00:00:00:00:00:00:01", 100,
+                "00:00:00:00:00:01");
+        List<InterfaceAddress> interfaceAddresses1 =
+                new LinkedList<InterfaceAddress>();
+        interfaceAddresses1.add(new InterfaceAddress(dpid1, 1, "192.168.10.101"));
+        interfaceAddresses1.add(new InterfaceAddress(dpid2, 1, "192.168.20.101"));
+        bgpSpeaker1.setInterfaceAddresses(interfaceAddresses1);
+        configuredBgpSpeakers.put(bgpSpeaker1.name(), bgpSpeaker1);
+
+        // BGP speaker2 is attached to the same switch port with speaker1
+        BgpSpeaker bgpSpeaker2 = new BgpSpeaker(
+                "bgpSpeaker2",
+                "00:00:00:00:00:00:00:01", 100,
+                "00:00:00:00:00:02");
+        List<InterfaceAddress> interfaceAddresses2 =
+                new LinkedList<InterfaceAddress>();
+        interfaceAddresses2.add(new InterfaceAddress(dpid1, 1, "192.168.10.102"));
+        interfaceAddresses2.add(new InterfaceAddress(dpid2, 1, "192.168.20.102"));
+        bgpSpeaker2.setInterfaceAddresses(interfaceAddresses2);
+        configuredBgpSpeakers.put(bgpSpeaker2.name(), bgpSpeaker2);
+
+        BgpSpeaker bgpSpeaker3 = new BgpSpeaker(
+                "bgpSpeaker3",
+                "00:00:00:00:00:00:00:02", 100,
+                "00:00:00:00:00:03");
+        List<InterfaceAddress> interfaceAddresses3 =
+                new LinkedList<InterfaceAddress>();
+        interfaceAddresses3.add(new InterfaceAddress(dpid1, 1, "192.168.10.103"));
+        interfaceAddresses3.add(new InterfaceAddress(dpid2, 1, "192.168.20.103"));
+        bgpSpeaker3.setInterfaceAddresses(interfaceAddresses3);
+        configuredBgpSpeakers.put(bgpSpeaker3.name(), bgpSpeaker3);
+
+        return configuredBgpSpeakers;
+    }
+
+    /**
+     * Sets up logical interfaces, which emulate the configured interfaces
+     * in SDN-IP application.
+     *
+     * @return configured interfaces as a MAP from Interface name to Interface
+     */
+    private Map<String, Interface> setUpInterfaces() {
+
+        configuredInterfaces = new HashMap<>();
+
+        String interfaceSw1Eth1 = "s1-eth1";
+        Interface intfsw1eth1 = new Interface(s1Eth1,
+                Collections.singleton(IpPrefix.valueOf("192.168.10.0/24")),
+                MacAddress.valueOf("00:00:00:00:00:01"));
+
+        configuredInterfaces.put(interfaceSw1Eth1, intfsw1eth1);
+        String interfaceSw2Eth1 = "s2-eth1";
+        Interface intfsw2eth1 = new Interface(s2Eth1,
+                Collections.singleton(IpPrefix.valueOf("192.168.20.0/24")),
+                MacAddress.valueOf("00:00:00:00:00:02"));
+        configuredInterfaces.put(interfaceSw2Eth1, intfsw2eth1);
+
+        interfaceService = createMock(InterfaceService.class);
+
+        expect(interfaceService.getInterface(s1Eth1))
+                .andReturn(intfsw1eth1).anyTimes();
+        expect(interfaceService.getInterface(s2Eth1))
+                .andReturn(intfsw2eth1).anyTimes();
+
+        // Non-existent interface used during one of the tests
+        expect(interfaceService.getInterface(new ConnectPoint(
+                    DeviceId.deviceId(SdnIp.dpidToUri("00:00:00:00:00:00:01:00")),
+                    PortNumber.portNumber(1))))
+                    .andReturn(null).anyTimes();
+
+        expect(interfaceService.getInterfaces()).andReturn(
+                Sets.newHashSet(configuredInterfaces.values())).anyTimes();
+        replay(interfaceService);
+
+        return configuredInterfaces;
+    }
+
+    /**
+     * Sets up BGP daemon peers.
+     *
+     * @return configured BGP peers as a MAP from peer IP address to BgpPeer
+     */
+    private Map<IpAddress, BgpPeer> setUpPeers() {
+
+        configuredPeers = new HashMap<>();
+
+        String peerSw1Eth1 = "192.168.10.1";
+        configuredPeers.put(IpAddress.valueOf(peerSw1Eth1),
+                new BgpPeer(dpid1, 1, peerSw1Eth1));
+
+        // Two BGP peers are connected to switch 2 port 1.
+        String peer1Sw2Eth1 = "192.168.20.1";
+        configuredPeers.put(IpAddress.valueOf(peer1Sw2Eth1),
+                new BgpPeer(dpid2, 1, peer1Sw2Eth1));
+
+        String peer2Sw2Eth1 = "192.168.20.2";
+        configuredPeers.put(IpAddress.valueOf(peer2Sw2Eth1),
+                new BgpPeer(dpid2, 1, peer2Sw2Eth1));
+
+        return configuredPeers;
+    }
+
+    /**
+     * Sets up expected point to point intent list.
+     *
+     * @return point to point intent list
+     */
+    private List<PointToPointIntent> setUpIntentList() {
+
+        intentList = new ArrayList<PointToPointIntent>();
+
+        setUpBgpIntents();
+        setUpIcmpIntents();
+
+        return intentList;
+
+    }
+
+    /**
+     * Constructs a BGP intent and put it into the intentList.
+     * <p/>
+     * The purpose of this method is too simplify the setUpBgpIntents() method,
+     * and to make the setUpBgpIntents() easy to read.
+     *
+     * @param srcPrefix source IP prefix to match
+     * @param dstPrefix destination IP prefix to match
+     * @param srcTcpPort source TCP port to match
+     * @param dstTcpPort destination TCP port to match
+     * @param srcConnectPoint source connect point for PointToPointIntent
+     * @param dstConnectPoint destination connect point for PointToPointIntent
+     */
+    private void bgpPathintentConstructor(String srcPrefix, String dstPrefix,
+            Short srcTcpPort, Short dstTcpPort,
+            ConnectPoint srcConnectPoint, ConnectPoint dstConnectPoint) {
+
+        TrafficSelector.Builder builder = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_TCP)
+                .matchIPSrc(IpPrefix.valueOf(srcPrefix))
+                .matchIPDst(IpPrefix.valueOf(dstPrefix));
+
+        if (srcTcpPort != null) {
+            builder.matchTcpSrc(srcTcpPort);
+        }
+        if (dstTcpPort != null) {
+            builder.matchTcpDst(dstTcpPort);
+        }
+
+        PointToPointIntent intent = new PointToPointIntent(
+                testIntentId, builder.build(), noTreatment,
+                srcConnectPoint, dstConnectPoint);
+
+        intentList.add(intent);
+    }
+
+    /**
+     * Sets up intents for BGP paths.
+     */
+    private void setUpBgpIntents() {
+
+        Short bgpPort = Short.valueOf((short) BgpConstants.BGP_PORT);
+
+        // Start to build intents between BGP speaker1 and BGP peer1
+        bgpPathintentConstructor(
+                "192.168.10.101/32", "192.168.10.1/32", null, bgpPort,
+                s1Eth100, s1Eth1);
+        bgpPathintentConstructor(
+                "192.168.10.101/32", "192.168.10.1/32", bgpPort, null,
+                s1Eth100, s1Eth1);
+
+        bgpPathintentConstructor(
+                "192.168.10.1/32", "192.168.10.101/32", null, bgpPort,
+                s1Eth1, s1Eth100);
+        bgpPathintentConstructor(
+                "192.168.10.1/32", "192.168.10.101/32", bgpPort, null,
+                s1Eth1, s1Eth100);
+
+        // Start to build intents between BGP speaker1 and BGP peer2
+        bgpPathintentConstructor(
+                "192.168.20.101/32", "192.168.20.1/32", null, bgpPort,
+                s1Eth100, s2Eth1);
+        bgpPathintentConstructor(
+                "192.168.20.101/32", "192.168.20.1/32", bgpPort, null,
+                s1Eth100, s2Eth1);
+
+        bgpPathintentConstructor(
+                "192.168.20.1/32", "192.168.20.101/32", null, bgpPort,
+                s2Eth1, s1Eth100);
+        bgpPathintentConstructor(
+                "192.168.20.1/32", "192.168.20.101/32", bgpPort, null,
+                s2Eth1, s1Eth100);
+
+        // Start to build intents between BGP speaker1 and BGP peer3
+        bgpPathintentConstructor(
+                "192.168.20.101/32", "192.168.20.2/32", null, bgpPort,
+                s1Eth100, s2Eth1);
+        bgpPathintentConstructor(
+                "192.168.20.101/32", "192.168.20.2/32", bgpPort, null,
+                s1Eth100, s2Eth1);
+
+        bgpPathintentConstructor(
+                "192.168.20.2/32", "192.168.20.101/32", null, bgpPort,
+                s2Eth1, s1Eth100);
+        bgpPathintentConstructor(
+                "192.168.20.2/32", "192.168.20.101/32", bgpPort, null,
+                s2Eth1, s1Eth100);
+
+        //
+        // Start to build intents between BGP speaker2 and BGP peer1
+        bgpPathintentConstructor(
+                "192.168.10.102/32", "192.168.10.1/32", null, bgpPort,
+                s1Eth100, s1Eth1);
+        bgpPathintentConstructor(
+                "192.168.10.102/32", "192.168.10.1/32", bgpPort, null,
+                s1Eth100, s1Eth1);
+
+        bgpPathintentConstructor(
+                "192.168.10.1/32", "192.168.10.102/32", null, bgpPort,
+                s1Eth1, s1Eth100);
+        bgpPathintentConstructor(
+                "192.168.10.1/32", "192.168.10.102/32", bgpPort, null,
+                s1Eth1, s1Eth100);
+        // Start to build intents between BGP speaker2 and BGP peer2
+        bgpPathintentConstructor(
+                "192.168.20.102/32", "192.168.20.1/32", null, bgpPort,
+                s1Eth100, s2Eth1);
+        bgpPathintentConstructor(
+                "192.168.20.102/32", "192.168.20.1/32", bgpPort, null,
+                s1Eth100, s2Eth1);
+
+        bgpPathintentConstructor(
+                "192.168.20.1/32", "192.168.20.102/32", null, bgpPort,
+                s2Eth1, s1Eth100);
+        bgpPathintentConstructor(
+                "192.168.20.1/32", "192.168.20.102/32", bgpPort, null,
+                s2Eth1, s1Eth100);
+
+        // Start to build intents between BGP speaker2 and BGP peer3
+        bgpPathintentConstructor(
+                "192.168.20.102/32", "192.168.20.2/32", null, bgpPort,
+                s1Eth100, s2Eth1);
+        bgpPathintentConstructor(
+                "192.168.20.102/32", "192.168.20.2/32", bgpPort, null,
+                s1Eth100, s2Eth1);
+
+        bgpPathintentConstructor(
+                "192.168.20.2/32", "192.168.20.102/32", null, bgpPort,
+                s2Eth1, s1Eth100);
+        bgpPathintentConstructor(
+                "192.168.20.2/32", "192.168.20.102/32", bgpPort, null,
+                s2Eth1, s1Eth100);
+
+        //
+        // Start to build intents between BGP speaker3 and BGP peer1
+        bgpPathintentConstructor(
+                "192.168.10.103/32", "192.168.10.1/32", null, bgpPort,
+                s2Eth100, s1Eth1);
+        bgpPathintentConstructor(
+                "192.168.10.103/32", "192.168.10.1/32", bgpPort, null,
+                s2Eth100, s1Eth1);
+
+        bgpPathintentConstructor(
+                "192.168.10.1/32", "192.168.10.103/32", null, bgpPort,
+                s1Eth1, s2Eth100);
+        bgpPathintentConstructor(
+                "192.168.10.1/32", "192.168.10.103/32", bgpPort, null,
+                s1Eth1, s2Eth100);
+
+        // Start to build intents between BGP speaker3 and BGP peer2
+        bgpPathintentConstructor(
+                "192.168.20.103/32", "192.168.20.1/32", null, bgpPort,
+                s2Eth100, s2Eth1);
+        bgpPathintentConstructor(
+                "192.168.20.103/32", "192.168.20.1/32", bgpPort, null,
+                s2Eth100, s2Eth1);
+
+        bgpPathintentConstructor(
+                "192.168.20.1/32", "192.168.20.103/32", null, bgpPort,
+                s2Eth1, s2Eth100);
+        bgpPathintentConstructor(
+                "192.168.20.1/32", "192.168.20.103/32", bgpPort, null,
+                s2Eth1, s2Eth100);
+
+        // Start to build intents between BGP speaker3 and BGP peer3
+        bgpPathintentConstructor(
+                "192.168.20.103/32", "192.168.20.2/32", null, bgpPort,
+                s2Eth100, s2Eth1);
+        bgpPathintentConstructor(
+                "192.168.20.103/32", "192.168.20.2/32", bgpPort, null,
+                s2Eth100, s2Eth1);
+
+        bgpPathintentConstructor(
+                "192.168.20.2/32", "192.168.20.103/32", null, bgpPort,
+                s2Eth1, s2Eth100);
+        bgpPathintentConstructor(
+                "192.168.20.2/32", "192.168.20.103/32", bgpPort, null,
+                s2Eth1, s2Eth100);
+    }
+
+    /**
+     * Constructs a BGP intent and put it into the intentList.
+     * <p/>
+     * The purpose of this method is too simplify the setUpBgpIntents() method,
+     * and to make the setUpBgpIntents() easy to read.
+     *
+     * @param srcPrefix source IP prefix to match
+     * @param dstPrefix destination IP prefix to match
+     * @param srcConnectPoint source connect point for PointToPointIntent
+     * @param dstConnectPoint destination connect point for PointToPointIntent
+     */
+    private void icmpPathintentConstructor(String srcPrefix, String dstPrefix,
+            ConnectPoint srcConnectPoint, ConnectPoint dstConnectPoint) {
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_ICMP)
+                .matchIPSrc(IpPrefix.valueOf(srcPrefix))
+                .matchIPDst(IpPrefix.valueOf(dstPrefix))
+                .build();
+
+        PointToPointIntent intent = new PointToPointIntent(
+                testIntentId, selector, noTreatment,
+                srcConnectPoint, dstConnectPoint);
+
+        intentList.add(intent);
+    }
+
+    /**
+     * Sets up intents for ICMP paths.
+     */
+    private void setUpIcmpIntents() {
+
+        // Start to build intents between BGP speaker1 and BGP peer1
+        icmpPathintentConstructor(
+                "192.168.10.101/32", "192.168.10.1/32", s1Eth100, s1Eth1);
+        icmpPathintentConstructor(
+                "192.168.10.1/32", "192.168.10.101/32", s1Eth1, s1Eth100);
+
+        // Start to build intents between BGP speaker1 and BGP peer2
+        icmpPathintentConstructor(
+                "192.168.20.101/32", "192.168.20.1/32", s1Eth100, s2Eth1);
+        icmpPathintentConstructor(
+                "192.168.20.1/32", "192.168.20.101/32", s2Eth1, s1Eth100);
+
+        // Start to build intents between BGP speaker1 and BGP peer3
+        icmpPathintentConstructor(
+                "192.168.20.101/32", "192.168.20.2/32", s1Eth100, s2Eth1);
+        icmpPathintentConstructor(
+                "192.168.20.2/32", "192.168.20.101/32", s2Eth1, s1Eth100);
+
+        //
+        // Start to build intents between BGP speaker2 and BGP peer1
+        icmpPathintentConstructor(
+                "192.168.10.102/32", "192.168.10.1/32", s1Eth100, s1Eth1);
+        icmpPathintentConstructor(
+                "192.168.10.1/32", "192.168.10.102/32", s1Eth1, s1Eth100);
+
+        // Start to build intents between BGP speaker2 and BGP peer2
+        icmpPathintentConstructor(
+                "192.168.20.102/32", "192.168.20.1/32", s1Eth100, s2Eth1);
+        icmpPathintentConstructor(
+                "192.168.20.1/32", "192.168.20.102/32", s2Eth1, s1Eth100);
+
+        // Start to build intents between BGP speaker2 and BGP peer3
+        icmpPathintentConstructor(
+                "192.168.20.102/32", "192.168.20.2/32", s1Eth100, s2Eth1);
+        icmpPathintentConstructor(
+                "192.168.20.2/32", "192.168.20.102/32", s2Eth1, s1Eth100);
+
+        //
+        // Start to build intents between BGP speaker3 and BGP peer1
+        icmpPathintentConstructor(
+                "192.168.10.103/32", "192.168.10.1/32", s2Eth100, s1Eth1);
+        icmpPathintentConstructor(
+                "192.168.10.1/32", "192.168.10.103/32", s1Eth1, s2Eth100);
+
+        // Start to build intents between BGP speaker3 and BGP peer2
+        icmpPathintentConstructor(
+                "192.168.20.103/32", "192.168.20.1/32", s2Eth100, s2Eth1);
+        icmpPathintentConstructor(
+                "192.168.20.1/32", "192.168.20.103/32", s2Eth1, s2Eth100);
+
+        // Start to build intents between BGP speaker3 and BGP peer3
+        icmpPathintentConstructor(
+                "192.168.20.103/32", "192.168.20.2/32", s2Eth100, s2Eth1);
+        icmpPathintentConstructor(
+                "192.168.20.2/32", "192.168.20.103/32", s2Eth1, s2Eth100);
+
+    }
+
+    /**
+     * Initializes peer connectivity testing environment.
+     */
+    private void initPeerConnectivity() {
+
+        configInfoService = createMock(SdnIpConfigService.class);
+        expect(configInfoService.getBgpPeers()).andReturn(peers).anyTimes();
+        expect(configInfoService.getBgpSpeakers()).andReturn(bgpSpeakers).anyTimes();
+        replay(configInfoService);
+
+        intentService = createMock(IntentService.class);
+        replay(intentService);
+
+        peerConnectivityManager = new PeerConnectivityManager(configInfoService,
+                interfaceService, intentService);
+    }
+
+    /*
+     * EasyMock matcher that matches {@link PointToPointIntent}s but
+     * ignores the {@link IntentId} when matching.
+     * <p/>
+     * The normal intent equals method tests that the intent IDs are equal,
+     * however in these tests we can't know what the intent IDs will be in
+     * advance, so we can't set up expected intents with the correct IDs. Thus,
+     * the solution is to use an EasyMock matcher that verifies that all the
+     * value properties of the provided intent match the expected values, but
+     * ignores the intent ID when testing equality.
+     */
+    private static final class IdAgnosticPointToPointIntentMatcher implements
+    IArgumentMatcher {
+
+        private final PointToPointIntent intent;
+        private String providedIntentString;
+
+        /**
+         * Constructor taking the expected intent to match against.
+         *
+         * @param intent the expected intent
+         */
+        public IdAgnosticPointToPointIntentMatcher(PointToPointIntent intent) {
+            this.intent = intent;
+        }
+
+        @Override
+        public void appendTo(StringBuffer strBuffer) {
+            strBuffer.append("PointToPointIntentMatcher unable to match: "
+                    + providedIntentString);
+        }
+
+        @Override
+        public boolean matches(Object object) {
+            if (!(object instanceof PointToPointIntent)) {
+                return false;
+            }
+
+            PointToPointIntent providedIntent = (PointToPointIntent) object;
+            providedIntentString = providedIntent.toString();
+
+            PointToPointIntent matchIntent =
+                    new PointToPointIntent(providedIntent.id(),
+                            intent.selector(), intent.treatment(),
+                            intent.ingressPoint(), intent.egressPoint());
+
+            return matchIntent.equals(providedIntent);
+        }
+    }
+
+    /**
+     * Matcher method to set an expected intent to match against (ignoring the
+     * the intent ID).
+     *
+     * @param intent the expected intent
+     * @return something of type PointToPointIntent
+     */
+    private static PointToPointIntent eqExceptId(
+            PointToPointIntent intent) {
+        reportMatcher(new IdAgnosticPointToPointIntentMatcher(intent));
+        return null;
+    }
+
+    /**
+     * Tests whether peer connectivity manager can set up correct BGP and
+     * ICMP intents according to specific configuration.
+     * <p/>
+     * Two tricky cases included in the configuration are: 2 peers on a same
+     * switch port, peer on the same switch with BGPd.
+     */
+    @Test
+    public void testConnectionSetup() {
+
+        reset(intentService);
+
+        // Sets up the expected PointToPoint intents.
+        for (int i = 0; i < intentList.size(); i++) {
+            intentService.submit(eqExceptId(intentList.get(i)));
+        }
+
+        replay(intentService);
+
+        // Running the interface to be tested.
+        peerConnectivityManager.start();
+
+        verify(intentService);
+
+    }
+
+    /**
+     *  Tests a corner case, when there are no interfaces in the configuration.
+     */
+    @Test
+    public void testNullInterfaces() {
+        reset(interfaceService);
+        expect(interfaceService.getInterfaces()).andReturn(
+                Sets.<Interface>newHashSet()).anyTimes();
+        expect(interfaceService.getInterface(s2Eth1))
+                .andReturn(null).anyTimes();
+        expect(interfaceService.getInterface(s1Eth1))
+        .andReturn(null).anyTimes();
+        replay(interfaceService);
+
+        reset(configInfoService);
+        expect(configInfoService.getBgpPeers()).andReturn(peers).anyTimes();
+        expect(configInfoService.getBgpSpeakers()).andReturn(bgpSpeakers).anyTimes();
+        replay(configInfoService);
+
+        reset(intentService);
+        replay(intentService);
+        peerConnectivityManager.start();
+        verify(intentService);
+    }
+
+    /**
+     *  Tests a corner case, when there are no BGP peers in the configuration.
+     */
+    @Test
+    public void testNullBgpPeers() {
+        reset(interfaceService);
+        expect(interfaceService.getInterfaces()).andReturn(
+                Sets.newHashSet(interfaces.values())).anyTimes();
+        replay(interfaceService);
+
+        reset(configInfoService);
+        expect(configInfoService.getBgpPeers()).andReturn(
+                new HashMap<IpAddress, BgpPeer>()).anyTimes();
+        expect(configInfoService.getBgpSpeakers()).andReturn(
+                bgpSpeakers).anyTimes();
+        replay(configInfoService);
+
+        reset(intentService);
+        replay(intentService);
+        peerConnectivityManager.start();
+        verify(intentService);
+    }
+
+    /**
+     *  Tests a corner case, when there is no BGP speakers in the configuration.
+     */
+    @Test
+    public void testNullBgpSpeakers() {
+        reset(interfaceService);
+        expect(interfaceService.getInterfaces()).andReturn(
+                Sets.newHashSet(interfaces.values())).anyTimes();
+        replay(interfaceService);
+
+        reset(configInfoService);
+        expect(configInfoService.getBgpPeers()).andReturn(
+                peers).anyTimes();
+        expect(configInfoService.getBgpSpeakers()).andReturn(
+                null).anyTimes();
+        replay(configInfoService);
+
+        reset(intentService);
+        replay(intentService);
+        peerConnectivityManager.start();
+        verify(intentService);
+    }
+
+    /**
+     * Tests a corner case, when there is no Interface configured for one BGP
+     * peer.
+     */
+    @Test
+    public void testNoPeerInterface() {
+        String peerSw100Eth1 = "192.168.200.1";
+        configuredPeers.put(IpAddress.valueOf(peerSw100Eth1),
+                new BgpPeer("00:00:00:00:00:00:01:00", 1, peerSw100Eth1));
+        testConnectionSetup();
+    }
+
+    /**
+     * Tests a corner case, when there is no Interface configured for one BGP
+     * speaker.
+     * TODO: we should add a configuration correctness checking module/method
+     * before testing this corner case.
+     */
+    @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<InterfaceAddress>();
+        interfaceAddresses100.add(new InterfaceAddress(dpid1, 1, "192.168.10.201"));
+        interfaceAddresses100.add(new InterfaceAddress(dpid2, 1, "192.168.20.201"));
+        bgpSpeaker100.setInterfaceAddresses(interfaceAddresses100);
+        configuredBgpSpeakers.put(bgpSpeaker100.name(), bgpSpeaker100);
+        testConnectionSetup();
+    }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/topology/Topology.java b/core/api/src/main/java/org/onlab/onos/net/topology/Topology.java
index f71a5ec..ea3c68d 100644
--- a/core/api/src/main/java/org/onlab/onos/net/topology/Topology.java
+++ b/core/api/src/main/java/org/onlab/onos/net/topology/Topology.java
@@ -7,6 +7,8 @@
  */
 public interface Topology extends Provided {
 
+    // FIXME: Following is not true right now. It is actually System.nanoTime(),
+    // which has no relation to epoch time, wall clock, etc.
     /**
      * Returns the time, specified in milliseconds since start of epoch,
      * when the topology became active and made available.
diff --git a/core/store/dist/pom.xml b/core/store/dist/pom.xml
index b1e7172..6482729 100644
--- a/core/store/dist/pom.xml
+++ b/core/store/dist/pom.xml
@@ -52,6 +52,12 @@
           <artifactId>easymock</artifactId>
           <scope>test</scope>
         </dependency>
+        <dependency>
+          <groupId>org.onlab.onos</groupId>
+          <artifactId>onos-api</artifactId>
+          <classifier>tests</classifier>
+          <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/flow/ReplicaInfoService.java b/core/store/dist/src/main/java/org/onlab/onos/store/flow/ReplicaInfoService.java
index c3f1bbf..e613348 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/flow/ReplicaInfoService.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/flow/ReplicaInfoService.java
@@ -3,7 +3,7 @@
 import org.onlab.onos.net.DeviceId;
 
 /**
- * Service to return where the Replica should be placed.
+ * Service to return where the replica should be placed.
  */
 public interface ReplicaInfoService {
 
@@ -15,4 +15,19 @@
      * @return placement information
      */
     ReplicaInfo getReplicaInfoFor(DeviceId deviceId);
+
+    /**
+     * Adds the specified replica placement info change listener.
+     *
+     * @param listener the replica placement info change listener
+     */
+    void addListener(ReplicaInfoEventListener listener);
+
+    /**
+     * Removes the specified replica placement info change listener.
+     *
+     * @param listener the replica placement info change listener
+     */
+    void removeListener(ReplicaInfoEventListener listener);
+
 }
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/ReplicaInfoManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/ReplicaInfoManager.java
index 0d4cd08..dcbdb4a 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/ReplicaInfoManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/ReplicaInfoManager.java
@@ -1,5 +1,6 @@
 package org.onlab.onos.store.flow.impl;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 import static org.onlab.onos.store.flow.ReplicaInfoEvent.Type.MASTER_CHANGED;
 
@@ -66,6 +67,16 @@
                                Collections.<NodeId>emptyList());
     }
 
+    @Override
+    public void addListener(ReplicaInfoEventListener listener) {
+        listenerRegistry.addListener(checkNotNull(listener));
+    }
+
+    @Override
+    public void removeListener(ReplicaInfoEventListener listener) {
+        listenerRegistry.removeListener(checkNotNull(listener));
+    }
+
     final class InternalMastershipListener implements MastershipListener {
 
         @Override
diff --git a/core/store/dist/src/test/java/org/onlab/onos/store/flow/impl/ReplicaInfoManagerTest.java b/core/store/dist/src/test/java/org/onlab/onos/store/flow/impl/ReplicaInfoManagerTest.java
new file mode 100644
index 0000000..105e37b
--- /dev/null
+++ b/core/store/dist/src/test/java/org/onlab/onos/store/flow/impl/ReplicaInfoManagerTest.java
@@ -0,0 +1,165 @@
+package org.onlab.onos.store.flow.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.junit.Assert.*;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.onos.cluster.NodeId;
+import org.onlab.onos.event.AbstractListenerRegistry;
+import org.onlab.onos.event.DefaultEventSinkRegistry;
+import org.onlab.onos.event.Event;
+import org.onlab.onos.event.EventDeliveryService;
+import org.onlab.onos.event.EventSink;
+import org.onlab.onos.mastership.MastershipEvent;
+import org.onlab.onos.mastership.MastershipEvent.Type;
+import org.onlab.onos.mastership.MastershipListener;
+import org.onlab.onos.mastership.MastershipService;
+import org.onlab.onos.mastership.MastershipServiceAdapter;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.store.flow.ReplicaInfo;
+import org.onlab.onos.store.flow.ReplicaInfoEvent;
+import org.onlab.onos.store.flow.ReplicaInfoEventListener;
+import org.onlab.onos.store.flow.ReplicaInfoService;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Maps;
+
+public class ReplicaInfoManagerTest {
+
+
+    private static final DeviceId DID1 = DeviceId.deviceId("of:1");
+    private static final DeviceId DID2 = DeviceId.deviceId("of:2");
+    private static final NodeId NID1 = new NodeId("foo");
+
+    private ReplicaInfoManager mgr;
+    private ReplicaInfoService service;
+
+    private AbstractListenerRegistry<MastershipEvent, MastershipListener>
+        mastershipListenerRegistry;
+    private TestEventDispatcher eventDispatcher;
+
+
+    @Before
+    public void setUp() throws Exception {
+        mastershipListenerRegistry = new AbstractListenerRegistry<>();
+
+        mgr = new ReplicaInfoManager();
+        service = mgr;
+
+        eventDispatcher = new TestEventDispatcher();
+        mgr.eventDispatcher = eventDispatcher;
+        mgr.mastershipService = new TestMastershipService();
+
+        // register dummy mastership event source
+        mgr.eventDispatcher.addSink(MastershipEvent.class, mastershipListenerRegistry);
+
+        mgr.activate();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mgr.deactivate();
+    }
+
+    @Test
+    public void testGetReplicaInfoFor() {
+        ReplicaInfo info1 = service.getReplicaInfoFor(DID1);
+        assertEquals(Optional.of(NID1), info1.master());
+        // backups are always empty for now
+        assertEquals(Collections.emptyList(), info1.backups());
+
+        ReplicaInfo info2 = service.getReplicaInfoFor(DID2);
+        assertEquals("There's no master", Optional.absent(), info2.master());
+        // backups are always empty for now
+        assertEquals(Collections.emptyList(), info2.backups());
+    }
+
+    @Test
+    public void testReplicaInfoEvent() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        service.addListener(new MasterNodeCheck(latch, DID1, NID1));
+
+        // fake MastershipEvent
+        eventDispatcher.post(new MastershipEvent(Type.MASTER_CHANGED, DID1, NID1));
+
+        assertTrue(latch.await(1, TimeUnit.SECONDS));
+    }
+
+
+    private final class MasterNodeCheck implements ReplicaInfoEventListener {
+        private final CountDownLatch latch;
+        private Optional<NodeId> expectedMaster;
+        private DeviceId expectedDevice;
+
+
+        MasterNodeCheck(CountDownLatch latch, DeviceId did,
+                NodeId nid) {
+            this.latch = latch;
+            this.expectedMaster = Optional.fromNullable(nid);
+            this.expectedDevice = did;
+        }
+
+        @Override
+        public void event(ReplicaInfoEvent event) {
+            assertEquals(expectedDevice, event.subject());
+            assertEquals(expectedMaster, event.replicaInfo().master());
+            // backups are always empty for now
+            assertEquals(Collections.emptyList(), event.replicaInfo().backups());
+            latch.countDown();
+        }
+    }
+
+
+    private final class TestMastershipService
+            extends MastershipServiceAdapter
+            implements MastershipService {
+
+        private Map<DeviceId, NodeId> masters;
+
+        TestMastershipService() {
+            masters = Maps.newHashMap();
+            masters.put(DID1, NID1);
+            // DID2 has no master
+        }
+
+        @Override
+        public NodeId getMasterFor(DeviceId deviceId) {
+            return masters.get(deviceId);
+        }
+
+        @Override
+        public void addListener(MastershipListener listener) {
+            mastershipListenerRegistry.addListener(listener);
+        }
+
+        @Override
+        public void removeListener(MastershipListener listener) {
+            mastershipListenerRegistry.removeListener(listener);
+        }
+    }
+
+
+    // code clone
+    /**
+     * Implements event delivery system that delivers events synchronously, or
+     * in-line with the post method invocation.
+     */
+    private static class TestEventDispatcher extends DefaultEventSinkRegistry
+            implements EventDeliveryService {
+
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        @Override
+        public void post(Event event) {
+            EventSink sink = getSink(event.getClass());
+            checkState(sink != null, "No sink for event %s", event);
+            sink.process(event);
+        }
+    }
+}
diff --git a/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java b/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
index 88cb1ac..f322270 100644
--- a/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
+++ b/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
@@ -172,7 +172,7 @@
          */
         private List<PortDescription> buildPortDescriptions(
                 List<OFPortDesc> ports) {
-            final List<PortDescription> portDescs = new ArrayList<>();
+            final List<PortDescription> portDescs = new ArrayList<>(ports.size());
             for (OFPortDesc port : ports) {
                 portDescs.add(buildPortDescription(port));
             }