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/AbstractFabricHandlerBehavior.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/AbstractFabricHandlerBehavior.java
index 2753844..db4da90 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/AbstractFabricHandlerBehavior.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/AbstractFabricHandlerBehavior.java
@@ -30,21 +30,48 @@
 /**
  * Abstract implementation of HandlerBehaviour for the fabric pipeconf
  * behaviors.
+ * <p>
+ * All sub-classes must implement a default constructor, used by the abstract
+ * projectable model (i.e., {@link org.onosproject.net.Device#as(Class)}.
  */
-public class AbstractFabricHandlerBehavior extends AbstractHandlerBehaviour {
+public abstract class AbstractFabricHandlerBehavior extends AbstractHandlerBehaviour {
 
     protected final Logger log = getLogger(getClass());
 
     protected FabricCapabilities capabilities;
 
+    /**
+     * Creates a new instance of this behavior with the given capabilities.
+     * Note: this constructor should be invoked only by other classes of this
+     * package that can retrieve capabilities on their own.
+     * <p>
+     * When using the abstract projectable model (i.e., {@link
+     * org.onosproject.net.Device#as(Class)}, capabilities will be set by the
+     * driver manager when calling {@link #setHandler(DriverHandler)})
+     *
+     * @param capabilities capabilities
+     */
+    protected AbstractFabricHandlerBehavior(FabricCapabilities capabilities) {
+        this.capabilities = capabilities;
+    }
+
+    /**
+     * Create a new instance of this behaviour. Used by the abstract projectable
+     * model (i.e., {@link org.onosproject.net.Device#as(Class)}.
+     */
+    public AbstractFabricHandlerBehavior() {
+        // Do nothing
+    }
+
     @Override
     public void setHandler(DriverHandler handler) {
         super.setHandler(handler);
         final PiPipeconfService pipeconfService = handler().get(PiPipeconfService.class);
-        setCapabilities(handler().data().deviceId(), pipeconfService);
+        setCapabilitiesFromHandler(handler().data().deviceId(), pipeconfService);
     }
 
-    private void setCapabilities(DeviceId deviceId, PiPipeconfService pipeconfService) {
+    private void setCapabilitiesFromHandler(
+            DeviceId deviceId, PiPipeconfService pipeconfService) {
         checkNotNull(deviceId);
         checkNotNull(pipeconfService);
         // Get pipeconf capabilities.
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricCapabilities.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricCapabilities.java
index 07986f9..b0ce208 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricCapabilities.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricCapabilities.java
@@ -76,4 +76,14 @@
             return Optional.empty();
         }
     }
+
+    public boolean supportDoubleVlanTerm() {
+        if (pipeconf.pipelineModel()
+                .table(FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN).isPresent()) {
+            return pipeconf.pipelineModel().table(FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN)
+                    .get().action(FabricConstants.FABRIC_INGRESS_NEXT_SET_DOUBLE_VLAN)
+                    .isPresent();
+        }
+        return false;
+    }
 }
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricIntProgrammable.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricIntProgrammable.java
index d593262..1770576 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricIntProgrammable.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricIntProgrammable.java
@@ -86,6 +86,23 @@
     private DeviceId deviceId;
     private ApplicationId appId;
 
+    /**
+     * Creates a new instance of this behavior with the given capabilities.
+     *
+     * @param capabilities capabilities
+     */
+    protected FabricIntProgrammable(FabricCapabilities capabilities) {
+        super(capabilities);
+    }
+
+    /**
+     * Create a new instance of this behaviour. Used by the abstract projectable
+     * model (i.e., {@link org.onosproject.net.Device#as(Class)}.
+     */
+    public FabricIntProgrammable() {
+        super();
+    }
+
     private boolean setupBehaviour() {
         deviceId = this.data().deviceId();
         flowRuleService = handler().get(FlowRuleService.class);
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
index 69edeff..8567a3a 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
@@ -27,6 +27,7 @@
 import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverHandler;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.flow.instructions.Instructions;
@@ -116,6 +117,36 @@
                     .put(FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4, NOP)
                     .build();
 
+    private FabricTreatmentInterpreter treatmentInterpreter;
+
+    /**
+     * Creates a new instance of this behavior with the given capabilities.
+     *
+     * @param capabilities capabilities
+     */
+    public FabricInterpreter(FabricCapabilities capabilities) {
+        super(capabilities);
+        instantiateTreatmentInterpreter();
+    }
+
+    /**
+     * Create a new instance of this behaviour. Used by the abstract projectable
+     * model (i.e., {@link org.onosproject.net.Device#as(Class)}.
+     */
+    public FabricInterpreter() {
+        super();
+    }
+
+    private void instantiateTreatmentInterpreter() {
+        this.treatmentInterpreter = new FabricTreatmentInterpreter(this.capabilities);
+    }
+
+    @Override
+    public void setHandler(DriverHandler handler) {
+        super.setHandler(handler);
+        instantiateTreatmentInterpreter();
+    }
+
     @Override
     public Optional<PiMatchFieldId> mapCriterionType(Criterion.Type type) {
         return Optional.ofNullable(CRITERION_MAP.get(type));
@@ -132,15 +163,15 @@
     public PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId)
             throws PiInterpreterException {
         if (FILTERING_CTRL_TBLS.contains(piTableId)) {
-            return FabricTreatmentInterpreter.mapFilteringTreatment(treatment, piTableId);
+            return treatmentInterpreter.mapFilteringTreatment(treatment, piTableId);
         } else if (FORWARDING_CTRL_TBLS.contains(piTableId)) {
-            return FabricTreatmentInterpreter.mapForwardingTreatment(treatment, piTableId);
+            return treatmentInterpreter.mapForwardingTreatment(treatment, piTableId);
         } else if (ACL_CTRL_TBLS.contains(piTableId)) {
-            return FabricTreatmentInterpreter.mapAclTreatment(treatment, piTableId);
+            return treatmentInterpreter.mapAclTreatment(treatment, piTableId);
         } else if (NEXT_CTRL_TBLS.contains(piTableId)) {
-            return FabricTreatmentInterpreter.mapNextTreatment(treatment, piTableId);
+            return treatmentInterpreter.mapNextTreatment(treatment, piTableId);
         } else if (E_NEXT_CTRL_TBLS.contains(piTableId)) {
-            return FabricTreatmentInterpreter.mapEgressNextTreatment(treatment, piTableId);
+            return treatmentInterpreter.mapEgressNextTreatment(treatment, piTableId);
         } else {
             throw new PiInterpreterException(format(
                     "Treatment mapping not supported for table '%s'", piTableId));
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java
index b7ee4c5..b501f33 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricTreatmentInterpreter.java
@@ -32,6 +32,9 @@
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionParam;
 
+import java.util.List;
+import java.util.stream.Collectors;
+
 import static java.lang.String.format;
 import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
 import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.ETH_DST;
@@ -42,12 +45,14 @@
 import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_POP;
 import static org.onosproject.pipelines.fabric.FabricUtils.instruction;
 import static org.onosproject.pipelines.fabric.FabricUtils.l2Instruction;
+import static org.onosproject.pipelines.fabric.FabricUtils.l2Instructions;
 
 /**
  * Treatment translation logic.
  */
 final class FabricTreatmentInterpreter {
 
+    private final FabricCapabilities capabilities;
     private static final ImmutableMap<PiTableId, PiActionId> NOP_ACTIONS =
             ImmutableMap.<PiTableId, PiActionId>builder()
                     .put(FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN,
@@ -60,8 +65,9 @@
                          FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_POP_VLAN)
                     .build();
 
-    private FabricTreatmentInterpreter() {
-        // Hide default constructor
+
+    FabricTreatmentInterpreter(FabricCapabilities capabilities) {
+        this.capabilities = capabilities;
     }
 
     static PiAction mapFilteringTreatment(TrafficTreatment treatment, PiTableId tableId)
@@ -73,7 +79,8 @@
             tableException(tableId);
         }
 
-        if (isNoAction(treatment)) {
+        // VLAN_POP action is equivalent to the permit action (VLANs pop is done anyway)
+        if (isNoAction(treatment) || isFilteringPopAction(treatment)) {
             // Permit action if table is ingress_port_vlan;
             return nop(tableId);
         }
@@ -99,7 +106,7 @@
         return null;
     }
 
-    static PiAction mapNextTreatment(TrafficTreatment treatment, PiTableId tableId)
+    PiAction mapNextTreatment(TrafficTreatment treatment, PiTableId tableId)
             throws PiInterpreterException {
         if (tableId == FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN) {
             return mapNextVlanTreatment(treatment, tableId);
@@ -114,16 +121,29 @@
                 "Treatment mapping not supported for table '%s'", tableId));
     }
 
-    private static PiAction mapNextVlanTreatment(TrafficTreatment treatment, PiTableId tableId)
+    private PiAction mapNextVlanTreatment(TrafficTreatment treatment, PiTableId tableId)
             throws PiInterpreterException {
-        final ModVlanIdInstruction modVlanIdInst = (ModVlanIdInstruction)
-                l2InstructionOrFail(treatment, VLAN_ID, tableId);
-        return PiAction.builder()
-                .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_VLAN)
-                .withParameter(new PiActionParam(
-                        FabricConstants.VLAN_ID,
-                        modVlanIdInst.vlanId().toShort()))
-                .build();
+        final List<ModVlanIdInstruction> modVlanIdInst = l2InstructionsOrFail(treatment, VLAN_ID, tableId)
+                .stream().map(i -> (ModVlanIdInstruction) i).collect(Collectors.toList());
+        if (modVlanIdInst.size() == 1) {
+            return PiAction.builder().withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_VLAN)
+                    .withParameter(new PiActionParam(
+                            FabricConstants.VLAN_ID,
+                            modVlanIdInst.get(0).vlanId().toShort()))
+                    .build();
+        }
+        if (modVlanIdInst.size() == 2 && capabilities.supportDoubleVlanTerm()) {
+            return PiAction.builder()
+                    .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_DOUBLE_VLAN)
+                    .withParameter(new PiActionParam(
+                            FabricConstants.INNER_VLAN_ID,
+                            modVlanIdInst.get(0).vlanId().toShort()))
+                    .withParameter(new PiActionParam(
+                            FabricConstants.OUTER_VLAN_ID,
+                            modVlanIdInst.get(1).vlanId().toShort()))
+                    .build();
+        }
+        throw new PiInterpreterException("Too many VLAN instructions");
     }
 
     private static PiAction mapNextHashedOrSimpleTreatment(
@@ -223,6 +243,10 @@
                 treatment.allInstructions().isEmpty();
     }
 
+    private static boolean isFilteringPopAction(TrafficTreatment treatment) {
+        return l2Instruction(treatment, VLAN_POP) != null;
+    }
+
     private static Instruction l2InstructionOrFail(
             TrafficTreatment treatment,
             L2ModificationInstruction.L2SubType subType, PiTableId tableId)
@@ -234,6 +258,17 @@
         return inst;
     }
 
