BUG 6347: Fine tune RSP removal on SFF update 61/48661/3
authorJaime Caamaño Ruiz <jaime.caamano.ruiz@ericsson.com>
Thu, 24 Nov 2016 13:50:10 +0000 (14:50 +0100)
committerBrady Johnson <brady.allen.johnson@ericsson.com>
Fri, 2 Dec 2016 15:17:21 +0000 (15:17 +0000)
Remove only RSPs affected by SFF dictionary change. If a dictionary
entry is changed or removed, an RSP with a hop on that SFF for the SF
corresponding to that dictionary entry will be removed. Otherwise, it
won't be removed for such a change.

Change-Id: I945bb725f620fd48d5e9bb279b27bdedf43831db
Signed-off-by: Jaime Caamaño Ruiz <jaime.caamano.ruiz@ericsson.com>
sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderRenderedPathAPI.java
sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderServiceForwarderAPI.java
sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderServiceFunctionAPI.java
sfc-provider/src/main/java/org/opendaylight/sfc/provider/listeners/ServiceFunctionForwarderListener.java
sfc-provider/src/test/java/org/opendaylight/sfc/provider/listeners/ServiceFunctionForwarderListenerTest.java

index 9e932dadc8b5c8400ad0a65b719811ad0c6b5b7e..6166640389df21f22e20c8b014952fe2f2b05fc7 100644 (file)
@@ -51,11 +51,11 @@ import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sl.rev14070
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sl.rev140701.VxlanGpe;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sl.rev140701.data.plane.locator.locator.type.Ip;
 import org.opendaylight.yang.gen.v1.urn.intel.params.xml.ns.yang.sfc.sfst.rev150312.LoadBalance;
+import org.opendaylight.yang.gen.v1.urn.intel.params.xml.ns.yang.sfc.sfst.rev150312.LoadPathAware;
 import org.opendaylight.yang.gen.v1.urn.intel.params.xml.ns.yang.sfc.sfst.rev150312.Random;
 import org.opendaylight.yang.gen.v1.urn.intel.params.xml.ns.yang.sfc.sfst.rev150312.RoundRobin;
 import org.opendaylight.yang.gen.v1.urn.intel.params.xml.ns.yang.sfc.sfst.rev150312.ServiceFunctionSchedulerTypeIdentity;
 import org.opendaylight.yang.gen.v1.urn.intel.params.xml.ns.yang.sfc.sfst.rev150312.ShortestPath;
-import org.opendaylight.yang.gen.v1.urn.intel.params.xml.ns.yang.sfc.sfst.rev150312.LoadPathAware;
 import org.opendaylight.yangtools.yang.binding.DataContainer;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
@@ -665,6 +665,18 @@ public class SfcProviderRenderedPathAPI {
         return ret;
     }
 
+    /**
+     * Delete a list of RSPs and associated states.
+     * @param rspNames the list of RSP names.
+     * @return true if everything was deleted ok, false otherwise.
+     */
+    public static boolean deleteRenderedServicePathsAndStates(List<RspName> rspNames) {
+        boolean sfStateOk = SfcProviderServiceFunctionAPI.deleteServicePathFromServiceFunctionState(rspNames);
+        boolean sffStateOk = SfcProviderServiceForwarderAPI.deletePathFromServiceForwarderState(rspNames);
+        boolean rspOk = SfcProviderRenderedPathAPI.deleteRenderedServicePaths(rspNames);
+        return sfStateOk && sffStateOk && rspOk;
+    }
+
     /**
      * This method deletes a RSP from the datastore and frees the Path ID
      * <p>
index cb90a72afb7ed31aea2f7440e58a445076e5731b..4198c31da2a8889125a10f83bcd734638ea09ff7 100755 (executable)
@@ -8,9 +8,15 @@
 
 package org.opendaylight.sfc.provider.api;
 
+import static org.opendaylight.sfc.provider.SfcProviderDebug.printTraceStart;
+import static org.opendaylight.sfc.provider.SfcProviderDebug.printTraceStop;
+
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.RspName;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.SffDataPlaneLocatorName;
@@ -33,8 +39,6 @@ import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev1407
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import static org.opendaylight.sfc.provider.SfcProviderDebug.printTraceStart;
-import static org.opendaylight.sfc.provider.SfcProviderDebug.printTraceStop;
 
 /**
  * This class has the APIs to operate on the ServiceFunction
@@ -405,4 +409,20 @@ public class SfcProviderServiceForwarderAPI {
         printTraceStop(LOG);
         return ret;
     }
+
+    /**
+     * Returns the list of {@link RspName} anchored by a SFF.
+     *
+     * @param sffName the SFF name.
+     * @return the list of {@link RspName}.
+     */
+    public static List<RspName> readRspNamesFromSffState(SffName sffName) {
+        return Optional.ofNullable(SfcProviderServiceForwarderAPI.readSffState(sffName))
+                .orElse(Collections.emptyList())
+                .stream()
+                .map(SffServicePath::getName)
+                .map(SfpName::getValue)
+                .map(RspName::new)
+                .collect(Collectors.toList());
+    }
 }
