Adding support for VLAN_PUSH with EtherType (incl. Q-in-Q)

 - Allowing VLAN_PUSH Instruction to use ethernetType (incl. using REST API)
 - Adding QINQ (0x88a8) Ethernet type
 - Updating InstructionCodec decoders/encoders
 - Updating TrafficTreatment/FlowEntryBuilder

Change-Id: I723cc936a8a49c39da9abe65ba9e5b1bdc1392bf
diff --git a/core/api/src/main/java/org/onosproject/net/flow/DefaultTrafficTreatment.java b/core/api/src/main/java/org/onosproject/net/flow/DefaultTrafficTreatment.java
index fbe240a..623ece1 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/DefaultTrafficTreatment.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/DefaultTrafficTreatment.java
@@ -401,6 +401,11 @@
         }
 
         @Override
+        public Builder pushVlan(EthType ethType) {
+            return add(Instructions.pushVlan(ethType));
+        }
+
+        @Override
         public Builder transition(Integer tableId) {
             return add(Instructions.transition(tableId));
         }
diff --git a/core/api/src/main/java/org/onosproject/net/flow/TrafficTreatment.java b/core/api/src/main/java/org/onosproject/net/flow/TrafficTreatment.java
index d0de8e4..1ccceea 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/TrafficTreatment.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/TrafficTreatment.java
@@ -291,6 +291,13 @@
         Builder pushVlan();
 
         /**
+         * Pushes a new VLAN tag using the supplied Ethernet type.
+         *
+         * @return a treatment builder
+         */
+        Builder pushVlan(EthType ethType);
+
+        /**
          * Any instructions preceded by this method call will be deferred.
          * @return a treatment builder
          */
