[CORD-576] Implements VPWS transport in OFDPA pipelines

Changes:
- Introduces the emulation of the MPLS-ECMP groups for CPQD;
- Adds a couple of check to avoid the creation of MPLS-ECMP for OFDPA;
- Implements the VPWS transport for OFDPA3;
- Implements the VPWS tranposrt for CPQD-OFDPA2;

Change-Id: I8181fceffa35f73f549e3df07fa353642c9d6872
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2GroupHandler.java
index 39fba4f..7488b32 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/CpqdOfdpa2GroupHandler.java
@@ -28,6 +28,7 @@
 import org.onosproject.net.flow.instructions.Instruction;
 import org.onosproject.net.flow.instructions.Instructions;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.flowobjective.NextObjective;
 import org.onosproject.net.group.DefaultGroupBucket;
 import org.onosproject.net.group.DefaultGroupDescription;
 import org.onosproject.net.group.DefaultGroupKey;
@@ -37,8 +38,13 @@
 import org.onosproject.net.group.GroupKey;
 import org.slf4j.Logger;
 
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
 
+import static org.onosproject.driver.pipeline.Ofdpa2GroupHandler.OfdpaMplsGroupSubType.MPLS_ECMP;
+import static org.onosproject.driver.pipeline.Ofdpa2Pipeline.isNotMplsBos;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -95,7 +101,7 @@
                 innerTtb.add(ins);
             } else {
                 log.warn("Driver does not handle this type of TrafficTreatment"
-                        + " instruction in nextObjectives:  {}", ins.type());
+                                 + " instruction in nextObjectives:  {}", ins.type());
             }
         }
 
@@ -113,8 +119,8 @@
 
         if (vlanid == null) {
             log.error("Driver cannot process an L2/L3 group chain without "
-                            + "egress vlan information for dev: {} port:{}",
-                    deviceId, portNum);
+                              + "egress vlan information for dev: {} port:{}",
+                      deviceId, portNum);
             return null;
         }
 
@@ -141,7 +147,7 @@
             int mplsInterfaceIndex = getNextAvailableIndex();
             int mplsgroupId = MPLS_INTERFACE_TYPE | (SUBTYPE_MASK & mplsInterfaceIndex);
             final GroupKey mplsgroupkey = new DefaultGroupKey(
-                               Ofdpa2Pipeline.appKryo.serialize(mplsInterfaceIndex));
+                    Ofdpa2Pipeline.appKryo.serialize(mplsInterfaceIndex));
             outerTtb.group(new DefaultGroupId(l2groupId));
             // create the mpls-interface group description to wait for the
             // l2 interface group to be processed
@@ -156,14 +162,14 @@
                     mplsgroupId,
                     appId);
             log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}",
-                    deviceId, Integer.toHexString(mplsgroupId),
-                    mplsgroupkey, nextId);
+                      deviceId, Integer.toHexString(mplsgroupId),
+                      mplsgroupkey, nextId);
         } else {
             // outer group is L3Unicast
             int l3unicastIndex = getNextAvailableIndex();
             int l3groupId = L3_UNICAST_TYPE | (TYPE_MASK & l3unicastIndex);
             final GroupKey l3groupkey = new DefaultGroupKey(
-                               Ofdpa2Pipeline.appKryo.serialize(l3unicastIndex));
+                    Ofdpa2Pipeline.appKryo.serialize(l3unicastIndex));
             outerTtb.group(new DefaultGroupId(l2groupId));
             // create the l3unicast group description to wait for the
             // l2 interface group to be processed
@@ -178,8 +184,8 @@
                     l3groupId,
                     appId);
             log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}",
-                    deviceId, Integer.toHexString(l3groupId),
-                    l3groupkey, nextId);
+                      deviceId, Integer.toHexString(l3groupId),
+                      l3groupkey, nextId);
         }
 
         // store l2groupkey with the groupChainElem for the outer-group that depends on it
@@ -199,8 +205,81 @@
                         l2groupId,
                         appId);
         log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
-                deviceId, Integer.toHexString(l2groupId),
-                l2groupkey, nextId);
+                  deviceId, Integer.toHexString(l2groupId),
+                  l2groupkey, nextId);
         return new GroupInfo(l2groupDescription, outerGrpDesc);
     }
