blob: e60f25fac7ef0681fdf36b47ea133c5b31f3369d [file] [log] [blame]
/*
* 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.PiRegisterId;
import org.onosproject.net.pi.model.PiRegisterModel;
import org.onosproject.net.pi.model.PiTableId;
import org.onosproject.net.pi.model.PiTableModel;
import org.onosproject.net.pi.model.PiTableType;
import p4.config.v1.P4InfoOuterClass;
import p4.config.v1.P4InfoOuterClass.Action;
import p4.config.v1.P4InfoOuterClass.ActionProfile;
import p4.config.v1.P4InfoOuterClass.ActionRef;
import p4.config.v1.P4InfoOuterClass.ControllerPacketMetadata;
import p4.config.v1.P4InfoOuterClass.Counter;
import p4.config.v1.P4InfoOuterClass.CounterSpec;
import p4.config.v1.P4InfoOuterClass.DirectCounter;
import p4.config.v1.P4InfoOuterClass.DirectMeter;
import p4.config.v1.P4InfoOuterClass.MatchField;
import p4.config.v1.P4InfoOuterClass.Meter;
import p4.config.v1.P4InfoOuterClass.MeterSpec;
import p4.config.v1.P4InfoOuterClass.P4Info;
import p4.config.v1.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.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));
// Registers.
final Map<Integer, PiRegisterModel> registerMap = Maps.newHashMap();
registerMap.putAll(parseRegisters(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.getIdleTimeoutBehavior()
.equals(Table.IdleTimeoutBehavior.NO_TIMEOUT),
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<PiRegisterId, PiRegisterModel> registerImmMap = ImmutableMap.copyOf(
registerMap.values().stream()
.collect(Collectors.toMap(PiRegisterModel::id, r -> r)));
ImmutableMap<PiActionProfileId, PiActionProfileModel> actProfileImmMap = ImmutableMap.copyOf(
actProfileMap.values().stream()
.collect(Collectors.toMap(PiActionProfileModel::id, a -> a)));
return new P4PipelineModel(
tableImmMapBuilder.build(),
counterImmMap,
meterImmMap,
registerImmMap,
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, PiRegisterModel> parseRegisters(P4Info p4info) {
final Map<Integer, PiRegisterModel> registerMap = Maps.newHashMap();
for (P4InfoOuterClass.Register registerMsg : p4info.getRegistersList()) {
registerMap.put(registerMsg.getPreamble().getId(),
new P4RegisterModel(PiRegisterId.of(registerMsg.getPreamble().getName()),
registerMsg.getSize()));
}
return registerMap;
}
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);
}
}