diff options
Diffstat (limited to 'cps-ncmp-service/src')
26 files changed, 581 insertions, 364 deletions
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java index 1fa801c3c5..e9e5f5499f 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java @@ -29,7 +29,6 @@ import static org.onap.cps.ncmp.impl.inventory.CmHandleQueryParametersValidator. import java.util.Collection; import java.util.Map; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; import org.onap.cps.ncmp.api.inventory.models.CompositeState; @@ -51,7 +50,6 @@ import org.onap.cps.spi.model.ModuleReference; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.stereotype.Service; -@Slf4j @Service @RequiredArgsConstructor public class NetworkCmProxyInventoryFacade { @@ -64,15 +62,16 @@ public class NetworkCmProxyInventoryFacade { private final TrustLevelManager trustLevelManager; private final AlternateIdMatcher alternateIdMatcher; + + /** * Registration of Created, Removed, Updated or Upgraded CM Handles. * * @param dmiPluginRegistration Dmi Plugin Registration details * @return dmiPluginRegistrationResponse */ - public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule( - final DmiPluginRegistration dmiPluginRegistration) { - return cmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration); + public DmiPluginRegistrationResponse updateDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) { + return cmHandleRegistrationService.updateDmiRegistration(dmiPluginRegistration); } /** @@ -89,12 +88,16 @@ public class NetworkCmProxyInventoryFacade { * Get all cm handle IDs by various properties. * * @param cmHandleQueryServiceParameters cm handle query parameters - * @return collection of cm handle IDs + * @param outputAlternateId Boolean for cm handle reference type either + * cm handle id (false) or alternate id (true) + * @return collection of cm handle IDs */ public Collection<String> executeParameterizedCmHandleIdSearch( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final Boolean outputAlternateId) { validateCmHandleQueryParameters(cmHandleQueryServiceParameters, InventoryQueryConditions.ALL_CONDITION_NAMES); - return parameterizedCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters); + + return parameterizedCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters, + outputAlternateId); } @@ -156,13 +159,16 @@ public class NetworkCmProxyInventoryFacade { * Retrieve cm handle ids for the given query parameters. * * @param cmHandleQueryApiParameters cm handle query parameters + * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) * @return cm handle ids */ - public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) { + public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters, + final Boolean outputAlternateId) { final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES); - return parameterizedCmHandleQueryService.queryCmHandleIds(cmHandleQueryServiceParameters); + return parameterizedCmHandleQueryService.queryCmHandleReferenceIds(cmHandleQueryServiceParameters, + outputAlternateId); } /** diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java index 5343a2e131..b9b295a8a0 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java @@ -28,16 +28,13 @@ import static org.onap.cps.ncmp.api.data.models.DatastoreType.OPERATIONAL; import java.util.Collection; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.data.models.CmResourceAddress; import org.onap.cps.ncmp.api.data.models.DataOperationRequest; import org.onap.cps.ncmp.api.data.models.DatastoreType; import org.onap.cps.ncmp.api.data.models.OperationType; -import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher; import org.onap.cps.spi.model.DataNode; import org.springframework.stereotype.Service; -@Slf4j @Service @RequiredArgsConstructor public class NetworkCmProxyFacade { @@ -45,7 +42,6 @@ public class NetworkCmProxyFacade { private final NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler; private final NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler; private final DmiDataOperations dmiDataOperations; - private final AlternateIdMatcher alternateIdMatcher; /** * Fetches resource data for a given data store using DMI (Data Management Interface). diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java index 95c3c77b71..86e9c934ce 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java @@ -32,25 +32,31 @@ public interface CmHandleQueryService { * Query Cm Handles based on additional (private) properties. * * @param additionalPropertyQueryPairs private properties for query + * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) * @return Ids of Cm Handles which have these private properties */ - Collection<String> queryCmHandleAdditionalProperties(Map<String, String> additionalPropertyQueryPairs); + Collection<String> queryCmHandleAdditionalProperties(Map<String, String> additionalPropertyQueryPairs, + Boolean outputAlternateId); /** * Query Cm Handles based on public properties. * * @param publicPropertyQueryPairs public properties for query + * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) * @return CmHandles which have these public properties */ - Collection<String> queryCmHandlePublicProperties(Map<String, String> publicPropertyQueryPairs); + Collection<String> queryCmHandlePublicProperties(Map<String, String> publicPropertyQueryPairs, + Boolean outputAlternateId); /** * Query Cm Handles based on Trust Level. * * @param trustLevelPropertyQueryPairs trust level properties for query + * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) * @return Ids of Cm Handles which have desired trust level */ - Collection<String> queryCmHandlesByTrustLevel(Map<String, String> trustLevelPropertyQueryPairs); + Collection<String> queryCmHandlesByTrustLevel(Map<String, String> trustLevelPropertyQueryPairs, + Boolean outputAlternateId); /** * Method which returns cm handles by the cm handles state. @@ -101,4 +107,12 @@ public interface CmHandleQueryService { */ Collection<String> getCmHandleIdsByDmiPluginIdentifier(String dmiPluginIdentifier); + /** + * Get map of cmHandle ids and alternate ids by DMI plugin identifier. + * + * @param dmiPluginIdentifier DMI plugin identifier + * @return map of cmHandle references key:CmHandleId Value:AlternateId + */ + Map<String, String> getCmHandleReferencesByDmiPluginIdentifier(String dmiPluginIdentifier); + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java index 71e7384208..4249b452c3 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java @@ -29,6 +29,7 @@ import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.stream.Collectors; @@ -53,6 +54,7 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { private static final String DESCENDANT_PATH = "//"; private static final String ANCESTOR_CM_HANDLES = "/ancestor::cm-handles"; + private static final String ALTERNATE_ID = "alternate-id"; private final CpsDataService cpsDataService; private final CpsQueryService cpsQueryService; @@ -65,21 +67,23 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { private final CpsValidator cpsValidator; @Override - public Collection<String> queryCmHandleAdditionalProperties(final Map<String, String> privatePropertyQueryPairs) { - return queryCmHandleAnyProperties(privatePropertyQueryPairs, PropertyType.ADDITIONAL); + public Collection<String> queryCmHandleAdditionalProperties(final Map<String, String> privatePropertyQueryPairs, + final Boolean outputAlternateId) { + return queryCmHandleAnyProperties(privatePropertyQueryPairs, PropertyType.ADDITIONAL, outputAlternateId); } @Override - public Collection<String> queryCmHandlePublicProperties(final Map<String, String> publicPropertyQueryPairs) { - return queryCmHandleAnyProperties(publicPropertyQueryPairs, PropertyType.PUBLIC); + public Collection<String> queryCmHandlePublicProperties(final Map<String, String> publicPropertyQueryPairs, + final Boolean outputAlternateId) { + return queryCmHandleAnyProperties(publicPropertyQueryPairs, PropertyType.PUBLIC, outputAlternateId); } @Override - public Collection<String> queryCmHandlesByTrustLevel(final Map<String, String> trustLevelPropertyQueryPairs) { + public Collection<String> queryCmHandlesByTrustLevel(final Map<String, String> trustLevelPropertyQueryPairs, + final Boolean outputAlternateId) { final String trustLevelProperty = trustLevelPropertyQueryPairs.values().iterator().next(); final TrustLevel targetTrustLevel = TrustLevel.valueOf(trustLevelProperty); - - return getCmHandleIdsByTrustLevel(targetTrustLevel); + return getCmHandleReferencesByTrustLevel(targetTrustLevel, outputAlternateId); } @Override @@ -130,37 +134,64 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { return cmHandleIds; } - private Collection<String> getCmHandleIdsByTrustLevel(final TrustLevel targetTrustLevel) { - final Collection<String> selectedCmHandleIds = new HashSet<>(); + @Override + public Map<String, String> getCmHandleReferencesByDmiPluginIdentifier(final String dmiPluginIdentifier) { + final Map<String, String> cmHandleReferencesMap = new HashMap<>(); + for (final ModelledDmiServiceLeaves modelledDmiServiceLeaf : ModelledDmiServiceLeaves.values()) { + for (final DataNode cmHandleAsDataNode: getCmHandlesByDmiPluginIdentifierAndDmiProperty( + dmiPluginIdentifier, + modelledDmiServiceLeaf.getLeafName())) { + cmHandleReferencesMap.put(cmHandleAsDataNode.getLeaves().get("id").toString(), + cmHandleAsDataNode.getLeaves().get(ALTERNATE_ID).toString()); + } + } + return cmHandleReferencesMap; + } + + private Collection<String> getCmHandleReferencesByTrustLevel(final TrustLevel targetTrustLevel, + final Boolean outputAlternateId) { + final Collection<String> selectedCmHandleReferences = new HashSet<>(); for (final Map.Entry<String, TrustLevel> mapEntry : trustLevelPerDmiPlugin.entrySet()) { final String dmiPluginIdentifier = mapEntry.getKey(); final TrustLevel dmiTrustLevel = mapEntry.getValue(); - final Collection<String> candidateCmHandleIds = getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier); - for (final String candidateCmHandleId : candidateCmHandleIds) { - final TrustLevel candidateCmHandleTrustLevel = trustLevelPerCmHandle.get(candidateCmHandleId); + final Map<String, String> candidateCmHandleReferences = + getCmHandleReferencesByDmiPluginIdentifier(dmiPluginIdentifier); + for (final Map.Entry<String, String> candidateCmHandleReference : candidateCmHandleReferences.entrySet()) { + final TrustLevel candidateCmHandleTrustLevel = + trustLevelPerCmHandle.get(candidateCmHandleReference.getKey()); final TrustLevel effectiveTrustlevel = candidateCmHandleTrustLevel.getEffectiveTrustLevel(dmiTrustLevel); if (targetTrustLevel.equals(effectiveTrustlevel)) { - selectedCmHandleIds.add(candidateCmHandleId); + if (Boolean.TRUE.equals(outputAlternateId)) { + selectedCmHandleReferences.add(candidateCmHandleReference.getValue()); + } else { + selectedCmHandleReferences.add(candidateCmHandleReference.getKey()); + } } } } - - return selectedCmHandleIds; + return selectedCmHandleReferences; } - private Collection<String> collectCmHandleIdsFromDataNodes(final Collection<DataNode> dataNodes) { - return dataNodes.stream().map(dataNode -> (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet()); + private Collection<String> collectCmHandleReferencesFromDataNodes(final Collection<DataNode> dataNodes, + final Boolean outputAlternateId) { + if (Boolean.TRUE.equals(outputAlternateId)) { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get(ALTERNATE_ID)).collect(Collectors.toSet()); + } else { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet()); + } } private Collection<String> queryCmHandleAnyProperties( final Map<String, String> propertyQueryPairs, - final PropertyType propertyType) { + final PropertyType propertyType, final Boolean outputAlternateId) { if (propertyQueryPairs.isEmpty()) { return Collections.emptySet(); } - Collection<String> cmHandleIds = null; + Collection<String> cmHandleReferences = null; for (final Map.Entry<String, String> publicPropertyQueryPair : propertyQueryPairs.entrySet()) { final String cpsPath = DESCENDANT_PATH + propertyType.getYangContainerName() + "[@name=\"" + publicPropertyQueryPair.getKey() @@ -168,17 +199,18 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { final Collection<DataNode> dataNodes = queryCmHandleAncestorsByCpsPath(cpsPath, OMIT_DESCENDANTS); - if (cmHandleIds == null) { - cmHandleIds = collectCmHandleIdsFromDataNodes(dataNodes); + if (cmHandleReferences == null) { + cmHandleReferences = collectCmHandleReferencesFromDataNodes(dataNodes, outputAlternateId); } else { - final Collection<String> cmHandleIdsToRetain = collectCmHandleIdsFromDataNodes(dataNodes); - cmHandleIds.retainAll(cmHandleIdsToRetain); + final Collection<String> cmHandleReferencesToRetain; + cmHandleReferencesToRetain = collectCmHandleReferencesFromDataNodes(dataNodes, outputAlternateId); + cmHandleReferences.retainAll(cmHandleReferencesToRetain); } - if (cmHandleIds.isEmpty()) { + if (cmHandleReferences.isEmpty()) { break; } } - return cmHandleIds; + return cmHandleReferences; } private Collection<DataNode> getCmHandlesByDmiPluginIdentifierAndDmiProperty(final String dmiPluginIdentifier, diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java index d9f7e38993..cb55b09d41 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java @@ -87,8 +87,7 @@ public class CmHandleRegistrationService { * @param dmiPluginRegistration Dmi Plugin Registration details * @return dmiPluginRegistrationResponse */ - public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule( - final DmiPluginRegistration dmiPluginRegistration) { + public DmiPluginRegistrationResponse updateDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) { dmiPluginRegistration.validateDmiPluginRegistration(); final DmiPluginRegistrationResponse dmiPluginRegistrationResponse = new DmiPluginRegistrationResponse(); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java index a0d3a3eaee..850edf7d57 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java @@ -149,9 +149,12 @@ public interface InventoryPersistence extends NcmpPersistence { * get CM handles that has given module names. * * @param moduleNamesForQuery module names + * @param outputAlternateIds Boolean for cm handle reference type either + * cm handle id (false or null) or alternate id (true) * @return Collection of CM handle Ids */ - Collection<String> getCmHandleIdsWithGivenModules(Collection<String> moduleNamesForQuery); + Collection<String> getCmHandleReferencesWithGivenModules(Collection<String> moduleNamesForQuery, + Boolean outputAlternateIds); /** * Check database if cm handle id exists if not return false. @@ -159,5 +162,5 @@ public interface InventoryPersistence extends NcmpPersistence { * @param cmHandleId cmHandle Id * @return Boolean */ - boolean isExistingCmHandleId(String cmHandleId); + Boolean isExistingCmHandleId(String cmHandleId); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java index 06c3f8d2f4..655d8437b1 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java @@ -201,12 +201,19 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv } @Override - public Collection<String> getCmHandleIdsWithGivenModules(final Collection<String> moduleNamesForQuery) { - return cpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); + public Collection<String> getCmHandleReferencesWithGivenModules(final Collection<String> moduleNamesForQuery, + final Boolean outputAlternateIds) { + if (Boolean.TRUE.equals(outputAlternateIds)) { + final Collection<String> cmHandleIds = + cpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); + return getAlternateIdsFromDataNodes(getCmHandleDataNodes(cmHandleIds)); + } else { + return cpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); + } } @Override - public boolean isExistingCmHandleId(final String cmHandleId) { + public Boolean isExistingCmHandleId(final String cmHandleId) { try { return !getCmHandleDataNodeByCmHandleId(cmHandleId).isEmpty(); } catch (final DataNodeNotFoundException exception) { @@ -234,4 +241,9 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv private String createCmHandlesJsonData(final List<YangModelCmHandle> yangModelCmHandles) { return "{\"cm-handles\":" + jsonObjectMapper.asJsonString(yangModelCmHandles) + "}"; } + + private Collection<String> getAlternateIdsFromDataNodes(final Collection<DataNode> dataNodes) { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get("alternate-id")).collect(Collectors.toSet()); + } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java index e5848c0dfa..8301579b42 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java @@ -26,19 +26,22 @@ import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; public interface ParameterizedCmHandleQueryService { /** - * Query and return cm handle ids that match the given query parameters. + * Query and return cm handle ids or alternate ids that match the given query parameters. * Supported query types: * public properties * modules * cps-path * * @param cmHandleQueryServiceParameters the cm handle query parameters - * @return collection of cm handle ids + * @param outputAlternateId Boolean for cm handle reference type either + * cm handle id (false or null) or alternate id (true) + * @return collection of cm handle ids or alternate ids */ - Collection<String> queryCmHandleIds(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); + Collection<String> queryCmHandleReferenceIds(CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + Boolean outputAlternateId); /** - * Query and return cm handle ids that match the given query parameters. + * Query and return cm handle ids or alternate ids that match the given query parameters. * Supported query types: * public properties * private (additional) properties @@ -46,9 +49,12 @@ public interface ParameterizedCmHandleQueryService { * The inventory interface also allows conditions on private (additional) properties and dmi names * * @param cmHandleQueryServiceParameters the cm handle query parameters + * @param outputAlternateId Boolean for cm handle reference type either + * cm handle id (false or null) or alternate id (true) * @return collection of cm handle ids */ - Collection<String> queryCmHandleIdsForInventory(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); + Collection<String> queryCmHandleIdsForInventory(CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + Boolean outputAlternateId); /** * Query and return cm handle objects that match the given query parameters. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java index 34eeaccf8f..bacbbe0c95 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java @@ -37,10 +37,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.onap.cps.cpspath.parser.PathParsingException; import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; @@ -54,7 +53,6 @@ import org.onap.cps.spi.model.DataNode; import org.springframework.stereotype.Service; @Service -@Slf4j @RequiredArgsConstructor public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHandleQueryService { @@ -63,19 +61,21 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan private final InventoryPersistence inventoryPersistence; @Override - public Collection<String> queryCmHandleIds( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { - return executeQueries(cmHandleQueryServiceParameters, - this::executeCpsPathQuery, - this::queryCmHandlesByPublicProperties, - this::executeModuleNameQuery, - this::queryCmHandlesByTrustLevel); + public Collection<String> queryCmHandleReferenceIds( + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + final Boolean outputAlternateId) { + return executeQueries(cmHandleQueryServiceParameters, outputAlternateId, + this::executeCpsPathQuery, + this::queryCmHandlesByPublicProperties, + this::executeModuleNameQuery, + this::queryCmHandlesByTrustLevel); } @Override public Collection<String> queryCmHandleIdsForInventory( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { - return executeQueries(cmHandleQueryServiceParameters, + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + final Boolean outputAlternateId) { + return executeQueries(cmHandleQueryServiceParameters, outputAlternateId, this::executeCpsPathQuery, this::queryCmHandlesByPublicProperties, this::queryCmHandlesByPrivateProperties, @@ -90,7 +90,7 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan return getAllCmHandles(); } - final Collection<String> cmHandleIds = queryCmHandleIds(cmHandleQueryServiceParameters); + final Collection<String> cmHandleIds = queryCmHandleReferenceIds(cmHandleQueryServiceParameters, false); return getNcmpServiceCmHandles(cmHandleIds); } @@ -102,7 +102,7 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan } private Collection<String> queryCmHandlesByDmiPlugin( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final Boolean outputAlternateId) { final Map<String, String> dmiPropertyQueryPairs = getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(), InventoryQueryConditions.CM_HANDLE_WITH_DMI_PLUGIN.getName()); @@ -113,11 +113,15 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan final String dmiPluginIdentifierValue = dmiPropertyQueryPairs .get(PropertyType.DMI_PLUGIN.getYangContainerName()); - return cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifierValue); + if (Boolean.TRUE.equals(outputAlternateId)) { + return cmHandleQueryService.getCmHandleReferencesByDmiPluginIdentifier(dmiPluginIdentifierValue).values(); + } else { + return cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifierValue); + } } private Collection<String> queryCmHandlesByPrivateProperties( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final Boolean outputAlternateId) { final Map<String, String> privatePropertyQueryPairs = getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(), @@ -126,11 +130,11 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan if (privatePropertyQueryPairs.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return cmHandleQueryService.queryCmHandleAdditionalProperties(privatePropertyQueryPairs); + return cmHandleQueryService.queryCmHandleAdditionalProperties(privatePropertyQueryPairs, outputAlternateId); } private Collection<String> queryCmHandlesByPublicProperties( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final Boolean outputAlternateId) { final Map<String, String> publicPropertyQueryPairs = getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(), @@ -139,11 +143,12 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan if (publicPropertyQueryPairs.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return cmHandleQueryService.queryCmHandlePublicProperties(publicPropertyQueryPairs); + return cmHandleQueryService.queryCmHandlePublicProperties(publicPropertyQueryPairs, outputAlternateId); } private Collection<String> queryCmHandlesByTrustLevel(final CmHandleQueryServiceParameters - cmHandleQueryServiceParameters) { + cmHandleQueryServiceParameters, + final Boolean outputAlternateId) { final Map<String, String> trustLevelPropertyQueryPairs = getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(), @@ -152,21 +157,21 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan if (trustLevelPropertyQueryPairs.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return cmHandleQueryService.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs); + return cmHandleQueryService.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs, outputAlternateId); } private Collection<String> executeModuleNameQuery( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final Boolean outputAlternateId) { final Collection<String> moduleNamesForQuery = getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); if (moduleNamesForQuery.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return inventoryPersistence.getCmHandleIdsWithGivenModules(moduleNamesForQuery); + return inventoryPersistence.getCmHandleReferencesWithGivenModules(moduleNamesForQuery, outputAlternateId); } private Collection<String> executeCpsPathQuery( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final Boolean outputAlternateId) { final Map<String, String> cpsPathCondition = getCpsPathCondition(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); if (!validateCpsPathConditionProperties(cpsPathCondition)) { @@ -177,9 +182,9 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan return NO_QUERY_TO_EXECUTE; } try { - cpsPathQueryResult = collectCmHandleIdsFromDataNodes( - cmHandleQueryService.queryCmHandleAncestorsByCpsPath( - cpsPathCondition.get("cpsPath"), OMIT_DESCENDANTS)); + cpsPathQueryResult = collectCmHandleReferencesFromDataNodes( + cmHandleQueryService.queryCmHandleAncestorsByCpsPath(cpsPathCondition.get("cpsPath"), OMIT_DESCENDANTS), + outputAlternateId); } catch (final PathParsingException pathParsingException) { throw new DataValidationException(pathParsingException.getMessage(), pathParsingException.getDetails(), pathParsingException); @@ -220,10 +225,10 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan return Collections.emptyList(); } - private Collection<String> getAllCmHandleIds() { + private Collection<String> getAllCmHandleReferences(final Boolean outputAlternateId) { final DataNode dataNode = inventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT, DIRECT_CHILDREN_ONLY) - .iterator().next(); - return collectCmHandleIdsFromDataNodes(dataNode.getChildDataNodes()); + .iterator().next(); + return collectCmHandleReferencesFromDataNodes(dataNode.getChildDataNodes(), outputAlternateId); } private Collection<NcmpServiceCmHandle> getNcmpServiceCmHandles(final Collection<String> cmHandleIds) { @@ -243,14 +248,17 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan } private Collection<String> executeQueries(final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, - final Function<CmHandleQueryServiceParameters, Collection<String>>... - queryFunctions) { + final Boolean outputAlternateId, + final BiFunction<CmHandleQueryServiceParameters, Boolean, + Collection<String>>... queryFunctions) { if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) { - return getAllCmHandleIds(); + return getAllCmHandleReferences(outputAlternateId); } Collection<String> combinedQueryResult = NO_QUERY_TO_EXECUTE; - for (final Function<CmHandleQueryServiceParameters, Collection<String>> queryFunction : queryFunctions) { - final Collection<String> queryResult = queryFunction.apply(cmHandleQueryServiceParameters); + for (final BiFunction<CmHandleQueryServiceParameters, Boolean, + Collection<String>> queryFunction : queryFunctions) { + final Collection<String> queryResult = queryFunction.apply(cmHandleQueryServiceParameters, + outputAlternateId); if (noEntriesFoundCanStopQuerying(queryResult)) { return Collections.emptySet(); } @@ -277,8 +285,14 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan } } - private Collection<String> collectCmHandleIdsFromDataNodes(final Collection<DataNode> dataNodes) { - return dataNodes.stream().map(dataNode -> (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet()); + private Collection<String> collectCmHandleReferencesFromDataNodes(final Collection<DataNode> dataNodes, + final Boolean outputAlternateId) { + if (Boolean.TRUE.equals(outputAlternateId)) { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get("alternate-id")).collect(Collectors.toSet()); + } else { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet()); + } } - } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java index b8bb64f537..80bc4ab69f 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,9 +68,9 @@ public class AsyncTaskExecutor { private void handleTaskCompletion(final Object response, final Throwable throwable) { if (throwable != null) { if (throwable instanceof TimeoutException) { - log.warn("Async task didn't completed within the required time."); + log.error("Async task didn't complete within the required time.", throwable); } else { - log.debug("Watchdog async batch failed. caused by : {}", throwable.getMessage()); + log.error("Watchdog async batch failed.", throwable); } } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java index e627f8f894..31fcbad08b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java @@ -58,37 +58,16 @@ public class ModuleSyncTasks { */ public CompletableFuture<Void> performModuleSync(final Collection<DataNode> cmHandlesAsDataNodes, final AtomicInteger batchCounter) { + final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle = + new HashMap<>(cmHandlesAsDataNodes.size()); try { - final Map<YangModelCmHandle, CmHandleState> cmHandelStatePerCmHandle - = new HashMap<>(cmHandlesAsDataNodes.size()); - for (final DataNode cmHandleAsDataNode : cmHandlesAsDataNodes) { - final String cmHandleId = String.valueOf(cmHandleAsDataNode.getLeaves().get("id")); + cmHandlesAsDataNodes.forEach(cmHandleAsDataNode -> { final YangModelCmHandle yangModelCmHandle = YangDataConverter.toYangModelCmHandle(cmHandleAsDataNode); - final CompositeState compositeState = inventoryPersistence.getCmHandleState(cmHandleId); - final boolean inUpgrade = ModuleOperationsUtils.inUpgradeOrUpgradeFailed(compositeState); - try { - if (inUpgrade) { - moduleSyncService.syncAndUpgradeSchemaSet(yangModelCmHandle); - } else { - moduleSyncService.deleteSchemaSetIfExists(cmHandleId); - moduleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle); - } - yangModelCmHandle.getCompositeState().setLockReason(null); - cmHandelStatePerCmHandle.put(yangModelCmHandle, CmHandleState.READY); - } catch (final Exception e) { - log.warn("Processing of {} module failed due to reason {}.", cmHandleId, e.getMessage()); - final LockReasonCategory lockReasonCategory = inUpgrade ? LockReasonCategory.MODULE_UPGRADE_FAILED - : LockReasonCategory.MODULE_SYNC_FAILED; - moduleOperationsUtils.updateLockReasonWithAttempts(compositeState, - lockReasonCategory, e.getMessage()); - setCmHandleStateLocked(yangModelCmHandle, compositeState.getLockReason()); - cmHandelStatePerCmHandle.put(yangModelCmHandle, CmHandleState.LOCKED); - } - log.info("{} is now in {} state", cmHandleId, cmHandelStatePerCmHandle.get(yangModelCmHandle).name()); - } - lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandelStatePerCmHandle); + cmHandleStatePerCmHandle.put(yangModelCmHandle, processCmHandle(yangModelCmHandle)); + }); } finally { batchCounter.getAndDecrement(); + lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle); log.info("Processing module sync batch finished. {} batch(es) active.", batchCounter.get()); } return CompletableFuture.completedFuture(null); @@ -108,13 +87,36 @@ public class ModuleSyncTasks { final CompositeState compositeState = yangModelCmHandle.getCompositeState(); final String resetCmHandleId = yangModelCmHandle.getId(); log.debug("Resetting CM handle {} state to ADVISED for retry by the module-sync watchdog. Lock reason: {}", - yangModelCmHandle.getId(), compositeState.getLockReason().getLockReasonCategory().name()); + yangModelCmHandle.getId(), compositeState.getLockReason().getLockReasonCategory().name()); cmHandleStatePerCmHandle.put(yangModelCmHandle, CmHandleState.ADVISED); removeResetCmHandleFromModuleSyncMap(resetCmHandleId); } lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle); } + private CmHandleState processCmHandle(final YangModelCmHandle yangModelCmHandle) { + final CompositeState compositeState = inventoryPersistence.getCmHandleState(yangModelCmHandle.getId()); + final boolean inUpgrade = ModuleOperationsUtils.inUpgradeOrUpgradeFailed(compositeState); + try { + if (inUpgrade) { + moduleSyncService.syncAndUpgradeSchemaSet(yangModelCmHandle); + } else { + moduleSyncService.deleteSchemaSetIfExists(yangModelCmHandle.getId()); + moduleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle); + } + yangModelCmHandle.getCompositeState().setLockReason(null); + return CmHandleState.READY; + } catch (final Exception e) { + log.warn("Processing of {} module failed due to reason {}.", yangModelCmHandle.getId(), e.getMessage()); + final LockReasonCategory lockReasonCategory = inUpgrade ? LockReasonCategory.MODULE_UPGRADE_FAILED + : LockReasonCategory.MODULE_SYNC_FAILED; + moduleOperationsUtils.updateLockReasonWithAttempts(compositeState, + lockReasonCategory, e.getMessage()); + setCmHandleStateLocked(yangModelCmHandle, compositeState.getLockReason()); + return CmHandleState.LOCKED; + } + } + private void setCmHandleStateLocked(final YangModelCmHandle advisedCmHandle, final CompositeState.LockReason lockReason) { advisedCmHandle.getCompositeState().setLockReason(lockReason); @@ -125,4 +127,4 @@ public class ModuleSyncTasks { log.info("{} removed from in progress map", resetCmHandleId); } } -} +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java index 4061298cd0..898b8d5bf4 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java @@ -32,6 +32,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.Sleeper; import org.onap.cps.spi.model.DataNode; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -47,6 +48,7 @@ public class ModuleSyncWatchdog { private final ModuleSyncTasks moduleSyncTasks; private final AsyncTaskExecutor asyncTaskExecutor; private final Lock workQueueLock; + private final Sleeper sleeper; private static final int MODULE_SYNC_BATCH_SIZE = 100; private static final long PREVENT_CPU_BURN_WAIT_TIME_MILLIS = 10; @@ -59,9 +61,10 @@ public class ModuleSyncWatchdog { * Check DB for any cm handles in 'ADVISED' state. * Queue and create batches to process them asynchronously. * This method will only finish when there are no more 'ADVISED' cm handles in the DB. - * This method wil be triggered on a configurable interval + * This method is triggered on a configurable interval (ncmp.timers.advised-modules-sync.sleep-time-ms) */ - @Scheduled(fixedDelayString = "${ncmp.timers.advised-modules-sync.sleep-time-ms:5000}") + @Scheduled(initialDelayString = "${test.ncmp.timers.advised-modules-sync.initial-delay-ms:0}", + fixedDelayString = "${ncmp.timers.advised-modules-sync.sleep-time-ms:5000}") public void moduleSyncAdvisedCmHandles() { log.debug("Processing module sync watchdog waking up."); populateWorkQueueIfNeeded(); @@ -82,26 +85,20 @@ public class ModuleSyncWatchdog { } } - private void preventBusyWait() { - try { - log.debug("Busy waiting now"); - TimeUnit.MILLISECONDS.sleep(PREVENT_CPU_BURN_WAIT_TIME_MILLIS); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - private void populateWorkQueueIfNeeded() { - if (moduleSyncWorkQueue.isEmpty()) { - if (workQueueLock.tryLock()) { - try { - populateWorkQueue(); - if (moduleSyncWorkQueue.isEmpty()) { - setPreviouslyLockedCmHandlesToAdvised(); - } - } finally { - workQueueLock.unlock(); + /** + * Populate work queue with advised cm handles from db. + * This method is made public for (integration) testing purposes. + * So it can be tested without the queue being emptied immediately as the main public method does. + */ + public void populateWorkQueueIfNeeded() { + if (moduleSyncWorkQueue.isEmpty() && workQueueLock.tryLock()) { + try { + populateWorkQueue(); + if (moduleSyncWorkQueue.isEmpty()) { + setPreviouslyLockedCmHandlesToAdvised(); } + } finally { + workQueueLock.unlock(); } } } @@ -156,4 +153,13 @@ public class ModuleSyncWatchdog { log.info("nextBatch size : {}", nextBatch.size()); return nextBatch; } + + private void preventBusyWait() { + try { + log.debug("Busy waiting now"); + sleeper.haveALittleRest(PREVENT_CPU_BURN_WAIT_TIME_MILLIS); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandler.java index 6cce153269..de3df6b9da 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandler.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandler.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,14 +32,6 @@ import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; public interface LcmEventsCmHandleStateHandler { /** - * Updates the composite state of cmHandle based on cmHandleState. - * - * @param yangModelCmHandle cm handle represented as yang model - * @param targetCmHandleState target cm handle state - */ - void updateCmHandleState(final YangModelCmHandle yangModelCmHandle, final CmHandleState targetCmHandleState); - - /** * Updates the composite state of cmHandle based on cmHandleState in batch. * * @param cmHandleStatePerCmHandle Map of Yang Model Cm Handle and corresponding cm handle state. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java index cf7921c350..a53c902683 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.events.lcm.v1.LcmEvent; import org.onap.cps.ncmp.events.lcm.v1.LcmEventHeader; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandlerImpl.CmHandleTransitionPair; import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -38,25 +39,12 @@ public class LcmEventsCmHandleStateHandlerAsyncHelper { private final LcmEventsService lcmEventsService; /** - * Publish LCM Event in asynchronous manner. - * - * @param targetNcmpServiceCmHandle target NcmpServiceCmHandle - * @param currentNcmpServiceCmHandle current NcmpServiceCmHandle - */ - @Async("notificationExecutor") - public void publishLcmEventAsynchronously(final NcmpServiceCmHandle targetNcmpServiceCmHandle, - final NcmpServiceCmHandle currentNcmpServiceCmHandle) { - publishLcmEvent(targetNcmpServiceCmHandle, currentNcmpServiceCmHandle); - } - - /** * Publish LcmEvent in batches and in asynchronous manner. * * @param cmHandleTransitionPairs Pair of existing and modified cm handle represented as YangModelCmHandle */ @Async("notificationExecutor") - public void publishLcmEventBatchAsynchronously( - final Collection<LcmEventsCmHandleStateHandlerImpl.CmHandleTransitionPair> cmHandleTransitionPairs) { + public void publishLcmEventBatchAsynchronously(final Collection<CmHandleTransitionPair> cmHandleTransitionPairs) { cmHandleTransitionPairs.forEach(cmHandleTransitionPair -> publishLcmEvent( toNcmpServiceCmHandle(cmHandleTransitionPair.getTargetYangModelCmHandle()), toNcmpServiceCmHandle(cmHandleTransitionPair.getCurrentYangModelCmHandle()))); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java index b1b7b955f7..e9bd37219a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java @@ -38,12 +38,10 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.inventory.models.CompositeState; -import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.impl.inventory.CompositeStateUtils; import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; -import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.springframework.stereotype.Service; @Slf4j @@ -55,25 +53,6 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState private final LcmEventsCmHandleStateHandlerAsyncHelper lcmEventsCmHandleStateHandlerAsyncHelper; @Override - public void updateCmHandleState(final YangModelCmHandle updatedYangModelCmHandle, - final CmHandleState targetCmHandleState) { - - final CompositeState compositeState = updatedYangModelCmHandle.getCompositeState(); - - if (isCompositeStateSame(compositeState, targetCmHandleState)) { - log.debug("CmHandle with id : {} already in state : {}", updatedYangModelCmHandle.getId(), - targetCmHandleState); - } else { - final YangModelCmHandle currentYangModelCmHandle = YangModelCmHandle.deepCopyOf(updatedYangModelCmHandle); - updateToSpecifiedCmHandleState(updatedYangModelCmHandle, targetCmHandleState); - persistCmHandle(updatedYangModelCmHandle, currentYangModelCmHandle); - lcmEventsCmHandleStateHandlerAsyncHelper.publishLcmEventAsynchronously( - toNcmpServiceCmHandle(updatedYangModelCmHandle), - toNcmpServiceCmHandle(currentYangModelCmHandle)); - } - } - - @Override @Timed(value = "cps.ncmp.cmhandle.state.update.batch", description = "Time taken to update a batch of cm handle states") public void updateCmHandleStateBatch(final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle) { @@ -113,28 +92,13 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState return cmHandleTransitionPairs; } - - private void persistCmHandle(final YangModelCmHandle targetYangModelCmHandle, - final YangModelCmHandle currentYangModelCmHandle) { - if (isNew(currentYangModelCmHandle.getCompositeState())) { - log.debug("Registering a new cm handle {}", targetYangModelCmHandle.getId()); - inventoryPersistence.saveCmHandle(targetYangModelCmHandle); - } else if (isDeleted(targetYangModelCmHandle.getCompositeState())) { - log.info("CmHandle with Id : {} is DELETED", targetYangModelCmHandle.getId()); - } else { - inventoryPersistence.saveCmHandleState(targetYangModelCmHandle.getId(), - targetYangModelCmHandle.getCompositeState()); - } - } - private void persistCmHandleBatch(final Collection<CmHandleTransitionPair> cmHandleTransitionPairs) { final List<YangModelCmHandle> newCmHandles = new ArrayList<>(); final Map<String, CompositeState> compositeStatePerCmHandleId = new LinkedHashMap<>(); cmHandleTransitionPairs.forEach(cmHandleTransitionPair -> { - if (isNew(cmHandleTransitionPair.getCurrentYangModelCmHandle().getCompositeState() - )) { + if (isNew(cmHandleTransitionPair.getCurrentYangModelCmHandle().getCompositeState())) { newCmHandles.add(cmHandleTransitionPair.getTargetYangModelCmHandle()); } else if (!isDeleted(cmHandleTransitionPair.getTargetYangModelCmHandle().getCompositeState())) { compositeStatePerCmHandleId.put(cmHandleTransitionPair.getTargetYangModelCmHandle().getId(), @@ -145,10 +109,11 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState inventoryPersistence.saveCmHandleBatch(newCmHandles); inventoryPersistence.saveCmHandleStateBatch(compositeStatePerCmHandleId); + logCmHandleStateChanges(cmHandleTransitionPairs); } private void updateToSpecifiedCmHandleState(final YangModelCmHandle yangModelCmHandle, - final CmHandleState targetCmHandleState) { + final CmHandleState targetCmHandleState) { if (READY == targetCmHandleState) { setInitialStates(yangModelCmHandle); @@ -193,8 +158,11 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState return (compositeState != null && compositeState.getCmHandleState() == targetCmHandleState); } - private NcmpServiceCmHandle toNcmpServiceCmHandle(final YangModelCmHandle yangModelCmHandle) { - return YangDataConverter.toNcmpServiceCmHandle(yangModelCmHandle); + private static void logCmHandleStateChanges(final Collection<CmHandleTransitionPair> cmHandleTransitionPairs) { + cmHandleTransitionPairs.stream() + .map(CmHandleTransitionPair::getTargetYangModelCmHandle) + .forEach(yangModelCmHandle -> log.info("{} is now in {} state", yangModelCmHandle.getId(), + yangModelCmHandle.getCompositeState().getCmHandleState().name())); } @Getter diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/Sleeper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/Sleeper.java new file mode 100644 index 0000000000..7a02fa06e0 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/Sleeper.java @@ -0,0 +1,35 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.utils; + +import java.util.concurrent.TimeUnit; +import org.springframework.stereotype.Service; + +/** + * This class is to extract out sleep functionality so the interrupted exception handling can + * be covered with a test (e.g. using spy on Sleeper) and help to get to 100% code coverage. + */ +@Service +public class Sleeper { + public void haveALittleRest(final long timeInMillis) throws InterruptedException { + TimeUnit.MILLISECONDS.sleep(timeInMillis); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java index d8e8350345..8ae942eb7b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java @@ -20,7 +20,6 @@ package org.onap.cps.ncmp.impl.utils.http; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.netty.channel.ChannelOption; import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.WriteTimeoutHandler; @@ -63,7 +62,6 @@ public class WebClientConfiguration { .compress(true); } - @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE") private static ConnectionProvider getConnectionProvider(final ServiceConfig serviceConfig) { return ConnectionProvider.builder(serviceConfig.getConnectionProviderName()) .maxConnections(serviceConfig.getMaximumConnectionsTotal()) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy index 5f83ad5f83..c62e93c214 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy @@ -42,9 +42,8 @@ class NetworkCmProxyFacadeSpec extends Specification { def mockDmiDataOperations = Mock(DmiDataOperations) def mockNcmpCachedResourceRequestHandler = Mock(NcmpCachedResourceRequestHandler) def mockNcmpPassthroughResourceRequestHandler = Mock(NcmpPassthroughResourceRequestHandler) - def mockAlternateIdMatcher = Mock(AlternateIdMatcher) - def objectUnderTest = new NetworkCmProxyFacade(mockNcmpCachedResourceRequestHandler, mockNcmpPassthroughResourceRequestHandler, mockDmiDataOperations, mockAlternateIdMatcher) + def objectUnderTest = new NetworkCmProxyFacade(mockNcmpCachedResourceRequestHandler, mockNcmpPassthroughResourceRequestHandler, mockDmiDataOperations) def NO_TOPIC = null @@ -89,7 +88,6 @@ class NetworkCmProxyFacadeSpec extends Specification { given: 'a cm resource address for datastore operational' def cmResourceAddress = new CmResourceAddress('ncmp-datastore:operational', 'some CM Handle', 'some resource Id') and: 'get resource data from DMI is called' - mockAlternateIdMatcher.getCmHandleId('some CM Handle') >> 'some CM Handle' mockNcmpCachedResourceRequestHandler.executeRequest(cmResourceAddress, 'options', NO_TOPIC, false, 'authorization') >> Mono.just('dmi response') when: 'get resource data operational for the given cm resource address is called' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy index 7e34fe2822..0c50e3d4ee 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy @@ -64,16 +64,16 @@ class CmHandleQueryServiceImplSpec extends Specification { given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' mockResponses() when: 'a query on cmhandle public properties is performed with a public property pair' - def result = objectUnderTest.queryCmHandlePublicProperties(publicPropertyPairs) + def result = objectUnderTest.queryCmHandlePublicProperties(publicPropertyPairs, outputAlternateId) then: 'the correct cm handle data objects are returned' - result.containsAll(expectedCmHandleIds) - result.size() == expectedCmHandleIds.size() + result.containsAll(expectedCmHandleReferences) + result.size() == expectedCmHandleReferences.size() where: 'the following data is used' - scenario | publicPropertyPairs || expectedCmHandleIds - 'single property matches' | [Contact: 'newemailforstore@bookstore.com'] || ['PNFDemo', 'PNFDemo2', 'PNFDemo4'] - 'public property does not match' | [wont_match: 'wont_match'] || [] - '2 properties, only one match' | [Contact: 'newemailforstore@bookstore.com', Contact2: 'newemailforstore2@bookstore.com'] || ['PNFDemo4'] - '2 properties, no matches' | [Contact: 'newemailforstore@bookstore.com', Contact2: ''] || [] + scenario | publicPropertyPairs | outputAlternateId || expectedCmHandleReferences + 'single property matches' | [Contact: 'newemailforstore@bookstore.com'] | false || ['PNFDemo', 'PNFDemo2', 'PNFDemo4'] + 'public property does not match' | [wont_match: 'wont_match'] | false || [] + '2 properties, only one match' | [Contact: 'newemailforstore@bookstore.com', Contact2: 'newemailforstore2@bookstore.com'] | true || ['alt-PNFDemo4'] + '2 properties, no matches' | [Contact: 'newemailforstore@bookstore.com', Contact2: ''] | false || [] } def 'Query cm handles on trust level'() { @@ -84,22 +84,26 @@ class CmHandleQueryServiceImplSpec extends Specification { and: 'the DataNodes queried for a given cpsPath are returned from the persistence service' mockResponses() when: 'the query is run' - def result = objectUnderTest.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs) - then: 'the result contain trusted PNFDemo' + def result = objectUnderTest.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs, outputAlternateId) + then: 'the result contain trusted cmHandle reference' assert result.size() == 1 - assert result[0] == 'PNFDemo' + assert result[0] == expectedCmHandleReference + where: 'the following data is used' + senario | outputAlternateId | expectedCmHandleReference + 'output cmHandleId' | false | 'PNFDemo' + 'output AlternateId' | true | 'alt-PNFDemo' } def 'Query CmHandles using empty public properties query pair.'() { when: 'a query on CmHandle public properties is executed using an empty map' - def result = objectUnderTest.queryCmHandlePublicProperties([:]) + def result = objectUnderTest.queryCmHandlePublicProperties([:], false) then: 'no cm handles are returned' result.size() == 0 } def 'Query CmHandles using empty private properties query pair.'() { when: 'a query on CmHandle private properties is executed using an empty map' - def result = objectUnderTest.queryCmHandleAdditionalProperties([:]) + def result = objectUnderTest.queryCmHandleAdditionalProperties([:], false) then: 'no cm handles are returned' result.size() == 0 } @@ -108,7 +112,7 @@ class CmHandleQueryServiceImplSpec extends Specification { given: 'a data node exists with a certain additional-property' mockCpsQueryService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5] when: 'a query on CmHandle private properties is executed using a map' - def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com']) + def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com'], false) then: 'one cm handle is returned' result.size() == 1 } @@ -206,6 +210,17 @@ class CmHandleQueryServiceImplSpec extends Specification { assert result.containsAll('PNFDemo', 'PNFDemo2', 'PNFDemo4') } + def 'Get all alternateIds by dmi plugin identifier'() { + given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' + mockResponses() + when: 'cm Handles are fetched for a given dmi plugin identifier' + def result = objectUnderTest.getCmHandleReferencesByDmiPluginIdentifier('my-dmi-plugin-identifier').values() + then: 'result is the correct size' + assert result.size() == 3 + and: 'result contains the correct alternate Ids' + assert result.containsAll('alt-PNFDemo', 'alt-PNFDemo2', 'alt-PNFDemo4') + } + void mockResponses() { mockCpsQueryService.queryDataNodes(_, _, '//public-properties[@name=\"Contact\" and @value=\"newemailforstore@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo2, pnfDemo4] mockCpsQueryService.queryDataNodes(_, _, '//public-properties[@name=\"wont_match\" and @value=\"wont_match\"]/ancestor::cm-handles', _) >> [] @@ -219,6 +234,6 @@ class CmHandleQueryServiceImplSpec extends Specification { } def static createDataNode(dataNodeId) { - return new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'' + dataNodeId + '\']', leaves: ['id':dataNodeId]) + return new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'' + dataNodeId + '\']', leaves: ['id':dataNodeId, 'alternate-id':'alt-' + dataNodeId]) } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy index dcff2e9b89..70e26d993c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy @@ -87,7 +87,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'cm handle is in READY state' mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> true when: 'registration is processed' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + objectUnderTest.updateDmiRegistration(dmiRegistration) then: 'cm-handles are removed first' 1 * objectUnderTest.processRemovedCmHandles(*_) and: 'de-registered cm handle entry is removed from in progress map' @@ -108,7 +108,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'exception while checking cm handle state' mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: cmHandleState)) when: 'registration is processed' - def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + def result = objectUnderTest.updateDmiRegistration(dmiRegistration) then: 'upgrade operation contains expected error code' assert result.upgradedCmHandles[0].status == expectedResponseStatus where: 'the following parameters are used' @@ -124,7 +124,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'exception while checking cm handle state' mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> { throw exception } when: 'registration is processed' - def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + def result = objectUnderTest.updateDmiRegistration(dmiRegistration) then: 'upgrade operation contains expected error code' assert result.upgradedCmHandles.ncmpResponseStatus.code[0] == expectedErrorCode where: 'the following parameters are used' @@ -139,7 +139,7 @@ class CmHandleRegistrationServiceSpec extends Specification { dmiDataPlugin: dmiDataPlugin) dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] when: 'update registration and sync module is called with correct DMI plugin information' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'create cm handles registration and sync modules is called with the correct plugin information' 1 * objectUnderTest.processCreatedCmHandles(dmiPluginRegistration, _) where: @@ -155,7 +155,7 @@ class CmHandleRegistrationServiceSpec extends Specification { dmiDataPlugin: dmiDataPlugin) dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] when: 'registration is called with incorrect DMI plugin information' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a DMI Request Exception is thrown with correct message details' def exceptionThrown = thrown(DmiRequestException.class) assert exceptionThrown.getMessage().contains(expectedMessageDetails) @@ -178,7 +178,7 @@ class CmHandleRegistrationServiceSpec extends Specification { def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)] when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a successful response is received' response.createdCmHandles.size() == 1 with(response.createdCmHandles[0]) { @@ -206,7 +206,7 @@ class CmHandleRegistrationServiceSpec extends Specification { def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', createdCmHandles:[new NcmpServiceCmHandle(cmHandleId: 'ch-1', registrationTrustLevel: registrationTrustLevel)]) when: 'registration is updated' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'trustLevel is set for the created cm-handle' 1 * mockTrustLevelManager.registerCmHandles(expectedMapping) where: @@ -225,7 +225,7 @@ class CmHandleRegistrationServiceSpec extends Specification { def xpath = "somePathWithId[@id='cmhandle2']" mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw AlreadyDefinedException.forDataNodes([xpath], 'some-context') } when: 'registration is updated to create cm-handles' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a response is received for all cm-handles' response.createdCmHandles.size() == 1 and: 'all cm-handles creation fails' @@ -244,7 +244,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'cm-handler registration fails: #scenario' mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw exception } when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a failure response is received' response.createdCmHandles.size() == 1 with(response.createdCmHandles[0]) { @@ -269,7 +269,7 @@ class CmHandleRegistrationServiceSpec extends Specification { CmHandleRegistrationResponse.createFailureResponse('cm handle 4', CM_HANDLE_INVALID_ID)] mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(_) >> updateOperationResponse when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'the response contains updateOperationResponse' assert response.updatedCmHandles.size() == 4 assert response.updatedCmHandles.containsAll(updateOperationResponse) @@ -281,7 +281,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: '#scenario' mockCpsModuleService.deleteSchemaSetsWithCascade(_, ['cmhandle']) >> { if (!schemaSetExist) { throw new SchemaSetNotFoundException('', '') } } when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'the cmHandle state is updated to "DELETING"' 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args -> args[0].values()[0] == CmHandleState.DELETING } @@ -315,7 +315,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'cm-handle deletion is successful for 1st and 3rd; failed for 2nd' mockInventoryPersistence.deleteDataNode("/dmi-registry/cm-handles[@id='cmhandle2']") >> { throw new RuntimeException("Failed") } when: 'registration is updated to delete cmhandles' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'the cmHandle states are all updated to "DELETING"' 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch({ assert it.every { entry -> entry.value == CmHandleState.DELETING } }) and: 'a response is received for all cm-handles' @@ -361,7 +361,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'schema set single deletion failed with unknown error' mockInventoryPersistence.deleteSchemaSetWithCascade(_) >> { throw new RuntimeException('Failed') } when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'no exception is thrown' noExceptionThrown() and: 'cm-handle is not deleted' @@ -387,7 +387,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'cm-handle deletion fails on individual delete' mockInventoryPersistence.deleteDataNode(_) >> { throw deleteListElementException } when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a failure response is received' assert response.removedCmHandles.size() == 1 with(response.removedCmHandles[0]) { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy index 1830f1331d..e2261f4b7c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -38,6 +38,7 @@ import org.onap.cps.spi.model.ModuleDefinition import org.onap.cps.spi.model.ModuleReference import org.onap.cps.utils.ContentType import org.onap.cps.utils.JsonObjectMapper +import org.testcontainers.shaded.com.fasterxml.jackson.databind.introspect.BasicClassIntrospector import spock.lang.Shared import spock.lang.Specification @@ -332,13 +333,25 @@ class InventoryPersistenceImplSpec extends Specification { 0 * mockCmHandleQueries.queryNcmpRegistryByCpsPath(_, _) } - def 'Get CM handles that has given module names'() { + def 'Get CM handle ids for CM Handles that has given module names'() { when: 'the method to get cm handles is called' - objectUnderTest.getCmHandleIdsWithGivenModules(['sample-module-name']) + objectUnderTest.getCmHandleReferencesWithGivenModules(['sample-module-name'], false) then: 'the admin persistence service method to query anchors is invoked once with the same parameter' 1 * mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name']) } + def 'Get Alternate Ids for CM Handles that has given module names'() { + given: 'A Collection of data nodes' + def dataNodes = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']", leaves: ['id': 'ch-1', 'alternate-id': 'alt-1'])] + when: 'the methods to get dataNodes is called and returns correct values' + mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name']) >> ['ch-1'] + mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ["/dmi-registry/cm-handles[@id='ch-1']"], INCLUDE_ALL_DESCENDANTS) >> dataNodes + and: 'the method returns a result' + def result = objectUnderTest.getCmHandleReferencesWithGivenModules(['sample-module-name'], true) + then: 'the result contains the correct alternate Id' + assert result == ['alt-1'] as HashSet + } + def 'Replace list content'() { when: 'replace list content method is called with xpath and data nodes collection' objectUnderTest.replaceListContent('sample xpath', [new DataNode()]) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy index fec07556eb..4c554c6af5 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy @@ -56,12 +56,12 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { given: 'an (updated) dmi plugin registration' def dmiPluginRegistration = Mock(DmiPluginRegistration) when: 'the registration is submitted ' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'the call is delegated to the cm handle registration service' - 1 * mockCmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + 1 * mockCmHandleRegistrationService.updateDmiRegistration(dmiPluginRegistration) } - def 'Execute cm handle id search for inventory'() { + def 'Execute cm handle reference search for inventory'() { given: 'a ConditionApiProperties object' def conditionProperties = new ConditionProperties() conditionProperties.conditionName = 'hasAllProperties' @@ -71,7 +71,7 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { and: 'the system returns an set of cmHandle ids' mockParameterizedCmHandleQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ] when: 'executing the search' - def result = objectUnderTest.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) + def result = objectUnderTest.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters, false) then: 'the result returns the correct 2 elements' assert result.size() == 2 assert result.contains('cmHandle1') @@ -191,7 +191,7 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { 'Cm Handle Reference as alternate-id' | 'some-alternate-id' } - def 'Execute cm handle id search'() { + def 'Execute cm handle reference search'() { given: 'valid CmHandleQueryApiParameters input' def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() def conditionApiProperties = new ConditionApiProperties() @@ -199,11 +199,11 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] and: 'query cm handle method return with a data node list' - mockParameterizedCmHandleQueryService.queryCmHandleIds( - spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) + mockParameterizedCmHandleQueryService.queryCmHandleReferenceIds( + spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class), false) >> ['cm-handle-id-1'] when: 'execute cm handle search is called' - def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters) + def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters, false) then: 'result is the same collection as returned by the CPS Data Service' assert result == ['cm-handle-id-1'] } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy index 013bace04d..9a81807ef9 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,11 +50,15 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) and: 'the query get the cm handle datanodes excluding all descendants returns a datanode' - cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])] + cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id', 'alternate-id':'some-alternate-id'])] when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, outputAlternateId) then: 'the correct expected cm handles ids are returned' - assert result == ['some-cmhandle-id'] as Set + assert result == expectedCmhandleReference + where: 'the following data is used' + senario | outputAlternateId || expectedCmhandleReference + 'output CmHandle Ids' | false || ['some-cmhandle-id'] as Set + 'output Alternate Ids' | true || ['some-alternate-id'] as Set } def 'Query cm handle where cps path itself is ancestor axis.'() { @@ -63,11 +67,15 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) and: 'the query get the cm handle data nodes excluding all descendants returns a datanode' - cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])] + cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id', 'alternate-id':'some-alternate-id'])] when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIdsForInventory(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleIdsForInventory(cmHandleQueryParameters, outputAlternateId) then: 'the correct expected cm handles ids are returned' - assert result == ['some-cmhandle-id'] as Set + assert result == expectedCmhandleReference + where: 'the following data is used' + senario | outputAlternateId || expectedCmhandleReference + 'outputAlternate is false' | false || ['some-cmhandle-id'] as Set + 'outputAlternate is true' | true || ['some-alternate-id'] as Set } def 'Cm handle ids query with error: #scenario.'() { @@ -78,7 +86,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { and: 'cmHandleQueries throws a path parsing exception' cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> { throw thrownException } when: 'the query is executed for cm handle ids' - objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, false) then: 'a data validation exception is thrown' thrown(expectedException) where: 'the following data is used' @@ -93,7 +101,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/additional-properties']]) cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, false) then: 'empty result is returned' assert result.isEmpty() } @@ -104,9 +112,9 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']]) cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, false) then: 'the inventory service is called with the correct module names' - 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> cmHandleIdsFromService + 1 * mockInventoryPersistence.getCmHandleReferencesWithGivenModules(['some-module-name'], false) >> cmHandleIdsFromService and: 'the correct expected cm handles ids are returned' assert result.size() == cmHandleIdsFromService.size() assert result.containsAll(cmHandleIdsFromService) @@ -122,9 +130,9 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def trustLevelConditionProperties = createConditionProperties('cmHandleWithTrustLevel', [['trustLevel': 'COMPLETE'] as Map]) trustLevelQueryParameters.setCmHandleQueryParameters([trustLevelConditionProperties]) when: 'the query is being executed' - objectUnderTest.queryCmHandleIds(trustLevelQueryParameters) + objectUnderTest.queryCmHandleReferenceIds(trustLevelQueryParameters, false) then: 'the query is being delegated to the cm handle query service with correct parameter' - 1 * cmHandleQueries.queryCmHandlesByTrustLevel(['trustLevel': 'COMPLETE'] as Map) + 1 * cmHandleQueries.queryCmHandlesByTrustLevel(['trustLevel': 'COMPLETE'] as Map, false) } def 'Query cm handle details with module names when #scenario from query.'() { @@ -135,7 +143,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { when: 'the query is executed for cm handle ids' def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters) then: 'the inventory service is called with the correct module names' - 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> ['ch1'] + 1 * mockInventoryPersistence.getCmHandleReferencesWithGivenModules(['some-module-name'], false) >> ['ch1'] and: 'the inventory service is called with teh correct if and returns a yang model cm handle' 1 * mockInventoryPersistence.getYangModelCmHandles(['ch1']) >> [new YangModelCmHandle(id: 'abc', dmiProperties: [new YangModelCmHandle.Property('name','value')], publicProperties: [])] @@ -145,15 +153,19 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { assert result[0].dmiProperties == [name:'value'] } - def 'Query cm handle ids when the query is empty.'() { + def 'Query cm handle references when the query is empty.'() { given: 'We use an empty query' def cmHandleQueryParameters = new CmHandleQueryServiceParameters() and: 'the inventory persistence returns the dmi registry datanode with just ids' mockInventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT, FetchDescendantsOption.DIRECT_CHILDREN_ONLY) >> [dmiRegistry] when: 'the query is executed for both cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, outputAlternateId) then: 'the correct expected cm handles are returned' - assert result.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') + assert result.containsAll(expectedCmhandleReferences) + where: 'the following data is used' + senario | outputAlternateId || expectedCmhandleReferences + 'outputAlternate is false' | false || ['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'] + 'outputAlternate is true' | true || ['alt-PNFDemo1', 'alt-PNFDemo2', 'alt-PNFDemo3', 'alt-PNFDemo4'] } def 'Query cm handle details when the query is empty.'() { @@ -177,7 +189,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { partiallyMockedCmHandleQueries.queryCmHandlePublicProperties(*_) >> cmHandlesWithMatchingPublicProperties partiallyMockedCmHandleQueries.queryCmHandleAdditionalProperties(*_) >> cmHandlesWithMatchingPrivateProperties when: 'the query executed' - def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) + def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters, false) then: 'the expected number of results are returned.' assert result.size() == expectedCmHandleIdsSize where: 'the following data is used' @@ -195,14 +207,15 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) and: 'the inventoryPersistence returns different CmHandleIds' partiallyMockedCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(*_) >> cmHandleQueryResult + partiallyMockedCmHandleQueries.getCmHandleReferencesByDmiPluginIdentifier(*_) >> cmHandleQueryResult when: 'the query executed' - def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) + def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters, outputAlternateId) then: 'the expected number of results are returned.' assert result.size() == expectedCmHandleIdsSize where: 'the following data is used' - scenario | cmHandleQueryResult || expectedCmHandleIdsSize - 'some matches' | ['h1','h2'] || 2 - 'no matches' | [] || 0 + scenario | cmHandleQueryResult | outputAlternateId || expectedCmHandleIdsSize + 'some matches' | ['h1','h2'] | false || 2 + 'no matches' | [:] | true || 0 } def 'Combine two query results where #scenario.'() { @@ -227,7 +240,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def static createDataNodeList(dataNodeIds) { def dataNodes =[] - dataNodeIds.each{ dataNodes << new DataNode(xpath: "/dmi-registry/cm-handles[@id='${it}']", leaves: ['id':it]) } + dataNodeIds.each{ dataNodes << new DataNode(xpath: "/dmi-registry/cm-handles[@id='${it}']", leaves: ['id':it, 'alternate-id':'alt-' + it]) } return dataNodes } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy index 4d715d28c9..8ce1e934f2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy @@ -34,8 +34,10 @@ import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler +import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.model.DataNode import org.slf4j.LoggerFactory +import spock.lang.Ignore import spock.lang.Specification import java.util.concurrent.atomic.AtomicInteger @@ -121,6 +123,26 @@ class ModuleSyncTasksSpec extends Specification { 'module upgrade' | MODULE_UPGRADE | 'Upgrade in progress' || MODULE_UPGRADE_FAILED } + @Ignore // TODO Enable this test once the bug CPS-2474 is fixed + def 'Module sync succeeds even if a handle gets deleted during module sync.'() { + given: 'cm handles in an ADVISED state' + def cmHandle1 = cmHandleAsDataNodeByIdAndState('cm-handle-1', CmHandleState.ADVISED) + def cmHandle2 = cmHandleAsDataNodeByIdAndState('cm-handle-2', CmHandleState.ADVISED) + and: 'inventory persistence cannot find the first handle' + mockInventoryPersistence.getCmHandleState('cm-handle-1') >> { throw new DataNodeNotFoundException('dataspace', 'anchor', 'xpath') } + and: 'inventory persistence returns the second handle with ADVISED state' + mockInventoryPersistence.getCmHandleState('cm-handle-2') >> new CompositeState(cmHandleState: CmHandleState.ADVISED) + when: 'module sync poll is executed' + objectUnderTest.performModuleSync([cmHandle1, cmHandle2], batchCount) + then: 'no exception is thrown' + noExceptionThrown() + and: 'the deleted cm-handle did not sync' + 0 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-1' } + and: 'the existing cm-handle synced' + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-2' } + and: 'the state handler called' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) + } def 'Reset failed CM Handles #scenario.'() { given: 'cm handles in an locked state' @@ -153,7 +175,7 @@ class ModuleSyncTasksSpec extends Specification { when: 'module sync poll is executed' objectUnderTest.performModuleSync([cmHandle1], batchCount) then: 'module sync service is invoked for cm handle' - 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-1') } + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) and: 'the entry for other cm handle is still in the progress map' assert moduleSyncStartedOnCmHandles.get('other-cm-handle') != null } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy index 3064a78ff9..f2c88a511e 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy @@ -23,6 +23,7 @@ package org.onap.cps.ncmp.impl.inventory.sync import com.hazelcast.map.IMap import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.ncmp.impl.utils.Sleeper import org.onap.cps.spi.model.DataNode import spock.lang.Specification @@ -31,7 +32,7 @@ import java.util.concurrent.locks.Lock class ModuleSyncWatchdogSpec extends Specification { - def mockSyncUtils = Mock(ModuleOperationsUtils) + def mockModuleOperationsUtils = Mock(ModuleOperationsUtils) def static testQueueCapacity = 50 + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE @@ -45,16 +46,21 @@ class ModuleSyncWatchdogSpec extends Specification { def mockWorkQueueLock = Mock(Lock) - def objectUnderTest = new ModuleSyncWatchdog(mockSyncUtils, moduleSyncWorkQueue , mockModuleSyncStartedOnCmHandles, mockModuleSyncTasks, spiedAsyncTaskExecutor, mockWorkQueueLock) + def spiedSleeper = Spy(Sleeper) + + def objectUnderTest = new ModuleSyncWatchdog(mockModuleOperationsUtils, moduleSyncWorkQueue , mockModuleSyncStartedOnCmHandles, mockModuleSyncTasks, spiedAsyncTaskExecutor, mockWorkQueueLock, spiedSleeper) void setup() { spiedAsyncTaskExecutor.setupThreadPool() - mockWorkQueueLock.tryLock() >> true } def 'Module sync advised cm handles with #scenario.'() { - given: 'sync utilities returns #numberOfAdvisedCmHandles advised cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(numberOfAdvisedCmHandles) + given: 'module sync utilities returns #numberOfAdvisedCmHandles advised cm handles' + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(numberOfAdvisedCmHandles) + and: 'module sync utilities returns no failed (locked) cm handles' + mockModuleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> [] + and: 'the work queue is not locked' + mockWorkQueueLock.tryLock() >> true and: 'the executor has enough available threads' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 3 when: ' module sync is started' @@ -63,6 +69,7 @@ class ModuleSyncWatchdogSpec extends Specification { expectedNumberOfTaskExecutions * spiedAsyncTaskExecutor.executeTask(*_) where: 'the following parameter are used' scenario | numberOfAdvisedCmHandles || expectedNumberOfTaskExecutions + 'none at all' | 0 || 0 'less then 1 batch' | 1 || 1 'exactly 1 batch' | ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 1 '2 batches' | 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 2 @@ -70,9 +77,11 @@ class ModuleSyncWatchdogSpec extends Specification { 'over queue capacity' | testQueueCapacity + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 3 } - def 'Module sync advised cm handles starts with no available threads.'() { - given: 'sync utilities returns a advise cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(1) + def 'Module sync cm handles starts with no available threads.'() { + given: 'module sync utilities returns a advise cm handles' + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(1) + and: 'the work queue is not locked' + mockWorkQueueLock.tryLock() >> true and: 'the executor first has no threads but has one thread on the second attempt' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >>> [ 0, 1 ] when: ' module sync is started' @@ -81,9 +90,11 @@ class ModuleSyncWatchdogSpec extends Specification { 1 * spiedAsyncTaskExecutor.executeTask(*_) } - def 'Module sync advised cm handles already handled.'() { - given: 'sync utilities returns a advise cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(1) + def 'Module sync advised cm handle already handled by other thread.'() { + given: 'module sync utilities returns an advised cm handle' + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(1) + and: 'the work queue is not locked' + mockWorkQueueLock.tryLock() >> true and: 'the executor has a thread available' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 1 and: 'the semaphore cache indicates the cm handle is already being processed' @@ -98,7 +109,7 @@ class ModuleSyncWatchdogSpec extends Specification { given: 'there is still a cm handle in the queue' moduleSyncWorkQueue.offer(new DataNode()) and: 'sync utilities returns many advise cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(500) + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(500) and: 'the executor has plenty threads available' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 10 when: ' module sync is started' @@ -108,18 +119,42 @@ class ModuleSyncWatchdogSpec extends Specification { } def 'Reset failed cm handles.'() { - given: 'sync utilities returns failed cm handles' + given: 'module sync utilities returns failed cm handles' def failedCmHandles = [new YangModelCmHandle()] - mockSyncUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> failedCmHandles + mockModuleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> failedCmHandles when: 'reset failed cm handles is started' objectUnderTest.setPreviouslyLockedCmHandlesToAdvised() then: 'it is delegated to the module sync task (service)' 1 * mockModuleSyncTasks.setCmHandlesToAdvised(failedCmHandles) } + def 'Module Sync Locking.'() { + given: 'module sync utilities returns an advised cm handle' + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(1) + and: 'can lock is : #canLock' + mockWorkQueueLock.tryLock() >> canLock + when: 'attempt to populate the work queue' + objectUnderTest.populateWorkQueueIfNeeded() + then: 'the queue remains empty is #expectQueueRemainsEmpty' + assert moduleSyncWorkQueue.isEmpty() == expectQueueRemainsEmpty + where: 'the following lock states are applied' + canLock | expectQueueRemainsEmpty + false | true + true | false + } + + def 'Sleeper gets interrupted.'() { + given: 'sleeper gets interrupted' + spiedSleeper.haveALittleRest(_) >> { throw new InterruptedException() } + when: 'the watchdog attempts to sleep to save cpu cycles' + objectUnderTest.preventBusyWait() + then: 'no exception is thrown' + noExceptionThrown() + } + def createDataNodes(numberOfDataNodes) { def dataNodes = [] - (1..numberOfDataNodes).each {dataNodes.add(new DataNode())} + numberOfDataNodes.times { dataNodes.add(new DataNode()) } return dataNodes } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy index bd7c321bc7..4b676e1b4c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,19 @@ package org.onap.cps.ncmp.impl.inventory.sync.lcm +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.core.read.ListAppender import org.onap.cps.ncmp.api.inventory.models.CompositeState import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.slf4j.LoggerFactory import spock.lang.Specification +import static java.util.Collections.EMPTY_LIST +import static java.util.Collections.EMPTY_MAP import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETED import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETING @@ -35,6 +42,17 @@ import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { + def logger = Spy(ListAppender<ILoggingEvent>) + + void setup() { + ((Logger) LoggerFactory.getLogger(LcmEventsCmHandleStateHandlerImpl.class)).addAppender(logger) + logger.start() + } + + void cleanup() { + ((Logger) LoggerFactory.getLogger(LcmEventsCmHandleStateHandlerImpl.class)).detachAndStopAllAppenders() + } + def mockInventoryPersistence = Mock(InventoryPersistence) def mockLcmEventsCreator = Mock(LcmEventsCreator) def mockLcmEventsService = Mock(LcmEventsService) @@ -51,30 +69,39 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { compositeState = new CompositeState(cmHandleState: fromCmHandleState) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) when: 'update state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, toCmHandleState) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, toCmHandleState)) then: 'state is saved using inventory persistence' - expectedCallsToInventoryPersistence * mockInventoryPersistence.saveCmHandleState(cmHandleId, _) + 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { + args -> { + def cmHandleStatePerCmHandleId = args[0] as Map<String, CompositeState> + assert cmHandleStatePerCmHandleId.get(cmHandleId).cmHandleState == toCmHandleState + } + } + and: 'log message shows state change at INFO level' + def loggingEvent = (ILoggingEvent) logger.list[0] + assert loggingEvent.level == Level.INFO + assert loggingEvent.formattedMessage == "${cmHandleId} is now in ${toCmHandleState} state" and: 'event service is called to publish event' - expectedCallsToEventService * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) + 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) where: 'state change parameters are provided' - stateChange | fromCmHandleState | toCmHandleState || expectedCallsToInventoryPersistence | expectedCallsToEventService - 'ADVISED to READY' | ADVISED | READY || 1 | 1 - 'READY to LOCKED' | READY | LOCKED || 1 | 1 - 'ADVISED to ADVISED' | ADVISED | ADVISED || 0 | 0 - 'READY to READY' | READY | READY || 0 | 0 - 'LOCKED to LOCKED' | LOCKED | LOCKED || 0 | 0 - 'DELETED to ADVISED' | DELETED | ADVISED || 0 | 1 + stateChange | fromCmHandleState | toCmHandleState + 'ADVISED to READY' | ADVISED | READY + 'READY to LOCKED' | READY | LOCKED + 'ADVISED to LOCKED' | ADVISED | LOCKED + 'ADVISED to DELETING' | ADVISED | DELETING } - def 'Update and Publish Events on State Change from NO_EXISTING state to ADVISED'() { + def 'Update and Publish Events on State Change from non-existing to ADVISED'() { given: 'Cm Handle represented as YangModelCmHandle' yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: []) when: 'update state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, ADVISED) - then: 'state is saved using inventory persistence' - 1 * mockInventoryPersistence.saveCmHandle(yangModelCmHandle) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, ADVISED)) + then: 'CM-handle is saved using inventory persistence' + 1 * mockInventoryPersistence.saveCmHandleBatch(List.of(yangModelCmHandle)) and: 'event service is called to publish event' 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) + and: 'a log entry is written' + assert getLogMessage(0) == "${cmHandleId} is now in ADVISED state" } def 'Update and Publish Events on State Change from LOCKED to ADVISED'() { @@ -83,69 +110,62 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { lockReason: CompositeState.LockReason.builder().lockReasonCategory(MODULE_SYNC_FAILED).details('some lock details').build()) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) when: 'update state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, ADVISED) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, ADVISED)) then: 'state is saved using inventory persistence and old lock reason details are retained' - 1 * mockInventoryPersistence.saveCmHandleState(cmHandleId, _) >> { + 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { args -> { - assert (args[1] as CompositeState).lockReason.details == 'some lock details' + def cmHandleStatePerCmHandleId = args[0] as Map<String, CompositeState> + assert cmHandleStatePerCmHandleId.get(cmHandleId).lockReason.details == 'some lock details' } } and: 'event service is called to publish event' 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) + and: 'a log entry is written' + assert getLogMessage(0) == "${cmHandleId} is now in ADVISED state" } - def 'Update and Publish Events on State Change from DELETING to ADVISED'() { - given: 'Cm Handle represented as YangModelCmHandle in DELETING state' - yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) - when: 'update state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, ADVISED) - then: 'the cm handle is saved using inventory persistence' - 1 * mockInventoryPersistence.saveCmHandle(yangModelCmHandle) - and: 'event service is called to publish event' - 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) - } - - def 'Update and Publish Events on State Change to READY'() { + def 'Update and Publish Events on State Change to from ADVISED to READY'() { given: 'Cm Handle represented as YangModelCmHandle' compositeState = new CompositeState(cmHandleState: ADVISED) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) and: 'global sync flag is set' compositeState.setDataSyncEnabled(false) when: 'update cmhandle state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, READY) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, READY)) then: 'state is saved using inventory persistence with expected dataSyncState' - 1 * mockInventoryPersistence.saveCmHandleState(cmHandleId, _) >> { + 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { args-> { - def result = (args[1] as CompositeState) - assert result.dataSyncEnabled == false - assert result.dataStores.operationalDataStore.dataStoreSyncState == DataStoreSyncState.NONE_REQUESTED - + def cmHandleStatePerCmHandleId = args[0] as Map<String, CompositeState> + assert cmHandleStatePerCmHandleId.get(cmHandleId).dataSyncEnabled == false + assert cmHandleStatePerCmHandleId.get(cmHandleId).dataStores.operationalDataStore.dataStoreSyncState == DataStoreSyncState.NONE_REQUESTED } } and: 'event service is called to publish event' 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) + and: 'a log entry is written' + assert getLogMessage(0) == "${cmHandleId} is now in READY state" } - def 'Update cmHandle state to "DELETING"' (){ + def 'Update cmHandle state from READY to DELETING' (){ given: 'cm Handle as Yang model' compositeState = new CompositeState(cmHandleState: READY) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) when: 'updating cm handle state to "DELETING"' - objectUnderTest.updateCmHandleState(yangModelCmHandle, DELETING) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, DELETING)) then: 'the cm handle state is as expected' yangModelCmHandle.getCompositeState().getCmHandleState() == DELETING and: 'method to persist cm handle state is called once' - 1 * mockInventoryPersistence.saveCmHandleState(yangModelCmHandle.getId(), yangModelCmHandle.getCompositeState()) + 1 * mockInventoryPersistence.saveCmHandleStateBatch(Map.of(yangModelCmHandle.getId(), yangModelCmHandle.getCompositeState())) and: 'the method to publish Lcm event is called once' 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) } - def 'Update cmHandle state to "DELETED"' (){ + def 'Update cmHandle state to DELETING to DELETED' (){ given: 'cm Handle with state "DELETING" as Yang model ' compositeState = new CompositeState(cmHandleState: DELETING) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) when: 'updating cm handle state to "DELETED"' - objectUnderTest.updateCmHandleState(yangModelCmHandle, DELETED) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, DELETED)) then: 'the cm handle state is as expected' yangModelCmHandle.getCompositeState().getCmHandleState() == DELETED and: 'the method to publish Lcm event is called once' @@ -157,14 +177,13 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def cmHandleStateMap = setupBatch('NO_CHANGE') when: 'updating a batch of changes' objectUnderTest.updateCmHandleStateBatch(cmHandleStateMap) - then: 'batch is empty and nothing to update' - 1 * mockInventoryPersistence.saveCmHandleBatch(_) >> { - args -> { - assert (args[0] as Collection<YangModelCmHandle>).size() == 0 - } - } + then: 'no changes are persisted' + 1 * mockInventoryPersistence.saveCmHandleBatch(EMPTY_LIST) + 1 * mockInventoryPersistence.saveCmHandleStateBatch(EMPTY_MAP) and: 'no event will be published' 0 * mockLcmEventsService.publishLcmEvent(*_) + and: 'no log entries are written' + assert logger.list.empty } def 'Batch of new cm handles provided'() { @@ -178,8 +197,13 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { assert (args[0] as Collection<YangModelCmHandle>).id.containsAll('cmhandle1', 'cmhandle2') } } + and: 'no state updates are persisted' + 1 * mockInventoryPersistence.saveCmHandleStateBatch(EMPTY_MAP) and: 'event service is called to publish events' 2 * mockLcmEventsService.publishLcmEvent(_, _, _) + and: 'two log entries are written' + assert getLogMessage(0) == 'cmhandle1 is now in ADVISED state' + assert getLogMessage(1) == 'cmhandle2 is now in ADVISED state' } def 'Batch of existing cm handles is updated'() { @@ -187,14 +211,19 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def cmHandleStateMap = setupBatch('UPDATE') when: 'updating a batch of changes' objectUnderTest.updateCmHandleStateBatch(cmHandleStateMap) - then : 'existing cm handles composite state is persisted' + then: 'existing cm handles composite states are persisted' 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { args -> { - assert (args[0] as Map<String, CompositeState>).keySet().containsAll(['cmhandle1','cmhandle2']) + assert (args[0] as Map<String, CompositeState>).keySet().containsAll(['cmhandle1', 'cmhandle2']) } } + and: 'no new handles are persisted' + 1 * mockInventoryPersistence.saveCmHandleBatch(EMPTY_LIST) and: 'event service is called to publish events' 2 * mockLcmEventsService.publishLcmEvent(_, _, _) + and: 'two log entries are written' + assert getLogMessage(0) == 'cmhandle1 is now in READY state' + assert getLogMessage(1) == 'cmhandle2 is now in DELETING state' } def 'Batch of existing cm handles is deleted'() { @@ -202,14 +231,30 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def cmHandleStateMap = setupBatch('DELETED') when: 'updating a batch of changes' objectUnderTest.updateCmHandleStateBatch(cmHandleStateMap) - then : 'existing cm handles composite state is persisted' - 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { - args -> { - assert (args[0] as Map<String, CompositeState>).isEmpty() - } - } + then: 'state of deleted handles is not persisted' + 1 * mockInventoryPersistence.saveCmHandleStateBatch(EMPTY_MAP) + and: 'no new handles are persisted' + 1 * mockInventoryPersistence.saveCmHandleBatch(EMPTY_LIST) and: 'event service is called to publish events' 2 * mockLcmEventsService.publishLcmEvent(_, _, _) + and: 'two log entries are written' + assert getLogMessage(0) == 'cmhandle1 is now in DELETED state' + assert getLogMessage(1) == 'cmhandle2 is now in DELETED state' + } + + def 'Log entries and events are not sent when an error occurs during persistence'() { + given: 'A batch of updated cm handles' + def cmHandleStateMap = setupBatch('UPDATE') + and: 'an error will be thrown when trying to persist' + mockInventoryPersistence.saveCmHandleStateBatch(_) >> { throw new RuntimeException() } + when: 'updating a batch of changes' + objectUnderTest.updateCmHandleStateBatch(cmHandleStateMap) + then: 'the exception is not handled' + thrown(RuntimeException) + and: 'no events are published' + 0 * mockLcmEventsService.publishLcmEvent(_, _, _) + and: 'no log entries are written' + assert logger.list.empty } def setupBatch(type) { @@ -217,26 +262,31 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def yangModelCmHandle1 = new YangModelCmHandle(id: 'cmhandle1', dmiProperties: [], publicProperties: []) def yangModelCmHandle2 = new YangModelCmHandle(id: 'cmhandle2', dmiProperties: [], publicProperties: []) - if ('NEW' == type) { - return [yangModelCmHandle1, yangModelCmHandle2] - } + switch (type) { + case 'NEW': + return [yangModelCmHandle1, yangModelCmHandle2] - if ('DELETED' == type) { - yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: READY) - yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) - return [(yangModelCmHandle1): DELETED, (yangModelCmHandle2): DELETED] - } + case 'DELETED': + yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: READY) + yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) + return [(yangModelCmHandle1): DELETED, (yangModelCmHandle2): DELETED] - if ('UPDATE' == type) { - yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: ADVISED) - yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) - return [(yangModelCmHandle1): READY, (yangModelCmHandle2): DELETING] - } + case 'UPDATE': + yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: ADVISED) + yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) + return [(yangModelCmHandle1): READY, (yangModelCmHandle2): DELETING] + + case 'NO_CHANGE': + yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: ADVISED) + yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) + return [(yangModelCmHandle1): ADVISED, (yangModelCmHandle2): READY] - if ('NO_CHANGE' == type) { - yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: ADVISED) - yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) - return [(yangModelCmHandle1): ADVISED, (yangModelCmHandle2): READY] + default: + throw new IllegalArgumentException("batch type '${type}' not recognized") } } + + def getLogMessage(index) { + return logger.list[index].formattedMessage + } } |