+
+    @Override
+    public boolean verifyHashedNextObjective(NextObjective nextObjective) {
+        return true;
+    }
+
+    /**
+     * In OFDPA2 we do not support the MPLS-ECMP, while we do in
+     * CPQD implementation.
+     *
+     * @param nextObjective the hashed next objective to support.
+     */
+    @Override
+    protected void processHashedNextObjective(NextObjective nextObjective) {
+        // The case for MPLS-ECMP. For now, we try to create a MPLS-ECMP for
+        // the transport of a VPWS. The necessary info are contained in the
+        // meta selector. In particular we are looking for the case of BoS==False;
+        TrafficSelector metaSelector = nextObjective.meta();
+        if (metaSelector != null && isNotMplsBos(metaSelector)) {
+            // storage for all group keys in the chain of groups created
+            List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
+            List<GroupInfo> unsentGroups = new ArrayList<>();
+            createHashBucketChains(nextObjective, allGroupKeys, unsentGroups);
+            // now we can create the outermost MPLS ECMP group
+            List<GroupBucket> mplsEcmpGroupBuckets = new ArrayList<>();
+            for (GroupInfo gi : unsentGroups) {
+                // create ECMP bucket to point to the outer group
+                TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
+                ttb.group(new DefaultGroupId(gi.getNextGroupDesc().givenGroupId()));
+                GroupBucket sbucket = DefaultGroupBucket
+                        .createSelectGroupBucket(ttb.build());
+                mplsEcmpGroupBuckets.add(sbucket);
+            }
+            int mplsEcmpIndex = getNextAvailableIndex();
+            int mplsEcmpGroupId = makeMplsForwardingGroupId(MPLS_ECMP, mplsEcmpIndex);
+            GroupKey mplsEmpGroupKey = new DefaultGroupKey(
+                    Ofdpa2Pipeline.appKryo.serialize(mplsEcmpIndex)
+            );
+            GroupDescription mplsEcmpGroupDesc = new DefaultGroupDescription(
+                    deviceId,
+                    GroupDescription.Type.SELECT,
+                    new GroupBuckets(mplsEcmpGroupBuckets),
+                    mplsEmpGroupKey,
+                    mplsEcmpGroupId,
+                    nextObjective.appId()
+            );
+            GroupChainElem mplsEcmpGce = new GroupChainElem(mplsEcmpGroupDesc,
+                                                            mplsEcmpGroupBuckets.size(),
+                                                            false);
+
+            // create objects for local and distributed storage
+            allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(mplsEmpGroupKey));
+            OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObjective);
+
+            // store mplsEcmpGroupKey with the ofdpaGroupChain for the nextObjective
+            // that depends on it
+            updatePendingNextObjective(mplsEmpGroupKey, ofdpaGrp);
+
+            log.debug("Trying MPLS-ECMP: device:{} gid:{} gkey:{} nextId:{}",
+                      deviceId, Integer.toHexString(mplsEcmpGroupId),
+                      mplsEmpGroupKey, nextObjective.id());
+
+            // finally we are ready to send the innermost groups
+            for (GroupInfo gi : unsentGroups) {
+                log.debug("Sending innermost group {} in group chain on device {} ",
+                          Integer.toHexString(gi.getInnerMostGroupDesc().givenGroupId()), deviceId);
+                updatePendingGroups(gi.getNextGroupDesc().appCookie(), mplsEcmpGce);
+                groupService.addGroup(gi.getInnerMostGroupDesc());
+            }
+            return;
+        }
+        super.processHashedNextObjective(nextObjective);
+    }
 }
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2GroupHandler.java
index 60fefcf..2a4de98 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2GroupHandler.java
@@ -77,6 +77,11 @@
 import java.util.stream.Collectors;
 
 import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.driver.pipeline.Ofdpa2GroupHandler.OfdpaMplsGroupSubType.OFDPA_GROUP_TYPE_SHIFT;
