From f3ed46b2782020237b55928bb374e828265bb041 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Mon, 22 Jan 2018 17:44:15 +0100 Subject: [PATCH] Refine affected RSP search on SFF update MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Previously, any SFF locator update would cause the removal of any RSP associated with that SFF. With this patch, in regards to a SFF locator update, an associated RSP will be removed only if any of these conditions are met: * The SFF locator is used in a SF dictionary entry for an SF associated with the RSP. * The RSP is associated with more SFFs than the updated SFF so that the updated locator could be used in a SFF-SFF hop. This improvement is particularly meaningful for the logical SFF scenario in combination with the changes introduced for the directional-dpl spec. The logical SFF singleton is no longer an empty SFF and will be updated frequently as paths are created or removed. It is fundamental that other paths are not affected when they shouldn't. The current data model does not allow for optimum processing and there might be performance impacts on big SFFs, as the logical SFF most likely will be. For a future release, there is the option to enhance the data model to optimize this. Change-Id: I16660e241779ff24bf644e4377e12965b12f695b Signed-off-by: Jaime Caamaño Ruiz --- .../ServiceFunctionForwarderListener.java | 133 +++-- .../ServiceFunctionForwarderListenerTest.java | 562 ++++++++++++------ 2 files changed, 480 insertions(+), 215 deletions(-) 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 9c8544679..02136c63d 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 @@ -7,9 +7,12 @@ */ package org.opendaylight.sfc.provider.listeners; +import com.google.common.collect.Sets; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.inject.Inject; @@ -21,13 +24,16 @@ import org.opendaylight.sfc.provider.api.SfcProviderRenderedPathAPI; import org.opendaylight.sfc.provider.api.SfcProviderServiceForwarderAPI; 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.SffDataPlaneLocatorName; 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.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.rsp.rev140701.rendered.service.paths.rendered.service.path.RenderedServicePathHop; 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.service.function.forwarder.service.function.dictionary.SffSfDataPlaneLocator; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.slf4j.Logger; @@ -101,50 +107,107 @@ public class ServiceFunctionForwarderListener extends AbstractSyncDataTreeChange return rspNames; } - // 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 - List originalLocators = originalSff.getSffDataPlaneLocator(); List updatedLocators = updatedSff.getSffDataPlaneLocator(); - boolean isAnyLocatorChanged = originalLocators != null && !originalLocators.isEmpty() && ( - updatedLocators == null || !updatedLocators.containsAll(originalLocators)); - if (isAnyLocatorChanged) { - LOG.debug("SFF locators changed: original {} updated {}", originalLocators, updatedLocators); + if (updatedLocators == null || updatedLocators.isEmpty()) { + LOG.debug("Updated SFF has no locators"); return rspNames; } - // If a dictionary changed, any RSP making use of it is affected - List originalDictList = - originalSff.getServiceFunctionDictionary() != null ? originalSff - .getServiceFunctionDictionary() : Collections.emptyList(); - List updatedDictList = updatedSff.getServiceFunctionDictionary() != null ? updatedSff - .getServiceFunctionDictionary() : Collections.emptyList(); - List removedDictList = updatedDictList - .isEmpty() ? originalDictList : originalDictList.stream().filter(d -> !updatedDictList.contains(d)) - .collect(Collectors.toList()); - if (!removedDictList.isEmpty()) { - LOG.debug("SFF dictionaries removed {}", removedDictList); - return rspNames.stream().map(SfcProviderRenderedPathAPI::readRenderedServicePath) - .filter(rsp -> isAnyDictionaryUsedInRsp(sffName, rsp, removedDictList)) - .map(RenderedServicePath::getName).collect(Collectors.toList()); + List updatedDictList = updatedSff.getServiceFunctionDictionary(); + if (updatedDictList == null || updatedDictList.isEmpty()) { + LOG.debug("Updated SFF has no dictionary entries"); + return rspNames; } - return Collections.emptyList(); + + List originalLocators = originalSff.getSffDataPlaneLocator(); + if (originalLocators == null || originalLocators.isEmpty()) { + LOG.debug("Original SFF has no locators"); + return Collections.emptyList(); + } + + List originalDictList = originalSff.getServiceFunctionDictionary(); + if (originalDictList == null || originalDictList.isEmpty()) { + LOG.debug("Original SFF has no dictionary entries"); + return Collections.emptyList(); + } + + // What follows might require quite a bit of processing for a big SFF. + // Enhancements to the data model could help in this regard. + + // Find out about removed locators + Set removedLocatorNames; + removedLocatorNames = Sets.difference(new HashSet<>(originalLocators), new HashSet<>(updatedLocators)).stream() + .map(SffDataPlaneLocator::getName) + .collect(Collectors.toSet()); + + // Find out about removed dictionary entries + Set removedDictEntries; + removedDictEntries = Sets.difference(new HashSet<>(originalDictList), new HashSet<>(updatedDictList)); + Set invalidDictEntries = new HashSet<>(removedDictEntries); + // A removed locator use in a dictionary entry invalidates it + if (!removedLocatorNames.isEmpty()) { + invalidDictEntries = updatedDictList.stream().collect( + HashSet::new, + (hash, dict) -> { + SffSfDataPlaneLocator sffSfDataPlaneLocator = dict.getSffSfDataPlaneLocator(); + if (sffSfDataPlaneLocator == null) { + return; + } + boolean isInvalid = removedLocatorNames.contains(sffSfDataPlaneLocator.getSffDplName()) + || removedLocatorNames.contains(sffSfDataPlaneLocator.getSffForwardDplName()) + || removedLocatorNames.contains(sffSfDataPlaneLocator.getSffReverseDplName()); + if (isInvalid) { + hash.add(dict); + } + }, + HashSet::addAll); + } + // SFs cannot be used in the RSP if they have no dictionary entry + List invalidSfs = invalidDictEntries.stream() + .map(ServiceFunctionDictionary::getName) + .collect(Collectors.toList()); + // Removed locators may not affect single SFF RSPs + boolean onlyAllowThisSff = removedLocatorNames.size() > 0; + + // check affected RSPs + return rspNames.stream() + .filter(rspName -> !isRspValid(rspName, sffName, onlyAllowThisSff, invalidSfs)) + .collect(Collectors.toList()); } /** - * Whether any hop of a RSP makes use of any SF dictionary of a given list for - * the given SFF. + * Inspect RSP validness according to the following criteria: + * If isSingleSff is set to true, it will + * be checked that the entire path traverses only sffName. + * It will also be checked that none of the service functions + * included in invalidSfList are used for the path + * in any hop along with sffName. * - * @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. + * @param rspName the RSP name. + * @param sffName the SFF name. + * @param isSingleSff if the RSP must have a single SFF and + * has to be sffName. + * @param invalidSfList the disallowed SFs to be used along with + * sffName for the RSP. + * @return true if the RSP is valid according to the criteria defined + * above. */ - 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())) - .anyMatch(rspHop -> dictionarySfNames.contains(rspHop.getServiceFunctionName())); + private boolean isRspValid(final RspName rspName, + final SffName sffName, + final boolean isSingleSff, + final List invalidSfList) { + RenderedServicePath renderedServicePath = SfcProviderRenderedPathAPI.readRenderedServicePath(rspName); + for (RenderedServicePathHop hop : renderedServicePath.getRenderedServicePathHop()) { + SffName serviceFunctionForwarder = hop.getServiceFunctionForwarder(); + SfName serviceFunctionName = hop.getServiceFunctionName(); + boolean sameSff = sffName.equals(serviceFunctionForwarder); + if (!sameSff && isSingleSff) { + return false; + } + if (sameSff && invalidSfList.contains(serviceFunctionName)) { + return false; + } + } + return true; } } 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 e202feb74..d5056d6c7 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 @@ -15,9 +15,14 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -33,7 +38,6 @@ import org.opendaylight.sfc.provider.api.SfcProviderServiceChainAPI; import org.opendaylight.sfc.provider.api.SfcProviderServiceForwarderAPI; import org.opendaylight.sfc.provider.api.SfcProviderServicePathAPI; import org.opendaylight.sfc.provider.api.SfcProviderServiceTypeAPI; -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.SfDataPlaneLocatorName; 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.SfcName; @@ -44,7 +48,6 @@ import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev1 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.CreateRenderedPathInputBuilder; 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.sf.rev140701.ServiceFunctionsBuilder; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf.rev140701.service.function.base.SfDataPlaneLocator; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf.rev140701.service.function.base.SfDataPlaneLocatorBuilder; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf.rev140701.service.function.base.SfDataPlaneLocatorKey; @@ -53,15 +56,14 @@ import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf.rev14070 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sf.rev140701.service.functions.ServiceFunctionKey; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chain.grouping.ServiceFunctionChain; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chain.grouping.ServiceFunctionChainBuilder; -import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chain.grouping.ServiceFunctionChainKey; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chain.grouping.service.function.chain.SfcServiceFunction; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chain.grouping.service.function.chain.SfcServiceFunctionBuilder; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chain.grouping.service.function.chain.SfcServiceFunctionKey; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev140701.Open; -import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev140701.ServiceFunctionForwardersBuilder; 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.forwarder.base.SffDataPlaneLocatorBuilder; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev140701.service.function.forwarder.base.SffDataPlaneLocatorKey; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev140701.service.function.forwarder.base.sff.data.plane.locator.DataPlaneLocator; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev140701.service.function.forwarder.base.sff.data.plane.locator.DataPlaneLocatorBuilder; 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.ServiceFunctionForwarderBuilder; @@ -77,6 +79,7 @@ import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sff.rev1407 import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.service.function.paths.ServiceFunctionPath; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.service.function.paths.ServiceFunctionPathBuilder; 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.cisco.params.xml.ns.yang.sfc.sl.rev140701.data.plane.locator.locator.type.IpBuilder; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address; @@ -438,7 +441,108 @@ public class ServiceFunctionForwarderListenerTest extends AbstractDataStoreManag // Now we prepare the updated data. Change the DataPlaneLocator ServiceFunctionForwarderBuilder updatedServiceFunctionForwarderBuilder = new ServiceFunctionForwarderBuilder( originalServiceFunctionForwarder); - removeSffDpl(updatedServiceFunctionForwarderBuilder); + removeAnySffDpl(updatedServiceFunctionForwarderBuilder); + 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); + /* + * for (SffServicePath sffServicePath : sffServicePathList) { + * assertNotEquals(sffServicePath.getName(), + * renderedServicePath.getName()); } + */ + + 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)); + } + + /** + * Test that the removal of a SFF DPL that it is not used in any SF + * dictionary entry, will not cause the removal of RSP that only traverses + * that SFF. + */ + @Test + public void testOnServiceFunctionForwarderUpdateUnusedDplWontRemoveSingleSffRsp() throws Exception { + RenderedServicePath renderedServicePath = buildAndCommitRenderedServicePathSingleSff(); + 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 DataPlaneLocator + ServiceFunctionForwarderBuilder updatedServiceFunctionForwarderBuilder = new ServiceFunctionForwarderBuilder( + originalServiceFunctionForwarder); + removeSffDpl(updatedServiceFunctionForwarderBuilder, "196.168.66.106"); + 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 NOT remove the RSP + collection.add(dataTreeModification); + serviceFunctionForwarderListener.onDataTreeChanged(collection); + Thread.sleep(500); + assertNotNull(SfcProviderRenderedPathAPI.readRenderedServicePath(renderedServicePath.getName())); + + // Verify that State was NOT removed + List 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)); + } + + /** + * Test that the removal of a SFF DPL that it is used in a SF dictionary + * entry, will cause the removal of RSP even though it only traverses that + * SFF. + */ + @Test + public void testOnServiceFunctionForwarderUpdateUsedDplWillRemoveSingleSffRsp() throws Exception { + RenderedServicePath renderedServicePath = buildAndCommitRenderedServicePathSingleSff(); + 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 DataPlaneLocator + ServiceFunctionForwarderBuilder updatedServiceFunctionForwarderBuilder = new ServiceFunctionForwarderBuilder( + originalServiceFunctionForwarder); + removeSffDpl(updatedServiceFunctionForwarderBuilder, "196.168.66.105"); ServiceFunctionForwarder updatedServiceFunctionForwarder = updatedServiceFunctionForwarderBuilder.build(); // Now we prepare to update the entry through the listener @@ -477,12 +581,8 @@ public class ServiceFunctionForwarderListenerTest extends AbstractDataStoreManag List newSffSfDict = new ArrayList<>(); // First create a builder by copying the existing entries - for (ServiceFunctionDictionary sffSfDictEntry : sffSfDict) { - ServiceFunctionDictionaryBuilder sffSfDictBuilder = new ServiceFunctionDictionaryBuilder(); - sffSfDictBuilder.setName(sffSfDictEntry.getName()).setKey(sffSfDictEntry.getKey()) - .setSffSfDataPlaneLocator(sffSfDictEntry.getSffSfDataPlaneLocator()).setFailmode(Open.class) - .setSffInterfaces(null); - newSffSfDict.add(sffSfDictBuilder.build()); + if (sffSfDict != null) { + newSffSfDict.addAll(sffSfDict); } // Now add another entry @@ -500,15 +600,28 @@ public class ServiceFunctionForwarderListenerTest extends AbstractDataStoreManag sffBuilder.setServiceFunctionDictionary(newSffSfDict); } - private void removeSffDpl(ServiceFunctionForwarderBuilder sffBuilder) { + private void removeAnySffDpl(ServiceFunctionForwarderBuilder sffBuilder) { List sffDplList = sffBuilder.getSffDataPlaneLocator(); List locatorList = new ArrayList<>(); // We want to remove a DPL entry, so just copy all but the last one for (int i = 0; i < sffDplList.size() - 1; i++) { - SffDataPlaneLocator sffDpl = sffDplList.get(i); - SffDataPlaneLocatorBuilder locatorBuilder = new SffDataPlaneLocatorBuilder(sffDpl); - locatorList.add(locatorBuilder.build()); + locatorList.add(sffDplList.get(i)); + } + + sffBuilder.setSffDataPlaneLocator(locatorList); + } + + private void removeSffDpl(ServiceFunctionForwarderBuilder sffBuilder, String dplName) { + List sffDplList = sffBuilder.getSffDataPlaneLocator(); + List locatorList = new ArrayList<>(); + + // We want to remove a DPL entry, so just copy all but the last one + for (SffDataPlaneLocator sffDataPlaneLocator : sffDplList) { + if (Objects.equals(dplName, sffDataPlaneLocator.getName().getValue())) { + continue; + } + locatorList.add(sffDataPlaneLocator); } sffBuilder.setSffDataPlaneLocator(locatorList); @@ -588,218 +701,307 @@ public class ServiceFunctionForwarderListenerTest extends AbstractDataStoreManag } /** - * Builds and return a complete RSP Object. + * Builds and return a complete RSP Object that traverses a single SFF. + * The SFF has one unused locator, "196.168.66.106". * * @return RSP object */ @SuppressWarnings("serial") - RenderedServicePath buildAndCommitRenderedServicePath() throws Exception { + RenderedServicePath buildAndCommitRenderedServicePathSingleSff() throws Exception { - final List locatorIPAddresses = new ArrayList() { - - { - add("196.168.55.1"); - add("196.168.55.2"); - add("196.168.55.3"); - add("196.168.55.4"); - add("196.168.55.5"); - } + final String[] managementIPAddress = { + "196.168.55.101", }; - final List managementIPAddress = new ArrayList() { - - { - add("196.168.55.101"); - add("196.168.55.102"); - add("196.168.55.103"); - add("196.168.55.104"); - add("196.168.55.105"); - } + final String[] serviceFunctionTypes = { + "firewall", + "dpi", + "napt44", + "http-header-enrichment", + "qos" }; - final List ports = new ArrayList() { - - { - add(1111); - add(2222); - add(3333); - add(4444); - add(5555); - } + final String[] serviceFunctionAbstractNames = { + "firewall", + "dpi", + "napt", + "http-header-enrichment", + "qos" }; - final List serviceFunctionTypes = new ArrayList() { + final String sfcName = "unittest-chain-1"; + final String sfpName = "unittest-sfp-1"; + final String rspName = "unittest-rsp-1"; - { - add(new SftTypeName("firewall")); - add(new SftTypeName("dpi")); - add(new SftTypeName("napt44")); - add(new SftTypeName("http-header-enrichment")); - add(new SftTypeName("qos")); - } + final String[][] rsp = { + { "SFF1", "unittest-fw-1" }, + { "SFF1", "unittest-dpi-1" }, + { "SFF1", "unittest-napt-1" }, + { "SFF1", "unittest-http-header-enrichment-1" }, + { "SFF1", "unittest-qos-1" } }; - final List serviceFunctionAbstractNames = new ArrayList() { + final String[][] sfLocatorIps = { + {"196.168.55.1"}, + {"196.168.55.2"}, + {"196.168.55.3"}, + {"196.168.55.4"}, + {"196.168.55.5"} + }; + final String[][] sffLocatorIps = { { - add("firewall"); - add("dpi"); - add("napt"); - add("http-header-enrichment"); - add("qos"); + "196.168.66.101", + "196.168.66.102", + "196.168.66.103", + "196.168.66.104", + "196.168.66.105", + "196.168.66.106" } }; - final SfcName SFC_NAME = new SfcName("unittest-chain-1"); - final SfpName SFP_NAME = new SfpName("unittest-sfp-1"); - final RspName RSP_NAME = new RspName("unittest-rsp-1"); + return buildAndCommitRenderedServicePath(serviceFunctionTypes, + serviceFunctionAbstractNames, + managementIPAddress, + sfLocatorIps, + sffLocatorIps, + rsp, + sfcName, + sfpName, + rspName); + } - List sfNames = new ArrayList() { + /** + * Builds and return a complete RSP Object. + * + * @return RSP object + */ + @SuppressWarnings("serial") + RenderedServicePath buildAndCommitRenderedServicePath() throws Exception { - { - add(new SfName("unittest-fw-1")); - add(new SfName("unittest-dpi-1")); - add(new SfName("unittest-napt-1")); - add(new SfName("unittest-http-header-enrichment-1")); - add(new SfName("unittest-qos-1")); - } + final String[] managementIPAddress = { + "196.168.55.101", + "196.168.55.102", + "196.168.55.103", + "196.168.55.104", + "196.168.55.105" }; - final List sffNames = new ArrayList() { + final String[] serviceFunctionTypes = { + "firewall", + "dpi", + "napt44", + "http-header-enrichment", + "qos" + }; - { - add(new SffName("SFF1")); - add(new SffName("SFF2")); - add(new SffName("SFF3")); - add(new SffName("SFF4")); - add(new SffName("SFF5")); - } + final String[] serviceFunctionAbstractNames = { + "firewall", + "dpi", + "napt", + "http-header-enrichment", + "qos" }; - final String[][] toSffNames = { { "SFF2", "SFF5" }, { "SFF3", "SFF1" }, { "SFF4", "SFF2" }, { "SFF5", "SFF3" }, - { "SFF1", "SFF4" } }; + final String sfcName = "unittest-chain-1"; + final String sfpName = "unittest-sfp-1"; + final String rspName = "unittest-rsp-1"; - final List sffLocatorIPs = new ArrayList() { + final String[][] rsp = { + { "SFF1", "unittest-fw-1" }, + { "SFF2", "unittest-dpi-1" }, + { "SFF3", "unittest-napt-1" }, + { "SFF4", "unittest-http-header-enrichment-1" }, + { "SFF5", "unittest-qos-1" } + }; - { - add("196.168.66.101"); - add("196.168.66.102"); - add("196.168.66.103"); - add("196.168.66.104"); - add("196.168.66.105"); - } + final String[][] sfLocatorIps = { + {"196.168.55.1"}, + {"196.168.55.2"}, + {"196.168.55.3"}, + {"196.168.55.4"}, + {"196.168.55.5"} }; - List sfList = new ArrayList<>(); - List sffList = new ArrayList<>(); + final String[][] sffLocatorIps = { + {"196.168.66.101"}, + {"196.168.66.102"}, + {"196.168.66.103"}, + {"196.168.66.104"}, + {"196.168.66.105"} + }; - final IpAddress[] ipMgmtAddress = new IpAddress[sfNames.size()]; - final IpAddress[] locatorIpAddress = new IpAddress[sfNames.size()]; - SfDataPlaneLocator[] sfDataPlaneLocator = new SfDataPlaneLocator[sfNames.size()]; - ServiceFunctionKey[] key = new ServiceFunctionKey[sfNames.size()]; - for (int i = 0; i < sfNames.size(); i++) { - ipMgmtAddress[i] = new IpAddress(new Ipv4Address(managementIPAddress.get(0))); - locatorIpAddress[i] = new IpAddress(new Ipv4Address(locatorIPAddresses.get(0))); - PortNumber portNumber = new PortNumber(ports.get(i)); - key[i] = new ServiceFunctionKey(new SfName(sfNames.get(i))); - - IpBuilder ipBuilder = new IpBuilder(); - ipBuilder.setIp(locatorIpAddress[i]).setPort(portNumber); - SfDataPlaneLocatorBuilder locatorBuilder = new SfDataPlaneLocatorBuilder(); - locatorBuilder.setName(new SfDataPlaneLocatorName(locatorIPAddresses.get(i))) - .setLocatorType(ipBuilder.build()).setServiceFunctionForwarder(new SffName(sffNames.get(i))); - sfDataPlaneLocator[i] = locatorBuilder.build(); - - ServiceFunctionBuilder sfBuilder = new ServiceFunctionBuilder(); - List dataPlaneLocatorList = new ArrayList<>(); - dataPlaneLocatorList.add(sfDataPlaneLocator[i]); - sfBuilder.setName(new SfName(sfNames.get(i))).setKey(key[i]).setType(serviceFunctionTypes.get(i)) - .setIpMgmtAddress(ipMgmtAddress[i]).setSfDataPlaneLocator(dataPlaneLocatorList); - sfList.add(sfBuilder.build()); - } + return buildAndCommitRenderedServicePath(serviceFunctionTypes, + serviceFunctionAbstractNames, + managementIPAddress, + sfLocatorIps, + sffLocatorIps, + rsp, + sfcName, + sfpName, + rspName); + } - ServiceFunctionsBuilder sfsBuilder = new ServiceFunctionsBuilder(); - sfsBuilder.setServiceFunction(sfList); + private RenderedServicePath buildAndCommitRenderedServicePath(String[] serviceFunctionTypes, + String[] serviceFunctionAbstractNames, + String[] managementIPAddress, + String[][] sfLocatorIps, + String[][] sffLocatorIps, + String[][] rsp, + String sfcName, + String sfpName, + String rspName) + throws InterruptedException { + + Map sfMap = new HashMap<>(); + Map sffMap = new HashMap<>(); + + // for each hop + for (int i = 0; i < rsp.length; i++) { + final int pos = i; + final int numSfs = sfMap.size(); + final String sfName = rsp[pos][1]; + final int numSffs = sffMap.size(); + final String sffName = rsp[pos][0]; + + // update service function + ServiceFunction serviceFunction = sfMap.computeIfAbsent(sfName, (name) -> { + List sfDataPlaneLocatorList = Arrays + .stream(sfLocatorIps[numSfs]) + .map(ipStr -> { + Ip ip = new IpBuilder() + .setIp(new IpAddress(new Ipv4Address(ipStr))) + .build(); + return new SfDataPlaneLocatorBuilder() + .setName(new SfDataPlaneLocatorName(ipStr)) + .setLocatorType(ip) + .setServiceFunctionForwarder(new SffName(rsp[pos][0])) + .build(); + }) + .collect(Collectors.toList()); + return new ServiceFunctionBuilder() + .setName(new SfName(name)) + .setType(new SftTypeName(serviceFunctionTypes[numSfs])) + .setIpMgmtAddress(new IpAddress(new Ipv4Address(managementIPAddress[0]))) + .setSfDataPlaneLocator(sfDataPlaneLocatorList) + .build(); + }); + assertTrue(SfcDataStoreAPI.writeMergeTransactionAPI( + SfcInstanceIdentifiers.SF_IID.child(ServiceFunction.class, serviceFunction.getKey()), + serviceFunction, + LogicalDatastoreType.CONFIGURATION)); + + // update service function forwarder + ServiceFunctionForwarder serviceFunctionForwarder = sffMap.computeIfAbsent(sffName, (name) -> { + List sffDataPlaneLocatorList = Arrays + .stream(sffLocatorIps[numSffs]) + .map(ipStr -> { + Ip ip = new IpBuilder() + .setIp(new IpAddress(new Ipv4Address(ipStr))) + .build(); + DataPlaneLocator locator = new DataPlaneLocatorBuilder() + .setLocatorType(ip) + .setTransport(VxlanGpe.class) + .build(); + return new SffDataPlaneLocatorBuilder() + .setName(new SffDataPlaneLocatorName(ipStr)) + .setDataPlaneLocator(locator) + .build(); + }) + .collect(Collectors.toList()); + return new ServiceFunctionForwarderBuilder() + .setName(new SffName(name)) + .setSffDataPlaneLocator(sffDataPlaneLocatorList) + .setServiceFunctionDictionary(new ArrayList<>()) + .setConnectedSffDictionary(new ArrayList<>()) + .setIpMgmtAddress(new IpAddress(new Ipv4Address(managementIPAddress[pos]))) + .setServiceNode(new SnName(sffName)) + .build(); + }); + ServiceFunctionForwarderBuilder serviceFunctionForwarderBuilder; + serviceFunctionForwarderBuilder = new ServiceFunctionForwarderBuilder(serviceFunctionForwarder); + + // update SFF-SFF dictionary + List sffDictionaryList; + sffDictionaryList = new ArrayList<>(serviceFunctionForwarderBuilder.getConnectedSffDictionary()); + // previous hop sff + if (i > 0 && !Objects.equals(rsp[i - 1][0], sffName)) { + ConnectedSffDictionary prevSff = new ConnectedSffDictionaryBuilder() + .setName(new SffName(rsp[i - 1][0])) + .build(); + if (!sffDictionaryList.contains(prevSff)) { + sffDictionaryList.add(prevSff); + } + } + // next hop sff + if (i < rsp.length - 1 && !Objects.equals(rsp[i + 1][0], sffName)) { + ConnectedSffDictionary nextSff = new ConnectedSffDictionaryBuilder() + .setName(new SffName(rsp[i + 1][0])) + .build(); + if (!sffDictionaryList.contains(nextSff)) { + sffDictionaryList.add(nextSff); + } + } - assertTrue(SfcDataStoreAPI.writePutTransactionAPI(SfcInstanceIdentifiers.SF_IID, sfsBuilder.build(), - LogicalDatastoreType.CONFIGURATION)); - // executor.submit(SfcProviderServiceFunctionAPI.getPutAll(new - // Object[]{sfsBuilder.build()}, - // new Class[]{ServiceFunctions.class})).get(); - Thread.sleep(1000); // Wait they are really created + // update SF dictionary + List sfDictionaryList; + sfDictionaryList = serviceFunctionForwarderBuilder.getServiceFunctionDictionary(); + SffSfDataPlaneLocator sffSfDataPlaneLocator = new SffSfDataPlaneLocatorBuilder() + .setSfDplName( + serviceFunction.getSfDataPlaneLocator().stream() + .filter(sfDpl -> sfDpl.getServiceFunctionForwarder().getValue().equals(sffName)) + .map(SfDataPlaneLocator::getName) + .findAny() + .orElse(null)) + .setSffDplName( + serviceFunctionForwarder.getSffDataPlaneLocator().get(sfDictionaryList.size()).getName()) + .build(); + ServiceFunctionDictionary sfDictEntry = new ServiceFunctionDictionaryBuilder() + .setSffSfDataPlaneLocator(sffSfDataPlaneLocator) + .setFailmode(Open.class) + .setSffInterfaces(null) + .setName(new SfName(sfName)) + .build(); + if (!sfDictionaryList.contains(sfDictEntry)) { + sfDictionaryList.add(sfDictEntry); + } - // Create ServiceFunctionTypeEntry for all ServiceFunctions - for (ServiceFunction serviceFunction : sfList) { - assertTrue(SfcProviderServiceTypeAPI.createServiceFunctionTypeEntry(serviceFunction)); + // finally update sff in db + assertTrue(SfcDataStoreAPI.writeMergeTransactionAPI( + SfcInstanceIdentifiers.SFF_IID.child( + ServiceFunctionForwarder.class, + serviceFunctionForwarder.getKey()), + serviceFunctionForwarderBuilder.build(), + LogicalDatastoreType.CONFIGURATION)); } - // Create Service Function Forwarders - for (int i = 0; i < sffNames.size(); i++) { - // ServiceFunctionForwarders connected to SFF_NAMES[i] - List sffDictionaryList = new ArrayList<>(); - for (int j = 0; j < 2; j++) { - ConnectedSffDictionaryBuilder sffDictionaryEntryBuilder = new ConnectedSffDictionaryBuilder(); - ConnectedSffDictionary sffDictEntry = sffDictionaryEntryBuilder.setName(new SffName(toSffNames[i][j])) - .build(); - sffDictionaryList.add(sffDictEntry); - } + // wait for sff and sf listeners to trigger + Thread.sleep(1000); + + // add sf types + sfMap.values().forEach(sf -> assertTrue(SfcProviderServiceTypeAPI.createServiceFunctionTypeEntry(sf))); - // ServiceFunctions attached to SFF_NAMES[i] - List sfDictionaryList = new ArrayList<>(); - ServiceFunction serviceFunction = sfList.get(i); - SffSfDataPlaneLocatorBuilder sffSfDataPlaneLocatorBuilder = new SffSfDataPlaneLocatorBuilder(); - sffSfDataPlaneLocatorBuilder.setSfDplName(serviceFunction.getSfDataPlaneLocator().get(0).getName()); - SffSfDataPlaneLocator sffSfDataPlaneLocator = sffSfDataPlaneLocatorBuilder.build(); - ServiceFunctionDictionaryBuilder dictionaryEntryBuilder = new ServiceFunctionDictionaryBuilder(); - dictionaryEntryBuilder.setName(serviceFunction.getName()) - .setKey(new ServiceFunctionDictionaryKey(serviceFunction.getName())) - .setSffSfDataPlaneLocator(sffSfDataPlaneLocator).setFailmode(Open.class).setSffInterfaces(null); - ServiceFunctionDictionary sfDictEntry = dictionaryEntryBuilder.build(); - sfDictionaryList.add(sfDictEntry); - - IpBuilder ipBuilder = new IpBuilder(); - ipBuilder.setIp(new IpAddress(new Ipv4Address(sffLocatorIPs.get(i)))).setPort(new PortNumber(ports.get(i))); - DataPlaneLocatorBuilder sffLocatorBuilder = new DataPlaneLocatorBuilder(); - sffLocatorBuilder.setLocatorType(ipBuilder.build()).setTransport(VxlanGpe.class); - SffDataPlaneLocatorBuilder locatorBuilder = new SffDataPlaneLocatorBuilder(); - locatorBuilder.setName(new SffDataPlaneLocatorName(sffLocatorIPs.get(i))) - .setKey(new SffDataPlaneLocatorKey(new SffDataPlaneLocatorName(sffLocatorIPs.get(i)))) - .setDataPlaneLocator(sffLocatorBuilder.build()); - List locatorList = new ArrayList<>(); - locatorList.add(locatorBuilder.build()); - ServiceFunctionForwarderBuilder sffBuilder = new ServiceFunctionForwarderBuilder(); - sffBuilder.setName(new SffName(sffNames.get(i))) - .setKey(new ServiceFunctionForwarderKey(new SffName(sffNames.get(i)))) - .setSffDataPlaneLocator(locatorList).setServiceFunctionDictionary(sfDictionaryList) - .setConnectedSffDictionary(sffDictionaryList) - .setIpMgmtAddress(new IpAddress(new Ipv4Address(managementIPAddress.get(i)))) - .setServiceNode(new SnName(sffNames.get(i).getValue())); - ServiceFunctionForwarder sff = sffBuilder.build(); - sffList.add(sff); - } - ServiceFunctionForwardersBuilder serviceFunctionForwardersBuilder = new ServiceFunctionForwardersBuilder(); - serviceFunctionForwardersBuilder.setServiceFunctionForwarder(sffList); - assertTrue(SfcDataStoreAPI.writePutTransactionAPI(SfcInstanceIdentifiers.SFF_IID, - serviceFunctionForwardersBuilder.build(), LogicalDatastoreType.CONFIGURATION)); // Create Service Function Chain - ServiceFunctionChainKey sfcKey = new ServiceFunctionChainKey(SFC_NAME); List sfcServiceFunctionList = new ArrayList<>(); - for (int i = 0; i < serviceFunctionAbstractNames.size(); i++) { + for (int i = 0; i < serviceFunctionAbstractNames.length; i++) { SfcServiceFunctionBuilder sfcSfBuilder = new SfcServiceFunctionBuilder(); - SfcServiceFunction sfcServiceFunction = sfcSfBuilder.setName(serviceFunctionAbstractNames.get(i)) - .setKey(new SfcServiceFunctionKey(serviceFunctionAbstractNames.get(i))) - .setType(serviceFunctionTypes.get(i)).build(); + SfcServiceFunction sfcServiceFunction = sfcSfBuilder.setName(serviceFunctionAbstractNames[i]) + .setKey(new SfcServiceFunctionKey(serviceFunctionAbstractNames[i])) + .setType(new SftTypeName(serviceFunctionTypes[i])).build(); sfcServiceFunctionList.add(sfcServiceFunction); } ServiceFunctionChainBuilder sfcBuilder = new ServiceFunctionChainBuilder(); - sfcBuilder.setName(SFC_NAME).setKey(sfcKey).setSfcServiceFunction(sfcServiceFunctionList).setSymmetric(true); + sfcBuilder.setName(new SfcName(sfcName)) + .setSfcServiceFunction(sfcServiceFunctionList) + .setSymmetric(true); assertTrue(SfcProviderServiceChainAPI.putServiceFunctionChain(sfcBuilder.build())); Thread.sleep(1000); // Wait SFC is really crated - ServiceFunctionChain readServiceFunctionChain = SfcProviderServiceChainAPI.readServiceFunctionChain(SFC_NAME); + ServiceFunctionChain readServiceFunctionChain; + readServiceFunctionChain = SfcProviderServiceChainAPI.readServiceFunctionChain(new SfcName(sfcName)); assertNotNull(readServiceFunctionChain); @@ -807,7 +1009,7 @@ public class ServiceFunctionForwarderListenerTest extends AbstractDataStoreManag /* Create ServiceFunctionPath */ ServiceFunctionPathBuilder pathBuilder = new ServiceFunctionPathBuilder(); - pathBuilder.setName(new SfpName(SFP_NAME)).setServiceChainName(SFC_NAME).setSymmetric(true); + pathBuilder.setName(new SfpName(sfpName)).setServiceChainName(new SfcName(sfcName)).setSymmetric(true); ServiceFunctionPath serviceFunctionPath = pathBuilder.build(); assertNotNull("Must be not null", serviceFunctionPath); assertTrue(SfcProviderServicePathAPI.putServiceFunctionPath(serviceFunctionPath)); @@ -818,7 +1020,7 @@ public class ServiceFunctionForwarderListenerTest extends AbstractDataStoreManag RenderedServicePath renderedServicePath = null; CreateRenderedPathInputBuilder createRenderedPathInputBuilder = new CreateRenderedPathInputBuilder(); - createRenderedPathInputBuilder.setName(RSP_NAME.getValue()); + createRenderedPathInputBuilder.setName(rspName); createRenderedPathInputBuilder.setSymmetric(serviceFunctionPath.isSymmetric()); renderedServicePath = SfcProviderRenderedPathAPI.createRenderedServicePathAndState(serviceFunctionPath, createRenderedPathInputBuilder.build()); -- 2.36.6