[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',