+import static org.onosproject.driver.pipeline.Ofdpa2GroupHandler.OfdpaMplsGroupSubType.OFDPA_MPLS_SUBTYPE_SHIFT;
+import static org.onosproject.driver.pipeline.Ofdpa2Pipeline.isNotMplsBos;
+import static org.onosproject.net.flow.criteria.Criterion.Type.VLAN_VID;
+import static org.onosproject.net.flowobjective.NextObjective.Type.HASHED;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -185,6 +190,30 @@
         groupService.addListener(new InnerGroupListener());
     }
 
+    /**
+     * The purpose of this function is to verify if the hashed next
+     * objective is supported by the current pipeline.
+     *
+     * @param nextObjective the hashed objective to verify
+     * @return true if the hashed objective is supported. Otherwise false.
+     */
+    public boolean verifyHashedNextObjective(NextObjective nextObjective) {
+        // if it is not hashed, there is something wrong;
+        if (nextObjective.type() != HASHED) {
+            return false;
+        }
+        // The case non supported is the MPLS-ECMP. For now, we try
+        // to create a MPLS-ECMP for the transport of a VPWS. The
+        // necessary info are contained in the meta selector. In particular
+        // we are looking for the case of BoS==False;
+        TrafficSelector metaSelector = nextObjective.meta();
+        if (metaSelector != null && isNotMplsBos(metaSelector)) {
+            return false;
+        }
+
+        return true;
+    }
+
     //////////////////////////////////////
     //  Group Creation
     //////////////////////////////////////
@@ -206,6 +235,12 @@
                 processBroadcastNextObjective(nextObjective);
                 break;
             case HASHED:
+                if (!verifyHashedNextObjective(nextObjective)) {
+                    log.error("Next Objectives of type hashed not supported. Next Objective Id:{}",
+                              nextObjective.id());
+                    Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
+                    return;
+                }
                 processHashedNextObjective(nextObjective);
                 break;
             case FAILOVER:
@@ -248,9 +283,14 @@
             return;
         }
 
+        boolean isMpls = false;
+        if (nextObj.meta() != null) {
+            isMpls = isNotMplsBos(nextObj.meta());
+        }
+
         // break up simple next objective to GroupChain objects
         GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
-                                              nextObj.appId(), false,
+                                              nextObj.appId(), isMpls,
                                               nextObj.meta());
         if (groupInfo == null) {
             log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId);
