Merge "Fixing flow rule batches"
diff --git a/apps/config/src/main/java/org/onlab/onos/config/NetworkConfigReader.java b/apps/config/src/main/java/org/onlab/onos/config/NetworkConfigReader.java
index f015e36..846e83c 100644
--- a/apps/config/src/main/java/org/onlab/onos/config/NetworkConfigReader.java
+++ b/apps/config/src/main/java/org/onlab/onos/config/NetworkConfigReader.java
@@ -50,7 +50,10 @@
 
     private final Logger log = getLogger(getClass());
 
-    private static final String DEFAULT_CONFIG_FILE = "config/addresses.json";
+    // Current working dir seems to be /opt/onos/apache-karaf-3.0.2
+    // TODO: Set the path to /opt/onos/config
+    private static final String CONFIG_DIR = "../config";
+    private static final String DEFAULT_CONFIG_FILE = "addresses.json";
     private String configFileName = DEFAULT_CONFIG_FILE;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -60,52 +63,9 @@
     protected void activate() {
         log.info("Started network config reader");
 
-        log.info("Config file set to {}", configFileName);
-
         AddressConfiguration config = readNetworkConfig();
-
         if (config != null) {
-            for (AddressEntry entry : config.getAddresses()) {
-
-                ConnectPoint cp = new ConnectPoint(
-                        DeviceId.deviceId(dpidToUri(entry.getDpid())),
-                        PortNumber.portNumber(entry.getPortNumber()));
-
-                Set<InterfaceIpAddress> interfaceIpAddresses = new HashSet<>();
-
-                for (String strIp : entry.getIpAddresses()) {
-                    // Get the IP address and the subnet mask length
-                    try {
-                        String[] splits = strIp.split("/");
-                        if (splits.length != 2) {
-                            throw new IllegalArgumentException("Invalid IP address and prefix length format");
-                        }
-                        // NOTE: IpPrefix will mask-out the bits after the prefix length.
-                        IpPrefix subnet = IpPrefix.valueOf(strIp);
-                        IpAddress addr = IpAddress.valueOf(splits[0]);
-                        InterfaceIpAddress ia =
-                            new InterfaceIpAddress(addr, subnet);
-                        interfaceIpAddresses.add(ia);
-                    } catch (IllegalArgumentException e) {
-                        log.warn("Bad format for IP address in config: {}", strIp);
-                    }
-                }
-
-                MacAddress macAddress = null;
-                if (entry.getMacAddress() != null) {
-                    try {
-                        macAddress = MacAddress.valueOf(entry.getMacAddress());
-                    } catch (IllegalArgumentException e) {
-                        log.warn("Bad format for MAC address in config: {}",
-                                entry.getMacAddress());
-                    }
-                }
-
-                PortAddresses addresses = new PortAddresses(cp,
-                        interfaceIpAddresses, macAddress);
-
-                hostAdminService.bindAddressesToPort(addresses);
-            }
+            applyNetworkConfig(config);
         }
     }
 
@@ -114,12 +74,17 @@
         log.info("Stopped");
     }
 
+    /**
+     * Reads the network configuration.
+     *
+     * @return the network configuration on success, otherwise null
+     */
     private AddressConfiguration readNetworkConfig() {
-        File configFile = new File(configFileName);
-
+        File configFile = new File(CONFIG_DIR, configFileName);
         ObjectMapper mapper = new ObjectMapper();
 
         try {
+            log.info("Loading config: {}", configFile.getAbsolutePath());
             AddressConfiguration config =
                     mapper.readValue(configFile, AddressConfiguration.class);
 
@@ -127,12 +92,58 @@
         } catch (FileNotFoundException e) {
             log.warn("Configuration file not found: {}", configFileName);
         } catch (IOException e) {
-            log.error("Unable to read config from file:", e);
+            log.error("Error loading configuration", e);
         }
 
         return null;
     }
 
