Merge remote-tracking branch 'origin/master' into dev/murrelet
diff --git a/apps/config/src/main/java/org/onosproject/config/DynamicConfigEvent.java b/apps/config/src/main/java/org/onosproject/config/DynamicConfigEvent.java
index a2e513e..1e4296a 100644
--- a/apps/config/src/main/java/org/onosproject/config/DynamicConfigEvent.java
+++ b/apps/config/src/main/java/org/onosproject/config/DynamicConfigEvent.java
@@ -67,4 +67,17 @@
     public DynamicConfigEvent(Type type, ResourceId path) {
         super(type, path);
     }
-}
\ No newline at end of file
+
+    /**
+     * Returns ResourceId relative to root node.
+     * (=root will not be part of ResourceId returned)
+     *
+     * @return resource Id
+     */
+    @Override
+    public ResourceId subject() {
+        return super.subject();
+    }
+
+    // TODO add DataNode as event payload
+}
diff --git a/apps/config/src/main/java/org/onosproject/config/DynamicConfigService.java b/apps/config/src/main/java/org/onosproject/config/DynamicConfigService.java
index a5e881a..bb562e8 100644
--- a/apps/config/src/main/java/org/onosproject/config/DynamicConfigService.java
+++ b/apps/config/src/main/java/org/onosproject/config/DynamicConfigService.java
@@ -32,6 +32,12 @@
 public interface DynamicConfigService
         extends ListenerService<DynamicConfigEvent, DynamicConfigListener> {
 
+    // FIXME revisit and verify ResourceId documentation.
+    // it is likely that it actually is not expecting absolute ResourceId
+
+    // TODO revisit which path ResourceId these API should accepting.
+    // there is inconsistency, some expect parent, some expect node itself
+
     /**
      * Creates a new node in the dynamic config store.
      * This method would throw an exception if there is a node with the same
@@ -39,11 +45,11 @@
      * nodes were not present in the path leading up to the requested node.
      * Failure reason will be the error message in the exception.
      *
-     * @param path data structure with absolute path to the parent
+     * @param parent data structure with absolute path to the parent
      * @param node recursive data structure, holding a leaf node or a subtree
      * @throws FailedException if the new node could not be created
      */
-    void createNode(ResourceId path, DataNode node);
+    void createNode(ResourceId parent, DataNode node);
 
     /**
      * Reads the requested node form the dynamic config store.
@@ -76,11 +82,11 @@
      * parent nodes in the path were not present.
      * Failure reason will be the error message in the exception.
      *
-     * @param path data structure with absolute path to the parent
+     * @param parent data structure with absolute path to the parent
      * @param node recursive data structure, holding a leaf node or a subtree
      * @throws FailedException if the update request failed for any reason
      */
-    void updateNode(ResourceId path, DataNode node);
+    void updateNode(ResourceId parent, DataNode node);
 
     /**
      * Replaces nodes in the dynamic config store.
@@ -89,11 +95,11 @@
      * the requested node or any of the parent nodes in the path were not
      * present. Failure reason will be the error message in the exception.
      *
-     * @param path data structure with absolute path to the parent
+     * @param parent data structure with absolute path to the parent
      * @param node recursive data structure, holding a leaf node or a subtree
      * @throws FailedException if the replace request failed
      */
-    void replaceNode(ResourceId path, DataNode node);
+    void replaceNode(ResourceId parent, DataNode node);
 
     /**
      * Removes a node from the dynamic config store.
diff --git a/apps/config/src/main/java/org/onosproject/config/FailedException.java b/apps/config/src/main/java/org/onosproject/config/FailedException.java
index 0115c33..80559ed 100644
--- a/apps/config/src/main/java/org/onosproject/config/FailedException.java
+++ b/apps/config/src/main/java/org/onosproject/config/FailedException.java
@@ -23,6 +23,8 @@
 @Beta
 public class FailedException extends RuntimeException {
 
+    private static final long serialVersionUID = 266049754055616595L;
+
     /**
      * Constructs a new runtime exception with no error message.
      */
diff --git a/apps/config/src/main/java/org/onosproject/config/InvalidFilterException.java b/apps/config/src/main/java/org/onosproject/config/InvalidFilterException.java
index fcb73b0..1406b20 100644
--- a/apps/config/src/main/java/org/onosproject/config/InvalidFilterException.java
+++ b/apps/config/src/main/java/org/onosproject/config/InvalidFilterException.java
@@ -23,6 +23,8 @@
 @Beta
 public class InvalidFilterException extends RuntimeException {
 
+    private static final long serialVersionUID = 7270417891847061766L;
+
     /**
      * Constructs a new runtime exception with no error message.
      */
@@ -38,4 +40,6 @@
     public InvalidFilterException(String message) {
         super(message);
     }
+
+    // TODO add constructor with cause
 }
\ No newline at end of file
diff --git a/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java b/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
index 0236657..dc93e58 100644
--- a/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
+++ b/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
@@ -26,6 +26,8 @@
 import org.onosproject.yang.model.NodeKey;
 import org.onosproject.yang.model.ResourceId;
 
+import com.google.common.annotations.Beta;
+
 // FIXME add non-trivial examples
 /**
  * Utilities to work on the ResourceId.
@@ -38,6 +40,7 @@
  *
  */
 //FIXME add javadocs
+@Beta
 public final class ResourceIdParser {
 
     /**
@@ -129,6 +132,7 @@
     }
 
     public static String appendNodeKey(String path, NodeKey key) {
+        // FIXME this is not handling root path exception
         return (path + EL_SEP + key.schemaId().name() + NM_SEP + key.schemaId().namespace());
     }
 
@@ -199,7 +203,8 @@
         while (itr.hasNext()) {
             nodeKeyList.add(itr.next());
         }
-        if (nodeKeyList.get(0).schemaId().name().compareTo("/") == 0) {
+        // exception for dealing with root
+        if (nodeKeyList.get(0).schemaId().name().equals("/")) {
             nodeKeyList.remove(0);
         }
         for (NodeKey key : nodeKeyList) {
diff --git a/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java b/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java
index 7c73922..97731a6 100644
--- a/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java
+++ b/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java
@@ -29,6 +29,7 @@
 import org.onosproject.config.FailedException;
 import org.onosproject.config.Filter;
 import org.onosproject.config.ResourceIdParser;
+import org.onosproject.d.config.ResourceIds;
 import org.onosproject.store.AbstractStore;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.onosproject.store.service.AsyncDocumentTree;
@@ -57,7 +58,9 @@
 import org.slf4j.LoggerFactory;
 
 import java.math.BigInteger;
+import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 
@@ -75,11 +78,19 @@
 public class DistributedDynamicConfigStore
         extends AbstractStore<DynamicConfigEvent, DynamicConfigStoreDelegate>
         implements DynamicConfigStore {
+
     private final Logger log = LoggerFactory.getLogger(getClass());
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected StorageService storageService;
+
+    // FIXME transactionally mutate the 2 or consolidate into 1 AsyncDocTree
+    // effectively tree structure only
     private AsyncDocumentTree<DataNode.Type> keystore;
+    // TODO Can we pass DocumentPath directly to ConsistentMap?
+    // Map<DocumentPath as String, leaf value>
     private ConsistentMap<String, LeafNode> objectStore;
+
     private final DocumentTreeListener<DataNode.Type> klistener = new InternalDocTreeListener();
     private final MapEventListener<String, LeafNode> olistener = new InternalMapListener();
 
@@ -87,7 +98,7 @@
     public void activateStore() {
         KryoNamespace.Builder kryoBuilder = new KryoNamespace.Builder()
                 .register(KryoNamespaces.BASIC)
-                .register(java.lang.Class.class)
+                .register(Class.class)
                 .register(DataNode.Type.class)
                 .register(LeafNode.class)
                 .register(InnerNode.class)
@@ -98,7 +109,7 @@
                 .register(ListKey.class)
                 .register(KeyLeaf.class)
                 .register(BigInteger.class)
-                .register(java.util.LinkedHashMap.class);
+                .register(LinkedHashMap.class);
         keystore = storageService.<DataNode.Type>documentTreeBuilder()
                 .withSerializer(Serializer.using(kryoBuilder.build()))
                 .withName("config-key-store")
@@ -124,8 +135,10 @@
 
     @Override
     public CompletableFuture<Boolean>
-    addNode(ResourceId complete, DataNode node) {
-        String spath = ResourceIdParser.parseResId(complete);
+    addNode(ResourceId parent, DataNode node) {
+        String spath = ResourceIdParser.parseResId(parent);
+        log.trace(" addNode({}, {})", parent, node);
+        log.trace(" spath={}", spath);
         if (spath == null) {
             throw new FailedException("Invalid RsourceId, cannot create Node");
         }
@@ -134,12 +147,19 @@
                 throw new FailedException("Node or parent does not exist for " + spath);
             }
         }
-        spath = ResourceIdParser.appendNodeKey(spath, node.key());
-        parseNode(spath, node);
+        ResourceId abs = ResourceIds.resourceId(parent, node);
+        //spath = ResourceIdParser.appendNodeKey(spath, node.key());
+        parseNode(ResourceIdParser.parseResId(abs), node);
         return CompletableFuture.completedFuture(true);
     }
 
+    // FIXME this is more like addNode
+    /**
+     * @param path pointing to {@code node}
+     * @param node node
+     */
     private void parseNode(String path, DataNode node) {
+        log.trace("parseNode({}, {})", path, node);
         if (completeVersioned(keystore.get(DocumentPath.from(path))) != null) {
             throw new FailedException("Requested node already present in the" +
                                               " store, please use an update method");
@@ -167,12 +187,19 @@
         }
     }
 
+    // FIXME this is more like addInnteNode
+    /**
+     * @param path pointing to {@code node}
+     * @param node node
+     */
     private void traverseInner(String path, InnerNode node) {
+        log.trace("traverseInner({}, {})", path, node);
         addKey(path, node.type());
         Map<NodeKey, DataNode> entries = node.childNodes();
         if (entries.size() == 0) {
             return;
         }
+        // FIXME ignoring results
         entries.forEach((k, v) -> {
             String tempPath;
             tempPath = ResourceIdParser.appendNodeKey(path, v.key());
@@ -198,11 +225,19 @@
     }
 
     private Boolean addKey(String path, DataNode.Type type) {
-        if (completeVersioned(keystore.get(DocumentPath.from(path))) != null) {
-            completeVersioned(keystore.set(DocumentPath.from(path), type));
+        log.trace("addKey({}, {})", path, type);
+        DocumentPath dpath = DocumentPath.from(path);
+        log.trace("dpath={}", dpath);
+        // FIXME Not atomic, should probably use create or replace
+        if (completeVersioned(keystore.get(dpath)) != null) {
+            completeVersioned(keystore.set(dpath, type));
+            log.trace("true");
             return true;
         }
-        return complete(keystore.create(DocumentPath.from(path), type));
+        log.trace(" keystore.create({}, {})", dpath, type);
+        Boolean result = complete(keystore.create(dpath, type));
+        log.trace("{}", result);
+        return result;
     }
 
     @Override
@@ -489,10 +524,10 @@
             throw new FailedException(e.getCause().getMessage());
         } catch (ExecutionException e) {
             if (e.getCause() instanceof IllegalDocumentModificationException) {
-                throw new FailedException("Node or parent doesnot exist or is root or is not a Leaf Node",
+                throw new FailedException("Node or parent does not exist or is root or is not a Leaf Node",
                                           e.getCause());
             } else if (e.getCause() instanceof NoSuchDocumentPathException) {
-                throw new FailedException("Resource id does not exist", e.getCause());
+                throw new FailedException("ResourceId does not exist", e.getCause());
             } else {
                 throw new FailedException("Datastore operation failed", e.getCause());
             }
@@ -500,25 +535,8 @@
     }
 
     private <T> T completeVersioned(CompletableFuture<Versioned<T>> future) {
-        try {
-            if (future.get() != null) {
-                return future.get().value();
-            } else {
-                return null;
-            }
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new FailedException(e.getCause().getMessage());
-        } catch (ExecutionException e) {
-            if (e == null) {
-                throw new FailedException("Unknown Exception");
-            } else if (e.getCause() instanceof IllegalDocumentModificationException) {
-                throw new FailedException("Node or parent does not exist or is root or is not a Leaf Node");
-            } else if (e.getCause() instanceof NoSuchDocumentPathException) {
-                throw new FailedException("Resource id does not exist");
-            } else {
-                throw new FailedException("Datastore operation failed");
-            }
-        }
-    }
-}
\ No newline at end of file
+        return Optional.ofNullable(complete(future))
+                        .map(Versioned::value)
+                        .orElse(null);
+   }
+}
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
index 39e53ff..2ef43b7 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
@@ -722,7 +722,10 @@
             udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
         } else {
             // forward to another dhcp relay
-            udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
+            // FIXME: Currently we assume the DHCP comes from a L2 relay with
+            // Option 82, this might not work if DHCP message comes from
+            // L3 relay.
+            udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
         }
 
         udpPacket.setPayload(dhcpPayload);
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpensatckRouterWebResource.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackRouterWebResource.java
similarity index 98%
rename from apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpensatckRouterWebResource.java
rename to apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackRouterWebResource.java
index 5b769224..eff151d 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpensatckRouterWebResource.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackRouterWebResource.java
@@ -49,7 +49,7 @@
  */
 
 @Path("routers")
-public class OpensatckRouterWebResource extends AbstractWebResource {
+public class OpenstackRouterWebResource extends AbstractWebResource {
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     private static final String MESSAGE_ROUTER = "Received router %s request";
diff --git a/apps/openstacknetworking/src/main/webapp/WEB-INF/web.xml b/apps/openstacknetworking/src/main/webapp/WEB-INF/web.xml
index 99a0027..89f5be2 100644
--- a/apps/openstacknetworking/src/main/webapp/WEB-INF/web.xml
+++ b/apps/openstacknetworking/src/main/webapp/WEB-INF/web.xml
@@ -29,7 +29,7 @@
                 org.onosproject.openstacknetworking.web.OpenstackPortWebResource,
                 org.onosproject.openstacknetworking.web.OpenstackNetworkWebResource,
                 org.onosproject.openstacknetworking.web.OpenstackSubnetWebResource,
-                org.onosproject.openstacknetworking.web.OpensatckRouterWebResource,
+                org.onosproject.openstacknetworking.web.OpenstackRouterWebResource,
                 org.onosproject.openstacknetworking.web.OpenstackFloatingIpWebResource,
                 org.onosproject.openstacknetworking.web.OpenstackSecurityGroupWebResource,
                 org.onosproject.openstacknetworking.web.OpenstackSecurityGroupRuleWebResource
diff --git a/apps/p4-tutorial/icmpdropper/BUCK b/apps/p4-tutorial/icmpdropper/BUCK
new file mode 100644
index 0000000..026827d
--- /dev/null
+++ b/apps/p4-tutorial/icmpdropper/BUCK
@@ -0,0 +1,25 @@
+COMPILE_DEPS = [
+    '//lib:CORE_DEPS',
+    '//apps/p4-tutorial/pipeconf:onos-apps-p4-tutorial-pipeconf',
+]
+
+osgi_jar (
+    deps = COMPILE_DEPS,
+)
+
+BUNDLES = [
+    '//apps/p4-tutorial/icmpdropper:onos-apps-p4-tutorial-icmpdropper',
+]
+
+onos_app (
+    app_name = 'org.onosproject.p4tutorial.icmpdropper',
+    title = 'ICMP Dropper (P4 Tutorial)',
+    category = 'Security',
+    url = 'http://onosproject.org',
+    description = 'Inhibits forwarding of ICMP packets for PI-enabled devices using the ' +
+                    'p4-tutorial-pipeconf.',
+    included_bundles = BUNDLES,
+    required_apps = [
+        'org.onosproject.p4tutorial.pipeconf',
+    ]
+)
diff --git a/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/IcmpDropper.java b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/IcmpDropper.java
new file mode 100644
index 0000000..8914ee7
--- /dev/null
+++ b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/IcmpDropper.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4tutorial.icmpdropper;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.app.ApplicationAdminService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.pi.model.PiPipelineProgrammable;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionId;
+import org.onosproject.net.pi.runtime.PiHeaderFieldId;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
+import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.p4tutorial.pipeconf.PipeconfFactory;
+import org.slf4j.Logger;
+
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Simple application that drops all ICMP packets.
+ */
+@Component(immediate = true)
+public class IcmpDropper {
+
+    private static final Logger log = getLogger(IcmpDropper.class);
+
+    private static final String APP_NAME = "org.onosproject.p4tutorial.icmpdropper";
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private FlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private ApplicationAdminService appService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private PiPipeconfService piPipeconfService;
+
+    private final DeviceListener deviceListener = new InternalDeviceListener();
+
+    private ApplicationId appId;
+
+    @Activate
+    public void activate() {
+        log.info("Starting...");
+
+        appId = coreService.registerApplication(APP_NAME);
+        // Register listener for handling new devices.
+        deviceService.addListener(deviceListener);
+        // Install rules to existing devices.
+        deviceService.getDevices()
+                .forEach(device -> installDropRule(device.id()));
+
+        log.info("STARTED", appId.id());
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopping...");
+
+        deviceService.removeListener(deviceListener);
+        flowRuleService.removeFlowRulesById(appId);
+
+        log.info("STOPPED");
+    }
+
+    private boolean checkPipeconf(Device device) {
+        if (!device.is(PiPipelineProgrammable.class)) {
+            // Device is not PI-pipeline programmable. Ignore.
+            return false;
+        }
+        if (!piPipeconfService.ofDevice(device.id()).isPresent() ||
+                !piPipeconfService.ofDevice(device.id()).get().equals(PipeconfFactory.PIPECONF_ID)) {
+            log.warn("Device {} has pipeconf {} instead of {}, can't install flow rule for this device",
+                     device.id(), piPipeconfService.ofDevice(device.id()).get(), PipeconfFactory.PIPECONF_ID);
+            return false;
+        }
+
+        return true;
+    }
+
+    private void installDropRule(DeviceId deviceId) {
+        PiHeaderFieldId ipv4ProtoFieldId = PiHeaderFieldId.of("ipv4", "protocol");
+        PiActionId dropActionId = PiActionId.of("_drop");
+
+        PiCriterion piCriterion = PiCriterion.builder()
+                .matchExact(ipv4ProtoFieldId, (byte) 0x01)
+                .build();
+        PiAction dropAction = PiAction.builder()
+                .withId(dropActionId)
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .forTable(PiTableId.of("ip_proto_filter_table"))
+                .fromApp(appId)
+                .makePermanent()
+                .withPriority(1000)
+                .withSelector(DefaultTrafficSelector.builder()
+                                      .matchPi(piCriterion)
+                                      .build())
+                .withTreatment(
+                        DefaultTrafficTreatment.builder()
+                                .piTableAction(dropAction)
+                                .build())
+                .build();
+
+        log.warn("Installing ICMP drop rule to {}", deviceId);
+
+        flowRuleService.applyFlowRules(flowRule);
+    }
+
+    /**
+     * A listener of device events that installs a rule to drop packet for each new device.
+     */
+    private class InternalDeviceListener implements DeviceListener {
+        @Override
+        public void event(DeviceEvent event) {
+            Device device = event.subject();
+            if (checkPipeconf(device)) {
+                installDropRule(device.id());
+            }
+        }
+
+        @Override
+        public boolean isRelevant(DeviceEvent event) {
+            // Reacts only to new devices.
+            return event.type() == DEVICE_ADDED;
+        }
+    }
+}
diff --git a/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/package-info.java b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/package-info.java
new file mode 100644
index 0000000..5ea1180
--- /dev/null
+++ b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+/**
+ * P4 tutorial application classes.
+ */
+package org.onosproject.p4tutorial.icmpdropper;
\ No newline at end of file
diff --git a/apps/p4-tutorial/pipeconf/BUCK b/apps/p4-tutorial/pipeconf/BUCK
new file mode 100644
index 0000000..038736f
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/BUCK
@@ -0,0 +1,29 @@
+COMPILE_DEPS = [
+    '//lib:CORE_DEPS',
+    '//lib:minimal-json',
+    '//incubator/bmv2/model:onos-incubator-bmv2-model',
+    '//drivers/default:onos-drivers-default',
+    '//protocols/p4runtime/api:onos-protocols-p4runtime-api',
+]
+
+osgi_jar (
+    deps = COMPILE_DEPS,
+)
+
+BUNDLES = [
+    '//apps/p4-tutorial/pipeconf:onos-apps-p4-tutorial-pipeconf',
+    '//drivers/default:onos-drivers-default',
+    '//incubator/bmv2/model:onos-incubator-bmv2-model',
+]
+
+onos_app (
+    app_name = 'org.onosproject.p4tutorial.pipeconf',
+    title = 'P4 Tutorial Pipeconf',
+    category = 'Pipeconf',
+    url = 'http://onosproject.org',
+    description = 'Provides pipeconf for the ONOS-P4 Tutorial',
+    included_bundles = BUNDLES,
+    required_apps = [
+        'org.onosproject.drivers.p4runtime',
+    ]
+)
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java
new file mode 100644
index 0000000..b284eaa
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4tutorial.pipeconf;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
+import org.onosproject.driver.pipeline.DefaultSingleTablePipeline;
+import org.onosproject.net.behaviour.Pipeliner;
+import org.onosproject.net.device.PortStatisticsDiscovery;
+import org.onosproject.net.pi.model.DefaultPiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
+
+import java.net.URL;
+
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON;
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
+
+/**
+ * Component that produces and registers a pipeconf when loaded.
+ */
+@Component(immediate = true)
+public final class PipeconfFactory {
+
+    public static final PiPipeconfId PIPECONF_ID = new PiPipeconfId("p4-tutorial-pipeconf");
+    private static final URL P4INFO_URL = PipeconfFactory.class.getResource("/main.p4info");
+    private static final URL BMV2_JSON_URL = PipeconfFactory.class.getResource("/main.json");
+    private static final PiPipelineModel PIPELINE_MODEL = Bmv2PipelineModelParser.parse(BMV2_JSON_URL);
+
+    private static final PiPipeconf PIPECONF = DefaultPiPipeconf.builder()
+            .withId(PIPECONF_ID)
+            .withPipelineModel(PIPELINE_MODEL)
+            .addBehaviour(PiPipelineInterpreter.class, PipelineInterpreterImpl.class)
+            .addBehaviour(PortStatisticsDiscovery.class, PortStatisticsDiscoveryImpl.class)
+            // Since main.p4 defines only 1 table, we re-use the existing single-table pipeliner.
+            .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
+            .addExtension(P4_INFO_TEXT, P4INFO_URL)
+            .addExtension(BMV2_JSON, BMV2_JSON_URL)
+            .build();
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private PiPipeconfService piPipeconfService;
+
+    @Activate
+    public void activate() {
+        // Registers the pipeconf at component activation.
+        piPipeconfService.register(PIPECONF);
+    }
+
+    @Deactivate
+    public void deactivate() {
+        piPipeconfService.remove(PIPECONF.id());
+    }
+}
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java
new file mode 100644
index 0000000..c783236
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4tutorial.pipeconf;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import org.onlab.packet.Ethernet;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiCounterId;
+import org.onosproject.net.pi.runtime.PiHeaderFieldId;
+import org.onosproject.net.pi.runtime.PiPacketMetadata;
+import org.onosproject.net.pi.runtime.PiPacketMetadataId;
+import org.onosproject.net.pi.runtime.PiPacketOperation;
+import org.onosproject.net.pi.runtime.PiTableId;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import static java.lang.String.format;
+import static java.util.stream.Collectors.toList;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onlab.util.ImmutableByteSequence.fit;
+import static org.onosproject.net.PortNumber.CONTROLLER;
+import static org.onosproject.net.PortNumber.FLOOD;
+import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
+import static org.onosproject.net.pi.runtime.PiPacketOperation.Type.PACKET_OUT;
+
+/**
+ * Implementation of a PI interpreter for the main.p4 program.
+ */
+public final class PipelineInterpreterImpl extends AbstractHandlerBehaviour implements PiPipelineInterpreter {
+
+    private static final String TABLE0 = "table0";
+    private static final String IP_PROTO_FILTER_TABLE = "ip_proto_filter_table";
+    private static final String SEND_TO_CPU = "send_to_cpu";
+    private static final String PORT = "port";
+    private static final String DROP = "_drop";
+    private static final String SET_EGRESS_PORT = "set_egress_port";
+    private static final String EGRESS_PORT = "egress_port";
+    private static final String INGRESS_PORT = "ingress_port";
+    private static final String ETHERNET = "ethernet";
+    private static final String STANDARD_METADATA = "standard_metadata";
+    private static final int PORT_FIELD_BITWIDTH = 9;
+
+    private static final PiHeaderFieldId INGRESS_PORT_ID = PiHeaderFieldId.of(STANDARD_METADATA, "ingress_port");
+    private static final PiHeaderFieldId ETH_DST_ID = PiHeaderFieldId.of(ETHERNET, "dst_addr");
+    private static final PiHeaderFieldId ETH_SRC_ID = PiHeaderFieldId.of(ETHERNET, "src_addr");
+    private static final PiHeaderFieldId ETH_TYPE_ID = PiHeaderFieldId.of(ETHERNET, "ether_type");
+    private static final PiTableId TABLE0_ID = PiTableId.of(TABLE0);
+    private static final PiTableId IP_PROTO_FILTER_TABLE_ID = PiTableId.of(IP_PROTO_FILTER_TABLE);
+
+    private static final BiMap<Integer, PiTableId> TABLE_MAP = ImmutableBiMap.of(
+            0, TABLE0_ID,
+            1, IP_PROTO_FILTER_TABLE_ID);
+
+    private static final BiMap<Criterion.Type, PiHeaderFieldId> CRITERION_MAP =
+            new ImmutableBiMap.Builder<Criterion.Type, PiHeaderFieldId>()
+                    .put(Criterion.Type.IN_PORT, INGRESS_PORT_ID)
+                    .put(Criterion.Type.ETH_DST, ETH_DST_ID)
+                    .put(Criterion.Type.ETH_SRC, ETH_SRC_ID)
+                    .put(Criterion.Type.ETH_TYPE, ETH_TYPE_ID)
+                    .build();
+
+    @Override
+    public PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId)
+            throws PiInterpreterException {
+
+        if (treatment.allInstructions().size() == 0) {
+            // No instructions means drop for us.
+            return PiAction.builder()
+                    .withId(PiActionId.of(DROP))
+                    .build();
+        } else if (treatment.allInstructions().size() > 1) {
+            // We understand treatments with only 1 instruction.
+            throw new PiInterpreterException("Treatment has multiple instructions");
+        }
+
+        // Get the first and only instruction.
+        Instruction instruction = treatment.allInstructions().get(0);
+
+        switch (instruction.type()) {
+            case OUTPUT:
+                // We understand only instructions of type OUTPUT.
+                Instructions.OutputInstruction outInstruction = (Instructions.OutputInstruction) instruction;
+                PortNumber port = outInstruction.port();
+                if (!port.isLogical()) {
+                    return PiAction.builder()
+                            .withId(PiActionId.of(SET_EGRESS_PORT))
+                            .withParameter(new PiActionParam(PiActionParamId.of(PORT), copyFrom(port.toLong())))
+                            .build();
+                } else if (port.equals(CONTROLLER)) {
+                    return PiAction.builder()
+                            .withId(PiActionId.of(SEND_TO_CPU))
+                            .build();
+                } else {
+                    throw new PiInterpreterException(format("Output on logical port '%s' not supported", port));
+                }
+            default:
+                throw new PiInterpreterException(format("Instruction of type '%s' not supported", instruction.type()));
+        }
+    }
+
+    @Override
+    public Optional<PiCounterId> mapTableCounter(PiTableId piTableId) {
+        return Optional.empty();
+    }
+
+    @Override
+    public Collection<PiPacketOperation> mapOutboundPacket(OutboundPacket packet)
+            throws PiInterpreterException {
+
+        TrafficTreatment treatment = packet.treatment();
+
+        // We support only packet-out with OUTPUT instructions.
+        List<Instructions.OutputInstruction> outInstructions = treatment.allInstructions().stream()
+                .filter(i -> i.type().equals(OUTPUT))
+                .map(i -> (Instructions.OutputInstruction) i)
+                .collect(toList());
+
+        if (treatment.allInstructions().size() != outInstructions.size()) {
+            // There are other instructions that are not of type OUTPUT.
+            throw new PiInterpreterException("Treatment not supported: " + treatment);
+        }
+
+        ImmutableList.Builder<PiPacketOperation> builder = ImmutableList.builder();
+
+        for (Instructions.OutputInstruction outInst : outInstructions) {
+            if (outInst.port().isLogical() && !outInst.port().equals(FLOOD)) {
+                throw new PiInterpreterException(format("Output on logical port '%s' not supported", outInst.port()));
+            } else if (outInst.port().equals(FLOOD)) {
+                // Since main.p4 does not support flooding, we create a packet operation for each switch port.
+                DeviceService deviceService = handler().get(DeviceService.class);
+                for (Port port : deviceService.getPorts(packet.sendThrough())) {
+                    builder.add(createPiPacketOperation(packet.data(), port.number().toLong()));
+                }
+            } else {
+                builder.add(createPiPacketOperation(packet.data(), outInst.port().toLong()));
+            }
+        }
+        return builder.build();
+    }
+
+    @Override
+    public InboundPacket mapInboundPacket(DeviceId deviceId, PiPacketOperation packetIn)
+            throws PiInterpreterException {
+        // We assume that the packet is ethernet, which is fine since default.p4 can deparse only ethernet packets.
+        Ethernet ethPkt = new Ethernet();
+
+        ethPkt.deserialize(packetIn.data().asArray(), 0, packetIn.data().size());
+
+        // Returns the ingress port packet metadata.
+        Optional<PiPacketMetadata> packetMetadata = packetIn.metadatas().stream()
+                .filter(metadata -> metadata.id().name().equals(INGRESS_PORT))
+                .findFirst();
+
+        if (packetMetadata.isPresent()) {
+            ImmutableByteSequence portByteSequence = packetMetadata.get().value();
+            short s = portByteSequence.asReadOnlyBuffer().getShort();
+            ConnectPoint receivedFrom = new ConnectPoint(deviceId, PortNumber.portNumber(s));
+            return new DefaultInboundPacket(receivedFrom, ethPkt, packetIn.data().asReadOnlyBuffer());
+        } else {
+            throw new PiInterpreterException(format(
+                    "Missing metadata '%s' in packet-in received from '%s': %s", INGRESS_PORT, deviceId, packetIn));
+        }
+    }
+
+    private PiPacketOperation createPiPacketOperation(ByteBuffer data, long portNumber) throws PiInterpreterException {
+        PiPacketMetadata metadata = createPacketMetadata(portNumber);
+        return PiPacketOperation.builder()
+                .withType(PACKET_OUT)
+                .withData(copyFrom(data))
+                .withMetadatas(ImmutableList.of(metadata))
+                .build();
+    }
+
+    private PiPacketMetadata createPacketMetadata(long portNumber) throws PiInterpreterException {
+        try {
+            return PiPacketMetadata.builder()
+                    .withId(PiPacketMetadataId.of(EGRESS_PORT))
+                    .withValue(fit(copyFrom(portNumber), PORT_FIELD_BITWIDTH))
+                    .build();
+        } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
+            throw new PiInterpreterException(format("Port number %d too big, %s", portNumber, e.getMessage()));
+        }
+    }
+
+    @Override
+    public Optional<PiHeaderFieldId> mapCriterionType(Criterion.Type type) {
+        return Optional.ofNullable(CRITERION_MAP.get(type));
+    }
+
+    @Override
+    public Optional<Criterion.Type> mapPiHeaderFieldId(PiHeaderFieldId headerFieldId) {
+        return Optional.ofNullable(CRITERION_MAP.inverse().get(headerFieldId));
+    }
+
+    @Override
+    public Optional<PiTableId> mapFlowRuleTableId(int flowRuleTableId) {
+        return Optional.ofNullable(TABLE_MAP.get(flowRuleTableId));
+    }
+
+    @Override
+    public Optional<Integer> mapPiTableId(PiTableId piTableId) {
+        return Optional.ofNullable(TABLE_MAP.inverse().get(piTableId));
+    }
+}
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java
new file mode 100644
index 0000000..7d715ee1
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4tutorial.pipeconf;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DefaultPortStatistics;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.PortStatistics;
+import org.onosproject.net.device.PortStatisticsDiscovery;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCellId;
+import org.onosproject.net.pi.runtime.PiCounterId;
+import org.onosproject.net.pi.runtime.PiIndirectCounterCellId;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
+import org.onosproject.p4runtime.api.P4RuntimeClient;
+import org.onosproject.p4runtime.api.P4RuntimeController;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+import static org.onosproject.net.pi.runtime.PiCounterType.INDIRECT;
+
+/**
+ * Implementation of the PortStatisticsDiscovery behaviour for the main.p4 program. This behaviour works by using a
+ * P4Runtime client to read the values of the ingress/egress port counters defined in the P4 program.
+ */
+public final class PortStatisticsDiscoveryImpl extends AbstractHandlerBehaviour implements PortStatisticsDiscovery {
+
+    private static final Logger log = LoggerFactory.getLogger(PortStatisticsDiscoveryImpl.class);
+
+    private static final PiCounterId INGRESS_COUNTER_ID = PiCounterId.of("igr_port_counter", INDIRECT);
+    private static final PiCounterId EGRESS_COUNTER_ID = PiCounterId.of("egr_port_counter", INDIRECT);
+
+    @Override
+    public Collection<PortStatistics> discoverPortStatistics() {
+
+        DeviceService deviceService = this.handler().get(DeviceService.class);
+        DeviceId deviceId = this.data().deviceId();
+
+        // Get a client for this device.
+        P4RuntimeController controller = handler().get(P4RuntimeController.class);
+        if (!controller.hasClient(deviceId)) {
+            log.warn("Unable to find client for {}, aborting operation", deviceId);
+            return Collections.emptyList();
+        }
+        P4RuntimeClient client = controller.getClient(deviceId);
+
+        // Get the pipeconf of this device.
+        PiPipeconfService piPipeconfService = handler().get(PiPipeconfService.class);
+        if (!piPipeconfService.ofDevice(deviceId).isPresent() ||
+                !piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).isPresent()) {
+            log.warn("Unable to get the pipeconf of {}, aborting operation", deviceId);
+            return Collections.emptyList();
+        }
+        PiPipeconf pipeconf = piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).get();
+
+        // Prepare PortStatistics objects to return, one per port of this device.
+        Map<Long, DefaultPortStatistics.Builder> portStatBuilders = Maps.newHashMap();
+        deviceService
+                .getPorts(deviceId)
+                .forEach(p -> portStatBuilders.put(p.number().toLong(),
+                                                   DefaultPortStatistics.builder()
+                                                           .setPort(p.number())
+                                                           .setDeviceId(deviceId)));
+
+        // Generate the counter cell IDs.
+        Set<PiCounterCellId> counterCellIds = Sets.newHashSet();
+        portStatBuilders.keySet().forEach(p -> {
+            // Counter cell/index = port number.
+            counterCellIds.add(PiIndirectCounterCellId.of(INGRESS_COUNTER_ID, p));
+            counterCellIds.add(PiIndirectCounterCellId.of(EGRESS_COUNTER_ID, p));
+        });
+
+        // Query the device.
+        Collection<PiCounterCellData> counterEntryResponse;
+        try {
+            counterEntryResponse = client.readCounterCells(counterCellIds, pipeconf).get();
+        } catch (InterruptedException | ExecutionException e) {
+            log.warn("Exception while reading port counters from {}: {}", deviceId, e.toString());
+            log.debug("", e);
+            return Collections.emptyList();
+        }
+
+        // Process response.
+        counterEntryResponse.forEach(counterData -> {
+            if (counterData.cellId().type() != INDIRECT) {
+                log.warn("Invalid counter data type {}, skipping", counterData.cellId().type());
+                return;
+            }
+            PiIndirectCounterCellId indCellId = (PiIndirectCounterCellId) counterData.cellId();
+            if (!portStatBuilders.containsKey(indCellId.index())) {
+                log.warn("Unrecognized counter index {}, skipping", counterData);
+                return;
+            }
+            DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(indCellId.index());
+            if (counterData.cellId().counterId().equals(INGRESS_COUNTER_ID)) {
+                statsBuilder.setPacketsReceived(counterData.packets());
+                statsBuilder.setBytesReceived(counterData.bytes());
+            } else if (counterData.cellId().counterId().equals(EGRESS_COUNTER_ID)) {
+                statsBuilder.setPacketsSent(counterData.packets());
+                statsBuilder.setBytesSent(counterData.bytes());
+            } else {
+                log.warn("Unrecognized counter ID {}, skipping", counterData);
+            }
+        });
+
+        return portStatBuilders
+                .values()
+                .stream()
+                .map(DefaultPortStatistics.Builder::build)
+                .collect(Collectors.toList());
+    }
+}
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/package-info.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/package-info.java
new file mode 100644
index 0000000..3adbcda
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * P4 tutorial pipeconf classes.
+ */
+package org.onosproject.p4tutorial.pipeconf;
\ No newline at end of file
diff --git a/apps/p4-tutorial/pipeconf/src/main/resources/Makefile b/apps/p4-tutorial/pipeconf/src/main/resources/Makefile
new file mode 100644
index 0000000..9a6a8d0
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/resources/Makefile
@@ -0,0 +1,6 @@
+all: main
+
+main: main.p4
+	p4c-bm2-ss -o main.json --p4runtime-file main.p4info --p4runtime-format text main.p4
+	# Fix for BMv2/p4c bug...
+	sed -i -e 's/"value" : "0xff"/"value" : "0x00ff"/g' main.json
\ No newline at end of file
diff --git a/apps/p4-tutorial/pipeconf/src/main/resources/main.json b/apps/p4-tutorial/pipeconf/src/main/resources/main.json
new file mode 100644
index 0000000..3d059bb
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/resources/main.json
@@ -0,0 +1,931 @@
+{
+  "program" : "main.p4",
+  "__meta__" : {
+    "version" : [2, 7],
+    "compiler" : "https://github.com/p4lang/p4c"
+  },
+  "header_types" : [
+    {
+      "name" : "scalars_0",
+      "id" : 0,
+      "fields" : [
+        ["tmp", 32, false],
+        ["tmp_0", 32, false]
+      ]
+    },
+    {
+      "name" : "ethernet_t",
+      "id" : 1,
+      "fields" : [
+        ["dst_addr", 48, false],
+        ["src_addr", 48, false],
+        ["ether_type", 16, false]
+      ]
+    },
+    {
+      "name" : "ipv4_t",
+      "id" : 2,
+      "fields" : [
+        ["version", 4, false],
+        ["ihl", 4, false],
+        ["diffserv", 8, false],
+        ["len", 16, false],
+        ["identification", 16, false],
+        ["flags", 3, false],
+        ["frag_offset", 13, false],
+        ["ttl", 8, false],
+        ["protocol", 8, false],
+        ["hdr_checksum", 16, false],
+        ["src_addr", 32, false],
+        ["dst_addr", 32, false]
+      ]
+    },
+    {
+      "name" : "packet_out_header_t",
+      "id" : 3,
+      "fields" : [
+        ["egress_port", 9, false],
+        ["_padding", 7, false]
+      ]
+    },
+    {
+      "name" : "packet_in_header_t",
+      "id" : 4,
+      "fields" : [
+        ["ingress_port", 9, false],
+        ["_padding_0", 7, false]
+      ]
+    },
+    {
+      "name" : "standard_metadata",
+      "id" : 5,
+      "fields" : [
+        ["ingress_port", 9, false],
+        ["egress_spec", 9, false],
+        ["egress_port", 9, false],
+        ["clone_spec", 32, false],
+        ["instance_type", 32, false],
+        ["drop", 1, false],
+        ["recirculate_port", 16, false],
+        ["packet_length", 32, false],
+        ["enq_timestamp", 32, false],
+        ["enq_qdepth", 19, false],
+        ["deq_timedelta", 32, false],
+        ["deq_qdepth", 19, false],
+        ["ingress_global_timestamp", 48, false],
+        ["lf_field_list", 32, false],
+        ["mcast_grp", 16, false],
+        ["resubmit_flag", 1, false],
+        ["egress_rid", 16, false],
+        ["_padding_1", 5, false]
+      ]
+    }
+  ],
+  "headers" : [
+    {
+      "name" : "scalars",
+      "id" : 0,
+      "header_type" : "scalars_0",
+      "metadata" : true,
+      "pi_omit" : true
+    },
+    {
+      "name" : "standard_metadata",
+      "id" : 1,
+      "header_type" : "standard_metadata",
+      "metadata" : true,
+      "pi_omit" : true
+    },
+    {
+      "name" : "ethernet",
+      "id" : 2,
+      "header_type" : "ethernet_t",
+      "metadata" : false,
+      "pi_omit" : true
+    },
+    {
+      "name" : "ipv4",
+      "id" : 3,
+      "header_type" : "ipv4_t",
+      "metadata" : false,
+      "pi_omit" : true
+    },
+    {
+      "name" : "packet_out",
+      "id" : 4,
+      "header_type" : "packet_out_header_t",
+      "metadata" : false,
+      "pi_omit" : true
+    },
+    {
+      "name" : "packet_in",
+      "id" : 5,
+      "header_type" : "packet_in_header_t",
+      "metadata" : false,
+      "pi_omit" : true
+    }
+  ],
+  "header_stacks" : [],
+  "header_union_types" : [],
+  "header_unions" : [],
+  "header_union_stacks" : [],
+  "field_lists" : [],
+  "errors" : [
+    ["NoError", 1],
+    ["PacketTooShort", 2],
+    ["NoMatch", 3],
+    ["StackOutOfBounds", 4],
+    ["HeaderTooShort", 5],
+    ["ParserTimeout", 6]
+  ],
+  "enums" : [],
+  "parsers" : [
+    {
+      "name" : "parser",
+      "id" : 0,
+      "init_state" : "start",
+      "parse_states" : [
+        {
+          "name" : "start",
+          "id" : 0,
+          "parser_ops" : [],
+          "transitions" : [
+            {
+              "value" : "0x00ff",
+              "mask" : null,
+              "next_state" : "parse_packet_out"
+            },
+            {
+              "value" : "default",
+              "mask" : null,
+              "next_state" : "parse_ethernet"
+            }
+          ],
+          "transition_key" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_port"]
+            }
+          ]
+        },
+        {
+          "name" : "parse_packet_out",
+          "id" : 1,
+          "parser_ops" : [
+            {
+              "parameters" : [
+                {
+                  "type" : "regular",
+                  "value" : "packet_out"
+                }
+              ],
+              "op" : "extract"
+            }
+          ],
+          "transitions" : [
+            {
+              "value" : "default",
+              "mask" : null,
+              "next_state" : "parse_ethernet"
+            }
+          ],
+          "transition_key" : []
+        },
+        {
+          "name" : "parse_ethernet",
+          "id" : 2,
+          "parser_ops" : [
+            {
+              "parameters" : [
+                {
+                  "type" : "regular",
+                  "value" : "ethernet"
+                }
+              ],
+              "op" : "extract"
+            }
+          ],
+          "transitions" : [
+            {
+              "value" : "0x0800",
+              "mask" : null,
+              "next_state" : "parse_ipv4"
+            },
+            {
+              "value" : "default",
+              "mask" : null,
+              "next_state" : null
+            }
+          ],
+          "transition_key" : [
+            {
+              "type" : "field",
+              "value" : ["ethernet", "ether_type"]
+            }
+          ]
+        },
+        {
+          "name" : "parse_ipv4",
+          "id" : 3,
+          "parser_ops" : [
+            {
+              "parameters" : [
+                {
+                  "type" : "regular",
+                  "value" : "ipv4"
+                }
+              ],
+              "op" : "extract"
+            }
+          ],
+          "transitions" : [
+            {
+              "value" : "default",
+              "mask" : null,
+              "next_state" : null
+            }
+          ],
+          "transition_key" : []
+        }
+      ]
+    }
+  ],
+  "deparsers" : [
+    {
+      "name" : "deparser",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "main.p4",
+        "line" : 264,
+        "column" : 8,
+        "source_fragment" : "DeparserImpl"
+      },
+      "order" : ["packet_in", "ethernet", "ipv4"]
+    }
+  ],
+  "meter_arrays" : [],
+  "counter_arrays" : [
+    {
+      "name" : "egr_port_counter",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "main.p4",
+        "line" : 181,
+        "column" : 38,
+        "source_fragment" : "egr_port_counter"
+      },
+      "size" : 511,
+      "is_direct" : false
+    },
+    {
+      "name" : "igr_port_counter",
+      "id" : 1,
+      "source_info" : {
+        "filename" : "main.p4",
+        "line" : 182,
+        "column" : 38,
+        "source_fragment" : "igr_port_counter"
+      },
+      "size" : 511,
+      "is_direct" : false
+    }
+  ],
+  "register_arrays" : [],
+  "calculations" : [],
+  "learn_lists" : [],
+  "actions" : [
+    {
+      "name" : "NoAction",
+      "id" : 0,
+      "runtime_data" : [],
+      "primitives" : []
+    },
+    {
+      "name" : "send_to_cpu",
+      "id" : 1,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            },
+            {
+              "type" : "hexstr",
+              "value" : "0x00ff"
+            }
+          ],
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 24,
+            "column" : 24,
+            "source_fragment" : "255; ..."
+          }
+        },
+        {
+          "op" : "add_header",
+          "parameters" : [
+            {
+              "type" : "header",
+              "value" : "packet_in"
+            }
+          ],
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 137,
+            "column" : 8,
+            "source_fragment" : "hdr.packet_in.setValid()"
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["packet_in", "ingress_port"]
+            },
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "ingress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 138,
+            "column" : 8,
+            "source_fragment" : "hdr.packet_in.ingress_port = standard_metadata.ingress_port"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "set_egress_port",
+      "id" : 2,
+      "runtime_data" : [
+        {
+          "name" : "port",
+          "bitwidth" : 9
+        }
+      ],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            },
+            {
+              "type" : "runtime_data",
+              "value" : 0
+            }
+          ],
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 142,
+            "column" : 8,
+            "source_fragment" : "standard_metadata.egress_spec = port"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "_drop",
+      "id" : 3,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            },
+            {
+              "type" : "hexstr",
+              "value" : "0x01ff"
+            }
+          ],
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 25,
+            "column" : 25,
+            "source_fragment" : "511; ..."
+          }
+        }
+      ]
+    },
+    {
+      "name" : "_drop",
+      "id" : 4,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            },
+            {
+              "type" : "hexstr",
+              "value" : "0x01ff"
+            }
+          ],
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 25,
+            "column" : 25,
+            "source_fragment" : "511; ..."
+          }
+        }
+      ]
+    },
+    {
+      "name" : "act",
+      "id" : 5,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            },
+            {
+              "type" : "field",
+              "value" : ["packet_out", "egress_port"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 195,
+            "column" : 12,
+            "source_fragment" : "standard_metadata.egress_spec = hdr.packet_out.egress_port"
+          }
+        },
+        {
+          "op" : "remove_header",
+          "parameters" : [
+            {
+              "type" : "header",
+              "value" : "packet_out"
+            }
+          ],
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 196,
+            "column" : 12,
+            "source_fragment" : "hdr.packet_out.setInvalid()"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "act_0",
+      "id" : 6,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "&",
+                  "left" : {
+                    "type" : "field",
+                    "value" : ["standard_metadata", "egress_spec"]
+                  },
+                  "right" : {
+                    "type" : "hexstr",
+                    "value" : "0xffffffff"
+                  }
+                }
+              }
+            }
+          ]
+        },
+        {
+          "op" : "count",
+          "parameters" : [
+            {
+              "type" : "counter_array",
+              "value" : "egr_port_counter"
+            },
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 218,
+            "column" : 12,
+            "source_fragment" : "egr_port_counter.count((bit<32>) standard_metadata.egress_spec)"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "act_1",
+      "id" : 7,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp_0"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "&",
+                  "left" : {
+                    "type" : "field",
+                    "value" : ["standard_metadata", "ingress_port"]
+                  },
+                  "right" : {
+                    "type" : "hexstr",
+                    "value" : "0xffffffff"
+                  }
+                }
+              }
+            }
+          ]
+        },
+        {
+          "op" : "count",
+          "parameters" : [
+            {
+              "type" : "counter_array",
+              "value" : "igr_port_counter"
+            },
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp_0"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 221,
+            "column" : 12,
+            "source_fragment" : "igr_port_counter.count((bit<32>) standard_metadata.ingress_port)"
+          }
+        }
+      ]
+    }
+  ],
+  "pipelines" : [
+    {
+      "name" : "ingress",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "main.p4",
+        "line" : 126,
+        "column" : 8,
+        "source_fragment" : "IngressImpl"
+      },
+      "init_table" : "node_2",
+      "tables" : [
+        {
+          "name" : "tbl_act",
+          "id" : 0,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [5],
+          "actions" : ["act"],
+          "base_default_next" : "node_7",
+          "next_tables" : {
+            "act" : "node_7"
+          },
+          "default_entry" : {
+            "action_id" : 5,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        },
+        {
+          "name" : "table0",
+          "id" : 1,
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 149,
+            "column" : 10,
+            "source_fragment" : "table0"
+          },
+          "key" : [
+            {
+              "match_type" : "ternary",
+              "target" : ["standard_metadata", "ingress_port"],
+              "mask" : null
+            },
+            {
+              "match_type" : "ternary",
+              "target" : ["ethernet", "dst_addr"],
+              "mask" : null
+            },
+            {
+              "match_type" : "ternary",
+              "target" : ["ethernet", "src_addr"],
+              "mask" : null
+            },
+            {
+              "match_type" : "ternary",
+              "target" : ["ethernet", "ether_type"],
+              "mask" : null
+            }
+          ],
+          "match_type" : "ternary",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [2, 1, 3],
+          "actions" : ["set_egress_port", "send_to_cpu", "_drop"],
+          "base_default_next" : "node_7",
+          "next_tables" : {
+            "set_egress_port" : "node_5",
+            "send_to_cpu" : "node_7",
+            "_drop" : "node_7"
+          },
+          "default_entry" : {
+            "action_id" : 3,
+            "action_const" : false,
+            "action_data" : [],
+            "action_entry_const" : false
+          }
+        },
+        {
+          "name" : "ip_proto_filter_table",
+          "id" : 2,
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 164,
+            "column" : 10,
+            "source_fragment" : "ip_proto_filter_table"
+          },
+          "key" : [
+            {
+              "match_type" : "ternary",
+              "target" : ["ipv4", "src_addr"],
+              "mask" : null
+            },
+            {
+              "match_type" : "exact",
+              "target" : ["ipv4", "protocol"],
+              "mask" : null
+            }
+          ],
+          "match_type" : "ternary",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [4, 0],
+          "actions" : ["_drop", "NoAction"],
+          "base_default_next" : "node_7",
+          "next_tables" : {
+            "_drop" : "node_7",
+            "NoAction" : "node_7"
+          },
+          "default_entry" : {
+            "action_id" : 0,
+            "action_const" : false,
+            "action_data" : [],
+            "action_entry_const" : false
+          }
+        },
+        {
+          "name" : "tbl_act_0",
+          "id" : 3,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [6],
+          "actions" : ["act_0"],
+          "base_default_next" : "node_9",
+          "next_tables" : {
+            "act_0" : "node_9"
+          },
+          "default_entry" : {
+            "action_id" : 6,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        },
+        {
+          "name" : "tbl_act_1",
+          "id" : 4,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [7],
+          "actions" : ["act_1"],
+          "base_default_next" : null,
+          "next_tables" : {
+            "act_1" : null
+          },
+          "default_entry" : {
+            "action_id" : 7,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        }
+      ],
+      "action_profiles" : [],
+      "conditionals" : [
+        {
+          "name" : "node_2",
+          "id" : 0,
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 188,
+            "column" : 12,
+            "source_fragment" : "standard_metadata.ingress_port == CPU_PORT"
+          },
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "==",
+              "left" : {
+                "type" : "field",
+                "value" : ["standard_metadata", "ingress_port"]
+              },
+              "right" : {
+                "type" : "hexstr",
+                "value" : "0x00ff"
+              }
+            }
+          },
+          "true_next" : "tbl_act",
+          "false_next" : "table0"
+        },
+        {
+          "name" : "node_5",
+          "id" : 1,
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 205,
+            "column" : 24,
+            "source_fragment" : "hdr.ipv4.isValid()"
+          },
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "==",
+              "left" : {
+                "type" : "field",
+                "value" : ["ipv4", "$valid$"]
+              },
+              "right" : {
+                "type" : "hexstr",
+                "value" : "0x01"
+              }
+            }
+          },
+          "true_next" : "ip_proto_filter_table",
+          "false_next" : "node_7"
+        },
+        {
+          "name" : "node_7",
+          "id" : 2,
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 217,
+            "column" : 12,
+            "source_fragment" : "standard_metadata.egress_spec < 511"
+          },
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "<",
+              "left" : {
+                "type" : "field",
+                "value" : ["standard_metadata", "egress_spec"]
+              },
+              "right" : {
+                "type" : "hexstr",
+                "value" : "0x01ff"
+              }
+            }
+          },
+          "true_next" : "tbl_act_0",
+          "false_next" : "node_9"
+        },
+        {
+          "name" : "node_9",
+          "id" : 3,
+          "source_info" : {
+            "filename" : "main.p4",
+            "line" : 220,
+            "column" : 12,
+            "source_fragment" : "standard_metadata.ingress_port < 511"
+          },
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "<",
+              "left" : {
+                "type" : "field",
+                "value" : ["standard_metadata", "ingress_port"]
+              },
+              "right" : {
+                "type" : "hexstr",
+                "value" : "0x01ff"
+              }
+            }
+          },
+          "false_next" : null,
+          "true_next" : "tbl_act_1"
+        }
+      ]
+    },
+    {
+      "name" : "egress",
+      "id" : 1,
+      "source_info" : {
+        "filename" : "main.p4",
+        "line" : 230,
+        "column" : 8,
+        "source_fragment" : "EgressImpl"
+      },
+      "init_table" : null,
+      "tables" : [],
+      "action_profiles" : [],
+      "conditionals" : []
+    }
+  ],
+  "checksums" : [],
+  "force_arith" : [],
+  "extern_instances" : [],
+  "field_aliases" : [
+    [
+      "queueing_metadata.enq_timestamp",
+      ["standard_metadata", "enq_timestamp"]
+    ],
+    [
+      "queueing_metadata.enq_qdepth",
+      ["standard_metadata", "enq_qdepth"]
+    ],
+    [
+      "queueing_metadata.deq_timedelta",
+      ["standard_metadata", "deq_timedelta"]
+    ],
+    [
+      "queueing_metadata.deq_qdepth",
+      ["standard_metadata", "deq_qdepth"]
+    ],
+    [
+      "intrinsic_metadata.ingress_global_timestamp",
+      ["standard_metadata", "ingress_global_timestamp"]
+    ],
+    [
+      "intrinsic_metadata.lf_field_list",
+      ["standard_metadata", "lf_field_list"]
+    ],
+    [
+      "intrinsic_metadata.mcast_grp",
+      ["standard_metadata", "mcast_grp"]
+    ],
+    [
+      "intrinsic_metadata.resubmit_flag",
+      ["standard_metadata", "resubmit_flag"]
+    ],
+    [
+      "intrinsic_metadata.egress_rid",
+      ["standard_metadata", "egress_rid"]
+    ]
+  ]
+}
\ No newline at end of file
diff --git a/apps/p4-tutorial/pipeconf/src/main/resources/main.p4 b/apps/p4-tutorial/pipeconf/src/main/resources/main.p4
new file mode 100644
index 0000000..9f0b809
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/resources/main.p4
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+#include <core.p4>
+#include <v1model.p4>
+
+#define ETH_TYPE_IPV4 0x0800
+#define MAX_PORTS 511
+
+typedef bit<9> port_t;
+const port_t CPU_PORT = 255;
+const port_t DROP_PORT = 511;
+
+//------------------------------------------------------------------------------
+// HEADERS
+//------------------------------------------------------------------------------
+
+header ethernet_t {
+    bit<48> dst_addr;
+    bit<48> src_addr;
+    bit<16> ether_type;
+}
+
+header ipv4_t {
+    bit<4>  version;
+    bit<4>  ihl;
+    bit<8>  diffserv;
+    bit<16> len;
+    bit<16> identification;
+    bit<3>  flags;
+    bit<13> frag_offset;
+    bit<8>  ttl;
+    bit<8>  protocol;
+    bit<16> hdr_checksum;
+    bit<32> src_addr;
+    bit<32> dst_addr;
+}
+
+/*
+Packet-in header. Prepended to packets sent to the controller and used to carry
+the original ingress port where the packet was received.
+ */
+@controller_header("packet_in")
+header packet_in_header_t {
+    bit<9> ingress_port;
+}
+
+/*
+Packet-out header. Prepended to packets received by the controller and used to
+tell the switch on which physical port this packet should be forwarded.
+ */
+@controller_header("packet_out")
+header packet_out_header_t {
+    bit<9> egress_port;
+}
+
+/*
+For convenience we collect all headers under the same struct.
+ */
+struct headers_t {
+    ethernet_t ethernet;
+    ipv4_t ipv4;
+    packet_out_header_t packet_out;
+    packet_in_header_t packet_in;
+}
+
+/*
+Metadata can be used to carry information from one table to another.
+ */
+struct metadata_t {
+    /* Empty. We don't use it in this program. */
+}
+
+//------------------------------------------------------------------------------
+// PARSER
+//------------------------------------------------------------------------------
+
+parser ParserImpl(packet_in packet,
+                  out headers_t hdr,
+                  inout metadata_t meta,
+                  inout standard_metadata_t standard_metadata) {
+
+    state start {
+        transition select(standard_metadata.ingress_port) {
+            CPU_PORT: parse_packet_out;
+            default: parse_ethernet;
+        }
+    }
+
+    state parse_packet_out {
+        packet.extract(hdr.packet_out);
+        transition parse_ethernet;
+    }
+
+    state parse_ethernet {
+        packet.extract(hdr.ethernet);
+        transition select(hdr.ethernet.ether_type) {
+            ETH_TYPE_IPV4: parse_ipv4;
+            default: accept;
+        }
+    }
+
+    state parse_ipv4 {
+        packet.extract(hdr.ipv4);
+        transition accept;
+    }
+}
+
+//------------------------------------------------------------------------------
+// INGRESS PIPELINE
+//------------------------------------------------------------------------------
+
+control IngressImpl(inout headers_t hdr,
+                    inout metadata_t meta,
+                    inout standard_metadata_t standard_metadata) {
+
+    action send_to_cpu() {
+        standard_metadata.egress_spec = CPU_PORT;
+        /*
+        Packets sent to the controller needs to be prepended with the packet-in
+        header. By setting it valid we make sure it will be deparsed before the
+        ethernet header (see DeparserImpl).
+         */
+        hdr.packet_in.setValid();
+        hdr.packet_in.ingress_port = standard_metadata.ingress_port;
+    }
+
+    action set_egress_port(port_t port) {
+        standard_metadata.egress_spec = port;
+    }
+
+    action _drop() {
+        standard_metadata.egress_spec = DROP_PORT;
+    }
+
+    table table0 {
+        key = {
+            standard_metadata.ingress_port  : ternary;
+            hdr.ethernet.dst_addr           : ternary;
+            hdr.ethernet.src_addr           : ternary;
+            hdr.ethernet.ether_type         : ternary;
+        }
+        actions = {
+            set_egress_port();
+            send_to_cpu();
+            _drop();
+        }
+        default_action = _drop();
+    }
+
+    table ip_proto_filter_table {
+        key = {
+            hdr.ipv4.src_addr : ternary;
+            hdr.ipv4.protocol : exact;
+        }
+        actions = {
+            _drop();
+        }
+    }
+
+    /*
+    Port counters.
+    We use these counter instances to count packets/bytes received/sent on each
+    port. BMv2 always counts both packets and bytes, even if the counter is
+    instantiated as "packets". For each counter we instantiate a number of cells
+    equal to MAX_PORTS.
+     */
+    counter(MAX_PORTS, CounterType.packets) egr_port_counter;
+    counter(MAX_PORTS, CounterType.packets) igr_port_counter;
+
+    /*
+    We define here the processing to be executed by this ingress pipeline.
+     */
+    apply {
+        if (standard_metadata.ingress_port == CPU_PORT) {
+            /*
+            Packet received from CPU_PORT, this is a packet-out sent by the
+            controller. Skip pipeline processing, set the egress port as
+            requested by the controller (packet_out header) and remove the
+            packet_out header.
+             */
+            standard_metadata.egress_spec = hdr.packet_out.egress_port;
+            hdr.packet_out.setInvalid();
+        } else {
+            /*
+            Packet received from switch port. Apply table0, if action is
+            set_egress_port and packet is IPv4, then apply
+            ip_proto_filter_table.
+             */
+            switch(table0.apply().action_run) {
+                set_egress_port: {
+                    if (hdr.ipv4.isValid()) {
+                        ip_proto_filter_table.apply();
+                    }
+                }
+            }
+        }
+
+        /*
+        For each port counter, we update the cell at index = ingress/egress
+        port. We avoid counting packets sent/received on CPU_PORT or dropped
+        (DROP_PORT).
+         */
+        if (standard_metadata.egress_spec < MAX_PORTS) {
+            egr_port_counter.count((bit<32>) standard_metadata.egress_spec);
+        }
+        if (standard_metadata.ingress_port < MAX_PORTS) {
+            igr_port_counter.count((bit<32>) standard_metadata.ingress_port);
+        }
+     }
+}
+
+//------------------------------------------------------------------------------
+// EGRESS PIPELINE
+//------------------------------------------------------------------------------
+
+control EgressImpl(inout headers_t hdr,
+                   inout metadata_t meta,
+                   inout standard_metadata_t standard_metadata) {
+    apply {
+        /*
+        Nothing to do on the egress pipeline.
+        */
+    }
+}
+
+//------------------------------------------------------------------------------
+// CHECKSUM HANDLING
+//------------------------------------------------------------------------------
+
+control VerifyChecksumImpl(in headers_t hdr, inout metadata_t meta) {
+    apply {
+        /*
+        Nothing to do here, we assume checksum is always correct.
+        */
+    }
+}
+
+control ComputeChecksumImpl(inout headers_t hdr, inout metadata_t meta) {
+    apply {
+        /*
+        Nothing to do here, as we do not modify packet headers.
+        */
+    }
+}
+
+//------------------------------------------------------------------------------
+// DEPARSER
+//------------------------------------------------------------------------------
+
+control DeparserImpl(packet_out packet, in headers_t hdr) {
+    apply {
+        /*
+        Deparse headers in order. Only valid headers are emitted.
+        */
+        packet.emit(hdr.packet_in);
+        packet.emit(hdr.ethernet);
+        packet.emit(hdr.ipv4);
+    }
+}
+
+//------------------------------------------------------------------------------
+// SWITCH INSTANTIATION
+//------------------------------------------------------------------------------
+
+V1Switch(ParserImpl(),
+         VerifyChecksumImpl(),
+         IngressImpl(),
+         EgressImpl(),
+         ComputeChecksumImpl(),
+         DeparserImpl()) main;
diff --git a/apps/p4-tutorial/pipeconf/src/main/resources/main.p4info b/apps/p4-tutorial/pipeconf/src/main/resources/main.p4info
new file mode 100644
index 0000000..c4882d2
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/resources/main.p4info
@@ -0,0 +1,147 @@
+tables {
+  preamble {
+    id: 33617813
+    name: "table0"
+    alias: "table0"
+  }
+  match_fields {
+    id: 1
+    name: "standard_metadata.ingress_port"
+    bitwidth: 9
+    match_type: TERNARY
+  }
+  match_fields {
+    id: 2
+    name: "hdr.ethernet.dst_addr"
+    bitwidth: 48
+    match_type: TERNARY
+  }
+  match_fields {
+    id: 3
+    name: "hdr.ethernet.src_addr"
+    bitwidth: 48
+    match_type: TERNARY
+  }
+  match_fields {
+    id: 4
+    name: "hdr.ethernet.ether_type"
+    bitwidth: 16
+    match_type: TERNARY
+  }
+  action_refs {
+    id: 16794308
+  }
+  action_refs {
+    id: 16829080
+  }
+  action_refs {
+    id: 16784184
+  }
+  size: 1024
+}
+tables {
+  preamble {
+    id: 33573361
+    name: "ip_proto_filter_table"
+    alias: "ip_proto_filter_table"
+  }
+  match_fields {
+    id: 1
+    name: "hdr.ipv4.src_addr"
+    bitwidth: 32
+    match_type: TERNARY
+  }
+  match_fields {
+    id: 2
+    name: "hdr.ipv4.protocol"
+    bitwidth: 8
+    match_type: EXACT
+  }
+  action_refs {
+    id: 16784184
+  }
+  action_refs {
+    id: 16800567
+    annotations: "@defaultonly()"
+  }
+  size: 1024
+}
+actions {
+  preamble {
+    id: 16800567
+    name: "NoAction"
+    alias: "NoAction"
+  }
+}
+actions {
+  preamble {
+    id: 16829080
+    name: "send_to_cpu"
+    alias: "send_to_cpu"
+  }
+}
+actions {
+  preamble {
+    id: 16794308
+    name: "set_egress_port"
+    alias: "set_egress_port"
+  }
+  params {
+    id: 1
+    name: "port"
+    bitwidth: 9
+  }
+}
+actions {
+  preamble {
+    id: 16784184
+    name: "_drop"
+    alias: "_drop"
+  }
+}
+counters {
+  preamble {
+    id: 302012419
+    name: "egr_port_counter"
+    alias: "egr_port_counter"
+  }
+  spec {
+    unit: PACKETS
+  }
+  size: 511
+}
+counters {
+  preamble {
+    id: 302054463
+    name: "igr_port_counter"
+    alias: "igr_port_counter"
+  }
+  spec {
+    unit: PACKETS
+  }
+  size: 511
+}
+controller_packet_metadata {
+  preamble {
+    id: 2868941301
+    name: "packet_in"
+    annotations: "@controller_header(\"packet_in\")"
+  }
+  metadata {
+    id: 1
+    name: "ingress_port"
+    bitwidth: 9
+  }
+}
+controller_packet_metadata {
+  preamble {
+    id: 2868916615
+    name: "packet_out"
+    annotations: "@controller_header(\"packet_out\")"
+  }
+  metadata {
+    id: 1
+    name: "egress_port"
+    bitwidth: 9
+  }
+}
diff --git a/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/api/GatewayNodeConfig.java b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/api/GatewayNodeConfig.java
index caee767..d219fe7 100644
--- a/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/api/GatewayNodeConfig.java
+++ b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/api/GatewayNodeConfig.java
@@ -46,7 +46,7 @@
     /**
      * Returns the set of nodes read from network config.
      *
-     * @return set of OpensatckNodeConfig or null
+     * @return set of OpenstackNodeConfig or null
      */
     public Set<GatewayNode> gatewayNodes() {
 
diff --git a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css
index f65e636..d9d0c14 100644
--- a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css
+++ b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css
@@ -58,6 +58,16 @@
     display: inline-block;
     margin: 8px 0;
     padding-left: 6px;
+    font-size: 16pt;
+    font-weight: lighter;
+}
+
+#yang-model-details-panel h3 {
+    display: inline-block;
+    margin: 8px 0;
+    padding-left: 6px;
+    font-size: 13pt;
+    font-weight: lighter;
 }
 
 #yang-model-details-panel .src-frame {
diff --git a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js
index 42fa343..e35b8df 100644
--- a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js
+++ b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js
@@ -89,7 +89,7 @@
         top.append('hr');
 
         bottom = container.append('div').classed('bottom', true);
-        bottom.append('h2').text('YANG Source');
+        bottom.append('h3').text('YANG Source');
 
         srcFrame = bottom.append('div').classed('src-frame', true);
         srcDiv = srcFrame.append('div').classed('module-source', true);
diff --git a/cli/src/main/java/org/onosproject/cli/app/ApplicationNameCompleter.java b/cli/src/main/java/org/onosproject/cli/app/ApplicationNameCompleter.java
index 1848bf3..3f93ced 100644
--- a/cli/src/main/java/org/onosproject/cli/app/ApplicationNameCompleter.java
+++ b/cli/src/main/java/org/onosproject/cli/app/ApplicationNameCompleter.java
@@ -22,10 +22,19 @@
 import org.onosproject.cli.AbstractCompleter;
 import org.onosproject.core.Application;
 
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map.Entry;
 import java.util.SortedSet;
+import java.util.stream.Collectors;
 
+import static java.util.Arrays.asList;
 import static org.onosproject.app.ApplicationState.ACTIVE;
 import static org.onosproject.app.ApplicationState.INSTALLED;
 import static org.onosproject.cli.AbstractShellCommand.get;