+    private static List<L2ModificationInstruction> l2InstructionsOrFail(
+            TrafficTreatment treatment,
+            L2ModificationInstruction.L2SubType subType, PiTableId tableId)
+            throws PiInterpreterException {
+        final List<L2ModificationInstruction> inst = l2Instructions(treatment, subType);
+        if (inst == null || inst.isEmpty()) {
+            treatmentException(tableId, treatment, format("missing %s instruction", subType));
+        }
+        return inst;
+    }
+
     private static Instruction instructionOrFail(
             TrafficTreatment treatment, Instruction.Type type, PiTableId tableId)
             throws PiInterpreterException {
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricUtils.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricUtils.java
index 8e5caa1..ae000b5 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricUtils.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricUtils.java
@@ -27,6 +27,8 @@
 import org.onosproject.net.flowobjective.NextTreatment;
 
 import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static java.lang.String.format;
@@ -77,6 +79,15 @@
                 .findFirst().orElse(null);
     }
 
+    public static List<L2ModificationInstruction> l2Instructions(
+            TrafficTreatment treatment, L2ModificationInstruction.L2SubType subType) {
+        return treatment.allInstructions().stream()
+                .filter(i -> i.type().equals(Instruction.Type.L2MODIFICATION))
+                .map(i -> (L2ModificationInstruction) i)
+                .filter(i -> i.subtype().equals(subType))
+                .collect(Collectors.toList());
+    }
+
     public static Instructions.OutputInstruction outputInstruction(TrafficTreatment treatment) {
         return instruction(treatment, Instruction.Type.OUTPUT);
     }
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/AbstractObjectiveTranslator.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/AbstractObjectiveTranslator.java
index c1e2f4d..c2ca0ff 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/AbstractObjectiveTranslator.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/AbstractObjectiveTranslator.java
@@ -46,11 +46,12 @@
     protected final FabricCapabilities capabilities;
     protected final DeviceId deviceId;
 
