Different vlan-id for leaf spine pws.

With this patch we introduce a special vlan for transporting
traffic for leaf-spine pseudowires. Each l-s pw is assigned
a different vlan from the range 4000-2000. This information is
kept internally and exposed to the user with the "pseudowires"
cli command.

Leaf-Leaf pseudowire traffic is still transported untagged, but
we will probably soon need to use special transport vlans there
also.

Change-Id: If6d0b7176a9bd4b89fb7d46db2a49e048bd953ee
diff --git a/src/main/java/org/onosproject/segmentrouting/cli/PseudowireListCommand.java b/src/main/java/org/onosproject/segmentrouting/cli/PseudowireListCommand.java
index 18625db..70ae32f 100644
--- a/src/main/java/org/onosproject/segmentrouting/cli/PseudowireListCommand.java
+++ b/src/main/java/org/onosproject/segmentrouting/cli/PseudowireListCommand.java
@@ -16,6 +16,7 @@
 package org.onosproject.segmentrouting.cli;
 
 import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.VlanId;
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.segmentrouting.SegmentRoutingService;
 import org.onosproject.segmentrouting.pwaas.DefaultL2Tunnel;
@@ -36,11 +37,9 @@
             "Pseudowire id = %s \n" +
                     "   mode : %s, sdTag : %s, pwLabel : %s \n" +
                     "   cP1 : %s , cP1OuterTag : %s, cP1InnerTag : %s \n" +
-                    "   cP2 : %s , cP2OuterTag : %s, cP2InnerTag : %s \n" /* +
-                    "   Path used : (%s - %s) <-> (%s - %s) \n" */;
+                    "   cP2 : %s , cP2OuterTag : %s, cP2InnerTag : %s \n" +
+                    "   transportVlan : %s";
 
-                    // TODO:  uncomment string when path failures are fixed also for the links
-                    // TODO:  used in spine for pw traffic.
     @Override
     protected void execute() {
 
@@ -71,14 +70,14 @@
     private void printPseudowire(DefaultL2TunnelDescription pseudowire) {
 
 
+        VlanId vlan = pseudowire.l2Tunnel().transportVlan().equals(VlanId.vlanId((short) 4094)) ?
+                VlanId.NONE : pseudowire.l2Tunnel().transportVlan();
+
         print(FORMAT_PSEUDOWIRE, pseudowire.l2Tunnel().tunnelId(), pseudowire.l2Tunnel().pwMode(),
               pseudowire.l2Tunnel().sdTag(), pseudowire.l2Tunnel().pwLabel(),
               pseudowire.l2TunnelPolicy().cP1(), pseudowire.l2TunnelPolicy().cP1OuterTag(),
               pseudowire.l2TunnelPolicy().cP1InnerTag(), pseudowire.l2TunnelPolicy().cP2(),
-              pseudowire.l2TunnelPolicy().cP2OuterTag(), pseudowire.l2TunnelPolicy().cP2InnerTag()/*,
-              pseudowire.l2Tunnel().pathUsed().get(0).src(), pseudowire.l2Tunnel().pathUsed().get(0).dst(),
-              pseudowire.l2Tunnel().pathUsed().get(1).src(), pseudowire.l2Tunnel().pathUsed().get(1).dst()*/);
-
-        // TODO: uncomment arguments when path issue is fixed for spine switches
+              pseudowire.l2TunnelPolicy().cP2OuterTag(), pseudowire.l2TunnelPolicy().cP2InnerTag(),
+              vlan);
     }
 }