@@ -70,6 +79,33 @@
             }
         }
 
+        // add unique suffix to candidates, if user has something in buffer
+        if (!Strings.isNullOrEmpty(buffer)) {
+            List<String> suffixCandidates = strings.stream()
+                    // remove onos common prefix
+                    .map(full -> full.replaceFirst("org\\.onosproject\\.", ""))
+                    // a.b.c -> [c, b.c, a.b.c]
+                    .flatMap(appName -> {
+                        List<String> suffixes = new ArrayList<>();
+                        Deque<String> frags = new ArrayDeque<>();
+                        // a.b.c -> [c, b, a] -> [c, b.c, a.b.c]
+                        Lists.reverse(asList(appName.split("\\."))).forEach(frag -> {
+                            frags.addFirst(frag);
+                            suffixes.add(frags.stream().collect(Collectors.joining(".")));
+                        });
+                        return suffixes.stream();
+                    })
+                    // convert to occurrence map
+                    .collect(Collectors.groupingBy(e -> e, Collectors.counting()))
+                    .entrySet().stream()
+                    // only accept unique suffix
+                    .filter(e -> e.getValue() == 1L)
+                    .map(Entry::getKey)
+                    .collect(Collectors.toList());
+
+            delegate.getStrings().addAll(suffixCandidates);
+        }
+
         // Now let the completer do the work for figuring out what to offer.
         return delegate.complete(buffer, cursor, candidates);
     }
diff --git a/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java b/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
index 487380f..d0a4732 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
@@ -342,7 +342,7 @@
          *
          * @return PiCriterion
          */