index 29f0add081558775a35abe3a61ea50f96d7f0be8..934ee3b6cb35f121e3978d075c64d46b92e83119 100755 (executable)
@@ -8,6 +8,9 @@
 
 package org.opendaylight.sfc.provider.api;
 
+import static org.opendaylight.sfc.provider.SfcProviderDebug.printTraceStart;
+import static org.opendaylight.sfc.provider.SfcProviderDebug.printTraceStop;
+
 import java.util.ArrayList;
 import java.util.List;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
@@ -31,8 +34,6 @@ import org.opendaylight.yang.gen.v1.urn.intel.params.xml.ns.sf.desc.mon.rev14120
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import static org.opendaylight.sfc.provider.SfcProviderDebug.printTraceStart;
-import static org.opendaylight.sfc.provider.SfcProviderDebug.printTraceStop;
 
 /**
  * This class has the APIs to operate on the ServiceFunction
@@ -369,4 +370,17 @@ public class SfcProviderServiceFunctionAPI {
         return ret;
     }
 
+    /**
+     * Delete the given list of RSP from the SFF operational state.
+     *
+     * @param rspNames list of RSP names.
+     * @return True if everything went ok, false otherwise.
+     */
+    public static boolean deleteServicePathFromServiceFunctionState(List<RspName> rspNames) {
+       return rspNames.stream()
+               .map(rspName -> new SfpName(rspName.getValue()))
+               .map(SfcProviderServiceFunctionAPI::deleteServicePathFromServiceFunctionState)
+               .reduce(true, (aBoolean, aBoolean2) -> aBoolean && aBoolean2);
+    }
+
 }
index 073b2ed77e69f99aa3aa2d767edaa5471b2f50f2..9d804721a1befa0232af5c8e2f3f50c90cc9375c 100644 (file)
@@ -9,21 +9,25 @@ package org.opendaylight.sfc.provider.listeners;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
 import org.opendaylight.sfc.provider.api.SfcProviderRenderedPathAPI;
 import org.opendaylight.sfc.provider.api.SfcProviderServiceForwarderAPI;
-import org.opendaylight.sfc.provider.api.SfcProviderServiceFunctionAPI;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.RspName;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.SfName;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.SffName;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.SfpName;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.SnName;
+import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.rendered.service.paths.RenderedServicePath;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev140701.ServiceFunctionForwarders;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev140701.service.function.forwarder.base.SffDataPlaneLocator;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev140701.service.function.forwarders.ServiceFunctionForwarder;
 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev140701.service.function.forwarders.service.function.forwarder.ServiceFunctionDictionary;
