Implemented P4Info browser
Change-Id: Iebff15ac60cf1121b29afce7f67b4c31c5a60f0e
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java
new file mode 100644
index 0000000..d8edaff
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4runtime.ctl;
+
+
+import com.google.common.collect.Maps;
+import com.google.protobuf.Message;
+import p4.config.P4InfoOuterClass.Action;
+import p4.config.P4InfoOuterClass.ActionProfile;
+import p4.config.P4InfoOuterClass.ControllerPacketMetadata;
+import p4.config.P4InfoOuterClass.Counter;
+import p4.config.P4InfoOuterClass.DirectCounter;
+import p4.config.P4InfoOuterClass.DirectMeter;
+import p4.config.P4InfoOuterClass.MatchField;
+import p4.config.P4InfoOuterClass.Meter;
+import p4.config.P4InfoOuterClass.P4Info;
+import p4.config.P4InfoOuterClass.Preamble;
+import p4.config.P4InfoOuterClass.Table;
+
+import java.util.Map;
+
+import static java.lang.String.format;
+
+/**
+ * Utility class to easily retrieve information from a P4Info protobuf message.
+ */
+final class P4InfoBrowser {
+
+ private final EntityBrowser<Table> tables = new EntityBrowser<>("table");
+ private final EntityBrowser<Action> actions = new EntityBrowser<>("action");
+ private final EntityBrowser<ActionProfile> actionProfiles = new EntityBrowser<>("action profile");
+ private final EntityBrowser<Counter> counters = new EntityBrowser<>("counter");
+ private final EntityBrowser<DirectCounter> directCounters = new EntityBrowser<>("direct counter");
+ private final EntityBrowser<Meter> meters = new EntityBrowser<>("meter");
+ private final EntityBrowser<DirectMeter> directMeters = new EntityBrowser<>("direct meter");
+ private final EntityBrowser<ControllerPacketMetadata> ctrlPktMetadatas =
+ new EntityBrowser<>("controller packet metadata");
+ private final Map<Integer, EntityBrowser<Action.Param>> actionParams = Maps.newHashMap();
+ private final Map<Integer, EntityBrowser<MatchField>> matchFields = Maps.newHashMap();
+
+ /**
+ * Creates a new browser for the given P4Info.
+ *
+ * @param p4info P4Info protobuf message
+ */
+ P4InfoBrowser(P4Info p4info) {
+ parseP4Info(p4info);
+ }
+
+ private void parseP4Info(P4Info p4info) {
+ p4info.getTablesList().forEach(
+ entity -> {
+ tables.addWithPreamble(entity.getPreamble(), entity);
+ // Index match fields.
+ int tableId = entity.getPreamble().getId();
+ String tableName = entity.getPreamble().getName();
+ EntityBrowser<MatchField> matchFieldBrowser = new EntityBrowser<>(format(
+ "match field for table '%s'", tableName));
+ entity.getMatchFieldsList().forEach(m -> matchFieldBrowser.add(m.getName(), m.getId(), m));
+ matchFields.put(tableId, matchFieldBrowser);
+ });
+
+ p4info.getActionsList().forEach(
+ entity -> {
+ actions.addWithPreamble(entity.getPreamble(), entity);
+ // Index action params.
+ int actionId = entity.getPreamble().getId();
+ String actionName = entity.getPreamble().getName();
+ EntityBrowser<Action.Param> paramBrowser = new EntityBrowser<>(format(
+ "param for action '%s'", actionName));
+ entity.getParamsList().forEach(p -> paramBrowser.add(p.getName(), p.getId(), p));
+ actionParams.put(actionId, paramBrowser);
+ });
+
+ p4info.getActionProfilesList().forEach(
+ entity -> actionProfiles.addWithPreamble(entity.getPreamble(), entity));
+
+ p4info.getCountersList().forEach(
+ entity -> counters.addWithPreamble(entity.getPreamble(), entity));
+
+ p4info.getDirectCountersList().forEach(
+ entity -> directCounters.addWithPreamble(entity.getPreamble(), entity));
+
+ p4info.getMetersList().forEach(
+ entity -> meters.addWithPreamble(entity.getPreamble(), entity));
+
+ p4info.getDirectMetersList().forEach(
+ entity -> directMeters.addWithPreamble(entity.getPreamble(), entity));
+
+ p4info.getControllerPacketMetadataList().forEach(
+ entity -> ctrlPktMetadatas.addWithPreamble(entity.getPreamble(), entity));
+ }
+
+ /**
+ * Returns a browser for tables.
+ *
+ * @return table browser
+ */
+ EntityBrowser<Table> tables() {
+ return tables;
+ }
+
+ /**
+ * Returns a browser for actions.
+ *
+ * @return action browser
+ */
+ EntityBrowser<Action> actions() {
+ return actions;
+ }
+
+ /**
+ * Returns a browser for action profiles.
+ *
+ * @return action profile browser
+ */
+ EntityBrowser<ActionProfile> actionProfiles() {
+ return actionProfiles;
+ }
+
+ /**
+ * Returns a browser for counters.
+ *
+ * @return counter browser
+ */
+ EntityBrowser<Counter> counters() {
+ return counters;
+ }
+
+ /**
+ * Returns a browser for direct counters.
+ *
+ * @return direct counter browser
+ */
+ EntityBrowser<DirectCounter> directCounters() {
+ return directCounters;
+ }
+
+ /**
+ * Returns a browser for meters.
+ *
+ * @return meter browser
+ */
+ EntityBrowser<Meter> meters() {
+ return meters;
+ }
+
+ /**
+ * Returns a browser for direct meters.
+ *
+ * @return table browser
+ */
+ EntityBrowser<DirectMeter> directMeters() {
+ return directMeters;
+ }
+
+ /**
+ * Returns a browser for controller packet metadata.
+ *
+ * @return controller packet metadata browser
+ */
+ EntityBrowser<ControllerPacketMetadata> controllerPacketMetadatas() {
+ return ctrlPktMetadatas;
+ }
+
+ /**
+ * Returns a browser for params of the given action.
+ *
+ * @param actionId action identifier
+ * @return action params browser
+ * @throws NotFoundException if the action cannot be found
+ */
+ EntityBrowser<Action.Param> actionParams(int actionId) throws NotFoundException {
+ // Throws exception if action id is not found.
+ actions.getById(actionId);
+ return actionParams.get(actionId);
+ }
+
+ /**
+ * Returns a browser for match fields of the given table.
+ *
+ * @param tableId table identifier
+ * @return controller packet metadata browser
+ * @throws NotFoundException if the table cannot be found
+ */
+ EntityBrowser<MatchField> matchFields(int tableId) throws NotFoundException {
+ // Throws exception if action id is not found.
+ tables.getById(tableId);
+ return matchFields.get(tableId);
+ }
+
+ /**
+ * Browser of P4Info entities.
+ *
+ * @param <T> protobuf message type
+ */
+ static final class EntityBrowser<T extends Message> {
+
+ private String entityName;
+ private final Map<String, T> names = Maps.newHashMap();
+ private final Map<String, T> aliases = Maps.newHashMap();
+ private final Map<Integer, T> ids = Maps.newHashMap();
+
+ private EntityBrowser(String entityName) {
+ this.entityName = entityName;
+ }
+
+ /**
+ * Adds the given entity identified by the given name and id.
+ *
+ * @param name entity name
+ * @param id entity id
+ * @param entity entity message
+ */
+ void add(String name, int id, T entity) {
+ names.put(name, entity);
+ ids.put(id, entity);
+ }
+
+ /**
+ * Adds the given entity identified by the given P4Info preamble.
+ *
+ * @param preamble P4Info preamble protobuf message
+ * @param entity entity message
+ */
+ void addWithPreamble(Preamble preamble, T entity) {
+ names.put(preamble.getName(), entity);
+ aliases.put(preamble.getName(), entity);
+ ids.put(preamble.getId(), entity);
+ }
+
+ /**
+ * Returns true if the P4Info defines an entity with such name, false otherwise.
+ *
+ * @param name entity name
+ * @return boolean
+ */
+ boolean hasName(String name) {
+ return names.containsKey(name);
+ }
+
+ /**
+ * Returns the entity identified by the given name, if present, otherwise, throws an exception.
+ *
+ * @param name entity name
+ * @return entity message
+ * @throws NotFoundException if the entity cannot be found
+ */
+ T getByName(String name) throws NotFoundException {
+ if (!hasName(name)) {
+ throw new NotFoundException(entityName, name);
+ }
+ return names.get(name);
+ }
+
+ /**
+ * Returns true if the P4Info defines an entity with such alias, false otherwise.
+ *
+ * @param alias entity alias
+ * @return boolean
+ */
+ boolean hasAlias(String alias) {
+ return aliases.containsKey(alias);
+ }
+
+ /**
+ * Returns the entity identified by the given alias, if present, otherwise, throws an exception.
+ *
+ * @param alias entity alias
+ * @return entity message
+ * @throws NotFoundException if the entity cannot be found
+ */
+ T getByAlias(String alias) throws NotFoundException {
+ if (!hasName(alias)) {
+ throw new NotFoundException(entityName, alias);
+ }
+ return aliases.get(alias);
+ }
+
+ /**
+ * Returns true if the P4Info defines an entity with such id, false otherwise.
+ *
+ * @param id entity id
+ * @return boolean
+ */
+ boolean hasId(int id) {
+ return ids.containsKey(id);
+ }
+
+ /**
+ * Returns the entity identified by the given id, if present, otherwise, throws an exception.
+ *
+ * @param id entity id
+ * @return entity message
+ * @throws NotFoundException if the entity cannot be found
+ */
+ T getById(int id) throws NotFoundException {
+ if (!hasId(id)) {
+ throw new NotFoundException(entityName, id);
+ }
+ return ids.get(id);
+ }
+ }
+
+ /**
+ * Signals tha an entity cannot be found in the P4Info.
+ */
+ public static class NotFoundException extends Exception {
+
+ NotFoundException(String entityName, String key) {
+ super(format("No such %s in P4Info with name/alias '%s'", entityName, key));
+ }
+
+ NotFoundException(String entityName, int id) {
+ super(format("No such %s in P4Info with id '%d'", entityName, id));
+ }
+ }
+}
diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TestP4Info.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TestP4Info.java
new file mode 100644
index 0000000..17263e4
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TestP4Info.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4runtime.ctl;
+
+import com.google.protobuf.ExtensionRegistry;
+import com.google.protobuf.TextFormat;
+import org.junit.Test;
+import p4.config.P4InfoOuterClass;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+public class TestP4Info {
+
+ private InputStream p4InfoStream = this.getClass().getResourceAsStream("/default.p4info");
+
+ @Test
+ public void testP4InfoBrowser() throws Exception {
+
+ InputStreamReader input = new InputStreamReader(p4InfoStream);
+ ExtensionRegistry extensionRegistry = ExtensionRegistry.getEmptyRegistry();
+ P4InfoOuterClass.P4Info.Builder builder = P4InfoOuterClass.P4Info.newBuilder();
+
+ builder.clear();
+ TextFormat.getParser().merge(input, extensionRegistry, builder);
+ P4InfoOuterClass.P4Info p4Info = builder.build();
+
+ P4InfoBrowser browser = new P4InfoBrowser(p4Info);
+
+ assertThat(browser.tables().hasName("table0"), is(true));
+ assertThat(browser.actions().hasName("set_egress_port"), is(true));
+
+ int tableId = browser.tables().getByName("table0").getPreamble().getId();
+ int actionId = browser.actions().getByName("set_egress_port").getPreamble().getId();
+
+ assertThat(browser.matchFields(tableId).hasName("standard_metadata.ingress_port"), is(true));
+ assertThat(browser.actionParams(actionId).hasName("port"), is(true));
+
+ // TODO: improve, assert browsing of other entities (counters, meters, etc.)
+ }
+}
diff --git a/protocols/p4runtime/ctl/src/test/resources/default.p4info b/protocols/p4runtime/ctl/src/test/resources/default.p4info
new file mode 100644
index 0000000..bd3649d
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/test/resources/default.p4info
@@ -0,0 +1,113 @@
+tables {
+ preamble {
+ id: 33617813
+ name: "table0"
+ alias: "table0"
+ }
+ match_fields {
+ id: 1
+ name: "standard_metadata.ingress_port"
+ bitwidth: 9
+ match_type: TERNARY
+ }
+ match_fields {
+ id: 2
+ name: "hdr.ethernet.dstAddr"
+ bitwidth: 48
+ match_type: TERNARY
+ }
+ match_fields {
+ id: 3
+ name: "hdr.ethernet.srcAddr"
+ bitwidth: 48
+ match_type: TERNARY
+ }
+ match_fields {
+ id: 4
+ name: "hdr.ethernet.etherType"
+ bitwidth: 16
+ match_type: TERNARY
+ }
+ action_refs {
+ id: 16794308
+ }
+ action_refs {
+ id: 16829080
+ }
+ action_refs {
+ id: 16793508
+ }
+ action_refs {
+ id: 16800567
+ annotations: "@defaultonly()"
+ }
+ direct_resource_ids: 301990488
+ size: 1024
+ with_entry_timeout: true
+}
+actions {
+ preamble {
+ id: 16794308
+ name: "set_egress_port"
+ alias: "set_egress_port"
+ }
+ params {
+ id: 1
+ name: "port"
+ bitwidth: 9
+ }
+}
+actions {
+ preamble {
+ id: 16829080
+ name: "send_to_cpu"
+ alias: "send_to_cpu"
+ }
+}
+actions {
+ preamble {
+ id: 16793508
+ name: "drop"
+ alias: "drop"
+ }
+}
+actions {
+ preamble {
+ id: 16800567
+ name: "NoAction"
+ alias: "NoAction"
+ }
+}
+counters {
+ preamble {
+ id: 302025528
+ name: "port_counters_control.egress_port_counter"
+ alias: "egress_port_counter"
+ }
+ spec {
+ unit: PACKETS
+ }
+ size: 254
+}
+counters {
+ preamble {
+ id: 301999025
+ name: "port_counters_control.ingress_port_counter"
+ alias: "ingress_port_counter"
+ }
+ spec {
+ unit: PACKETS
+ }
+ size: 254
+}
+direct_counters {
+ preamble {
+ id: 301990488
+ name: "table0_counter"
+ alias: "table0_counter"
+ }
+ spec {
+ unit: PACKETS
+ }
+ direct_table_id: 33617813
+}