-        public Criterion build() {
+        public PiCriterion build() {
             ImmutableMap<PiHeaderFieldId, PiFieldMatch> fieldMatchMap = fieldMatchMapBuilder.build();
             checkArgument(fieldMatchMap.size() > 0, "Cannot build PI criterion with 0 field matches");
             return new PiCriterion(fieldMatchMap);
diff --git a/core/api/src/main/java/org/onosproject/ui/GlyphConstants.java b/core/api/src/main/java/org/onosproject/ui/GlyphConstants.java
index 364d44c..f760abb 100644
--- a/core/api/src/main/java/org/onosproject/ui/GlyphConstants.java
+++ b/core/api/src/main/java/org/onosproject/ui/GlyphConstants.java
@@ -55,6 +55,7 @@
     public static final String PORT_TABLE = "portTable";
     public static final String GROUP_TABLE = "groupTable";
     public static final String METER_TABLE = "meterTable";
+    public static final String PIPECONF_TABLE = "pipeconfTable";
 
     public static final String SUMMARY = "m_summary";
     public static final String DETAILS = "m_details";
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
index cd39d47..7a88366 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
@@ -85,6 +85,15 @@
 import org.onosproject.net.meter.Meter;
 import org.onosproject.net.meter.MeterRequest;
 import org.onosproject.net.packet.PacketRequest;
+import org.onosproject.net.pi.model.PiActionModel;
+import org.onosproject.net.pi.model.PiActionParamModel;
+import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
+import org.onosproject.net.pi.model.PiHeaderModel;
+import org.onosproject.net.pi.model.PiHeaderTypeModel;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.model.PiTableMatchFieldModel;
+import org.onosproject.net.pi.model.PiTableModel;
 import org.onosproject.net.region.Region;
 import org.onosproject.net.statistic.Load;
 import org.onosproject.net.topology.Topology;
@@ -176,6 +185,15 @@
         registerCodec(FilteredConnectPoint.class, new FilteredConnectPointCodec());
         registerCodec(TransportEndpointDescription.class, new TransportEndpointDescriptionCodec());
         registerCodec(PacketRequest.class, new PacketRequestCodec());
+        registerCodec(PiActionModel.class, new PiActionModelCodec());
+        registerCodec(PiHeaderModel.class, new PiHeaderModelCodec());
+        registerCodec(PiPipelineModel.class, new PiPipelineModelCodec());
+        registerCodec(PiPipeconf.class, new PiPipeconfCodec());
+        registerCodec(PiTableModel.class, new PiTableModelCodec());
+        registerCodec(PiTableMatchFieldModel.class, new PiTableMatchFieldModelCodec());
+        registerCodec(PiHeaderFieldTypeModel.class, new PiHeaderFieldTypeModelCodec());
+        registerCodec(PiHeaderTypeModel.class, new PiHeaderTypeModelCodec());
+        registerCodec(PiActionParamModel.class, new PiActionParamModelCodec());
         log.info("Started");
     }
 
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
index 7c26302..da34c5e 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
@@ -24,8 +24,10 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.flow.DefaultFlowRule;
 import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.IndexTableId;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.pi.runtime.PiTableId;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onlab.util.Tools.nullIsIllegal;
@@ -43,6 +45,8 @@
     private static final String DEVICE_ID = "deviceId";
     private static final String TREATMENT = "treatment";
     private static final String SELECTOR = "selector";
+    private static final String ID = "id";
+    private static final String TABLE_NAME = "tableName";
     private static final String MISSING_MEMBER_MESSAGE =
                                 " member is required in FlowRule";
     public static final String REST_APP_ID = "org.onosproject.rest";
@@ -56,24 +60,25 @@
         String strAppId = (appId == null) ? "<none>" : appId.name();
 
         final ObjectNode result = context.mapper().createObjectNode()
-                .put("id", Long.toString(flowRule.id().value()))
-                .put("tableId", flowRule.tableId())
-                .put("appId", strAppId)
-                .put("priority", flowRule.priority())
-                .put("timeout", flowRule.timeout())
-                .put("isPermanent", flowRule.isPermanent())
-                .put("deviceId", flowRule.deviceId().toString());
+                .put(ID, Long.toString(flowRule.id().value()))
+                .put(APP_ID, strAppId)
+                .put(PRIORITY, flowRule.priority())
+                .put(TIMEOUT, flowRule.timeout())
+                .put(IS_PERMANENT, flowRule.isPermanent())
+                .put(DEVICE_ID, flowRule.deviceId().toString())
+                .put(TABLE_ID, flowRule.tableId())
+                .put(TABLE_NAME, flowRule.table().toString());
 
         if (flowRule.treatment() != null) {
             final JsonCodec<TrafficTreatment> treatmentCodec =
                     context.codec(TrafficTreatment.class);
-            result.set("treatment", treatmentCodec.encode(flowRule.treatment(), context));
+            result.set(TREATMENT, treatmentCodec.encode(flowRule.treatment(), context));
         }
 
         if (flowRule.selector() != null) {
             final JsonCodec<TrafficSelector> selectorCodec =
                     context.codec(TrafficSelector.class);
-            result.set("selector", selectorCodec.encode(flowRule.selector(), context));
+            result.set(SELECTOR, selectorCodec.encode(flowRule.selector(), context));
         }
 
         return result;
@@ -109,7 +114,13 @@
 
         JsonNode tableIdJson = json.get(TABLE_ID);
         if (tableIdJson != null) {
-            resultBuilder.forTable(tableIdJson.asInt());
+            String tableId = tableIdJson.asText();
+            try {
+                int tid = Integer.parseInt(tableId);
+                resultBuilder.forTable(IndexTableId.of(tid));
+            } catch (NumberFormatException e) {
+                resultBuilder.forTable(PiTableId.of(tableId));
+            }
         }
 
         DeviceId deviceId = DeviceId.deviceId(nullIsIllegal(json.get(DEVICE_ID),
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiActionModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiActionModelCodec.java
new file mode 100644
index 0000000..10cef43
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiActionModelCodec.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiActionModel;
+import org.onosproject.net.pi.model.PiActionParamModel;
+
+/**
+ * Codec for PiActionModel.
+ */
+public class PiActionModelCodec extends JsonCodec<PiActionModel> {
+    private static final String NAME = "name";
+    private static final String PARAMS = "params";
+
+    @Override
+    public ObjectNode encode(PiActionModel action, CodecContext context) {
+        ObjectNode result = context.mapper().createObjectNode();
+        result.put(NAME, action.name());
+        ArrayNode params = result.putArray(PARAMS);
+        action.params().forEach(param -> {
+            ObjectNode paramData = context.encode(param, PiActionParamModel.class);
+            params.add(paramData);
+        });
+        return result;
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiActionParamModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiActionParamModelCodec.java
new file mode 100644
index 0000000..ac553fe
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiActionParamModelCodec.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiActionParamModel;
+
+/**
+ * Codec for PiActionParamModel.
+ */
+public class PiActionParamModelCodec extends JsonCodec<PiActionParamModel> {
+
+    private static final String NAME = "name";
+    private static final String BIT_WIDTH = "bitWidth";
+
+    @Override
+    public ObjectNode encode(PiActionParamModel param, CodecContext context) {
+        ObjectNode result = context.mapper().createObjectNode();
+        result.put(NAME, param.name());
+        result.put(BIT_WIDTH, param.bitWidth());
+        return result;
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderFieldTypeModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderFieldTypeModelCodec.java
new file mode 100644
index 0000000..09a1566
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderFieldTypeModelCodec.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
+
+/**
+ * Codec for PiHeaderFieldTypeModel.
+ */
+public class PiHeaderFieldTypeModelCodec extends JsonCodec<PiHeaderFieldTypeModel> {
+    private static final String NAME = "name";
+    private static final String BIT_WIDTH = "bitWidth";
+
+    @Override
+    public ObjectNode encode(PiHeaderFieldTypeModel headerFieldType, CodecContext context) {
+        ObjectNode result = context.mapper().createObjectNode();
+        result.put(NAME, headerFieldType.name());
+        result.put(BIT_WIDTH, headerFieldType.bitWidth());
+        return result;
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderModelCodec.java
new file mode 100644
index 0000000..7bcd134
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderModelCodec.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiHeaderModel;
+import org.onosproject.net.pi.model.PiHeaderTypeModel;
+
+/**
+ * Codec for PiHeaderModel.
+ */
+public class PiHeaderModelCodec extends JsonCodec<PiHeaderModel> {
+    private static final String NAME = "name";
+    private static final String TYPE = "type";
+    private static final String IS_META = "isMetadata";
+    private static final String INDEX = "index";
+
+    @Override
+    public ObjectNode encode(PiHeaderModel header, CodecContext context) {
+        ObjectNode result = context.mapper().createObjectNode();
+        ObjectNode headerTypeData = context.encode(header.type(), PiHeaderTypeModel.class);
+        result.put(NAME, header.name());
+        result.set(TYPE, headerTypeData);
+        result.put(IS_META, header.isMetadata());
+        result.put(INDEX, header.index());
+        return result;
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderTypeModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderTypeModelCodec.java
new file mode 100644
index 0000000..1065a94
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderTypeModelCodec.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
+import org.onosproject.net.pi.model.PiHeaderTypeModel;
+
+/**
+ * Codec for PiHeaderTypeModel.
+ */
+public class PiHeaderTypeModelCodec extends JsonCodec<PiHeaderTypeModel> {
+    private static final String NAME = "name";
+    private static final String FIELDS = "fields";
+
+    @Override
+    public ObjectNode encode(PiHeaderTypeModel headerType, CodecContext context) {
+        ObjectNode result = context.mapper().createObjectNode();
+        result.put(NAME, headerType.name());
+        ArrayNode fields = result.putArray(FIELDS);
+
+        headerType.fields().forEach(field -> {
+            fields.add(context.encode(field, PiHeaderFieldTypeModel.class));
+        });
+
+        return result;
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiPipeconfCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiPipeconfCodec.java
new file mode 100644
index 0000000..a719741
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiPipeconfCodec.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Lists;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiPipeconf;
+
+/**
+ * Codec for PiPipeconf.
+ */
+public class PiPipeconfCodec extends JsonCodec<PiPipeconf> {
+
+    private static final String ID = "id";
+    private static final String BEHAVIORS = "behaviors";
+    private static final String EXTENSIONS = "extensions";
+
+    @Override
+    public ObjectNode encode(PiPipeconf pipeconf, CodecContext context) {
+        ObjectNode result = context.mapper().createObjectNode();
+        result.put(ID, pipeconf.id().id());
+        ArrayNode behaviors = result.putArray(BEHAVIORS);
+        pipeconf.behaviours().forEach(behavior -> {
+            behaviors.add(behavior.getSimpleName());
+        });
+        ArrayNode extensions = result.putArray(EXTENSIONS);
+        Lists.newArrayList(PiPipeconf.ExtensionType.values()).forEach(extension -> {
+            if (pipeconf.extension(extension).isPresent()) {
+                extensions.add(extension.toString());
+            }
+        });
+        return result;
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiPipelineModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiPipelineModelCodec.java
new file mode 100644
index 0000000..6b42c8d
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiPipelineModelCodec.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiActionModel;
+import org.onosproject.net.pi.model.PiHeaderModel;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.model.PiTableModel;
+
+/**
+ * Codec for PiPipelineModel.
+ */
+public class PiPipelineModelCodec extends JsonCodec<PiPipelineModel> {
+    private static final String HEADERS = "headers";
+    private static final String ACTIONS = "actions";
+    private static final String TABLES = "tables";
+
+    @Override
+    public ObjectNode encode(PiPipelineModel pipeline, CodecContext context) {
+        ObjectNode result = context.mapper().createObjectNode();
+        ArrayNode headers = result.putArray(HEADERS);
+        pipeline.headers().stream()
+                .map(header -> context.encode(header, PiHeaderModel.class))
+                .forEach(headers::add);
+        ArrayNode actions = result.putArray(ACTIONS);
+        pipeline.actions().stream()
+                .map(action -> context.encode(action, PiActionModel.class))
+                .forEach(actions::add);
+        ArrayNode tables = result.putArray(TABLES);
+        pipeline.tables().stream()
+                .map(table -> context.encode(table, PiTableModel.class))
+                .forEach(tables::add);
+        return result;
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiTableMatchFieldModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiTableMatchFieldModelCodec.java
new file mode 100644
index 0000000..a94bcba
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiTableMatchFieldModelCodec.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
+import org.onosproject.net.pi.model.PiHeaderModel;
+import org.onosproject.net.pi.model.PiTableMatchFieldModel;
+
+/**
+ * Codec for PiTableMatchFieldModel.
+ */
+public class PiTableMatchFieldModelCodec extends JsonCodec<PiTableMatchFieldModel> {
+    private static final String MATCH_TYPE = "matchType";
+    private static final String HEADER = "header";
+    private static final String FIELD = "field";
+    @Override
+    public ObjectNode encode(PiTableMatchFieldModel tableMatchField, CodecContext context) {
+        ObjectNode result = context.mapper().createObjectNode();
+        result.put(MATCH_TYPE, tableMatchField.matchType().toString());
+        PiHeaderModel header = tableMatchField.field().header();
+        PiHeaderFieldTypeModel field = tableMatchField.field().type();
+        ObjectNode headerData = context.encode(header, PiHeaderModel.class);
+        ObjectNode headerFieldData = context.encode(field, PiHeaderFieldTypeModel.class);
+        result.set(HEADER, headerData);
+        result.set(FIELD, headerFieldData);
+        return result;
+    }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiTableModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiTableModelCodec.java
new file mode 100644
index 0000000..052e083
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiTableModelCodec.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiTableMatchFieldModel;
+import org.onosproject.net.pi.model.PiTableModel;
+
+/**
+ * Codec for PiTableModel.
+ */
+public class PiTableModelCodec extends JsonCodec<PiTableModel> {
+    private static final String NAME = "name";
+    private static final String MAX_SIZE = "maxSize";
+    private static final String HAS_COUNTERS = "hasCounters";
+    private static final String SUPPORT_AGING = "supportAging";
+    private static final String ACTIONS = "actions";
+    private static final String MATCH_FIELDS = "matchFields";
+
+
+    @Override
+    public ObjectNode encode(PiTableModel table, CodecContext context) {
+
+        ObjectNode result = context.mapper().createObjectNode();
+
+        result.put(NAME, table.name());
+        result.put(MAX_SIZE, table.maxSize());
+        result.put(HAS_COUNTERS, table.hasCounters());
+        result.put(SUPPORT_AGING, table.supportsAging());
+
+        ArrayNode matchFields = result.putArray(MATCH_FIELDS);
+        table.matchFields().forEach(matchField -> {
+            ObjectNode matchFieldData =
+                    context.encode(matchField, PiTableMatchFieldModel.class);
+            matchFields.add(matchFieldData);
+        });
+
+        ArrayNode actions = result.putArray(ACTIONS);
+        table.actions().forEach(action -> {
+            actions.add(action.name());
+        });
+
+        return result;
+    }
+}
diff --git a/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java b/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java
index 93fa3f0..e1fea28 100644
--- a/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java
+++ b/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java
@@ -106,6 +106,7 @@
                                             descr.vlan(),
                                             descr.location(),
                                             ImmutableSet.copyOf(descr.ipAddress()),
+                                            descr.configured(),
                                             descr.annotations());
         synchronized (this) {
             hosts.put(hostId, newhost);
@@ -141,6 +142,7 @@
         StoredHost updated = new StoredHost(providerId, host.id(),
                                             host.mac(), host.vlan(),
                                             descr.location(), addresses,
+                                            descr.configured(),
                                             annotations);
         event = new HostEvent(HOST_UPDATED, updated);
         synchronized (this) {
@@ -257,8 +259,8 @@
          */
         public StoredHost(ProviderId providerId, HostId id,
                           MacAddress mac, VlanId vlan, HostLocation location,
-                          Set<IpAddress> ips, Annotations... annotations) {
-            super(providerId, id, mac, vlan, location, ips, annotations);
+                          Set<IpAddress> ips, boolean configured, Annotations... annotations) {
+            super(providerId, id, mac, vlan, location, ips, configured, annotations);
             this.location = location;
         }
 
diff --git a/core/net/src/main/java/org/onosproject/cluster/impl/ConfigFileBasedClusterMetadataProvider.java b/core/net/src/main/java/org/onosproject/cluster/impl/ConfigFileBasedClusterMetadataProvider.java
index b097e2a..6d5abf1 100644
--- a/core/net/src/main/java/org/onosproject/cluster/impl/ConfigFileBasedClusterMetadataProvider.java
+++ b/core/net/src/main/java/org/onosproject/cluster/impl/ConfigFileBasedClusterMetadataProvider.java
@@ -204,16 +204,21 @@
                 version = file.lastModified();
                 metadata = mapper.readValue(new FileInputStream(file), ClusterMetadata.class);
             } else if ("http".equals(url.getProtocol())) {
-                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
-                if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+                try {
+                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+                    if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+                        log.warn("Could not reach metadata URL {}. Retrying...", url);
+                        return null;
+                    }
+                    if (conn.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT) {
+                        return null;
+                    }
+                    version = conn.getLastModified();
+                    metadata = mapper.readValue(conn.getInputStream(), ClusterMetadata.class);
+                } catch (IOException e) {
                     log.warn("Could not reach metadata URL {}. Retrying...", url);
                     return null;
                 }
-                if (conn.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT) {
-                    return null;
-                }
-                version = conn.getLastModified();
-                metadata = mapper.readValue(conn.getInputStream(), ClusterMetadata.class);
             }
 
             if (null == metadata) {
diff --git a/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java b/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
index 66159f7..b7a38e6 100644
--- a/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
+++ b/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
@@ -464,10 +464,13 @@
             store.removePendingHostLocation(probeMac);
         }
 
+        /**
+         * Providers should only be able to remove a host that is provided by itself,
+         * or a host that is not configured.
+         */
         private boolean allowedToChange(HostId hostId) {
-            // Disallow removing inexistent host or host provided by others
             Host host = store.getHost(hostId);
-            return host != null && host.providerId().equals(provider().id());
+            return host == null || !host.configured() || host.providerId().equals(provider().id());
         }
     }
 
diff --git a/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java b/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java
index ba27dcb..f2beda1 100644
--- a/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java
@@ -259,7 +259,8 @@
     }
 
     /**
-     * Providers should only be able to remove a host provided by itself.
+     * Providers should only be able to remove a host that is provided by itself,
+     * or a host that is not configured.
      */
     @Test
     public void hostVanishedWithMultipleProviders() {
@@ -267,16 +268,15 @@
         configured(HID2, MAC2, VLAN2, LOC2, IP2);
 
         providerService2.hostVanished(HID1);
-        assertNotNull("host should not be removed", mgr.getHost(HID1));
+        assertNull("Should be able to remove learnt host", mgr.getHost(HID1));
 
         providerService.hostVanished(HID2);
-        assertNotNull("host should not be removed", mgr.getHost(HID2));
-
-        providerService.hostVanished(HID1);
-        assertNull("host should be removed", mgr.getHost(HID1));
+        assertNotNull("Should not be able to remove configured host since the provider is different",
+                mgr.getHost(HID2));
 
         providerService2.hostVanished(HID2);
-        assertNull("host should be removed", mgr.getHost(HID2));
+        assertNull("Should be able to remove configured host when provider is the same",
+                mgr.getHost(HID2));
     }
 
     private void validateHosts(
diff --git a/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java b/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java
index c64942f..532b279 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java
@@ -554,7 +554,7 @@
                             log.warn("Unable to fetch bits for application {} from node {}",
                                      app.id().name(), node.id());
                         }
-                    }, executor);
+                    }, messageHandlingExecutor);
         }
 
         try {
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/FederatedDistributedPrimitiveCreator.java b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/FederatedDistributedPrimitiveCreator.java
index b7bb8f6..8d5668f 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/FederatedDistributedPrimitiveCreator.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/FederatedDistributedPrimitiveCreator.java
@@ -147,7 +147,7 @@
         Map<PartitionId, AsyncDocumentTree<V>> trees =
                 Maps.transformValues(members, partition -> partition.<V>newAsyncDocumentTree(name, serializer));
         Hasher<DocumentPath> hasher = key -> {
-            int bucket = Math.abs(Hashing.murmur3_32().hashUnencodedChars(key.toString()).asInt()) % buckets;
+            int bucket = Math.abs(Hashing.murmur3_32().hashUnencodedChars(String.valueOf(key)).asInt()) % buckets;
             return sortedMemberPartitionIds.get(Hashing.consistentHash(bucket, sortedMemberPartitionIds.size()));
         };
         return new PartitionedAsyncDocumentTree<>(name, trees, hasher);
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/PartitionedAsyncDocumentTree.java b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/PartitionedAsyncDocumentTree.java
index 3c53e30..71758a0 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/PartitionedAsyncDocumentTree.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/PartitionedAsyncDocumentTree.java
@@ -107,11 +107,16 @@
 
     @Override
     public CompletableFuture<Boolean> create(DocumentPath path, V value) {
+        if (path.parent() == null) {
+            // create value on root
+            return partition(path).createRecursive(path, value);
+        }
         // TODO: This operation is not atomic
-        return partition(path.parent()).get(path).thenCompose(parentValue -> {
+        return partition(path.parent()).get(path.parent()).thenCompose(parentValue -> {
             if (parentValue == null) {
-                return Tools.exceptionalFuture(new NoSuchDocumentPathException(path.parent().toString()));
+                return Tools.exceptionalFuture(new NoSuchDocumentPathException(String.valueOf(path.parent())));
             } else {
+                // not atomic: parent did exist at some point, so moving forward
                 return partition(path).createRecursive(path, value);
             }
         });
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/StoragePartitionServer.java b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/StoragePartitionServer.java
index 36b854f..bb071a2 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/StoragePartitionServer.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/StoragePartitionServer.java
@@ -40,7 +40,6 @@
 
     private final Logger log = getLogger(getClass());
 
-    private static final int MAX_ENTRIES_PER_LOG_SEGMENT = 32768;
     private static final int MAX_SEGMENT_SIZE = 1024 * 1024 * 64;
     private static final long ELECTION_TIMEOUT_MILLIS = 2500;
     private static final long HEARTBEAT_INTERVAL_MILLIS = 1000;
@@ -108,7 +107,6 @@
                         .withStorageLevel(StorageLevel.MAPPED)
                         .withSerializer(new AtomixSerializerAdapter(Serializer.using(StorageNamespaces.RAFT_STORAGE)))
                         .withDirectory(dataFolder)
-                        .withMaxEntriesPerSegment(MAX_ENTRIES_PER_LOG_SEGMENT)
                         .withMaxSegmentSize(MAX_SEGMENT_SIZE)
                         .build());
         StoragePartition.RAFT_SERVICES.forEach(builder::addService);
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index 49e2969..6413037 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -186,6 +186,7 @@
 import org.onosproject.net.intent.constraint.AnnotationConstraint;
 import org.onosproject.net.intent.constraint.BandwidthConstraint;
 import org.onosproject.net.intent.constraint.BooleanConstraint;
+import org.onosproject.net.intent.constraint.DomainConstraint;
 import org.onosproject.net.intent.constraint.EncapsulationConstraint;
 import org.onosproject.net.intent.constraint.HashedPathSelectionConstraint;
 import org.onosproject.net.intent.constraint.LatencyConstraint;
@@ -590,6 +591,7 @@
             .register(MarkerResource.class)
             .register(new BitSetSerializer(), BitSet.class)
             .register(DomainIntent.class)
+            .register(DomainConstraint.class)
             .register(
                     // PI model
                     PiMatchType.class,
diff --git a/drivers/optical/src/test/resources/org/onosproject/driver/optical/config/flow_table_config.json b/drivers/optical/src/test/resources/org/onosproject/driver/optical/config/flow_table_config.json
index 3d8a8c3..ca08279 100644
--- a/drivers/optical/src/test/resources/org/onosproject/driver/optical/config/flow_table_config.json
+++ b/drivers/optical/src/test/resources/org/onosproject/driver/optical/config/flow_table_config.json
@@ -7,6 +7,7 @@
           {
             "id": "3",
             "tableId": 0,
+            "tableName": "0",
             "appId": "test",
             "priority": 4,
             "timeout": 0,
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java
index 5fb0497..e14363e 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java
@@ -80,6 +80,7 @@
 
     public static final String TABLE0 = "table0";
     public static final String TABLE0_COUNTER = "table0_counter";
+    public static final String ECMP = "ecmp";
     public static final String SEND_TO_CPU = "send_to_cpu";
     public static final String PORT = "port";
     public static final String DROP = "_drop";
@@ -88,13 +89,15 @@
     public static final String INGRESS_PORT = "ingress_port";
 
     private static final PiTableId TABLE0_ID = PiTableId.of(TABLE0);
+    private static final PiTableId ECMP_ID = PiTableId.of(ECMP);
 
     protected static final PiHeaderFieldId ETH_DST_ID = PiHeaderFieldId.of("ethernet", "dstAddr");
     protected static final PiHeaderFieldId ETH_SRC_ID = PiHeaderFieldId.of("ethernet", "srcAddr");
     protected static final PiHeaderFieldId ETH_TYPE_ID = PiHeaderFieldId.of("ethernet", "etherType");
 
     private static final ImmutableBiMap<Integer, PiTableId> TABLE_MAP = ImmutableBiMap.of(
-            0, TABLE0_ID);
+            0, TABLE0_ID,
+            1, ECMP_ID);
 
     private static final ImmutableBiMap<PiTableId, PiCounterId> TABLE_COUNTER_MAP = ImmutableBiMap.of(
             TABLE0_ID, PiCounterId.of(TABLE0_COUNTER, PiCounterType.DIRECT));
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
index 50b808e..c2cc0b5 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
@@ -76,8 +76,7 @@
     /*
     If true, we avoid querying the device and return the content of the ENTRY_STORE.
      */
-    // TODO: can remove this check as soon as the BMv2 bug when reading ECMP entries is fixed.
-    private boolean ignoreDeviceWhenGet = true;
+    private boolean ignoreDeviceWhenGet = false;
 
     /*
     If true, we read all direct counters of a table with one request. Otherwise, send as many request as the number of
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModel.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModel.java
index 02d2274..b19ef61 100644
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModel.java
+++ b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModel.java
@@ -68,7 +68,7 @@
         Map<String, PiHeaderModel> headerModelMap = Maps.newHashMap();
         headerModels.stream()
                 .filter(Objects::nonNull)
-                .forEach(hm -> headerModelMap.put(hm.type().name(), hm));
+                .forEach(hm -> headerModelMap.put(hm.name(), hm));
         this.headerModels = ImmutableMap.copyOf(headerModelMap);
 
         Map<String, PiActionModel> actionModelMap = Maps.newHashMap();
diff --git a/incubator/protobuf/models/src/main/proto/cluster/NodeIdProto.proto b/incubator/protobuf/models/src/main/proto/cluster/NodeIdProto.proto
new file mode 100644
index 0000000..2d062fd
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/cluster/NodeIdProto.proto
@@ -0,0 +1,8 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.cluster.models";
+
+package cluster;
+
+message NodeIdProto {
+    string node_id = 1;
+}
\ No newline at end of file
diff --git a/incubator/protobuf/models/src/main/proto/net/meter/BandEnumsProto.proto b/incubator/protobuf/models/src/main/proto/net/meter/BandEnumsProto.proto
new file mode 100644
index 0000000..10879a7
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/net/meter/BandEnumsProto.proto
@@ -0,0 +1,28 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.meter.models";
+
+package net.meter;
+
+/**
+* Specifies the type of band.
+*/
+enum BandTypeProto {
+    /**
+    * Simple rate limiter which drops packets
+    * when the rate is exceeded.
+    */
+    DROP = 0;
+
+    /**
+    * defines a simple DiffServ policer that remark
+    * the drop precedence of the DSCP field in the
+    * IP header of the packets that exceed the band
+    * rate value.
+    */
+    REMARK = 1;
+
+    /**
+    * defines an experimental meter band.
+    */
+    EXPERIMENTAL = 2;
+}
diff --git a/incubator/protobuf/models/src/main/proto/net/meter/BandProto.proto b/incubator/protobuf/models/src/main/proto/net/meter/BandProto.proto
new file mode 100644
index 0000000..ca7f28b
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/net/meter/BandProto.proto
@@ -0,0 +1,16 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.meter.models";
+
+package net.meter;
+
+import "net/meter/BandEnumsProto.proto";
+
+// Corresponds to  org.onosproject.net.meter.Band.
+message BandProto {
+    uint64 rate = 1;
+    uint64 burst = 2;
+    uint32 drop_precedence = 3;
+    BandTypeProto type = 4;
+    uint64 packets = 5;
+    uint64 bytes = 6;
+}
\ No newline at end of file
diff --git a/incubator/protobuf/models/src/main/proto/net/meter/MeterEnumsProto.proto b/incubator/protobuf/models/src/main/proto/net/meter/MeterEnumsProto.proto
new file mode 100644
index 0000000..3d1a67f
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/net/meter/MeterEnumsProto.proto
@@ -0,0 +1,66 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.meter.models";
+
+package net.meter;
+
+enum MeterUnitProto {
+    /**
+    * Packets per second.
+    */
+    PKTS_PER_SEC = 0;
+
+    /**
+    * Kilo bits per second.
+    */
+    KB_PER_SEC = 1;
+}
+
+enum MeterStateProto {
+    /**
+    * The meter is in the process of being added.
+    */
+    PENDING_ADD = 0;
+
+    /**
+    * THe meter has been added.
+    */
+    ADDED = 1;
+
+    /**
+    * The meter is in the process of being removed.
+    */
+    PENDING_REMOVE = 2;
+
+    /**
+    * The meter has been removed.
+    */
+    REMOVED = 3;
+}
+
+enum MeterRequestTypeProto {
+    ADD = 0;
+    MODIFY = 1;
+    REMOVE = 2;
+}
+
+enum MeterEventTypeProto {
+     /**
+     * A meter addition was requested.
+     */
+     METER_ADD_REQ = 0;
+
+     /**
+     * A meter removal was requested.
+     */
+     METER_REM_REQ = 1;
+
+     /**
+     * A meter was finally added to device.
+     */
+     METER_ADDED = 2;
+
+     /**
+     * A meter was finally removed from device.
+     */
+      METER_REMOVED = 3;
+}
\ No newline at end of file
diff --git a/incubator/protobuf/models/src/main/proto/net/meter/MeterEventProto.proto b/incubator/protobuf/models/src/main/proto/net/meter/MeterEventProto.proto
new file mode 100644
index 0000000..54ad1cc
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/net/meter/MeterEventProto.proto
@@ -0,0 +1,13 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.meter.models";
+
+package net.meter;
+
+import "net/meter/MeterEnumsProto.proto";
+import "net/meter/MeterProto.proto";
+
+// Corresponds to org.onosproject.net.meter.MeterEvent.
+message MeterNotificationProto {
+    MeterEventTypeProto type = 1;
+    MeterProto meter = 2;
+}
\ No newline at end of file
diff --git a/incubator/protobuf/models/src/main/proto/net/meter/MeterProto.proto b/incubator/protobuf/models/src/main/proto/net/meter/MeterProto.proto
new file mode 100644
index 0000000..02be225
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/net/meter/MeterProto.proto
@@ -0,0 +1,22 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.meter.models";
+
+package net.meter;
+
+import "net/meter/BandProto.proto";
+import "net/meter/MeterEnumsProto.proto";
+import "core/ApplicationIdProto.proto";
+
+message MeterProto {
+    string device_id = 1;
+    uint64 meter_id = 2;
+    core.ApplicationIdProto application_id = 3;
+    MeterUnitProto unit = 4;
+    bool is_burst = 5;
+    repeated BandProto bands = 6;
+    MeterStateProto state = 7;
+    uint64 life = 8;
+    uint64 reference_count = 9;
+    uint64 packets_seen = 10;
+    uint64 bytes_seen = 11;
+}
\ No newline at end of file
diff --git a/incubator/protobuf/models/src/main/proto/net/meter/MeterRequestProto.proto b/incubator/protobuf/models/src/main/proto/net/meter/MeterRequestProto.proto
new file mode 100644
index 0000000..5fdebb6
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/net/meter/MeterRequestProto.proto
@@ -0,0 +1,17 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.meter.models";
+
+package net.meter;
+
+import "net/meter/BandProto.proto";
+import "net/meter/MeterEnumsProto.proto";
+import "core/ApplicationIdProto.proto";
+
+message MeterRequestProto {
+    string device_id = 1;
+    core.ApplicationIdProto application_id = 2;
+    MeterUnitProto unit = 3;
+    bool is_burst = 4;
+    repeated BandProto bands = 5;
+    MeterRequestTypeProto type = 6;
+}
\ No newline at end of file
diff --git a/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/net/meter/GrpcNbMeterService.java b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/net/meter/GrpcNbMeterService.java
new file mode 100644
index 0000000..64ced8a
--- /dev/null
+++ b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/net/meter/GrpcNbMeterService.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.grpc.nb.net.meter;
+
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.net.meter.MeterService;
+import org.onosproject.grpc.nb.utils.GrpcNbMeterServiceUtil;
+
+import org.onosproject.grpc.nb.net.meter.MeterServiceGrpc.MeterServiceImplBase;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.submitRequest;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.submitReply;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.withdrawRequest;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.withdrawReply;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.getMeterRequest;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.getMeterReply;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.getAllMetersRequest;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.getAllMetersReply;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.getMetersRequest;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.getMetersReply;
+
+import io.grpc.stub.StreamObserver;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Deactivate;
+import com.google.common.annotations.Beta;
+import org.apache.felix.scr.annotations.Component;
+
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterId;
+import org.onosproject.net.DeviceId;
+
+/**
+ * A server that provides access to the methods exposed by {@link MeterService}.
+ * TODO this requires major refactoring, translation should be delegated to calls to
+ * TODO{@link GrpcNbMeterServiceUtil}.
+ */
+@Beta
+@Component(immediate = true)
+public class GrpcNbMeterService {
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MeterService meterService;
+
+    @Activate
+    public void activate() {
+        //TODO this should contact the registry service and register an instance
+        // of this service.
+    }
+
+    @Deactivate
+    public void deactivate() {
+    }
+
+    private class MeterServiceNbServerInternal extends MeterServiceImplBase {
+
+        public MeterServiceNbServerInternal() {
+            super();
+        }
+
+        @Override
+        public void submit(submitRequest request,
+                           StreamObserver<submitReply> responseObserver) {
+            submitReply.Builder replyBuilder = submitReply.newBuilder();
+            Meter meter = meterService.submit(GrpcNbMeterServiceUtil.translate(request.getMeter()));
+            responseObserver.onNext(replyBuilder.setSubmitMeter(GrpcNbMeterServiceUtil.translate(meter)).build());
+            responseObserver.onCompleted();
+        }
+
+        @Override
+        public void withdraw(withdrawRequest request,
+                             StreamObserver<withdrawReply> responseObserver) {
+            withdrawReply.Builder replyBuilder = withdrawReply.newBuilder();
+            meterService.withdraw(GrpcNbMeterServiceUtil.translate(request.getMeter()),
+                    MeterId.meterId(request.getMeterId()));
+            responseObserver.onNext(replyBuilder.build());
+            responseObserver.onCompleted();
+        }
+
+        @Override
+        public void getMeter(getMeterRequest request,
+                             StreamObserver<getMeterReply> responseObserver) {
+            getMeterReply.Builder replyBuilder = getMeterReply.newBuilder();
+            Meter meter = meterService.getMeter(DeviceId.deviceId(request.getDeviceId()),
+                    MeterId.meterId(request.getMeterId()));
+            responseObserver.onNext(replyBuilder.setMeter(GrpcNbMeterServiceUtil.translate(meter)).build());
+            responseObserver.onCompleted();
+        }
+
+        @Override
+        public void getAllMeters(getAllMetersRequest request,
+                                 StreamObserver<getAllMetersReply> responseObserver) {
+            getAllMetersReply.Builder replyBuilder = getAllMetersReply.newBuilder();
+            meterService.getAllMeters().forEach(d -> {
+                replyBuilder.addMeters(GrpcNbMeterServiceUtil.translate(d));
+            });
+            responseObserver.onNext(replyBuilder.build());
+            responseObserver.onCompleted();
+        }
+
+        @Override
+        public void getMeters(getMetersRequest request,
+                              StreamObserver<getMetersReply> responseObserver) {
+            getMetersReply.Builder replyBuilder = getMetersReply.newBuilder();
+            meterService.getMeters(DeviceId.deviceId(request.getDeviceId())).forEach(d -> {
+                replyBuilder.addMeters(GrpcNbMeterServiceUtil.translate(d));
+            });
+            responseObserver.onNext(replyBuilder.build());
+            responseObserver.onCompleted();
+        }
+    }
+}
+
diff --git a/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/net/meter/package-info.java b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/net/meter/package-info.java
new file mode 100644
index 0000000..3b63c95
--- /dev/null
+++ b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/net/meter/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+/**
+ * gRPC server implementations for northbound services.
+ */
+package org.onosproject.grpc.nb.net.meter;
\ No newline at end of file
diff --git a/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/utils/GrpcNbMeterServiceUtil.java b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/utils/GrpcNbMeterServiceUtil.java
new file mode 100644
index 0000000..43c4908
--- /dev/null
+++ b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/utils/GrpcNbMeterServiceUtil.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.grpc.nb.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.onosproject.net.meter.Band;
+import org.onosproject.net.meter.MeterRequest;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.DefaultBand;
+import org.onosproject.net.meter.DefaultMeterRequest;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterState;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.grpc.core.models.ApplicationIdProtoOuterClass.ApplicationIdProto;
+import org.onosproject.grpc.net.meter.models.BandProtoOuterClass.BandProto;
+import org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterRequestTypeProto;
+import org.onosproject.grpc.net.meter.models.MeterRequestProtoOuterClass.MeterRequestProto;
+import org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterUnitProto;
+import org.onosproject.grpc.net.meter.models.BandEnumsProto.BandTypeProto;
+import org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterStateProto;
+import org.onosproject.grpc.net.meter.models.MeterProtoOuterClass.MeterProto;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.stream.Collectors;
+import com.google.common.annotations.Beta;
+
+/**
+ * gRPC message conversion related utilities for meter service.
+ */
+@Beta
+public final class GrpcNbMeterServiceUtil {
+    private static final Logger log = LoggerFactory.getLogger(GrpcNbMeterServiceUtil.class);
+
+    /**
+     * Translates gRPC ApplicationId to {@link ApplicationId}.
+     *
+     * @param gAppId gRPC message
+     * @return {@link ApplicationId}
+     */
+    public static ApplicationId translate(ApplicationIdProto gAppId) {
+        int id = gAppId.getId();
+        String name = gAppId.getName();
+        ApplicationId appId = new DefaultApplicationId(id, name);
+        return appId;
+    }
+
+    /**
+     * Translates gRPC Band to {@link Band}.
+     *
+     * @param gBand gRPC message
+     * @return {@link Band}
+     */
+    public static Band translate(BandProto gBand) {
+        Band.Type type = translate(gBand.getType());
+        long rate = gBand.getRate();
+        long burstSize = gBand.getBurst();
+        short prec = (short) gBand.getDropPrecedence();
+        Band band = new DefaultBand(type, rate, burstSize, prec);
+        return band;
+    }
+
+    /**
+     * Translates gRPC List Bands to Collection Band.
+     *
+     * @param listBands gRPC message
+     * @return Collection Band
+     */
+    public static Collection<Band> translate(java.util.List<BandProto>  listBands) {
+        Collection<Band> bands = new ArrayList<>();
+        listBands.forEach(d -> {
+            bands.add(translate(d));
+        });
+        return bands;
+    }
+
+    /**
+     * Translates gRPC MeterRequestType to {@link MeterRequest.Type}.
+     *
+     * @param type gRPC message
+     * @return {@link MeterRequest.Type}
+     */
+    public static MeterRequest.Type translate(MeterRequestTypeProto type) {
+        switch (type) {
+            case ADD:
+                return MeterRequest.Type.ADD;
+            case MODIFY:
+                return MeterRequest.Type.MODIFY;
+            case REMOVE:
+                return MeterRequest.Type.REMOVE;
+            case UNRECOGNIZED:
+                log.warn("Unrecognized MeterRequest type gRPC message: {}", type);
+                return null;
+            default:
+                log.warn("Unrecognized MeterRequest type gRPC message: {}", type);
+                return null;
+        }
+    }
+
+    /**
+     * Translates gRPC MeterRequest to {@link MeterRequest}.
+     *
+     * @param meterRequest gRPC message
+     * @return {@link MeterRequest}
+     */
+    public static MeterRequest translate(MeterRequestProto meterRequest) {
+
+        DeviceId deviceid = DeviceId.deviceId(meterRequest.getDeviceId());
+        ApplicationId appId = translate(meterRequest.getApplicationId());
+        Meter.Unit unit = translate(meterRequest.getUnit());
+        boolean burst = meterRequest.getIsBurst();
+        Collection<Band> bands = translate(meterRequest.getBandsList());
+        MeterRequest.Type type = translate(meterRequest.getType());
+        if (type == MeterRequest.Type.ADD) {
+            return DefaultMeterRequest.builder()
+                    .forDevice(deviceid)
+                    .fromApp(appId)
+                    .withUnit(unit)
+                    .withBands(bands)
+                    .add();
+        } else {
+            return DefaultMeterRequest.builder()
+                    .forDevice(deviceid)
+                    .fromApp(appId)
+                    .withUnit(unit)
+                    .withBands(bands)
+                    .remove();
+        }
+    }
+
+    /**
+     * Translates {@link ApplicationId} to gRPC ApplicationId message.
+     *
+     * @param appId {@link ApplicationId}
+     * @return gRPC ApplicationId message
+     */
+    public static ApplicationIdProto translate(ApplicationId appId) {
+        return ApplicationIdProto.newBuilder()
+                .setId(appId.id())
+                .setName(appId.name())
+                .build();
+    }
+
+    /**
+     * Translates gRPC enum MeterUnit to ONOS enum.
+     *
+     * @param unit meterUnit in gRPC enum
+     * @return equivalent in ONOS enum
+     */
+    public static Meter.Unit translate(MeterUnitProto unit) {
+        switch (unit) {
+            case PKTS_PER_SEC:
+                return Meter.Unit.PKTS_PER_SEC;
+            case KB_PER_SEC:
+                return Meter.Unit.KB_PER_SEC;
+            case UNRECOGNIZED:
+                log.warn("Unrecognized MeterUnit gRPC message: {}", unit);
+                return null;
+            default:
+                log.warn("Unrecognized MeterUnit gRPC message: {}", unit);
+                return null;
+        }
+    }
+
+    /**
+     * Translates ONOS enum Meter.Unit Type to gRPC enum.
+     *
+     * @param unit Meter.Unit in ONOS enum
+     * @return equivalent in gRPC enum
+     */
+    public static MeterUnitProto translate(Meter.Unit unit) {
+        switch (unit) {
+            case PKTS_PER_SEC:
+                return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterUnitProto.PKTS_PER_SEC;
+            case KB_PER_SEC:
+                return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterUnitProto.KB_PER_SEC;
+            default:
+                log.warn("Unrecognized MeterUnit ONOS message: {}", unit);
+                return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterUnitProto.UNRECOGNIZED;
+        }
+    }
+
+    /**
+     * Translates gRPC enum Band Type to ONOS enum.
+     *
+     * @param bandType BandType in gRPC enum
+     * @return equivalent in ONOS enum
+     */
+    public static Band.Type translate(BandTypeProto bandType) {
+        switch (bandType) {
+            case DROP:
+                return Band.Type.DROP;
+            case REMARK:
+                return Band.Type.REMARK;
+            case EXPERIMENTAL:
+                return Band.Type.EXPERIMENTAL;
+            case UNRECOGNIZED:
+                log.warn("Unrecognized BandType gRPC message: {}", bandType);
+                return null;
+            default:
+                log.warn("Unrecognized BandType gRPC message: {}", bandType);
+                return null;
+        }
+    }
+
+    /**
+     * Translates ONOS enum Band Type to gRPC enum.
+     *
+     * @param bandType BandType in ONOS enum
+     * @return equivalent in gRPC enum
+     */
+    public static BandTypeProto translate(Band.Type bandType) {
+        switch (bandType) {
+            case DROP:
+                return org.onosproject.grpc.net.meter.models.BandEnumsProto.BandTypeProto.DROP;
+            case REMARK:
+                return org.onosproject.grpc.net.meter.models.BandEnumsProto.BandTypeProto.REMARK;
+            case EXPERIMENTAL:
+                return org.onosproject.grpc.net.meter.models.BandEnumsProto.BandTypeProto.EXPERIMENTAL;
+            default:
+                log.warn("Unrecognized BandType ONOS message: {}", bandType);
+                return null;
+        }
+    }
+
+    /**
+     * Translates ONOS enum MeterState Type to gRPC enum.
+     *
+     * @param meterState MeterState in ONOS enum
+     * @return equivalent in gRPC enum
+     */
+    public static MeterStateProto translate(MeterState meterState) {
+        switch (meterState) {
+            case PENDING_ADD:
+                return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterStateProto.PENDING_ADD;
+            case ADDED:
+                return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterStateProto.ADDED;
+            case PENDING_REMOVE:
+                return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterStateProto.PENDING_REMOVE;
+            case REMOVED:
+                return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterStateProto.REMOVED;
+            default:
+                log.warn("Unrecognized MeterState ONOS message: {}", meterState);
+                return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterStateProto.UNRECOGNIZED;
+        }
+    }
+
+    /**
+     * Translates {@link Meter} to gRPC MeterCore message.
+     *
+     * @param meter {@link Meter}
+     * @return gRPC MeterCore message
+     */
+    public static MeterProto translate(Meter meter) {
+        return MeterProto.newBuilder()
+                .setDeviceId(meter.deviceId().toString())
+                .setApplicationId(translate(meter.appId()))
+                .setUnit(translate(meter.unit()))
+                .setIsBurst(meter.isBurst())
+                .addAllBands(meter.bands().stream()
+                        .map(b -> BandProto.newBuilder()
+                                .setRate(b.rate())
+                                .setBurst(b.burst())
+                                .setDropPrecedence(b.dropPrecedence())
+                                .setType(translate(b.type()))
+                                .setPackets(b.packets())
+                                .setBytes(b.bytes())
+                                .build())
+                        .collect(Collectors.toList()))
+                .setState(translate(meter.state()))
+                .setLife(meter.life())
+                .setReferenceCount(meter.referenceCount())
+                .setPacketsSeen(meter.packetsSeen())
+                .setBytesSeen(meter.bytesSeen())
+                .build();
+    }
+
+    // Utility class not intended for instantiation.
+    private GrpcNbMeterServiceUtil() {
+    }
+}
diff --git a/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/utils/package-info.java b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/utils/package-info.java
new file mode 100644
index 0000000..341744c
--- /dev/null
+++ b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/utils/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * A package contains a set of utilities that are used to convert gRPC model
+ * object to ONOS data model object.
+ */
+package org.onosproject.grpc.nb.utils;
\ No newline at end of file
diff --git a/incubator/protobuf/services/nb/src/main/proto/net/meter/MeterServiceNbProto.proto b/incubator/protobuf/services/nb/src/main/proto/net/meter/MeterServiceNbProto.proto
new file mode 100644
index 0000000..0289f26
--- /dev/null
+++ b/incubator/protobuf/services/nb/src/main/proto/net/meter/MeterServiceNbProto.proto
@@ -0,0 +1,55 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.nb.net.meter";
+
+package nb.net.meter;
+
+import "net/meter/MeterProto.proto";
+import "net/meter/MeterRequestProto.proto";
+
+message submitRequest {
+    .net.meter.MeterRequestProto meter = 1;
+}
+
+message submitReply {
+    .net.meter.MeterProto submit_meter = 1;
+}
+
+message withdrawRequest {
+    .net.meter.MeterRequestProto meter = 1;
+    uint64 meter_id = 2;
+}
+
+message withdrawReply {
+}
+
+message getMeterRequest {
+    string device_id = 1;
+    uint64 meter_id = 2;
+}
+
+message getMeterReply {
+    .net.meter.MeterProto meter = 1;
+}
+
+message getAllMetersRequest {
+}
+
+message getAllMetersReply {
+    repeated .net.meter.MeterProto meters = 1;
+}
+
+message getMetersRequest {
+    string device_id = 1;
+}
+
+message getMetersReply {
+    repeated .net.meter.MeterProto meters = 1;
+}
+
+service MeterService {
+     rpc submit(submitRequest) returns (submitReply) {}
+     rpc withdraw(withdrawRequest) returns (withdrawReply) {}
+     rpc getMeter(getMeterRequest) returns (getMeterReply) {}
+     rpc getAllMeters(getAllMetersRequest) returns (getAllMetersReply) {}
+     rpc getMeters(getMetersRequest) returns (getMetersReply) {}
+}
\ No newline at end of file
diff --git a/modules.defs b/modules.defs
index 589b236..87428a3 100644
--- a/modules.defs
+++ b/modules.defs
@@ -221,6 +221,8 @@
     '//apps/evpn-route-service:onos-apps-evpn-route-service-oar',
     '//incubator/protobuf/registry:onos-incubator-protobuf-registry-oar',
     '//apps/openstacknetworkingui:onos-apps-openstacknetworkingui-oar',
+    '//apps/p4-tutorial/pipeconf:onos-apps-p4-tutorial-pipeconf-oar',
+    '//apps/p4-tutorial/icmpdropper:onos-apps-p4-tutorial-icmpdropper-oar',
 ]
 
 PROTOCOL_APPS = [
diff --git a/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/impl/NetconfSessionMinaImpl.java b/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/impl/NetconfSessionMinaImpl.java
index dfb82ca..99f79cd 100644
--- a/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/impl/NetconfSessionMinaImpl.java
+++ b/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/impl/NetconfSessionMinaImpl.java
@@ -578,7 +578,7 @@
      * @return XML RPC message
      */
     private String formatXmlHeader(String request) {
-        if (!request.startsWith(XML_HEADER)) {
+        if (!request.contains(XML_HEADER)) {
             //FIXME if application provides his own XML header of different type there is a clash
             if (request.startsWith(LF + HASH)) {
                 request = request.split("<")[0] + XML_HEADER + request.substring(request.split("<")[0].length());
diff --git a/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfSessionImplTest.java b/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfSessionImplTest.java
index e0386f7..51bf91c 100644
--- a/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfSessionImplTest.java
+++ b/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfSessionImplTest.java
@@ -667,7 +667,7 @@
                     Pattern.DOTALL);
 
     public static final Pattern HELLO_REQ_PATTERN_1_1 =
-            Pattern.compile("(<\\?xml).*"
+            Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
                             + "(<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
                             + "( *)(<capabilities>)\\R?"
                             + "( *)(<capability>urn:ietf:params:netconf:base:1.0</capability>)\\R?"
@@ -677,7 +677,7 @@
                     Pattern.DOTALL);
 
     public static final Pattern EDIT_CONFIG_REQ_PATTERN =
-            Pattern.compile("(<\\?xml).*"
+            Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
                     + "(<rpc message-id=\")[0-9]*(\") *(xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
                     + "(<edit-config>)\\R?"
                     + "(<target>\\R?((<" + DatastoreId.CANDIDATE.toString() + "/>)|"
@@ -689,7 +689,7 @@
 
 
     public static final Pattern LOCK_REQ_PATTERN =
-            Pattern.compile("(<\\?xml).*"
+            Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
                     + "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "
                     + "message-id=\")[0-9]*(\">)\\R?"
                     + "(<lock>)\\R?"
@@ -699,7 +699,7 @@
                     + "(</lock>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
 
     public static final Pattern UNLOCK_REQ_PATTERN =
-            Pattern.compile("(<\\?xml).*"
+            Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
                     + "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "
                     + "message-id=\")[0-9]*(\">)\\R?"
                     + "(<unlock>)\\R?"
@@ -709,7 +709,7 @@
                     + "(</unlock>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
 
     public static final Pattern COPY_CONFIG_REQ_PATTERN =
-            Pattern.compile("(<\\?xml).*"
+            Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
                     + "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\")[0-9]*(\">)\\R?"
                     + "(<copy-config>)\\R?"
                     + "(<target>\\R?"
@@ -730,7 +730,7 @@
                     + "(</copy-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
 
     public static final Pattern GET_CONFIG_REQ_PATTERN =
-            Pattern.compile("(<\\?xml).*"
+            Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
                     + "(<rpc message-id=\")[0-9]*(\"  xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
                     + "(<get-config>)\\R?" + "(<source>)\\R?((<"
                     + DatastoreId.CANDIDATE.toString()
@@ -743,7 +743,7 @@
 
 
     public static final Pattern GET_REQ_PATTERN =
-            Pattern.compile("(<\\?xml).*"
+            Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
                     + "(<rpc message-id=\")[0-9]*(\"  xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
                     + "(<get>)\\R?"
                     + "(<filter type=\"subtree\">).*(</filter>)\\R?"
diff --git a/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java b/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java
index 40c5370..6fd0ff6 100644
--- a/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java
+++ b/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java
@@ -79,6 +79,7 @@
 import java.util.Collections;
 import java.util.Dictionary;
 import java.util.List;
+import java.util.Objects;
 import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
@@ -345,17 +346,22 @@
         } else {
             log.info("Connecting to device {} with driver {}", deviceId, basicDeviceConfig.driver());
 
-            Driver driver = driverService.getDriver(basicDeviceConfig.driver());
+            Driver driver;
+            try {
+                driver = driverService.getDriver(basicDeviceConfig.driver());
+            } catch (ItemNotFoundException e) {
+                log.warn("The driver of {} is not found : {}", deviceId, e.getMessage());
+                return;
+            }
+
             DriverData driverData = new DefaultDriverData(driver, deviceId);
-
-            DeviceHandshaker handshaker =
-                    getBehaviour(driver, DeviceHandshaker.class, driverData);
-
+            DeviceHandshaker handshaker = getBehaviour(driver, DeviceHandshaker.class, driverData);
             if (handshaker == null) {
                 log.error("Device {}, with driver {} does not support DeviceHandshaker " +
                         "behaviour, {}", deviceId, driver.name(), driver.behaviours());
                 return;
             }
+
             //Storing deviceKeyId and all other config values
             // as data in the driver with protocol_<info>
             // name as the key. e.g protocol_ip
@@ -511,12 +517,17 @@
     }
 
     private void updatePortStatistics(DeviceId deviceId) {
-        Collection<PortStatistics> statistics = deviceService.getDevice(deviceId)
-                .as(PortStatisticsDiscovery.class)
-                .discoverPortStatistics();
-        //updating statistcs only if not empty
-        if (!statistics.isEmpty()) {
-            providerService.updatePortStatistics(deviceId, statistics);
+        Device device = deviceService.getDevice(deviceId);
+        if (!Objects.isNull(device) && deviceService.isAvailable(deviceId) &&
+                device.is(PortStatisticsDiscovery.class)) {
+            Collection<PortStatistics> statistics = device.as(PortStatisticsDiscovery.class)
+                    .discoverPortStatistics();
+            //updating statistcs only if not empty
+            if (!statistics.isEmpty()) {
+                providerService.updatePortStatistics(deviceId, statistics);
+            }
+        } else {
+            log.debug("Can't update port statistics for device {}", deviceId);
         }
     }
 
@@ -539,7 +550,7 @@
                 log.debug("{} is not my scheme, skipping", deviceId);
                 return;
             }
-            if (deviceService.getDevice(deviceId) != null || deviceService.isAvailable(deviceId)) {
+            if (deviceService.getDevice(deviceId) != null && deviceService.isAvailable(deviceId)) {
                 log.info("Device {} is already connected to ONOS and is available", deviceId);
                 return;
             }
@@ -628,19 +639,17 @@
         @Override
         public void event(DeviceEvent event) {
             Type type = event.type();
+            DeviceId deviceId = event.subject().id();
             if (type.equals((Type.DEVICE_ADDED))) {
 
                 //For now this is scheduled periodically, when streaming API will
                 // be available we check and base it on the streaming API (e.g. gNMI)
-                if (deviceService.getDevice(event.subject().id()).
-                        is(PortStatisticsDiscovery.class)) {
-                    scheduledTasks.put(event.subject().id(), schedulePolling(event.subject().id(), false));
-                    updatePortStatistics(event.subject().id());
-                }
+                scheduledTasks.put(deviceId, schedulePolling(deviceId, false));
+                updatePortStatistics(deviceId);
 
             } else if (type.equals(Type.DEVICE_REMOVED)) {
                 connectionExecutor.submit(exceptionSafe(() ->
-                        disconnectDevice(event.subject().id())));
+                        disconnectDevice(deviceId)));
             }
         }
 
diff --git a/tools/dev/bin/onos-setup-p4-dev b/tools/dev/bin/onos-setup-p4-dev
index b8483c5..38ba91b 100755
--- a/tools/dev/bin/onos-setup-p4-dev
+++ b/tools/dev/bin/onos-setup-p4-dev
@@ -74,6 +74,8 @@
         tcpdump \
         wget \
         unzip
+
+    sudo pip install setuptools cffi
 }
 
 function do_requirements_1404 {
@@ -139,6 +141,30 @@
     unset LDFLAGS
 }
 
+function checkout_bmv2 {
+    cd ${BUILD_DIR}
+    if [ ! -d bmv2 ]; then
+        git clone https://github.com/p4lang/behavioral-model.git bmv2
+    fi
+    cd bmv2
+    git fetch
+    git checkout ${BMV2_COMMIT}
+}
+
+function do_pi_bmv2_deps {
+    checkout_bmv2
+    # From bmv2's install_deps.sh.
+    # Nanomsg is required also by p4runtime.
+    tmpdir=`mktemp -d -p .`
+    cd ${tmpdir}
+    bash ../travis/install-thrift.sh
+    bash ../travis/install-nanomsg.sh
+    sudo ldconfig
+    bash ../travis/install-nnpy.sh
+    cd ..
+    sudo rm -rf $tmpdir
+}
+
 function do_p4runtime {
     cd ${BUILD_DIR}
     if [ ! -d p4runtime ]; then
@@ -157,23 +183,7 @@
 }
 
 function do_bmv2 {
-    cd ${BUILD_DIR}
-    if [ ! -d bmv2 ]; then
-        git clone https://github.com/p4lang/behavioral-model.git bmv2
-    fi
-    cd bmv2
-    git fetch
-    git checkout ${BMV2_COMMIT}
-
-    # From bmv2's install_deps.sh
-    tmpdir=`mktemp -d -p .`
-    cd ${tmpdir}
-    bash ../travis/install-thrift.sh
-    bash ../travis/install-nanomsg.sh
-    sudo ldconfig
-    bash ../travis/install-nnpy.sh
-    cd ..
-    sudo rm -rf $tmpdir
+    checkout_bmv2
 
     ./autogen.sh
     ./configure --enable-debugger --with-pi 'CXXFLAGS=-O0 -g'
@@ -234,9 +244,10 @@
     commit_id="$1"
     proj_dir="$2"
     func_name="$3"
+    simple_name="$4"
     if ${MUST_DO_ALL} = true || check_commit ${commit_id} ${proj_dir}/.last_built_commit; then
         echo "#"
-        echo "# Building ${proj_dir} (${commit_id})"
+        echo "# Building ${simple_name} (${commit_id})"
         echo "#"
         # Print commands used to install to aid debugging
         set -x
@@ -268,10 +279,11 @@
 mkdir -p ${BUILD_DIR}
 cd ${BUILD_DIR}
 # In dependency order.
-check_and_do ${PROTOBUF_COMMIT} protobuf do_protobuf
-check_and_do ${GRPC_COMMIT} grpc do_grpc
-check_and_do ${PI_COMMIT} p4runtime do_p4runtime
-check_and_do ${BMV2_COMMIT} bmv2 do_bmv2
-check_and_do ${P4C_COMMIT} p4c do_p4c
+check_and_do ${PROTOBUF_COMMIT} protobuf do_protobuf protobuf
+check_and_do ${GRPC_COMMIT} grpc do_grpc grpc
+check_and_do ${BMV2_COMMIT} bmv2 do_pi_bmv2_deps bmv2-deps
+check_and_do ${PI_COMMIT} p4runtime do_p4runtime p4runtime
+check_and_do ${BMV2_COMMIT} bmv2 do_bmv2 bmv2
+check_and_do ${P4C_COMMIT} p4c do_p4c p4c
 
 echo "Done!"
diff --git a/tools/gui/gulp-tasks/bundles/bundle-css/index.js b/tools/gui/gulp-tasks/bundles/bundle-css/index.js
index 0c5c8d6..69c4c70 100644
--- a/tools/gui/gulp-tasks/bundles/bundle-css/index.js
+++ b/tools/gui/gulp-tasks/bundles/bundle-css/index.js
@@ -1,6 +1,7 @@
 import gulp from 'gulp';
 import concat from 'gulp-concat';
 import BundleResources from '../helpers/bundleResources';
+import { reload } from '../../dev-server';
 
 const GUI_BASE = '../../web/gui/src/main/webapp/';
 const bundleFiles = [
@@ -11,10 +12,18 @@
     'app/view/**/*.css',
 ];
 
-const task = gulp.task('bundle-css', function () {
-    return gulp.src(BundleResources(GUI_BASE, bundleFiles))
-        .pipe(concat('onos.css'))
-        .pipe(gulp.dest(GUI_BASE + '/dist/'));
-});
+const task = () => {
 
-export default task;
\ No newline at end of file
+    gulp.task('bundle-css', function () {
+        return gulp.src(BundleResources(GUI_BASE, bundleFiles))
+            .pipe(concat('onos.css'))
+            .pipe(gulp.dest(GUI_BASE + '/dist/'))
+            .on('end', () => { reload(); });
+    });
+
+    gulp.task('watch-css', () => {
+        gulp.watch([GUI_BASE + 'app/**/*.css'], ['bundle-css']);
+    });
+}
+
+export default task();
\ No newline at end of file
diff --git a/tools/gui/gulp-tasks/dev-server/index.js b/tools/gui/gulp-tasks/dev-server/index.js
index 119c3f2..135ee9f 100644
--- a/tools/gui/gulp-tasks/dev-server/index.js
+++ b/tools/gui/gulp-tasks/dev-server/index.js
@@ -42,6 +42,7 @@
             ws: true,
             middleware: [
                 proxy(['**/*.js', '!/onos/ui/onos.js'], { target: 'http://localhost:8189' }),
+                proxy(['**/*.css'], { target: 'http://localhost:8189' }),
                 proxy('**/*.js.map', {
                     target: 'http://localhost:8189',
                     changeOrigin: true,
diff --git a/tools/gui/gulpfile.babel.js b/tools/gui/gulpfile.babel.js
index 24327e0..51aead1 100644
--- a/tools/gui/gulpfile.babel.js
+++ b/tools/gui/gulpfile.babel.js
@@ -3,4 +3,4 @@
 
 gulp.task('build', ['bower', 'bundle-css', 'bundle-js']);
 gulp.task('tests', ['bower', 'test']);
-gulp.task('default', ['bundle-js', 'serve', 'watch-js']);
\ No newline at end of file
+gulp.task('default', ['bundle-js', 'bundle-css', 'serve', 'watch-js', 'watch-css']);
\ No newline at end of file
diff --git a/tools/test/p4src/p4-16/include/defines.p4 b/tools/test/p4src/p4-16/include/defines.p4
index ea6747a..c8640a8 100644
--- a/tools/test/p4src/p4-16/include/defines.p4
+++ b/tools/test/p4src/p4-16/include/defines.p4
@@ -17,7 +17,7 @@
 #ifndef DEFINES
 #define DEFINES
 
-#define MAX_PORTS 254
+#define MAX_PORTS 255
 
 #define ETH_TYPE_IPV4 16w0x800
 #define IP_TYPE_TCP 8w6
diff --git a/tools/test/p4src/p4-16/include/port_counters.p4 b/tools/test/p4src/p4-16/include/port_counters.p4
index 15ed02f..d8667a3 100644
--- a/tools/test/p4src/p4-16/include/port_counters.p4
+++ b/tools/test/p4src/p4-16/include/port_counters.p4
@@ -24,9 +24,11 @@
 
     apply {
         if (standard_metadata.egress_spec < MAX_PORTS) {
-            ingress_port_counter.count((bit<32>)standard_metadata.ingress_port);
             egress_port_counter.count((bit<32>)standard_metadata.egress_spec);
         }
+        if (standard_metadata.ingress_port < MAX_PORTS) {
+            ingress_port_counter.count((bit<32>)standard_metadata.ingress_port);
+        }
     }
 }
 #endif
diff --git a/tools/test/p4src/p4-16/p4c-out/default.json b/tools/test/p4src/p4-16/p4c-out/default.json
index 2dd5c49..241cbe1 100644
--- a/tools/test/p4src/p4-16/p4c-out/default.json
+++ b/tools/test/p4src/p4-16/p4c-out/default.json
@@ -447,7 +447,7 @@
         "column" : 38,
         "source_fragment" : "egress_port_counter"
       },
-      "size" : 254,
+      "size" : 255,
       "is_direct" : false
     },
     {
@@ -459,7 +459,7 @@
         "column" : 38,
         "source_fragment" : "ingress_port_counter"
       },
-      "size" : 254,
+      "size" : 255,
       "is_direct" : false
     }
   ],
@@ -3248,51 +3248,6 @@
                   "op" : "&",
                   "left" : {
                     "type" : "field",
-                    "value" : ["standard_metadata", "ingress_port"]
-                  },
-                  "right" : {
-                    "type" : "hexstr",
-                    "value" : "0xffffffff"
-                  }
-                }
-              }
-            }
-          ]
-        },
-        {
-          "op" : "count",
-          "parameters" : [
-            {
-              "type" : "counter_array",
-              "value" : "port_counters_control.ingress_port_counter"
-            },
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/port_counters.p4",
-            "line" : 27,
-            "column" : 12,
-            "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp_0"]
-            },
-            {
-              "type" : "expression",
-              "value" : {
-                "type" : "expression",
-                "value" : {
-                  "op" : "&",
-                  "left" : {
-                    "type" : "field",
                     "value" : ["standard_metadata", "egress_spec"]
                   },
                   "right" : {
@@ -3313,12 +3268,12 @@
             },
             {
               "type" : "field",
-              "value" : ["scalars", "tmp_0"]
+              "value" : ["scalars", "tmp"]
             }
           ],
           "source_info" : {
             "filename" : "include/port_counters.p4",
-            "line" : 28,
+            "line" : 27,
             "column" : 12,
             "source_fragment" : "egress_port_counter.count((bit<32>)standard_metadata.egress_spec)"
           }
@@ -3331,6 +3286,58 @@
       "runtime_data" : [],
       "primitives" : [
         {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp_0"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "&",
+                  "left" : {
+                    "type" : "field",
+                    "value" : ["standard_metadata", "ingress_port"]
+                  },
+                  "right" : {
+                    "type" : "hexstr",
+                    "value" : "0xffffffff"
+                  }
+                }
+              }
+            }
+          ]
+        },
+        {
+          "op" : "count",
+          "parameters" : [
+            {
+              "type" : "counter_array",
+              "value" : "port_counters_control.ingress_port_counter"
+            },
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp_0"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/port_counters.p4",
+            "line" : 30,
+            "column" : 12,
+            "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "act_2",
+      "id" : 9,
+      "runtime_data" : [],
+      "primitives" : [
+        {
           "op" : "add_header",
           "parameters" : [
             {
@@ -3367,8 +3374,8 @@
       ]
     },
     {
-      "name" : "act_2",
-      "id" : 9,
+      "name" : "act_3",
+      "id" : 10,
       "runtime_data" : [],
       "primitives" : [
         {
@@ -3518,9 +3525,9 @@
           "direct_meters" : null,
           "action_ids" : [7],
           "actions" : ["act_0"],
-          "base_default_next" : null,
+          "base_default_next" : "node_9",
           "next_tables" : {
-            "act_0" : null
+            "act_0" : "node_9"
           },
           "default_entry" : {
             "action_id" : 7,
@@ -3528,6 +3535,29 @@
             "action_data" : [],
             "action_entry_const" : true
           }
+        },
+        {
+          "name" : "tbl_act_1",
+          "id" : 4,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [8],
+          "actions" : ["act_1"],
+          "base_default_next" : null,
+          "next_tables" : {
+            "act_1" : null
+          },
+          "default_entry" : {
+            "action_id" : 8,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
         }
       ],
       "action_profiles" : [
@@ -3630,7 +3660,7 @@
             "filename" : "include/port_counters.p4",
             "line" : 26,
             "column" : 12,
-            "source_fragment" : "standard_metadata.egress_spec < 254"
+            "source_fragment" : "standard_metadata.egress_spec < 255"
           },
           "expression" : {
             "type" : "expression",
@@ -3642,12 +3672,38 @@
               },
               "right" : {
                 "type" : "hexstr",
-                "value" : "0x00fe"
+                "value" : "0x00ff"
+              }
+            }
+          },
+          "true_next" : "tbl_act_0",
+          "false_next" : "node_9"
+        },
+        {
+          "name" : "node_9",
+          "id" : 3,
+          "source_info" : {
+            "filename" : "include/port_counters.p4",
+            "line" : 29,
+            "column" : 12,
+            "source_fragment" : "standard_metadata.ingress_port < 255"
+          },
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "<",
+              "left" : {
+                "type" : "field",
+                "value" : ["standard_metadata", "ingress_port"]
+              },
+              "right" : {
+                "type" : "hexstr",
+                "value" : "0x00ff"
               }
             }
           },
           "false_next" : null,
-          "true_next" : "tbl_act_0"
+          "true_next" : "tbl_act_1"
         }
       ]
     },
@@ -3660,32 +3716,9 @@
         "column" : 8,
         "source_fragment" : "egress"
       },
-      "init_table" : "tbl_act_1",
+      "init_table" : "tbl_act_2",
       "tables" : [
         {
-          "name" : "tbl_act_1",
-          "id" : 4,
-          "key" : [],
-          "match_type" : "exact",
-          "type" : "simple",
-          "max_size" : 1024,
-          "with_counters" : false,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [9],
-          "actions" : ["act_2"],
-          "base_default_next" : "node_12",
-          "next_tables" : {
-            "act_2" : "node_12"
-          },
-          "default_entry" : {
-            "action_id" : 9,
-            "action_const" : true,
-            "action_data" : [],
-            "action_entry_const" : true
-          }
-        },
-        {
           "name" : "tbl_act_2",
           "id" : 5,
           "key" : [],
@@ -3695,14 +3728,37 @@
           "with_counters" : false,
           "support_timeout" : false,
           "direct_meters" : null,
-          "action_ids" : [8],
-          "actions" : ["act_1"],
-          "base_default_next" : null,
+          "action_ids" : [10],
+          "actions" : ["act_3"],
+          "base_default_next" : "node_14",
           "next_tables" : {
-            "act_1" : null
+            "act_3" : "node_14"
           },
           "default_entry" : {
-            "action_id" : 8,
+            "action_id" : 10,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        },
+        {
+          "name" : "tbl_act_3",
+          "id" : 6,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [9],
+          "actions" : ["act_2"],
+          "base_default_next" : null,
+          "next_tables" : {
+            "act_2" : null
+          },
+          "default_entry" : {
+            "action_id" : 9,
             "action_const" : true,
             "action_data" : [],
             "action_entry_const" : true
@@ -3712,8 +3768,8 @@
       "action_profiles" : [],
       "conditionals" : [
         {
-          "name" : "node_12",
-          "id" : 3,
+          "name" : "node_14",
+          "id" : 4,
           "source_info" : {
             "filename" : "include/packet_io.p4",
             "line" : 31,
@@ -3735,7 +3791,7 @@
             }
           },
           "false_next" : null,
-          "true_next" : "tbl_act_2"
+          "true_next" : "tbl_act_3"
         }
       ]
     }
diff --git a/tools/test/p4src/p4-16/p4c-out/default.p4info b/tools/test/p4src/p4-16/p4c-out/default.p4info
index 6bf4a27..d08d40a 100644
--- a/tools/test/p4src/p4-16/p4c-out/default.p4info
+++ b/tools/test/p4src/p4-16/p4c-out/default.p4info
@@ -130,7 +130,7 @@
   spec {
     unit: PACKETS
   }
-  size: 254
+  size: 255
 }
 counters {
   preamble {
@@ -141,7 +141,7 @@
   spec {
     unit: PACKETS
   }
-  size: 254
+  size: 255
 }
 direct_counters {
   preamble {
diff --git a/tools/test/p4src/p4-16/p4c-out/ecmp.json b/tools/test/p4src/p4-16/p4c-out/ecmp.json
index 506db53..2a23882 100644
--- a/tools/test/p4src/p4-16/p4c-out/ecmp.json
+++ b/tools/test/p4src/p4-16/p4c-out/ecmp.json
@@ -448,7 +448,7 @@
         "column" : 38,
         "source_fragment" : "egress_port_counter"
       },
-      "size" : 254,
+      "size" : 255,
       "is_direct" : false
     },
     {
@@ -460,7 +460,7 @@
         "column" : 38,
         "source_fragment" : "ingress_port_counter"
       },
-      "size" : 254,
+      "size" : 255,
       "is_direct" : false
     }
   ],
@@ -3312,51 +3312,6 @@
                   "op" : "&",
                   "left" : {
                     "type" : "field",
-                    "value" : ["standard_metadata", "ingress_port"]
-                  },
-                  "right" : {
-                    "type" : "hexstr",
-                    "value" : "0xffffffff"
-                  }
-                }
-              }
-            }
-          ]
-        },
-        {
-          "op" : "count",
-          "parameters" : [
-            {
-              "type" : "counter_array",
-              "value" : "port_counters_control.ingress_port_counter"
-            },
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/port_counters.p4",
-            "line" : 27,
-            "column" : 12,
-            "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp_0"]
-            },
-            {
-              "type" : "expression",
-              "value" : {
-                "type" : "expression",
-                "value" : {
-                  "op" : "&",
-                  "left" : {
-                    "type" : "field",
                     "value" : ["standard_metadata", "egress_spec"]
                   },
                   "right" : {
@@ -3377,12 +3332,12 @@
             },
             {
               "type" : "field",
-              "value" : ["scalars", "tmp_0"]
+              "value" : ["scalars", "tmp"]
             }
           ],
           "source_info" : {
             "filename" : "include/port_counters.p4",
-            "line" : 28,
+            "line" : 27,
             "column" : 12,
             "source_fragment" : "egress_port_counter.count((bit<32>)standard_metadata.egress_spec)"
           }
@@ -3395,6 +3350,58 @@
       "runtime_data" : [],
       "primitives" : [
         {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp_0"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "&",
+                  "left" : {
+                    "type" : "field",
+                    "value" : ["standard_metadata", "ingress_port"]
+                  },
+                  "right" : {
+                    "type" : "hexstr",
+                    "value" : "0xffffffff"
+                  }
+                }
+              }
+            }
+          ]
+        },
+        {
+          "op" : "count",
+          "parameters" : [
+            {
+              "type" : "counter_array",
+              "value" : "port_counters_control.ingress_port_counter"
+            },
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp_0"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/port_counters.p4",
+            "line" : 30,
+            "column" : 12,
+            "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "act_2",
+      "id" : 9,
+      "runtime_data" : [],
+      "primitives" : [
+        {
           "op" : "add_header",
           "parameters" : [
             {
@@ -3431,8 +3438,8 @@
       ]
     },
     {
-      "name" : "act_2",
-      "id" : 9,
+      "name" : "act_3",
+      "id" : 10,
       "runtime_data" : [],
       "primitives" : [
         {
@@ -3592,9 +3599,9 @@
           "direct_meters" : null,
           "action_ids" : [7],
           "actions" : ["act_0"],
-          "base_default_next" : null,
+          "base_default_next" : "node_9",
           "next_tables" : {
-            "act_0" : null
+            "act_0" : "node_9"
           },
           "default_entry" : {
             "action_id" : 7,
@@ -3602,6 +3609,29 @@
             "action_data" : [],
             "action_entry_const" : true
           }
+        },
+        {
+          "name" : "tbl_act_1",
+          "id" : 4,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [8],
+          "actions" : ["act_1"],
+          "base_default_next" : null,
+          "next_tables" : {
+            "act_1" : null
+          },
+          "default_entry" : {
+            "action_id" : 8,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
         }
       ],
       "action_profiles" : [],
@@ -3665,7 +3695,7 @@
             "filename" : "include/port_counters.p4",
             "line" : 26,
             "column" : 12,
-            "source_fragment" : "standard_metadata.egress_spec < 254"
+            "source_fragment" : "standard_metadata.egress_spec < 255"
           },
           "expression" : {
             "type" : "expression",
@@ -3677,12 +3707,38 @@
               },
               "right" : {
                 "type" : "hexstr",
-                "value" : "0x00fe"
+                "value" : "0x00ff"
+              }
+            }
+          },
+          "true_next" : "tbl_act_0",
+          "false_next" : "node_9"
+        },
+        {
+          "name" : "node_9",
+          "id" : 3,
+          "source_info" : {
+            "filename" : "include/port_counters.p4",
+            "line" : 29,
+            "column" : 12,
+            "source_fragment" : "standard_metadata.ingress_port < 255"
+          },
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "<",
+              "left" : {
+                "type" : "field",
+                "value" : ["standard_metadata", "ingress_port"]
+              },
+              "right" : {
+                "type" : "hexstr",
+                "value" : "0x00ff"
               }
             }
           },
           "false_next" : null,
-          "true_next" : "tbl_act_0"
+          "true_next" : "tbl_act_1"
         }
       ]
     },
@@ -3695,32 +3751,9 @@
         "column" : 8,
         "source_fragment" : "egress"
       },
-      "init_table" : "tbl_act_1",
+      "init_table" : "tbl_act_2",
       "tables" : [
         {
-          "name" : "tbl_act_1",
-          "id" : 4,
-          "key" : [],
-          "match_type" : "exact",
-          "type" : "simple",
-          "max_size" : 1024,
-          "with_counters" : false,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [9],
-          "actions" : ["act_2"],
-          "base_default_next" : "node_12",
-          "next_tables" : {
-            "act_2" : "node_12"
-          },
-          "default_entry" : {
-            "action_id" : 9,
-            "action_const" : true,
-            "action_data" : [],
-            "action_entry_const" : true
-          }
-        },
-        {
           "name" : "tbl_act_2",
           "id" : 5,
           "key" : [],
@@ -3730,14 +3763,37 @@
           "with_counters" : false,
           "support_timeout" : false,
           "direct_meters" : null,
-          "action_ids" : [8],
-          "actions" : ["act_1"],
-          "base_default_next" : null,
+          "action_ids" : [10],
+          "actions" : ["act_3"],
+          "base_default_next" : "node_14",
           "next_tables" : {
-            "act_1" : null
+            "act_3" : "node_14"
           },
           "default_entry" : {
-            "action_id" : 8,
+            "action_id" : 10,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        },
+        {
+          "name" : "tbl_act_3",
+          "id" : 6,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [9],
+          "actions" : ["act_2"],
+          "base_default_next" : null,
+          "next_tables" : {
+            "act_2" : null
+          },
+          "default_entry" : {
+            "action_id" : 9,
             "action_const" : true,
             "action_data" : [],
             "action_entry_const" : true
@@ -3747,8 +3803,8 @@
       "action_profiles" : [],
       "conditionals" : [
         {
-          "name" : "node_12",
-          "id" : 3,
+          "name" : "node_14",
+          "id" : 4,
           "source_info" : {
             "filename" : "include/packet_io.p4",
             "line" : 31,
@@ -3770,7 +3826,7 @@
             }
           },
           "false_next" : null,
-          "true_next" : "tbl_act_2"
+          "true_next" : "tbl_act_3"
         }
       ]
     }
diff --git a/tools/test/p4src/p4-16/p4c-out/ecmp.p4info b/tools/test/p4src/p4-16/p4c-out/ecmp.p4info
index 29fd5c0..2355b90 100644
--- a/tools/test/p4src/p4-16/p4c-out/ecmp.p4info
+++ b/tools/test/p4src/p4-16/p4c-out/ecmp.p4info
@@ -126,7 +126,7 @@
   spec {
     unit: PACKETS
   }
-  size: 254
+  size: 255
 }
 counters {
   preamble {
@@ -137,7 +137,7 @@
   spec {
     unit: PACKETS
   }
-  size: 254
+  size: 255
 }
 direct_counters {
   preamble {
diff --git a/tools/test/p4src/p4-16/p4c-out/wcmp.json b/tools/test/p4src/p4-16/p4c-out/wcmp.json
index dc50c96..3367017 100644
--- a/tools/test/p4src/p4-16/p4c-out/wcmp.json
+++ b/tools/test/p4src/p4-16/p4c-out/wcmp.json
@@ -393,7 +393,7 @@
           "parser_ops" : [],
           "transitions" : [
             {
-              "value" : "0xff",
+              "value" : "0x00ff",
               "mask" : null,
               "next_state" : "parse_packet_out"
             },
@@ -449,7 +449,7 @@
         "column" : 38,
         "source_fragment" : "egress_port_counter"
       },
-      "size" : 254,
+      "size" : 255,
       "is_direct" : false
     },
     {
@@ -461,7 +461,7 @@
         "column" : 38,
         "source_fragment" : "ingress_port_counter"
       },
-      "size" : 254,
+      "size" : 255,
       "is_direct" : false
     }
   ],
@@ -3422,51 +3422,6 @@
                   "op" : "&",
                   "left" : {
                     "type" : "field",
-                    "value" : ["standard_metadata", "ingress_port"]
-                  },
-                  "right" : {
-                    "type" : "hexstr",
-                    "value" : "0xffffffff"
-                  }
-                }
-              }
-            }
-          ]
-        },
-        {
-          "op" : "count",
-          "parameters" : [
-            {
-              "type" : "counter_array",
-              "value" : "port_counters_control.ingress_port_counter"
-            },
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/port_counters.p4",
-            "line" : 27,
-            "column" : 12,
-            "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp_0"]
-            },
-            {
-              "type" : "expression",
-              "value" : {
-                "type" : "expression",
-                "value" : {
-                  "op" : "&",
-                  "left" : {
-                    "type" : "field",
                     "value" : ["standard_metadata", "egress_spec"]
                   },
                   "right" : {
@@ -3487,12 +3442,12 @@
             },
             {
               "type" : "field",
-              "value" : ["scalars", "tmp_0"]
+              "value" : ["scalars", "tmp"]
             }
           ],
           "source_info" : {
             "filename" : "include/port_counters.p4",
-            "line" : 28,
+            "line" : 27,
             "column" : 12,
             "source_fragment" : "egress_port_counter.count((bit<32>)standard_metadata.egress_spec)"
           }
@@ -3505,6 +3460,58 @@
       "runtime_data" : [],
       "primitives" : [
         {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp_0"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "&",
+                  "left" : {
+                    "type" : "field",
+                    "value" : ["standard_metadata", "ingress_port"]
+                  },
+                  "right" : {
+                    "type" : "hexstr",
+                    "value" : "0xffffffff"
+                  }
+                }
+              }
+            }
+          ]
+        },
+        {
+          "op" : "count",
+          "parameters" : [
+            {
+              "type" : "counter_array",
+              "value" : "port_counters_control.ingress_port_counter"
+            },
+            {
+              "type" : "field",
+              "value" : ["scalars", "tmp_0"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "include/port_counters.p4",
+            "line" : 30,
+            "column" : 12,
+            "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "act_2",
+      "id" : 10,
+      "runtime_data" : [],
+      "primitives" : [
+        {
           "op" : "add_header",
           "parameters" : [
             {
@@ -3541,8 +3548,8 @@
       ]
     },
     {
-      "name" : "act_2",
-      "id" : 10,
+      "name" : "act_3",
+      "id" : 11,
       "runtime_data" : [],
       "primitives" : [
         {
@@ -3725,9 +3732,9 @@
           "direct_meters" : null,
           "action_ids" : [8],
           "actions" : ["act_0"],
-          "base_default_next" : null,
+          "base_default_next" : "node_10",
           "next_tables" : {
-            "act_0" : null
+            "act_0" : "node_10"
           },
           "default_entry" : {
             "action_id" : 8,
@@ -3735,6 +3742,29 @@
             "action_data" : [],
             "action_entry_const" : true
           }
+        },
+        {
+          "name" : "tbl_act_1",
+          "id" : 5,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [9],
+          "actions" : ["act_1"],
+          "base_default_next" : null,
+          "next_tables" : {
+            "act_1" : null
+          },
+          "default_entry" : {
+            "action_id" : 9,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
         }
       ],
       "action_profiles" : [],
@@ -3798,7 +3828,7 @@
             "filename" : "include/port_counters.p4",
             "line" : 26,
             "column" : 12,
-            "source_fragment" : "standard_metadata.egress_spec < 254"
+            "source_fragment" : "standard_metadata.egress_spec < 255"
           },
           "expression" : {
             "type" : "expression",
@@ -3810,12 +3840,38 @@
               },
               "right" : {
                 "type" : "hexstr",
-                "value" : "0x00fe"
+                "value" : "0x00ff"
+              }
+            }
+          },
+          "true_next" : "tbl_act_0",
+          "false_next" : "node_10"
+        },
+        {
+          "name" : "node_10",
+          "id" : 3,
+          "source_info" : {
+            "filename" : "include/port_counters.p4",
+            "line" : 29,
+            "column" : 12,
+            "source_fragment" : "standard_metadata.ingress_port < 255"
+          },
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "<",
+              "left" : {
+                "type" : "field",
+                "value" : ["standard_metadata", "ingress_port"]
+              },
+              "right" : {
+                "type" : "hexstr",
+                "value" : "0x00ff"
               }
             }
           },
           "false_next" : null,
-          "true_next" : "tbl_act_0"
+          "true_next" : "tbl_act_1"
         }
       ]
     },
