Support in fabric pipeliner for pushing double VLAN tag in Next obj

- Small modification to better support pop and route
- To support route and push we expect to receive a Next Objective with two VLAN_ID
- Added capability to check if the pipeline support double VLAN termination

Change-Id: I8bfbf61ccd838a069121e5ab4a804f695a446bac
(cherry picked from commit f51d0c110af13bd0bfc0d006e070f0e6bbbcd231)
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/NextObjectiveTranslator.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/NextObjectiveTranslator.java
index ec1c64b..d06d383 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/NextObjectiveTranslator.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/NextObjectiveTranslator.java
@@ -61,6 +61,7 @@
 import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_POP;
 import static org.onosproject.pipelines.fabric.FabricUtils.criterion;
 import static org.onosproject.pipelines.fabric.FabricUtils.l2Instruction;
+import static org.onosproject.pipelines.fabric.FabricUtils.l2Instructions;
 import static org.onosproject.pipelines.fabric.FabricUtils.outputPort;
 
 /**
@@ -112,35 +113,45 @@
     private void nextVlan(NextObjective obj,
                           ObjectiveTranslation.Builder resultBuilder)
             throws FabricPipelinerException {
+        // We expect NextObjective treatments to contain one or two VLAN instructions.
+        // If two, this treatment should be mapped to an action for double-vlan push.
+        // In fabric.p4, mapping next IDs to VLAN IDs is done by a direct table (next_vlan),
+        // for this reason, we also make sure that all treatments in the NextObjective
+        // have exactly the same VLAN instructions, as they will be mapped to a single action
 
-        // Set the egress VLAN for this next ID. Expect a VLAN_ID instruction
-        // in the treatment, or use what's in meta.
-        final List<ModVlanIdInstruction> vlanInstructions = defaultNextTreatments(
+        // Try to extract VLAN instructions in the treatment,
+        //  later we check if we support multiple VLAN termination.
+        final List<List<ModVlanIdInstruction>> vlanInstructions = defaultNextTreatments(
                 obj.nextTreatments(), false).stream()
-                .map(t -> (ModVlanIdInstruction) l2Instruction(t.treatment(), VLAN_ID))
-                .filter(Objects::nonNull)
+                .map(defaultNextTreatment ->
+                             l2Instructions(defaultNextTreatment.treatment(), VLAN_ID)
+                                     .stream().map(v -> (ModVlanIdInstruction) v)
+                                     .collect(Collectors.toList()))
+                .filter(l -> !l.isEmpty())
                 .collect(Collectors.toList());
+
         final VlanIdCriterion vlanIdCriterion = obj.meta() == null ? null
                 : (VlanIdCriterion) criterion(obj.meta().criteria(), Criterion.Type.VLAN_VID);
 
-        VlanId vlanId;
+        final List<VlanId> vlanIdList;
         if (vlanInstructions.isEmpty() && vlanIdCriterion == null) {
             // No VLAN_ID to apply.
             return;
-        } else if (!vlanInstructions.isEmpty()) {
+        }
+        if (!vlanInstructions.isEmpty()) {
             // Give priority to what found in the instructions.
-            // Expect the same VLAN ID for all instructions.
-            final Set<VlanId> vlanIds = vlanInstructions.stream()
-                    .map(ModVlanIdInstruction::vlanId)
+            // Expect the same VLAN ID (or two VLAN IDs in the same order) for all instructions.
+            final Set<List<VlanId>> vlanIds = vlanInstructions.stream()
+                    .map(l -> l.stream().map(ModVlanIdInstruction::vlanId).collect(Collectors.toList()))
                     .collect(Collectors.toSet());
             if (obj.nextTreatments().size() != vlanInstructions.size() ||
                     vlanIds.size() != 1) {
                 throw new FabricPipelinerException(
                         "Inconsistent VLAN_ID instructions, cannot process " +
                                 "next_vlan rule. It is required that all " +
-                                "treatments have the same VLAN_ID instruction.");
+                                "treatments have the same VLAN_ID instructions.");
             }
-            vlanId = vlanIds.iterator().next();
+            vlanIdList = vlanIds.iterator().next();
         } else {
             // Use the value in meta.
             // FIXME: there should be no need to generate a next_vlan rule for
@@ -149,13 +160,13 @@
             //  existing packet fields. But, for some reason, if we remove this
             //  rule, traffic is not forwarded at spines. We might need to look
             //  at the way default VLANs are handled in fabric.p4.
-            vlanId = vlanIdCriterion.vlanId();
+            vlanIdList = List.of(vlanIdCriterion.vlanId());
         }
 
         final TrafficSelector selector = nextIdSelector(obj.id());
-        final TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setVlanId(vlanId)
-                .build();
+        final TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
+        vlanIdList.stream().forEach(vlanId -> treatmentBuilder.setVlanId(vlanId));
+        final TrafficTreatment treatment = treatmentBuilder.build();
 
         resultBuilder.addFlowRule(flowRule(
                 obj, FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN,