Handling multiple layers of spines.

Also in this commit:
     - Triggering swap group creation and accounting for it in DestinationSet
     - Fixes in ofdpa2 and ofdpa3 pipeline to allow SR Continue operation
     - Renaming mplsSet in DestinationSet to notBos
     - Removing unused RandomDestinationSet
     - Bug fix in ofdpa driver for swap group chain creation
     - Bug fix in ofdpa driver for verify group operation
     - Better internal bookeeping of device ports and associated neighbors

Change-Id: I2b8f1c4c0b305ef847d57ca7a5320943e06d190d
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2GroupHandler.java
index 401e924..078e120 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2GroupHandler.java
@@ -99,8 +99,9 @@
                 portNum = ((Instructions.OutputInstruction) ins).port().toLong();
                 innerTtb.add(ins);
             } else {
-                log.warn("Driver does not handle this type of TrafficTreatment"
-                                 + " instruction in nextObjectives:  {}", ins.type());
+                log.debug("Driver does not handle this type of TrafficTreatment"
+                        + " instruction in l2l3chain:  {} - {}", ins.type(),
+                         ins);
             }
         }
 
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
index fdee8d6..2c2f3ba 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2GroupHandler.java
@@ -245,14 +245,14 @@
 
     /**
      * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
-     * a chain of groups. The simple Next Objective passed
-     * in by the application has to be broken up into a group chain
-     * comprising of an L3 Unicast Group that points to an L2 Interface
-     * Group which in-turn points to an output port or an MPLS Interface Group
-     * that points to an L2 Interface Group. In some cases, the simple
-     * next Objective can just be an L2 interface without the need for chaining.
-     * Further, if the label is set to the Next objective then the Group chain
-     * MPLS Swap - MPLS Interface - L2 Interface is created
+     * a chain of groups. The simple Next Objective passed in by the application
+     * is broken up into a group chain. The following chains can be created
+     * depending on the parameters in the Next Objective.
+     * 1. L2 Interface group (no chaining)
+     * 2. L3 Unicast group -> L2 Interface group
+     * 3. MPLS Interface group -> L2 Interface group
+     * 4. MPLS Swap group -> MPLS Interface group -> L2 Interface group
+     * 5. PW initiation group chain
      *
      * @param nextObj  the nextObjective of type SIMPLE
      */
@@ -275,7 +275,6 @@
                     mplsSwap = true;
                     mplsLabel = ((L2ModificationInstruction.ModMplsLabelInstruction) l2ins).label();
                 }
-
             }
         }
 
@@ -284,24 +283,20 @@
             return;
         }
 
-        boolean isMpls = false;
-        // In order to understand if it is a pseudo wire related
+        // In order to understand if it is a pseudowire related
         // next objective we look for the tunnel id in the meta.
         boolean isPw = false;
         if (nextObj.meta() != null) {
-            isMpls = isNotMplsBos(nextObj.meta());
-
             TunnelIdCriterion tunnelIdCriterion = (TunnelIdCriterion) nextObj
                     .meta()
                     .getCriterion(TUNNEL_ID);
             if (tunnelIdCriterion != null) {
                 isPw = true;
             }
-
         }
 
         if (mplsSwap && !isPw) {
-            log.debug("Creating a MPLS Swap - MPLS Interface - L2 Interface group chain.");
+            log.debug("Creating a MPLS Swap -> MPLS Interface -> L2 Interface group chain.");
 
             // break up simple next objective to GroupChain objects
             GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
@@ -314,38 +309,44 @@
             }
 
             Deque<GroupKey> gkeyChain = new ArrayDeque<>();
-            gkeyChain.addFirst(groupInfo.innerMostGroupDesc().appCookie());
-            gkeyChain.addFirst(groupInfo.nextGroupDesc().appCookie());
+            gkeyChain.addFirst(groupInfo.innerMostGroupDesc().appCookie()); // l2 interface
+            gkeyChain.addFirst(groupInfo.nextGroupDesc().appCookie()); // mpls interface
 
             // creating the mpls swap group and adding it to the chain
