Support creation of vendor-specific versions of the fabric pipeconf

We provide a new service to facilitate registration of vendor-specific
versions of the Fabric pipeconf (e.g., for Tofino) from third-party
apps. This service is designed such that third-party apps do not need to
depend on internal classes at compile time, such as the behaviour
implementations.

To make this possible, the package structure has been refactored to
separate APIs from implementation.

Change-Id: I487cb806541eb9e6877ebf398a94f057613df8cc
diff --git a/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ForwardingObjectiveTranslator.java b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ForwardingObjectiveTranslator.java
new file mode 100644
index 0000000..1d7d5c0
--- /dev/null
+++ b/pipelines/fabric/impl/src/main/java/org/onosproject/pipelines/fabric/impl/behaviour/pipeliner/ForwardingObjectiveTranslator.java
@@ -0,0 +1,352 @@
+/*
+ * 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.impl.behaviour.pipeliner;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.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.IPCriterion;
+import org.onosproject.net.flow.criteria.MplsCriterion;
+import org.onosproject.net.flow.criteria.VlanIdCriterion;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.ObjectiveError;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.pi.model.PiActionId;
+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.impl.behaviour.FabricCapabilities;
+import org.onosproject.pipelines.fabric.impl.behaviour.FabricConstants;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static org.onosproject.net.group.DefaultGroupBucket.createCloneGroupBucket;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.criterionNotNull;
+import static org.onosproject.pipelines.fabric.impl.behaviour.FabricUtils.outputPort;
+
+
+/**
+ * ObjectiveTranslator implementation ForwardingObjective.
+ */
+class ForwardingObjectiveTranslator
+        extends AbstractObjectiveTranslator<ForwardingObjective> {
+
+    //FIXME: Max number supported by PI
+    static final int CLONE_TO_CPU_ID = 511;
+    private static final List<String> DEFAULT_ROUTE_PREFIXES = Lists.newArrayList(
+            "0.0.0.0/1", "128.0.0.0/1");
+
+    private static final Set<Criterion.Type> ACL_CRITERIA = ImmutableSet.of(
+            Criterion.Type.IN_PORT,
+            Criterion.Type.IN_PHY_PORT,
+            Criterion.Type.ETH_DST,
+            Criterion.Type.ETH_DST_MASKED,
+            Criterion.Type.ETH_SRC,
+            Criterion.Type.ETH_SRC_MASKED,
+            Criterion.Type.ETH_TYPE,
+            Criterion.Type.VLAN_VID,
+            Criterion.Type.IP_PROTO,
+            Criterion.Type.IPV4_SRC,
+            Criterion.Type.IPV4_DST,
+            Criterion.Type.TCP_SRC,
+            Criterion.Type.TCP_SRC_MASKED,
+            Criterion.Type.TCP_DST,
+            Criterion.Type.TCP_DST_MASKED,
+            Criterion.Type.UDP_SRC,
+            Criterion.Type.UDP_SRC_MASKED,
+            Criterion.Type.UDP_DST,
+            Criterion.Type.UDP_DST_MASKED,
+            Criterion.Type.ICMPV4_TYPE,
+            Criterion.Type.ICMPV4_CODE,
+            Criterion.Type.PROTOCOL_INDEPENDENT);
+
+    private static final Map<PiTableId, PiActionId> NEXT_ID_ACTIONS = ImmutableMap.<PiTableId, PiActionId>builder()
+            .put(FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING,
+                 FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_BRIDGING)
+            .put(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4,
+                 FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_ROUTING_V4)
+            .put(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V6,
+                 FabricConstants.FABRIC_INGRESS_FORWARDING_SET_NEXT_ID_ROUTING_V6)
+            .put(FabricConstants.FABRIC_INGRESS_FORWARDING_MPLS,
+                 FabricConstants.FABRIC_INGRESS_FORWARDING_POP_MPLS_AND_NEXT)
+            .build();
+
+    ForwardingObjectiveTranslator(DeviceId deviceId, FabricCapabilities capabilities) {
+        super(deviceId, capabilities);
+    }
+
+    @Override
+    public ObjectiveTranslation doTranslate(ForwardingObjective obj)
+            throws FabricPipelinerException {
+        final ObjectiveTranslation.Builder resultBuilder =
+                ObjectiveTranslation.builder();
+        switch (obj.flag()) {
+            case SPECIFIC:
+                processSpecificFwd(obj, resultBuilder);
+                break;
+            case VERSATILE:
+                processVersatileFwd(obj, resultBuilder);
+                break;
+            case EGRESS:
+            default:
+                log.warn("Unsupported ForwardingObjective type '{}'", obj.flag());
+                return ObjectiveTranslation.ofError(ObjectiveError.UNSUPPORTED);
+        }
+        return resultBuilder.build();
+    }
+
+    private void processVersatileFwd(ForwardingObjective obj,
+                                     ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        final Set<Criterion.Type> unsupportedCriteria = obj.selector().criteria()
+                .stream()
+                .map(Criterion::type)
+                .filter(t -> !ACL_CRITERIA.contains(t))
+                .collect(Collectors.toSet());
+
+        if (!unsupportedCriteria.isEmpty()) {
+            throw new FabricPipelinerException(format(
+                    "unsupported ACL criteria %s", unsupportedCriteria.toString()));
+        }
+
+        aclRule(obj, resultBuilder);
+    }
+
+    private void processSpecificFwd(ForwardingObjective obj,
+                                    ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        final Set<Criterion> criteriaWithMeta = Sets.newHashSet(obj.selector().criteria());
+
+        // FIXME: Is this really needed? Meta is such an ambiguous field...
+        // Why would we match on a META field?
+        if (obj.meta() != null) {
+            criteriaWithMeta.addAll(obj.meta().criteria());
+        }
+
+        final ForwardingFunctionType fft = ForwardingFunctionType.getForwardingFunctionType(obj);
+
+        switch (fft) {
+            case UNKNOWN:
+                throw new FabricPipelinerException(
+                        "unable to detect forwarding function type");
+            case L2_UNICAST:
+                bridgingRule(obj, criteriaWithMeta, resultBuilder, false);
+                break;
+            case L2_BROADCAST:
+                bridgingRule(obj, criteriaWithMeta, resultBuilder, true);
+                break;
+            case IPV4_ROUTING:
+            case IPV4_ROUTING_MULTICAST:
+                ipv4RoutingRule(obj, criteriaWithMeta, resultBuilder);
+                break;
+            case MPLS_SEGMENT_ROUTING:
+                mplsRule(obj, criteriaWithMeta, resultBuilder);
+                break;
+            case IPV6_ROUTING:
+            case IPV6_ROUTING_MULTICAST:
+            default:
+                throw new FabricPipelinerException(format(
+                        "unsupported forwarding function type '%s'",
+                        fft));
+        }
+    }
+
+    private void bridgingRule(ForwardingObjective obj, Set<Criterion> criteriaWithMeta,
+                              ObjectiveTranslation.Builder resultBuilder,
+                              boolean broadcast)
+            throws FabricPipelinerException {
+
+        final VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) criterionNotNull(
+                criteriaWithMeta, Criterion.Type.VLAN_VID);
+        final TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
+                .add(vlanIdCriterion);
+
+        if (!broadcast) {
+            final EthCriterion ethDstCriterion = (EthCriterion) criterionNotNull(
+                    obj.selector(), Criterion.Type.ETH_DST);
+            selector.matchEthDstMasked(ethDstCriterion.mac(), MacAddress.EXACT_MASK);
+        }
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING, selector.build()));
+    }
+
+    private void ipv4RoutingRule(ForwardingObjective obj, Set<Criterion> criteriaWithMeta,
+                                 ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+        final IPCriterion ipDstCriterion = (IPCriterion) criterionNotNull(
+                criteriaWithMeta, Criterion.Type.IPV4_DST);
+
+        if (ipDstCriterion.ip().prefixLength() == 0) {
+            defaultIpv4Route(obj, resultBuilder);
+            return;
+        }
+
+        final TrafficSelector selector = DefaultTrafficSelector.builder()
+                .add(ipDstCriterion)
+                .build();
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4, selector));
+    }
+
+    private void defaultIpv4Route(ForwardingObjective obj,
+                                  ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        // Hack to work around the inability to program default rules.
+        for (String prefix : DEFAULT_ROUTE_PREFIXES) {
+            final TrafficSelector selector = DefaultTrafficSelector.builder()
+                    .matchIPDst(IpPrefix.valueOf(prefix)).build();
+            resultBuilder.addFlowRule(flowRule(
+                    obj, FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4, selector));
+        }
+    }
+
+    private void mplsRule(ForwardingObjective obj, Set<Criterion> criteriaWithMeta,
+                          ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+
+        final MplsCriterion mplsCriterion = (MplsCriterion) criterionNotNull(
+                criteriaWithMeta, Criterion.Type.MPLS_LABEL);
+        final TrafficSelector selector = DefaultTrafficSelector.builder()
+                .add(mplsCriterion)
+                .build();
+
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_FORWARDING_MPLS, selector));
+    }
+
+    private void aclRule(ForwardingObjective obj,
+                         ObjectiveTranslation.Builder resultBuilder)
+            throws FabricPipelinerException {
+        if (obj.nextId() == null && obj.treatment() != null) {
+            final TrafficTreatment treatment = obj.treatment();
+            final PortNumber outPort = outputPort(treatment);
+            if (outPort != null
+                    && outPort.equals(PortNumber.CONTROLLER)
+                    && treatment.allInstructions().size() == 1) {
+
+                final PiAction aclAction;
+                if (treatment.clearedDeferred()) {
+                    aclAction = PiAction.builder()
+                            .withId(FabricConstants.FABRIC_INGRESS_ACL_PUNT_TO_CPU)
+                            .build();
+                } else {
+                    // Action is SET_CLONE_SESSION_ID
+                    if (obj.op() == Objective.Operation.ADD) {
+                        // Action is ADD, create clone group
+                        final DefaultGroupDescription cloneGroup =
+                                createCloneGroup(obj.appId(),
+                                                 CLONE_TO_CPU_ID,
+                                                 outPort);
+                        resultBuilder.addGroup(cloneGroup);
+                    }
+                    aclAction = PiAction.builder()
+                            .withId(FabricConstants.FABRIC_INGRESS_ACL_SET_CLONE_SESSION_ID)
+                            .withParameter(new PiActionParam(
+                                    FabricConstants.CLONE_ID, CLONE_TO_CPU_ID))
+                            .build();
+                }
+                final TrafficTreatment piTreatment = DefaultTrafficTreatment.builder()
+                        .piTableAction(aclAction)
+                        .build();
+                resultBuilder.addFlowRule(flowRule(
+                        obj, FabricConstants.FABRIC_INGRESS_ACL_ACL, obj.selector(), piTreatment));
+                return;
+            }
+        }
+        resultBuilder.addFlowRule(flowRule(
+                obj, FabricConstants.FABRIC_INGRESS_ACL_ACL, obj.selector()));
+    }
+
+    private DefaultGroupDescription createCloneGroup(
+            ApplicationId appId,
+            int cloneSessionId,
+            PortNumber outPort) {
+        final GroupKey groupKey = new DefaultGroupKey(
+                FabricPipeliner.KRYO.serialize(cloneSessionId));
+
+        final List<GroupBucket> bucketList = ImmutableList.of(
+                createCloneGroupBucket(DefaultTrafficTreatment.builder()
+                                               .setOutput(outPort)
+                                               .build()));
+        final DefaultGroupDescription cloneGroup = new DefaultGroupDescription(
+                deviceId, GroupDescription.Type.CLONE,
+                new GroupBuckets(bucketList),
+                groupKey, cloneSessionId, appId);
+        return cloneGroup;
+    }
+
+    private FlowRule flowRule(
+            ForwardingObjective obj, PiTableId tableId, TrafficSelector selector)
+            throws FabricPipelinerException {
+        return flowRule(obj, tableId, selector, nextIdOrTreatment(obj, tableId));
+    }
+
+    private static TrafficTreatment nextIdOrTreatment(
+            ForwardingObjective obj, PiTableId tableId)
+            throws FabricPipelinerException {
+        if (obj.nextId() == null) {
+            return obj.treatment();
+        } else {
+            if (!NEXT_ID_ACTIONS.containsKey(tableId)) {
+                throw new FabricPipelinerException(format(
+                        "BUG? no next_id action set for table %s", tableId));
+            }
+            return DefaultTrafficTreatment.builder()
+                    .piTableAction(
+                            setNextIdAction(obj.nextId(),
+                                            NEXT_ID_ACTIONS.get(tableId)))
+                    .build();
+        }
+    }
+
+    private static PiAction setNextIdAction(Integer nextId, PiActionId actionId) {
+        final PiActionParam nextIdParam = new PiActionParam(FabricConstants.NEXT_ID, nextId);
+        return PiAction.builder()
+                .withId(actionId)
+                .withParameter(nextIdParam)
+                .build();
+    }
+}