-    private final PiPipelineInterpreter interpreter = new FabricInterpreter();
+    private final PiPipelineInterpreter interpreter;
 
     AbstractObjectiveTranslator(DeviceId deviceId, FabricCapabilities capabilities) {
         this.deviceId = checkNotNull(deviceId);
         this.capabilities = checkNotNull(capabilities);
+        this.interpreter = new FabricInterpreter(capabilities);
     }
 
     public ObjectiveTranslation translate(T obj) {
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
index 4bf1bb5..20cafde 100644
--- 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
@@ -38,6 +38,7 @@
 import org.onosproject.net.group.GroupDescription;
 import org.onosproject.net.group.GroupService;
 import org.onosproject.pipelines.fabric.AbstractFabricHandlerBehavior;
+import org.onosproject.pipelines.fabric.FabricCapabilities;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.slf4j.Logger;
 
@@ -78,6 +79,23 @@
 
     private final ExecutorService callbackExecutor = SharedExecutors.getPoolThreadExecutor();
 
+    /**
+     * Creates a new instance of this behavior with the given capabilities.
+     *
+     * @param capabilities capabilities
+     */
+    public FabricPipeliner(FabricCapabilities capabilities) {
+        super(capabilities);
+    }
+
+    /**
+     * Create a new instance of this behaviour. Used by the abstract projectable
+     * model (i.e., {@link org.onosproject.net.Device#as(Class)}.
+     */
+    public FabricPipeliner() {
+        super();
+    }
+
     @Override
     public void init(DeviceId deviceId, PipelinerContext context) {
         this.deviceId = deviceId;
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,
diff --git a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/FabricInterpreterTest.java b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/FabricInterpreterTest.java
index e59086b..bf37694 100644
--- a/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/FabricInterpreterTest.java
+++ b/pipelines/fabric/src/test/java/org/onosproject/pipelines/fabric/FabricInterpreterTest.java
@@ -29,6 +29,9 @@
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionParam;
 
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
 import static org.junit.Assert.assertEquals;
 
 /**
@@ -43,9 +46,15 @@
 
     private FabricInterpreter interpreter;
 
+    FabricCapabilities allCapabilities;
+
     @Before
     public void setup() {
-        interpreter = new FabricInterpreter();
+        allCapabilities = createNiceMock(FabricCapabilities.class);
+        expect(allCapabilities.hasHashedTable()).andReturn(true).anyTimes();
+        expect(allCapabilities.supportDoubleVlanTerm()).andReturn(true).anyTimes();
+        replay(allCapabilities);
+        interpreter = new FabricInterpreter(allCapabilities);
     }
 
     /* Filtering control block */
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
index dbb2bd4..e15b117 100644
--- 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
@@ -265,6 +265,52 @@
     }
 
     /**
+     * Test double VLAN pop filtering objective
+     * Creates one rule for ingress_port_vlan table and 3 rules for
+     * fwd_classifier table (IPv4, IPv6 and MPLS unicast) when
+     * the condition is MAC + VLAN + INNER_VLAN.
+     */
+    @Test
+    public void testPopVlan() throws FabricPipelinerException {
+        FilteringObjective filteringObjective = DefaultFilteringObjective.builder()
+                .withKey(Criteria.matchInPort(PORT_1))
+                .addCondition(Criteria.matchEthDst(ROUTER_MAC))
+                .addCondition(Criteria.matchVlanId(VLAN_100))
+                .addCondition(Criteria.matchInnerVlanId(VLAN_200))
+                .withPriority(PRIORITY)
+                .fromApp(APP_ID)
+                .withMeta(DefaultTrafficTreatment.builder()
+                                  .popVlan()
+                                  .build())
+                .permit()
+                .add();
+        ObjectiveTranslation actualTranslation = translator.translate(filteringObjective);
+
+        // Ingress port vlan rule
+        FlowRule inportFlowRuleExpected = buildExpectedVlanInPortRule(
+                PORT_1, VLAN_100, VLAN_200, VlanId.NONE,
+                FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN);
+        // Forwarding classifier rules (ipv6, ipv4, mpls)
+        FlowRule classifierV4FlowRuleExpected = buildExpectedFwdClassifierRule(
+                PORT_1, ROUTER_MAC, null,  Ethernet.TYPE_IPV4,
+                FilteringObjectiveTranslator.FWD_IPV4_ROUTING);
+        FlowRule classifierV6FlowRuleExpected = buildExpectedFwdClassifierRule(
+                PORT_1, ROUTER_MAC, null,  Ethernet.TYPE_IPV6,
+                FilteringObjectiveTranslator.FWD_IPV6_ROUTING);
+        FlowRule classifierMplsFlowRuleExpected = buildExpectedFwdClassifierRule(
+                PORT_1, ROUTER_MAC, null,  Ethernet.MPLS_UNICAST,
+                FilteringObjectiveTranslator.FWD_MPLS);
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(inportFlowRuleExpected)
+                .addFlowRule(classifierV4FlowRuleExpected)
+                .addFlowRule(classifierV6FlowRuleExpected)
+                .addFlowRule(classifierMplsFlowRuleExpected)
+                .build();
+
+        assertEquals(expectedTranslation, actualTranslation);
+    }
+
+    /**
      * Incorrect filtering key or filtering conditions test.
      */
     @Test
@@ -381,6 +427,9 @@
                     .build();
         } else {
             selector.matchVlanId(vlanId);
+            if (vlanValid(innerVlanId)) {
+                selector.matchInnerVlanId(innerVlanId);
+            }
             piAction = PiAction.builder()
                     .withId(FabricConstants.FABRIC_INGRESS_FILTERING_PERMIT)
                     .build();
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
index cf7d741..28f699c 100644
--- 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
@@ -207,6 +207,88 @@
     }
 
     /**
+     * Test Route and Push Next Objective (set mac, set double vlan and output port).
+     */
+    @Test
+    public void testRouteAndPushNextObjective() throws FabricPipelinerException {
+        TrafficTreatment routeAndPushTreatment = DefaultTrafficTreatment.builder()
+                .setEthSrc(ROUTER_MAC)
+                .setEthDst(HOST_MAC)
+                .setOutput(PORT_1)
+                .setVlanId(VLAN_100)
+                .pushVlan()
+                .setVlanId(VLAN_200)
+                .build();
+
+        NextObjective nextObjective = DefaultNextObjective.builder()
+                .withId(NEXT_ID_1)
+                .withPriority(PRIORITY)
+                .addTreatment(routeAndPushTreatment)
+                .withType(NextObjective.Type.SIMPLE)
+                .makePermanent()
+                .fromApp(APP_ID)
+                .add();
+
+        ObjectiveTranslation actualTranslation = translatorSimple.translate(nextObjective);
+
+        PiAction piActionRouting = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_ROUTING_SIMPLE)
+                .withParameter(new PiActionParam(
+                        FabricConstants.SMAC, ROUTER_MAC.toBytes()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.DMAC, HOST_MAC.toBytes()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.PORT_NUM, PORT_1.toLong()))
+                .build();
+
+        PiAction piActionPush = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_NEXT_SET_DOUBLE_VLAN)
+                .withParameter(new PiActionParam(
+                        FabricConstants.INNER_VLAN_ID, VLAN_100.toShort()))
+                .withParameter(new PiActionParam(
+                        FabricConstants.OUTER_VLAN_ID, VLAN_200.toShort()))
+                .build();
+
+
+        TrafficSelector nextIdSelector = DefaultTrafficSelector.builder()
+                .matchPi(PiCriterion.builder()
+                                 .matchExact(FabricConstants.HDR_NEXT_ID, NEXT_ID_1)
+                                 .build())
+                .build();
+        FlowRule expectedFlowRuleRouting = DefaultFlowRule.builder()
+                .forDevice(DEVICE_ID)
+                .fromApp(APP_ID)
+                .makePermanent()
+                // FIXME: currently next objective doesn't support priority, ignore this
+                .withPriority(0)
+                .forTable(FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE)
+                .withSelector(nextIdSelector)
+                .withTreatment(DefaultTrafficTreatment.builder()
+                                       .piTableAction(piActionRouting).build())
+                .build();
+        FlowRule expectedFlowRuleDoublePush = DefaultFlowRule.builder()
+                .withSelector(nextIdSelector)
+                .withTreatment(DefaultTrafficTreatment.builder()
+                                       .piTableAction(piActionPush)
+                                       .build())
+                .forTable(FabricConstants.FABRIC_INGRESS_NEXT_NEXT_VLAN)
+                .makePermanent()
+                // FIXME: currently next objective doesn't support priority, ignore this
+                .withPriority(0)
+                .forDevice(DEVICE_ID)
+                .fromApp(APP_ID)
+                .build();
+
+        ObjectiveTranslation expectedTranslation = ObjectiveTranslation.builder()
+                .addFlowRule(expectedFlowRuleDoublePush)
+                .addFlowRule(expectedFlowRuleRouting)
+                .build();
+
+
+        assertEquals(expectedTranslation, actualTranslation);
+    }
+
+    /**
      * Test program ecmp output group for Hashed table.
      */
     @Test
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
index 619a0b7..6c3551b 100644
--- 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
@@ -41,6 +41,7 @@
     static final PortNumber PORT_1 = PortNumber.portNumber(1);
     static final PortNumber PORT_2 = PortNumber.portNumber(2);
     static final VlanId VLAN_100 = VlanId.vlanId("100");
+    static final VlanId VLAN_200 = VlanId.vlanId("200");
     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");
@@ -61,6 +62,7 @@
         this.capabilitiesSimple = createNiceMock(FabricCapabilities.class);
         expect(capabilitiesHashed.hasHashedTable()).andReturn(true).anyTimes();
         expect(capabilitiesSimple.hasHashedTable()).andReturn(false).anyTimes();
+        expect(capabilitiesSimple.supportDoubleVlanTerm()).andReturn(true).anyTimes();
         replay(capabilitiesHashed);
         replay(capabilitiesSimple);
     }