-import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev140701.service.function.forwarders.state.service.function.forwarder.state.SffServicePath;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
@@ -68,187 +72,103 @@ public class ServiceFunctionForwarderListener extends AbstractDataTreeChangeList
 
     @Override
     protected void add(ServiceFunctionForwarder serviceFunctionForwarder) {
-        LOG.debug("Adding Service Function Forwarder: {}", serviceFunctionForwarder.getName());
+        LOG.info("Adding Service Function Forwarder: {}", serviceFunctionForwarder.getName());
     }
 
     @Override
     protected void remove(ServiceFunctionForwarder serviceFunctionForwarder) {
-        LOG.debug("Deleting Service Function Forwarder: {}", serviceFunctionForwarder.getName());
-        removeRSPs(serviceFunctionForwarder);
+        SffName sffName = serviceFunctionForwarder.getName();
+        // Get RSPs of SFF
+        LOG.debug("Deleting Service Function Forwarder {}", sffName);
+        List<RspName> rspNames = SfcProviderServiceForwarderAPI.readRspNamesFromSffState(sffName);
+        LOG.debug("Deleting Rendered Service Paths {}", rspNames);
+        SfcProviderRenderedPathAPI.deleteRenderedServicePathsAndStates(rspNames);
     }
 
     @Override
     protected void update(ServiceFunctionForwarder originalServiceFunctionForwarder,
             ServiceFunctionForwarder updatedServiceFunctionForwarder) {
         LOG.debug("Updating Service Function Forwarder: {}", originalServiceFunctionForwarder.getName());
-        if (!compareSFFs(originalServiceFunctionForwarder, updatedServiceFunctionForwarder)) {
-            // Only delete the SFF RSPs for changes the require it
-            removeRSPs(updatedServiceFunctionForwarder);
-        }
-    }
-
-    /**
-     * Removes all the RSP in which the Service Function Forwarder is
-     * referenced.
-     *
-     * @param serviceFunctionForwarder
-     */
-    private void removeRSPs(ServiceFunctionForwarder serviceFunctionForwarder) {
-        // TODO: this method is almost literally copied from the previous
-        // version of the listener and should be reviewed.
-
-        /*
-         * Before removing RSPs used by this Service Function, we need to remove
-         * all references in the SFF/SF operational trees
-         */
-        SffName serviceFunctionForwarderName = serviceFunctionForwarder.getName();
-        List<RspName> rspList = new ArrayList<>();
-        List<SffServicePath> sffServicePathList = SfcProviderServiceForwarderAPI
-                .readSffState(serviceFunctionForwarderName);
-        if (sffServicePathList != null && !sffServicePathList.isEmpty()) {
-            if (!SfcProviderServiceForwarderAPI.deleteServiceFunctionForwarderState(serviceFunctionForwarderName)) {
-                LOG.error("Failed to delete Service Function Forwarder {} operational state.",
-                        serviceFunctionForwarder);
-            }
-            for (SffServicePath sffServicePath : sffServicePathList) {
-                // TODO Bug 4495 - RPCs hiding heuristics using Strings -
-                // alagalah
-
-                RspName rspName = new RspName(sffServicePath.getName().getValue());
-                // XXX Another example of Method Overloading confusion brought
-                // about
-                // by Strings
-                SfcProviderServiceFunctionAPI
-                        .deleteServicePathFromServiceFunctionState(new SfpName(rspName.getValue()));
-                rspList.add(rspName);
-                LOG.info("Deleting RSP [{}] on SFF [{}]", rspName, serviceFunctionForwarderName);
-            }
-            SfcProviderRenderedPathAPI.deleteRenderedServicePaths(rspList);
-        }
-
-        /*
-         * We do not update the SFF dictionary. Since the user configured it in
-         * the first place, (s)he is also responsible for updating it.
-         */
+        List<RspName> rspNames = findAffectedRsp(originalServiceFunctionForwarder, updatedServiceFunctionForwarder);
+        LOG.debug("Deleting Rendered Service Paths {}", rspNames);
+        SfcProviderRenderedPathAPI.deleteRenderedServicePathsAndStates(rspNames);
     }
 
     /**
-     * Compare 2 Service Function Forwarders for basic equality. That is only
-     * compare the ServiceNode, DataPlaneLocator, and SfDictionary
-     *
-     * @param originalSff
-     *            the SFF before the change
-     * @param sff
-     *            the changed SFF
-     * @return true on basic equality, false otherwise
+     * Obtains the list of RSP affected by a change on the SFF.
+     * @param originalSff the original SFF.
+     * @param updatedSff the updated SFF.
+     * @return a list of {@link RspName} of the affected RSP.
      */