@@ -3828,32 +3884,9 @@
         "column" : 8,
         "source_fragment" : "egress"
       },
-      "init_table" : "tbl_act_1",
+      "init_table" : "tbl_act_2",
       "tables" : [
         {
-          "name" : "tbl_act_1",
-          "id" : 5,
-          "key" : [],
-          "match_type" : "exact",
-          "type" : "simple",
-          "max_size" : 1024,
-          "with_counters" : false,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [10],
-          "actions" : ["act_2"],
-          "base_default_next" : "node_13",
-          "next_tables" : {
-            "act_2" : "node_13"
-          },
-          "default_entry" : {
-            "action_id" : 10,
-            "action_const" : true,
-            "action_data" : [],
-            "action_entry_const" : true
-          }
-        },
-        {
           "name" : "tbl_act_2",
           "id" : 6,
           "key" : [],
@@ -3863,14 +3896,37 @@
           "with_counters" : false,
           "support_timeout" : false,
           "direct_meters" : null,
-          "action_ids" : [9],
-          "actions" : ["act_1"],
-          "base_default_next" : null,
+          "action_ids" : [11],
+          "actions" : ["act_3"],
+          "base_default_next" : "node_15",
           "next_tables" : {
-            "act_1" : null
+            "act_3" : "node_15"
           },
           "default_entry" : {
-            "action_id" : 9,
+            "action_id" : 11,
+            "action_const" : true,
+            "action_data" : [],
+            "action_entry_const" : true
+          }
+        },
+        {
+          "name" : "tbl_act_3",
+          "id" : 7,
+          "key" : [],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [10],
+          "actions" : ["act_2"],
+          "base_default_next" : null,
+          "next_tables" : {
+            "act_2" : null
+          },
+          "default_entry" : {
+            "action_id" : 10,
             "action_const" : true,
             "action_data" : [],
             "action_entry_const" : true
@@ -3880,8 +3936,8 @@
       "action_profiles" : [],
       "conditionals" : [
         {
-          "name" : "node_13",
-          "id" : 3,
+          "name" : "node_15",
+          "id" : 4,
           "source_info" : {
             "filename" : "include/packet_io.p4",
             "line" : 31,
@@ -3903,7 +3959,7 @@
             }
           },
           "false_next" : null,
-          "true_next" : "tbl_act_2"
+          "true_next" : "tbl_act_3"
         }
       ]
     }
diff --git a/tools/test/p4src/p4-16/p4c-out/wcmp.p4info b/tools/test/p4src/p4-16/p4c-out/wcmp.p4info
index 41aabf4..b6c3b50 100644
--- a/tools/test/p4src/p4-16/p4c-out/wcmp.p4info
+++ b/tools/test/p4src/p4-16/p4c-out/wcmp.p4info
@@ -133,7 +133,7 @@
   spec {
     unit: PACKETS
   }
-  size: 254
+  size: 255
 }
 counters {
   preamble {
@@ -144,7 +144,7 @@
   spec {
     unit: PACKETS
   }
-  size: 254
+  size: 255
 }
 direct_counters {
   preamble {
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
index 6e49dba..71c7d5a 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
@@ -37,6 +37,7 @@
 import org.onosproject.ui.table.CellFormatter;
 import org.onosproject.ui.table.TableModel;
 import org.onosproject.ui.table.TableRequestHandler;
+import org.onosproject.ui.table.cell.DefaultCellFormatter;
 import org.onosproject.ui.table.cell.EnumFormatter;
 import org.onosproject.ui.table.cell.HexLongFormatter;
 import org.onosproject.ui.table.cell.NumberFormatter;
@@ -69,7 +70,7 @@
     private static final String APP_ID = "appId";
     private static final String APP_NAME = "appName";
     private static final String GROUP_ID = "groupId";
-    private static final String TABLE_ID = "tableId";
+    private static final String TABLE_NAME = "tableName";
     private static final String PRIORITY = "priority";
     private static final String SELECTOR_C = "selector_c"; // for table column
     private static final String SELECTOR = "selector";
@@ -128,7 +129,7 @@
             PACKETS,
             DURATION,
             PRIORITY,
-            TABLE_ID,
+            TABLE_NAME,
             APP_ID,
             APP_NAME,
 
@@ -206,6 +207,7 @@
             tm.setFormatter(SELECTOR, new SelectorFormatter());
             tm.setFormatter(TREATMENT_C, new TreatmentShortFormatter());
             tm.setFormatter(TREATMENT, new TreatmentFormatter());
+            tm.setFormatter(TABLE_NAME, DefaultCellFormatter.INSTANCE);
             return tm;
         }
 
@@ -231,7 +233,7 @@
                     .cell(PACKETS, flow.packets())
                     .cell(DURATION, flow.life())
                     .cell(PRIORITY, flow.priority())
-                    .cell(TABLE_ID, flow.tableId())
+                    .cell(TABLE_NAME, flow.table())
                     .cell(APP_ID, flow.appId())
                     .cell(APP_NAME, makeAppName(flow.appId(), lookup))
 
@@ -407,7 +409,7 @@
                 data.put(DURATION, NumberFormatter.INTEGER.format(flow.life()));
 
                 data.put(FLOW_PRIORITY, flow.priority());
-                data.put(TABLE_ID, flow.tableId());
+                data.put(TABLE_NAME, flow.table().toString());
                 data.put(APP_ID, flow.appId());
                 // NOTE: horribly inefficient... make a map and retrieve a single value...
                 data.put(APP_NAME, makeAppName(flow.appId(), appShortMap()));
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/PipeconfViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/PipeconfViewMessageHandler.java
new file mode 100644
index 0000000..9ecbeca
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/PipeconfViewMessageHandler.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.pi.model.PiActionModel;
+import org.onosproject.net.pi.model.PiHeaderFieldModel;
+import org.onosproject.net.pi.model.PiHeaderModel;
+import org.onosproject.net.pi.model.PiHeaderTypeModel;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.model.PiTableMatchFieldModel;
+import org.onosproject.net.pi.model.PiTableModel;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
+import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+public class PipeconfViewMessageHandler extends UiMessageHandler {
+    private static final Logger log =
+            LoggerFactory.getLogger(PipeconfViewMessageHandler.class);
+    private static final String PIPECONF_REQUEST = "pipeconfRequest";
+    private static final String PIPECONF_RESP = "pipeConfResponse";
+    private static final String DEVICE_ID = "devId";
+    private static final String PIPECONF = "pipeconf";
+    private static final String PIPELINE_MODEL = "pipelineModel";
+    private static final String NO_PIPECONF_RESP = "noPipeconfResp";
+
+    @Override
+    protected Collection<RequestHandler> createRequestHandlers() {
+        return ImmutableSet.of(new PipeconfRequestHandler());
+    }
+
+    private class PipeconfRequestHandler extends RequestHandler {
+
+        public PipeconfRequestHandler() {
+            super(PIPECONF_REQUEST);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            PiPipeconfService piPipeconfService = get(PiPipeconfService.class);
+            DeviceService deviceService = get(DeviceService.class);
+            ObjectNode responseData = objectNode();
+            String devId = string(payload, DEVICE_ID);
+            if (devId == null || devId.isEmpty()) {
+                log.warn("{}: Invalid device id", PIPECONF_REQUEST);
+                sendMessage(NO_PIPECONF_RESP, null);
+                return;
+            }
+            DeviceId deviceId = DeviceId.deviceId(devId);
+            Optional<PiPipeconfId> pipeconfId = piPipeconfService.ofDevice(deviceId);
+            if (!pipeconfId.isPresent()) {
+                log.warn("{}: Can't find pipeconf id for device {}", PIPECONF_REQUEST, deviceId);
+                sendMessage(NO_PIPECONF_RESP, null);
+                return;
+            }
+
+            Optional<PiPipeconf> pipeconf = piPipeconfService.getPipeconf(pipeconfId.get());
+            if (!pipeconf.isPresent()) {
+                log.warn("{}: Can't find pipeconf {}", PIPECONF_REQUEST, pipeconfId);
+                sendMessage(NO_PIPECONF_RESP, null);
+                return;
+            }
+            CodecContext codecContext = getJsonCodecContext();
+
+            ObjectNode pipeconfData = codecContext.encode(pipeconf.get(), PiPipeconf.class);
+            responseData.set(PIPECONF, pipeconfData);
+
+            // Filtered out models not exists in interpreter
+            // usually they generated by compiler automatically
+            Device device = deviceService.getDevice(deviceId);
+            if (device == null || !deviceService.isAvailable(deviceId)) {
+                log.warn("{}: Device {} is not available", PIPECONF_REQUEST, deviceId);
+                sendMessage(NO_PIPECONF_RESP, null);
+                return;
+            }
+            PiPipelineInterpreter interpreter = device.as(PiPipelineInterpreter.class);
+            PiPipelineModel pipelineModel =
+                    filteredOutAdditionalData(pipeconf.get().pipelineModel(), interpreter);
+
+            ObjectNode pipelineModelData =
+                    codecContext.encode(pipelineModel, PiPipelineModel.class);
+            responseData.set(PIPELINE_MODEL, pipelineModelData);
+
+            sendMessage(PIPECONF_RESP, responseData);
+        }
+    }
+
+    private PiPipelineModel filteredOutAdditionalData(PiPipelineModel piPipelineModel,
+                                                      PiPipelineInterpreter interpreter) {
+        if (interpreter == null) {
+            // Do nothing if there is no interpreter
+            return piPipelineModel;
+        }
+        // filter out actions, headers and tables if not exists in interpreter
+        Set<PiHeaderTypeModel> newHeaderTypesModels = Sets.newHashSet();
+        Set<PiHeaderModel> newHeaderModels = Sets.newHashSet();
+        Set<PiActionModel> newActionModels = Sets.newHashSet();
+        Set<PiTableModel> newTableModels = Sets.newHashSet();
+
+        piPipelineModel.tables().forEach(table -> {
+            String tableName = table.name();
+            PiTableId tableId = PiTableId.of(tableName);
+
+            if (interpreter.mapPiTableId(tableId).isPresent()) {
+                newTableModels.add(table);
+
+                newActionModels.addAll(table.actions());
+                table.matchFields().stream()
+                        .map(PiTableMatchFieldModel::field)
+                        .map(PiHeaderFieldModel::header)
+                        .forEach(header -> {
+                            newHeaderModels.add(header);
+                            newHeaderTypesModels.add(header.type());
+                        });
+
+            }
+        });
+
+        return new FilteredPipelineModel(newHeaderTypesModels,
+                                         newHeaderModels,
+                                         newActionModels,
+                                         newTableModels);
+    }
+
+    /**
+     * Pipeline model for UI message.
+     * FIXME: Is it necessary to create this class?
+     */
+    private class FilteredPipelineModel implements PiPipelineModel {
+
+        private Set<PiHeaderTypeModel> headerTypesModels;
+        private Set<PiHeaderModel> headerModels;
+        private Set<PiActionModel> actionModels;
+        private Set<PiTableModel> tableModels;
+
+        public FilteredPipelineModel(Set<PiHeaderTypeModel> headerTypesModels,
+                                     Set<PiHeaderModel> headerModels,
+                                     Set<PiActionModel> actionModels,
+                                     Set<PiTableModel> tableModels) {
+            this.headerTypesModels = headerTypesModels;
+            this.headerModels = headerModels;
+            this.actionModels = actionModels;
+            this.tableModels = tableModels;
+        }
+
+        @Override
+        public Optional<PiHeaderTypeModel> headerType(String name) {
+            return headerTypesModels.stream()
+                    .filter(headerType -> headerType.name().equals(name))
+                    .findFirst();
+        }
+
+        @Override
+        public Collection<PiHeaderTypeModel> headerTypes() {
+            return headerTypesModels;
+        }
+
+        @Override
+        public Optional<PiHeaderModel> header(String name) {
+            return headerModels.stream()
+                    .filter(headerModel -> headerModel.name().equals(name))
+                    .findFirst();
+        }
+
+        @Override
+        public Collection<PiHeaderModel> headers() {
+            return headerModels;
+        }
+
+        @Override
+        public Optional<PiActionModel> action(String name) {
+            return actionModels.stream()
+                    .filter(actionModel -> actionModel.name().equals(name))
+                    .findFirst();
+        }
+
+        @Override
+        public Collection<PiActionModel> actions() {
+            return actionModels;
+        }
+
+        @Override
+        public Optional<PiTableModel> table(String name) {
+            return tableModels.stream()
+                    .filter(tableModel -> tableModel.name().equals(name))
+                    .findFirst();
+        }
+
+        @Override
+        public Collection<PiTableModel> tables() {
+            return tableModels;
+        }
+    }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
index 121df58..f3afa1a 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
@@ -194,6 +194,7 @@
                 new UiViewHidden("port"),
                 new UiViewHidden("group"),
                 new UiViewHidden("meter"),
+                new UiViewHidden("pipeconf"),
 
                 mkView(NETWORK, "link", "nav_links"),
                 mkView(NETWORK, "host", "nav_hosts"),
@@ -221,7 +222,8 @@
                         new ClusterViewMessageHandler(),
                         new ProcessorViewMessageHandler(),
                         new TunnelViewMessageHandler(),
-                        new PartitionViewMessageHandler()
+                        new PartitionViewMessageHandler(),
+                        new PipeconfViewMessageHandler()
                 );
 
         UiTopoOverlayFactory topoOverlayFactory =
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/fw/Mast_zh_TW.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/fw/Mast_zh_TW.properties
index 923dc04..f3dabae 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/fw/Mast_zh_TW.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/fw/Mast_zh_TW.properties
@@ -19,5 +19,14 @@
 # Tooltip for context-sensitive help button [?]
 tt_help=顯示本頁說明
 
+# unknown user
+unknown_user=(未知使用者)
+
 # Logout button
 logout=登出
+
+# UI components added/removed, etc.
+uicomp_added=新元件已新增
+uicomp_removed=有元件已被移除
+ui_ok_to_update=點擊確定更新介面
+confirm_refresh_title=確認重整介面
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_es.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_es.properties
new file mode 100644
index 0000000..6631bc5
--- /dev/null
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_es.properties
@@ -0,0 +1,143 @@
+#
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+#
+#
+
+# Text that appears in the navigation panel
+nav_item_topo=Topologia
+
+# Message when no devices are connected
+no_devices_are_connected=Ningun dispositivo conectado
+
+# Action Key Toolbar Tooltips ...
+tbtt_tog_instances=Muestra el panel de instancias de ONOS
+tbtt_tog_summary=Muestra el panel de sumario de ONOS
+tbtt_tog_use_detail=Habilita el panel de detalles
+tbtt_tog_host=Activa visibilidad de hosts
+tbtt_tog_offline=Activa la visibilidad offline
+tbtt_tog_porthi=Muestra puertos resaltados
+tbtt_bad_links=Muestra enlaces con problemas 
+tbtt_tog_map=Muestra mapa geográfico en el fondo
+tbtt_sel_map=Selecciona mapa geográfico de fondo
+tbtt_tog_sprite=Activa capa sprite
+tbtt_reset_loc=Inicializa localización de nodos
+tbtt_tog_oblique=Activa la vista oblicua (experimental)
+tbtt_cyc_layers=Capas cíclicas de nodo
+tbtt_cyc_dev_labs=Etiquetas cíclicas de dispositivo
+tbtt_cyc_host_labs=Etiquetas cíclicas de hoste
+tbtt_unpin_node=Unpin node (hover mouse over)
+tbtt_reset_zoom=Reinicia nivel de zoom
+tbtt_tog_toolbar=Muestra la barra de herramientas
+tbtt_eq_master=Equalize mastership roles
+
+# Quick Help Gestures
+qh_gest_click=Selecciona item y muestra detalles
+qh_gest_shift_click=Activa seleccion de estado
+qh_gest_drag=Vuelve a fijar en un punto el dispositivo / host
+qh_gest_cmd_scroll=Zoom in / out
+qh_gest_cmd_drag=Pan
+
+# Flash Messages
+fl_background_map=Mapa de fondo
+fl_sprite_layer=Capa sprite
+fl_pan_zoom_reset=Reinicializa nivel de zoom
+fl_eq_masters=Equalizing master roles
+
+fl_device_labels_hide=Oculta etiquetas de dispositivo
+fl_device_labels_show_friendly=Muestra etiquetas con el nombre del dispositivo 
+fl_device_labels_show_id=Muestra etiquetas con el indicador (device ID)
+fl_host_labels_show_friendly=Muestra etiquetas con el nombre del host 
+fl_host_labels_show_ip=Muestra dirección IP del host
+fl_host_labels_show_mac=Muestra la dirección MAC del host
+fl_offline_devices=Dispositivos fuera de línea
+fl_bad_links=Enlaces con problemas
+fl_reset_node_locations=Reinicia la localización de nodos
+
+fl_layer_all=Muestra todas las capas
+fl_layer_pkt=Muestra la capa de paquetes
+fl_layer_opt=Muestra la capa óptica
+
+fl_panel_instances=Instances Panel de instancias
+fl_panel_summary=Panel de sumario
+fl_panel_details=Panel de detalles
+
+fl_port_highlighting=Puerto resalatado
+
+fl_oblique_view=Vista oblicua
+fl_normal_view=Vista Normal
+
+fl_monitoring_canceled=Monitorización cancelada
+fl_selecting_intent=Seleccionando Intent
+
+# Core Overlays
+ov_tt_protected_intents=Capa de Intents protegidos
+ov_tt_traffic=Capa de tráfico
+ov_tt_none=Sin capas
+
+# Traffic Overlay
+tr_btn_create_h2h_flow=Crear flujo Host-to-Host Flow
+tr_btn_create_msrc_flow=Crear flujo Multi-Source
+tr_btn_show_device_flows=Muestra flujos de dispositivo
+tr_btn_show_related_traffic=Muestra el tráfico relacionado
+tr_btn_cancel_monitoring=Cancela la motitorización de tráfico
+tr_btn_monitor_all=Monitoriza todo el tráfico
+tr_btn_show_dev_link_flows=Muestra flujos en enlace de dispositivo
+tr_btn_show_all_rel_intents=Muestra todos los intents relacionados
+tr_btn_show_prev_rel_intent=Muestra los anteriores intents relacionados
+tr_btn_show_next_rel_intent=Muestra los siguientes intents relacionados
+tr_btn_monitor_sel_intent=Monitoriza el trafico del intent seleccionado
+tr_fl_fstats_bytes=Flow estadísticas (bytes)
+tr_fl_pstats_bits=Port estadísticas (bits / second)
+tr_fl_pstats_pkts=Port estadísticas (packets / second)
+tr_fl_dev_flows=Flujos de dispositivo
+tr_fl_rel_paths=Caminos relacionados
+tr_fl_prev_rel_int=Previos intent relacionados
+tr_fl_next_rel_int=Siguientes intent relacionados
+tr_fl_traf_on_path=Tráfico en el camino seleccionado
+tr_fl_h2h_flow_added=Añadido flujo Host-to-Host
+tr_fl_multisrc_flow=Flujo Multi-Source
+
+# Button tooltips
+btn_show_view_device=Muestra la vista de dispositivo
+btn_show_view_flow=Muestra vista de flujos de este dispositivo
+btn_show_view_port=Muestra la vista de puertos de este dispositivo
+btn_show_view_group=Muestra la vista de grupo para este dispositivo
+btn_show_view_meter=Muestra la vista de contadores para este dispositivo
+
+# Panel Titles
+title_select_map=Mapa seleccionado
+title_panel_summary=Sumario de ONOS
+title_selected_items=Items Seleccionados
+title_edge_link=Enlace de terminación
+title_infra_link=Enlace de infraestructura
+
+# Custom Panel Labels / Values
+lp_label_friendly=Friendly
+
+lp_label_a_type=Tipo A
+lp_label_a_id=A id
+lp_label_a_friendly=A friendly
+lp_label_a_port=Puerto A 
+
+lp_label_b_type=Tipo B
+lp_label_b_id=B id
+lp_label_b_friendly=B friendly
+lp_label_b_port=Puerto B
+
+lp_label_a2b=A to B
+lp_label_b2a=B to A
+
+lp_value_no_link=[no link]
+
diff --git a/web/gui/src/main/webapp/app/fw/layer/details-panel.css b/web/gui/src/main/webapp/app/fw/layer/details-panel.css
new file mode 100644
index 0000000..1c41835
--- /dev/null
+++ b/web/gui/src/main/webapp/app/fw/layer/details-panel.css
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
diff --git a/web/gui/src/main/webapp/app/fw/layer/details-panel.js b/web/gui/src/main/webapp/app/fw/layer/details-panel.js
new file mode 100644
index 0000000..cc72a06
--- /dev/null
+++ b/web/gui/src/main/webapp/app/fw/layer/details-panel.js
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+(function () {
+
+    var ps, fs, mast, wss, is, EditableTextComponent;
+
+    var panel,
+        pStartY,
+        wSize,
+        wssHandlers = {},
+        options;
+
+    // Constants
+    var topPdg = 28,
+        defaultLabelWidth = 110,
+        defaultValueWidth = 80;
+
+    // Elements
+    var container,
+        top,
+        bottom;
+
+    function createDetailsPanel(name, _options) {
+        options = _options;
+        scope = options.scope;
+
+        panel = ps.createPanel(name, options);
+
+        calculatePositions();
+
+        panel.el().style({
+            position: 'absolute',
+            top: pStartY + 'px',
+        });
+
+        hide();
+
+        return panel;
+    }
+
+    function calculatePositions() {
+        pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
+            + mast.mastHeight() + topPdg;
+        wSize = fs.windowSize(pStartY);
+        pHeight = wSize.height;
+    }
+
+    function hide() {
+        panel.hide();
+    }
+
+    function setResponse(name, callback) {
+        var additionalHandler = {};
+        additionalHandler[name] = callback;
+
+        wss.bindHandlers(additionalHandler);
+        wssHandlers = _.extend({}, wssHandlers, additionalHandler);
+    }
+
+    function addContainers() {
+        container = panel.append('div').classed('container', true);
+        top = container.append('div').classed('top', true);
+        bottom = container.append('div').classed('bottom', true);
+    }
+
+    function addCloseButton(onClose) {
+        var closeBtn = top.append('div').classed('close-btn', true);
+
+        is.loadEmbeddedIcon(closeBtn, 'close', 20);
+        closeBtn.on('click', onClose || function () {});
+    }
+
+    function addHeading(icon) {
+        top.append('div').classed('iconDiv ' + icon, true);
+        new EditableTextComponent(top.append('h2'), {
+            scope: options.scope,
+            nameChangeRequest: options.nameChangeRequest,
+            keyBindings: options.keyBindings,
+        });
+    }
+
+    function addTable(parent, className) {
+        return parent.append('div').classed(className, true).append('table');
+    }
+
+    function addProp(tbody, key, value) {
+        console.log(tbody);
+        var tr = tbody.append('tr');
+
+        function addCell(cls, txt, width) {
+            tr.append('td').attr('class', cls).attr('width', width).text(txt);
+        }
+
+        addCell('label', key + ' :', defaultLabelWidth);
+        addCell('value', value, defaultValueWidth);
+    }
+
+    function addPropsList(el, props) {
+        var tblDiv = el.append('div').classed('top-tables', true);
+        var left = addTable(tblDiv, 'left').append('tbody');
+        var right = addTable(tblDiv, 'right').append('tbody');
+
+        var keys = _.keys(props);
+
+        _.each(props, function (value, key) {
+            var index = keys.indexOf(key);
+
+            if (index < keys.length / 2) {
+                addProp(left, key, value);
+            } else {
+                addProp(right, key, value);
+            }
+        });
+    }
+
+    function empty() {
+        panel.empty();
+    }
+
+    function select(id) {
+        return panel.el().select(id);
+    }
+
+    function destroy() {
+        wss.unbindHandlers(wssHandlers);
+    }
+
+    angular.module('onosLayer')
+        .factory('DetailsPanelService', [
+
+            'PanelService', 'FnService', 'MastService', 'WebSocketService',
+            'IconService', 'EditableTextComponent',
+
+            function (_ps_, _fs_, _mast_, _wss_, _is_, _etc_) {
+
+                ps = _ps_;
+                fs = _fs_;
+                mast = _mast_;
+                wss = _wss_;
+                is = _is_;
+                EditableTextComponent = _etc_;
+
+                return {
+                    create: createDetailsPanel,
+                    setResponse: setResponse,
+
+                    addContainers: addContainers,
+                    addCloseButton: addCloseButton,
+                    addHeading: addHeading,
+                    addPropsList: addPropsList,
+
+                    // Elements
+                    container: function () { return container; },
+                    top: function () { return top; },
+                    bottom: function () { return bottom; },
+                    select: select,
+
+                    empty: empty,
+                    hide: hide,
+                    destroy: destroy,
+                };
+            },
+        ]);
+})();
diff --git a/web/gui/src/main/webapp/app/fw/layer/editable-text.js b/web/gui/src/main/webapp/app/fw/layer/editable-text.js
new file mode 100644
index 0000000..09da1ec
--- /dev/null
+++ b/web/gui/src/main/webapp/app/fw/layer/editable-text.js
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+(function () {
+
+    var ks, wss;
+
+    var EditableText = function (el, options) {
+      // constructor
+        this.el = el;
+        this.scope = options.scope;
+        this.options = options;
+
+        this.el.classed('editable clickable', true).on('click', this.onEdit.bind(this));
+        this.editingName = false;
+    };
+
+    EditableText.prototype = {
+
+        bindHandlers: function () {
+            ks.keyBindings({
+                'enter': this.save.bind(this),
+                'esc': [this.cancel.bind(this), 'Close the details panel']
+            });
+        },
+
+        unbindHandlers: function () {
+            ks.unbindKeys();
+
+            if (this.options.keyBindings) {
+                // Reset to original bindings before editable text
+                ks.keyBindings(this.options.keyBindings);
+            }
+        },
+
+        addTextField: function () {
+            return this.el.append('input').classed('name-input', true)
+                .attr('type', 'text')
+                .attr('value', this.scope.panelData.name)[0][0];
+        },
+
+        onEdit: function () {
+            if (!this.editingName) {
+                this.el.classed('editable clickable', false);
+                this.el.text('');
+
+                var el = this.addTextField();
+                el.focus();
+                el.select();
+                this.editingName = true;
+
+                this.bindHandlers();
+
+                ks.enableGlobalKeys(false);
+            }
+        },
+
+        exit: function (name) {
+            this.el.text(name);
+            this.el.classed('editable clickable', true);
+            this.editingName = false;
+            ks.enableGlobalKeys(true);
+            this.unbindHandlers();
+        },
+
+        cancel: function (a, b, ev) {
+
+            if (this.editingName) {
+                this.exit(this.scope.panelData.name);
+                return true;
+            }
+
+            return false;
+        },
+
+        save: function () {
+            var id = this.scope.panelData.id,
+                val,
+                newVal;
+
+            if (this.editingName) {
+                val = this.el.select('input').property('value').trim();
+                newVal = val || id;
+
+                this.exit(newVal);
+                this.scope.panelData.name = newVal;
+                wss.sendEvent(this.options.nameChangeRequest, { id: id, name: val });
+            }
+        },
+    };
+
+
+    angular.module('onosLayer')
+        .factory('EditableTextComponent', [
+
+            'KeyService', 'WebSocketService',
+
+            function (_ks_, _wss_) {
+                ks = _ks_;
+                wss = _wss_;
+
+                return EditableText;
+            },
+        ]);
+
+})();
diff --git a/web/gui/src/main/webapp/app/fw/svg/glyphData.js b/web/gui/src/main/webapp/app/fw/svg/glyphData.js
index 386a74c..9667aeb 100644
--- a/web/gui/src/main/webapp/app/fw/svg/glyphData.js
+++ b/web/gui/src/main/webapp/app/fw/svg/glyphData.js
@@ -355,6 +355,16 @@
             'H39.4c0-7.2,8.4-13.1,15.7-13.1S70.6,72,70.6,79.2H96.3z' +
             'M48,65.6L36.8,53c0-.5-1.5.5-1.4,0.7l5.3,16.6z',
 
+            pipeconfTable: tableFrame +
+            'M10,66h13v3h-13z' +
+            'M23,62.5L28,67.5L23,72.5z' +
+            'M30,55h12.5v25h-12.5z' +
+            'M45,66h13v3h-13z' +
+            'M58,62.5L63,67.5L58,72.5z' +
+            'M65,55h12.5v25h-12.5z' +
+            'M79,66h15v3h-15z' +
+            'M94,62.5L99,67.5L94,72.5z',
+
             // --- Topology toolbar specific glyphs ----------------------
 
             summary: 'M95.8,9.2H14.2c-2.8,0-5,2.2-5,5v81.5c0,2.8,2.2,5,5,' +
diff --git a/web/gui/src/main/webapp/app/fw/svg/icon.js b/web/gui/src/main/webapp/app/fw/svg/icon.js
index 9027960..f2a6b66 100644
--- a/web/gui/src/main/webapp/app/fw/svg/icon.js
+++ b/web/gui/src/main/webapp/app/fw/svg/icon.js
@@ -63,6 +63,7 @@
         portTable: 'portTable',
         groupTable: 'groupTable',
         meterTable: 'meterTable',
+        pipeconfTable: 'pipeconfTable',
 
         hostIcon_endstation: 'endstation',
         hostIcon_router: 'router',
diff --git a/web/gui/src/main/webapp/app/fw/widget/table.css b/web/gui/src/main/webapp/app/fw/widget/table.css
index 2569031..4d4649a 100644
--- a/web/gui/src/main/webapp/app/fw/widget/table.css
+++ b/web/gui/src/main/webapp/app/fw/widget/table.css
@@ -52,10 +52,12 @@
     padding: 4px;
     text-align: left;
     word-wrap: break-word;
-    font-size: 12pt;
+    font-size: 10pt;
 }
 
 div.summary-list td.table-icon {
+    padding-top: 4px;
+    padding-bottom: 0px;
     padding-left: 4px;
     text-align: center;
 }
@@ -64,10 +66,9 @@
     font-weight: bold;
     font-variant: small-caps;
     text-transform: uppercase;
-    font-size: 11pt;
-    padding-top: 14px;
-    padding-bottom: 14px;
-
+    font-size: 10pt;
+    padding-top: 8px;
+    padding-bottom: 8px;
     letter-spacing: 0.02em;
     cursor: pointer;
 }
