blob: 3407cf29bbc0d07f0d1db3c10e021ba832bd346c [file] [log] [blame]
/*
* Copyright 2018-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.net.pi.impl;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.onosproject.net.Device;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
import org.onosproject.net.flow.instructions.Instructions.TruncateInstruction;
import org.onosproject.net.group.Group;
import org.onosproject.net.group.GroupBucket;
import org.onosproject.net.group.GroupDescription;
import org.onosproject.net.pi.model.PiPipeconf;
import org.onosproject.net.pi.model.PiPipelineInterpreter;
import org.onosproject.net.pi.runtime.PiCloneSessionEntry;
import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
import org.onosproject.net.pi.runtime.PiPreEntry;
import org.onosproject.net.pi.runtime.PiPreReplica;
import org.onosproject.net.pi.service.PiTranslationException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;
/**
* Implementation of replication group translation logic.
*/
final class PiReplicationGroupTranslatorImpl {
private PiReplicationGroupTranslatorImpl() {
// Hides constructor.
}
/**
* Returns a PI PRE entry equivalent to the given group, for the given
* pipeconf and device.
* <p>
* The passed group is expected to have type {@link GroupDescription.Type#ALL}
* or {@link GroupDescription.Type#CLONE}.
*
* @param group group
* @param pipeconf pipeconf
* @param device device
* @return PI PRE entry
* @throws PiTranslationException if the group cannot be translated
*/
static PiPreEntry translate(Group group, PiPipeconf pipeconf, Device device)
throws PiTranslationException {
checkNotNull(group);
final List<OutputInstruction> outInstructions = Lists.newArrayList();
int truncateMaxLen = PiCloneSessionEntry.DO_NOT_TRUNCATE;
for (GroupBucket bucket : group.buckets().buckets()) {
int numInstructionsInBucket = bucket.treatment().allInstructions().size();
List<OutputInstruction> outputs =
getInstructions(bucket, Instruction.Type.OUTPUT, OutputInstruction.class);
List<TruncateInstruction> truncates =
getInstructions(bucket, Instruction.Type.TRUNCATE, TruncateInstruction.class);
if (outputs.size() != 1) {
throw new PiTranslationException(
"support only groups with just one OUTPUT instruction per bucket");
}
outInstructions.add(outputs.get(0));
if (truncates.size() != 0) {
if (group.type() != GroupDescription.Type.CLONE) {
throw new PiTranslationException("only CLONE group support truncate instruction");
}
if (truncates.size() != 1) {
throw new PiTranslationException(
"support only groups with just one TRUNCATE instruction per bucket");
}
int truncateInstMaxLen = truncates.get(0).maxLen();
if (truncateMaxLen != PiCloneSessionEntry.DO_NOT_TRUNCATE &&
truncateMaxLen != truncateInstMaxLen) {
throw new PiTranslationException("all TRUNCATE instruction must be the same in a CLONE group");
}
truncateMaxLen = truncateInstMaxLen;
} else if (truncateMaxLen != PiCloneSessionEntry.DO_NOT_TRUNCATE) {
// No truncate instruction found in this bucket, but previous bucket contains one.
throw new PiTranslationException("all TRUNCATE instruction must be the same in a CLONE group");
}
if (numInstructionsInBucket != outputs.size() + truncates.size()) {
throw new PiTranslationException("bucket contains unsupported instruction(s)");
}
}
switch (group.type()) {
case ALL:
return PiMulticastGroupEntry.builder()
.withGroupId(group.id().id())
.addReplicas(getReplicas(outInstructions, device))
.build();
case CLONE:
return PiCloneSessionEntry.builder()
.withSessionId(group.id().id())
.addReplicas(getReplicas(outInstructions, device))
.withMaxPacketLengthBytes(truncateMaxLen)
.build();
default:
throw new PiTranslationException(format(
"group type %s not supported", group.type()));
}
}
private static Set<PiPreReplica> getReplicas(
Collection<OutputInstruction> instructions, Device device)
throws PiTranslationException {
// Account for multiple replicas for the same port.
final Map<PortNumber, Set<PiPreReplica>> replicaMap = Maps.newHashMap();
final List<PortNumber> ports = instructions.stream()
.map(OutputInstruction::port)
.collect(Collectors.toList());
for (PortNumber port : ports) {
if (port.isLogical()) {
port = logicalToPipelineSpecific(port, device);
}
// Use incremental instance IDs for replicas of the same port.
replicaMap.putIfAbsent(port, Sets.newHashSet());
replicaMap.get(port).add(
new PiPreReplica(port, replicaMap.get(port).size()));
}
return replicaMap.values().stream()
.flatMap(Collection::stream)
.collect(Collectors.toSet());
}
private static PortNumber logicalToPipelineSpecific(
PortNumber logicalPort, Device device)
throws PiTranslationException {
if (!device.is(PiPipelineInterpreter.class)) {
throw new PiTranslationException(
"missing interpreter, cannot map logical port " + logicalPort.toString());
}
final PiPipelineInterpreter interpreter = device.as(PiPipelineInterpreter.class);
Optional<Integer> mappedPort = interpreter.mapLogicalPortNumber(logicalPort);
if (!mappedPort.isPresent()) {
throw new PiTranslationException(
"interpreter cannot map logical port " + logicalPort.toString());
}
return PortNumber.portNumber(mappedPort.get());
}
private static <I extends Instruction> List<I> getInstructions(GroupBucket bucket,
Instruction.Type type,
Class<I> instructionClass) {
return bucket.treatment().allInstructions().stream()
.filter(i -> i.type() == type)
.map(instructionClass::cast)
.collect(Collectors.toList());
}
}