\ No newline at end of file
diff --git a/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2Tunnel.java b/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2Tunnel.java
index 60de5c8..64923bb 100644
--- a/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2Tunnel.java
+++ b/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2Tunnel.java
@@ -58,6 +58,12 @@
     private List<Link> pathUsed;
 
     /**
+     * Vlan which will be used for the encapsualted
+     * vlan traffic.
+     */
+    private VlanId transportVlan;
+
+    /**
      * Creates a inter-co l2 tunnel using the
      * supplied parameters.
      *
@@ -93,6 +99,7 @@
         this.pwLabel = l2Tunnel.pwLabel();
         this.interCoLabel = l2Tunnel.interCoLabel();
         this.pathUsed = l2Tunnel.pathUsed();
+        this.transportVlan = l2Tunnel.transportVlan;
     }
 
     /**
@@ -167,6 +174,15 @@
     }
 
     /**
+     * Set the transport vlan for the pseudowire.
+     *
+     * @param vlan the vlan to use.
+     */
+    public void setTransportVlan(VlanId vlan) {
+        transportVlan = vlan;
+    }
+
+    /**
      * Returns the used path of the pseudowire.
      *
      * @return pathUsed
@@ -175,6 +191,11 @@
         return pathUsed;
     }
 
+    public VlanId transportVlan() {
+        return transportVlan;
+    }
+
+
     /**
      * Returns the inter-co label.
      *
@@ -215,6 +236,7 @@
                 .add("tunnelId", tunnelId())
                 .add("pwLabel", pwLabel())
                 .add("interCoLabel", interCoLabel())
+                .add("transportVlan", transportVlan())
                 .toString();
     }
 
diff --git a/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2TunnelDescription.java b/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2TunnelDescription.java
index a91f5ee..b5206a3 100644
--- a/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2TunnelDescription.java
+++ b/src/main/java/org/onosproject/segmentrouting/pwaas/DefaultL2TunnelDescription.java
@@ -79,6 +79,24 @@
         return l2TunnelPolicy;
     }
 
+    /**
+     * Sets the l2 tunnel.
+     *
+     * @param tunnel the l2 tunnel to set.
+     */
+    public void setL2Tunnel(DefaultL2Tunnel tunnel) {
+        l2Tunnel = tunnel;
+    }
+
+    /**
+     * Sets the l2 policy.
+     *
+     * @param policy the policy to set.
+     */
+    public void setL2TunnelPolicy(DefaultL2TunnelPolicy policy) {
+        l2TunnelPolicy = policy;
+    }
+
     @Override
     public int hashCode() {
         return Objects.hash(this.l2Tunnel, this.l2TunnelPolicy);
diff --git a/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java b/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
index a2048b0..133de0c 100644
--- a/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
+++ b/src/main/java/org/onosproject/segmentrouting/pwaas/L2TunnelHandler.java
@@ -52,6 +52,7 @@
 import org.onosproject.segmentrouting.config.PwaasConfig;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.DistributedSet;
 import org.onosproject.store.service.Serializer;
 import org.onosproject.store.service.Versioned;
 import org.slf4j.Logger;
@@ -100,6 +101,18 @@
     private final KryoNamespace.Builder l2TunnelKryo;
 
     /**
+     * Contains transport vlans used for spine-leaf pseudowires.
+     */
+    private final DistributedSet<VlanId> vlanStore;
+
+    /**
+     * Used for determining transport vlans for leaf-spine.
+     */
+    private short transportVlanUpper = 4093, transportVlanLower = 3500;
+
+    private static final VlanId UNTAGGED_TRANSPORT_VLAN = VlanId.vlanId((short) 4094);
+
+    /**
      * Create a l2 tunnel handler for the deploy and
      * for the tear down of pseudo wires.
      *
@@ -140,6 +153,15 @@
                 .withName("onos-l2-tunnel-store")
                 .withSerializer(Serializer.using(l2TunnelKryo.build()))
                 .build();
+
+        vlanStore = srManager.storageService.<VlanId>setBuilder()
+                .withName("onos-transport-vlan-store")
+                .withSerializer(Serializer.using(
+                        new KryoNamespace.Builder()
+                                .register(KryoNamespaces.API)
+                                .build()))
+                .build()
+                .asDistributedSet();
     }
 
     /**
@@ -282,6 +304,41 @@
     }
 
     /**
+     * Determines vlan used for transporting the pw traffic.
+     *
+     * Leaf-Leaf traffic is transferred untagged, thus we choose the UNTAGGED_TRANSPORT_VLAN
+     * and also make sure to add the popVlan instruction.
+     * For spine-leaf pws we choose the highest vlan value available from a certain range.
+     *
+     * @param spinePw if the pw is leaf-spine.
+     * @return The vlan id chossen to transport this pseudowire. If vlan is UNTAGGED_TRANSPORT_VLAN
+     *         then the pw is transported untagged.
+     */
+    private VlanId determineTransportVlan(boolean spinePw) {
+
+        if (!spinePw) {
+
+            log.info("Untagged transport with internal vlan {} for pseudowire!", UNTAGGED_TRANSPORT_VLAN);
+            return UNTAGGED_TRANSPORT_VLAN;
+        } else {
+            for (short i = transportVlanUpper; i > transportVlanLower; i--) {
+
+                VlanId vlanToUse = VlanId.vlanId((short) i);
+                if (!vlanStore.contains(vlanToUse)) {
+
+                    vlanStore.add(vlanToUse);
+                    log.info("Transport vlan {} for pseudowire!", vlanToUse);
+                    return vlanToUse;
+                }
+            }
+
+            log.info("No available transport vlan found, pseudowire traffic will be carried untagged " +
+                             "with internal vlan {}!", UNTAGGED_TRANSPORT_VLAN);
+            return UNTAGGED_TRANSPORT_VLAN;
+        }
+    }
+
+    /**
      * Adds a single pseudowire from leaf to a leaf.
      * This method can be called from cli commands
      * without configuration updates, thus it does not check for mastership
@@ -335,6 +392,7 @@
         }
 
         pw.l2Tunnel().setPath(path);
+        pw.l2Tunnel().setTransportVlan(determineTransportVlan(spinePw));
 
         // next hops for next objectives
 
@@ -574,26 +632,22 @@
             return;
         }
         // only determine if the new pseudowire is leaf-spine, because
-        // removal process is the same for both leaf-leaf and leaf-spine
-        // pws.
+        // removal process is the same for both leaf-leaf and leaf-spine pws
         boolean newPwSpine;
         try {
             newPwSpine = !srManager.deviceConfiguration().isEdgeDevice(newPw.l2TunnelPolicy().cP1().deviceId()) ||
                     !srManager.deviceConfiguration().isEdgeDevice(newPw.l2TunnelPolicy().cP2().deviceId());
         } catch (DeviceConfigNotFoundException e) {
-            // if exception is caught treat the newpw as leaf-leaf
+            // if exception is caught treat the new pw as leaf-leaf
             newPwSpine = false;
         }
 
-        // copy the variable here because we need to
-        // use it in lambda thus it needs to be final
+        // copy the variable here because we need to use it in lambda thus it needs to be final
         boolean finalNewPwSpine = newPwSpine;
 
-
         log.info("Updating pseudowire {}", oldPw.l2Tunnel().tunnelId());
 
-        // The async tasks to orchestrate the next and
-        // forwarding update.
+        // The async tasks to orchestrate the next and forwarding update
         CompletableFuture<ObjectiveError> fwdInitNextFuture = new CompletableFuture<>();
         CompletableFuture<ObjectiveError> revInitNextFuture = new CompletableFuture<>();
         CompletableFuture<ObjectiveError> fwdTermNextFuture = new CompletableFuture<>();
@@ -601,20 +655,27 @@
         CompletableFuture<ObjectiveError> fwdPwFuture = new CompletableFuture<>();
         CompletableFuture<ObjectiveError> revPwFuture = new CompletableFuture<>();
 
-        // First we remove both policy.
-        log.debug("Start deleting fwd policy for {}", tunnelId);
-
-        // first delete all information from our stores
-        // we can not do it asynchronously
+        // first delete all information from our stores, we can not do it asynchronously
         l2PolicyStore.remove(Long.toString(tunnelId));
+
+        // grab the old l2 tunnel from the store, since it carries information which is not exposed
+        // to the user configuration and set it to oldPw.
+        oldPw.setL2Tunnel(l2TunnelStore.get(Long.toString(tunnelId)).value());
+        VlanId transportVlan = l2TunnelStore.get(Long.toString(tunnelId)).value().transportVlan();
         l2TunnelStore.remove(Long.toString(tunnelId));
 
+        // remove the reserved transport vlan, if one is used
+        if (!transportVlan.equals(UNTAGGED_TRANSPORT_VLAN)) {
+            vlanStore.remove(transportVlan);
+        }
+
+        // First we remove both policy.
+        log.debug("Start deleting fwd policy for {}", tunnelId);
         VlanId egressVlan = determineEgressVlan(oldPw.l2TunnelPolicy().cP1OuterTag(),
                                                  oldPw.l2TunnelPolicy().cP1InnerTag(),
                                                  oldPw.l2TunnelPolicy().cP2OuterTag(),
                                                  oldPw.l2TunnelPolicy().cP2InnerTag());
-        deletePolicy(tunnelId,
-                      oldPw.l2TunnelPolicy().cP1(),
+        deletePolicy(tunnelId, oldPw.l2TunnelPolicy().cP1(),
                       oldPw.l2TunnelPolicy().cP1InnerTag(),
                       oldPw.l2TunnelPolicy().cP1OuterTag(),
                       egressVlan,
@@ -626,8 +687,7 @@
                                           oldPw.l2TunnelPolicy().cP2InnerTag(),
                                           oldPw.l2TunnelPolicy().cP1OuterTag(),
                                           oldPw.l2TunnelPolicy().cP1InnerTag());
-        deletePolicy(tunnelId,
-                      oldPw.l2TunnelPolicy().cP2(),
+        deletePolicy(tunnelId, oldPw.l2TunnelPolicy().cP2(),
                       oldPw.l2TunnelPolicy().cP2InnerTag(),
                       oldPw.l2TunnelPolicy().cP2OuterTag(),
                       egressVlan, revInitNextFuture,
@@ -667,7 +727,7 @@
         List<Link> path = getPath(newPw.l2TunnelPolicy().cP1(),
                                    newPw.l2TunnelPolicy().cP2());
         if (path == null) {
-            log.info("Deploying process : " +
+            log.error("Update process : " +
                              "No path between the connection points for pseudowire {}", newPw.l2Tunnel().tunnelId());
             return;
         }
@@ -675,25 +735,25 @@
         Link fwdNextHop, revNextHop;
         if (!finalNewPwSpine) {
             if (path.size() != 2) {
-                log.info("Update process : Error, path between two leafs should have size of 2, for pseudowire {}",
+                log.error("Update process : Error, path between two leafs should have size of 2, for pseudowire {}",
                          newPw.l2Tunnel().tunnelId());
                 return;
             }
-
             fwdNextHop = path.get(0);
             revNextHop = reverseLink(path.get(1));
         } else {
             if (path.size() != 1) {
-                log.info("Update process : Error, path between leaf spine should equal to 1, for pseudowire {}",
+                log.error("Update process : Error, path between leaf spine should equal to 1, for pseudowire {}",
                          newPw.l2Tunnel().tunnelId());
                 return;
             }
-
             fwdNextHop = path.get(0);
             revNextHop = reverseLink(path.get(0));
         }
 
+        // set new path and transport vlan.
         newPw.l2Tunnel().setPath(path);
+        newPw.l2Tunnel().setTransportVlan(determineTransportVlan(newPwSpine));
 
         // At the end we install the updated PW.
         fwdPwFuture.thenAcceptAsync(status -> {
@@ -731,6 +791,7 @@
         });
         revPwFuture.thenAcceptAsync(status -> {
             if (status == null) {
+
                 log.debug("Update process : Deploying new rev pw for {}", tunnelId);
                 Result lamdaResult = deployPseudoWireInit(newPw.l2Tunnel(),
                                                            newPw.l2TunnelPolicy().cP2(),
@@ -822,6 +883,11 @@
         l2PolicyStore.remove(Long.toString(l2TunnelId));
         l2TunnelStore.remove(Long.toString(l2TunnelId));
 
+        // remove the reserved transport vlan
+        if (!pwToRemove.l2Tunnel().transportVlan().equals(UNTAGGED_TRANSPORT_VLAN)) {
+            vlanStore.remove(pwToRemove.l2Tunnel().transportVlan());
+        }
+
         log.info("Removal process : Tearing down forward direction of pseudowire {}", l2TunnelId);
 
         VlanId egressVlan = determineEgressVlan(pwToRemove.l2TunnelPolicy().cP1OuterTag(),
@@ -1077,7 +1143,7 @@
 
         // We create the group relative to the termination.
         NextObjective.Builder nextObjectiveBuilder = createNextObjective(TERMINATION, egress, null,
-                                                                         null, egress.deviceId(),
+                                                                         l2Tunnel, egress.deviceId(),
                                                                          spinePw);
         if (nextObjectiveBuilder == null) {
             return INTERNAL_ERROR;
@@ -1119,8 +1185,82 @@
         srManager.flowObjectiveService.forward(egress.deviceId(), fwdBuilder.add(context));
         log.debug("Creating new FwdObj for termination NextObj with id={} for tunnel {}",
                   nextId, l2Tunnel.tunnelId());
-        return SUCCESS;
 
+        if (spinePw) {
+
+            // determine the input port at the
+            PortNumber inPort;
+
+            if (egress.deviceId().
+                    equals(l2Tunnel.pathUsed().get(0).dst().deviceId())) {
+                    inPort = l2Tunnel.pathUsed().get(0).dst().port();
+            } else {
+                    inPort = l2Tunnel.pathUsed().get(0).src().port();
+            }
+
+            MacAddress dstMac;
+            try {
+                dstMac = srManager.deviceConfiguration().getDeviceMac(egress.deviceId());
+            } catch (Exception e) {
+                log.info("Device not found in configuration, no programming of MAC address");
+                dstMac = null;
+            }
+
+            log.info("Populating filtering objective for pseudowire transport" +
+                             " with vlan = {}, port = {}, mac = {}",
+                     l2Tunnel.transportVlan(),
+                     inPort,
+                     dstMac);
+            FilteringObjective.Builder filteringObjectiveBuilder =
+                    createNormalPipelineFiltObjective(inPort, l2Tunnel.transportVlan(), dstMac);
+            context = new DefaultObjectiveContext(( objective ) ->
+                                                          log.debug("Special filtObj for  " + "for {} populated",
+                                                                    l2Tunnel.tunnelId()),
+                                                  ( objective, error ) ->
+                                                          log.warn("Failed to populate " +
+                                                                           "special filtObj " +
+                                                                           "rule for {}: {}",
+                                                                                   l2Tunnel.tunnelId(), error));
+            TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+            filteringObjectiveBuilder.withMeta(treatment.build());
+            srManager.flowObjectiveService.filter(egress.deviceId(), filteringObjectiveBuilder.add(context));
+            log.debug("Creating new special FiltObj for termination point with tunnel {} for port {}",
+                      l2Tunnel.tunnelId(),
+                      inPort);
+        }
+
+        return SUCCESS;
+    }
+
+
+    /**
+     * Creates the filtering objective according to a given port and vlanid.
+     *
+     * @param inPort   the in port
+     * @param vlanId the inner vlan tag
+     * @return the filtering objective
+     */
+    private FilteringObjective.Builder createNormalPipelineFiltObjective(PortNumber inPort,
+                                                                         VlanId vlanId,
+                                                                         MacAddress dstMac) {
+
+        log.info("Creating filtering objective for pseudowire transport with vlan={}, port={}, mac={}",
+                 vlanId,
+                 inPort,
+                 dstMac);
+        FilteringObjective.Builder fwdBuilder = DefaultFilteringObjective
+                .builder()
+                .withKey(Criteria.matchInPort(inPort))
+                .addCondition(Criteria.matchVlanId(vlanId))
+                .withPriority(SegmentRoutingService.DEFAULT_PRIORITY)
+                .permit()
+                .fromApp(srManager.appId());
+
+        if (dstMac != null) {
+            fwdBuilder.addCondition(Criteria.matchEthDst(dstMac));
+        }
+
+        return fwdBuilder;
     }
 
     /**
@@ -1290,6 +1430,12 @@
                 return null;
             }
             treatmentBuilder.setEthDst(neighborMac);
+
+            // if not a leaf-spine pw we need to POP the vlan at the output
+            // since we carry this traffic untagged.
+            if (!spinePw) {
+                treatmentBuilder.popVlan();
+            }
         } else {
             // We create the next objective which
             // will be a simple l2 group.
@@ -1298,6 +1444,9 @@
                     .withType(NextObjective.Type.SIMPLE)
                     .fromApp(srManager.appId());
         }
+
+        // set the appropriate transport vlan
+        treatmentBuilder.setVlanId(l2Tunnel.transportVlan());
         treatmentBuilder.setOutput(srcCp.port());
         nextObjBuilder.addTreatment(treatmentBuilder.build());
         return nextObjBuilder;
@@ -1441,6 +1590,7 @@
             return;
         }
         NextObjective nextObjective = l2InitiationNextObjStore.get(key).value();
+
         // un-comment in case you want to delete groups used by the pw
         // however, this will break the update of pseudowires cause the L2 interface group can
         // not be deleted (it is referenced by other groups)
@@ -1495,14 +1645,20 @@
                                                                         l2Tunnel.tunnelId(),
                                                                         egress.port(),
                                                                         nextObjective.id());
-        ObjectiveContext context = new DefaultObjectiveContext((objective) -> log.debug("FwdObj for {} {} removed",
+        ObjectiveContext context = new DefaultObjectiveContext((objective) ->
+                                                                       log.debug("FwdObj for {} {}, " +
+                                                                                         "direction {} removed",
                                                                                         TERMINATION,
-                                                                                        l2Tunnel.tunnelId()),
+                                                                                        l2Tunnel.tunnelId(),
+                                                                                        direction),
                                                                (objective, error) ->
-                                                                       log.warn("Failed to remove fwdObj for {} {}",
+                                                                       log.warn("Failed to remove fwdObj " +
+                                                                                        "for {} {}" +
+                                                                                        ", direction {}",
                                                                                 TERMINATION,
                                                                                 l2Tunnel.tunnelId(),
-                                                                                error));
+                                                                                error,
+                                                                                direction));
         srManager.flowObjectiveService.forward(egress.deviceId(), fwdBuilder.remove(context));
 
         // un-comment in case you want to delete groups used by the pw
@@ -1529,8 +1685,50 @@
         srManager.flowObjectiveService.next(egress.deviceId(), (NextObjective) nextObjective.copy().remove(context));
         */
 
-        future.complete(null);
+        // delete the extra filtering objective for terminating
+        // spine-spine pws
+        if (!l2Tunnel.transportVlan().equals(UNTAGGED_TRANSPORT_VLAN)) {
+
+            // determine the input port at the
+            PortNumber inPort;
+
+            if (egress.deviceId().
+                    equals(l2Tunnel.pathUsed().get(0).dst().deviceId())) {
+                inPort = l2Tunnel.pathUsed().get(0).dst().port();
+            } else {
+                inPort = l2Tunnel.pathUsed().get(0).src().port();
+            }
+
+            MacAddress dstMac;
+            try {
+                dstMac = srManager.deviceConfiguration().getDeviceMac(egress.deviceId());
+            } catch (Exception e) {
+                log.info("Device not found in configuration, no programming of MAC address");
+                dstMac = null;
+            }
+
+            log.info("Removing filtering objective for pseudowire transport" +
+                             " with vlan = {}, port = {}, mac = {}",
+                     l2Tunnel.transportVlan(),
+                     inPort,
+                     dstMac);
+            FilteringObjective.Builder filteringObjectiveBuilder =
+                    createNormalPipelineFiltObjective(inPort, l2Tunnel.transportVlan(), dstMac);
+            context = new DefaultObjectiveContext(( objective ) ->
+                                                          log.debug("Special filtObj for  " + "for {} removed",
+                                                                    l2Tunnel.tunnelId()), ( objective, error ) ->
+                    log.warn("Failed to populate " + "special filtObj " +
+                                     "rule for {}: {}", l2Tunnel.tunnelId(), error));
+            TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+            filteringObjectiveBuilder.withMeta(treatment.build());
+            srManager.flowObjectiveService.filter(egress.deviceId(), filteringObjectiveBuilder.remove(context));
+            log.debug("Removing special FiltObj for termination point with tunnel {} for port {}",
+                      l2Tunnel.tunnelId(),
+                      inPort);
+        }
+
         l2TerminationNextObjStore.remove(key);
+        future.complete(null);
     }
 
     /**