diff --git a/web/gui/src/main/webapp/app/onos.css b/web/gui/src/main/webapp/app/onos.css
index 52a0d63f..fa485ac 100644
--- a/web/gui/src/main/webapp/app/onos.css
+++ b/web/gui/src/main/webapp/app/onos.css
@@ -46,6 +46,6 @@
     -webkit-margin-after: 0;
     margin: 32px 0 4px 16px;
     padding: 0;
-    font-size: 21pt;
+    font-size: 18pt;
     font-weight: lighter;
 }
diff --git a/web/gui/src/main/webapp/app/view/app/app.css b/web/gui/src/main/webapp/app/view/app/app.css
index 4d61f81..a1963c0 100644
--- a/web/gui/src/main/webapp/app/view/app/app.css
+++ b/web/gui/src/main/webapp/app/view/app/app.css
@@ -55,7 +55,6 @@
 /* -- Details Panel -- */
 #application-details-panel.floatpanel {
     z-index: 0;
-    font-size: 12pt;
 }
 
 #application-details-panel.floatpanel a {
@@ -83,7 +82,7 @@
 #application-details-panel h2 {
     display: inline-block;
     margin: 12px 0 6px 0;
-    font-size: 13pt;
+    font-size: 11pt;
     font-variant: small-caps;
     text-transform: uppercase;
 }
@@ -91,7 +90,7 @@
 #application-details-panel .top .app-title {
     width: 90%;
     height: 62px;
-    font-size: 20pt;
+    font-size: 16pt;
     font-weight: lighter;
     overflow: hidden;
     white-space: nowrap;
@@ -115,8 +114,7 @@
 #application-details-panel .app-url i {
     font-weight: bold;
     text-align: right;
-    font-size: 12pt;
-
+    font-size: 10pt;
     padding-right: 6px;
 }
 
@@ -127,6 +125,7 @@
 #application-details-panel .app-url {
     padding: 10px 6px 6px;
     overflow: hidden;
+    clear: left;
 }
 
 #application-details-panel hr {
@@ -139,7 +138,7 @@
 }
 
 #application-details-panel .bottom {
-    padding: 12px 0 0 6px;
+    padding: 0px;
 }
 
 #application-details-panel .bottom table {
diff --git a/web/gui/src/main/webapp/app/view/app/app.html b/web/gui/src/main/webapp/app/view/app/app.html
index 54a5390..db5d69b 100644
--- a/web/gui/src/main/webapp/app/view/app/app.html
+++ b/web/gui/src/main/webapp/app/view/app/app.html
@@ -76,8 +76,8 @@
                     <td class="table-icon">
                         <div icon icon-id="{{app._iconid_state}}"></div>
                     </td>
-                    <td><img data-ng-src="./rs/applications/{{app.icon}}/icon"
-                             height="28px" width="28px" /></td>
+                    <td class="table-icon"><img data-ng-src="./rs/applications/{{app.icon}}/icon"
+                             height="24px" width="24px" /></td>
                     <td>{{app.title}}</td>
                     <td>{{app.id}}</td>
                     <td>{{app.version}}</td>
diff --git a/web/gui/src/main/webapp/app/view/cluster/cluster-theme.css b/web/gui/src/main/webapp/app/view/cluster/cluster-theme.css
new file mode 100644
index 0000000..b908721
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/cluster/cluster-theme.css
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Cluster View (theme) -- CSS file
+ */
+
+.light #cluster-details-panel .bottom th {
+    background-color: #e5e5e6;
+}
+
+.light #cluster-details-panel .bottom tr:nth-child(odd) {
+    background-color: #fbfbfb;
+}
+.light #cluster-details-panel .bottom tr:nth-child(even) {
+    background-color: #f4f4f4;
+}
+
diff --git a/web/gui/src/main/webapp/app/view/cluster/cluster.css b/web/gui/src/main/webapp/app/view/cluster/cluster.css
index 50622ad..1a93224 100644
--- a/web/gui/src/main/webapp/app/view/cluster/cluster.css
+++ b/web/gui/src/main/webapp/app/view/cluster/cluster.css
@@ -57,7 +57,7 @@
 }
 
 #cluster-details-panel .top-content table {
-    font-size: 12pt;
+    font-size: 10pt;
 }
 
 #cluster-details-panel td.label {
@@ -65,3 +65,18 @@
     text-align: right;
     padding-right: 6px;
 }
+
+#cluster-details-panel .bottom table {
+    border-spacing: 0;
+}
+
+#cluster-details-panel .bottom th {
+    letter-spacing: 0.02em;
+}
+
+#cluster-details-panel .bottom th,
+#cluster-details-panel .bottom td {
+    padding: 6px 12px;
+    text-align: center;
+    white-space: nowrap;
+}
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/app/view/device/device.css b/web/gui/src/main/webapp/app/view/device/device.css
index 2e23b3b..c578112 100644
--- a/web/gui/src/main/webapp/app/view/device/device.css
+++ b/web/gui/src/main/webapp/app/view/device/device.css
@@ -62,7 +62,8 @@
 }
 
 #device-details-panel .top-tables {
-    font-size: 12pt;
+    font-size: 10pt;
+    white-space: nowrap;
 }
 
 #device-details-panel .top div.left {
diff --git a/web/gui/src/main/webapp/app/view/device/device.html b/web/gui/src/main/webapp/app/view/device/device.html
index ac0febf..c26e067 100644
--- a/web/gui/src/main/webapp/app/view/device/device.html
+++ b/web/gui/src/main/webapp/app/view/device/device.html
@@ -31,6 +31,11 @@
                  icon icon-id="meterTable" icon-size="42"
                  tooltip tt-msg="meterTip"
                  ng-click="nav('meter')"></div>
+
+            <div ng-class="{active: !!selId}"
+                 icon icon-id="pipeconfTable" icon-size="42"
+                 tooltip tt-msg="pipeconfTip"
+                 ng-click="nav('pipeconf')"></div>
         </div>
     </div>
 
diff --git a/web/gui/src/main/webapp/app/view/device/device.js b/web/gui/src/main/webapp/app/view/device/device.js
index 4fbb0df..536b789 100644
--- a/web/gui/src/main/webapp/app/view/device/device.js
+++ b/web/gui/src/main/webapp/app/view/device/device.js
@@ -22,17 +22,14 @@
     'use strict';
 
     // injected refs
-    var $log, $scope, $loc, fs, mast, ps, wss, is, ns, ks;
+    var $log, $scope, $loc, fs, mast, ps, wss, is, ns, ks, dps;
 
     // internal state
     var detailsPanel,
         pStartY,
         pHeight,
         top,
-        bottom,
-        iconDiv,
         wSize,
-        editingName = false,
         device;
 
     // constants
@@ -40,19 +37,12 @@
         ctnrPdg = 24,
         scrollSize = 17,
         portsTblPdg = 50,
-        defaultLabelWidth = 110,
-        defaultValueWidth  = 80,
 
         pName = 'device-details-panel',
         detailsReq = 'deviceDetailsRequest',
         detailsResp = 'deviceDetailsResponse',
         nameChangeReq = 'deviceNameChangeRequest',
         nameChangeResp = 'deviceNameChangeResponse',
-        friendlyProps = [
-            'URI', 'Type', 'Master ID', 'Chassis ID',
-            'Vendor', 'H/W Version', 'S/W Version', 'Protocol', 'Serial #',
-            'Pipeconf',
-        ],
         portCols = [
             'enabled', 'id', 'speed', 'type', 'elinks_dest', 'name',
         ],
@@ -60,6 +50,11 @@
             'Enabled', 'ID', 'Speed', 'Type', 'Egress Links', 'Name',
         ];
 
