Added a REST command to upload topology configuration.
diff --git a/pom.xml b/pom.xml
index cef3b79..8aa5ce4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -88,7 +88,6 @@
                 <version>18.0</version>
             </dependency>
 
-
             <dependency>
                 <groupId>io.netty</groupId>
                 <artifactId>netty</artifactId>
diff --git a/tools/test/cells/single b/tools/test/cells/single
index 6b13756..125477a 100644
--- a/tools/test/cells/single
+++ b/tools/test/cells/single
@@ -7,4 +7,4 @@
 export OCN="192.168.56.103"
 export OCI="${OC1}"
 
-export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core-trivial,onos-cli,onos-openflow,onos-app-fwd,onos-app-proxyarp,onos-app-tvue}"
+export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core-trivial,onos-cli,onos-rest,onos-openflow,onos-app-fwd,onos-app-proxyarp,onos-app-tvue}"
diff --git a/tools/test/topos/oe-linear-3.json b/tools/test/topos/oe-linear-3.json
new file mode 100644
index 0000000..9214bd9
--- /dev/null
+++ b/tools/test/topos/oe-linear-3.json
@@ -0,0 +1,45 @@
+{
+    "devices" : [
+        {
+            "uri": "of:0000ffffffffff01", "mac": "ffffffffffff01", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?",
+            "annotations": { "latitude": 37.6, "longitude": 122.3, "optical.regens": 0 }
+        },
+        {
+            "uri": "of:0000ffffffffff02", "mac": "ffffffffffff02", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?",
+            "annotations": { "latitude": 37.3, "longitude": 121.9, "optical.regens": 0 }
+        },
+        {
+            "uri": "of:0000ffffffffff03", "mac": "ffffffffffff03", "type": "ROADM",
+            "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?",
+            "annotations": { "latitude": 33.9, "longitude": 118.4, "optical.regens": 2 }
+        },
+
+        {
+            "uri": "of:0000ffffffff0001", "mac": "ffffffffff0003", "type": "SWITCH",
+            "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?",
+            "annotations": { "latitude": 37.6, "longitude": 122.3 }
+        },
+        {
+            "uri": "of:0000ffffffff0002", "mac": "ffffffffff0002", "type": "SWITCH",
+            "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?",
+            "annotations": { "latitude": 37.3, "longitude": 121.9 }
+        }
+    ],
+
+    "links" : [
+        { "src": "of:0000ffffffffff01/10", "dst": "of:0000ffffffffff03/30", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM" } },
+        { "src": "of:0000ffffffffff02/20", "dst": "of:0000ffffffffff03/31", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM" } },
+
+        { "src": "of:0000ffffffff0001/10", "dst": "of:0000ffffffffff01/11", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect" } },
+        { "src": "of:0000ffffffff0002/10", "dst": "of:0000ffffffffff02/21", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect" } }
+    ],
+
+    "hosts" : [
+        { "mac": "a0:00:00:00:00:11", "vlan": -1, "location": "of:0000ffffffff0001/11", "ip": "1.2.3.4" },
+        { "mac": "a0:00:00:00:00:12", "vlan": -1, "location": "of:0000ffffffff0001/12", "ip": "1.2.3.5" },
+        { "mac": "a0:00:00:00:00:21", "vlan": -1, "location": "of:0000ffffffff0002/11", "ip": "2.2.3.4" },
+        { "mac": "a0:00:00:00:00:22", "vlan": -1, "location": "of:0000ffffffff0002/12", "ip": "2.2.3.5" }
+    ]
+}
\ No newline at end of file
diff --git a/utils/misc/src/main/java/org/onlab/packet/ChassisId.java b/utils/misc/src/main/java/org/onlab/packet/ChassisId.java
index 3029647..5b48e63 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ChassisId.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ChassisId.java
@@ -32,7 +32,7 @@
      * @param value the value to use.
      */
     public ChassisId(String value) {
-        this.value = Long.valueOf(value);
+        this.value = Long.valueOf(value, 16);
     }
 
     /**
diff --git a/web/api/pom.xml b/web/api/pom.xml
index e6f9ff8..da8fd1c 100644
--- a/web/api/pom.xml
+++ b/web/api/pom.xml
@@ -23,12 +23,6 @@
             <version>1.0.0-SNAPSHOT</version>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>com.google.guava</groupId>
-            <artifactId>guava</artifactId>
-            <scope>test</scope>
-        </dependency>
-
     </dependencies>
 
     <properties>
diff --git a/web/api/src/main/java/org/onlab/onos/rest/ConfigProvider.java b/web/api/src/main/java/org/onlab/onos/rest/ConfigProvider.java
new file mode 100644
index 0000000..3120511
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/ConfigProvider.java
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultAnnotations;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.Host;
+import org.onlab.onos.net.HostId;
+import org.onlab.onos.net.HostLocation;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.MastershipRole;
+import org.onlab.onos.net.SparseAnnotations;
+import org.onlab.onos.net.device.DefaultDeviceDescription;
+import org.onlab.onos.net.device.DeviceDescription;
+import org.onlab.onos.net.device.DeviceProvider;
+import org.onlab.onos.net.device.DeviceProviderRegistry;
+import org.onlab.onos.net.device.DeviceProviderService;
+import org.onlab.onos.net.host.DefaultHostDescription;
+import org.onlab.onos.net.host.HostProvider;
+import org.onlab.onos.net.host.HostProviderRegistry;
+import org.onlab.onos.net.host.HostProviderService;
+import org.onlab.onos.net.link.DefaultLinkDescription;
+import org.onlab.onos.net.link.LinkProvider;
+import org.onlab.onos.net.link.LinkProviderRegistry;
+import org.onlab.onos.net.link.LinkProviderService;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.packet.ChassisId;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+import java.net.URI;
+import java.util.Iterator;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.PortNumber.portNumber;
+
+/**
+ * Provider of devices and links parsed from a JSON configuration structure.
+ */
+class ConfigProvider implements DeviceProvider, LinkProvider, HostProvider {
+
+    private static final ProviderId PID =
+            new ProviderId("cfg", "org.onlab.onos.rest", true);
+
+    private final JsonNode cfg;
+    private final DeviceProviderRegistry deviceProviderRegistry;
+    private final LinkProviderRegistry linkProviderRegistry;
+    private final HostProviderRegistry hostProviderRegistry;
+
+    /**
+     * Creates a new configuration provider.
+     *
+     * @param cfg                    JSON configuration
+     * @param deviceProviderRegistry device provider registry
+     * @param linkProviderRegistry   link provider registry
+     * @param hostProviderRegistry   host provider registry
+     */
+    ConfigProvider(JsonNode cfg,
+                   DeviceProviderRegistry deviceProviderRegistry,
+                   LinkProviderRegistry linkProviderRegistry,
+                   HostProviderRegistry hostProviderRegistry) {
+        this.cfg = checkNotNull(cfg, "Configuration cannot be null");
+        this.deviceProviderRegistry = checkNotNull(deviceProviderRegistry, "Device provider registry cannot be null");
+        this.linkProviderRegistry = checkNotNull(linkProviderRegistry, "Link provider registry cannot be null");
+        this.hostProviderRegistry = checkNotNull(hostProviderRegistry, "Host provider registry cannot be null");
+    }
+
+    /**
+     * Parses the given JSON and provides links as configured.
+     */
+    void parse() {
+        parseDevices();
+        parseLinks();
+        parseHosts();
+    }
+
+    // Parses the given JSON and provides devices.
+    private void parseDevices() {
+        try {
+            DeviceProviderService dps = deviceProviderRegistry.register(this);
+            JsonNode nodes = cfg.get("devices");
+            if (nodes != null) {
+                for (JsonNode node : nodes) {
+                    parseDevice(dps, node);
+                }
+            }
+        } finally {
+            deviceProviderRegistry.unregister(this);
+        }
+    }
+
+    // Parses the given node with device data and supplies the device.
+    private void parseDevice(DeviceProviderService dps, JsonNode node) {
+        URI uri = URI.create(get(node, "uri"));
+        Device.Type type = Device.Type.valueOf(get(node, "type"));
+        String mfr = get(node, "mfr");
+        String hw = get(node, "hw");
+        String sw = get(node, "sw");
+        String serial = get(node, "serial");
+        ChassisId cid = new ChassisId(get(node, "mac"));
+        SparseAnnotations annotations = annotations(node.get("annotations"));
+
+        DeviceDescription desc =
+                new DefaultDeviceDescription(uri, type, mfr, hw, sw, serial,
+                                             cid, annotations);
+        dps.deviceConnected(deviceId(uri), desc);
+    }
+
+    // Parses the given JSON and provides links as configured.
+    private void parseLinks() {
+        try {
+            LinkProviderService lps = linkProviderRegistry.register(this);
+            JsonNode nodes = cfg.get("links");
+            if (nodes != null) {
+                for (JsonNode node : nodes) {
+                    parseLink(lps, node, false);
+                    if (!node.has("halfplex")) {
+                        parseLink(lps, node, true);
+                    }
+                }
+            }
+        } finally {
+            linkProviderRegistry.unregister(this);
+        }
+    }
+
+    // Parses the given node with link data and supplies the link.
+    private void parseLink(LinkProviderService lps, JsonNode node, boolean reverse) {
+        ConnectPoint src = connectPoint(get(node, "src"));
+        ConnectPoint dst = connectPoint(get(node, "dst"));
+        Link.Type type = Link.Type.valueOf(get(node, "type"));
+        SparseAnnotations annotations = annotations(node.get("annotations"));
+
+        DefaultLinkDescription desc = reverse ?
+                new DefaultLinkDescription(dst, src, type, annotations) :
+                new DefaultLinkDescription(src, dst, type, annotations);
+        lps.linkDetected(desc);
+    }
+
+    // Parses the given JSON and provides hosts as configured.
+    private void parseHosts() {
+        try {
+            HostProviderService hps = hostProviderRegistry.register(this);
+            JsonNode nodes = cfg.get("hosts");
+            if (nodes != null) {
+                for (JsonNode node : nodes) {
+                    parseHost(hps, node);
+                }
+            }
+        } finally {
+            hostProviderRegistry.unregister(this);
+        }
+    }
+
+    // Parses the given node with host data and supplies the host.
+    private void parseHost(HostProviderService hps, JsonNode node) {
+        MacAddress mac = MacAddress.valueOf(get(node, "mac"));
+        VlanId vlanId = VlanId.vlanId(node.get("vlan").shortValue());
+        HostId hostId = HostId.hostId(mac, vlanId);
+        SparseAnnotations annotations = annotations(node.get("annotations"));
+        HostLocation location = new HostLocation(connectPoint(get(node, "location")), 0);
+        IpPrefix ip = IpPrefix.valueOf(get(node, "ip"));
+
+        DefaultHostDescription desc =
+                new DefaultHostDescription(mac, vlanId, location, ip, annotations);
+        hps.hostDetected(hostId, desc);
+    }
+
+    // Produces set of annotations from the given JSON node.
+    private SparseAnnotations annotations(JsonNode node) {
+        if (node == null) {
+            return null;
+        }
+
+        DefaultAnnotations.Builder builder = DefaultAnnotations.builder();
+        Iterator<String> it = node.fieldNames();
+        while (it.hasNext()) {
+            String k = it.next();
+            builder.set(k, node.get(k).asText());
+        }
+        return builder.build();
+    }
+
+    // Produces a connection point from the specified uri/port text.
+    private ConnectPoint connectPoint(String text) {
+        int i = text.lastIndexOf("/");
+        return new ConnectPoint(deviceId(text.substring(0, i)),
+                                portNumber(text.substring(i + 1)));
+    }
+
+    // Returns string form of the named property in the given JSON object.
+    private String get(JsonNode node, String name) {
+        return node.path(name).asText();
+    }
+
+    @Override
+    public void triggerProbe(Device device) {
+    }
+
+    @Override
+    public void roleChanged(Device device, MastershipRole newRole) {
+    }
+
+    @Override
+    public void triggerProbe(Host host) {
+    }
+
+    @Override
+    public ProviderId id() {
+        return PID;
+    }
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java b/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java
new file mode 100644
index 0000000..219abbd
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.onlab.onos.net.device.DeviceProviderRegistry;
+import org.onlab.onos.net.host.HostProviderRegistry;
+import org.onlab.onos.net.link.LinkProviderRegistry;
+import org.onlab.rest.BaseResource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Resource that acts as an ancillary provider for uploading pre-configured
+ * devices, ports and links.
+ */
+@Path("config")
+public class ConfigResource extends BaseResource {
+
+    @POST
+    @Path("topology")
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response topology(InputStream input) throws IOException {
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode cfg = mapper.readTree(input);
+        new ConfigProvider(cfg, get(DeviceProviderRegistry.class),
+                           get(LinkProviderRegistry.class),
+                           get(HostProviderRegistry.class)).parse();
+        return Response.ok(mapper.createObjectNode().toString()).build();
+    }
+
+}
diff --git a/web/api/src/test/java/org/onlab/onos/rest/topo.json b/web/api/src/test/java/org/onlab/onos/rest/topo.json
new file mode 100644
index 0000000..cdef976
--- /dev/null
+++ b/web/api/src/test/java/org/onlab/onos/rest/topo.json
@@ -0,0 +1,19 @@
+{
+    "devices" : [
+        {
+            "uri": "of:00000000000001", "type": "ROADM", "mfr": "Foo, Inc.", "hw": "Alpha", "sw": "1.2.3",
+            "serial": "ab321", "mac": "00000000000001", "annotations": {"foo": "bar"},
+            "ports": []
+        },
+        {
+            "uri": "of:00000000000002", "type": "ROADM", "mfr": "Foo, Inc.", "hw": "Alpha", "sw": "1.2.3",
+            "serial": "ab456", "mac": "00000000000002", "annotations": {"foo": "bar"},
+            "ports": []
+        }
+    ],
+
+    "links" : [
+        { "src": "of:00000000000001/1", "dst": "of:00000000000002/1", "type": "OPTICAL" },
+        { "src": "of:00000000000002/1", "dst": "of:00000000000001/1", "type": "OPTICAL" }
+    ]
+}
\ No newline at end of file
diff --git a/web/pom.xml b/web/pom.xml
index 3e9f2a0..ebe4d89 100644
--- a/web/pom.xml
+++ b/web/pom.xml
@@ -44,6 +44,11 @@
         </dependency>
 
         <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>com.sun.jersey</groupId>
             <artifactId>jersey-servlet</artifactId>
         </dependency>
@@ -93,6 +98,7 @@
                             ${project.groupId}.${project.artifactId}
                         </Bundle-SymbolicName>
                         <Import-Package>
+                            org.slf4j,
                             org.osgi.framework,
                             javax.ws.rs,javax.ws.rs.core,
                             com.sun.jersey.api.core,
@@ -100,6 +106,8 @@
                             com.sun.jersey.server.impl.container.servlet,
                             com.fasterxml.jackson.databind,
                             com.fasterxml.jackson.databind.node,
+                            com.google.common.base.*,
+                            org.onlab.packet.*,
                             org.onlab.rest.*,
                             org.onlab.onos.*
                         </Import-Package>