+    /**
+     * Applies the network configuration.
+     *
+     * @param config the network configuration to apply
+     */
+    private void applyNetworkConfig(AddressConfiguration config) {
+        for (AddressEntry entry : config.getAddresses()) {
+            ConnectPoint cp = new ConnectPoint(
+                        DeviceId.deviceId(dpidToUri(entry.getDpid())),
+                        PortNumber.portNumber(entry.getPortNumber()));
+
+            Set<InterfaceIpAddress> interfaceIpAddresses = new HashSet<>();
+            for (String strIp : entry.getIpAddresses()) {
+                // Get the IP address and the subnet mask length
+                try {
+                    String[] splits = strIp.split("/");
+                    if (splits.length != 2) {
+                        throw new IllegalArgumentException("Invalid IP address and prefix length format");
+                    }
+                    // NOTE: IpPrefix will mask-out the bits after the prefix length.
+                    IpPrefix subnet = IpPrefix.valueOf(strIp);
+                    IpAddress addr = IpAddress.valueOf(splits[0]);
+                    InterfaceIpAddress ia =
+                        new InterfaceIpAddress(addr, subnet);
+                    interfaceIpAddresses.add(ia);
+                } catch (IllegalArgumentException e) {
+                    log.warn("Bad format for IP address in config: {}", strIp);
+                }
+            }
+
+            MacAddress macAddress = null;
+            if (entry.getMacAddress() != null) {
+                try {
+                    macAddress = MacAddress.valueOf(entry.getMacAddress());
+                } catch (IllegalArgumentException e) {
+                    log.warn("Bad format for MAC address in config: {}",
+                             entry.getMacAddress());
+                }
+            }
+
+            PortAddresses addresses = new PortAddresses(cp,
+                        interfaceIpAddresses, macAddress);
+            hostAdminService.bindAddressesToPort(addresses);
+        }
+    }
+
     private static String dpidToUri(String dpid) {
         return "of:" + dpid.replace(":", "");
     }