+    var keyBindings = {
+        esc: [closePanel, 'Close the details panel'],
+        _helpFormat: ['esc'],
+    };
+
     function closePanel() {
         if (detailsPanel.isVisible()) {
             $scope.selId = null;
@@ -69,119 +64,44 @@
         return false;
     }
 
-    function addCloseBtn(div) {
-        is.loadEmbeddedIcon(div, 'close', 20);
-        div.on('click', closePanel);
-    }
-
-    function exitEditMode(nameH2, name) {
-        nameH2.text(name);
-        nameH2.classed('editable clickable', true);
-        editingName = false;
-        ks.enableGlobalKeys(true);
-    }
-
-    function editNameSave() {
-        var nameH2 = top.select('h2'),
-            id = $scope.panelData.id,
-            val,
-            newVal;
-
-        if (editingName) {
-            val = nameH2.select('input').property('value').trim();
-            newVal = val || id;
-
-            exitEditMode(nameH2, newVal);
-            $scope.panelData.name = newVal;
-            wss.sendEvent(nameChangeReq, { id: id, name: val });
-        }
-    }
-
-    function editNameCancel() {
-        if (editingName) {
-            exitEditMode(top.select('h2'), $scope.panelData.name);
-            return true;
-        }
-        return false;
-    }
-
-    function editName() {
-        var nameH2 = top.select('h2'),
-            tf, el;
-
-        if (!editingName) {
-            nameH2.classed('editable clickable', false);
-            nameH2.text('');
-            tf = nameH2.append('input').classed('name-input', true)
-                .attr('type', 'text')
-                .attr('value', $scope.panelData.name);
-            el = tf[0][0];
-            el.focus();
-            el.select();
-            editingName = true;
-            ks.enableGlobalKeys(false);
-        }
-    }
-
-    function handleEscape() {
-        return editNameCancel() || closePanel();
-    }
-
     function setUpPanel() {
-        var container, closeBtn, tblDiv;
-        detailsPanel.empty();
+        // var container, closeBtn, tblDiv;
 
-        container = detailsPanel.append('div').classed('container', true);
+        dps.empty();
+        dps.addContainers();
+        dps.addCloseButton(closePanel);
 
-        top = container.append('div').classed('top', true);
-        closeBtn = top.append('div').classed('close-btn', true);
-        addCloseBtn(closeBtn);
-        iconDiv = top.append('div').classed('dev-icon', true);
-        top.append('h2').classed('editable clickable', true).on('click', editName);
+        var top = dps.top();
+        var bottom = dps.bottom();
 
-        tblDiv = top.append('div').classed('top-tables', true);
-        tblDiv.append('div').classed('left', true).append('table');
-        tblDiv.append('div').classed('right', true).append('table');
+        dps.addHeading('dev-icon');
+        top.append('div').classed('top-content', true);
 
         top.append('hr');
 
-        bottom = container.append('div').classed('bottom', true);
         bottom.append('h2').classed('ports-title', true).text('Ports');
         bottom.append('table');
     }
 
-    function addProp(tbody, index, value) {
-        var tr = tbody.append('tr');
-
-        function addCell(cls, txt, width) {
-            tr.append('td').attr('class', cls).attr('width', width).text(txt);
-        }
-        addCell('label', friendlyProps[index] + ' :', defaultLabelWidth);
-        addCell('value', value, defaultValueWidth);
+    function friendlyPropsList(details) {
+        return {
+            'URI': device.id,
+            'Type': device.type,
+            'Master ID': details['masterid'],
+            'Chassis ID': details['chassid'],
+            'Vendor': device.mfr,
+            'H/W Version': device.hw,
+            'S/W Version': device.sw,
+            'Protocol': details['protocol'],
+            'Serial #': device.serial,
+            'Pipeconf': details['pipeconf'],
+        };
     }
 
     function populateTop(tblDiv, details) {
-        var leftTbl = tblDiv.select('.left')
-                        .select('table')
-                        .append('tbody'),
-            rightTbl = tblDiv.select('.right')
-                        .select('table')
-                        .append('tbody');
-
-        is.loadEmbeddedIcon(iconDiv, details._iconid_type, 40);
-        top.select('h2').text(details.name);
-
-        // === demonstrate use of JsonCodec object see ONOS-5976
-        addProp(leftTbl, 0, device.id);
-        addProp(leftTbl, 1, device.type);
-        addProp(leftTbl, 2, details['masterid']);
-        addProp(leftTbl, 3, details['chassid']);
-        addProp(leftTbl, 4, device.mfr);
-        addProp(rightTbl, 5, device.hw);
-        addProp(rightTbl, 6, device.sw);
-        addProp(rightTbl, 7, details['protocol']);
-        addProp(rightTbl, 8, device.serial);
-        addProp(rightTbl, 9, details['pipeconf']);
+        is.loadEmbeddedIcon(dps.select('.iconDiv'), details._iconid_type, 40);
+        dps.top().select('h2').text(details.name);
+        dps.addPropsList(tblDiv, friendlyPropsList(details));
     }
 
     function addPortRow(tbody, port) {
@@ -193,6 +113,7 @@
     }
 
     function populateBottom(table, ports) {
+
         var theader = table.append('thead').append('tr'),
             tbody = table.append('tbody'),
             tbWidth, tbHeight;
@@ -200,16 +121,15 @@
         friendlyPortCols.forEach(function (col) {
             theader.append('th').text(col);
         });
+
         ports.forEach(function (port) {
             addPortRow(tbody, port);
         });
 
         tbWidth = fs.noPxStyle(tbody, 'width') + scrollSize;
         tbHeight = pHeight
-                    - (fs.noPxStyle(detailsPanel.el()
-                                        .select('.top'), 'height')
-                    + fs.noPxStyle(detailsPanel.el()
-                                        .select('.ports-title'), 'height')
+                    - (fs.noPxStyle(detailsPanel.el().select('.top'), 'height')
+                    + fs.noPxStyle(detailsPanel.el().select('.ports-title'), 'height')
                     + portsTblPdg);
 
         table.style({
@@ -223,15 +143,14 @@
     }
 
     function populateDetails(details) {
-        var topTbs, btmTbl, ports;
+        var btmTbl, ports;
 
         setUpPanel();
 
-        topTbs = top.select('.top-tables');
-        btmTbl = bottom.select('table');
+        btmTbl = dps.bottom().select('table');
         ports = details.ports;
 
-        populateTop(topTbs, details);
+        populateTop(dps.select('.top-content'), details);
         populateBottom(btmTbl, ports);
 
         detailsPanel.height(pHeight);
@@ -250,18 +169,19 @@
         }
     }
 
-    function createDetailsPane() {
-        detailsPanel = ps.createPanel(pName, {
+    function createDetailsPanel() {
+        detailsPanel = dps.create(pName, {
             width: wSize.width,
             margin: 0,
             hideMargin: 0,
+            scope: $scope,
+            keyBindings: keyBindings,
+            nameChangeRequest: nameChangeReq,
         });
-        detailsPanel.el().style({
-            position: 'absolute',
-            top: pStartY + 'px',
-        });
+
+        dps.setResponse(detailsResp, respDetailsCb);
+
         $scope.hidePanel = function () { detailsPanel.hide(); };
-        detailsPanel.hide();
     }
 
     // Sample functions for detail panel creation
@@ -283,10 +203,10 @@
         ['$log', '$scope', '$location', 'TableBuilderService',
             'TableDetailService', 'FnService',
             'MastService', 'PanelService', 'WebSocketService', 'IconService',
-            'NavService', 'KeyService',
+            'NavService', 'KeyService', 'DetailsPanelService',
 
         function (_$log_, _$scope_, _$location_,
-                  tbs, tds, _fs_, _mast_, _ps_, _wss_, _is_, _ns_, _ks_) {
+                  tbs, tds, _fs_, _mast_, _ps_, _wss_, _is_, _ns_, _ks_, _dps_) {
             var params,
                 handlers = {};
 
@@ -300,6 +220,7 @@
             is = _is_;
             ns = _ns_;
             ks = _ks_;
+            dps = _dps_;
 
             params = $loc.search();
 
@@ -308,9 +229,10 @@
             $scope.portTip = 'Show port view for selected device';
             $scope.groupTip = 'Show group view for selected device';
             $scope.meterTip = 'Show meter view for selected device';
+            $scope.pipeconfTip = 'Show pipeconf view for selected device';
 
             // details panel handlers
-            handlers[detailsResp] = respDetailsCb;
+            // handlers[detailsResp] = respDetailsCb;
             handlers[nameChangeResp] = respNameCb;
             wss.bindHandlers(handlers);
 
@@ -352,6 +274,7 @@
             };
 
             $scope.$on('$destroy', function () {
+                dps.destroy();
                 wss.unbindHandlers(handlers);
             });
 
@@ -373,7 +296,7 @@
 
             function initPanel() {
                 heightCalc();
-                createDetailsPane();
+                createDetailsPanel();
             }
 
             // Safari has a bug where it renders the fixed-layout table wrong
@@ -384,11 +307,8 @@
                 initPanel();
             }
             // create key bindings to handle panel
-            ks.keyBindings({
-                enter: editNameSave,
-                esc: [handleEscape, 'Close the details panel'],
-                _helpFormat: ['esc'],
-            });
+            ks.keyBindings(keyBindings);
+
             ks.gestureNotes([
                 ['click', 'Select a row to show device details'],
                 ['scroll down', 'See more devices'],
diff --git a/web/gui/src/main/webapp/app/view/flow/flow.html b/web/gui/src/main/webapp/app/view/flow/flow.html
index af24a63..a6fed1a 100644
--- a/web/gui/src/main/webapp/app/view/flow/flow.html
+++ b/web/gui/src/main/webapp/app/view/flow/flow.html
@@ -53,6 +53,11 @@
                  tooltip tt-msg="meterTip"
                  ng-click="nav('meter')"></div>
 
+            <div class="active"
+                 icon icon-id="pipeconfTable" icon-size="42"
+                 tooltip tt-msg="pipeconfTip"
+                 ng-click="nav('pipeconf')"></div>
+
         </div>
 
         <div class="search">
@@ -62,7 +67,7 @@
                 <option value="$">All Fields</option>
                 <!--<option value="id">Flow ID</option>-->
                 <option value="priority">Priority</option>
-                <option value="tableId">Table ID</option>
+                <option value="tableName">Table Name</option>
                 <option value="selector">Selector</option>
                 <option value="treatment">Treatment</option>
                 <option value="appName">App Name</option>
@@ -81,7 +86,7 @@
                     <td class="right" colId="packets" col-width="90px" sortable> Packets </td>
                     <td class="right" colId="duration" col-width="90px" sortable> Duration </td>
                     <td colId="priority" col-width="80px" sortable> Priority </td>
-                    <td colId="tableId" col-width="80px" sortable> Table ID </td>
+                    <td colId="tableName" col-width="120px" sortable> Table Name </td>
                     <td colId="selector" sortable> Selector </td>
                     <td colId="treatment" sortable> Treatment </td>
                     <td colId="appName" sortable> App Name </td>
@@ -108,7 +113,7 @@
                     <td class="right">{{flow.packets}}</td>
                     <td class="right">{{flow.duration}}</td>
                     <td>{{flow.priority}}</td>
-                    <td>{{flow.tableId}}</td>
+                    <td>{{flow.tableName}}</td>
                     <td>{{flow.selector_c}}</td>
                     <td>{{flow.treatment_c}}</td>
                     <td>{{flow.appName}}</td>
diff --git a/web/gui/src/main/webapp/app/view/flow/flow.js b/web/gui/src/main/webapp/app/view/flow/flow.js
index 2000997..71794bd 100644
--- a/web/gui/src/main/webapp/app/view/flow/flow.js
+++ b/web/gui/src/main/webapp/app/view/flow/flow.js
@@ -54,7 +54,7 @@
             'duration',
 
             'priority',
-            'tableId',
+            'tableName',
             'appName',
             'appId',
 
@@ -71,7 +71,7 @@
             'Duration',
 
             'Flow Priority',
-            'Table ID',
+            'Table Name',
             'App Name',
             'App ID',
 
@@ -246,8 +246,10 @@
                     $scope.portTip = 'Show port view for this device';
                     $scope.groupTip = 'Show group view for this device';
                     $scope.meterTip = 'Show meter view for selected device';
+                    $scope.pipeconfTip = 'Show pipeconf view for selected device';
                     $scope.briefTip = 'Switch to brief view';
                     $scope.detailTip = 'Switch to detailed view';
+
                     $scope.brief = true;
                     params = $location.search();
                     if (params.hasOwnProperty('devId')) {
diff --git a/web/gui/src/main/webapp/app/view/group/group.html b/web/gui/src/main/webapp/app/view/group/group.html
index a09dd18..fb6b277 100644
--- a/web/gui/src/main/webapp/app/view/group/group.html
+++ b/web/gui/src/main/webapp/app/view/group/group.html
@@ -52,6 +52,11 @@
                  icon icon-id="meterTable" icon-size="42"
                  tooltip tt-msg="meterTip"
                  ng-click="nav('meter')"></div>
+
+            <div class="active"
+                 icon icon-id="pipeconfTable" icon-size="42"
+                 tooltip tt-msg="pipeconfTip"
+                 ng-click="nav('pipeconf')"></div>
         </div>
 
         <div class="search">
diff --git a/web/gui/src/main/webapp/app/view/group/group.js b/web/gui/src/main/webapp/app/view/group/group.js
index 063daef..0b36d2c 100644
--- a/web/gui/src/main/webapp/app/view/group/group.js
+++ b/web/gui/src/main/webapp/app/view/group/group.js
@@ -41,6 +41,7 @@
             $scope.flowTip = 'Show flow view for this device';
             $scope.portTip = 'Show port view for this device';
             $scope.meterTip = 'Show meter view for selected device';
+            $scope.pipeconfTip = 'Show pipeconf view for selected device';
             $scope.briefTip = 'Switch to brief view';
             $scope.detailTip = 'Switch to detailed view';
             $scope.brief = true;
diff --git a/web/gui/src/main/webapp/app/view/host/host.css b/web/gui/src/main/webapp/app/view/host/host.css
index b1aa914..461373f 100644
--- a/web/gui/src/main/webapp/app/view/host/host.css
+++ b/web/gui/src/main/webapp/app/view/host/host.css
@@ -62,7 +62,7 @@
 }
 
 #host-details-panel .top-tables {
-    font-size: 12pt;
+    font-size: 10pt;
 }
 
 #host-details-panel .top div.left {
diff --git a/web/gui/src/main/webapp/app/view/host/host.js b/web/gui/src/main/webapp/app/view/host/host.js
index 9cb674c..37668bb 100644
--- a/web/gui/src/main/webapp/app/view/host/host.js
+++ b/web/gui/src/main/webapp/app/view/host/host.js
@@ -22,16 +22,14 @@
     'use strict';
 
     // injected refs
-    var $log, $scope, $loc, fs, mast, ps, wss, is, ns, ks;
+    var $log, $scope, $loc, fs, mast, ps, wss, is, ns, ks, dps;
 
     // internal state
     var detailsPanel,
         pStartY,
         pHeight,
         top,
-        iconDiv,
-        wSize,
-        editingName = false;
+        wSize;
 
     // constants
     var topPdg = 28,
@@ -41,13 +39,10 @@
         nameChangeReq = 'hostNameChangeRequest',
         nameChangeResp = 'hostNameChangeResponse';
 
-    var propOrder = [
-            'id', 'ip', 'mac', 'vlan', 'configured', 'location',
-        ],
-        friendlyProps = [
-            'Host ID', 'IP Address', 'MAC Address', 'VLAN',
-            'Configured', 'Location',
-        ];
+    var keyBindings = {
+        esc: [closePanel, 'Close the details panel'],
+        _helpFormat: ['esc'],
+    };
 
     function closePanel() {
         if (detailsPanel.isVisible()) {
@@ -58,105 +53,41 @@
         return false;
     }
 
-    function addCloseBtn(div) {
-        is.loadEmbeddedIcon(div, 'close', 20);
-        div.on('click', closePanel);
-    }
-
-    function exitEditMode(nameH2, name) {
-        nameH2.text(name);
-        nameH2.classed('editable clickable', true);
-        editingName = false;
-        ks.enableGlobalKeys(true);
-    }
-
-    function editNameSave() {
-        var nameH2 = top.select('h2'),
-            id = $scope.panelData.id,
-            ip = $scope.panelData.ip,
-            val,
-            newVal;
-
-        if (editingName) {
-            val = nameH2.select('input').property('value').trim();
-            newVal = val || ip;
-
-            exitEditMode(nameH2, newVal);
-            $scope.panelData.name = newVal;
-            wss.sendEvent(nameChangeReq, { id: id, name: val });
-        }
-    }
-
-    function editNameCancel() {
-        if (editingName) {
-            exitEditMode(top.select('h2'), $scope.panelData.name);
-            return true;
-        }
-        return false;
-    }
-
-    function editName() {
-        var nameH2 = top.select('h2'),
-            tf, el;
-
-        if (!editingName) {
-            nameH2.classed('editable clickable', false);
-            nameH2.text('');
-            tf = nameH2.append('input').classed('name-input', true)
-                .attr('type', 'text')
-                .attr('value', $scope.panelData.name);
-            el = tf[0][0];
-            el.focus();
-            el.select();
-            editingName = true;
-            ks.enableGlobalKeys(false);
-        }
-    }
-
-    function handleEscape() {
-        return editNameCancel() || closePanel();
-    }
 
     function setUpPanel() {
-        var container, closeBtn;
-        detailsPanel.empty();
 
-        container = detailsPanel.append('div').classed('container', true);
+        dps.empty();
+        dps.addContainers();
+        dps.addCloseButton(closePanel);
 
-        top = container.append('div').classed('top', true);
-        closeBtn = top.append('div').classed('close-btn', true);
-        addCloseBtn(closeBtn);
-        iconDiv = top.append('div').classed('host-icon', true);
-        top.append('h2').classed('editable clickable', true).on('click', editName);
+        var top = dps.top();
 
-        top.append('div').classed('top-tables', true);
+        dps.addHeading('host-icon');
+        top.append('div').classed('top-content', true);
+
         top.append('hr');
     }
 
-    function addProp(tbody, index, value) {
-        var tr = tbody.append('tr');
-
-        function addCell(cls, txt) {
-            tr.append('td').attr('class', cls).text(txt);
-        }
-        addCell('label', friendlyProps[index] + ' :');
-        addCell('value', value);
+    function friendlyPropsList(details) {
+        return {
+            'Host ID': details.id,
+            'IP Address': details.ip[0],
+            'MAC Address': details.mac,
+            'VLAN': details.vlan,
+            'Configured': details.configured,
+            'Location': details.location,
+        };
     }
 
-    function populateTop(details) {
-        var tab = top.select('.top-tables').append('tbody');
-
-        is.loadEmbeddedIcon(iconDiv, details._iconid_type, 40);
-        top.select('h2').text(details.name);
-
-        propOrder.forEach(function (prop, i) {
-            addProp(tab, i, details[prop]);
-        });
+    function populateTop(tblDiv, details) {
+        is.loadEmbeddedIcon(dps.select('.iconDiv'), details._iconid_type, 40);
+        dps.top().select('h2').text(details.name);
+        dps.addPropsList(tblDiv, friendlyPropsList(details));
     }
 
     function populateDetails(details) {
         setUpPanel();
-        populateTop(details);
+        populateTop(dps.select('.top-content'), details);
         detailsPanel.height(pHeight);
         // configure width based on content.. for now hardcoded
         detailsPanel.width(400);
@@ -174,18 +105,19 @@
         }
     }
 
-    function createDetailsPane() {
-        detailsPanel = ps.createPanel(pName, {
+    function createDetailsPanel() {
+        detailsPanel = dps.create(pName, {
             width: wSize.width,
             margin: 0,
             hideMargin: 0,
+            scope: $scope,
+            keyBindings: keyBindings,
+            nameChangeRequest: nameChangeReq,
         });
-        detailsPanel.el().style({
-            position: 'absolute',
-            top: pStartY + 'px',
-        });
+
+        dps.setResponse(detailsResp, respDetailsCb);
+
         $scope.hidePanel = function () { detailsPanel.hide(); };
-        detailsPanel.hide();
     }
 
 
@@ -196,12 +128,12 @@
             '$location',
             'TableBuilderService',
             'FnService', 'MastService', 'PanelService', 'WebSocketService',
-            'IconService', 'NavService', 'KeyService',
+            'IconService', 'NavService', 'KeyService', 'DetailsPanelService',
 
         function (_$log_, _$scope_, _$location_,
                   tbs,
                   _fs_, _mast_, _ps_, _wss_,
-                  _is_, _ns_, _ks_) {
+                  _is_, _ns_, _ks_, _dps_) {
 
             var params,
                 handlers = {};
@@ -216,13 +148,13 @@
             is = _is_;
             ns = _ns_;
             ks = _ks_;
+            dps = _dps_;
 
             params = $loc.search();
 
             $scope.panelData = {};
 
             // details panel handlers
-            handlers[detailsResp] = respDetailsCb;
             handlers[nameChangeResp] = respNameCb;
             wss.bindHandlers(handlers);
 
@@ -254,6 +186,7 @@
             };
 
             $scope.$on('$destroy', function () {
+                dps.destroy();
                 wss.unbindHandlers(handlers);
             });
 
@@ -261,7 +194,7 @@
         }])
 
     .directive('hostDetailsPanel',
-    ['$rootScope', '$window', '$timeout', 'KeyService',
+    ['$rootScope', '$window', '$timeout', 'KeyService', 'DetailsPanelService',
     function ($rootScope, $window, $timeout, ks) {
         return function (scope) {
             var unbindWatch;
@@ -275,7 +208,7 @@
 
             function initPanel() {
                 heightCalc();
-                createDetailsPane();
+                createDetailsPanel();
             }
 
             // Safari has a bug where it renders the fixed-layout table wrong
@@ -286,11 +219,7 @@
                 initPanel();
             }
             // create key bindings to handle panel
-            ks.keyBindings({
-                enter: editNameSave,
-                esc: [handleEscape, 'Close the details panel'],
-                _helpFormat: ['esc'],
-            });
+            ks.keyBindings(keyBindings);
             ks.gestureNotes([
                 ['click', 'Select a row to show device details'],
                 ['scroll down', 'See more devices'],
diff --git a/web/gui/src/main/webapp/app/view/meter/meter.html b/web/gui/src/main/webapp/app/view/meter/meter.html
index cd83392..ddd17f0 100644
--- a/web/gui/src/main/webapp/app/view/meter/meter.html
+++ b/web/gui/src/main/webapp/app/view/meter/meter.html
@@ -36,6 +36,11 @@
 
             <div class="current-view"
                  icon icon-id="meterTable" icon-size="42"></div>
+
+            <div class="active"
+                 icon icon-id="pipeconfTable" icon-size="42"
+                 tooltip tt-msg="pipeconfTip"
+                 ng-click="nav('pipeconf')"></div>
         </div>
 
         <div class="search">
diff --git a/web/gui/src/main/webapp/app/view/meter/meter.js b/web/gui/src/main/webapp/app/view/meter/meter.js
index bec7d2e..32058b6 100644
--- a/web/gui/src/main/webapp/app/view/meter/meter.js
+++ b/web/gui/src/main/webapp/app/view/meter/meter.js
@@ -40,6 +40,7 @@
             $scope.flowTip = 'Show flow view for this device';
             $scope.portTip = 'Show port view for this device';
             $scope.groupTip = 'Show group view for this device';
+            $scope.pipeconfTip = 'Show pipeconf view for selected device';
 
             params = $location.search();
             if (params.hasOwnProperty('devId')) {
diff --git a/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.css b/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.css
new file mode 100644
index 0000000..81eb603
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.css
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/* Base */
+#pipeconf-info h2 {
+    display: inline-block;
+    margin: 10px 0 10px 0;
+}
+
+#pipeconf-info h3 {
+    display: inline-block;
+    margin-bottom: 10px;
+}
+
+#pipeconf-info {
+    height: inherit;
+    overflow-y: scroll;
+    overflow-x: hidden;
+    padding-left: 16px;
+    padding-right: 20px;
+}
+
+#pipeconf-info::-webkit-scrollbar {
+    display: none;
+}
+
+/* Table */
+.pipeconf-table {
+    width: 100%;
+}
+
+.pipeconf-table tr {
+    transition: background-color 500ms;
+    text-align: left;
+    padding: 8px 4px;
+}
+
+.pipeconf-table .selected {
+    background-color: #dbeffc !important;
+}
+
+.pipeconf-table tr:nth-child(even) {
+    background-color: #f4f4f4;
+}
+
+.pipeconf-table tr:nth-child(odd) {
+    background-color: #fbfbfb;
+}
+
+.pipeconf-table tr th {
+    background-color: #e5e5e6;
+    color: #3c3a3a;
+    font-weight: bold;
+    font-variant: small-caps;
+    text-transform: uppercase;
+    font-size: 10pt;
+    letter-spacing: 0.02em;
+}
+
+.pipeconf-table tr td {
+    padding: 4px;
+    text-align: left;
+    word-wrap: break-word;
+    font-size: 10pt;
+}
+
+.pipeconf-table tr td p {
+    margin: 5px 0;
+}
+
+/* Detail panel */
+.container {
+    padding: 10px;
+    overflow: hidden;
+}
+
+.container .top, .bottom {
+    padding: 15px;
+}
+.container .bottom {
+    overflow-y: scroll;
+}
+
+.container .bottom h2 {
+    margin: 0;
+}
+
+.container .top .detail-panel-header {
+    display: inline-block;
+    margin: 0 0 10px;
+}
+
+
+.detail-panel-table {
+    width: 100%;
+    overflow-y: hidden;
+}
+
+.detail-panel-table td, th {
+    text-align: left;
+    padding: 6px 12px;
+}
+
+.top-info {
+    font-size: 12pt;
+}
+
+.top-info .label {
+    font-weight: bold;
+    text-align: right;
+    display: inline-block;
+    margin: 0;
+    padding-right:6px;
+}
+
+.top-info .value {
+    margin: 0;
+    text-align: left;
+    display: inline-block;
+}
+
+/* Widgets */
+#ov-pipeconf h2 {
+    display: inline-block;
+}
+
+.collapse-btn {
+    cursor: pointer;
+    display: inline-block;
+    max-height: 30px;
+    overflow-y: hidden;
+    position: relative;
+    top: 8px;
+}
+
+.close-btn {
+    display: inline-block;
+    float: right;
+    margin: 0.1em;
+    cursor: pointer;
+}
+
+.text-center {
+    text-align: center !important;
+}
+
+.no-data {
+    text-align: center !important;
+    font-style: italic;
+}
diff --git a/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.html b/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.html
new file mode 100644
index 0000000..4d4e8f6
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.html
@@ -0,0 +1,204 @@
+<!--
+  ~ Copyright 2017-present Open Networking Foundation
+  ~
+  ~ 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.
+  -->
+
+<div id="ov-pipeconf">
+    <div class="tabular-header">
+        <h2>
+            Pipeconf for Device {{devId || "(No device selected)"}}
+        </h2>
+
+        <div class="ctrl-btns">
+            <div class="refresh" ng-class="{active: autoRefresh}"
+                 icon icon-size="42" icon-id="refresh"
+                 tooltip tt-msg="autoRefreshTip"
+                 ng-click="autoRefresh = !autoRefresh"></div>
+
+            <div class="separator"></div>
+
+            <div class="active"
+                 icon icon-id="deviceTable" icon-size="42"
+                 tooltip tt-msg="deviceTip"
+                 ng-click="nav('device')"></div>
+
+            <div class="active"
+                 icon icon-id="flowTable" icon-size="42"
+                 tooltip tt-msg="flowTip"
+                 ng-click="nav('flow')"></div>
+
+            <div class="active"
+                 icon icon-id="portTable" icon-size="42"
+                 tooltip tt-msg="portTip"
+                 ng-click="nav('port')"></div>
+
+            <div class="active"
+                 icon icon-id="groupTable" icon-size="42"
+                 tooltip tt-msg="groupTip"
+                 ng-click="nav('group')"></div>
+
+            <div class="active"
+                 icon icon-id="meterTable" icon-size="42"
+                 tooltip tt-msg="meterTip"
+                 ng-click="nav('meter')"></div>
+
+            <div class="current-view"
+                 icon icon-id="pipeconfTable" icon-size="42"
+                 tooltip tt-msg="pipeconfTip"></div>
+        </div>
+    </div>
+    <div id="pipeconf-info" auto-height>
+        <div id="pipeconf-basic">
+            <h2>Basic information</h2>
+            <table class="pipeconf-table">
+                <tr>
+                    <th class="text-center" style="width: 160px">Name</th>
+                    <th>Info</th>
+                </tr>
+                <tr ng-show="collapsePipeconf">
+                    <td colspan="2" class="text-center">....</td>
+                </tr>
+                <tr ng-show="pipeconf === null">
+                    <td colspan="2" class="no-data">
+                        No PiPipeconf for this device
+                    </td>
+                </tr>
+                <tr ng-show-start="!collapsePipeconf && pipeconf !== null">
+                    <td class="text-center">ID</td>
+                    <td>{{pipeconf.id}}</td>
+                </tr>
+                <tr>
+                    <td class="text-center">Behaviors</td>
+                    <td>{{pipeconf.behaviors.join(", ")}}</td>
+                </tr>
+                <tr ng-show-end>
+                    <td class="text-center">Extensions</td>
+                    <td>{{pipeconf.extensions.join(", ")}}</td>
+                </tr>
+            </table>
+        </div>
+        <!-- ng-show-start for checking pipeconf !== null -->
+        <h2 ng-show-start="pipeconf !== null">Pipeline Model</h2>
+        <div id="pipeconf-headers">
+            <h3>Headers</h3>
+            <div ng-show="!collapseHeaders"
+                 class="collapse-btn" icon icon-id="plus" icon-size="30"
+                 ng-click="collapseHeaders = !collapseHeaders"></div>
+            <div ng-show="collapseHeaders"
+                 class="collapse-btn" icon icon-id="minus" icon-size="30"
+                 ng-click="collapseHeaders = !collapseHeaders"></div>
+            <table class="pipeconf-table">
+                <tr>
+                    <th style="width: 160px">Name</th>
+                    <th class="text-center" style="width: 100px">Is metadata</th>
+                    <th class="text-center" style="width: 100px">Index</th>
+                    <th>Header type</th>
+                </tr>
+                <tr ng-show="collapseHeaders">
+                    <td colspan="4" class="clickable text-center"
+                        ng-click="collapseHeaders = !collapseHeaders">....</td>
+                </tr>
+                <tr ng-show="!collapseHeaders && pipelineModel.headers.length === 0">
+                    <td colspan="4" class="no-data">No Data</td>
+                </tr>
+                <tr ng-show="!collapseHeaders"
+                    ng-repeat="header in pipelineModel.headers"
+                    ng-click="headerSelectCb($event, header)"
+                    ng-class="{selected: header.name === selectedId.name && selectedId.type === 'header'}"
+                    class="clickable">
+                    <td>{{header.name}}</td>
+                    <td class="text-center">{{header.isMetadata}}</td>
+                    <td class="text-center">{{header.index}}</td>
+                    <td>{{header.type.name}}</td>
+                </tr>
+            </table>
+        </div>
+        <div id="pipeconf-actions">
+            <h3>Actions</h3>
+            <div ng-show="!collapseActions"
+                 class="collapse-btn" icon icon-id="plus" icon-size="30"
+                 ng-click="collapseActions = !collapseActions"></div>
+            <div ng-show="collapseActions"
+                 class="collapse-btn" icon icon-id="minus" icon-size="30"
+                 ng-click="collapseActions = !collapseActions"></div>
+            <table class="pipeconf-table">
+                <tr>
+                    <th style="width: 160px">Name</th>
+                    <th>Action parameters</th>
+                </tr>
+                <tr ng-show="collapseActions">
+                    <td colspan="2" class="clickable text-center"
+                        ng-click="collapseActions = !collapseActions">....</td>
+                </tr>
+                <tr ng-show="!collapseActions && pipelineModel.actions.length === 0">
+                    <td colspan="2" class="no-data">No Data</td>
+                </tr>
+                <tr ng-show="!collapseActions"
+                    ng-repeat="action in pipelineModel.actions"
+                    ng-click="actionSelectCb($event, action)"
+                    ng-class="{selected: action.name === selectedId.name && selectedId.type === 'action'}"
+                    class="clickable">
+                    <td style="width: 160px">{{action.name}}</td>
+                    <td ng-show="action.params.length != 0">{{ mapToNames(action.params).join(', ') }}</td>
+                    <td ng-show="action.params.length == 0">No action params</td>
+                </tr>
+            </table>
+        </div>
+
+        <div id="pipeconf-tables" ng-show-end>
+            <h3>Tables</h3>
+            <div ng-show="!collapseTables" class="collapse-btn"
+                 icon icon-id="plus" icon-size="30"
+                 ng-click="collapseTables = !collapseTables"></div>
+            <div ng-show="collapseTables" class="collapse-btn"
+                 icon icon-id="minus" icon-size="30"
+                 ng-click="collapseTables = !collapseTables"></div>
+            <table class="pipeconf-table">
+                <tr>
+                    <th class="text-center" style="width: 160px">Name</th>
+                    <th class="text-center" style="width: 100px">Max size</th>
+                    <th class="text-center" style="width: 100px">Has Counters</th>
+                    <th class="text-center" style="width: 100px">Support Aging</th>
+                    <th>Match fields</th>
+                    <th>Actions</th>
+                </tr>
+                <tr ng-show="collapseTables">
+                    <td colspan="6" class="clickable text-center"
+                        ng-click="collapseTables = !collapseTables">....</td>
+                </tr>
+                <tr ng-show="!collapseTables && pipelineModel.tables.length === 0">
+                    <td colspan="6" class="no-data">No Data</td>
+                </tr>
+                <tr ng-show="!collapseTables"
+                    ng-repeat="table in pipelineModel.tables"
+                    ng-click="tableSelectCb($event, table)"
+                    ng-class="{selected: table.name === selectedId.name && selectedId.type === 'table'}"
+                    class="clickable">
+                    <td class="text-center">{{table.name}}</td>
+                    <td class="text-center">{{table.maxSize}}</td>
+                    <td class="text-center">{{table.hasCounters}}</td>
+                    <td class="text-center">{{table.supportAging}}</td>
+                    <td ng-show="table.matchFields.length != 0">
+                        {{matMatchFields(table.matchFields).join(', ')}}
+                    </td>
+                    <td ng-show="table.matchFields.length == 0">No match fields</td>
+                    <td ng-show="table.actions.length != 0">{{table.actions.join(", ")}}</td>
+                    <td ng-show="table.actions.length == 0">No table actions</td>
+                </tr>
+            </table>
+        </div>
+        <!-- ng-show-end for checking pipeconf !== null -->
+    </div>
+    <pipeconf-view-detail-panel></pipeconf-view-detail-panel>
+</div>
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.js b/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.js
new file mode 100644
index 0000000..f5156bb
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.js
@@ -0,0 +1,464 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Pipeconf View Module
+ */
+
+(function () {
+    'use strict';
+
+    // injected refs
+    var $log, $scope, $loc, $interval, $timeout, fs, ns, wss, ls, ps, mast, is, dps;
+
+    // Constants
+    var pipeconfRequest = "pipeconfRequest",
+        pipeConfResponse = "pipeConfResponse",
+        noPipeconfResp = "noPipeconfResp",
+        invalidDevId = "invalidDevId",
+        pipeconf = "pipeconf",
+        pipelineModel = "pipelineModel",
+        devId = "devId",
+        topPdg = 28,
+        pName = 'pipeconf-detail-panel',
+        refreshRate = 5000;
+
+    // For request handling
+    var handlers,
+        refreshPromise;
+
+    // Details panel
+    var pWidth = 600,
+        pTopHeight = 111,
+        pStartY,
+        wSize,
+        pHeight,
+        detailsPanel;
+
+    // create key bindings to handle panel
+    var keyBindings = {
+        esc: [closePanel, 'Close the details panel'],
+        _helpFormat: ['esc'],
+    };
+
+    function fetchPipeconfData() {
+        if ($scope.autoRefresh && wss.isConnected() && !ls.waiting()) {
+            ls.start();
+            var requestData = {
+                devId: $scope.devId
+            };
+            wss.sendEvent(pipeconfRequest, requestData);
+        }
+    }
+
+    function pipeConfRespCb(data) {
+        ls.stop();
+        if (!data.hasOwnProperty(pipeconf)) {
+            $scope.pipeconf = null;
+            return;
+        }
+        $scope.pipeconf = data[pipeconf];
+        $scope.pipelineModel = data[pipelineModel];
+        $scope.$apply();
+    }
+
+    function noPipeconfRespCb(data) {
+        ls.stop();
+        $scope.pipeconf = null;
+        $scope.pipelineModel = null;
+        $scope.$apply();
+    }
+
+    function viewDestroy() {
+        wss.unbindHandlers(handlers);
+        $interval.cancel(refreshPromise);
+        refreshPromise = null;
+        ls.stop();
+    }
+
+    function headerSelectCb($event, header) {
+        if ($scope.selectedId !== null &&
+            $scope.selectedId.type === 'header' &&
+            $scope.selectedId.name === header.name) {
+
+            // Hide the panel when select same row
+            closePanel();
+            return;
+        }
+
+        $scope.selectedId = {
+            type: 'header',
+            name: header.name,
+        };
+
+        var subtitles = [
+            {
+                label: 'Header Type: ',
+                value: header.type.name,
+            },
+            {
+                label: 'Is metadata: ',
+                value: header.isMetadata,
+            },
+            {
+                label: 'Index: ',
+                value: header.index,
+            },
+        ];
+
+        var tables = [
+            {
+                title: 'Fields',
+                headers: ['Name', 'Bit width'],
+                data: header.type.fields,
+                noDataText: 'No header fields'
+            },
+        ];
+        populateDetailPanel(header.name, subtitles, tables);
+    }
+
+    function actionSelectCb($event, action) {
+        if ($scope.selectedId !== null &&
+            $scope.selectedId.type === 'action' &&
+            $scope.selectedId.name === action.name) {
+
+            // Hide the panel when select same row
+            closePanel();
+            return;
+        }
+
+        $scope.selectedId = {
+            type: 'action',
+            name: action.name,
+        };
+
+        var subtitles = [];
+        var tables = [
+            {
+                title: 'Parameters',
+                headers: ['Name', 'Bit width'],
+                data: action.params,
+                noDataText: 'No action parameters',
+            },
+        ];
+
+        populateDetailPanel(action.name, subtitles, tables);
+    }
+
+    function tableSelectCb($event, table) {
+        if ($scope.selectedId !== null &&
+            $scope.selectedId.type === 'table' &&
+            $scope.selectedId.name === table.name) {
+
+            // Hide the panel when select same row
+            closePanel();
+            return;
+        }
+
+        $scope.selectedId = {
+            type: 'table',
+            name: table.name,
+        };
+
+        var subtitles = [
+            {
+                label: 'Max Size: ',
+                value: table.maxSize,
+            },
+            {
+                label: 'Has counters: ',
+                value: table.hasCounters,
+            },
+            {
+                label: 'Support Aging: ',
+                value: table.supportAging,
+            },
+        ];
+
+        var matchFields = table.matchFields.map(function(mp) {
+            return {
+                name: mp.header.name + '.' + mp.field.name,
+                bitWidth: mp.field.bitWidth,
+                matchType: mp.matchType,
+            }
+        });
+
+        var tables = [
+            {
+                title: 'Match fields',
+                headers: ['Name', 'Bit width', 'Match type'],
+                data: matchFields,
+                noDataText: 'No match fields'
+            },
+            {
+                title: 'Actions',
+                headers: ['Name'],
+                data: table.actions,
+                noDataText: 'No actions'
+            },
+        ];
+
+        populateDetailPanel(table.name, subtitles, tables);
+    }
+
+    function closePanel() {
+        if (detailsPanel.isVisible()) {
+
+            detailsPanel.hide();
+
+            // Avoid Angular inprog error
+            $timeout(function() {
+                $scope.selectedId = null;
+            }, 0);
+            return true;
+        }
+        return false;
+    }
+
+    function populateDetailTable(tableContainer, table) {
+        var tableTitle = table.title;
+        var tableData = table.data;
+        var tableHeaders = table.headers;
+        var noDataText = table.noDataText;
+
+        tableContainer.append('h2').classed('detail-panel-bottom-title', true).text(tableTitle);
+
+        var detailPanelTable = tableContainer.append('table').classed('detail-panel-table', true);
+        var headerTr = detailPanelTable.append('tr').classed('detail-panel-table-header', true);
+
+        tableHeaders.forEach(function(h) {
+            headerTr.append('th').text(h);
+        });
+
+        if (tableData.length === 0) {
+            var row = detailPanelTable.append('tr').classed('detail-panel-table-row', true);
+            row.append('td')
+                .classed('detail-panel-table-col no-data', true)
+                .attr('colspan', tableHeaders.length)
+                .text(noDataText);
+        }
+
+        tableData.forEach(function(data) {
+            var row = detailPanelTable.append('tr').classed('detail-panel-table-row', true);
+            if (fs.isS(data)) {
+                row.append('td').classed('detail-panel-table-col', true).text(data);
+            } else {
+                Object.keys(data).forEach(function(k) {
+                    row.append('td').classed('detail-panel-table-col', true).text(data[k]);
+                });
+            }
+        });
+
+        tableContainer.append('hr');
+    }
+
+    function populateDetailTables(tableContainer, tables) {
+        tables.forEach(function(table) {
+            populateDetailTable(tableContainer, table);
+        })
+    }
+
+    function populateDetailPanel(topTitle, topSubtitles, tables) {
+        dps.empty();
+        dps.addContainers();
+        dps.addCloseButton(closePanel);
+
+        var top = dps.top();
+        top.append('h2').classed('detail-panel-header', true).text(topTitle);
+        topSubtitles.forEach(function(st) {
+            var typeText = top.append('div').classed('top-info', true);
+            typeText.append('p').classed('label', true).text(st.label);
+            typeText.append('p').classed('value', true).text(st.value);
+        });
+
+        var bottom = dps.bottom();
+        var bottomHeight = pHeight - pTopHeight - 60;
+        bottom.style('height', bottomHeight + 'px');
+        populateDetailTables(bottom, tables);
+
+        detailsPanel.width(pWidth);
+        detailsPanel.show();
+        resizeDetailPanel();
+    }
+
+    function heightCalc() {
+        pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
+            + mast.mastHeight() + topPdg;
+        wSize = fs.windowSize(pStartY);
+        pHeight = wSize.height - 20;
+    }
+
+    function resizeDetailPanel() {
+        if (detailsPanel.isVisible()) {
+            heightCalc();
+            var bottomHeight = pHeight - pTopHeight - 60;
+            d3.select('.bottom').style('height', bottomHeight + 'px');
+            detailsPanel.height(pHeight);
+        }
+    }
+
+    angular.module('ovPipeconf', [])
+        .controller('OvPipeconfCtrl',
+            ['$log', '$scope', '$location', '$interval', '$timeout', 'FnService', 'NavService', 'WebSocketService',
+                'LoadingService', 'PanelService', 'MastService', 'IconService', 'DetailsPanelService',
+                function (_$log_, _$scope_, _$loc_, _$interval_, _$timeout_, _fs_,
+                          _ns_, _wss_, _ls_, _ps_, _mast_, _is_, _dps_) {
+                    $log = _$log_;
+                    $scope = _$scope_;
+                    $loc = _$loc_;
+                    $interval = _$interval_;
+                    $timeout = _$timeout_;
+                    fs = _fs_;
+                    ns = _ns_;
+                    wss = _wss_;
+                    ls = _ls_;
+                    ps = _ps_;
+                    mast = _mast_;
+                    is = _is_;
+                    dps = _dps_;
+
+                    $scope.deviceTip = 'Show device table';
+                    $scope.flowTip = 'Show flow view for this device';
+                    $scope.portTip = 'Show port view for this device';
+                    $scope.groupTip = 'Show group view for this device';
+                    $scope.meterTip = 'Show meter view for selected device';
+                    $scope.pipeconfTip = 'Show pipeconf view for selected device';
+
+                    var params = $loc.search();
+                    if (params.hasOwnProperty(devId)) {
+                        $scope.devId = params[devId];
+                    }
+                    $scope.nav = function (path) {
+                        if ($scope.devId) {
+                            ns.navTo(path, { devId: $scope.devId });
+                        }
+                    };
+                    handlers = {
+                        pipeConfResponse: pipeConfRespCb,
+                        noPipeconfResp: noPipeconfRespCb,
+                        invalidDevId: noPipeconfRespCb,
+                    };
+                    wss.bindHandlers(handlers);
+                    $scope.$on('$destroy', viewDestroy);
+
+                    $scope.autoRefresh = true;
+                    fetchPipeconfData();
+
+                    // On click callbacks, initialize select id
+                    $scope.selectedId = null;
+                    $scope.headerSelectCb = headerSelectCb;
+                    $scope.actionSelectCb = actionSelectCb;
+                    $scope.tableSelectCb = tableSelectCb;
+
+                    // Make them collapsable
+                    $scope.collapsePipeconf = false;
+                    $scope.collapseHeaders = false;
+                    $scope.collapseActions = false;
+                    $scope.collapseTables = false;
+
+                    $scope.mapToNames = function(data) {
+                        return data.map(function(d) {
+                            return d.name;
+                        });
+                    };
+
+                    $scope.matMatchFields = function(matchFields) {
+                        return matchFields.map(function(mf) {
+                            return mf.header.name + '.' + mf.field.name;
+                        });
+                    };
+
+                    refreshPromise = $interval(function() {
+                        fetchPipeconfData();
+                    }, refreshRate);
+
+                    $log.log('OvPipeconfCtrl has been created');
+                }])
+        .directive('autoHeight', ['$window', 'FnService',
+            function($window, fs) {
+                return function(scope, element) {
+                    var autoHeightElem = d3.select(element[0]);
+
+                    scope.$watchCollection(function() {
+                        return {
+                            h: $window.innerHeight
+                        };
+                    }, function() {
+                        var wsz = fs.windowSize(140, 0);
+                        autoHeightElem.style('height', wsz.height + 'px');
+                    });
+                };
+            }
+        ])
+        .directive('pipeconfViewDetailPanel', ['$rootScope', '$window', '$timeout', 'KeyService',
+            function($rootScope, $window, $timeout, ks) {
+                function createDetailsPanel() {
+                    detailsPanel = dps.create(pName, {
+                        width: wSize.width,
+                        margin: 0,
+                        hideMargin: 0,
+                        scope: $scope,
+                        keyBindings: keyBindings,
+                        nameChangeRequest: null,
+                    });
+                    $scope.hidePanel = function () { detailsPanel.hide(); };
+                    detailsPanel.hide();
+                }
+
+                function initPanel() {
+                    heightCalc();
+                    createDetailsPanel();
+                }
+
+                return function(scope) {
+                    var unbindWatch;
+                    // Safari has a bug where it renders the fixed-layout table wrong
+                    // if you ask for the window's size too early
+                    if (scope.onos.browser === 'safari') {
+                        $timeout(initPanel);
+                    } else {
+                        initPanel();
+                    }
+
+                    // if the panelData changes
+                    scope.$watch('panelData', function () {
+                        if (!fs.isEmptyObject(scope.panelData)) {
+                            // populateDetails(scope.panelData);
+                            detailsPanel.show();
+                        }
+                    });
+
+                    // if the window size changes
+                    unbindWatch = $rootScope.$watchCollection(
+                        function () {
+                            return {
+                                h: $window.innerHeight,
+                                w: $window.innerWidth,
+                            };
+                        }, function () {
+                            resizeDetailPanel();
+                        }
+                    );
+
+                    scope.$on('$destroy', function () {
+                        unbindWatch();
+                        ks.unbindKeys();
+                        ps.destroyPanel(pName);
+                    });
+                };
+            }
+        ]);
+}());
diff --git a/web/gui/src/main/webapp/app/view/port/port.html b/web/gui/src/main/webapp/app/view/port/port.html
index 753ca22..f6e9b2e 100644
--- a/web/gui/src/main/webapp/app/view/port/port.html
+++ b/web/gui/src/main/webapp/app/view/port/port.html
@@ -48,6 +48,11 @@
                  icon icon-id="meterTable" icon-size="42"
                  tooltip tt-msg="meterTip"
                  ng-click="nav('meter')"></div>
+
+            <div class="active"
+                 icon icon-id="pipeconfTable" icon-size="42"
+                 tooltip tt-msg="pipeconfTip"
+                 ng-click="nav('pipeconf')"></div>
         </div>
 
         <div class="search">
diff --git a/web/gui/src/main/webapp/app/view/port/port.js b/web/gui/src/main/webapp/app/view/port/port.js
index 7aad374..5bf7871 100644
--- a/web/gui/src/main/webapp/app/view/port/port.js
+++ b/web/gui/src/main/webapp/app/view/port/port.js
@@ -90,6 +90,7 @@
                 $scope.flowTip = 'Show flow view for this device';
                 $scope.groupTip = 'Show group view for this device';
                 $scope.meterTip = 'Show meter view for selected device';
+                $scope.pipeconfTip = 'Show pipeconf view for selected device';
                 $scope.toggleDeltaTip = 'Toggle port delta statistics';
                 $scope.toggleNZTip = 'Toggle non zero port statistics';
 
diff --git a/web/gui/src/main/webapp/app/view/settings/settings.css b/web/gui/src/main/webapp/app/view/settings/settings.css
index 6f05c63..d57df80 100644
--- a/web/gui/src/main/webapp/app/view/settings/settings.css
+++ b/web/gui/src/main/webapp/app/view/settings/settings.css
@@ -38,14 +38,14 @@
 
 #settings-details-panel .close-btn {
     position: absolute;
-    right: 26px;
-    top: 26px;
+    right: 12px;
+    top: 12px;
     cursor: pointer;
 }
 
 #settings-details-panel .top .settings-title-1 {
     width: 90%;
-    height: 28px;
+    height: 20px;
     font-size: 14pt;
     font-weight: lighter;
     overflow: hidden;
@@ -58,7 +58,7 @@
 #settings-details-panel .top .settings-title-2 {
     width: 90%;
     height: 52px;
-    font-size: 20pt;
+    font-size: 18pt;
     font-weight: lighter;
     overflow: hidden;
     white-space: nowrap;
@@ -70,7 +70,7 @@
 #settings-details-panel td.label {
     font-weight: bold;
     text-align: right;
-    font-size: 12pt;
+    font-size: 10pt;
     padding-right: 6px;
     vertical-align: top;
     width: 120px;
diff --git a/web/gui/src/main/webapp/app/view/settings/settings.html b/web/gui/src/main/webapp/app/view/settings/settings.html
index 8e23617..2346858 100644
--- a/web/gui/src/main/webapp/app/view/settings/settings.html
+++ b/web/gui/src/main/webapp/app/view/settings/settings.html
@@ -16,8 +16,8 @@
                 <tr>
                     <td colId="component" sortable col-width="260px">Component </td>
                     <td colId="prop" sortable col-width="260px">Property </td>
-                    <td colId="type" sortable col-width="100px">Type </td>
-                    <td colId="value" sortable col-width="100px">Value </td>
+                    <td colId="type" sortable col-width="80px">Type </td>
+                    <td colId="value" sortable col-width="260px">Value </td>
                     <td colId="desc">Description </td>
                 </tr>
             </table>
diff --git a/web/gui/src/main/webapp/app/view/settings/settings.js b/web/gui/src/main/webapp/app/view/settings/settings.js
index 78ae829..caca00c 100644
--- a/web/gui/src/main/webapp/app/view/settings/settings.js
+++ b/web/gui/src/main/webapp/app/view/settings/settings.js
@@ -38,7 +38,7 @@
         topPdg = 60,
         propOrder = ['fqComponent', 'prop', 'type', 'value', 'defValue', 'desc'],
         friendlyProps = [
-            'Component', 'Property', 'Type', 'Value', 'Default Value',
+            'Component', 'Property', 'Type', 'Value', 'Default',
             'Description',
         ];
 
@@ -68,7 +68,7 @@
     }
 
     function addCloseBtn(div) {
-        is.loadEmbeddedIcon(div, 'close', 26);
+        is.loadEmbeddedIcon(div, 'close', 20);
         div.on('click', closePanel);
     }
 
@@ -110,7 +110,7 @@
     }
 
     function populateTop(details) {
-        var propsBody = top.select('.settings-props').append('tbody');
+        var propsBody = top.select('.settings-props table').append('tbody');
 
         top.select('.settings-title-1').text(details.component);
         top.select('.settings-title-2').text(details.prop);
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index b4073a0..6f77bed 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -174,26 +174,8 @@
         lu[id] = d;
         updateNodes();
 
-        function mkLinkKey(devId, devPort) {
-            return id + '/0-' + devId + '/' + devPort;
-        }
-
         // need to handle possible multiple links (multi-homed host)
-        d.links = [];
-        data.allCps.forEach(function (cp) {
-            var linkData = {
-                key: mkLinkKey(cp.device, cp.port),
-                dst: cp.device,
-                dstPort: cp.port,
-            };
-            d.links.push(linkData);
-
-            var lnk = tms.createHostLink(id, cp.device, cp.port);
-            if (lnk) {
-                network.links.push(lnk);
-                lu[linkData.key] = lnk;
-            }
-        });
+        createHostLinks(data.allCps, d);
 
         if (d.links.length) {
             updateLinks();
@@ -213,15 +195,30 @@
         }
     }
 
+    function createHostLinks(cps, model) {
+        model.links = [];
+        cps.forEach(function (cp) {
+            var linkData = {
+                key: model.id + '/0-' + cp.device + '/' + cp.port,
+                dst: cp.device,
+                dstPort: cp.port,
+            };
+            model.links.push(linkData);
+
+            var lnk = tms.createHostLink(model.id, cp.device, cp.port);
+            if (lnk) {
+                network.links.push(lnk);
+                lu[linkData.key] = lnk;
+            }
+        });
+    }
+
     function moveHost(data) {
         var id = data.id,
-            d = lu[id],
-            lnk;
+            d = lu[id];
 
         if (d) {
-            // first remove the old host link
-            // FIXME: what if the host has multiple links??????
-            removeLinkElement(d.linkData);
+            removeAllLinkElements(d.links);
 
             // merge new data
             angular.extend(d, data);
@@ -229,19 +226,8 @@
                 sendUpdateMeta(d);
             }
 
-            // now create a new host link
-            // TODO: verify this is the APPROPRIATE host link
-            lnk = tms.createHostLink(id, data.cp.device, data.cp.port);
-            if (lnk) {
-                network.links.push(lnk);
-                lu[lnk.key] = lnk;
-
-                d.links.push({
-                    key: id + '/0-' + cp.device + '/' + cp.port,
-                    dst: data.cp.device,
-                    dstPort: data.cp.port,
-                });
-            }
+            // now create new host link(s)
+            createHostLinks(data.allCps, d);
 
             updateNodes();
             updateLinks();
@@ -391,6 +377,12 @@
         }
     }
 
+    function removeAllLinkElements(links) {
+        links.forEach(function (lnk) {
+            removeLinkElement(lnk);
+        });
+    }
+
     function removeLinkElement(d) {
         var idx = fs.find(d.key, network.links, 'key'),
             removed;
@@ -405,12 +397,8 @@
     }
 
     function removeHostElement(d, upd) {
-        // first, remove associated hostLink...
-        removeLinkElement(d.linkData);
-
-        // remove hostLink bindings
-        delete lu[d.ingress];
-        delete lu[d.egress];
+        // first, remove associated hostLink(s)...
+        removeAllLinkElements(d.links);
 
         // remove from lookup cache
         delete lu[d.id];
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css b/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css
index 8ae6c2e..03138c1 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css
@@ -258,7 +258,7 @@
 
 #ov-topo2 svg .node.host text {
     stroke: none;
-    font: 9pt sans-serif;
+    font-size: 13px;
     fill: #846;
 }
 
@@ -266,6 +266,7 @@
     stroke: #a3a596;
     fill: #e0dfd6;
 }
+
 #ov-topo2 svg .node.host.selected .hostIcon > circle {
     stroke-width: 2.0;
     stroke: #009fdb;
@@ -275,6 +276,10 @@
     fill: #3c3a3a;
 }
 
+#ov-topo2 svg .node.host rect {
+    fill: #ffffff;
+}
+
 /* --- Topo Links --- */
 
 #ov-topo2 svg .link {
@@ -633,7 +638,7 @@
 
 .dark #ov-topo2 svg .node.host text {
     stroke: none;
-    font: 9pt sans-serif;
+    font-size: 13px;
     fill: #ad5781;
 }
 
@@ -641,6 +646,11 @@
     stroke: #a3a596;
     fill: #8f8272;
 }
+
+.dark #ov-topo2 svg .node.host rect {
+    fill: #525660;
+}
+
 .dark #ov-topo2 svg .node.host.selected .hostIcon > circle {
     stroke-width: 2.0;
     stroke: #009fdb;
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
index bd04c7b..4247f64 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
@@ -27,6 +27,7 @@
     var hostIconDim = 15,
         hostIconDimMin = 8,
         hostIconDimMax = 15,
+        labelPadding = 20,
         remappedDeviceTypes = {};
 
     function createHostCollection(data, region) {
@@ -90,6 +91,20 @@
 
                     return labels[idx];
                 },
+                updateLabel: function () {
+                    var node = this.el,
+                        label = this.trimLabel(this.label()),
+                        labelWidth;
+
+                    node.select('text').text(label);
+                    labelWidth = !_.isEmpty(this.label()) ? this.computeLabelWidth(node) + (labelPadding * 2) : 0;
+
+                    node.select('rect')
+                        .transition()
+                        .attr({
+                            width: labelWidth,
+                        });
+                },
                 setScale: function () {
 
                     if (!this.el) return;
@@ -114,13 +129,21 @@
                     var node = d3.select(el),
                         icon = this.icon(),
                         textDy = 5,
-                        textDx = (hostIconDim * 2) + 20;
+                        textDx = (hostIconDim * 2);
 
                     this.el = node;
 
                     var g = node.append('g')
                         .attr('class', 'svgIcon hostIcon');
 
+                    // Add Label background to host
+
+                    var rect = g.append('rect').attr({
+                        width: 0,
+                        height: hostIconDim * 2,
+                        y: - hostIconDim,
+                    });
+
                     g.append('circle').attr('r', hostIconDim);
 
                     var glyphSize = hostIconDim * 1.5;
@@ -138,11 +161,14 @@
                         .text(labelText)
                         .attr('dy', textDy)
                         .attr('dx', textDx)
-                        .attr('text-anchor', 'middle');
+                        .attr('text-anchor', 'left');
 
                     this.setScale();
                     this.setUpEvents();
                     this.setVisibility();
+
+                    var labelWidth = !_.isEmpty(this.label()) ? this.computeLabelWidth(node) + (labelPadding * 2) : 0;
+                    rect.attr({ width: labelWidth });
                 },
             });
 