-            GroupChainElem groupChainElem;
-            GroupKey groupKey;
-            GroupDescription groupDescription;
             int nextGid = groupInfo.nextGroupDesc().givenGroupId();
             int index = getNextAvailableIndex();
 
-            groupDescription = createMplsSwap(
+            GroupDescription swapGroupDescription = createMplsSwap(
                     nextGid,
                     OfdpaMplsGroupSubType.MPLS_SWAP_LABEL,
                     index,
                     mplsLabel,
                     nextObj.appId()
             );
+            // ensure swap group is added after L2L3 chain
+            GroupKey swapGroupKey = swapGroupDescription.appCookie();
+            GroupChainElem swapChainElem = new GroupChainElem(swapGroupDescription,
+                                                               1, false, deviceId);
+            updatePendingGroups(groupInfo.nextGroupDesc().appCookie(), swapChainElem);
+            gkeyChain.addFirst(swapGroupKey);
 
-            groupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(index));
-            groupChainElem = new GroupChainElem(groupDescription, 1, false, deviceId);
-            updatePendingGroups(groupInfo.nextGroupDesc().appCookie(), groupChainElem);
-            gkeyChain.addFirst(groupKey);
-
-            // create a new List from singletonList that is mutable
-            OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(Collections.singletonList(gkeyChain), nextObj);
-            updatePendingNextObjective(groupInfo.nextGroupDesc().appCookie(), ofdpaGrp);
+            // ensure nextObjective waits on the outermost groupKey
+            List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
+            allGroupKeys.add(gkeyChain);
+            OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
+            updatePendingNextObjective(swapGroupKey, ofdpaGrp);
 
             // now we are ready to send the l2 groupDescription (inner), as all the stores
             // that will get async replies have been updated. By waiting to update
             // the stores, we prevent nasty race conditions.
             groupService.addGroup(groupInfo.innerMostGroupDesc());
         } else if (!isPw) {
+            boolean isMpls = false;
+            if (nextObj.meta() != null) {
+                isMpls = isNotMplsBos(nextObj.meta());
+            }
+            log.debug("Creating a {} -> L2 Interface group chain.",
+                      (isMpls) ? "MPLS Interface" : "L3 Unicast");
             // break up simple next objective to GroupChain objects
             GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
                                                   nextObj.appId(), isMpls,
@@ -359,8 +360,9 @@
             Deque<GroupKey> gkeyChain = new ArrayDeque<>();
             gkeyChain.addFirst(groupInfo.innerMostGroupDesc().appCookie());
             gkeyChain.addFirst(groupInfo.nextGroupDesc().appCookie());
-            OfdpaNextGroup ofdpaGrp =
-                    new OfdpaNextGroup(Collections.singletonList(gkeyChain), nextObj);
+            List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
+            allGroupKeys.add(gkeyChain);
+            OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
 
             // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
             updatePendingNextObjective(groupInfo.nextGroupDesc().appCookie(), ofdpaGrp);
@@ -424,9 +426,7 @@
                                               int index,
                                               MplsLabel mplsLabel,
                                               ApplicationId applicationId) {
-
         TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
-
         treatment.setMpls(mplsLabel);
 
         // We point the group to the next group.
@@ -436,16 +436,14 @@
         // Finally we build the group description.
         int groupId = makeMplsLabelGroupId(subtype, index);
         GroupKey groupKey = new DefaultGroupKey(
-                Ofdpa2Pipeline.appKryo.serialize(index)
-        );
+                Ofdpa2Pipeline.appKryo.serialize(index));
         return new DefaultGroupDescription(
                 deviceId,
                 INDIRECT,
                 new GroupBuckets(Collections.singletonList(groupBucket)),
                 groupKey,
                 groupId,
-                applicationId
-        );
+                applicationId);
     }
 
     /**
@@ -542,7 +540,8 @@
                 innerTtb.add(ins);
             } else {
                 log.debug("Driver does not handle this type of TrafficTreatment"
-                        + " instruction in nextObjectives:  {}", ins.type());
+                        + " instruction in l2l3chain:  {} - {}", ins.type(),
+                          ins);
             }
         }
 
@@ -963,7 +962,7 @@
             }
 
             Deque<GroupKey> gKeyChain = new ArrayDeque<>();
-            // XXX we only deal with 0 and 1 label push right now
+            // here we only deal with 0 and 1 label push
             if (labelsPushed == 0) {
                 GroupInfo noLabelGroupInfo;
                 TrafficSelector metaSelector = nextObj.meta();
@@ -1661,12 +1660,17 @@
 
         // Detect situation where the next data has more buckets
         // (not duplicates) respect to the next objective
-        if (allActiveKeys.size() > nextObjective.next().size()) {
+        if (allActiveKeys.size() > nextObjective.next().size() &&
+                // ignore specific case of empty group
+                !(nextObjective.next().size() == 0 && allActiveKeys.size() == 1
+                && allActiveKeys.get(0).size() == 1)) {
             log.warn("Mismatch detected between next and flowobjstore for device {}: " +
-                             "nextId:{}, nextObjective-size:{} next-size:{} .. correcting",
-                     deviceId, nextObjective.id(), nextObjective.next().size(), allActiveKeys.size());
-            List<Integer> otherIndices = indicesToRemoveFromNextGroup(allActiveKeys, nextObjective,
-                                                                      groupService, deviceId);
+                    "nextId:{}, nextObjective-size:{} next-size:{} .. correcting",
+                    deviceId, nextObjective.id(), nextObjective.next().size(),
+                    allActiveKeys.size());
+            List<Integer> otherIndices =
+                    indicesToRemoveFromNextGroup(allActiveKeys, nextObjective,
+                                                 groupService, deviceId);
             // Filter out the indices not present
             otherIndices = otherIndices.stream()
                     .filter(index -> !indicesToRemove.contains(index))
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
index b481c3f..0dc8b18 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa2Pipeline.java
@@ -1164,7 +1164,6 @@
         TrafficSelector selector = fwd.selector();
         EthTypeCriterion ethType =
                 (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE);
-        boolean popMpls = false;
         boolean emptyGroup = false;
         int forTableId;
         TrafficSelector.Builder filteredSelector = DefaultTrafficSelector.builder();
@@ -1233,7 +1232,6 @@
                 for (Instruction instr : fwd.treatment().allInstructions()) {
                     if (instr instanceof L2ModificationInstruction &&
                             ((L2ModificationInstruction) instr).subtype() == L2SubType.MPLS_POP) {
-                        popMpls = true;
                         // OF-DPA does not pop in MPLS table in some cases. For the L3 VPN, it requires
                         // setting the MPLS_TYPE so pop can happen down the pipeline
                         if (requireMplsPop()) {
@@ -1266,35 +1264,24 @@
         }
 
         if (fwd.nextId() != null) {
-            if (forTableId == MPLS_TABLE_1 && !popMpls) {
-                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);
-                // XXX We could convert to forwarding to a single-port, via a MPLS interface,
-                // or a MPLS SWAP (with-same) but that would have to be handled in the next-objective.
-                // Also the pop-mpls logic used here won't work in non-BoS case.
-                fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED);
-                return Collections.emptySet();
-            }
-
             NextGroup next = getGroupForNextObjective(fwd.nextId());
             if (next != null) {
                 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(SELECT)) {
-                    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);
                     fail(fwd, ObjectiveError.GROUPMISSING);
                     return Collections.emptySet();
                 }
+                if (isNotMplsBos(selector) && group.type().equals(SELECT)) {
+                    log.warn("SR CONTINUE case cannot be handled as MPLS ECMP "
+                            + "is not implemented in OF-DPA yet. Aborting flow {} -> next:{} "
+                            + "in this device {}", fwd.id(), fwd.nextId(), deviceId);
+                    fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED);
+                    return Collections.emptySet();
+                }
                 tb.deferred().group(group.id());
                 // retrying flows may be necessary due to bug CORD-554
                 if (gkeys.size() == 1 && gkeys.get(0).size() == 1) {
@@ -1639,16 +1626,53 @@
         return mappings;
     }
 
+    /**
+     * Returns true iff the given selector matches on BOS==true, indicating that
+     * the selector is trying to match on a label that is bottom-of-stack.
+     *
+     * @param selector the given match
+     * @return true iff BoS==true; false if BOS==false, or BOS matching is not
+     *         expressed in the given selector
+     */
     static boolean isMplsBos(TrafficSelector selector) {
         MplsBosCriterion bosCriterion = (MplsBosCriterion) selector.getCriterion(MPLS_BOS);
         return bosCriterion != null && bosCriterion.mplsBos();
     }
 
