From 0b80972fa953cf02b68b7967be3e9bf0d4b29ba2 Mon Sep 17 00:00:00 2001 From: Yi Tseng Date: Fri, 3 Nov 2017 10:23:26 -0700 Subject: [PATCH] [ONOS-7129] Pipeliner for fabric pipeline Change-Id: I86b44694e1251611359e8ddc8be2533a741230cc --- pipelines/fabric/BUCK | 2 + .../pipelines/fabric/PipeconfLoader.java | 3 + .../pipeliner/FabricFilteringPipeliner.java | 244 ++++++++++++ .../pipeliner/FabricForwardingPipeliner.java | 211 +++++++++++ .../fabric/pipeliner/FabricNextPipeliner.java | 159 ++++++++ .../fabric/pipeliner/FabricPipeliner.java | 303 +++++++++++++++ .../pipeliner/ForwardingFunctionType.java | 122 ++++++ .../pipeliner/PipelinerTranslationResult.java | 154 ++++++++ .../fabric/pipeliner/package-info.java | 20 + .../FabricFilteringPipelinerTest.java | 355 ++++++++++++++++++ .../FabricForwardingPipelineTest.java | 260 +++++++++++++ .../pipeliner/FabricNextPipelinerTest.java | 151 ++++++++ .../fabric/pipeliner/FabricPipelinerTest.java | 86 +++++ .../pipeliner/ForwardingFunctionTypeTest.java | 130 +++++++ 14 files changed, 2200 insertions(+) create mode 100644 pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipeliner.java create mode 100644 pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipeliner.java create mode 100644 pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java create mode 100644 pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipeliner.java create mode 100644 pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionType.java create mode 100644 pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/PipelinerTranslationResult.java create mode 100644 pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/package-info.java create mode 100644 pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipelinerTest.java create mode 100644 pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipelineTest.java create mode 100644 pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java create mode 100644 pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerTest.java create mode 100644 pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionTypeTest.java diff --git a/pipelines/fabric/BUCK b/pipelines/fabric/BUCK index 114701d40e..9dfe33e63f 100644 --- a/pipelines/fabric/BUCK +++ b/pipelines/fabric/BUCK @@ -1,8 +1,10 @@ COMPILE_DEPS = [ '//lib:CORE_DEPS', + '//lib:KRYO', '//protocols/p4runtime/model:onos-protocols-p4runtime-model', '//protocols/p4runtime/api:onos-protocols-p4runtime-api', '//pipelines/basic:onos-pipelines-basic', + '//core/store/serializers:onos-core-serializers', ] TEST_DEPS = [ diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java index 2eec587723..c7c7421178 100644 --- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java +++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java @@ -22,6 +22,7 @@ import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onosproject.net.behaviour.Pipeliner; import org.onosproject.net.device.PortStatisticsDiscovery; import org.onosproject.net.pi.model.DefaultPiPipeconf; import org.onosproject.net.pi.model.PiPipeconf; @@ -31,6 +32,7 @@ import org.onosproject.net.pi.model.PiPipelineModel; import org.onosproject.net.pi.service.PiPipeconfService; import org.onosproject.p4runtime.model.P4InfoParser; import org.onosproject.p4runtime.model.P4InfoParserException; +import org.onosproject.pipelines.fabric.pipeliner.FabricPipeliner; import java.net.URL; import java.util.Collection; @@ -78,6 +80,7 @@ public class PipeconfLoader { .withId(FABRIC_PIPECONF_ID) .withPipelineModel(model) .addBehaviour(PiPipelineInterpreter.class, FabricInterpreter.class) + .addBehaviour(Pipeliner.class, FabricPipeliner.class) .addBehaviour(PortStatisticsDiscovery.class, FabricPortStatisticsDiscovery.class) .addExtension(P4_INFO_TEXT, p4InfoUrl) .addExtension(BMV2_JSON, jsonUrl) diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipeliner.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipeliner.java new file mode 100644 index 0000000000..69ec0ab9b3 --- /dev/null +++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipeliner.java @@ -0,0 +1,244 @@ +/* + * 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.Sets; +import org.onlab.packet.Ethernet; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onlab.util.ImmutableByteSequence; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +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.EthCriterion; +import org.onosproject.net.flow.criteria.PiCriterion; +import org.onosproject.net.flow.criteria.PortCriterion; +import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.pi.runtime.PiAction; +import org.onosproject.net.pi.runtime.PiActionParam; +import org.onosproject.pipelines.fabric.FabricConstants; +import org.slf4j.Logger; + +import java.util.Collection; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Handling filtering objective for fabric pipeliner. + */ +public class FabricFilteringPipeliner { + private static final Logger log = getLogger(FabricFilteringPipeliner.class); + // Forwarding types + private static final byte FWD_BRIDGING = 0; + private static final byte FWD_MPLS = 1; + private static final byte FWD_IPV4_UNICAST = 2; + private static final byte FWD_IPV4_MULTICAST = 3; + private static final byte FWD_IPV6_UNICAST = 4; + private static final byte FWD_IPV6_MULTICAST = 5; + private static final PiCriterion VLAN_VALID = PiCriterion.builder() + .matchExact(FabricConstants.HF_VLAN_TAG_IS_VALID_ID, new byte[]{1}) + .build(); + private static final PiCriterion VLAN_INVALID = PiCriterion.builder() + .matchExact(FabricConstants.HF_VLAN_TAG_IS_VALID_ID, new byte[]{0}) + .build(); + + protected DeviceId deviceId; + + public FabricFilteringPipeliner(DeviceId deviceId) { + this.deviceId = deviceId; + } + + /** + * Translates filtering objective to flows and groups. + * + * @param filterObjective the filtering objective + * @return translation result, contains flows, groups or error it generated + */ + public PipelinerTranslationResult filter(FilteringObjective filterObjective) { + PipelinerTranslationResult.Builder resultBuilder = PipelinerTranslationResult.builder(); + // maps selector and treatment from filtering objective to filtering + // control block. + + if (filterObjective.type() == FilteringObjective.Type.DENY) { + log.warn("Unsupported filtering objective type {}", filterObjective.type()); + resultBuilder.setError(ObjectiveError.UNSUPPORTED); + return resultBuilder.build(); + } + + if (filterObjective.key() == null || + filterObjective.key().type() != Criterion.Type.IN_PORT) { + log.warn("Unsupported filter key {}", filterObjective.key()); + resultBuilder.setError(ObjectiveError.BADPARAMS); + return resultBuilder.build(); + } + PortCriterion inPortCriterion = (PortCriterion) filterObjective.key(); + VlanIdCriterion vlanCriterion = filterObjective.conditions().stream() + .filter(criterion -> criterion.type() == Criterion.Type.VLAN_VID) + .map(criterion -> (VlanIdCriterion) criterion) + .findFirst() + .orElse(null); + EthCriterion ethDstCriterion = filterObjective.conditions().stream() + .filter(criterion -> criterion.type() == Criterion.Type.ETH_DST) + .map(criterion -> (EthCriterion) criterion) + .findFirst() + .orElse(null); + + FlowRule inPortVlanTableRule = createInPortVlanTable(inPortCriterion, vlanCriterion, + filterObjective); + Collection fwdClassifierRules = createFwdClassifierRules(inPortCriterion, ethDstCriterion, + filterObjective); + + resultBuilder.addFlowRule(inPortVlanTableRule); + fwdClassifierRules.forEach(resultBuilder::addFlowRule); + return resultBuilder.build(); + } + + private FlowRule createInPortVlanTable(Criterion inPortCriterion, + VlanIdCriterion vlanCriterion, + FilteringObjective filterObjective) { + Criterion vlanIsVlalidCriterion; + TrafficSelector.Builder selector = DefaultTrafficSelector.builder() + .add(inPortCriterion); + + VlanId vlanId = null; + if (vlanCriterion != null) { + vlanId = vlanCriterion.vlanId(); + } + + vlanIsVlalidCriterion = VLAN_VALID; + if (vlanId == null || vlanId.equals(VlanId.NONE)) { + // untag vlan, match in port only + vlanIsVlalidCriterion = VLAN_INVALID; + } + + selector.add(vlanIsVlalidCriterion); + + // TODO: check if this treatment is valid or not + TrafficTreatment treatment = filterObjective.meta(); + if (treatment == null) { + treatment = DefaultTrafficTreatment.emptyTreatment(); + } + + return DefaultFlowRule.builder() + .fromApp(filterObjective.appId()) + .withPriority(filterObjective.priority()) + .withSelector(selector.build()) + .withTreatment(treatment) + .withPriority(filterObjective.priority()) + .forTable(FabricConstants.TBL_INGRESS_PORT_VLAN_ID) + .forDevice(deviceId) + .makePermanent() + .build(); + } + + private Collection createFwdClassifierRules(PortCriterion inPortCriterion, + EthCriterion ethDstCriterion, + FilteringObjective filterObjective) { + Collection flowRules = Sets.newHashSet(); + if (ethDstCriterion == null) { + // Bridging table, do nothing + return flowRules; + } + PortNumber port = inPortCriterion.port(); + MacAddress dstMac = ethDstCriterion.mac(); + if (dstMac.isMulticast()) { + flowRules.add(createMulticastFwdClassifierRule(port, dstMac, filterObjective)); + return flowRules; + } + + flowRules.addAll(createIpFwdClassifierRules(port, dstMac, filterObjective)); + flowRules.add(createMplsFwdClassifierRule(port, dstMac, filterObjective)); + return flowRules; + } + + private FlowRule createMulticastFwdClassifierRule(PortNumber inPort, MacAddress dstMac, + FilteringObjective filterObjective) { + TrafficTreatment treatment; + short ethType; + if (dstMac.equals(MacAddress.IPV4_MULTICAST)) { + // Ipv4 multicast + treatment = createFwdClassifierTreatment(FWD_IPV4_MULTICAST); + ethType = Ethernet.TYPE_IPV4; + } else { + // IPv6 multicast + treatment = createFwdClassifierTreatment(FWD_IPV6_MULTICAST); + ethType = Ethernet.TYPE_IPV6; + } + return createFwdClassifierRule(inPort, ethType, dstMac, treatment, filterObjective); + } + + private Collection createIpFwdClassifierRules(PortNumber inPort, + MacAddress dstMac, + FilteringObjective filterObjective) { + Collection flowRules = Sets.newHashSet(); + TrafficTreatment treatment; + treatment = createFwdClassifierTreatment(FWD_IPV4_UNICAST); + flowRules.add(createFwdClassifierRule(inPort, Ethernet.TYPE_IPV4, dstMac, treatment, filterObjective)); + treatment = createFwdClassifierTreatment(FWD_IPV6_UNICAST); + flowRules.add(createFwdClassifierRule(inPort, Ethernet.TYPE_IPV6, dstMac, treatment, filterObjective)); + return flowRules; + } + + private FlowRule createMplsFwdClassifierRule(PortNumber inPort, + MacAddress dstMac, + FilteringObjective filterObjective) { + TrafficTreatment treatment = createFwdClassifierTreatment(FWD_MPLS); + return createFwdClassifierRule(inPort, Ethernet.MPLS_UNICAST, dstMac, treatment, filterObjective); + } + + private FlowRule createFwdClassifierRule(PortNumber inPort, + short ethType, + MacAddress dstMac, + TrafficTreatment treatment, + FilteringObjective filterObjective) { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder() + .matchInPort(inPort) + .matchEthDst(dstMac) + .matchEthType(ethType); + + return DefaultFlowRule.builder() + .withSelector(selector.build()) + .withTreatment(treatment) + .fromApp(filterObjective.appId()) + .withPriority(filterObjective.priority()) + .forDevice(deviceId) + .makePermanent() + .forTable(FabricConstants.TBL_FWD_CLASSIFIER_ID) + .build(); + } + + private TrafficTreatment createFwdClassifierTreatment(byte fwdType) { + PiActionParam param = new PiActionParam(FabricConstants.ACT_PRM_FWD_TYPE_ID, + ImmutableByteSequence.copyFrom(fwdType)); + PiAction action = PiAction.builder() + .withId(FabricConstants.ACT_SET_FORWARDING_TYPE_ID) + .withParameter(param) + .build(); + return DefaultTrafficTreatment.builder() + .piTableAction(action) + .build(); + + } +} diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipeliner.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipeliner.java new file mode 100644 index 0000000000..d37402d118 --- /dev/null +++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipeliner.java @@ -0,0 +1,211 @@ +/* + * 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.ImmutableSet; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onlab.util.ImmutableByteSequence; +import org.onosproject.net.DeviceId; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +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.EthCriterion; +import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.pi.runtime.PiAction; +import org.onosproject.net.pi.runtime.PiActionParam; +import org.onosproject.pipelines.fabric.FabricConstants; +import org.slf4j.Logger; + +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Handling forwarding objective for fabric pipeliner. + */ +public class FabricForwardingPipeliner { + private static final Logger log = getLogger(FabricForwardingPipeliner.class); + + protected DeviceId deviceId; + + public FabricForwardingPipeliner(DeviceId deviceId) { + this.deviceId = deviceId; + } + + public PipelinerTranslationResult forward(ForwardingObjective forwardObjective) { + PipelinerTranslationResult.Builder resultBuilder = PipelinerTranslationResult.builder(); + if (forwardObjective.flag() == ForwardingObjective.Flag.VERSATILE) { + processVersatileFwd(forwardObjective, resultBuilder); + } else { + processSpecificFwd(forwardObjective, resultBuilder); + } + return resultBuilder.build(); + } + + private void processVersatileFwd(ForwardingObjective fwd, + PipelinerTranslationResult.Builder resultBuilder) { + // program ACL table only + FlowRule flowRule = DefaultFlowRule.builder() + .withSelector(fwd.selector()) + .withTreatment(fwd.treatment()) + .forTable(FabricConstants.TBL_ACL_ID) + .withPriority(fwd.priority()) + .forDevice(deviceId) + .makePermanent() + .fromApp(fwd.appId()) + .build(); + resultBuilder.addFlowRule(flowRule); + } + + private void processSpecificFwd(ForwardingObjective fwd, + PipelinerTranslationResult.Builder resultBuilder) { + TrafficSelector selector = fwd.selector(); + TrafficSelector meta = fwd.meta(); + + ImmutableSet.Builder criterionSetBuilder = ImmutableSet.builder(); + criterionSetBuilder.addAll(selector.criteria()); + + if (meta != null) { + criterionSetBuilder.addAll(meta.criteria()); + } + + Set criteria = criterionSetBuilder.build(); + + VlanIdCriterion vlanIdCriterion = null; + EthCriterion ethDstCriterion = null; + + for (Criterion criterion : criteria) { + switch (criterion.type()) { + case ETH_DST: + ethDstCriterion = (EthCriterion) criterion; + break; + case VLAN_VID: + vlanIdCriterion = (VlanIdCriterion) criterion; + break; + default: + log.warn("Unsupported criterion {}", criterion); + break; + } + } + + ForwardingFunctionType forwardingFunctionType = + ForwardingFunctionType.getForwardingFunctionType(fwd); + switch (forwardingFunctionType) { + case L2_UNICAST: + processL2UnicastRule(vlanIdCriterion, ethDstCriterion, fwd, resultBuilder); + break; + case L2_BROADCAST: + processL2BroadcastRule(vlanIdCriterion, fwd, resultBuilder); + break; + case IPV4_UNICAST: + case IPV4_MULTICAST: + case IPV6_UNICAST: + case IPV6_MULTICAST: + case MPLS: + default: + log.warn("Unsupported forwarding function type {}", criteria); + resultBuilder.setError(ObjectiveError.UNSUPPORTED); + break; + } + } + + // L2 Unicast: learnt mac address + vlan + private void processL2UnicastRule(VlanIdCriterion vlanIdCriterion, + EthCriterion ethDstCriterion, + ForwardingObjective fwd, + PipelinerTranslationResult.Builder resultBuilder) { + checkNotNull(vlanIdCriterion, "VlanId criterion should not be null"); + checkNotNull(ethDstCriterion, "EthDst criterion should not be null"); + + if (fwd.nextId() == null) { + log.warn("Forwarding objective for L2 unicast should contains next id"); + resultBuilder.setError(ObjectiveError.BADPARAMS); + return; + } + + VlanId vlanId = vlanIdCriterion.vlanId(); + MacAddress ethDst = ethDstCriterion.mac(); + + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchVlanId(vlanId) + .matchEthDst(ethDst) + .build(); + TrafficTreatment treatment = buildSetNextIdTreatment(fwd.nextId()); + FlowRule flowRule = DefaultFlowRule.builder() + .withSelector(selector) + .withTreatment(treatment) + .fromApp(fwd.appId()) + .withPriority(fwd.priority()) + .makePermanent() + .forDevice(deviceId) + .forTable(FabricConstants.TBL_BRIDGING_ID) + .build(); + + resultBuilder.addFlowRule(flowRule); + } + + private void processL2BroadcastRule(VlanIdCriterion vlanIdCriterion, + ForwardingObjective fwd, + PipelinerTranslationResult.Builder resultBuilder) { + checkNotNull(vlanIdCriterion, "VlanId criterion should not be null"); + if (fwd.nextId() == null) { + log.warn("Forwarding objective for L2 broadcast should contains next id"); + resultBuilder.setError(ObjectiveError.BADPARAMS); + return; + } + + VlanId vlanId = vlanIdCriterion.vlanId(); + + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchVlanId(vlanId) + .build(); + TrafficTreatment treatment = buildSetNextIdTreatment(fwd.nextId()); + FlowRule flowRule = DefaultFlowRule.builder() + .withSelector(selector) + .withTreatment(treatment) + .fromApp(fwd.appId()) + .withPriority(fwd.priority()) + .makePermanent() + .forDevice(deviceId) + .forTable(FabricConstants.TBL_BRIDGING_ID) + .build(); + + resultBuilder.addFlowRule(flowRule); + } + + private static TrafficTreatment buildSetNextIdTreatment(Integer nextId) { + PiActionParam nextIdParam = new PiActionParam(FabricConstants.ACT_PRM_NEXT_ID_ID, + ImmutableByteSequence.copyFrom(nextId.byteValue())); + PiAction nextIdAction = PiAction.builder() + .withId(FabricConstants.ACT_SET_NEXT_ID_ID) + .withParameter(nextIdParam) + .build(); + + return DefaultTrafficTreatment.builder() + .piTableAction(nextIdAction) + .build(); + } + +} diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java new file mode 100644 index 0000000000..7487fbe7b8 --- /dev/null +++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java @@ -0,0 +1,159 @@ +/* + * 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.ImmutableMap; +import org.onlab.util.ImmutableByteSequence; +import org.onosproject.net.DeviceId; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.PiCriterion; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions.OutputInstruction; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.pi.runtime.PiAction; +import org.onosproject.net.pi.runtime.PiActionParam; +import org.onosproject.pipelines.fabric.FabricConstants; +import org.slf4j.Logger; + +import java.util.Map; + +import static org.onosproject.pipelines.fabric.pipeliner.FabricPipeliner.fail; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Handling next objective for fabric pipeliner. + */ +public class FabricNextPipeliner { + private static final Logger log = getLogger(FabricNextPipeliner.class); + + // Next types + private static final byte TBL_SIMPLE = 0; + private static final byte TBL_HASHED = 1; + private static final byte TBL_BROADCAST = 2; + private static final byte PUNT = 3; + private static final Map NEXT_TYPE_MAP = + ImmutableMap.builder() + .put(NextObjective.Type.SIMPLE, TBL_SIMPLE) + .put(NextObjective.Type.HASHED, TBL_HASHED) + .put(NextObjective.Type.BROADCAST, TBL_BROADCAST) + .build(); + + protected DeviceId deviceId; + + public FabricNextPipeliner(DeviceId deviceId) { + this.deviceId = deviceId; + } + + public PipelinerTranslationResult next(NextObjective nextObjective) { + PipelinerTranslationResult.Builder resultBuilder = PipelinerTranslationResult.builder(); + FlowRule nextIdMappingRule = processNextIdMapping(nextObjective); + FlowRule nextRule = null; + switch (nextObjective.type()) { + case SIMPLE: + nextRule = processSimpleNext(nextObjective); + break; + default: + log.warn("Unsupported next type {}", nextObjective); + resultBuilder.setError(ObjectiveError.UNSUPPORTED); + break; + } + + if (nextIdMappingRule != null && nextRule != null) { + resultBuilder.addFlowRule(nextIdMappingRule); + resultBuilder.addFlowRule(nextRule); + } + + return resultBuilder.build(); + } + + private FlowRule processNextIdMapping(NextObjective next) { + // program the next id mapping table + TrafficSelector nextIdSelector = buildNextIdSelector(next.id()); + TrafficTreatment setNextTypeTreatment = buildSetNextTypeTreatment(next.type()); + + return DefaultFlowRule.builder() + .withSelector(nextIdSelector) + .withTreatment(setNextTypeTreatment) + .forDevice(deviceId) + .forTable(FabricConstants.TBL_NEXT_ID_MAPPING_ID) + .makePermanent() + .withPriority(next.priority()) + .fromApp(next.appId()) + .build(); + } + + private FlowRule processSimpleNext(NextObjective next) { + if (next.next().size() > 1) { + log.warn("Only one treatment in simple next objective"); + fail(next, ObjectiveError.BADPARAMS); + return null; + } + + TrafficSelector selector = buildNextIdSelector(next.id()); + TrafficTreatment treatment = next.next().iterator().next(); + OutputInstruction outputInst = treatment.allInstructions() + .stream() + .filter(inst -> inst.type() == Instruction.Type.OUTPUT) + .map(inst -> (OutputInstruction) inst) + .findFirst() + .orElse(null); + + if (outputInst == null) { + log.warn("At least one output instruction in simple next objective"); + fail(next, ObjectiveError.BADPARAMS); + return null; + } + return DefaultFlowRule.builder() + .withSelector(selector) + .withTreatment(treatment) + .forTable(FabricConstants.TBL_SIMPLE_ID) + .makePermanent() + .withPriority(next.priority()) + .forDevice(deviceId) + .fromApp(next.appId()) + .build(); + } + + private TrafficSelector buildNextIdSelector(int nextId) { + byte[] nextIdVal = new byte[]{(byte) nextId}; + PiCriterion nextIdCrriterion = PiCriterion.builder() + .matchExact(FabricConstants.HF_FABRIC_METADATA_NEXT_ID_ID, nextIdVal) + .build(); + return DefaultTrafficSelector.builder() + .matchPi(nextIdCrriterion) + .build(); + } + + private TrafficTreatment buildSetNextTypeTreatment(NextObjective.Type nextType) { + byte nextTypeVal = NEXT_TYPE_MAP.getOrDefault(nextType, PUNT); + PiActionParam nextTypeParam = new PiActionParam(FabricConstants.ACT_PRM_NEXT_TYPE_ID, + ImmutableByteSequence.copyFrom(nextTypeVal)); + PiAction nextTypeAction = PiAction.builder() + .withId(FabricConstants.ACT_SET_NEXT_TYPE_ID) + .withParameter(nextTypeParam) + .build(); + return DefaultTrafficTreatment.builder() + .piTableAction(nextTypeAction) + .build(); + } +} diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipeliner.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipeliner.java new file mode 100644 index 0000000000..e2d8048fb1 --- /dev/null +++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipeliner.java @@ -0,0 +1,303 @@ +/* + * 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.ImmutableList; +import com.google.common.collect.Lists; +import org.onlab.util.KryoNamespace; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.behaviour.NextGroup; +import org.onosproject.net.behaviour.Pipeliner; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.FlowObjectiveStore; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.group.GroupEvent; +import org.onosproject.net.group.GroupListener; +import org.onosproject.net.group.GroupService; +import org.onosproject.store.serializers.KryoNamespaces; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Pipeliner for fabric pipeline. + */ +public class FabricPipeliner extends AbstractHandlerBehaviour implements Pipeliner { + private static final Logger log = getLogger(FabricPipeliner.class); + + protected static final KryoNamespace KRYO = new KryoNamespace.Builder() + .register(KryoNamespaces.API) + .register(FabricNextGroup.class) + .build("FabricPipeliner"); + + // TODO: make this configurable + private static final long DEFAULT_INSTALLATION_TIME_OUT = 10; + + protected DeviceId deviceId; + protected FlowRuleService flowRuleService; + protected GroupService groupService; + protected FlowObjectiveStore flowObjectiveStore; + protected FabricFilteringPipeliner pipelinerFilter; + protected FabricForwardingPipeliner pipelinerForward; + protected FabricNextPipeliner pipelinerNext; + + + @Override + public void init(DeviceId deviceId, PipelinerContext context) { + this.deviceId = deviceId; + this.flowRuleService = context.directory().get(FlowRuleService.class); + this.groupService = context.directory().get(GroupService.class); + this.flowObjectiveStore = context.directory().get(FlowObjectiveStore.class); + this.pipelinerFilter = new FabricFilteringPipeliner(deviceId); + this.pipelinerForward = new FabricForwardingPipeliner(deviceId); + this.pipelinerNext = new FabricNextPipeliner(deviceId); + } + + @Override + public void filter(FilteringObjective filterObjective) { + PipelinerTranslationResult result = pipelinerFilter.filter(filterObjective); + if (result.error().isPresent()) { + fail(filterObjective, result.error().get()); + return; + } + + applyTranslationResult(filterObjective, result, success -> { + if (success) { + success(filterObjective); + } else { + fail(filterObjective, ObjectiveError.FLOWINSTALLATIONFAILED); + } + }); + } + + @Override + public void forward(ForwardingObjective forwardObjective) { + PipelinerTranslationResult result = pipelinerForward.forward(forwardObjective); + if (result.error().isPresent()) { + fail(forwardObjective, result.error().get()); + return; + } + + applyTranslationResult(forwardObjective, result, success -> { + if (success) { + success(forwardObjective); + } else { + fail(forwardObjective, ObjectiveError.FLOWINSTALLATIONFAILED); + } + }); + } + + @Override + public void next(NextObjective nextObjective) { + PipelinerTranslationResult result = pipelinerNext.next(nextObjective); + + if (result.error().isPresent()) { + fail(nextObjective, result.error().get()); + return; + } + + applyTranslationResult(nextObjective, result, success -> { + if (!success) { + fail(nextObjective, ObjectiveError.GROUPINSTALLATIONFAILED); + return; + } + + // Success, put next group to objective store + List portNumbers = Lists.newArrayList(); + nextObjective.next().forEach(treatment -> { + Instructions.OutputInstruction outputInst = treatment.allInstructions() + .stream() + .filter(inst -> inst.type() == Instruction.Type.OUTPUT) + .map(inst -> (Instructions.OutputInstruction) inst) + .findFirst() + .orElse(null); + + if (outputInst != null) { + portNumbers.add(outputInst.port()); + } + }); + FabricNextGroup nextGroup = new FabricNextGroup(nextObjective.type(), + portNumbers); + flowObjectiveStore.putNextGroup(nextObjective.id(), nextGroup); + success(nextObjective); + }); + } + + @Override + public List getNextMappings(NextGroup nextGroup) { + return null; + } + + private void applyTranslationResult(Objective objective, + PipelinerTranslationResult result, + Consumer callback) { + Collection groups = result.groups(); + Collection flowRules = result.flowRules(); + CompletableFuture.supplyAsync(() -> installGroups(objective, groups)) + .thenApplyAsync(groupSuccess -> groupSuccess && installFlows(objective, flowRules)) + .thenAcceptAsync(callback) + .exceptionally((ex) -> { + log.warn("Got unexpected exception while applying translation result {}", + result); + fail(objective, ObjectiveError.UNKNOWN); + return null; + }); + } + + private boolean installFlows(Objective objective, Collection flowRules) { + if (flowRules.isEmpty()) { + return true; + } + CompletableFuture flowInstallFuture = new CompletableFuture<>(); + FlowRuleOperationsContext ctx = new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + flowInstallFuture.complete(true); + } + + @Override + public void onError(FlowRuleOperations ops) { + log.warn("Failed to install flow rules: {}", flowRules); + flowInstallFuture.complete(false); + } + }; + + FlowRuleOperations ops = buildFlowRuleOps(objective, flowRules, ctx); + flowRuleService.apply(ops); + + try { + return flowInstallFuture.get(DEFAULT_INSTALLATION_TIME_OUT, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + log.warn("Got exception while installing groups: {}", e); + return false; + } + } + + private boolean installGroups(Objective objective, Collection groups) { + if (groups.isEmpty()) { + return true; + } + int numGroupsToBeInstalled = groups.size(); + CompletableFuture groupInstallFuture = new CompletableFuture<>(); + AtomicInteger numGroupsInstalled = new AtomicInteger(0); + GroupListener listener = new GroupListener() { + @Override + public void event(GroupEvent event) { + int currentNumGroupInstalled = numGroupsInstalled.incrementAndGet(); + if (currentNumGroupInstalled == numGroupsToBeInstalled) { + // install completed + groupService.removeListener(this); + groupInstallFuture.complete(true); + } + } + @Override + public boolean isRelevant(GroupEvent event) { + return groups.contains(event.subject()); + } + }; + groupService.addListener(listener); + + switch (objective.op()) { + case ADD: + groups.forEach(groupService::addGroup); + break; + case REMOVE: + groups.forEach(group -> groupService.removeGroup(deviceId, group.appCookie(), objective.appId())); + break; + default: + log.warn("Unsupported objective operation {}", objective.op()); + groupService.removeListener(listener); + } + try { + return groupInstallFuture.get(DEFAULT_INSTALLATION_TIME_OUT, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + log.warn("Got exception while installing groups: {}", e); + return false; + } + } + + static void fail(Objective objective, ObjectiveError error) { + objective.context().ifPresent(ctx -> ctx.onError(objective, error)); + } + + static void success(Objective objective) { + objective.context().ifPresent(ctx -> ctx.onSuccess(objective)); + } + + static FlowRuleOperations buildFlowRuleOps(Objective objective, Collection flowRules, + FlowRuleOperationsContext ctx) { + FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); + switch (objective.op()) { + case ADD: + flowRules.forEach(ops::add); + break; + case REMOVE: + flowRules.forEach(ops::remove); + break; + default: + log.warn("Unsupported op {} for {}", objective); + fail(objective, ObjectiveError.BADPARAMS); + return null; + } + return ops.build(ctx); + } + + class FabricNextGroup implements NextGroup { + private NextObjective.Type type; + private Collection outputPorts; + + public FabricNextGroup(NextObjective.Type type, Collection outputPorts) { + this.type = type; + this.outputPorts = ImmutableList.copyOf(outputPorts); + } + + public NextObjective.Type type() { + return type; + } + + public Collection outputPorts() { + return outputPorts; + } + + @Override + public byte[] data() { + return KRYO.serialize(this); + } + } +} diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionType.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionType.java new file mode 100644 index 0000000000..8d8de5409a --- /dev/null +++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionType.java @@ -0,0 +1,122 @@ +/* + * 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.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flowobjective.ForwardingObjective; + +import java.util.Map; +import java.util.Set; + +import static org.onosproject.net.flow.criteria.Criterion.Type.ETH_DST; +import static org.onosproject.net.flow.criteria.Criterion.Type.ETH_TYPE; +import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST; +import static org.onosproject.net.flow.criteria.Criterion.Type.IPV6_DST; +import static org.onosproject.net.flow.criteria.Criterion.Type.MPLS_BOS; +import static org.onosproject.net.flow.criteria.Criterion.Type.MPLS_LABEL; +import static org.onosproject.net.flow.criteria.Criterion.Type.VLAN_VID; + +public enum ForwardingFunctionType { + /** + * L2 unicast, with vlan id + mac address criterion. + */ + L2_UNICAST, + + /** + * L2 broadcast, with vlan id criterion only. + */ + L2_BROADCAST, + + /** + * IPv4 unicast, with EtherType and IPv4 unicast destination address. + */ + IPV4_UNICAST, + + /** + * IPv4 multicast, with EtherType and IPv4 multicast destination address. + */ + IPV4_MULTICAST, + + /** + * IPv6 unicast, with EtherType and IPv6 unicast destination address. + */ + IPV6_UNICAST, + + /** + * IPv6 multicast, with EtherType and IPv6 multicast destination address. + */ + IPV6_MULTICAST, + + /** + * MPLS, with EtherType, MPLS label and MPLS BOS criterion. + */ + MPLS, + + /** + * Unsupported type. + */ + UNSUPPORTED; + + // Different criteria combinations for different FFT + private static final Set L2_UNI_CRITERIA_TYPE = + ImmutableSet.of(VLAN_VID, ETH_DST); + private static final Set L2_BRC_CRITERIA_TYPE = + ImmutableSet.of(VLAN_VID); + private static final Set IPV4_UNI_CRITERIA_TYPE = + ImmutableSet.of(ETH_TYPE, IPV4_DST); + private static final Set IPV4_MCAST_CRITERIA_TYPE = + ImmutableSet.of(ETH_TYPE, VLAN_VID, IPV4_DST); + private static final Set IPV6_UNI_CRITERIA_TYPE = + ImmutableSet.of(ETH_TYPE, IPV6_DST); + private static final Set IPV6_MCAST_CRITERIA_TYPE = + ImmutableSet.of(ETH_TYPE, VLAN_VID, IPV6_DST); + private static final Set MPLS_UNI_CRITERIA_TYPE = + ImmutableSet.of(ETH_TYPE, MPLS_LABEL, MPLS_BOS); + + private static final Map, ForwardingFunctionType> FFT_MAP = + ImmutableMap., ForwardingFunctionType>builder() + .put(L2_UNI_CRITERIA_TYPE, L2_UNICAST) + .put(L2_BRC_CRITERIA_TYPE, L2_BROADCAST) + .put(IPV4_UNI_CRITERIA_TYPE, IPV4_UNICAST) + .put(IPV4_MCAST_CRITERIA_TYPE, IPV4_MULTICAST) + .put(IPV6_UNI_CRITERIA_TYPE, IPV6_UNICAST) + .put(IPV6_MCAST_CRITERIA_TYPE, IPV6_MULTICAST) + .put(MPLS_UNI_CRITERIA_TYPE, MPLS) + .build(); + + /** + * Gets forwarding function type of the forwarding objective. + * + * @param fwd the forwarding objective + * @return forwarding function type of the forwarding objective + */ + public static ForwardingFunctionType getForwardingFunctionType(ForwardingObjective fwd) { + Set criteriaType = Sets.newHashSet(); + fwd.selector().criteria().stream().map(Criterion::type) + .forEach(criteriaType::add); + + if (fwd.meta() != null) { + fwd.meta().criteria().stream().map(Criterion::type) + .forEach(criteriaType::add); + } + + return FFT_MAP.getOrDefault(criteriaType, UNSUPPORTED); + } +} diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/PipelinerTranslationResult.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/PipelinerTranslationResult.java new file mode 100644 index 0000000000..575d590ab3 --- /dev/null +++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/PipelinerTranslationResult.java @@ -0,0 +1,154 @@ +/* + * 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.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.group.GroupDescription; + +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; + +/** + * Translation results from fabric pipeliner. + */ +public final class PipelinerTranslationResult { + private Collection flowRules; + private Collection groups; + private ObjectiveError error; + + private PipelinerTranslationResult(Collection flowRules, + Collection groups, + ObjectiveError error) { + this.flowRules = flowRules; + this.groups = groups; + this.error = error; + } + + /** + * Gets flow rules from result. + * + * @return flow rules + */ + public Collection flowRules() { + return flowRules; + } + + /** + * Gets groups from result. + * + * @return groups + */ + public Collection groups() { + return groups; + } + + /** + * Gets error from result. + * + * @return error of the result; empty if there is no error + */ + public Optional error() { + return Optional.ofNullable(error); + } + + /** + * Creates a new builder. + * + * @return the builder + */ + public static Builder builder() { + return new Builder(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("flowRules", flowRules) + .add("groups", groups) + .add("error", error) + .toString(); + } + + @Override + public int hashCode() { + return Objects.hash(flowRules, groups, error); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final PipelinerTranslationResult other = (PipelinerTranslationResult) obj; + return Objects.equals(this.flowRules, other.flowRules) + && Objects.equals(this.groups, other.groups) + && Objects.equals(this.error, other.error); + } + + /** + * Builder for PipelinerTranslationResult. + */ + public static final class Builder { + private ImmutableList.Builder flowRules = ImmutableList.builder(); + private ImmutableList.Builder groups = ImmutableList.builder(); + private ObjectiveError error = null; + + // Hide default constructor + private Builder() { + } + + /** + * Adds flow rule to the result. + * + * @param flowRule the flow rule + */ + public void addFlowRule(FlowRule flowRule) { + flowRules.add(flowRule); + } + + /** + * Adds group to the result. + * + * @param group the group + */ + public void addGroup(GroupDescription group) { + groups.add(group); + } + + /** + * Sets objective error to the result. + * + * @param error the error + */ + public void setError(ObjectiveError error) { + this.error = error; + } + + public PipelinerTranslationResult build() { + return new PipelinerTranslationResult(flowRules.build(), + groups.build(), + error); + } + } +} diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/package-info.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/package-info.java new file mode 100644 index 0000000000..75bf40ed6b --- /dev/null +++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Pipeliner for fabric.p4. + */ +package org.onosproject.pipelines.fabric.pipeliner; \ No newline at end of file diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipelinerTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipelinerTest.java new file mode 100644 index 0000000000..f3de7b7314 --- /dev/null +++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricFilteringPipelinerTest.java @@ -0,0 +1,355 @@ +/* + * 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.junit.Test; +import org.onlab.packet.Ethernet; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onlab.util.ImmutableByteSequence; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TableId; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criteria; +import org.onosproject.net.flowobjective.DefaultFilteringObjective; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.pi.runtime.PiAction; +import org.onosproject.net.pi.runtime.PiActionParam; +import org.onosproject.pipelines.fabric.FabricConstants; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Test cases for fabric.p4 pipeline filtering control block. + */ +public class FabricFilteringPipelinerTest extends FabricPipelinerTest { + + /** + * Creates one rule for ingress_port_vlan table and 3 rules for + * fwd_classifier table (IPv4, IPv6 and MPLS unicast) when + * the condition is VLAN + MAC. + */ + @Test + public void testRouterMacAndVlanFilter() { + FilteringObjective filteringObjective = buildFilteringObjective(ROUTER_MAC); + PipelinerTranslationResult result = pipeliner.pipelinerFilter.filter(filteringObjective); + + List flowRulesInstalled = (List) result.flowRules(); + List groupsInstalled = (List) result.groups(); + + assertTrue(groupsInstalled.isEmpty()); + + // in port vlan flow rule + FlowRule actualFlowRule = flowRulesInstalled.get(0); + FlowRule flowRuleExpected = buildExpectedVlanInPortRule(PORT_1, + VlanId.NONE, + VLAN_100, + FabricConstants.TBL_INGRESS_PORT_VLAN_ID); + assertTrue(flowRuleExpected.exactMatch(actualFlowRule)); + + // forwarding classifier ipv4 + actualFlowRule = flowRulesInstalled.get(1); + flowRuleExpected = buildExpectedFwdClassifierRule(PORT_1, + ROUTER_MAC, + Ethernet.TYPE_IPV4, + FWD_IPV4_UNICAST); + assertTrue(flowRuleExpected.exactMatch(actualFlowRule)); + + // forwarding classifier ipv6 + actualFlowRule = flowRulesInstalled.get(2); + flowRuleExpected = buildExpectedFwdClassifierRule(PORT_1, + ROUTER_MAC, + Ethernet.TYPE_IPV6, + FWD_IPV6_UNICAST); + assertTrue(flowRuleExpected.exactMatch(actualFlowRule)); + + // forwarding classifier mpls + actualFlowRule = flowRulesInstalled.get(3); + flowRuleExpected = buildExpectedFwdClassifierRule(PORT_1, + ROUTER_MAC, + Ethernet.MPLS_UNICAST, + FWD_MPLS); + assertTrue(flowRuleExpected.exactMatch(actualFlowRule)); + } + + /** + * Creates one rule for ingress_port_vlan table and one rule for + * fwd_classifier table (IPv4 multicast) when the condition is ipv4 + * multicast mac address. + */ + @Test + public void testIpv4MulticastFwdClass() { + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .pushVlan() + .setVlanId(VLAN_100) + .build(); + FilteringObjective filteringObjective = DefaultFilteringObjective.builder() + .permit() + .withPriority(PRIORITY) + .withKey(Criteria.matchInPort(PORT_1)) + .addCondition(Criteria.matchEthDst(MacAddress.IPV4_MULTICAST)) + .addCondition(Criteria.matchVlanId(VlanId.NONE)) + .withMeta(treatment) + .fromApp(APP_ID) + .makePermanent() + .add(); + PipelinerTranslationResult result = pipeliner.pipelinerFilter.filter(filteringObjective); + List flowRulesInstalled = (List) result.flowRules(); + List groupsInstalled = (List) result.groups(); + + assertTrue(groupsInstalled.isEmpty()); + + // in port vlan flow rule + FlowRule actualFlowRule = flowRulesInstalled.get(0); + FlowRule flowRuleExpected = buildExpectedVlanInPortRule(PORT_1, + VlanId.NONE, + VLAN_100, + FabricConstants.TBL_INGRESS_PORT_VLAN_ID); + assertTrue(flowRuleExpected.exactMatch(actualFlowRule)); + + // forwarding classifier + actualFlowRule = flowRulesInstalled.get(1); + flowRuleExpected = buildExpectedFwdClassifierRule(PORT_1, + MacAddress.IPV4_MULTICAST, + Ethernet.TYPE_IPV4, + FWD_IPV4_MULTICAST); + assertTrue(flowRuleExpected.exactMatch(actualFlowRule)); + } + + /** + * Creates one rule for ingress_port_vlan table and one rule for + * fwd_classifier table (IPv6 multicast) when the condition is ipv6 + * multicast mac address. + */ + @Test + public void testIpv6MulticastFwdClass() { + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .pushVlan() + .setVlanId(VLAN_100) + .build(); + FilteringObjective filteringObjective = DefaultFilteringObjective.builder() + .permit() + .withPriority(PRIORITY) + .withKey(Criteria.matchInPort(PORT_1)) + .addCondition(Criteria.matchEthDst(MacAddress.IPV6_MULTICAST)) + .addCondition(Criteria.matchVlanId(VlanId.NONE)) + .withMeta(treatment) + .fromApp(APP_ID) + .makePermanent() + .add(); + PipelinerTranslationResult result = pipeliner.pipelinerFilter.filter(filteringObjective); + List flowRulesInstalled = (List) result.flowRules(); + List groupsInstalled = (List) result.groups(); + + assertTrue(groupsInstalled.isEmpty()); + + // in port vlan flow rule + FlowRule actualFlowRule = flowRulesInstalled.get(0); + FlowRule flowRuleExpected = buildExpectedVlanInPortRule(PORT_1, + VlanId.NONE, + VLAN_100, + FabricConstants.TBL_INGRESS_PORT_VLAN_ID); + assertTrue(flowRuleExpected.exactMatch(actualFlowRule)); + + // forwarding classifier + actualFlowRule = flowRulesInstalled.get(1); + flowRuleExpected = buildExpectedFwdClassifierRule(PORT_1, + MacAddress.IPV6_MULTICAST, + Ethernet.TYPE_IPV6, + FWD_IPV6_MULTICAST); + assertTrue(flowRuleExpected.exactMatch(actualFlowRule)); + } + + /** + * Creates only one rule for ingress_port_vlan table if there is no condition + * of destination mac address. + * The packet will be handled by bridging table by default. + */ + @Test + public void testFwdBridging() { + FilteringObjective filteringObjective = buildFilteringObjective(null); + PipelinerTranslationResult result = pipeliner.pipelinerFilter.filter(filteringObjective); + List flowRulesInstalled = (List) result.flowRules(); + List groupsInstalled = (List) result.groups(); + + assertTrue(groupsInstalled.isEmpty()); + + // in port vlan flow rule + FlowRule actualFlowRule = flowRulesInstalled.get(0); + FlowRule flowRuleExpected = buildExpectedVlanInPortRule(PORT_1, + VlanId.NONE, + VLAN_100, + FabricConstants.TBL_INGRESS_PORT_VLAN_ID); + assertTrue(flowRuleExpected.exactMatch(actualFlowRule)); + + // No rules in forwarding classifier, will do default action: set fwd type to bridging + } + + /** + * We supports only PERMIT type of filtering objective. + */ + @Test + public void testUnsupportedObjective() { + FilteringObjective filteringObjective = DefaultFilteringObjective.builder() + .deny() + .withKey(Criteria.matchInPort(PORT_1)) + .addCondition(Criteria.matchVlanId(VLAN_100)) + .fromApp(APP_ID) + .makePermanent() + .add(); + + PipelinerTranslationResult result = pipeliner.pipelinerFilter.filter(filteringObjective); + pipeliner.pipelinerFilter.filter(filteringObjective); + + List flowRulesInstalled = (List) result.flowRules(); + List groupsInstalled = (List) result.groups(); + + assertTrue(flowRulesInstalled.isEmpty()); + assertTrue(groupsInstalled.isEmpty()); + + assertTrue(result.error().isPresent()); + ObjectiveError error = result.error().get(); + assertEquals(ObjectiveError.UNSUPPORTED, error); + } + + /** + * Incorrect filtering key or filtering conditions test. + */ + @Test + public void badParamTest() { + // Filtering objective should contains filtering key + FilteringObjective filteringObjective = DefaultFilteringObjective.builder() + .permit() + .addCondition(Criteria.matchVlanId(VLAN_100)) + .fromApp(APP_ID) + .makePermanent() + .add(); + + PipelinerTranslationResult result = pipeliner.pipelinerFilter.filter(filteringObjective); + pipeliner.pipelinerFilter.filter(filteringObjective); + + assertTrue(result.error().isPresent()); + ObjectiveError error = result.error().get(); + assertEquals(ObjectiveError.BADPARAMS, error); + + // Filtering objective should use in_port as key + filteringObjective = DefaultFilteringObjective.builder() + .permit() + .withKey(Criteria.matchEthDst(ROUTER_MAC)) + .addCondition(Criteria.matchVlanId(VLAN_100)) + .withMeta(DefaultTrafficTreatment.emptyTreatment()) + .fromApp(APP_ID) + .makePermanent() + .add(); + + result = pipeliner.pipelinerFilter.filter(filteringObjective); + pipeliner.pipelinerFilter.filter(filteringObjective); + + assertTrue(result.error().isPresent()); + error = result.error().get(); + assertEquals(ObjectiveError.BADPARAMS, error); + } + + /* Utilities */ + + private FilteringObjective buildFilteringObjective(MacAddress dstMac) { + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .pushVlan() + .setVlanId(VLAN_100) + .build(); + DefaultFilteringObjective.Builder builder = DefaultFilteringObjective.builder() + .permit() + .withPriority(PRIORITY) + .withKey(Criteria.matchInPort(PORT_1)); + if (dstMac != null) { + builder.addCondition(Criteria.matchEthDst(dstMac)); + } + + builder.addCondition(Criteria.matchVlanId(VlanId.NONE)) + .withMeta(treatment) + .fromApp(APP_ID) + .makePermanent(); + return builder.add(); + } + + private FlowRule buildExpectedVlanInPortRule(PortNumber inPort, VlanId vlanId, + VlanId internalVlan, + TableId tableId) { + + TrafficSelector.Builder selector = DefaultTrafficSelector.builder() + .matchInPort(inPort); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + if (vlanId == null || vlanId.equals(VlanId.NONE)) { + selector.matchPi(VLAN_INVALID); + treatment.pushVlan(); + treatment.setVlanId(internalVlan); + } else { + selector.matchPi(VLAN_VALID); + selector.matchVlanId(vlanId); + } + + return DefaultFlowRule.builder() + .withPriority(PRIORITY) + .withSelector(selector.build()) + .withTreatment(treatment.build()) + .fromApp(APP_ID) + .forDevice(DEVICE_ID) + .makePermanent() + .forTable(tableId) + .build(); + } + + private FlowRule buildExpectedFwdClassifierRule(PortNumber inPort, + MacAddress dstMac, + short ethType, + byte fwdClass) { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthDst(dstMac) + .matchInPort(inPort) + .matchEthType(ethType) + .build(); + PiActionParam classParam = new PiActionParam(FabricConstants.ACT_PRM_FWD_TYPE_ID, + ImmutableByteSequence.copyFrom(fwdClass)); + PiAction fwdClassifierAction = PiAction.builder() + .withId(FabricConstants.ACT_SET_FORWARDING_TYPE_ID) + .withParameter(classParam) + .build(); + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .piTableAction(fwdClassifierAction) + .build(); + + return DefaultFlowRule.builder() + .withPriority(PRIORITY) + .withSelector(selector) + .withTreatment(treatment) + .fromApp(APP_ID) + .forDevice(DEVICE_ID) + .makePermanent() + .forTable(FabricConstants.TBL_FWD_CLASSIFIER_ID) + .build(); + } +} diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipelineTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipelineTest.java new file mode 100644 index 0000000000..d58082753a --- /dev/null +++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricForwardingPipelineTest.java @@ -0,0 +1,260 @@ +/* + * 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.junit.Ignore; +import org.junit.Test; +import org.onlab.packet.Ethernet; +import org.onlab.packet.IPv4; +import org.onlab.packet.TpPort; +import org.onlab.packet.UDP; +import org.onlab.util.ImmutableByteSequence; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flowobjective.DefaultForwardingObjective; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.pi.model.PiTableId; +import org.onosproject.net.pi.runtime.PiAction; +import org.onosproject.net.pi.runtime.PiActionParam; +import org.onosproject.pipelines.fabric.FabricConstants; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Test cases for fabric.p4 pipeline forwarding control block. + */ +public class FabricForwardingPipelineTest extends FabricPipelinerTest { + + /** + * Test versatile flag of forwarding objective with ARP match. + */ + @Test + public void testAclArp() { + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .wipeDeferred() + .punt() + .build(); + // ARP + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_ARP) + .build(); + ForwardingObjective fwd = DefaultForwardingObjective.builder() + .withSelector(selector) + .withPriority(PRIORITY) + .fromApp(APP_ID) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withTreatment(treatment) + .add(); + + PipelinerTranslationResult result = pipeliner.pipelinerForward.forward(fwd); + + List flowRulesInstalled = (List) result.flowRules(); + List groupsInstalled = (List) result.groups(); + assertEquals(1, flowRulesInstalled.size()); + assertTrue(groupsInstalled.isEmpty()); + + FlowRule actualFlowRule = flowRulesInstalled.get(0); + FlowRule expectedFlowRule = DefaultFlowRule.builder() + .forDevice(DEVICE_ID) + .forTable(FabricConstants.TBL_ACL_ID) + .withPriority(PRIORITY) + .makePermanent() + .withSelector(selector) + .withTreatment(treatment) + .fromApp(APP_ID) + .build(); + + assertTrue(expectedFlowRule.exactMatch(actualFlowRule)); + } + + /** + * Test versatile flag of forwarding objective with DHCP match. + */ + @Test + public void testAclDhcp() { + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .wipeDeferred() + .punt() + .build(); + // DHCP + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV4) + .matchIPProtocol(IPv4.PROTOCOL_UDP) + .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT)) + .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT)) + .build(); + ForwardingObjective fwd = DefaultForwardingObjective.builder() + .withSelector(selector) + .withPriority(PRIORITY) + .fromApp(APP_ID) + .makePermanent() + .withFlag(ForwardingObjective.Flag.VERSATILE) + .withTreatment(treatment) + .add(); + + PipelinerTranslationResult result = pipeliner.pipelinerForward.forward(fwd); + + List flowRulesInstalled = (List) result.flowRules(); + List groupsInstalled = (List) result.groups(); + assertEquals(1, flowRulesInstalled.size()); + assertTrue(groupsInstalled.isEmpty()); + + FlowRule actualFlowRule = flowRulesInstalled.get(0); + FlowRule expectedFlowRule = DefaultFlowRule.builder() + .forDevice(DEVICE_ID) + .forTable(FabricConstants.TBL_ACL_ID) + .withPriority(PRIORITY) + .makePermanent() + .withSelector(selector) + .withTreatment(treatment) + .fromApp(APP_ID) + .build(); + + assertTrue(expectedFlowRule.exactMatch(actualFlowRule)); + } + + /** + * Test programming L2 unicast rule to bridging table. + */ + @Test + public void testL2Unicast() { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchVlanId(VLAN_100) + .matchEthDst(HOST_MAC) + .build(); + testSpecificForward(FabricConstants.TBL_BRIDGING_ID, selector, selector, NEXT_ID_1); + } + + @Test + public void testL2Broadcast() { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchVlanId(VLAN_100) + .build(); + testSpecificForward(FabricConstants.TBL_BRIDGING_ID, selector, selector, NEXT_ID_1); + } + + @Test + @Ignore + public void testIPv4Unicast() { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV4) + .matchIPDst(IPV4_UNICAST_ADDR) + .build(); + TrafficSelector expectedSelector = DefaultTrafficSelector.builder() + .matchIPDst(IPV4_UNICAST_ADDR) + .build(); + testSpecificForward(FabricConstants.TBL_UNICAST_V4_ID, expectedSelector, selector, NEXT_ID_1); + } + + @Test + @Ignore + public void testIPv4Multicast() { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV4) + .matchVlanId(VLAN_100) + .matchIPDst(IPV4_MCAST_ADDR) + .build(); + TrafficSelector expectedSelector = DefaultTrafficSelector.builder() + .matchIPDst(IPV4_MCAST_ADDR) + .build(); + testSpecificForward(FabricConstants.TBL_MULTICAST_V4_ID, expectedSelector, selector, NEXT_ID_1); + } + + @Test + @Ignore + public void testIPv6Unicast() { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV6) + .matchIPDst(IPV6_UNICAST_ADDR) + .build(); + TrafficSelector expectedSelector = DefaultTrafficSelector.builder() + .matchIPDst(IPV6_UNICAST_ADDR) + .build(); + testSpecificForward(FabricConstants.TBL_UNICAST_V6_ID, expectedSelector, selector, NEXT_ID_1); + } + + @Test + @Ignore + public void testIPv6Multicast() { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV6) + .matchVlanId(VLAN_100) + .matchIPDst(IPV6_MCAST_ADDR) + .build(); + TrafficSelector expectedSelector = DefaultTrafficSelector.builder() + .matchIPDst(IPV6_MCAST_ADDR) + .build(); + testSpecificForward(FabricConstants.TBL_MULTICAST_V6_ID, expectedSelector, selector, NEXT_ID_1); + } + + @Test + @Ignore + public void testMpls() { + + } + + private void testSpecificForward(PiTableId expectedTableId, TrafficSelector expectedSelector, + TrafficSelector selector, Integer nextId) { + ForwardingObjective fwd = DefaultForwardingObjective.builder() + .withSelector(selector) + .withPriority(PRIORITY) + .fromApp(APP_ID) + .makePermanent() + .withFlag(ForwardingObjective.Flag.SPECIFIC) + .nextStep(nextId) + .add(); + + PipelinerTranslationResult result = pipeliner.pipelinerForward.forward(fwd); + + List flowRulesInstalled = (List) result.flowRules(); + List groupsInstalled = (List) result.groups(); + assertEquals(1, flowRulesInstalled.size()); + assertTrue(groupsInstalled.isEmpty()); + + FlowRule actualFlowRule = flowRulesInstalled.get(0); + PiActionParam nextIdParam = new PiActionParam(FabricConstants.ACT_PRM_NEXT_ID_ID, + ImmutableByteSequence.copyFrom(nextId.byteValue())); + PiAction setNextIdAction = PiAction.builder() + .withId(FabricConstants.ACT_SET_NEXT_ID_ID) + .withParameter(nextIdParam) + .build(); + TrafficTreatment setNextIdTreatment = DefaultTrafficTreatment.builder() + .piTableAction(setNextIdAction) + .build(); + + FlowRule expectedFlowRule = DefaultFlowRule.builder() + .forDevice(DEVICE_ID) + .forTable(expectedTableId) + .withPriority(PRIORITY) + .makePermanent() + .withSelector(expectedSelector) + .withTreatment(setNextIdTreatment) + .fromApp(APP_ID) + .build(); + + assertTrue(expectedFlowRule.exactMatch(actualFlowRule)); + } +} diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java new file mode 100644 index 0000000000..107752c820 --- /dev/null +++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipelinerTest.java @@ -0,0 +1,151 @@ +/* + * 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.junit.Ignore; +import org.junit.Test; +import org.onlab.util.ImmutableByteSequence; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.PiCriterion; +import org.onosproject.net.flowobjective.DefaultNextObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.pi.runtime.PiAction; +import org.onosproject.net.pi.runtime.PiActionParam; +import org.onosproject.pipelines.fabric.FabricConstants; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Test cases for fabric.p4 pipeline next control block. + */ +public class FabricNextPipelinerTest extends FabricPipelinerTest { + + /** + * Test program output rule for Simple table. + */ + @Test + public void testSimpleOutput() { + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PORT_1) + .build(); + testSimple(treatment); + } + + /** + * Test program set vlan and output rule for Simple table. + */ + @Test + public void testSimpleOutputWithVlanTranslation() { + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setVlanId(VLAN_100) + .setOutput(PORT_1) + .build(); + testSimple(treatment); + } + + private void testSimple(TrafficTreatment treatment) { + NextObjective nextObjective = DefaultNextObjective.builder() + .withId(NEXT_ID_1) + .withPriority(PRIORITY) + .addTreatment(treatment) + .withType(NextObjective.Type.SIMPLE) + .makePermanent() + .fromApp(APP_ID) + .add(); + + PipelinerTranslationResult result = pipeliner.pipelinerNext.next(nextObjective); + + List flowRulesInstalled = (List) result.flowRules(); + List groupsInstalled = (List) result.groups(); + assertEquals(2, flowRulesInstalled.size()); + assertTrue(groupsInstalled.isEmpty()); + + FlowRule actualFlowRule; + FlowRule expectedFlowRule; + + // Next id mapping table + actualFlowRule = flowRulesInstalled.get(0); + byte[] nextIdVal = new byte[]{NEXT_ID_1.byteValue()}; + PiCriterion nextIdCriterion = PiCriterion.builder() + .matchExact(FabricConstants.HF_FABRIC_METADATA_NEXT_ID_ID, nextIdVal) + .build(); + TrafficSelector nextIdSelector = DefaultTrafficSelector.builder() + .matchPi(nextIdCriterion) + .build(); + PiActionParam setNextToSimpleParam = new PiActionParam(FabricConstants.ACT_PRM_NEXT_TYPE_ID, + ImmutableByteSequence.copyFrom(NEXT_TYPE_SIMPLE)); + PiAction setNextToSimpleAction = PiAction.builder() + .withId(FabricConstants.ACT_SET_NEXT_TYPE_ID) + .withParameter(setNextToSimpleParam) + .build(); + TrafficTreatment setNextTypeTreatment = DefaultTrafficTreatment.builder() + .piTableAction(setNextToSimpleAction) + .build(); + expectedFlowRule = DefaultFlowRule.builder() + .forDevice(DEVICE_ID) + .fromApp(APP_ID) + .makePermanent() + // FIXME: currently next objective doesn't support priority, set priority to zero + .withPriority(0) + .forTable(FabricConstants.TBL_NEXT_ID_MAPPING_ID) + .withSelector(nextIdSelector) + .withTreatment(setNextTypeTreatment) + .build(); + assertTrue(expectedFlowRule.exactMatch(actualFlowRule)); + + // Simple table + actualFlowRule = flowRulesInstalled.get(1); + expectedFlowRule = DefaultFlowRule.builder() + .forDevice(DEVICE_ID) + .fromApp(APP_ID) + .makePermanent() + // FIXME: currently next objective doesn't support priority, ignore this + .withPriority(0) + .forTable(FabricConstants.TBL_SIMPLE_ID) + .withSelector(nextIdSelector) + .withTreatment(treatment) + .build(); + assertTrue(expectedFlowRule.exactMatch(actualFlowRule)); + } + + /** + * Test program ecmp output group for Hashed table. + */ + @Test + @Ignore + public void testHashedOutput() { + + } + + /** + * Test program output group for Broadcast table. + */ + @Test + @Ignore + public void testBroadcastOutput() { + + } +} diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerTest.java new file mode 100644 index 0000000000..30610ed1d7 --- /dev/null +++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/FabricPipelinerTest.java @@ -0,0 +1,86 @@ +/* + * 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.junit.Before; +import org.onlab.osgi.ServiceDirectory; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onlab.packet.VlanId; +import org.onosproject.TestApplicationId; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.flow.criteria.PiCriterion; +import org.onosproject.pipelines.fabric.FabricConstants; + +import static org.easymock.EasyMock.createNiceMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; + +public abstract class FabricPipelinerTest { + static final ApplicationId APP_ID = TestApplicationId.create("FabricPipelinerTest"); + static final DeviceId DEVICE_ID = DeviceId.deviceId("device:bmv2:11"); + static final int PRIORITY = 100; + static final PortNumber PORT_1 = PortNumber.portNumber(1); + static final VlanId VLAN_100 = VlanId.vlanId("100"); + static final MacAddress HOST_MAC = MacAddress.valueOf("00:00:00:00:00:01"); + static final MacAddress ROUTER_MAC = MacAddress.valueOf("00:00:00:00:02:01"); + static final IpPrefix IPV4_UNICAST_ADDR = IpPrefix.valueOf("10.0.0.1/32"); + static final IpPrefix IPV4_MCAST_ADDR = IpPrefix.valueOf("224.0.0.1/32"); + static final IpPrefix IPV6_UNICAST_ADDR = IpPrefix.valueOf("2000::1/32"); + static final IpPrefix IPV6_MCAST_ADDR = IpPrefix.valueOf("ff00::1/32"); + static final MplsLabel MPLS_10 = MplsLabel.mplsLabel(10); + static final Integer NEXT_ID_1 = 1; + + // Forwarding types + static final byte FWD_BRIDGING = 0; + static final byte FWD_MPLS = 1; + static final byte FWD_IPV4_UNICAST = 2; + static final byte FWD_IPV4_MULTICAST = 3; + static final byte FWD_IPV6_UNICAST = 4; + static final byte FWD_IPV6_MULTICAST = 5; + + // Next types + static final byte NEXT_TYPE_SIMPLE = 0; + static final byte NEXT_TYPE_HASHED = 1; + static final byte NEXT_TYPE_BROADCAST = 2; + static final byte NEXT_TYPE_PUNT = 3; + + static final PiCriterion VLAN_VALID = PiCriterion.builder() + .matchExact(FabricConstants.HF_VLAN_TAG_IS_VALID_ID, new byte[]{1}) + .build(); + static final PiCriterion VLAN_INVALID = PiCriterion.builder() + .matchExact(FabricConstants.HF_VLAN_TAG_IS_VALID_ID, new byte[]{0}) + .build(); + + FabricPipeliner pipeliner; + + @Before + public void setup() { + pipeliner = new FabricPipeliner(); + + ServiceDirectory serviceDirectory = createNiceMock(ServiceDirectory.class); + PipelinerContext pipelinerContext = createNiceMock(PipelinerContext.class); + expect(pipelinerContext.directory()).andReturn(serviceDirectory).anyTimes(); + replay(serviceDirectory, pipelinerContext); + + pipeliner.init(DEVICE_ID, pipelinerContext); + } +} diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionTypeTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionTypeTest.java new file mode 100644 index 0000000000..9c68cd6f56 --- /dev/null +++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/pipeliner/ForwardingFunctionTypeTest.java @@ -0,0 +1,130 @@ +/* + * 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.junit.Ignore; +import org.junit.Test; +import org.onlab.packet.Ethernet; +import org.onlab.packet.IpPrefix; +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onlab.packet.VlanId; +import org.onosproject.TestApplicationId; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flowobjective.DefaultForwardingObjective; +import org.onosproject.net.flowobjective.ForwardingObjective; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for Forwarding class. + */ +public class ForwardingFunctionTypeTest { + private static final ApplicationId APP_ID = TestApplicationId.create("ForwardingFunctionTypeTest"); + private static final VlanId VLAN_100 = VlanId.vlanId((short) 100); + private static final MacAddress MAC_ADDR = MacAddress.valueOf("00:00:00:00:00:01"); + private static final IpPrefix IPV4_UNICAST_ADDR = IpPrefix.valueOf("10.0.0.1/32"); + private static final IpPrefix IPV4_MCAST_ADDR = IpPrefix.valueOf("224.0.0.1/32"); + private static final IpPrefix IPV6_UNICAST_ADDR = IpPrefix.valueOf("2000::1/32"); + private static final IpPrefix IPV6_MCAST_ADDR = IpPrefix.valueOf("ff00::1/32"); + private static final MplsLabel MPLS_10 = MplsLabel.mplsLabel(10); + private TrafficSelector selector; + + /** + * Match Vlan + EthDst. + */ + @Test + public void testL2Unicast() { + selector = DefaultTrafficSelector.builder() + .matchVlanId(VLAN_100) + .matchEthDst(MAC_ADDR) + .build(); + testFft(selector, ForwardingFunctionType.L2_UNICAST); + } + + @Test + public void testL2Broadcast() { + selector = DefaultTrafficSelector.builder() + .matchVlanId(VLAN_100) + .build(); + testFft(selector, ForwardingFunctionType.L2_BROADCAST); + } + + @Test + @Ignore + public void testIpv4Unicast() { + selector = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV4) + .matchIPDst(IPV4_UNICAST_ADDR) + .build(); + testFft(selector, ForwardingFunctionType.IPV4_UNICAST); + } + + @Test + @Ignore + public void testIpv4Multicast() { + selector = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV4) + .matchIPDst(IPV4_MCAST_ADDR) + .build(); + testFft(selector, ForwardingFunctionType.IPV4_MULTICAST); + } + + @Test + @Ignore + public void testIpv6Unicast() { + selector = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV6) + .matchIPDst(IPV6_UNICAST_ADDR) + .build(); + testFft(selector, ForwardingFunctionType.IPV6_UNICAST); + } + + @Test + @Ignore + public void testIpv6Multicast() { + selector = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV6) + .matchIPDst(IPV6_MCAST_ADDR) + .build(); + testFft(selector, ForwardingFunctionType.IPV4_MULTICAST); + } + + @Test + @Ignore + public void testMplsUnicast() { + selector = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.MPLS_UNICAST) + .matchMplsLabel(MPLS_10) + .matchMplsBos(true) + .build(); + testFft(selector, ForwardingFunctionType.MPLS); + } + + private void testFft(TrafficSelector selector, ForwardingFunctionType expectedFft) { + ForwardingObjective fwd = DefaultForwardingObjective.builder() + .withSelector(selector) + .withFlag(ForwardingObjective.Flag.SPECIFIC) + .nextStep(0) + .fromApp(APP_ID) + .add(); + assertEquals(expectedFft, + ForwardingFunctionType.getForwardingFunctionType(fwd)); + } +}