From b50e07f46ad29efccb2cc51133a51bef94ccda3f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Thu, 24 Nov 2016 14:50:10 +0100 Subject: [PATCH] BUG 6347: Fine tune RSP removal on SFF update MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 --- .../api/SfcProviderRenderedPathAPI.java | 14 +- .../api/SfcProviderServiceForwarderAPI.java | 24 +- .../api/SfcProviderServiceFunctionAPI.java | 18 +- .../ServiceFunctionForwarderListener.java | 238 ++++++------------ .../ServiceFunctionForwarderListenerTest.java | 76 +++++- 5 files changed, 200 insertions(+), 170 deletions(-) diff --git a/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderRenderedPathAPI.java b/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderRenderedPathAPI.java index 9e932dadc..616664038 100644 --- a/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderRenderedPathAPI.java +++ b/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderRenderedPathAPI.java @@ -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 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 *

diff --git a/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderServiceForwarderAPI.java b/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderServiceForwarderAPI.java index cb90a72af..4198c31da 100755 --- a/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderServiceForwarderAPI.java +++ b/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderServiceForwarderAPI.java @@ -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 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()); + } } diff --git a/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderServiceFunctionAPI.java b/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderServiceFunctionAPI.java index 29f0add08..934ee3b6c 100755 --- a/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderServiceFunctionAPI.java +++ b/sfc-provider/src/main/java/org/opendaylight/sfc/provider/api/SfcProviderServiceFunctionAPI.java @@ -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 rspNames) { + return rspNames.stream() + .map(rspName -> new SfpName(rspName.getValue())) + .map(SfcProviderServiceFunctionAPI::deleteServicePathFromServiceFunctionState) + .reduce(true, (aBoolean, aBoolean2) -> aBoolean && aBoolean2); + } + } diff --git a/sfc-provider/src/main/java/org/opendaylight/sfc/provider/listeners/ServiceFunctionForwarderListener.java b/sfc-provider/src/main/java/org/opendaylight/sfc/provider/listeners/ServiceFunctionForwarderListener.java index 073b2ed77..9d804721a 100644 --- a/sfc-provider/src/main/java/org/opendaylight/sfc/provider/listeners/ServiceFunctionForwarderListener.java +++ b/sfc-provider/src/main/java/org/opendaylight/sfc/provider/listeners/ServiceFunctionForwarderListener.java @@ -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 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 rspList = new ArrayList<>(); - List 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 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 findAffectedRsp( + ServiceFunctionForwarder originalSff, + ServiceFunctionForwarder updatedSff) { + + SffName sffName = originalSff.getName(); + List 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 origSffSfDict, - List 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 originalLocators = originalSff.getSffDataPlaneLocator(); + Collection updatedLocators = updatedSff.getSffDataPlaneLocator(); + Collection removedLocators = new ArrayList<>(originalLocators); + removedLocators.removeAll(updatedLocators); + if (!removedLocators.isEmpty()) { + LOG.debug("SFF locators removed {}", removedLocators); + return rspNames; } - Collection 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 originalDictionaries = originalSff.getServiceFunctionDictionary(); + List updatedDictionaries = updatedSff.getServiceFunctionDictionary(); + List 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 origSffDplList, List 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 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 dictionaries) { + List 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(); } } diff --git a/sfc-provider/src/test/java/org/opendaylight/sfc/provider/listeners/ServiceFunctionForwarderListenerTest.java b/sfc-provider/src/test/java/org/opendaylight/sfc/provider/listeners/ServiceFunctionForwarderListenerTest.java index e64ae21ae..77a2ed800 100644 --- a/sfc-provider/src/test/java/org/opendaylight/sfc/provider/listeners/ServiceFunctionForwarderListenerTest.java +++ b/sfc-provider/src/test/java/org/opendaylight/sfc/provider/listeners/ServiceFunctionForwarderListenerTest.java @@ -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 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 sffServicePathList = SfcProviderServiceForwarderAPI.readSffState(sffName); + assertNull(sffServicePathList); assertTrue(SfcDataStoreAPI.deleteTransactionAPI(SfcInstanceIdentifiers.SFF_IID, LogicalDatastoreType.CONFIGURATION)); assertTrue(SfcDataStoreAPI.deleteTransactionAPI(SfcInstanceIdentifiers.SF_IID, LogicalDatastoreType.CONFIGURATION)); -- 2.36.6