diff --git a/core/api/src/main/java/org/onosproject/net/flow/instructions/Instructions.java b/core/api/src/main/java/org/onosproject/net/flow/instructions/Instructions.java
index aaf37e2..01d2c1e 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/instructions/Instructions.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/instructions/Instructions.java
@@ -383,6 +383,18 @@
     }
 
     /**
+     * Creates a push VLAN header instruction using the supplied Ethernet type.
+     *
+     * @param ethType the Ethernet type to use
+     * @return a L2 modification
+     */
+    public static Instruction pushVlan(EthType ethType) {
+        return new L2ModificationInstruction.ModVlanHeaderInstruction(
+                L2ModificationInstruction.L2SubType.VLAN_PUSH,
+                ethType);
+    }
+
+    /**
      * Sends the packet to the table id.
      *
      * @param tableId flow rule table id
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java b/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java
index 316e9d24..fd8bd74 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java
@@ -19,6 +19,7 @@
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.onlab.osgi.DefaultServiceDirectory;
 import org.onlab.osgi.ServiceDirectory;
+import org.onlab.packet.EthType;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.MplsLabel;
@@ -48,6 +49,9 @@
 import org.onosproject.net.meter.MeterId;
 import org.slf4j.Logger;
 
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 import static org.onlab.util.Tools.nullIsIllegal;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -58,6 +62,7 @@
     protected static final Logger log = getLogger(DecodeInstructionCodecHelper.class);
     private final ObjectNode json;
     private final CodecContext context;
+    private static final Pattern ETHTYPE_PATTERN = Pattern.compile("0x([0-9a-fA-F]{4})");
 
     /**
      * Creates a decode instruction codec object.
@@ -108,6 +113,9 @@
         } else if (subType.equals(L2ModificationInstruction.L2SubType.VLAN_POP.name())) {
             return Instructions.popVlan();
         } else if (subType.equals(L2ModificationInstruction.L2SubType.VLAN_PUSH.name())) {
+            if (json.has(InstructionCodec.ETHERNET_TYPE)) {
+                return Instructions.pushVlan(getEthType());
+            }
             return Instructions.pushVlan();
         } else if (subType.equals(L2ModificationInstruction.L2SubType.TUNNEL_ID.name())) {
             long tunnelId = nullIsIllegal(json.get(InstructionCodec.TUNNEL_ID),
@@ -312,6 +320,23 @@
     }
 
     /**
+     * Returns Ethernet type.
+     *
+     * @return ethernet type
+     * @throws IllegalArgumentException if the JSON is invalid
+     */
+    private EthType getEthType() {
+        String ethTypeStr = nullIsIllegal(json.get(InstructionCodec.ETHERNET_TYPE),
+                  InstructionCodec.ETHERNET_TYPE + InstructionCodec.MISSING_MEMBER_MESSAGE).asText();
+        Matcher matcher = ETHTYPE_PATTERN.matcher(ethTypeStr);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException("ETHERNET_TYPE must be a four digit hex string starting with 0x");
+        }
+        short ethernetType = (short) Integer.parseInt(matcher.group(1), 16);
+        return new EthType(ethernetType);
+    }
+
+    /**
      * Decodes the JSON into an instruction object.
      *
      * @return Criterion object
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java b/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java
index 2b3c9d7..f523430 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java
@@ -136,6 +136,11 @@
                         (L2ModificationInstruction.ModVlanPcpInstruction) l2Instruction;
                 result.put(InstructionCodec.VLAN_PCP, modVlanPcpInstruction.vlanPcp());
                 break;
+            case VLAN_PUSH:
+                final L2ModificationInstruction.ModVlanHeaderInstruction pushVlanInstruction =
+                        (L2ModificationInstruction.ModVlanHeaderInstruction) l2Instruction;
+                result.put(InstructionCodec.ETHERNET_TYPE, pushVlanInstruction.ethernetType().toString());
+                break;
             case MPLS_LABEL:
                 final L2ModificationInstruction.ModMplsLabelInstruction modMplsLabelInstruction =
                         (L2ModificationInstruction.ModMplsLabelInstruction) l2Instruction;
diff --git a/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/util/FlowEntryBuilder.java b/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/util/FlowEntryBuilder.java
index d2ed633..afa2eb2 100644
--- a/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/util/FlowEntryBuilder.java
+++ b/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/util/FlowEntryBuilder.java
@@ -60,6 +60,7 @@
 import org.projectfloodlight.openflow.protocol.action.OFActionGroup;
 import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
 import org.projectfloodlight.openflow.protocol.action.OFActionPopMpls;
+import org.projectfloodlight.openflow.protocol.action.OFActionPushVlan;
 import org.projectfloodlight.openflow.protocol.action.OFActionSetDlDst;
 import org.projectfloodlight.openflow.protocol.action.OFActionSetDlSrc;
 import org.projectfloodlight.openflow.protocol.action.OFActionSetField;
@@ -400,7 +401,8 @@
                     builder.popVlan();
                     break;
                 case PUSH_VLAN:
-                    builder.pushVlan();
+                    OFActionPushVlan pushVlan = (OFActionPushVlan) act;
+                    builder.pushVlan(new EthType((short) pushVlan.getEthertype().getValue()));
                     break;
                 case SET_TP_DST:
                 case SET_TP_SRC:
@@ -438,7 +440,6 @@
         } else {
             treatmentInterpreter = null;
         }
-
         OFOxm<?> oxm = action.getField();
         switch (oxm.getMatchField().id) {
         case VLAN_PCP:
diff --git a/utils/misc/src/main/java/org/onlab/packet/EthType.java b/utils/misc/src/main/java/org/onlab/packet/EthType.java
index 1519ac0..d2dfec0 100644
--- a/utils/misc/src/main/java/org/onlab/packet/EthType.java
+++ b/utils/misc/src/main/java/org/onlab/packet/EthType.java
@@ -33,6 +33,7 @@
         IPV6(0x86dd, "ipv6", org.onlab.packet.IPv6.deserializer()),
         LLDP(0x88cc, "lldp", org.onlab.packet.LLDP.deserializer()),
         VLAN(0x8100, "vlan", null),
+        QINQ(0x88a8, "qinq", null),
         BDDP(0x8942, "bddp", org.onlab.packet.LLDP.deserializer()),
         MPLS_UNICAST(0x8847, "mpls_unicast", org.onlab.packet.MPLS.deserializer()),
         MPLS_MULTICAST(0x8848, "mpls_unicast", org.onlab.packet.MPLS.deserializer()),