org.onosproject.p4runtime.model.P4InfoParser.java Source code

Java tutorial

Introduction

Here is the source code for org.onosproject.p4runtime.model.P4InfoParser.java

Source

/*
 * 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);
    }
}