diff --git a/apps/sdnip/src/main/resources/config-examples/addresses.json b/apps/config/src/main/resources/addresses.json
similarity index 100%
rename from apps/sdnip/src/main/resources/config-examples/addresses.json
rename to apps/config/src/main/resources/addresses.json
diff --git a/apps/config/src/main/resources/config.json b/apps/config/src/main/resources/config.json
deleted file mode 100644
index ca4be83..0000000
--- a/apps/config/src/main/resources/config.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-    "interfaces" : [
-	{
-	    "dpid" : "00:00:00:00:00:00:01",
-	    "port" : "1",
-	    "ips" : ["192.168.10.101/24"],
-	    "mac" : "00:00:00:11:22:33"
-	},
-	{
-	    "dpid" : "00:00:00:00:00:00:02",
-	    "port" : "1",
-	    "ips" : ["192.168.20.101/24", "192.168.30.101/24"]
-	},
-	{
-	    "dpid" : "00:00:00:00:00:00:03",
-	    "port" : "1",
-	    "ips" : ["10.1.0.1/16"],
-	    "mac" : "00:00:00:00:00:01"
-	}
-    ]
-}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/config/SdnIpConfigReader.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/config/SdnIpConfigReader.java
index 2fcd1fe..7262c42 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/config/SdnIpConfigReader.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/config/SdnIpConfigReader.java
@@ -37,24 +37,31 @@
  */
 public class SdnIpConfigReader implements SdnIpConfigService {
 
-    private static final Logger log = LoggerFactory.getLogger(SdnIpConfigReader.class);
+    private final Logger log = LoggerFactory.getLogger(getClass());
 
-    private static final String DEFAULT_CONFIG_FILE = "config/sdnip.json";
+    // Current working dir seems to be /opt/onos/apache-karaf-3.0.2
+    // TODO: Set the path to /opt/onos/config
+    private static final String CONFIG_DIR = "../config";
+    private static final String DEFAULT_CONFIG_FILE = "sdnip.json";
     private String configFileName = DEFAULT_CONFIG_FILE;
+
     private Map<String, BgpSpeaker> bgpSpeakers = new ConcurrentHashMap<>();
     private Map<IpAddress, BgpPeer> bgpPeers = new ConcurrentHashMap<>();
 
     /**
-     * Reads the info contained in the configuration file.
+     * Reads SDN-IP related information contained in the configuration file.
      *
-     * @param configFilename The name of configuration file for SDN-IP application.
+     * @param configFilename the name of the configuration file for the SDN-IP
+     * application
      */
     private void readConfiguration(String configFilename) {
-        File gatewaysFile = new File(configFilename);
+        File configFile = new File(CONFIG_DIR, configFilename);
         ObjectMapper mapper = new ObjectMapper();
 
         try {
-            Configuration config = mapper.readValue(gatewaysFile, Configuration.class);
+            log.info("Loading config: {}", configFile.getAbsolutePath());
+            Configuration config = mapper.readValue(configFile,
+                                                    Configuration.class);
             for (BgpSpeaker speaker : config.getBgpSpeakers()) {
                 bgpSpeakers.put(speaker.name(), speaker);
             }
@@ -64,13 +71,11 @@
         } catch (FileNotFoundException e) {
             log.warn("Configuration file not found: {}", configFileName);
         } catch (IOException e) {
-            log.error("Error reading JSON file", e);
+            log.error("Error loading configuration", e);
         }
     }
 
     public void init() {
-        log.debug("Config file set to {}", configFileName);
-
         readConfiguration(configFileName);
     }
 
diff --git a/apps/sdnip/src/main/resources/config-examples/README b/apps/sdnip/src/main/resources/config-examples/README
index 0444e41..7642a4d 100644
--- a/apps/sdnip/src/main/resources/config-examples/README
+++ b/apps/sdnip/src/main/resources/config-examples/README
@@ -1 +1,5 @@
-ONOS looks for these config files by default in $KARAF_HOME/config/
\ No newline at end of file
+The SDN-IP configuration files should be copied to directory
+  $ONOS_HOME/tools/package/config
+
+After deployment and starting up the ONOS cluster, ONOS looks for these
+configuration files in /opt/onos/config on each cluster member.
diff --git a/core/api/src/main/java/org/onlab/onos/net/AnnotationKeys.java b/core/api/src/main/java/org/onlab/onos/net/AnnotationKeys.java
index eace91c..5777022 100644
--- a/core/api/src/main/java/org/onlab/onos/net/AnnotationKeys.java
+++ b/core/api/src/main/java/org/onlab/onos/net/AnnotationKeys.java
@@ -28,4 +28,23 @@
      * Annotation key for latency.
      */
     public static final String LATENCY = "latency";
+
+    /**
+     * Returns the value annotated object for the specified annotation key.
+     * The annotated value is expected to be String that can be parsed as double.
+     * If parsing fails, the returned value will be 1.0.
+     *
+     * @param annotated annotated object whose annotated value is obtained
+     * @param key key of annotation
+     * @return double value of annotated object for the specified key
+     */
+    public static double getAnnotatedValue(Annotated annotated, String key) {
+        double value;
+        try {
+            value = Double.parseDouble(annotated.annotations().value(key));
+        } catch (NumberFormatException e) {
+            value = 1.0;
+        }
+        return value;
+    }
 }
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java
index c2d901b..c6cb361 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java
@@ -80,6 +80,7 @@
         this.state = FlowEntryState.FAILED;
         this.errType = errType;
         this.errCode = errCode;
+        this.lastSeen = System.currentTimeMillis();
     }
 
     @Override
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/constraint/AnnotationConstraint.java b/core/api/src/main/java/org/onlab/onos/net/intent/constraint/AnnotationConstraint.java
index ac76303..a093be9 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/constraint/AnnotationConstraint.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/constraint/AnnotationConstraint.java
@@ -21,6 +21,8 @@
 
 import java.util.Objects;
 
