[ONOS-6964][ONOS-6966] Add pipeconf codec and pipeconf view

Change-Id: Ie60a5451bcc24a27ede655c8230d82998ea4f3be
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/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/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java
index da56bfc..e2fe315 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
@@ -77,6 +77,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";
@@ -85,13 +86,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/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/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/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 6bc66d8..536b789 100644
--- a/web/gui/src/main/webapp/app/view/device/device.js
+++ b/web/gui/src/main/webapp/app/view/device/device.js
@@ -229,6 +229,7 @@
             $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;
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 3039983..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">
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 cd1e58a..71794bd 100644
--- a/web/gui/src/main/webapp/app/view/flow/flow.js
+++ b/web/gui/src/main/webapp/app/view/flow/flow.js
@@ -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/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/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',