[ONOS-7285][ONOS-7263] VLAN support by fabric.p4

Change-Id: I9ea460bca2698eb74f0d4988830a1e7cc7bc2768
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java
index c7c7647..12602b4 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/pipeliner/FabricNextPipeliner.java
@@ -16,16 +16,21 @@
 
 package org.onosproject.pipelines.fabric.pipeliner;
 
+import org.onlab.packet.VlanId;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.driver.Driver;
 import org.onosproject.net.flow.DefaultFlowRule;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 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.PiCriterion;
+import org.onosproject.net.flow.criteria.VlanIdCriterion;
 import org.onosproject.net.flow.instructions.Instruction;
 import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
 import org.onosproject.net.flowobjective.DefaultNextObjective;
 import org.onosproject.net.flowobjective.NextObjective;
 import org.onosproject.net.flowobjective.Objective;
@@ -44,8 +49,11 @@
 
 import static org.onosproject.pipelines.fabric.FabricConstants.ACT_PRF_FABRICINGRESS_NEXT_ECMP_SELECTOR_ID;
 import static org.onosproject.pipelines.fabric.FabricConstants.HF_FABRIC_METADATA_NEXT_ID_ID;
+import static org.onosproject.pipelines.fabric.FabricConstants.HF_STANDARD_METADATA_EGRESS_PORT_ID;
+import static org.onosproject.pipelines.fabric.FabricConstants.TBL_EGRESS_VLAN_ID;
 import static org.onosproject.pipelines.fabric.FabricConstants.TBL_HASHED_ID;
 import static org.onosproject.pipelines.fabric.FabricConstants.TBL_SIMPLE_ID;
