ONOS-7066 ONOS-7067 PI abstractions refactoring and P4Info model parser

Includes changes previously reviewed in #15607, #15877, and #15955.

Change-Id: Ie2ff62e415f2099832ebfe05961a879b7b188fc3
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java
new file mode 100644
index 0000000..dd365f6
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java
@@ -0,0 +1,412 @@
+/*
+ * 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.p4runtime.model;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.protobuf.ExtensionRegistry;
+import com.google.protobuf.TextFormat;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionModel;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiActionParamModel;
+import org.onosproject.net.pi.model.PiActionProfileId;
+import org.onosproject.net.pi.model.PiActionProfileModel;
+import org.onosproject.net.pi.model.PiControlMetadataId;
+import org.onosproject.net.pi.model.PiControlMetadataModel;
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiCounterModel;
+import org.onosproject.net.pi.model.PiCounterType;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiMatchFieldModel;
+import org.onosproject.net.pi.model.PiMatchType;
+import org.onosproject.net.pi.model.PiMeterId;
+import org.onosproject.net.pi.model.PiMeterModel;
+import org.onosproject.net.pi.model.PiMeterType;
+import org.onosproject.net.pi.model.PiPacketOperationModel;
+import org.onosproject.net.pi.model.PiPacketOperationType;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.model.PiTableModel;
+import org.onosproject.net.pi.model.PiTableType;
+import p4.config.P4InfoOuterClass.Action;
+import p4.config.P4InfoOuterClass.ActionProfile;
+import p4.config.P4InfoOuterClass.ActionRef;
+import p4.config.P4InfoOuterClass.ControllerPacketMetadata;
+import p4.config.P4InfoOuterClass.Counter;
+import p4.config.P4InfoOuterClass.CounterSpec;
+import p4.config.P4InfoOuterClass.DirectCounter;
+import p4.config.P4InfoOuterClass.DirectMeter;
+import p4.config.P4InfoOuterClass.MatchField;
+import p4.config.P4InfoOuterClass.Meter;
+import p4.config.P4InfoOuterClass.MeterSpec;
+import p4.config.P4InfoOuterClass.P4Info;
+import p4.config.P4InfoOuterClass.Table;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+
+/**
+ * Parser of P4Info to PI pipeline model instances.
+ */
+public final class P4InfoParser {
+
+    private static final String PACKET_IN = "packet_in";
+    private static final String PACKET_OUT = "packet_out";
+
+    private static final Map<CounterSpec.Unit, PiCounterModel.Unit> COUNTER_UNIT_MAP =
+            new ImmutableMap.Builder<CounterSpec.Unit, PiCounterModel.Unit>()
+                    .put(CounterSpec.Unit.BYTES, PiCounterModel.Unit.BYTES)
+                    .put(CounterSpec.Unit.PACKETS, PiCounterModel.Unit.PACKETS)
+                    .put(CounterSpec.Unit.BOTH, PiCounterModel.Unit.PACKETS_AND_BYTES)
+                    // Don't map UNSPECIFIED as we don't support it at the moment.
+                    .build();
+
+    private static final Map<MeterSpec.Unit, PiMeterModel.Unit> METER_UNIT_MAP =
+            new ImmutableMap.Builder<MeterSpec.Unit, PiMeterModel.Unit>()
+                    .put(MeterSpec.Unit.BYTES, PiMeterModel.Unit.BYTES)
+                    .put(MeterSpec.Unit.PACKETS, PiMeterModel.Unit.PACKETS)
+                    // Don't map UNSPECIFIED as we don't support it at the moment.
+                    .build();
+
+    private static final Map<String, PiPacketOperationType> PACKET_OPERATION_TYPE_MAP =
+            new ImmutableMap.Builder<String, PiPacketOperationType>()
+                    .put(PACKET_IN, PiPacketOperationType.PACKET_IN)
+                    .put(PACKET_OUT, PiPacketOperationType.PACKET_OUT)
+                    .build();
+
+    private static final Map<MatchField.MatchType, PiMatchType> MATCH_TYPE_MAP =
+            new ImmutableMap.Builder<MatchField.MatchType, PiMatchType>()
+                    .put(MatchField.MatchType.VALID, PiMatchType.VALID)
+                    .put(MatchField.MatchType.EXACT, PiMatchType.EXACT)
+                    .put(MatchField.MatchType.LPM, PiMatchType.LPM)
+                    .put(MatchField.MatchType.TERNARY, PiMatchType.TERNARY)
+                    .put(MatchField.MatchType.RANGE, PiMatchType.RANGE)
+                    // Don't map UNSPECIFIED as we don't support it at the moment.
+                    .build();
+    public static final int NO_SIZE = -1;
+
+    private P4InfoParser() {
+        // Utility class, hides constructor.
+    }
+
+    /**
+     * Parse the given URL pointing to a P4Info file (in text format) to a PI pipeline model.
+     *
+     * @param p4InfoUrl URL to P4Info in text form
+     * @return PI pipeline model
+     * @throws P4InfoParserException if the P4Info file cannot be parsed (see message)
+     */
+    public static PiPipelineModel parse(URL p4InfoUrl) throws P4InfoParserException {
+
+        final P4Info p4info;
+        try {
+            p4info = getP4InfoMessage(p4InfoUrl);
+        } catch (IOException e) {
+            throw new P4InfoParserException("Unable to parse protobuf " + p4InfoUrl.toString(), e);
+        }
+
+        // Start by parsing and mapping instances to to their integer P4Info IDs.
+        // Convenient to build the table model at the end.
+
+        // Counters.
+        final Map<Integer, PiCounterModel> counterMap = Maps.newHashMap();
+        counterMap.putAll(parseCounters(p4info));
+        counterMap.putAll(parseDirectCounters(p4info));
+
+        // Meters.
+        final Map<Integer, PiMeterModel> meterMap = Maps.newHashMap();
+        meterMap.putAll(parseMeters(p4info));
+        meterMap.putAll(parseDirectMeters(p4info));
+
+        // Action profiles.
+        final Map<Integer, PiActionProfileModel> actProfileMap = parseActionProfiles(p4info);
+
+        // Actions.
+        final Map<Integer, PiActionModel> actionMap = parseActions(p4info);
+
+        // Controller packet metadatas.
+        final Map<PiPacketOperationType, PiPacketOperationModel> pktOpMap = parseCtrlPktMetadatas(p4info);
+
+        // Finally, parse tables.
+        final ImmutableMap.Builder<PiTableId, PiTableModel> tableImmMapBuilder =
+                ImmutableMap.builder();
+        for (Table tableMsg : p4info.getTablesList()) {
+            final PiTableId tableId = PiTableId.of(tableMsg.getPreamble().getName());
+            // Parse match fields.
+            final ImmutableMap.Builder<PiMatchFieldId, PiMatchFieldModel> tableFieldMapBuilder =
+                    ImmutableMap.builder();
+            for (MatchField fieldMsg : tableMsg.getMatchFieldsList()) {
+                final PiMatchFieldId fieldId = PiMatchFieldId.of(fieldMsg.getName());
+                tableFieldMapBuilder.put(
+                        fieldId,
+                        new P4MatchFieldModel(fieldId,
+                                              fieldMsg.getBitwidth(),
+                                              mapMatchFieldType(fieldMsg.getMatchType())));
+
+            }
+            // Retrieve action models by inter IDs.
+            final ImmutableMap.Builder<PiActionId, PiActionModel> tableActionMapBuilder =
+                    ImmutableMap.builder();
+            tableMsg.getActionRefsList().stream()
+                    .map(ActionRef::getId)
+                    .map(actionMap::get)
+                    .forEach(actionModel -> tableActionMapBuilder.put(actionModel.id(), actionModel));
+            // Retrieve direct meters by integer IDs.
+            final ImmutableMap.Builder<PiMeterId, PiMeterModel> tableMeterMapBuilder =
+                    ImmutableMap.builder();
+            tableMsg.getDirectResourceIdsList()
+                    .stream()
+                    .map(meterMap::get)
+                    // Direct resource ID might be that of a counter.
+                    // Filter out missed mapping.
+                    .filter(Objects::nonNull)
+                    .forEach(meterModel -> tableMeterMapBuilder.put(meterModel.id(), meterModel));
+            // Retrieve direct counters by integer IDs.
+            final ImmutableMap.Builder<PiCounterId, PiCounterModel> tableCounterMapBuilder =
+                    ImmutableMap.builder();
+            tableMsg.getDirectResourceIdsList()
+                    .stream()
+                    .map(counterMap::get)
+                    // As before, resource ID might be that of a meter.
+                    // Filter out missed mapping.
+                    .filter(Objects::nonNull)
+                    .forEach(counterModel -> tableCounterMapBuilder.put(counterModel.id(), counterModel));
+            tableImmMapBuilder.put(
+                    tableId,
+                    new P4TableModel(
+                            PiTableId.of(tableMsg.getPreamble().getName()),
+                            tableMsg.getImplementationId() == 0 ? PiTableType.DIRECT : PiTableType.INDIRECT,
+                            actProfileMap.get(tableMsg.getImplementationId()),
+                            tableMsg.getSize(),
+                            tableCounterMapBuilder.build(),
+                            tableMeterMapBuilder.build(),
+                            tableMsg.getWithEntryTimeout(),
+                            tableFieldMapBuilder.build(),
+                            tableActionMapBuilder.build(),
+                            actionMap.get(tableMsg.getConstDefaultActionId()),
+                            tableMsg.getConstDefaultActionHasMutableParams()));
+
+        }
+
+        // Get a map with proper PI IDs for some of those maps we created at the beginning.
+        ImmutableMap<PiCounterId, PiCounterModel> counterImmMap = ImmutableMap.copyOf(
+                counterMap.values().stream()
+                        .collect(Collectors.toMap(PiCounterModel::id, c -> c)));
+        ImmutableMap<PiMeterId, PiMeterModel> meterImmMap = ImmutableMap.copyOf(
+                meterMap.values().stream()
+                        .collect(Collectors.toMap(PiMeterModel::id, m -> m)));
+        ImmutableMap<PiActionProfileId, PiActionProfileModel> actProfileImmMap = ImmutableMap.copyOf(
+                actProfileMap.values().stream()
+                        .collect(Collectors.toMap(PiActionProfileModel::id, a -> a)));
+
+        return new P4PipelineModel(
+                tableImmMapBuilder.build(),
+                counterImmMap,
+                meterImmMap,
+                actProfileImmMap,
+                ImmutableMap.copyOf(pktOpMap));
+    }
+
+
+    private static Map<Integer, PiCounterModel> parseCounters(P4Info p4info)
+            throws P4InfoParserException {
+        final Map<Integer, PiCounterModel> counterMap = Maps.newHashMap();
+        for (Counter counterMsg : p4info.getCountersList()) {
+            counterMap.put(
+                    counterMsg.getPreamble().getId(),
+                    new P4CounterModel(
+                            PiCounterId.of(counterMsg.getPreamble().getName()),
+                            PiCounterType.INDIRECT,
+                            mapCounterSpecUnit(counterMsg.getSpec()),
+                            null,
+                            counterMsg.getSize()));
+        }
+        return counterMap;
+    }
+
+    private static Map<Integer, PiCounterModel> parseDirectCounters(P4Info p4info)
+            throws P4InfoParserException {
+        final Map<Integer, PiCounterModel> counterMap = Maps.newHashMap();
+        for (DirectCounter dirCounterMsg : p4info.getDirectCountersList()) {
+            counterMap.put(
+                    dirCounterMsg.getPreamble().getId(),
+                    new P4CounterModel(
+                            PiCounterId.of(dirCounterMsg.getPreamble().getName()),
+                            PiCounterType.DIRECT,
+                            mapCounterSpecUnit(dirCounterMsg.getSpec()),
+                            PiTableId.of(getTableName(dirCounterMsg.getDirectTableId(), p4info)),
+                            NO_SIZE));
+        }
+        return counterMap;
+    }
+
+    private static Map<Integer, PiMeterModel> parseMeters(P4Info p4info)
+            throws P4InfoParserException {
+        final Map<Integer, PiMeterModel> meterMap = Maps.newHashMap();
+        for (Meter meterMsg : p4info.getMetersList()) {
+            meterMap.put(
+                    meterMsg.getPreamble().getId(),
+                    new P4MeterModel(
+                            PiMeterId.of(meterMsg.getPreamble().getName()),
+                            PiMeterType.INDIRECT,
+                            mapMeterSpecUnit(meterMsg.getSpec()),
+                            null,
+                            meterMsg.getSize()));
+        }
+        return meterMap;
+    }
+
+    private static Map<Integer, PiMeterModel> parseDirectMeters(P4Info p4info)
+            throws P4InfoParserException {
+        final Map<Integer, PiMeterModel> meterMap = Maps.newHashMap();
+        for (DirectMeter dirMeterMsg : p4info.getDirectMetersList()) {
+            meterMap.put(
+                    dirMeterMsg.getPreamble().getId(),
+                    new P4MeterModel(
+                            PiMeterId.of(dirMeterMsg.getPreamble().getName()),
+                            PiMeterType.DIRECT,
+                            mapMeterSpecUnit(dirMeterMsg.getSpec()),
+                            PiTableId.of(getTableName(dirMeterMsg.getDirectTableId(), p4info)),
+                            NO_SIZE));
+        }
+        return meterMap;
+    }
+
+    private static Map<Integer, PiActionProfileModel> parseActionProfiles(P4Info p4info)
+            throws P4InfoParserException {
+        final Map<Integer, PiActionProfileModel> actProfileMap = Maps.newHashMap();
+        for (ActionProfile actProfileMsg : p4info.getActionProfilesList()) {
+            final ImmutableSet.Builder<PiTableId> tableIdSetBuilder = ImmutableSet.builder();
+            for (int tableId : actProfileMsg.getTableIdsList()) {
+                tableIdSetBuilder.add(PiTableId.of(getTableName(tableId, p4info)));
+            }
+            actProfileMap.put(
+                    actProfileMsg.getPreamble().getId(),
+                    new P4ActionProfileModel(
+                            PiActionProfileId.of(actProfileMsg.getPreamble().getName()),
+                            tableIdSetBuilder.build(),
+                            actProfileMsg.getWithSelector(),
+                            actProfileMsg.getSize()));
+        }
+        return actProfileMap;
+    }
+
+    private static Map<Integer, PiActionModel> parseActions(P4Info p4info) {
+        final Map<Integer, PiActionModel> actionMap = Maps.newHashMap();
+        for (Action actionMsg : p4info.getActionsList()) {
+            final ImmutableMap.Builder<PiActionParamId, PiActionParamModel> paramMapBuilder =
+                    ImmutableMap.builder();
+            actionMsg.getParamsList().forEach(paramMsg -> {
+                final PiActionParamId paramId = PiActionParamId.of(paramMsg.getName());
+                paramMapBuilder.put(paramId,
+                                    new P4ActionParamModel(PiActionParamId.of(paramMsg.getName()),
+                                                           paramMsg.getBitwidth()));
+            });
+            actionMap.put(
+                    actionMsg.getPreamble().getId(),
+                    new P4ActionModel(
+                            PiActionId.of(actionMsg.getPreamble().getName()),
+                            paramMapBuilder.build()));
+
+        }
+        return actionMap;
+    }
+
+    private static Map<PiPacketOperationType, PiPacketOperationModel> parseCtrlPktMetadatas(P4Info p4info)
+            throws P4InfoParserException {
+        final Map<PiPacketOperationType, PiPacketOperationModel> packetOpMap = Maps.newHashMap();
+        for (ControllerPacketMetadata ctrlPktMetaMsg : p4info.getControllerPacketMetadataList()) {
+            final ImmutableList.Builder<PiControlMetadataModel> metadataListBuilder =
+                    ImmutableList.builder();
+            ctrlPktMetaMsg.getMetadataList().forEach(metadataMsg -> metadataListBuilder.add(
+                    new P4ControlMetadataModel(PiControlMetadataId.of(metadataMsg.getName()),
+                                               metadataMsg.getBitwidth())));
+            packetOpMap.put(
+                    mapPacketOpType(ctrlPktMetaMsg.getPreamble().getName()),
+                    new P4PacketOperationModel(mapPacketOpType(ctrlPktMetaMsg.getPreamble().getName()),
+                                               metadataListBuilder.build()));
+
+        }
+        return packetOpMap;
+    }
+
+    private static P4Info getP4InfoMessage(URL p4InfoUrl) throws IOException {
+        InputStream p4InfoStream = p4InfoUrl.openStream();
+        P4Info.Builder p4InfoBuilder = P4Info.newBuilder();
+        TextFormat.getParser().merge(new InputStreamReader(p4InfoStream),
+                                     ExtensionRegistry.getEmptyRegistry(),
+                                     p4InfoBuilder);
+        return p4InfoBuilder.build();
+    }
+
+    private static String getTableName(int id, P4Info p4info)
+            throws P4InfoParserException {
+        return p4info.getTablesList().stream()
+                .filter(t -> t.getPreamble().getId() == id)
+                .findFirst()
+                .orElseThrow(() -> new P4InfoParserException(format(
+                        "Not such table with ID %d", id)))
+                .getPreamble()
+                .getName();
+    }
+
+    private static PiCounterModel.Unit mapCounterSpecUnit(CounterSpec spec)
+            throws P4InfoParserException {
+        if (!COUNTER_UNIT_MAP.containsKey(spec.getUnit())) {
+            throw new P4InfoParserException(format(
+                    "Unrecognized counter unit '%s'", spec.getUnit()));
+        }
+        return COUNTER_UNIT_MAP.get(spec.getUnit());
+    }
+
+    private static PiMeterModel.Unit mapMeterSpecUnit(MeterSpec spec)
+            throws P4InfoParserException {
+        if (!METER_UNIT_MAP.containsKey(spec.getUnit())) {
+            throw new P4InfoParserException(format(
+                    "Unrecognized meter unit '%s'", spec.getUnit()));
+        }
+        return METER_UNIT_MAP.get(spec.getUnit());
+    }
+
+    private static PiPacketOperationType mapPacketOpType(String name)
+            throws P4InfoParserException {
+        if (!PACKET_OPERATION_TYPE_MAP.containsKey(name)) {
+            throw new P4InfoParserException(format(
+                    "Unrecognized controller packet metadata name '%s'", name));
+        }
+        return PACKET_OPERATION_TYPE_MAP.get(name);
+    }
+
+    private static PiMatchType mapMatchFieldType(MatchField.MatchType type)
+            throws P4InfoParserException {
+        if (!MATCH_TYPE_MAP.containsKey(type)) {
+            throw new P4InfoParserException(format(
+                    "Unrecognized match field type '%s'", type));
+        }
+        return MATCH_TYPE_MAP.get(type);
+    }
+}