+    /**
+     * Returns true iff the given selector matches on BOS==false, indicating
+     * that the selector is trying to match on a label that is not the
+     * bottom-of-stack label.
+     *
+     * @param selector the given match
+     * @return true iff BoS==false;
+     *         false if BOS==true, or BOS matching is not expressed in the given selector
+     */
     static boolean isNotMplsBos(TrafficSelector selector) {
         MplsBosCriterion bosCriterion = (MplsBosCriterion) selector.getCriterion(MPLS_BOS);
         return bosCriterion != null && !bosCriterion.mplsBos();
     }
 
+    /**
+     * Returns true iff the forwarding objective includes a treatment to pop the
+     * MPLS label.
+     *
+     * @param fwd the given forwarding objective
+     * @return true iff mpls pop treatment exists
+     */
+    static boolean isMplsPop(ForwardingObjective fwd) {
+        if (fwd.treatment() != null) {
+            for (Instruction instr : fwd.treatment().allInstructions()) {
+                if (instr instanceof L2ModificationInstruction
+                        && ((L2ModificationInstruction) instr)
+                                .subtype() == L2SubType.MPLS_POP) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private static boolean isIpv6(TrafficSelector selector) {
         EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) selector.getCriterion(ETH_TYPE);
         return ethTypeCriterion != null && ethTypeCriterion.ethType().toShort() == Ethernet.TYPE_IPV6;
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3GroupHandler.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3GroupHandler.java
index 99c6c61..e0e5099 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3GroupHandler.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3GroupHandler.java
@@ -342,9 +342,8 @@
                         mplsTreatment.add(ins);
                         break;
                     default:
-                        log.warn("Driver does not handle this type of TrafficTreatment"
-                                         + " instruction in nextObjectives: {} - {}",
-                                 ins.type(), ins);
+                    log.warn("Driver does not handle TrafficTreatment"
+                            + " L2Mod {} for pw next-obj", l2ins.subtype());
                         break;
                 }
             } else if (ins.type() == Instruction.Type.OUTPUT) {
@@ -358,14 +357,13 @@
                         mplsTreatment.add(ins);
                         break;
                     default:
-                        log.warn("Driver does not handle this type of TrafficTreatment"
-                                         + " instruction in nextObjectives: {} - {}",
-                                 ins.type(), ins);
+                    log.warn("Driver does not handle TrafficTreatment"
+                            + " L3Mod for pw next-obj", l3ins.subtype());
                 }
 
             } else {
                 log.warn("Driver does not handle this type of TrafficTreatment"
-                                 + " instruction in nextObjectives: {} - {}",
+                        + " instruction for pw next-obj: {} - {}",
                          ins.type(), ins);
             }
         }
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3Pipeline.java
index 0059b64..7ea79bd 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3Pipeline.java
@@ -386,9 +386,13 @@
 
     @Override
     protected Collection<FlowRule> processEthTypeSpecific(ForwardingObjective fwd) {
-        if (isNotMplsBos(fwd.selector())) {
+        // if its not-bos, we go to MPLS_TYPE_TABLE regardless of whether we pop or swap
+        // if it is bos, we go to MPLS_TYPE_TABLE only if we swap
+        if (isNotMplsBos(fwd.selector())
+                || (isMplsBos(fwd.selector()) && !isMplsPop(fwd))) {
             return processEthTypeSpecificInternal(fwd, true, MPLS_TYPE_TABLE);
         }
+        // if it is bos, and we pop, we go to MPLS_L3_TYPE_TABLE
         return processEthTypeSpecificInternal(fwd, true, MPLS_L3_TYPE_TABLE);
     }