QMX switches require the unicast flow being installed before multicast flow in TMAC table

Change-Id: I2258f7ecceb9a151c4ce65518e9553fe371cf3ac
diff --git a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/mcast/McastHandler.java b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/mcast/McastHandler.java
index 3cab53b..b323c17 100644
--- a/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/mcast/McastHandler.java
+++ b/apps/segmentrouting/app/src/main/java/org/onosproject/segmentrouting/mcast/McastHandler.java
@@ -733,7 +733,7 @@
         }
 
         FilteringObjective.Builder filtObjBuilder =
-                filterObjBuilder(deviceId, port, assignedVlan, mcastIp, routerMac);
+                filterObjBuilder(port, assignedVlan, mcastIp, routerMac);
         ObjectiveContext context = new DefaultObjectiveContext(
                 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
                         deviceId, port.toLong(), assignedVlan),
@@ -996,14 +996,13 @@
     /**
      * Creates a filtering objective builder for multicast.
      *
-     * @param deviceId Device ID
      * @param ingressPort ingress port of the multicast stream
      * @param assignedVlan assigned VLAN ID
      * @param routerMac router MAC. This is carried in metadata and used from some switches that
      *                  need to put unicast entry before multicast entry in TMAC table.
      * @return filtering objective builder
      */
-    private FilteringObjective.Builder filterObjBuilder(DeviceId deviceId, PortNumber ingressPort,
+    private FilteringObjective.Builder filterObjBuilder(PortNumber ingressPort,
             VlanId assignedVlan, IpAddress mcastIp, MacAddress routerMac) {
         FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
 
@@ -1367,7 +1366,7 @@
         }
 
         FilteringObjective.Builder filtObjBuilder =
-                filterObjBuilder(deviceId, port, assignedVlan, mcastIp, routerMac);
+                filterObjBuilder(port, assignedVlan, mcastIp, routerMac);
         ObjectiveContext context = new DefaultObjectiveContext(
                 (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
                                          deviceId, port.toLong(), assignedVlan),
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2Pipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2Pipeline.java
index 8957bff..9c22b37 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2Pipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2Pipeline.java
@@ -20,6 +20,7 @@
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.GroupId;
@@ -318,6 +319,7 @@
                                                  EthCriterion ethCriterion,
                                                  VlanIdCriterion vidCriterion,
                                                  VlanId assignedVlan,
+                                                 MacAddress unicastMac,
                                                  ApplicationId applicationId) {
         // Consider PortNumber.ANY as wildcard. Match ETH_DST only
         if (portCriterion != null && portCriterion.port() == PortNumber.ANY) {
@@ -326,7 +328,7 @@
 
         // Multicast MAC
         if (ethCriterion.mask() != null) {
-            return processMcastEthDstFilter(ethCriterion, assignedVlan, applicationId);
+            return processMcastEthDstFilter(ethCriterion, assignedVlan, unicastMac, applicationId);
         }
 
         //handling untagged packets via assigned VLAN
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2VlanPipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2VlanPipeline.java
index 36be98d..1c4cdcd 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2VlanPipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/CpqdOfdpa2VlanPipeline.java
@@ -25,6 +25,7 @@
 
 import com.google.common.collect.ImmutableList;
 import org.onlab.packet.Ethernet;
+import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.Port;
@@ -92,6 +93,7 @@
                                                  EthCriterion ethCriterion,
                                                  VlanIdCriterion vidCriterion,
                                                  VlanId assignedVlan,
+                                                 MacAddress unicastMac,
                                                  ApplicationId applicationId) {
         // Consider PortNumber.ANY as wildcard. Match ETH_DST only
         if (portCriterion != null && portCriterion.port() == PortNumber.ANY) {
@@ -100,7 +102,7 @@
 
         // Multicast MAC
         if (ethCriterion.mask() != null) {
-            return processMcastEthDstFilter(ethCriterion, assignedVlan, applicationId);
+            return processMcastEthDstFilter(ethCriterion, assignedVlan, unicastMac, applicationId);
         }
 
         //handling untagged packets via assigned VLAN
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 232e5c6..034e3d1 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
@@ -22,6 +22,7 @@
 import org.onlab.packet.EthType.EtherType;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
 import org.onlab.util.KryoNamespace;
 import org.onosproject.core.ApplicationId;
@@ -65,6 +66,7 @@
 import org.onosproject.net.flow.instructions.L2ModificationInstruction;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsHeaderInstruction;
 import org.onosproject.net.flow.instructions.L3ModificationInstruction;
 import org.onosproject.net.flow.instructions.L3ModificationInstruction.L3SubType;
@@ -258,6 +260,16 @@
         return true;
     }
 
+    /**
+     * Determines whether this driver requires unicast flow to be installed before multicast flow
+     * in TMAC table.
+     *
+     * @return true if required
+     */
+    protected boolean requireUnicastBeforeMulticast() {
+        return false;
+    }
+
     //////////////////////////////////////
     //  Flow Objectives
     //////////////////////////////////////
@@ -473,16 +485,16 @@
             // NOTE: it is possible that a filtering objective only has vidCriterion
             log.warn("filtering objective missing dstMac, cannot program TMAC table");
         } else {
+            MacAddress unicastMac = readEthDstFromTreatment(filt.meta());
             List<List<FlowRule>> allStages = processEthDstFilter(portCriterion, ethCriterion,
-                    vidCriterion, assignedVlan, applicationId);
+                    vidCriterion, assignedVlan, unicastMac, applicationId);
             for (List<FlowRule> flowRules : allStages) {
-                log.trace("Starting a new flow rule stage");
+                log.trace("Starting a new flow rule stage for TMAC table flow");
                 ops.newStage();
 
                 for (FlowRule flowRule : flowRules) {
                     log.trace("{} flow rules in TMAC table: {} for dev: {}",
                             (install) ? "adding" : "removing", flowRules, deviceId);
-
                     if (install) {
                         ops = ops.add(flowRule);
                     } else {
@@ -505,7 +517,7 @@
             List<List<FlowRule>> allStages = processVlanIdFilter(
                     portCriterion, vidCriterion, assignedVlan, applicationId);
             for (List<FlowRule> flowRules : allStages) {
-                log.trace("Starting a new flow rule stage");
+                log.trace("Starting a new flow rule stage for VLAN table flow");
                 ops.newStage();
 
                 for (FlowRule flowRule : flowRules) {
@@ -651,12 +663,15 @@
 
     /**
      * Allows routed packets with correct destination MAC to be directed
-     * to unicast-IP routing table or MPLS forwarding table.
+     * to unicast routing table, multicast routing table or MPLS forwarding table.
      *
      * @param portCriterion  port on device for which this filter is programmed
      * @param ethCriterion   dstMac of device for which is filter is programmed
      * @param vidCriterion   vlan assigned to port, or NONE for untagged
      * @param assignedVlan   assigned vlan-id for untagged packets
+     * @param unicastMac     some switches require a unicast TMAC flow to be programmed before multicast
+     *                       TMAC flow. This MAC address will be used for the unicast TMAC flow.
+     *                       This is unused if the filtering objective is a unicast.
      * @param applicationId  for application programming this filter
      * @return stages of flow rules for port-vlan filters
 
@@ -665,6 +680,7 @@
                                                  EthCriterion ethCriterion,
                                                  VlanIdCriterion vidCriterion,
                                                  VlanId assignedVlan,
+                                                 MacAddress unicastMac,
                                                  ApplicationId applicationId) {
         // Consider PortNumber.ANY as wildcard. Match ETH_DST only
         if (portCriterion != null && PortNumber.ANY.equals(portCriterion.port())) {
@@ -673,7 +689,7 @@
 
         // Multicast MAC
         if (ethCriterion.mask() != null) {
-            return processMcastEthDstFilter(ethCriterion, assignedVlan, applicationId);
+            return processMcastEthDstFilter(ethCriterion, assignedVlan, unicastMac, applicationId);
         }
 
         //handling untagged packets via assigned VLAN
@@ -907,13 +923,35 @@
 
     List<List<FlowRule>> processMcastEthDstFilter(EthCriterion ethCriterion,
                                                       VlanId assignedVlan,
+                                                      MacAddress unicastMac,
                                                       ApplicationId applicationId) {
-        ImmutableList.Builder<FlowRule> builder = ImmutableList.builder();
-        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
-        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        ImmutableList.Builder<FlowRule> unicastFlows = ImmutableList.builder();
+        ImmutableList.Builder<FlowRule> multicastFlows = ImmutableList.builder();
+        TrafficSelector.Builder selector;
+        TrafficTreatment.Builder treatment;
         FlowRule rule;
 
         if (IPV4_MULTICAST.equals(ethCriterion.mac())) {
+            if (requireUnicastBeforeMulticast()) {
+                selector = DefaultTrafficSelector.builder();
+                treatment = DefaultTrafficTreatment.builder();
+                selector.matchEthType(Ethernet.TYPE_IPV4);
+                selector.matchEthDst(unicastMac);
+                selector.matchVlanId(assignedVlan);
+                treatment.transition(UNICAST_ROUTING_TABLE);
+                rule = DefaultFlowRule.builder()
+                        .forDevice(deviceId)
+                        .withSelector(selector.build())
+                        .withTreatment(treatment.build())
+                        .withPriority(DEFAULT_PRIORITY)
+                        .fromApp(applicationId)
+                        .makePermanent()
+                        .forTable(TMAC_TABLE).build();
+                unicastFlows.add(rule);
+            }
+
+            selector = DefaultTrafficSelector.builder();
+            treatment = DefaultTrafficTreatment.builder();
             selector.matchEthType(Ethernet.TYPE_IPV4);
             selector.matchEthDstMasked(ethCriterion.mac(), ethCriterion.mask());
             selector.matchVlanId(assignedVlan);
@@ -926,10 +964,28 @@
                     .fromApp(applicationId)
                     .makePermanent()
                     .forTable(TMAC_TABLE).build();
-            builder.add(rule);
+            multicastFlows.add(rule);
         }
 
         if (IPV6_MULTICAST.equals(ethCriterion.mac())) {
+            if (requireUnicastBeforeMulticast()) {
+                selector = DefaultTrafficSelector.builder();
+                treatment = DefaultTrafficTreatment.builder();
+                selector.matchEthType(Ethernet.TYPE_IPV6);
+                selector.matchEthDst(unicastMac);
+                selector.matchVlanId(assignedVlan);
+                treatment.transition(UNICAST_ROUTING_TABLE);
+                rule = DefaultFlowRule.builder()
+                        .forDevice(deviceId)
+                        .withSelector(selector.build())
+                        .withTreatment(treatment.build())
+                        .withPriority(DEFAULT_PRIORITY)
+                        .fromApp(applicationId)
+                        .makePermanent()
+                        .forTable(TMAC_TABLE).build();
+                unicastFlows.add(rule);
+            }
+
             selector = DefaultTrafficSelector.builder();
             treatment = DefaultTrafficTreatment.builder();
             selector.matchEthType(Ethernet.TYPE_IPV6);
@@ -944,9 +1000,9 @@
                     .fromApp(applicationId)
                     .makePermanent()
                     .forTable(TMAC_TABLE).build();
-            builder.add(rule);
+            multicastFlows.add(rule);
         }
-        return ImmutableList.of(builder.build());
+        return ImmutableList.of(unicastFlows.build(), multicastFlows.build());
     }
 
     private Collection<FlowRule> processForward(ForwardingObjective fwd) {
@@ -1684,6 +1740,21 @@
         return null;
     }
 
+    private static MacAddress readEthDstFromTreatment(TrafficTreatment treatment) {
+        if (treatment == null) {
+            return null;
+        }
+        for (Instruction i : treatment.allInstructions()) {
+            if (i instanceof ModEtherInstruction) {
+                ModEtherInstruction modEtherInstruction = (ModEtherInstruction) i;
+                if (modEtherInstruction.subtype() == L2SubType.ETH_DST) {
+                    return modEtherInstruction.mac();
+                }
+            }
+        }
+        return null;
+    }
+
     /**
      *  Utility class that retries sending flows a fixed number of times, even if
      *  some of the attempts are successful. Used only for forwarding objectives.
diff --git a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3QmxPipeline.java b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3QmxPipeline.java
index d989217..418cfbf 100644
--- a/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3QmxPipeline.java
+++ b/drivers/default/src/main/java/org/onosproject/driver/pipeline/ofdpa/Ofdpa3QmxPipeline.java
@@ -41,4 +41,9 @@
     protected boolean supportIpv6L4Dst() {
         return false;
     }
+
+    @Override
+    protected boolean requireUnicastBeforeMulticast() {
+        return true;
+    }
 }