blob: b2c5406666fb84f1430bd56f24e6f6e2d1da2c69 [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.pipelines.fabric.pipeliner;
import com.google.common.collect.Lists;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.PiCriterion;
import org.onosproject.net.flow.criteria.VlanIdCriterion;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flowobjective.DefaultNextTreatment;
import org.onosproject.net.flowobjective.NextObjective;
import org.onosproject.net.flowobjective.NextTreatment;
import org.onosproject.net.flowobjective.Objective;
import org.onosproject.net.flowobjective.ObjectiveError;
import org.onosproject.net.group.DefaultGroupBucket;
import org.onosproject.net.group.DefaultGroupDescription;
import org.onosproject.net.group.DefaultGroupKey;
import org.onosproject.net.group.GroupBucket;
import org.onosproject.net.group.GroupBuckets;
import org.onosproject.net.group.GroupDescription;
import org.onosproject.net.group.GroupKey;
import org.onosproject.net.pi.model.PiTableId;
import org.onosproject.net.pi.runtime.PiAction;
import org.onosproject.net.pi.runtime.PiActionGroupId;
import org.onosproject.net.pi.runtime.PiActionParam;
import org.onosproject.net.pi.runtime.PiGroupKey;
import org.onosproject.pipelines.fabric.FabricCapabilities;
import org.onosproject.pipelines.fabric.FabricConstants;
import org.onosproject.pipelines.fabric.FabricUtils;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static java.lang.String.format;
import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_POP;
import static org.onosproject.pipelines.fabric.FabricUtils.criterion;
import static org.onosproject.pipelines.fabric.FabricUtils.l2Instruction;
import static org.onosproject.pipelines.fabric.FabricUtils.outputPort;
/**
* ObjectiveTranslator implementation for NextObjective.
*/
class NextObjectiveTranslator
extends AbstractObjectiveTranslator<NextObjective> {
private static final String XCONNECT = "xconnect";
NextObjectiveTranslator(DeviceId deviceId, FabricCapabilities capabilities) {
super(deviceId, capabilities);
}
@Override
public ObjectiveTranslation doTranslate(NextObjective obj)
throws FabricPipelinerException {
final ObjectiveTranslation.Builder resultBuilder =
ObjectiveTranslation.builder();
switch (obj.type()) {
case SIMPLE:
simpleNext(obj, resultBuilder, false);
break;
case HASHED:
hashedNext(obj, resultBuilder);
break;
case BROADCAST:
if (isXconnect(obj)) {
xconnectNext(obj, resultBuilder);
} else {
multicastNext(obj, resultBuilder);
}
break;
default:
log.warn("Unsupported NextObjective type '{}'", obj);
return ObjectiveTranslation.ofError(ObjectiveError.UNSUPPORTED);
}
if (!isGroupModifyOp(obj)) {
// Generate next VLAN rules.
nextVlan(obj, resultBuilder);
}
return resultBuilder.build();
}
private void nextVlan(NextObjective obj,
ObjectiveTranslation.Builder resultBuilder)
throws FabricPipelinerException {
if (obj.meta() == null) {
// Do nothing if there is no metadata in the NextObjective.
return;
}
final VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) criterion(
obj.meta().criteria(), Criterion.Type.VLAN_VID);
if (vlanIdCriterion == null) {
// Do nothing if we can't find vlan from NextObjective metadata.
return;
}
// A VLAN ID as meta of a NextObjective indicates that packets matching
// the given next ID should be set with such VLAN ID.
final TrafficSelector selector = nextIdSelector(obj.id());
final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setVlanId(vlanIdCriterion.vlanId())
.build();
resultBuilder.addFlowRule(flowRule(
obj, FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN,
selector, treatment));
}
private void simpleNext(NextObjective obj,
ObjectiveTranslation.Builder resultBuilder,
boolean forceSimple)
throws FabricPipelinerException {
if (capabilities.hasHashedTable()) {
// Use hashed table when possible.
hashedNext(obj, resultBuilder);
return;
}
if (obj.nextTreatments().isEmpty()) {
// Do nothing.
return;
} else if (!forceSimple && obj.nextTreatments().size() != 1) {
throw new FabricPipelinerException(format(
"SIMPLE NextObjective should contain only 1 treatment, found %d",
obj.nextTreatments().size()), ObjectiveError.BADPARAMS);
}
final TrafficSelector selector = nextIdSelector(obj.id());
final List<DefaultNextTreatment> treatments = defaultNextTreatmentsOrFail(
obj.nextTreatments());
if (forceSimple && treatments.size() > 1) {
log.warn("Forcing SIMPLE behavior for NextObjective with {} treatments []",
treatments.size(), obj);
}
// If not forcing, we are essentially extracting the only available treatment.
final TrafficTreatment treatment = defaultNextTreatmentsOrFail(
obj.nextTreatments()).get(0).treatment();
resultBuilder.addFlowRule(flowRule(
obj, FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE,
selector, treatment));
handleEgress(obj, treatment, resultBuilder, false);
}
private void hashedNext(NextObjective obj,
ObjectiveTranslation.Builder resultBuilder)
throws FabricPipelinerException {
if (!capabilities.hasHashedTable()) {
simpleNext(obj, resultBuilder, true);
return;
}
// Updated result builder with hashed group.
final int groupId = selectGroup(obj, resultBuilder);
if (isGroupModifyOp(obj)) {
// No changes to flow rules.
return;
}
final TrafficSelector selector = nextIdSelector(obj.id());
final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.piTableAction(PiActionGroupId.of(groupId))
.build();
resultBuilder.addFlowRule(flowRule(
obj, FabricConstants.FABRIC_INGRESS_NEXT_HASHED,
selector, treatment));
}
private void handleEgress(NextObjective obj, TrafficTreatment treatment,
ObjectiveTranslation.Builder resultBuilder,
boolean strict)
throws FabricPipelinerException {
final PortNumber outPort = outputPort(treatment);
final Instruction popVlanInst = l2Instruction(treatment, VLAN_POP);
if (popVlanInst != null && outPort != null) {
if (strict && treatment.allInstructions().size() > 2) {
throw new FabricPipelinerException(
"Treatment contains instructions other " +
"than OUTPUT and VLAN_POP, cannot generate " +
"egress rules");
}
egressVlanPop(outPort, obj, resultBuilder);
}
}
private void egressVlanPop(PortNumber outPort, NextObjective obj,
ObjectiveTranslation.Builder resultBuilder)
throws FabricPipelinerException {
if (obj.meta() == null) {
throw new FabricPipelinerException(
"Cannot process egress pop VLAN rule, NextObjective has null meta",
ObjectiveError.BADPARAMS);
}
final VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) criterion(
obj.meta(), Criterion.Type.VLAN_VID);
if (vlanIdCriterion == null) {
throw new FabricPipelinerException(
"Cannot process egress pop VLAN rule, missing VLAN_VID criterion " +
"in NextObjective meta",
ObjectiveError.BADPARAMS);
}
final PiCriterion egressVlanTableMatch = PiCriterion.builder()
.matchExact(FabricConstants.HDR_EG_PORT, outPort.toLong())
.build();
final TrafficSelector selector = DefaultTrafficSelector.builder()
.matchPi(egressVlanTableMatch)
.matchVlanId(vlanIdCriterion.vlanId())
.build();
final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.popVlan()
.build();
resultBuilder.addFlowRule(flowRule(
obj, FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN,
selector, treatment));
}
private TrafficSelector nextIdSelector(int nextId) {
return nextIdSelectorBuilder(nextId).build();
}
private TrafficSelector.Builder nextIdSelectorBuilder(int nextId) {
final PiCriterion nextIdCriterion = PiCriterion.builder()
.matchExact(FabricConstants.HDR_NEXT_ID, nextId)
.build();
return DefaultTrafficSelector.builder()
.matchPi(nextIdCriterion);
}
private void xconnectNext(NextObjective obj, ObjectiveTranslation.Builder resultBuilder)
throws FabricPipelinerException {
final Collection<DefaultNextTreatment> defaultNextTreatments =
defaultNextTreatmentsOrFail(obj.nextTreatments());
final List<PortNumber> outPorts = defaultNextTreatments.stream()
.map(DefaultNextTreatment::treatment)
.map(FabricUtils::outputPort)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (outPorts.size() != 2) {
throw new FabricPipelinerException(format(
"Handling XCONNECT with %d treatments (ports), but expected is 2",
defaultNextTreatments.size()), ObjectiveError.UNSUPPORTED);
}
final PortNumber port1 = outPorts.get(0);
final PortNumber port2 = outPorts.get(1);
final TrafficSelector selector1 = nextIdSelectorBuilder(obj.id())
.matchInPort(port1)
.build();
final TrafficTreatment treatment1 = DefaultTrafficTreatment.builder()
.setOutput(port2)
.build();
final TrafficSelector selector2 = nextIdSelectorBuilder(obj.id())
.matchInPort(port2)
.build();
final TrafficTreatment treatment2 = DefaultTrafficTreatment.builder()
.setOutput(port1)
.build();
resultBuilder.addFlowRule(flowRule(
obj, FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT,
selector1, treatment1));
resultBuilder.addFlowRule(flowRule(
obj, FabricConstants.FABRIC_INGRESS_NEXT_XCONNECT,
selector2, treatment2));
}
private void multicastNext(NextObjective obj,
ObjectiveTranslation.Builder resultBuilder)
throws FabricPipelinerException {
// Create ALL group that will be translated to a PRE multicast entry.
final int groupId = allGroup(obj, resultBuilder);
if (isGroupModifyOp(obj)) {
// No changes to flow rules.
return;
}
final TrafficSelector selector = nextIdSelector(obj.id());
final PiActionParam groupIdParam = new PiActionParam(
FabricConstants.GROUP_ID, groupId);
final PiAction setMcGroupAction = PiAction.builder()
.withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_MCAST_GROUP_ID)
.withParameter(groupIdParam)
.build();
final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.piTableAction(setMcGroupAction)
.build();
resultBuilder.addFlowRule(flowRule(
obj, FabricConstants.FABRIC_INGRESS_NEXT_MULTICAST,
selector, treatment));
}
private int selectGroup(NextObjective obj,
ObjectiveTranslation.Builder resultBuilder)
throws FabricPipelinerException {
final PiTableId hashedTableId = FabricConstants.FABRIC_INGRESS_NEXT_HASHED;
final List<DefaultNextTreatment> defaultNextTreatments =
defaultNextTreatmentsOrFail(obj.nextTreatments());
final List<TrafficTreatment> piTreatments = Lists.newArrayList();
for (DefaultNextTreatment t : defaultNextTreatments) {
// Map treatment to PI...
piTreatments.add(mapTreatmentToPiIfNeeded(t.treatment(), hashedTableId));
// ...and handle egress if necessary.
handleEgress(obj, t.treatment(), resultBuilder, false);
}
final List<GroupBucket> bucketList = piTreatments.stream()
.map(DefaultGroupBucket::createSelectGroupBucket)
.collect(Collectors.toList());
final int groupId = obj.id();
final PiGroupKey groupKey = new PiGroupKey(
hashedTableId,
FabricConstants.FABRIC_INGRESS_NEXT_HASHED_SELECTOR,
groupId);
resultBuilder.addGroup(new DefaultGroupDescription(
deviceId,
GroupDescription.Type.SELECT,
new GroupBuckets(bucketList),
groupKey,
groupId,
obj.appId()));
return groupId;
}
private int allGroup(NextObjective obj,
ObjectiveTranslation.Builder resultBuilder)
throws FabricPipelinerException {
final Collection<DefaultNextTreatment> defaultNextTreatments =
defaultNextTreatmentsOrFail(obj.nextTreatments());
// No need to map treatments to PI as translation of ALL groups to PRE
// multicast entries is based solely on the output port.
for (DefaultNextTreatment t : defaultNextTreatments) {
handleEgress(obj, t.treatment(), resultBuilder, true);
}
// FIXME: this implementation supports only the case in which each
// switch interface is associated with only one VLAN, otherwise we would
// need to support replicating multiple times the same packet for the
// same port while setting different VLAN IDs. Hence, collect in a set.
final Set<PortNumber> outPorts = defaultNextTreatments.stream()
.map(DefaultNextTreatment::treatment)
.map(FabricUtils::outputPort)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
if (outPorts.size() != defaultNextTreatments.size()) {
throw new FabricPipelinerException(format(
"Found BROADCAST NextObjective with %d treatments but " +
"found only %d distinct OUTPUT port numbers, cannot " +
"translate to ALL groups",
defaultNextTreatments.size(), outPorts.size()),
ObjectiveError.UNSUPPORTED);
}
final List<GroupBucket> bucketList = outPorts.stream()
.map(p -> DefaultTrafficTreatment.builder().setOutput(p).build())
.map(DefaultGroupBucket::createAllGroupBucket)
.collect(Collectors.toList());
// FIXME: remove once support for clone sessions is available
// Right now we add a CPU port to all multicast groups. The egress
// pipeline is expected to drop replicated packets to the CPU if a clone
// was not requested in the ingress pipeline.
bucketList.add(
DefaultGroupBucket.createAllGroupBucket(
DefaultTrafficTreatment.builder()
.setOutput(PortNumber.CONTROLLER)
.build()));
final int groupId = obj.id();
// Use DefaultGroupKey instead of PiGroupKey as we don't have any
// action profile to apply to the groups of ALL type.
final GroupKey groupKey = new DefaultGroupKey(
FabricPipeliner.KRYO.serialize(groupId));
resultBuilder.addGroup(
new DefaultGroupDescription(
deviceId,
GroupDescription.Type.ALL,
new GroupBuckets(bucketList),
groupKey,
groupId,
obj.appId()));
return groupId;
}
private List<DefaultNextTreatment> defaultNextTreatmentsOrFail(
Collection<NextTreatment> nextTreatments)
throws FabricPipelinerException {
final List<DefaultNextTreatment> defaultNextTreatments = Lists.newArrayList();
final List<NextTreatment> unsupportedNextTreatments = Lists.newArrayList();
for (NextTreatment n : nextTreatments) {
if (n.type() == NextTreatment.Type.TREATMENT) {
defaultNextTreatments.add((DefaultNextTreatment) n);
} else {
unsupportedNextTreatments.add(n);
}
}
if (!unsupportedNextTreatments.isEmpty()) {
throw new FabricPipelinerException(format(
"Unsupported NextTreatments: %s",
unsupportedNextTreatments));
}
return defaultNextTreatments;
}
private boolean isGroupModifyOp(NextObjective obj) {
// If operation is ADD_TO_EXIST or REMOVE_FROM_EXIST, it means we modify
// group buckets only, no changes for flow rules.
return obj.op() == Objective.Operation.ADD_TO_EXISTING ||
obj.op() == Objective.Operation.REMOVE_FROM_EXISTING;
}
private boolean isXconnect(NextObjective obj) {
return obj.appId().name().contains(XCONNECT);
}
}