blob: 479b1d4cb9bb4438fc470486cff21542bfa66035 [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 org.onlab.packet.VlanId;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.driver.Driver;
import org.onosproject.net.flow.DefaultFlowRule;
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.flow.instructions.L2ModificationInstruction;
import org.onosproject.net.flowobjective.DefaultNextObjective;
import org.onosproject.net.flowobjective.NextObjective;
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.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.FabricConstants;
import org.onosproject.pipelines.fabric.FabricUtils;
import org.slf4j.Logger;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.onosproject.pipelines.fabric.FabricUtils.getOutputPort;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Handling next objective for fabric pipeliner.
*/
public class FabricNextPipeliner {
private static final Logger log = getLogger(FabricNextPipeliner.class);
private static final String NO_HASHED_TABLE = "noHashedTable";
protected DeviceId deviceId;
protected Driver driver;
public FabricNextPipeliner(DeviceId deviceId, Driver driver) {
this.deviceId = deviceId;
this.driver = driver;
}
public PipelinerTranslationResult next(NextObjective nextObjective) {
PipelinerTranslationResult.Builder resultBuilder = PipelinerTranslationResult.builder();
if (nextObjective.op() != Objective.Operation.ADD_TO_EXISTING &&
nextObjective.op() != Objective.Operation.REMOVE_FROM_EXISTING) {
processNextVlanMeta(nextObjective, resultBuilder);
}
switch (nextObjective.type()) {
case SIMPLE:
processSimpleNext(nextObjective, resultBuilder);
break;
case HASHED:
processHashedNext(nextObjective, resultBuilder);
break;
case BROADCAST:
processBroadcastNext(nextObjective, resultBuilder);
break;
default:
log.warn("Unsupported next type {}", nextObjective);
resultBuilder.setError(ObjectiveError.UNSUPPORTED);
break;
}
return resultBuilder.build();
}
private void processNextVlanMeta(NextObjective next,
PipelinerTranslationResult.Builder resultBuilder) {
TrafficSelector meta = next.meta();
if (meta == null) {
// do nothing if there is no metadata in the next objective.
return;
}
VlanIdCriterion vlanIdCriterion =
(VlanIdCriterion) meta.getCriterion(Criterion.Type.VLAN_VID);
if (vlanIdCriterion == null) {
// do nothing if we can't find vlan from next objective metadata.
return;
}
VlanId vlanId = vlanIdCriterion.vlanId();
TrafficSelector selector = buildNextIdSelector(next.id());
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setVlanId(vlanId)
.build();
resultBuilder.addFlowRule(DefaultFlowRule.builder()
.withSelector(selector)
.withTreatment(treatment)
.forTable(FabricConstants.FABRIC_INGRESS_NEXT_VLAN_META)
.makePermanent()
.withPriority(next.priority())
.forDevice(deviceId)
.fromApp(next.appId())
.build());
}
private void processSimpleNext(NextObjective next,
PipelinerTranslationResult.Builder resultBuilder) {
if (next.next().size() > 1) {
log.warn("Only one treatment in simple next objective");
resultBuilder.setError(ObjectiveError.BADPARAMS);
return;
}
TrafficSelector selector = buildNextIdSelector(next.id());
TrafficTreatment treatment = next.next().iterator().next();
PortNumber outputPort = getOutputPort(treatment);
if (outputPort == null) {
log.warn("At least one output instruction in simple next objective");
resultBuilder.setError(ObjectiveError.BADPARAMS);
return;
}
resultBuilder.addFlowRule(DefaultFlowRule.builder()
.withSelector(selector)
.withTreatment(treatment)
.forTable(FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE)
.makePermanent()
.withPriority(next.priority())
.forDevice(deviceId)
.fromApp(next.appId())
.build());
if (includesPopVlanInst(treatment)) {
processVlanPopRule(outputPort, next, resultBuilder);
}
}
private boolean includesPopVlanInst(TrafficTreatment treatment) {
return treatment.allInstructions()
.stream()
.filter(inst -> inst.type() == Instruction.Type.L2MODIFICATION)
.map(inst -> (L2ModificationInstruction) inst)
.anyMatch(inst -> inst.subtype() == L2ModificationInstruction.L2SubType.VLAN_POP);
}
private void processVlanPopRule(PortNumber port, NextObjective next,
PipelinerTranslationResult.Builder resultBuilder) {
TrafficSelector meta = next.meta();
VlanIdCriterion vlanIdCriterion =
(VlanIdCriterion) meta.getCriterion(Criterion.Type.VLAN_VID);
VlanId vlanId = vlanIdCriterion.vlanId();
PiCriterion egressVlanTableMatch = PiCriterion.builder()
.matchExact(FabricConstants.STANDARD_METADATA_EGRESS_PORT,
(short) port.toLong())
.build();
// Add VLAN pop rule to egress pipeline table
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchPi(egressVlanTableMatch)
.matchVlanId(vlanId)
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.popVlan()
.build();
resultBuilder.addFlowRule(DefaultFlowRule.builder()
.withSelector(selector)
.withTreatment(treatment)
.forTable(FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN)
.makePermanent()
.withPriority(next.priority())
.forDevice(deviceId)
.fromApp(next.appId())
.build());
}
private void processHashedNext(NextObjective next, PipelinerTranslationResult.Builder resultBuilder) {
boolean noHashedTable = Boolean.parseBoolean(driver.getProperty(NO_HASHED_TABLE));
if (noHashedTable) {
if (next.next().isEmpty()) {
return;
}
// use first action if not support hashed group
TrafficTreatment treatment = next.next().iterator().next();
NextObjective.Builder simpleNext = DefaultNextObjective.builder()
.addTreatment(treatment)
.withId(next.id())
.fromApp(next.appId())
.makePermanent()
.withMeta(next.meta())
.withPriority(next.priority())
.withType(NextObjective.Type.SIMPLE);
if (next.context().isPresent()) {
processSimpleNext(simpleNext.add(next.context().get()), resultBuilder);
} else {
processSimpleNext(simpleNext.add(), resultBuilder);
}
return;
}
// create hash groups
int groupId = next.id();
List<GroupBucket> bucketList = next.next().stream()
.map(DefaultGroupBucket::createSelectGroupBucket)
.collect(Collectors.toList());
// Egress VLAN handling
next.next().forEach(treatment -> {
PortNumber outputPort = getOutputPort(treatment);
if (includesPopVlanInst(treatment) && outputPort != null) {
processVlanPopRule(outputPort, next, resultBuilder);
}
});
if (bucketList.size() != next.next().size()) {
// some action not converted
// set error
log.warn("Expected bucket size {}, got {}", next.next().size(), bucketList.size());
resultBuilder.setError(ObjectiveError.BADPARAMS);
return;
}
GroupBuckets buckets = new GroupBuckets(bucketList);
PiGroupKey groupKey = new PiGroupKey(FabricConstants.FABRIC_INGRESS_NEXT_HASHED,
FabricConstants.FABRIC_INGRESS_NEXT_ECMP_SELECTOR,
groupId);
resultBuilder.addGroup(new DefaultGroupDescription(deviceId,
GroupDescription.Type.SELECT,
buckets,
groupKey,
groupId,
next.appId()));
// flow
// If operation is ADD_TO_EXIST or REMOVE_FROM_EXIST, means we modify
// group buckets only, no changes for flow rule
if (next.op() == Objective.Operation.ADD_TO_EXISTING ||
next.op() == Objective.Operation.REMOVE_FROM_EXISTING) {
return;
}
TrafficSelector selector = buildNextIdSelector(next.id());
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.piTableAction(PiActionGroupId.of(next.id()))
.build();
resultBuilder.addFlowRule(DefaultFlowRule.builder()
.withSelector(selector)
.withTreatment(treatment)
.forTable(FabricConstants.FABRIC_INGRESS_NEXT_HASHED)
.makePermanent()
.withPriority(next.priority())
.forDevice(deviceId)
.fromApp(next.appId())
.build());
}
private TrafficSelector buildNextIdSelector(int nextId) {
PiCriterion nextIdCriterion = PiCriterion.builder()
.matchExact(FabricConstants.FABRIC_METADATA_NEXT_ID, nextId)
.build();
return DefaultTrafficSelector.builder()
.matchPi(nextIdCriterion)
.build();
}
private void processBroadcastNext(NextObjective next, PipelinerTranslationResult.Builder resultBuilder) {
final GroupDescription allGroup = getAllGroup(next);
if (allGroup == null) {
// Error already logged.
resultBuilder.setError(ObjectiveError.BADPARAMS);
return;
}
resultBuilder.addGroup(allGroup);
//flow rule
final TrafficSelector selector = buildNextIdSelector(next.id());
final PiActionParam groupIdParam = new PiActionParam(
FabricConstants.GID, allGroup.givenGroupId());
final PiAction setMcGroupAction = PiAction.builder()
.withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_MCAST_GROUP)
.withParameter(groupIdParam)
.build();
final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.piTableAction(setMcGroupAction)
.build();
resultBuilder.addFlowRule(
DefaultFlowRule.builder()
.withSelector(selector)
.withTreatment(treatment)
.forTable(FabricConstants.FABRIC_INGRESS_NEXT_MULTICAST)
.makePermanent()
.withPriority(next.priority())
.forDevice(deviceId)
.fromApp(next.appId())
.build());
// Egress VLAN handling
next.next().forEach(t -> {
PortNumber outputPort = getOutputPort(t);
if (includesPopVlanInst(t) && outputPort != null) {
processVlanPopRule(outputPort, next, resultBuilder);
}
if (t.allInstructions().size() > 2) {
// More than OUTPUT and VLAN_POP...
log.warn("Some instructions of BROADCAST NextObjective might" +
"not have been applied, supported only " +
"OUTPUT and VLAN_POP, but found {}", t);
}
});
}
private GroupDescription getAllGroup(NextObjective next) {
final List<GroupBucket> bucketList = next.next().stream()
.map(FabricUtils::getOutputInstruction)
.filter(Optional::isPresent)
.map(Optional::get)
.map(i -> DefaultTrafficTreatment.builder().add(i).build())
.map(DefaultGroupBucket::createAllGroupBucket)
.collect(Collectors.toList());
if (bucketList.size() != next.next().size()) {
log.warn("Got BROADCAST NextObjective with {} treatments but " +
"found only {} OUTPUT instructions, cannot " +
"translate to ALL groups",
next.next().size(), bucketList.size());
return null;
}
// 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 = next.id();
final GroupBuckets buckets = new GroupBuckets(bucketList);
// Used 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));
return new DefaultGroupDescription(deviceId,
GroupDescription.Type.ALL,
buckets,
groupKey,
groupId,
next.appId());
}
}