@@ -404,7 +444,7 @@
 
         if (vlanid == null && meta != null) {
             // use metadata if available
-            Criterion vidCriterion = meta.getCriterion(Criterion.Type.VLAN_VID);
+            Criterion vidCriterion = meta.getCriterion(VLAN_VID);
             if (vidCriterion != null) {
                 vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
             }
@@ -746,7 +786,7 @@
      *
      * @param nextObj  the nextObjective of type HASHED
      */
-    private void processHashedNextObjective(NextObjective nextObj) {
+    protected void processHashedNextObjective(NextObjective nextObj) {
         // storage for all group keys in the chain of groups created
         List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
         List<GroupInfo> unsentGroups = new ArrayList<>();
@@ -812,7 +852,7 @@
      * @param allGroupKeys  a list to store groupKey for each bucket-group-chain
      * @param unsentGroups  a list to store GroupInfo for each bucket-group-chain
      */
-    private void createHashBucketChains(NextObjective nextObj,
+    protected void createHashBucketChains(NextObjective nextObj,
                                         List<Deque<GroupKey>> allGroupKeys,
                                         List<GroupInfo> unsentGroups) {
         // break up hashed next objective to multiple groups
@@ -839,9 +879,23 @@
             Deque<GroupKey> gkeyChain = new ArrayDeque<>();
             // XXX we only deal with 0 and 1 label push right now
             if (labelsPushed == 0) {
-                GroupInfo nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
-                        nextObj.appId(), false,
-                        nextObj.meta());
+                GroupInfo nolabelGroupInfo;
+                TrafficSelector metaSelector = nextObj.meta();
+                if (metaSelector != null) {
+                    if (isNotMplsBos(metaSelector)) {
+                        nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
+                                        nextObj.appId(), true,
+                                        nextObj.meta());
+                    } else {
+                        nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
+                                        nextObj.appId(), false,
+                                        nextObj.meta());
+                    }
+                } else {
+                    nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
+                                    nextObj.appId(), false,
+                                    nextObj.meta());
+                }
                 if (nolabelGroupInfo == null) {
                     log.error("Could not process nextObj={} in dev:{}",
                             nextObj.id(), deviceId);
@@ -1326,7 +1380,7 @@
     //  Helper Methods and Classes
     //////////////////////////////////////
 
-    private void updatePendingNextObjective(GroupKey key, OfdpaNextGroup value) {
+    protected void updatePendingNextObjective(GroupKey key, OfdpaNextGroup value) {
         List<OfdpaNextGroup> nextList = new CopyOnWriteArrayList<OfdpaNextGroup>();
         nextList.add(value);
         List<OfdpaNextGroup> ret = pendingAddNextObjectives.asMap()
@@ -1489,8 +1543,7 @@
      * @param portNumber Port number
      * @return L2 interface group key
      */
-    protected int l2InterfaceGroupKey(
-            DeviceId deviceId, VlanId vlanId, long portNumber) {
+    protected int l2InterfaceGroupKey(DeviceId deviceId, VlanId vlanId, long portNumber) {
         int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
         long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
         int hash = Objects.hash(deviceId, vlanId, portHigherBits);
@@ -1544,6 +1597,24 @@
             this.innerMostGroupDesc = innerMostGroupDesc;
             this.nextGroupDesc = nextGroupDesc;
         }
+
+        /**
+         * Getter for innerMostGroupDesc.
+         *
+         * @return the inner most group description
+         */
+        public GroupDescription getInnerMostGroupDesc() {
+            return innerMostGroupDesc;
+        }
+
+        /**
+         * Getter for the next group description.
+         *
+         * @return the next group description
+         */
+        public GroupDescription getNextGroupDesc() {
+            return nextGroupDesc;
+        }
     }
 
     /**
@@ -1628,4 +1699,70 @@
                     " device: " + deviceId);
         }
     }
+
+    /**
+     * Helper enum to handle the different MPLS group
+     * types.
+     */
+    protected enum OfdpaMplsGroupSubType {
+
+        MPLS_INTF((short) 0),
+
+        L2_VPN((short) 1),
+
+        L3_VPN((short) 2),
+
+        MPLS_TUNNEL_LABEL_1((short) 3),
+
+        MPLS_TUNNEL_LABEL_2((short) 4),
+
+        MPLS_SWAP_LABEL((short) 5),
+
+        MPLS_ECMP((short) 8);
+
+        private short value;
+
+        public static final int OFDPA_GROUP_TYPE_SHIFT = 28;
+        public static final int OFDPA_MPLS_SUBTYPE_SHIFT = 24;
+
+        OfdpaMplsGroupSubType(short value) {
+            this.value = value;
+        }
+
+        /**
+         * Gets the value as an short.
+         *
+         * @return the value as an short
+         */
+        public short getValue() {
+            return this.value;
+        }
+
+    }
+
+    /**
+     * Creates MPLS Label group id given a sub type and
+     * the index.
+     *
+     * @param subType the MPLS Label group sub type
+     * @param index the index of the group
+     * @return the OFDPA group id
+     */
+    public Integer makeMplsLabelGroupId(OfdpaMplsGroupSubType subType, int index) {
+        index = index & 0x00FFFFFF;
+        return index | (9 << OFDPA_GROUP_TYPE_SHIFT) | (subType.value << OFDPA_MPLS_SUBTYPE_SHIFT);
+    }
+
+    /**
+     * Creates MPLS Forwarding group id given a sub type and
+     * the index.
+     *
+     * @param subType the MPLS forwarding group sub type
+     * @param index the index of the group
+     * @return the OFDPA group id
+     */
+    public Integer makeMplsForwardingGroupId(OfdpaMplsGroupSubType subType, int index) {
+        index = index & 0x00FFFFFF;
+        return index | (10 << OFDPA_GROUP_TYPE_SHIFT) | (subType.value << OFDPA_MPLS_SUBTYPE_SHIFT);
+    }
 }
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2Pipeline.java
index a12b76f..a49e4fa 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa2Pipeline.java
@@ -94,6 +94,8 @@
 import static org.onlab.packet.MacAddress.NONE;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.slf4j.LoggerFactory.getLogger;
+import static org.onosproject.net.flow.criteria.Criterion.Type.MPLS_BOS;
+import static org.onosproject.net.flowobjective.NextObjective.Type.HASHED;
 
 /**
  * Driver for Broadcom's OF-DPA v2.0 TTP.
@@ -107,7 +109,8 @@
     protected static final int MULTICAST_ROUTING_TABLE = 40;
     protected static final int MPLS_TABLE_0 = 23;
     protected static final int MPLS_TABLE_1 = 24;
-    protected static final int MPLS_L3_TYPE = 27;
+    protected static final int MPLS_L3_TYPE_TABLE = 27;
+    protected static final int MPLS_TYPE_TABLE = 29;
     protected static final int BRIDGING_TABLE = 50;
     protected static final int ACL_TABLE = 60;
     protected static final int MAC_LEARNING_TABLE = 254;
@@ -750,8 +753,6 @@
         return Collections.emptySet();
     }
 
-
-
     /**
      * In the OF-DPA 2.0 pipeline, versatile forwarding objectives go to the
      * ACL table.
@@ -1018,7 +1019,7 @@
                 .matchMplsLabel(((MplsCriterion)
                         selector.getCriterion(Criterion.Type.MPLS_LABEL)).label());
             MplsBosCriterion bos = (MplsBosCriterion) selector
-                                        .getCriterion(Criterion.Type.MPLS_BOS);
+                                        .getCriterion(MPLS_BOS);
             if (bos != null) {
                 filteredSelector.matchMplsBos(bos.mplsBos());
             }
@@ -1064,6 +1065,13 @@
                 List<Deque<GroupKey>> gkeys = appKryo.deserialize(next.data());
                 // we only need the top level group's key to point the flow to it
                 Group group = groupService.getGroup(deviceId, gkeys.get(0).peekFirst());
+                if (isNotMplsBos(selector) && group.type().equals(HASHED)) {
+                    log.warn("SR CONTINUE case cannot be handled as MPLS ECMP "
+                                     + "is not implemented in OF-DPA yet. Aborting this flow {} -> next:{}"
+                                     + "in this device {}", fwd.id(), fwd.nextId(), deviceId);
+                    fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED);
+                    return Collections.emptySet();
+                }
                 if (group == null) {
                     log.warn("Group with key:{} for next-id:{} not found in dev:{}",
                              gkeys.get(0).peekFirst(), fwd.nextId(), deviceId);
@@ -1086,7 +1094,7 @@
         }
 
         if (forTableId == MPLS_TABLE_1) {
-            if (mplsNextTable == MPLS_L3_TYPE) {
+            if (mplsNextTable == MPLS_L3_TYPE_TABLE) {
                 Ofdpa3SetMplsType setMplsType = new Ofdpa3SetMplsType(Ofdpa3MplsType.L3_PHP);
                 // set mpls type as apply_action
                 tb.immediate().extension(setMplsType, deviceId);
@@ -1310,6 +1318,16 @@
         return mappings;
     }
 
+    static boolean isMplsBos(TrafficSelector selector) {
+        MplsBosCriterion bosCriterion = (MplsBosCriterion) selector.getCriterion(MPLS_BOS);
+        return bosCriterion != null && bosCriterion.mplsBos();
+    }
+
+    static boolean isNotMplsBos(TrafficSelector selector) {
+        MplsBosCriterion bosCriterion = (MplsBosCriterion) selector.getCriterion(MPLS_BOS);
+        return bosCriterion != null && !bosCriterion.mplsBos();
+    }
+
     protected static VlanId readVlanFromSelector(TrafficSelector selector) {
         if (selector == null) {
             return null;
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3Pipeline.java
index 966933b..895c187 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/Ofdpa3Pipeline.java
@@ -54,6 +54,9 @@
 
     @Override
     protected Collection<FlowRule> processEthTypeSpecific(ForwardingObjective fwd) {
-        return processEthTypeSpecificInternal(fwd, true, MPLS_L3_TYPE);
+        if (isNotMplsBos(fwd.selector())) {
+            return processEthTypeSpecificInternal(fwd, true, MPLS_TYPE_TABLE);
+        }
+        return processEthTypeSpecificInternal(fwd, true, MPLS_L3_TYPE_TABLE);
     }
 }