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