+import static org.onlab.onos.net.AnnotationKeys.getAnnotatedValue;
+
 /**
  * Constraint that evaluates an arbitrary link annotated value is under the specified threshold.
  */
@@ -65,25 +67,6 @@
         return value <= threshold;
     }
 
-    /**
-     * Returns the annotated value of the specified link. The annotated value
-     * is expected to be String that can be parsed as double. If parsing fails,
-     * the returned value will be 1.0.
-     *
-     * @param link link whose annotated value is obtained
-     * @param key key of link annotation
-     * @return double value of link annotation for the specified key
-     */
-    private double getAnnotatedValue(Link link, String key) {
-        double value;
-        try {
-            value = Double.parseDouble(link.annotations().value(key));
-        } catch (NumberFormatException e) {
-            value = 1.0;
-        }
-        return value;
-    }
-
     @Override
     public double cost(Link link, LinkResourceService resourceService) {
         if (isValid(link, resourceService)) {
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/constraint/LatencyConstraint.java b/core/api/src/main/java/org/onlab/onos/net/intent/constraint/LatencyConstraint.java
index 45151ef..7b1411a 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/constraint/LatencyConstraint.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/constraint/LatencyConstraint.java
@@ -26,6 +26,7 @@
 import java.util.Objects;
 
 import static org.onlab.onos.net.AnnotationKeys.LATENCY;
+import static org.onlab.onos.net.AnnotationKeys.getAnnotatedValue;
 
 /**
  * Constraint that evaluates the latency through a path.
@@ -48,16 +49,7 @@
 
     @Override
     public double cost(Link link, LinkResourceService resourceService) {
-        String value = link.annotations().value(LATENCY);
-
-        double latencyInMicroSec;
-        try {
-            latencyInMicroSec = Double.parseDouble(value);
-        } catch (NumberFormatException e) {
-            latencyInMicroSec = 1.0;
-        }
-
-        return latencyInMicroSec;
+        return getAnnotatedValue(link, LATENCY);
     }
 
     @Override
diff --git a/core/net/src/test/java/org/onlab/onos/net/flow/DefaultFlowEntryTest.java b/core/net/src/test/java/org/onlab/onos/net/flow/DefaultFlowEntryTest.java
new file mode 100644
index 0000000..b8d7799
--- /dev/null
+++ b/core/net/src/test/java/org/onlab/onos/net/flow/DefaultFlowEntryTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2014 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.onlab.onos.net.flow;
+
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+import org.onlab.onos.net.intent.IntentTestsMocks;
+
+import com.google.common.testing.EqualsTester;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+import static org.onlab.onos.net.NetTestTools.did;
+
+/**
+ * Unit tests for the DefaultFlowEntry class.
+ */
+public class DefaultFlowEntryTest {
+    private static final IntentTestsMocks.MockSelector SELECTOR =
+            new IntentTestsMocks.MockSelector();
+    private static final IntentTestsMocks.MockTreatment TREATMENT =
+            new IntentTestsMocks.MockTreatment();
+
+    private static DefaultFlowEntry makeFlowEntry(int uniqueValue) {
+        return new DefaultFlowEntry(did("id" + Integer.toString(uniqueValue)),
+                SELECTOR,
+                TREATMENT,
+                uniqueValue,
+                FlowEntry.FlowEntryState.ADDED,
+                uniqueValue,
+                uniqueValue,
+                uniqueValue,
+                uniqueValue,
+                uniqueValue);
+    }
+
+    final DefaultFlowEntry defaultFlowEntry1 = makeFlowEntry(1);
+    final DefaultFlowEntry sameAsDefaultFlowEntry1 = makeFlowEntry(1);
+    final DefaultFlowEntry defaultFlowEntry2 = makeFlowEntry(2);
+
+    /**
+     * Tests the equals, hashCode and toString methods using Guava EqualsTester.
+     */
+    @Test
+    public void testEquals() {
+        new EqualsTester()
+                .addEqualityGroup(defaultFlowEntry1, sameAsDefaultFlowEntry1)
+                .addEqualityGroup(defaultFlowEntry2)
+                .testEquals();
+    }
+
+    /**
+     * Tests the construction of a default flow entry from a device id.
+     */
+    @Test
+    public void testDeviceBasedObject() {
+        assertThat(defaultFlowEntry1.deviceId(), is(did("id1")));
+        assertThat(defaultFlowEntry1.selector(), is(SELECTOR));
+        assertThat(defaultFlowEntry1.treatment(), is(TREATMENT));
+        assertThat(defaultFlowEntry1.timeout(), is(1));
+        assertThat(defaultFlowEntry1.life(), is(1L));
+        assertThat(defaultFlowEntry1.packets(), is(1L));
+        assertThat(defaultFlowEntry1.bytes(), is(1L));
+        assertThat(defaultFlowEntry1.state(), is(FlowEntry.FlowEntryState.ADDED));
+        assertThat(defaultFlowEntry1.lastSeen(),
+                   greaterThan(System.currentTimeMillis() -
+                           TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
+    }
+
+    /**
+     * Tests the setters on a default flow entry object.
+     */
+    @Test
+    public void testSetters() {
+        final DefaultFlowEntry entry = makeFlowEntry(1);
+
+        entry.setLastSeen();
+        entry.setState(FlowEntry.FlowEntryState.PENDING_REMOVE);
+        entry.setPackets(11);
+        entry.setBytes(22);
+        entry.setLife(33);
+
+        assertThat(entry.deviceId(), is(did("id1")));
+        assertThat(entry.selector(), is(SELECTOR));
+        assertThat(entry.treatment(), is(TREATMENT));
+        assertThat(entry.timeout(), is(1));
+        assertThat(entry.life(), is(33L));
+        assertThat(entry.packets(), is(11L));
+        assertThat(entry.bytes(), is(22L));
+        assertThat(entry.state(), is(FlowEntry.FlowEntryState.PENDING_REMOVE));
+        assertThat(entry.lastSeen(),
+                greaterThan(System.currentTimeMillis() -
+                        TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
+    }
+
+    /**
+     * Tests a default flow rule built for an error.
+     */
+    @Test
+    public void testErrorObject() {
+        final DefaultFlowEntry errorEntry =
+                new DefaultFlowEntry(new IntentTestsMocks.MockFlowRule(1),
+                                     111,
+                                     222);
+        assertThat(errorEntry.errType(), is(111));
+        assertThat(errorEntry.errCode(), is(222));
+        assertThat(errorEntry.state(), is(FlowEntry.FlowEntryState.FAILED));
+        assertThat(errorEntry.lastSeen(),
+                greaterThan(System.currentTimeMillis() -
+                        TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
+    }
+
+    /**
+     * Tests a default flow entry constructed from a flow rule.
+     */
+    @Test
+    public void testFlowBasedObject() {
+        final DefaultFlowEntry entry =
+                new DefaultFlowEntry(new IntentTestsMocks.MockFlowRule(1));
+        assertThat(entry.priority(), is(1));
+        assertThat(entry.appId(), is((short) 0));
+        assertThat(entry.lastSeen(),
+                greaterThan(System.currentTimeMillis() -
+                        TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
+    }
+
+    /**
+     * Tests a default flow entry constructed from a flow rule plus extra
+     * parameters.
+     */
+    @Test
+    public void testFlowBasedObjectWithParameters() {
+        final DefaultFlowEntry entry =
+                new DefaultFlowEntry(new IntentTestsMocks.MockFlowRule(33),
+                        FlowEntry.FlowEntryState.REMOVED,
+                        101, 102, 103);
+        assertThat(entry.state(), is(FlowEntry.FlowEntryState.REMOVED));
+        assertThat(entry.life(), is(101L));
+        assertThat(entry.packets(), is(102L));
+        assertThat(entry.bytes(), is(103L));
+        assertThat(entry.lastSeen(),
+                greaterThan(System.currentTimeMillis() -
+                        TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
+    }
+}
diff --git a/core/net/src/test/java/org/onlab/onos/net/flow/DefaultFlowRuleTest.java b/core/net/src/test/java/org/onlab/onos/net/flow/DefaultFlowRuleTest.java
index cddaf23..f2c418f 100644
--- a/core/net/src/test/java/org/onlab/onos/net/flow/DefaultFlowRuleTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/flow/DefaultFlowRuleTest.java
@@ -17,7 +17,6 @@
 package org.onlab.onos.net.flow;
 
 import org.junit.Test;
-import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.intent.IntentTestsMocks;
 
 import com.google.common.testing.EqualsTester;
@@ -25,8 +24,8 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass;
-import static org.onlab.onos.net.NetTestTools.did;
 import static org.onlab.onos.net.NetTestTools.APP_ID;
+import static org.onlab.onos.net.NetTestTools.did;
 
 /**
  * Unit tests for the default flow rule class.
@@ -37,63 +36,13 @@
     private static final IntentTestsMocks.MockTreatment TREATMENT =
             new IntentTestsMocks.MockTreatment();
 
-    final FlowRule flowRule1 = new MockFlowRule(1);
-    final FlowRule sameAsFlowRule1 = new MockFlowRule(1);
-    final FlowRule flowRule2 = new MockFlowRule(2);
+    final FlowRule flowRule1 = new IntentTestsMocks.MockFlowRule(1);
+    final FlowRule sameAsFlowRule1 = new IntentTestsMocks.MockFlowRule(1);
+    final FlowRule flowRule2 = new IntentTestsMocks.MockFlowRule(2);
     final DefaultFlowRule defaultFlowRule1 = new DefaultFlowRule(flowRule1);
     final DefaultFlowRule sameAsDefaultFlowRule1 = new DefaultFlowRule(sameAsFlowRule1);
     final DefaultFlowRule defaultFlowRule2 = new DefaultFlowRule(flowRule2);
 
-    private static class MockFlowRule implements FlowRule {
-
-        int priority;
-        MockFlowRule(int priority) {
-            this.priority = priority;
-        }
-
-        @Override
-        public FlowId id() {
-            return FlowId.valueOf(1);
-        }
-
-        @Override
-        public short appId() {
-            return 0;
-        }
-
-        @Override
-        public int priority() {
-            return priority;
-        }
-
-        @Override
-        public DeviceId deviceId() {
-            return did("1");
-        }
-
-        @Override
-        public TrafficSelector selector() {
-            return SELECTOR;
-        }
-
-        @Override
-        public TrafficTreatment treatment() {
-            return TREATMENT;
-        }
-
-        @Override
-        public int timeout() {
-            return 0;
-        }
-
-        @Override
-        public boolean isPermanent() {
-            return false;
-        }
-
-
-    }
-
     /**
      * Checks that the DefaultFlowRule class is immutable but can be inherited
      * from.
diff --git a/core/net/src/test/java/org/onlab/onos/net/intent/IntentTestsMocks.java b/core/net/src/test/java/org/onlab/onos/net/intent/IntentTestsMocks.java
index e0fe09e..65df6b2 100644
--- a/core/net/src/test/java/org/onlab/onos/net/intent/IntentTestsMocks.java
+++ b/core/net/src/test/java/org/onlab/onos/net/intent/IntentTestsMocks.java
@@ -16,6 +16,7 @@
 package org.onlab.onos.net.intent;
 
 import static org.onlab.onos.net.NetTestTools.createPath;
+import static org.onlab.onos.net.NetTestTools.did;
 import static org.onlab.onos.net.NetTestTools.link;
 
 import java.util.ArrayList;
@@ -31,6 +32,8 @@
 import org.onlab.onos.net.ElementId;
 import org.onlab.onos.net.Link;
 import org.onlab.onos.net.Path;
+import org.onlab.onos.net.flow.FlowId;
+import org.onlab.onos.net.flow.FlowRule;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
 import org.onlab.onos.net.flow.criteria.Criterion;
@@ -271,4 +274,60 @@
         }
     }
 
+    private static final IntentTestsMocks.MockSelector SELECTOR =
+            new IntentTestsMocks.MockSelector();
+    private static final IntentTestsMocks.MockTreatment TREATMENT =
+            new IntentTestsMocks.MockTreatment();
+
+    public static class MockFlowRule implements FlowRule {
+
+        int priority;
+        public MockFlowRule(int priority) {
+            this.priority = priority;
+        }
+
+        @Override
+        public FlowId id() {
+            return FlowId.valueOf(1);
+        }
+
+        @Override
+        public short appId() {
+            return 0;
+        }
+
+        @Override
+        public int priority() {
+            return priority;
+        }
+
+        @Override
+        public DeviceId deviceId() {
+            return did("1");
+        }
+
+        @Override
+        public TrafficSelector selector() {
+            return SELECTOR;
+        }
+
+        @Override
+        public TrafficTreatment treatment() {
+            return TREATMENT;
+        }
+
+        @Override
+        public int timeout() {
+            return 0;
+        }
+
+        @Override
+        public boolean isPermanent() {
+            return false;
+        }
+
+
+    }
+
+
 }
diff --git a/features/features.xml b/features/features.xml
index efd31cf..551c4de 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -197,6 +197,8 @@
     <feature name="onos-app-sdnip" version="1.0.0"
              description="SDN-IP peering application">
         <feature>onos-api</feature>
+        <feature>onos-app-proxyarp</feature>
+        <feature>onos-app-config</feature>
         <bundle>mvn:org.onlab.onos/onos-app-sdnip/1.0.0-SNAPSHOT</bundle>
     </feature>
 
diff --git a/tools/package/config/README b/tools/package/config/README
index 62b758d..970f87a 100644
--- a/tools/package/config/README
+++ b/tools/package/config/README
@@ -1,2 +1,2 @@
-onos-config command will copy files contained in this directory to ONOS instances according to cell definition
-
+The onos-config command will copy files contained in this directory to ONOS
+instances according to cell definition.
diff --git a/tools/test/topos/oe-nonlinear-10.json b/tools/test/topos/oe-nonlinear-10.json
index f23bb9b..938ce73 100644
--- a/tools/test/topos/oe-nonlinear-10.json
+++ b/tools/test/topos/oe-nonlinear-10.json
@@ -108,33 +108,26 @@
     ],
 
     "links" : [
-        { "src": "of:0000ffffffffff01/10", "dst": "of:0000ffffffffff02/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
-        { "src": "of:0000ffffffffff02/10", "dst": "of:0000ffffffffff03/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
-        { "src": "of:0000ffffffffff03/30", "dst": "of:0000ffffffffff04/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
-        { "src": "of:0000ffffffffff02/20", "dst": "of:0000ffffffffff05/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
-        { "src": "of:0000ffffffffff03/20", "dst": "of:0000ffffffffff06/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
-        { "src": "of:0000ffffffffff05/30", "dst": "of:0000ffffffffff06/20", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
-        { "src": "of:0000ffffffffff05/20", "dst": "of:0000ffffffffff07/21", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
-        { "src": "of:0000ffffffffff06/30", "dst": "of:0000ffffffffff08/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
-        { "src": "of:0000ffffffffff07/30", "dst": "of:0000ffffffffff08/20", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
-        { "src": "of:0000ffffffffff07/20", "dst": "of:0000ffffffffff09/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
-        { "src": "of:0000ffffffffff08/30", "dst": "of:0000ffffffffff0A/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
+        { "src": "of:0000ffffffffff01/50", "dst": "of:0000ffffffffff02/30","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
+        { "src": "of:0000ffffffffff02/50", "dst": "of:0000ffffffffff03/30","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
+        { "src": "of:0000ffffffffff03/50", "dst": "of:0000ffffffffff04/50","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
+        { "src": "of:0000ffffffffff01/20", "dst": "of:0000ffffffffff05/50","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
+        { "src": "of:0000ffffffffff02/20", "dst": "of:0000ffffffffff05/20","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
+        { "src": "of:0000ffffffffff03/20", "dst": "of:0000ffffffffff06/50","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
+        { "src": "of:0000ffffffffff04/20", "dst": "of:0000ffffffffff06/20","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
+        { "src": "of:0000ffffffffff05/30", "dst": "of:0000ffffffffff06/40","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
+        { "src": "of:0000ffffffffff05/40", "dst": "of:0000ffffffffff07/50", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
+        { "src": "of:0000ffffffffff06/30", "dst": "of:0000ffffffffff08/50", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
+        { "src": "of:0000ffffffffff07/20", "dst": "of:0000ffffffffff08/30", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
+        { "src": "of:0000ffffffffff07/30", "dst": "of:0000ffffffffff09/50", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
+        { "src": "of:0000ffffffffff08/20", "dst": "of:0000ffffffffff0A/50", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
         { "src": "of:0000ffffffffff09/20", "dst": "of:0000ffffffffff0A/20", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
 
-        { "src": "of:0000ffffffff0001/2", "dst": "of:0000ffffffffff01/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
-        { "src": "of:0000ffffffff0003/2", "dst": "of:0000ffffffffff03/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
-        { "src": "of:0000ffffffff0004/2", "dst": "of:0000ffffffffff04/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
-        { "src": "of:0000ffffffff0007/2", "dst": "of:0000ffffffffff07/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
-        { "src": "of:0000ffffffff0009/2", "dst": "of:0000ffffffffff09/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
-        { "src": "of:0000ffffffff000A/2", "dst": "of:0000ffffffffff0A/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } }
-    ],
-
-    "hosts" : [
-        { "mac": "00:00:00:00:00:01", "vlan": -1, "location": "of:0000ffffffff0001/1", "ip": "10.0.0.1" },
-        { "mac": "00:00:00:00:00:03", "vlan": -1, "location": "of:0000ffffffff0003/1", "ip": "10.0.0.3" },
-        { "mac": "00:00:00:00:00:04", "vlan": -1, "location": "of:0000ffffffff0004/1", "ip": "10.0.0.4" },
-        { "mac": "00:00:00:00:00:07", "vlan": -1, "location": "of:0000ffffffff0007/1", "ip": "10.0.0.7" },
-        { "mac": "00:00:00:00:00:09", "vlan": -1, "location": "of:0000ffffffff0009/1", "ip": "10.0.0.9" },
-        { "mac": "00:00:00:00:00:0A", "vlan": -1, "location": "of:0000ffffffff000A/1", "ip": "10.0.0.10" }
+        { "src": "of:0000ffffffff0001/2", "dst": "of:0000ffffffffff01/10", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
+        { "src": "of:0000ffffffff0002/2", "dst": "of:0000ffffffffff04/10", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
+        { "src": "of:0000ffffffff0003/2", "dst": "of:0000ffffffffff04/10", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
+        { "src": "of:0000ffffffff0004/2", "dst": "of:0000ffffffffff07/10", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
+        { "src": "of:0000ffffffff0005/2", "dst": "of:0000ffffffffff09/10", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
+        { "src": "of:0000ffffffff0006/2", "dst": "of:0000ffffffffff0A/10",  "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } }
     ]
-}
\ No newline at end of file
+}