+import static org.onosproject.pipelines.fabric.FabricConstants.TBL_VLAN_META_ID;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -66,6 +74,8 @@
     public PipelinerTranslationResult next(NextObjective nextObjective) {
         PipelinerTranslationResult.Builder resultBuilder = PipelinerTranslationResult.builder();
 
+        processNextVlanMeta(nextObjective, resultBuilder);
+
         switch (nextObjective.type()) {
             case SIMPLE:
                 processSimpleNext(nextObjective, resultBuilder);
@@ -82,6 +92,38 @@
         return resultBuilder.build();
     }
 
+    private void processNextVlanMeta(NextObjective next,
+                                     PipelinerTranslationResult.Builder resultBuilder) {
+        TrafficSelector meta = next.meta();
+        if (meta == null) {
+            // do nothing if there is no metadata in the next objective.
+            return;
+        }
+        VlanIdCriterion vlanIdCriterion =
+                (VlanIdCriterion) meta.getCriterion(Criterion.Type.VLAN_VID);
+
+        if (vlanIdCriterion == null) {
+            // do nothing if we can't find vlan from next objective metadata.
+            return;
+        }
+
+        VlanId vlanId = vlanIdCriterion.vlanId();
+        TrafficSelector selector = buildNextIdSelector(next.id());
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setVlanId(vlanId)
+                .build();
+
+        resultBuilder.addFlowRule(DefaultFlowRule.builder()
+                                          .withSelector(selector)
+                                          .withTreatment(treatment)
+                                          .forTable(TBL_VLAN_META_ID)
+                                          .makePermanent()
+                                          .withPriority(next.priority())
+                                          .forDevice(deviceId)
+                                          .fromApp(next.appId())
+                                          .build());
+    }
+
     private void processSimpleNext(NextObjective next,
                                    PipelinerTranslationResult.Builder resultBuilder) {
 
@@ -93,18 +135,14 @@
 
         TrafficSelector selector = buildNextIdSelector(next.id());
         TrafficTreatment treatment = next.next().iterator().next();
-        OutputInstruction outputInst = treatment.allInstructions()
-                .stream()
-                .filter(inst -> inst.type() == Instruction.Type.OUTPUT)
-                .map(inst -> (OutputInstruction) inst)
-                .findFirst()
-                .orElse(null);
+        PortNumber outputPort = getOutputPort(treatment);
 
-        if (outputInst == null) {
+        if (outputPort == null) {
             log.warn("At least one output instruction in simple next objective");
             resultBuilder.setError(ObjectiveError.BADPARAMS);
             return;
         }
+
         resultBuilder.addFlowRule(DefaultFlowRule.builder()
                                           .withSelector(selector)
                                           .withTreatment(treatment)
@@ -114,29 +152,81 @@
                                           .forDevice(deviceId)
                                           .fromApp(next.appId())
                                           .build());
+
+        if (includesPopVlanInst(treatment)) {
+            processVlanPopRule(outputPort, next, resultBuilder);
+        }
     }
 
-    private void processHashedNext(NextObjective nextObjective, PipelinerTranslationResult.Builder resultBuilder) {
+    private PortNumber getOutputPort(TrafficTreatment treatment) {
+        return treatment.allInstructions()
+                .stream()
+                .filter(inst -> inst.type() == Instruction.Type.OUTPUT)
+                .map(inst -> (OutputInstruction) inst)
+                .findFirst()
+                .map(OutputInstruction::port)
+                .orElse(null);
+    }
+
+    private boolean includesPopVlanInst(TrafficTreatment treatment) {
+        return treatment.allInstructions()
+                .stream()
+                .filter(inst -> inst.type() == Instruction.Type.L2MODIFICATION)
+                .map(inst -> (L2ModificationInstruction) inst)
+                .anyMatch(inst -> inst.subtype() == L2ModificationInstruction.L2SubType.VLAN_POP);
+    }
+
+    private void processVlanPopRule(PortNumber port, NextObjective next,
+                                    PipelinerTranslationResult.Builder resultBuilder) {
+        TrafficSelector meta = next.meta();
+        VlanIdCriterion vlanIdCriterion =
+                (VlanIdCriterion) meta.getCriterion(Criterion.Type.VLAN_VID);
+        VlanId vlanId = vlanIdCriterion.vlanId();
+
+        PiCriterion egressVlanTableMatch = PiCriterion.builder()
+                .matchExact(HF_STANDARD_METADATA_EGRESS_PORT_ID,
+                            (short) port.toLong())
+                .build();
+        // Add VLAN pop rule to egress pipeline table
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchPi(egressVlanTableMatch)
+                .matchVlanId(vlanId)
+                .build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .popVlan()
+                .build();
+        resultBuilder.addFlowRule(DefaultFlowRule.builder()
+                                          .withSelector(selector)
+                                          .withTreatment(treatment)
+                                          .forTable(TBL_EGRESS_VLAN_ID)
+                                          .makePermanent()
+                                          .withPriority(next.priority())
+                                          .forDevice(deviceId)
+                                          .fromApp(next.appId())
+                                          .build());
+    }
+
+    private void processHashedNext(NextObjective next, PipelinerTranslationResult.Builder resultBuilder) {
         boolean noHashedTable = Boolean.parseBoolean(driver.getProperty(NO_HASHED_TABLE));
 
         if (noHashedTable) {
-            if (nextObjective.next().isEmpty()) {
+            if (next.next().isEmpty()) {
                 return;
             }
             // use first action if not support hashed group
-            TrafficTreatment treatment = nextObjective.next().iterator().next();
+            TrafficTreatment treatment = next.next().iterator().next();
 
             NextObjective.Builder simpleNext = DefaultNextObjective.builder()
                     .addTreatment(treatment)
-                    .withId(nextObjective.id())
-                    .fromApp(nextObjective.appId())
+                    .withId(next.id())
+                    .fromApp(next.appId())
                     .makePermanent()
-                    .withMeta(nextObjective.meta())
-                    .withPriority(nextObjective.priority())
+                    .withMeta(next.meta())
+                    .withPriority(next.priority())
                     .withType(NextObjective.Type.SIMPLE);
 
-            if (nextObjective.context().isPresent()) {
-                processSimpleNext(simpleNext.add(nextObjective.context().get()), resultBuilder);
+            if (next.context().isPresent()) {
+                processSimpleNext(simpleNext.add(next.context().get()), resultBuilder);
             } else {
                 processSimpleNext(simpleNext.add(), resultBuilder);
             }
@@ -144,15 +234,23 @@
         }
 
         // create hash groups
-        int groupId = nextObjective.id();
-        List<GroupBucket> bucketList = nextObjective.next().stream()
+        int groupId = next.id();
+        List<GroupBucket> bucketList = next.next().stream()
                 .map(DefaultGroupBucket::createSelectGroupBucket)
                 .collect(Collectors.toList());
 
-        if (bucketList.size() != nextObjective.next().size()) {
+        // Egress VLAN handling
+        next.next().forEach(treatment -> {
+            PortNumber outputPort = getOutputPort(treatment);
+            if (includesPopVlanInst(treatment) && outputPort != null) {
+                processVlanPopRule(outputPort, next, resultBuilder);
+            }
+        });
+
+        if (bucketList.size() != next.next().size()) {
             // some action not converted
             // set error
-            log.warn("Expected bucket size {}, got {}", nextObjective.next().size(), bucketList.size());
+            log.warn("Expected bucket size {}, got {}", next.next().size(), bucketList.size());
             resultBuilder.setError(ObjectiveError.BADPARAMS);
             return;
         }
@@ -167,18 +265,18 @@
                                                            buckets,
                                                            groupKey,
                                                            groupId,
-                                                           nextObjective.appId()));
+                                                           next.appId()));
 
         // flow
         // If operation is ADD_TO_EXIST or REMOVE_FROM_EXIST, means we modify
         // group buckets only, no changes for flow rule
-        if (nextObjective.op() == Objective.Operation.ADD_TO_EXISTING ||
-                nextObjective.op() == Objective.Operation.REMOVE_FROM_EXISTING) {
+        if (next.op() == Objective.Operation.ADD_TO_EXISTING ||
+                next.op() == Objective.Operation.REMOVE_FROM_EXISTING) {
             return;
         }
-        TrafficSelector selector = buildNextIdSelector(nextObjective.id());
+        TrafficSelector selector = buildNextIdSelector(next.id());
         TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .piTableAction(PiActionGroupId.of(nextObjective.id()))
+                .piTableAction(PiActionGroupId.of(next.id()))
                 .build();
 
         resultBuilder.addFlowRule(DefaultFlowRule.builder()
@@ -186,9 +284,9 @@
                                           .withTreatment(treatment)
                                           .forTable(TBL_HASHED_ID)
                                           .makePermanent()
-                                          .withPriority(nextObjective.priority())
+                                          .withPriority(next.priority())
                                           .forDevice(deviceId)
-                                          .fromApp(nextObjective.appId())
+                                          .fromApp(next.appId())
                                           .build());
     }
 
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 5c66474..363a95c 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
@@ -263,7 +263,12 @@
         };
 
         FlowRuleOperations ops = buildFlowRuleOps(objective, flowRules, ctx);
-        flowRuleService.apply(ops);
+        if (ops != null) {
+            flowRuleService.apply(ops);
+        } else {
+            // remove pendings
+            flowRules.forEach(flowRule -> pendingInstallObjectiveFlows.remove(flowRule.id()));
+        }
     }
 
     private void installGroups(Objective objective, Collection<GroupDescription> groups) {
@@ -317,8 +322,13 @@
             case REMOVE:
                 flowRules.forEach(ops::remove);
                 break;
+            case ADD_TO_EXISTING:
+            case REMOVE_FROM_EXISTING:
+                // Next objective may use ADD_TO_EXIST or REMOVE_FROM_EXIST op
+                // No need to update FlowRuls for vlan_meta table.
+                return null;
             default:
-                log.warn("Unsupported op {} for {}", objective);
+                log.warn("Unsupported op {} for {}", objective.op(), objective);
                 fail(objective, ObjectiveError.BADPARAMS);
                 return null;
         }