diff --git a/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js b/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js
index 73c1d6c..b600a10 100644
--- a/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js
@@ -21,7 +21,7 @@
 describe('factory: fw/svg/glyph.js', function() {
     var $log, fs, gs, d3Elem, svg;
 
-    var numBaseGlyphs = 105,
+    var numBaseGlyphs = 106,
         vbBird = '352 224 113 112',
         vbGlyph = '0 0 110 110',
         vbBadge = '0 0 10 10',
diff --git a/web/gui/src/main/webapp/tests/package-lock.json b/web/gui/src/main/webapp/tests/package-lock.json
index b5f7f3c..4eb860d 100644
--- a/web/gui/src/main/webapp/tests/package-lock.json
+++ b/web/gui/src/main/webapp/tests/package-lock.json
@@ -9,6 +9,16 @@
       "integrity": "sha1-Vdvr7wrZFVOC2enT5JfBNyNFtEo=",
       "dev": true
     },
+    "debug": {
+      "version": "2.6.8",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
+      "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw="
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+    },
     "jasmine-core": {
       "version": "2.6.4",
       "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.6.4.tgz",
@@ -3514,6 +3524,16 @@
         }
       }
     },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "nan": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
+      "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY="
+    },
     "phantomjs": {
       "version": "2.1.7",
       "resolved": "https://registry.npmjs.org/phantomjs/-/phantomjs-2.1.7.tgz",
@@ -4261,6 +4281,21 @@
           }
         }
       }
+    },
+    "typedarray-to-buffer": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.2.tgz",
+      "integrity": "sha1-EBezLZhP9VbroQD1AViauhrOLgQ="
+    },
+    "websocket": {
+      "version": "1.0.24",
+      "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.24.tgz",
+      "integrity": "sha1-dJA+dfJUW2suHeFCW8HJBZF6GJA="
+    },
+    "yaeti": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
+      "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc="
     }
   }
 }
diff --git a/web/gui/src/main/webapp/tests/package.json b/web/gui/src/main/webapp/tests/package.json
index 59b8102..c0ae7c3 100644
--- a/web/gui/src/main/webapp/tests/package.json
+++ b/web/gui/src/main/webapp/tests/package.json
@@ -26,6 +26,7 @@
     "karma-mocha-reporter": "^1.1.3",
     "karma-ng-html2js-preprocessor": "^0.2.0",
     "karma-phantomjs-launcher": "^0.2.1",
-    "phantomjs": "^2.1.3"
+    "phantomjs": "^2.1.3",
+    "websocket": "^1.0.24"
   }
 }
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_10_removeHost.json b/web/gui/src/test/_karma/ev/dualHomed/ev_10_removeHost.json
new file mode 100644
index 0000000..9f7d6f5
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_10_removeHost.json
@@ -0,0 +1,35 @@
+{
+  "event": "removeHost",
+  "payload": {
+    "id": "3C:FD:FE:9E:6E:D0/None",
+    "type": "endstation",
+    "cp": {
+      "device": "of:0000000000000206",
+      "port": 54
+    },
+    "allCps": [
+      {
+        "device": "of:0000000000000206",
+        "port": 54
+      },
+      {
+        "device": "of:0000000000000205",
+        "port": 77
+      }
+    ],
+    "labels": [
+      "fe80::3efd:feff:fe9e:6ed0",
+      "fe80::3efd:feff:fe9e:6ed0",
+      "3C:FD:FE:9E:6E:D0"
+    ],
+    "props": {},
+    "metaUi": {
+      "x": 771.2614872361207,
+      "y": 870.9614439514203,
+      "equivLoc": {
+        "lng": -86.82423167773878,
+        "lat": 27.650078076056364
+      }
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_11_updateDevice.json b/web/gui/src/test/_karma/ev/dualHomed/ev_11_updateDevice.json
new file mode 100644
index 0000000..d5dd11b
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_11_updateDevice.json
@@ -0,0 +1,36 @@
+{
+  "event": "updateDevice",
+  "payload": {
+    "id": "of:0000000000000206",
+    "type": "switch",
+    "online": true,
+    "master": "10.128.0.217",
+    "labels": [
+      "",
+      "of:0000000000000206",
+      "of:0000000000000206"
+    ],
+    "props": {
+      "managementAddress": "10.128.0.206",
+      "protocol": "OF_13",
+      "latitude": "34.2403",
+      "name": "of:0000000000000206",
+      "locType": "geo",
+      "channelId": "10.128.0.206:52972",
+      "longitude": "-87.0413"
+    },
+    "location": {
+      "locType": "geo",
+      "latOrY": 34.2403,
+      "longOrX": -87.0413
+    },
+    "metaUi": {
+      "x": 764.7366062749779,
+      "y": 639.7691957045747,
+      "equivLoc": {
+        "lng": -87.0413,
+        "lat": 34.2403
+      }
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_12_addHost.json b/web/gui/src/test/_karma/ev/dualHomed/ev_12_addHost.json
new file mode 100644
index 0000000..4359989
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_12_addHost.json
@@ -0,0 +1,31 @@
+{
+  "event": "addHost",
+  "payload": {
+    "id": "3C:FD:FE:9E:6E:D0/None",
+    "type": "endstation",
+    "cp": {
+      "device": "of:0000000000000206",
+      "port": 54
+    },
+    "allCps": [
+      {
+        "device": "of:0000000000000206",
+        "port": 54
+      }
+    ],
+    "labels": [
+      "10.0.5.55",
+      "10.0.5.55",
+      "3C:FD:FE:9E:6E:D0"
+    ],
+    "props": {},
+    "metaUi": {
+      "x": 771.2614872361207,
+      "y": 870.9614439514203,
+      "equivLoc": {
+        "lng": -86.82423167773878,
+        "lat": 27.650078076056364
+      }
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_13_updateHost.json b/web/gui/src/test/_karma/ev/dualHomed/ev_13_updateHost.json
new file mode 100644
index 0000000..c6357d2
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_13_updateHost.json
@@ -0,0 +1,35 @@
+{
+  "event": "updateHost",
+  "payload": {
+    "id": "3C:FD:FE:9E:6E:D0/None",
+    "type": "endstation",
+    "cp": {
+      "device": "of:0000000000000206",
+      "port": 54
+    },
+    "prevCp": {
+      "device": "of:0000000000000206",
+      "port": 54
+    },
+    "allCps": [
+      {
+        "device": "of:0000000000000206",
+        "port": 54
+      }
+    ],
+    "labels": [
+      "fe80::3efd:feff:fe9e:6ed0",
+      "fe80::3efd:feff:fe9e:6ed0",
+      "3C:FD:FE:9E:6E:D0"
+    ],
+    "props": {},
+    "metaUi": {
+      "x": 771.2614872361207,
+      "y": 870.9614439514203,
+      "equivLoc": {
+        "lng": -86.82423167773878,
+        "lat": 27.650078076056364
+      }
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_1_addInstance.json b/web/gui/src/test/_karma/ev/dualHomed/ev_1_addInstance.json
new file mode 100644
index 0000000..daf074b
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_1_addInstance.json
@@ -0,0 +1,15 @@
+{
+  "event": "addInstance",
+  "payload": {
+    "id": "127.0.0.1",
+    "ip": "127.0.0.1",
+    "online": true,
+    "ready": true,
+    "uiAttached": true,
+    "switches": 2,
+    "labels": [
+      "127.0.0.1",
+      "127.0.0.1"
+    ]
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_2_addDevice_01.json b/web/gui/src/test/_karma/ev/dualHomed/ev_2_addDevice_01.json
new file mode 100644
index 0000000..e1aeffc
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_2_addDevice_01.json
@@ -0,0 +1,36 @@
+{
+  "event": "addDevice",
+  "payload": {
+    "id": "of:0000000000000205",
+    "type": "switch",
+    "online": true,
+    "master": "10.128.0.218",
+    "labels": [
+      "",
+      "of:0000000000000205",
+      "of:0000000000000205"
+    ],
+    "props": {
+      "managementAddress": "10.128.0.205",
+      "protocol": "OF_13",
+      "latitude": "34.1202",
+      "name": "of:0000000000000205",
+      "locType": "geo",
+      "channelId": "10.128.0.205:46174",
+      "longitude": "-90.4754"
+    },
+    "location": {
+      "locType": "geo",
+      "latOrY": 34.1202,
+      "longOrX": -90.4754
+    },
+    "metaUi": {
+      "x": 661.5106105366713,
+      "y": 644.1330453927671,
+      "equivLoc": {
+        "lng": -90.4754,
+        "lat": 34.12019999999999
+      }
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_3_addDevice_02.json b/web/gui/src/test/_karma/ev/dualHomed/ev_3_addDevice_02.json
new file mode 100644
index 0000000..3163d74
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_3_addDevice_02.json
@@ -0,0 +1,19 @@
+{
+  "event": "addDevice",
+  "payload": {
+    "id": "of:0000000000000206",
+    "type": "switch",
+    "online": true,
+    "master": "10.128.0.217",
+    "labels": [
+      "",
+      "of:0000000000000206",
+      "of:0000000000000206"
+    ],
+    "props": {
+      "managementAddress": "192.168.36.101",
+      "protocol": "OF_13",
+      "channelId": "192.168.36.101:55712"
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_4_addLink_1to2.json b/web/gui/src/test/_karma/ev/dualHomed/ev_4_addLink_1to2.json
new file mode 100644
index 0000000..e306ce1
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_4_addLink_1to2.json
@@ -0,0 +1,14 @@
+{
+  "event": "addLink",
+  "payload": {
+    "id": "of:0000000000000205/2-of:0000000000000206/2",
+    "type": "direct",
+    "expected": false,
+    "online": true,
+    "linkWidth": 1.2,
+    "src": "of:0000000000000205",
+    "srcPort": "2",
+    "dst": "of:0000000000000206",
+    "dstPort": "2"
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_5_addLink_2to1.json b/web/gui/src/test/_karma/ev/dualHomed/ev_5_addLink_2to1.json
new file mode 100644
index 0000000..69b8dc2
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_5_addLink_2to1.json
@@ -0,0 +1,14 @@
+{
+  "event": "addLink",
+  "payload": {
+    "id": "of:0000000000000206/2-of:0000000000000205/2",
+    "type": "direct",
+    "expected": false,
+    "online": true,
+    "linkWidth": 1.2,
+    "src": "of:0000000000000206",
+    "srcPort": "2",
+    "dst": "of:0000000000000205",
+    "dstPort": "2"
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_6_addHost_to1.json b/web/gui/src/test/_karma/ev/dualHomed/ev_6_addHost_to1.json
new file mode 100644
index 0000000..2e9a33d
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_6_addHost_to1.json
@@ -0,0 +1,35 @@
+{
+  "event": "addHost",
+  "payload": {
+    "id": "3C:FD:FE:9E:6E:D0/None",
+    "type": "endstation",
+    "cp": {
+      "device": "of:0000000000000206",
+      "port": 54
+    },
+    "prevCp": {
+      "device": "of:0000000000000205",
+      "port": 54
+    },
+    "allCps": [
+      {
+        "device": "of:0000000000000206",
+        "port": 54
+      }
+    ],
+    "labels": [
+      "fe80::3efd:feff:fe9e:6ed0",
+      "fe80::3efd:feff:fe9e:6ed0",
+      "3C:FD:FE:9E:6E:D0"
+    ],
+    "props": {},
+    "metaUi": {
+      "x": 771.2614872361207,
+      "y": 870.9614439514203,
+      "equivLoc": {
+        "lng": -86.82423167773878,
+        "lat": 27.650078076056364
+      }
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_7_updateDevice_01.json b/web/gui/src/test/_karma/ev/dualHomed/ev_7_updateDevice_01.json
new file mode 100644
index 0000000..6c61921
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_7_updateDevice_01.json
@@ -0,0 +1,28 @@
+{
+  "event": "updateDevice",
+  "payload": {
+    "id": "of:0000000000000205",
+    "type": "switch",
+    "online": true,
+    "master": "10.128.0.218",
+    "labels": [
+      "",
+      "SW-A",
+      "of:0000000000000001"
+    ],
+    "props": {
+      "managementAddress": "192.168.36.101",
+      "protocol": "OF_13",
+      "latitude": "43.37",
+      "name": "SW-A",
+      "locType": "geo",
+      "channelId": "192.168.36.101:55710",
+      "longitude": "-104.6572"
+    },
+    "location": {
+      "locType": "geo",
+      "latOrY": 43.37,
+      "longOrX": -104.6572
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_8_updateDevice_02.json b/web/gui/src/test/_karma/ev/dualHomed/ev_8_updateDevice_02.json
new file mode 100644
index 0000000..2801fe8
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_8_updateDevice_02.json
@@ -0,0 +1,36 @@
+{
+  "event": "updateDevice",
+  "payload": {
+    "id": "of:0000000000000206",
+    "type": "switch",
+    "online": true,
+    "master": "10.128.0.217",
+    "labels": [
+      "",
+      "SW-B",
+      "of:0000000000000206"
+    ],
+    "props": {
+      "managementAddress": "10.128.0.206",
+      "protocol": "OF_13",
+      "latitude": "34.2403",
+      "name": "of:0000000000000206",
+      "locType": "geo",
+      "channelId": "10.128.0.206:52972",
+      "longitude": "-87.0413"
+    },
+    "location": {
+      "locType": "geo",
+      "latOrY": 34.2403,
+      "longOrX": -87.0413
+    },
+    "metaUi": {
+      "x": 764.7366062749779,
+      "y": 639.7691957045747,
+      "equivLoc": {
+        "lng": -87.0413,
+        "lat": 34.2403
+      }
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_9_moveHost.json b/web/gui/src/test/_karma/ev/dualHomed/ev_9_moveHost.json
new file mode 100644
index 0000000..b70c0ff
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_9_moveHost.json
@@ -0,0 +1,39 @@
+{
+  "event": "moveHost",
+  "payload": {
+    "id": "3C:FD:FE:9E:6E:D0/None",
+    "type": "endstation",
+    "cp": {
+      "device": "of:0000000000000206",
+      "port": 54
+    },
+    "prevCp": {
+      "device": "of:0000000000000205",
+      "port": 54
+    },
+    "allCps": [
+      {
+        "device": "of:0000000000000206",
+        "port": 54
+      },
+      {
+        "device": "of:0000000000000205",
+        "port": 77
+      }
+    ],
+    "labels": [
+      "fe80::3efd:feff:fe9e:6ed0",
+      "fe80::3efd:feff:fe9e:6ed0",
+      "3C:FD:FE:9E:6E:D0"
+    ],
+    "props": {},
+    "metaUi": {
+      "x": 771.2614872361207,
+      "y": 870.9614439514203,
+      "equivLoc": {
+        "lng": -86.82423167773878,
+        "lat": 27.650078076056364
+      }
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/scenario.json b/web/gui/src/test/_karma/ev/dualHomed/scenario.json
new file mode 100644
index 0000000..4f86c5b
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/scenario.json
@@ -0,0 +1,26 @@
+{
+  "comments": [
+    "Single Host connected to two devices (Dual-Homed)."
+  ],
+  "title": "Dual-Homed Scenario",
+  "params": {
+    "lastAuto": 0
+  },
+  "description": [
+    "Simple sequence of events...",
+    "",
+    "1. add instance",
+    "2. add device [205]",
+    "3. add device [206]",
+    "4. add link [205] --> [206]",
+    "5. add link [206] --> [205]",
+    "6. add host (to [206])",
+    "7. update device [205]",
+    "8. update device [206]",
+    "9. move host (to include [205])",
+    "10. remove host",
+    "11. update device [206",
+    "12. add host (to [206])",
+    "13. update host"
+  ]
+}