-    private boolean compareSFFs(ServiceFunctionForwarder originalSff, ServiceFunctionForwarder sff) {
-        //
-        // Compare SFF Service Nodes
-        //
-        if (sff.getServiceNode() != null && originalSff.getServiceNode() != null) {
-            if (!sff.getServiceNode().getValue().equals(originalSff.getServiceNode().getValue())) {
-                LOG.info("compareSFFs: service nodes changed orig [{}] new [{}]",
-                        originalSff.getServiceNode().getValue(), sff.getServiceNode().getValue());
-                return false;
-            }
-        } else if (originalSff.getServiceNode() != null && sff.getServiceNode() == null) {
-            LOG.info("compareSFFs: the service node has been removed");
-            return false;
-        }
-
-        //
-        // Compare SFF IP Mgmt Addresses
-        //
-        if (sff.getIpMgmtAddress() != null && originalSff.getIpMgmtAddress() != null) {
-            if (!sff.getIpMgmtAddress().toString().equals(originalSff.getIpMgmtAddress().toString())) {
-                LOG.info("compareSFFs: IP mgmt addresses changed orig [{}] new [{}]",
-                        originalSff.getIpMgmtAddress().toString(), sff.getIpMgmtAddress().toString());
-                return false;
-            }
-        } else if (originalSff.getIpMgmtAddress() != null && sff.getIpMgmtAddress() == null) {
-            LOG.info("compareSFFs: the IP mgmt address has been removed");
-            return false;
-        }
-
-        //
-        // Compare SFF ServiceFunction Dictionaries
-        //
-        if (!compareSffSfDictionaries(originalSff.getServiceFunctionDictionary(), sff.getServiceFunctionDictionary())) {
-            return false;
+    private List<RspName> findAffectedRsp(
+            ServiceFunctionForwarder originalSff,
+            ServiceFunctionForwarder updatedSff) {
+
+        SffName sffName = originalSff.getName();
+        List<RspName> rspNames = SfcProviderServiceForwarderAPI.readRspNamesFromSffState(sffName);
+
+        // If service node changed, all RSPs are affected
+        SnName originalSnName = originalSff.getServiceNode();
+        SnName updatedSnName = updatedSff.getServiceNode();
+        if (!Objects.equals(originalSnName, updatedSnName)) {
+            LOG.debug("SFF service node updated: original {} updated {}", originalSnName, updatedSnName);
+            return rspNames;
         }
 
-        //
-        // Compare SFF Data Plane Locators
-        //
-        if (!compareSffDpls(originalSff.getSffDataPlaneLocator(), sff.getSffDataPlaneLocator())) {
-            return false;
+        // If ip address changed, all RSPs are affected
+        IpAddress originalIpAddress = originalSff.getIpMgmtAddress();
+        IpAddress updatedIpAddress = updatedSff.getIpMgmtAddress();
+        if (!Objects.equals(originalIpAddress, updatedIpAddress)) {
+            LOG.debug("SFF IpAddress updated: original {} updated {}", originalIpAddress, updatedIpAddress);
+            return rspNames;
         }
 
-        return true;
-    }
-
-    /**
-     * Compare 2 lists of SffSfDictionaries for equality
-     *
-     * @param origSffSfDict
-     *            a list of the original SffSfDict entries before the change
-     * @param sffSfDict
-     *            a list of the SffSfDict entries after the change was made
-     * @return true if the lists are equal, false otherwise
-     */
-    private boolean compareSffSfDictionaries(List<ServiceFunctionDictionary> origSffSfDict,
-            List<ServiceFunctionDictionary> sffSfDict) {
-        if (origSffSfDict.size() > sffSfDict.size()) {
-            LOG.info("compareSffSfDictionaries An SF has been removed");
-            // TODO should we check if the removed SF is used??
-            return false;
+        // If any data plane locator changed, all RSPs are affected
+        // TODO Current data model does not allow to know which DPL is used on a RSP
+        Collection<SffDataPlaneLocator> originalLocators = originalSff.getSffDataPlaneLocator();
+        Collection<SffDataPlaneLocator> updatedLocators = updatedSff.getSffDataPlaneLocator();
+        Collection<SffDataPlaneLocator> removedLocators = new ArrayList<>(originalLocators);
+        removedLocators.removeAll(updatedLocators);
+        if (!removedLocators.isEmpty()) {
+            LOG.debug("SFF locators removed {}", removedLocators);
+            return rspNames;
         }
 
-        Collection<ServiceFunctionDictionary> differentSffSfs = new ArrayList<>(sffSfDict);
-        // This will remove everything in common, thus leaving only the
-        // different values
-        differentSffSfs.removeAll(origSffSfDict);
-
-        // If the different SffSfDict entries are all contained in the
-        // sffSfDict,
-        // then this was a simple case of adding a new SffSfDict entry, else one
-        // of the entries was modified, and the RSPs should be deleted
-        if (!sffSfDict.containsAll(differentSffSfs)) {
-            return false;
+        // If a dictionary changed, any RSP making use of it is affected
+        List<ServiceFunctionDictionary> originalDictionaries = originalSff.getServiceFunctionDictionary();
+        List<ServiceFunctionDictionary> updatedDictionaries = updatedSff.getServiceFunctionDictionary();
+        List<ServiceFunctionDictionary> removedDictionaries = new ArrayList<>(originalDictionaries);
+        removedDictionaries.removeAll(updatedDictionaries);
+        if (!removedDictionaries.isEmpty()) {
+            LOG.debug("SFF dictionaries removed {}", removedDictionaries);
+            return rspNames.stream()
+                    .map(SfcProviderRenderedPathAPI::readRenderedServicePath)
+                    .filter(rsp -> isAnyDictionaryUsedInRsp(sffName, rsp, removedDictionaries))
+                    .map(RenderedServicePath::getName)
+                    .collect(Collectors.toList());
         }
 
-        return true;
+        return Collections.emptyList();
     }
 
     /**
-     * Compare 2 lists of SffDataPlaneLocators for equality
-     *
-     * @param origSffDplList
-     *            a list of the original SffDpl entries before the change
-     * @param sffDplList
-     *            a list of the SffDpl entries after the change was made
-     * @return true if the lists are equal, false otherwise
+     * Whether any hop of a RSP makes use of any SF dictionary of a given list for the given SFF.
+     * @param sffName the SFF name.
+     * @param renderedServicePath the RSP.
+     * @param dictionaries the list of SF dictionaries.
+     * @return true if the RSP makes use of the dictionary.
      */
-    private boolean compareSffDpls(List<SffDataPlaneLocator> origSffDplList, List<SffDataPlaneLocator> sffDplList) {
-        if (origSffDplList.size() > sffDplList.size()) {
-            LOG.info("compareSffDpls An SFF DPL has been removed");
-            // TODO should we check if the removed SFF DPL is used??
-            return false;
-        }
-
-        Collection<SffDataPlaneLocator> differentSffDpls = new ArrayList<>(sffDplList);
-        // This will remove everything in common, thus leaving only the
-        // different values
-        differentSffDpls.removeAll(origSffDplList);
-
-        // If the different SffDpl entries are all contained in the sffDplList,
-        // then this was a simple case of adding a new SffDpl entry, else one
-        // of the entries was modified, and the RSPs should be deleted
-        if (!sffDplList.containsAll(differentSffDpls)) {
-            return false;
-        }
-
-        return true;
+    private boolean isAnyDictionaryUsedInRsp(
+            final SffName sffName,
+            final RenderedServicePath renderedServicePath,
+            final List<ServiceFunctionDictionary> dictionaries) {
+        List<SfName> dictionarySfNames = dictionaries.stream()
+                .map(ServiceFunctionDictionary::getName)
+                .collect(Collectors.toList());
+        return renderedServicePath.getRenderedServicePathHop().stream()
+                .filter(rspHop -> sffName.equals(rspHop.getServiceFunctionForwarder()))
+                .filter(rspHop -> dictionarySfNames.contains(rspHop.getServiceFunctionName()))
+                .findFirst()
+                .isPresent();
     }
 }
index e64ae21aefbc05c61ad5f2f05a31725bd2bd0675..77a2ed8005ac5cc60f872ca78e476712d22ef558 100644 (file)
@@ -16,6 +16,7 @@ import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import org.junit.After;
 import org.junit.Before;
@@ -302,10 +303,12 @@ public class ServiceFunctionForwarderListenerTest extends AbstractDataStoreManag
      * stored in the
      * original data
      * - Call listener explicitly.
+     * - Update the SfDictionary by removing the new SF. The RSP should NOT be deleted.
+     * - Call listener explicitly.
      * - Cleans up
      */
     @Test
-    public void testOnServiceFunctionForwarderUpdated_UpdateSfDict() throws Exception {
+    public void testOnServiceFunctionForwarderUpdated_UpdateAddAndRemoveSfDict() throws Exception {
         RenderedServicePath renderedServicePath = build_and_commit_rendered_service_path();
         assertNotNull(renderedServicePath);
 
@@ -336,11 +339,72 @@ public class ServiceFunctionForwarderListenerTest extends AbstractDataStoreManag
         // Verify that State was NOT removed
         List<SffServicePath> sffServicePathList = SfcProviderServiceForwarderAPI.readSffState(sffName);
         assertNotNull(sffServicePathList);
-        /*
-         * for (SffServicePath sffServicePath : sffServicePathList) {
-         * assertNotEquals(sffServicePath.getName(), renderedServicePath.getName());
-         * }
-         */
+
+        // Now we remove the added unused dictionary
+        updatedServiceFunctionForwarder = originalServiceFunctionForwarder;
+        originalServiceFunctionForwarder = updatedServiceFunctionForwarderBuilder.build();
+        when(dataObjectModification.getDataBefore()).thenReturn(originalServiceFunctionForwarder);
+        when(dataObjectModification.getDataAfter()).thenReturn(updatedServiceFunctionForwarder);
+
+        // The listener will NOT remove the RSP
+        serviceFunctionForwarderListener.onDataTreeChanged(collection);
+        Thread.sleep(500);
+        assertNotNull(SfcProviderRenderedPathAPI.readRenderedServicePath(renderedServicePath.getName()));
+
+        // Verify that State was NOT removed
+        sffServicePathList = SfcProviderServiceForwarderAPI.readSffState(sffName);
+        assertNotNull(sffServicePathList);
+
+        assertTrue(SfcDataStoreAPI.deleteTransactionAPI(SfcInstanceIdentifiers.SFF_IID, LogicalDatastoreType.CONFIGURATION));
+        assertTrue(SfcDataStoreAPI.deleteTransactionAPI(SfcInstanceIdentifiers.SF_IID, LogicalDatastoreType.CONFIGURATION));
+        assertTrue(SfcDataStoreAPI.deleteTransactionAPI(SfcInstanceIdentifiers.SFC_IID, LogicalDatastoreType.CONFIGURATION));
+        assertTrue(SfcDataStoreAPI.deleteTransactionAPI(SfcInstanceIdentifiers.SFP_IID, LogicalDatastoreType.CONFIGURATION));
+    }
+
+    /**
+     * In this test we create a RSP and update a SFF used by it. This will trigger a more complete
+     * code coverage within the listener.
+     * In order to simulate a removal from the data store this test does the following:
+     * - Create RSP
+     * - Update the SfDictionary by adding a removing a used SF dictionary. The RSP should  be deleted.
+     * - creates a IID and add to removedPaths data structure. This IID points to the SFF objects
+     * stored in the
+     * original data
+     * - Call listener explicitly.
+     * - Cleans up
+     */
+    @Test
+    public void testOnServiceFunctionForwarderUpdated_UpdateRemoveUsedSfDict() throws Exception {
+        RenderedServicePath renderedServicePath = build_and_commit_rendered_service_path();
+        assertNotNull(renderedServicePath);
+
+        // Prepare to remove the first SF used by the RSP.
+        SffName sffName = renderedServicePath.getRenderedServicePathHop().get(0).getServiceFunctionForwarder();
+        ServiceFunctionForwarder originalServiceFunctionForwarder =
+                SfcProviderServiceForwarderAPI.readServiceFunctionForwarder(sffName);
+        assertNotNull(originalServiceFunctionForwarder);
+
+        // Now we prepare the updated data. Change the SffSfDictionary
+        ServiceFunctionForwarderBuilder updatedServiceFunctionForwarderBuilder =
+                new ServiceFunctionForwarderBuilder(originalServiceFunctionForwarder);
+        updatedServiceFunctionForwarderBuilder.setServiceFunctionDictionary(Collections.emptyList());
+        ServiceFunctionForwarder updatedServiceFunctionForwarder = updatedServiceFunctionForwarderBuilder.build();
+
+        // Now we prepare to update the entry through the listener
+        when(dataTreeModification.getRootNode()).thenReturn(dataObjectModification);
+        when(dataObjectModification.getModificationType()).thenReturn(ModificationType.SUBTREE_MODIFIED);
+        when(dataObjectModification.getDataBefore()).thenReturn(originalServiceFunctionForwarder);
+        when(dataObjectModification.getDataAfter()).thenReturn(updatedServiceFunctionForwarder);
+
+        // The listener will remove the RSP
+        collection.add(dataTreeModification);
+        serviceFunctionForwarderListener.onDataTreeChanged(collection);
+        Thread.sleep(500);
+        assertNull(SfcProviderRenderedPathAPI.readRenderedServicePath(renderedServicePath.getName()));
+
+        // Verify that State was removed
+        List<SffServicePath> sffServicePathList = SfcProviderServiceForwarderAPI.readSffState(sffName);
+        assertNull(sffServicePathList);
 
         assertTrue(SfcDataStoreAPI.deleteTransactionAPI(SfcInstanceIdentifiers.SFF_IID, LogicalDatastoreType.CONFIGURATION));
         assertTrue(SfcDataStoreAPI.deleteTransactionAPI(SfcInstanceIdentifiers.SF_IID, LogicalDatastoreType.CONFIGURATION));