Repository: telstra/open-kilda
Issue: Ingress mirror install excess pre ingress rule on switch

**Steps to reproduce:**
1. Create a flow
2. Create forward mirror on this flow
3. Get switch rules

**Expected result:**
one QinQ rule in table 1 (it has cookie `0x0080_0000_0***_****`)

**Actual result:**
two same QinQ rules in table 1. The only difference is rule priority 

Changed files:
src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/flow/ingress/IngressMirrorFlowSegmentInstallCommand.java
src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/flow/ingress/IngressMirrorFlowSegmentRemoveCommand.java

diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/flow/ingress/IngressMirrorFlowSegmentInstallCommand.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/flow/ingress/IngressMirrorFlowSegmentInstallCommand.java
index a6f721c8b..3fed2ebda 100644
--- a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/flow/ingress/IngressMirrorFlowSegmentInstallCommand.java
+++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/flow/ingress/IngressMirrorFlowSegmentInstallCommand.java
@@ -15,27 +15,34 @@
 
 package org.openkilda.floodlight.command.flow.ingress;
 
+import org.openkilda.floodlight.command.SpeakerCommandProcessor;
+import org.openkilda.floodlight.command.flow.FlowSegmentReport;
 import org.openkilda.floodlight.command.flow.ingress.of.IngressFlowSegmentInstallMultiTableMirrorFlowModFactory;
 import org.openkilda.floodlight.command.flow.ingress.of.IngressFlowSegmentInstallSingleTableMirrorFlowModFactory;
 import org.openkilda.floodlight.model.FlowSegmentMetadata;
 import org.openkilda.floodlight.model.RulesContext;
 import org.openkilda.messaging.MessageContext;
+import org.openkilda.model.FlowEncapsulationType;
 import org.openkilda.model.FlowEndpoint;
 import org.openkilda.model.FlowTransitEncapsulation;
 import org.openkilda.model.MeterConfig;
 import org.openkilda.model.MirrorConfig;
+import org.openkilda.model.SwitchFeature;
 import org.openkilda.model.SwitchId;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.Sets;
 import lombok.Getter;
 
+import java.util.List;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 
 @Getter
 @SuppressWarnings("squid:MaximumInheritanceDepth")
-public class IngressMirrorFlowSegmentInstallCommand extends IngressFlowSegmentInstallCommand {
+public class IngressMirrorFlowSegmentInstallCommand extends IngressFlowSegmentCommand {
     @JsonCreator
     public IngressMirrorFlowSegmentInstallCommand(
             @JsonProperty("message_context") MessageContext context,
@@ -63,4 +70,28 @@ public class IngressMirrorFlowSegmentInstallCommand extends IngressFlowSegmentIn
                     new IngressFlowSegmentInstallSingleTableMirrorFlowModFactory(this, getSw(), getSwitchFeatures()));
         }
     }
+
+    @Override
+    protected List<Set<SwitchFeature>> getRequiredFeatures() {
+        List<Set<SwitchFeature>> required = super.getRequiredFeatures();
+        if (encapsulation.getType() == FlowEncapsulationType.VXLAN) {
+            required.add(Sets.newHashSet(
+                    SwitchFeature.NOVIFLOW_PUSH_POP_VXLAN, SwitchFeature.KILDA_OVS_PUSH_POP_MATCH_VXLAN));
+        }
+        if (metadata.isMultiTable()) {
+            required.add(Sets.newHashSet(SwitchFeature.MULTI_TABLE));
+        }
+
+        return required;
+    }
+
+    @Override
+    protected CompletableFuture<FlowSegmentReport> makeExecutePlan(SpeakerCommandProcessor commandProcessor) {
+        return makeInstallPlan(commandProcessor);
+    }
+
+    @Override
+    protected SegmentAction getSegmentAction() {
+        return SegmentAction.INSTALL;
+    }
 }
diff --git a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/flow/ingress/IngressMirrorFlowSegmentRemoveCommand.java b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/flow/ingress/IngressMirrorFlowSegmentRemoveCommand.java
index 91c507817..73a17240a 100644
--- a/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/flow/ingress/IngressMirrorFlowSegmentRemoveCommand.java
+++ b/src-java/floodlight-service/floodlight-modules/src/main/java/org/openkilda/floodlight/command/flow/ingress/IngressMirrorFlowSegmentRemoveCommand.java
@@ -15,6 +15,8 @@
 
 package org.openkilda.floodlight.command.flow.ingress;
 
+import org.openkilda.floodlight.command.SpeakerCommandProcessor;
+import org.openkilda.floodlight.command.flow.FlowSegmentReport;
 import org.openkilda.floodlight.command.flow.ingress.of.IngressFlowSegmentRemoveMultiTableMirrorFlowModFactory;
 import org.openkilda.floodlight.command.flow.ingress.of.IngressFlowSegmentRemoveSingleTableMirrorFlowModFactory;
 import org.openkilda.floodlight.model.FlowSegmentMetadata;
@@ -30,9 +32,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 
 @SuppressWarnings("squid:MaximumInheritanceDepth")
-public class IngressMirrorFlowSegmentRemoveCommand extends IngressFlowSegmentRemoveCommand {
+public class IngressMirrorFlowSegmentRemoveCommand extends IngressFlowSegmentCommand {
     public IngressMirrorFlowSegmentRemoveCommand(
             @JsonProperty("message_context") MessageContext context,
             @JsonProperty("command_id") UUID commandId,
@@ -59,4 +62,14 @@ public class IngressMirrorFlowSegmentRemoveCommand extends IngressFlowSegmentRem
                     new IngressFlowSegmentRemoveSingleTableMirrorFlowModFactory(this, getSw(), getSwitchFeatures()));
         }
     }
+
+    @Override
+    protected CompletableFuture<FlowSegmentReport> makeExecutePlan(SpeakerCommandProcessor commandProcessor) {
+        return makeRemovePlan(commandProcessor);
+    }
+
+    @Override
+    protected SegmentAction getSegmentAction() {
+        return SegmentAction.REMOVE;
+    }
 }