diff options
Diffstat (limited to 'cps-ncmp-service/src')
29 files changed, 668 insertions, 275 deletions
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandlerQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandlerQueryService.java index 92b1e82c3..faf58b95b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandlerQueryService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandlerQueryService.java @@ -20,9 +20,9 @@ package org.onap.cps.ncmp.api; -import java.util.Collection; +import java.util.Set; +import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; import org.onap.cps.spi.model.CmHandleQueryServiceParameters; -import org.onap.cps.spi.model.DataNode; public interface NetworkCmProxyCmHandlerQueryService { /** @@ -31,5 +31,13 @@ public interface NetworkCmProxyCmHandlerQueryService { * @param cmHandleQueryServiceParameters the cm handle query parameters * @return collection of cm handles */ - Collection<DataNode> queryCmHandles(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); + Set<NcmpServiceCmHandle> queryCmHandles(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); + + /** + * Query and return cm handles that match the given query parameters. + * + * @param cmHandleQueryServiceParameters the cm handle query parameters + * @return collection of cm handle ids + */ + Set<String> queryCmHandleIds(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java index ce850cc82..ea27d4a1e 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java @@ -28,10 +28,12 @@ import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum import java.util.Collection; import java.util.Map; import java.util.Set; +import org.onap.cps.ncmp.api.inventory.CompositeState; import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.models.DmiPluginRegistration; import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; +import org.onap.cps.spi.model.ModuleDefinition; import org.onap.cps.spi.model.ModuleReference; /* @@ -106,6 +108,14 @@ public interface NetworkCmProxyDataService { Collection<ModuleReference> getYangResourcesModuleReferences(String cmHandleId); /** + * Retrieve module definitions for the given cm handle. + * + * @param cmHandleId cm handle identifier + * @return a collection of module definition (moduleName, revision and yang resource content) + */ + Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(String cmHandleId); + + /** * Query cm handle details by cm handle's name. * * @param cmHandleId cm handle identifier @@ -122,6 +132,14 @@ public interface NetworkCmProxyDataService { Map<String, String> getCmHandlePublicProperties(String cmHandleId); /** + * Get cm handle composite state by cm handle id. + * + * @param cmHandleId cm handle identifier + * @return a cm handle composite state + */ + CompositeState getCmHandleCompositeState(String cmHandleId); + + /** * Query and return cm handles that match the given query parameters. * * @param cmHandleQueryApiParameters the cm handle query parameters diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java index 3deaa7d23..000627bec 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java @@ -22,6 +22,7 @@ package org.onap.cps.ncmp.api.impl; import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NCMP_DATASPACE_NAME; import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NCMP_DMI_REGISTRY_ANCHOR; +import static org.onap.cps.ncmp.api.impl.utils.YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle; import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS; import static org.onap.cps.utils.CmHandleQueryRestParametersValidator.validateModuleNameConditionProperties; @@ -29,20 +30,22 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService; +import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; +import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; import org.onap.cps.spi.CpsAdminPersistenceService; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.model.Anchor; import org.onap.cps.spi.model.CmHandleQueryServiceParameters; import org.onap.cps.spi.model.ConditionProperties; import org.onap.cps.spi.model.DataNode; -import org.onap.cps.spi.model.DataNodeIdentifier; -import org.onap.cps.utils.JsonObjectMapper; import org.springframework.stereotype.Service; @Service @@ -52,9 +55,9 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm private static final String PROPERTY_QUERY_NAME = "hasAllProperties"; private static final String MODULE_QUERY_NAME = "hasAllModules"; + private static final Map<String, NcmpServiceCmHandle> NO_QUERY_EXECUTED = null; private final CpsDataPersistenceService cpsDataPersistenceService; private final CpsAdminPersistenceService cpsAdminPersistenceService; - private final JsonObjectMapper jsonObjectMapper; /** * Query and return cm handles that match the given query parameters. @@ -63,86 +66,122 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm * @return collection of cm handles */ @Override - public Collection<DataNode> queryCmHandles(final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + public Set<NcmpServiceCmHandle> queryCmHandles( + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) { return getAllCmHandles(); } - final Collection<DataNodeIdentifier> amalgamatedQueryResultIdentifiers = new ArrayList<>(); - final Map<DataNodeIdentifier, DataNode> amalgamatedQueryResults = new HashMap<>(); + final Map<String, NcmpServiceCmHandle> publicPropertyQueryResult + = executePublicPropertyQueries(cmHandleQueryServiceParameters); - final boolean firstQuery = moduleNameQuery(cmHandleQueryServiceParameters, - amalgamatedQueryResultIdentifiers, amalgamatedQueryResults); + final Map<String, NcmpServiceCmHandle> combinedQueryResult = + combineWithModuleNameQuery(cmHandleQueryServiceParameters, publicPropertyQueryResult); - publicPropertyQuery(cmHandleQueryServiceParameters, amalgamatedQueryResultIdentifiers, - amalgamatedQueryResults, firstQuery); + return combinedQueryResult == NO_QUERY_EXECUTED + ? Collections.emptySet() : new HashSet<>(combinedQueryResult.values()); + } - final Collection<DataNode> filteredDataNodes = new ArrayList<>(); - amalgamatedQueryResultIdentifiers.forEach(amalgamatedQueryResultIdentifier -> - filteredDataNodes.add(amalgamatedQueryResults.get(amalgamatedQueryResultIdentifier)) - ); + /** + * Query and return cm handles that match the given query parameters. + * + * @param cmHandleQueryServiceParameters the cm handle query parameters + * @return collection of cm handle ids + */ + @Override + public Set<String> queryCmHandleIds( + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + + if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) { + return getAllCmHandleIds(); + } + + final Map<String, NcmpServiceCmHandle> publicPropertyQueryResult + = executePublicPropertyQueries(cmHandleQueryServiceParameters); + + final Collection<String> moduleNamesForQuery = + getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); + if (moduleNamesForQuery.isEmpty()) { + return publicPropertyQueryResult == NO_QUERY_EXECUTED + ? Collections.emptySet() : publicPropertyQueryResult.keySet(); + } + final Set<String> moduleNameQueryResult = getNamesOfAnchorsWithGivenModules(moduleNamesForQuery); + + if (publicPropertyQueryResult == NO_QUERY_EXECUTED) { + return moduleNameQueryResult; + } - return filteredDataNodes; + moduleNameQueryResult.retainAll(publicPropertyQueryResult.keySet()); + return moduleNameQueryResult; } - private void publicPropertyQuery(final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, - final Collection<DataNodeIdentifier> amalgamatedQueryResultIdentifiers, - final Map<DataNodeIdentifier, DataNode> amalgamatedQueryResults, - boolean firstQuery) { - for (final Map.Entry<String, String> entry : - getPublicPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters()).entrySet()) { - final String cmHandlePath = "//public-properties[@name='" + entry.getKey() + "' " + "and @value='" - + entry.getValue() + "']" + "/ancestor::cm-handles"; - - final Collection<DataNode> dataNodes = getDataNodes(cmHandlePath); - - if (firstQuery) { - firstQuery = false; - dataNodes.forEach(dataNode -> { - final DataNodeIdentifier dataNodeIdentifier = - jsonObjectMapper.convertToValueType(dataNode, DataNodeIdentifier.class); - amalgamatedQueryResultIdentifiers.add(dataNodeIdentifier); - amalgamatedQueryResults.put(dataNodeIdentifier, dataNode); - }); + private Map<String, NcmpServiceCmHandle> executePublicPropertyQueries( + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final Map<String, String> publicPropertyQueryPairs = + getPublicPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); + if (publicPropertyQueryPairs.isEmpty()) { + return NO_QUERY_EXECUTED; + } + Map<String, NcmpServiceCmHandle> cmHandleIdToNcmpServiceCmHandles = null; + for (final Map.Entry<String, String> entry : publicPropertyQueryPairs.entrySet()) { + final String cpsPath = "//public-properties[@name='" + entry.getKey() + "' and @value='" + + entry.getValue() + "']/ancestor::cm-handles"; + + final Collection<DataNode> dataNodes = queryDataNodes(cpsPath); + if (cmHandleIdToNcmpServiceCmHandles == NO_QUERY_EXECUTED) { + cmHandleIdToNcmpServiceCmHandles = collectDataNodesToNcmpServiceCmHandles(dataNodes); } else { - final Collection<DataNodeIdentifier> singleConditionQueryDataNodeIdentifiers = new ArrayList<>(); - dataNodes.forEach(dataNode -> { - final DataNodeIdentifier dataNodeIdentifier = - jsonObjectMapper.convertToValueType(dataNode, DataNodeIdentifier.class); - singleConditionQueryDataNodeIdentifiers.add(dataNodeIdentifier); - amalgamatedQueryResults.put(dataNodeIdentifier, dataNode); - }); - amalgamatedQueryResultIdentifiers.retainAll(singleConditionQueryDataNodeIdentifiers); + final Collection<String> cmHandleIdsToRetain = dataNodes.parallelStream() + .map(dataNode -> dataNode.getLeaves().get("id").toString()).collect(Collectors.toSet()); + cmHandleIdToNcmpServiceCmHandles.keySet().retainAll(cmHandleIdsToRetain); } - - if (amalgamatedQueryResultIdentifiers.isEmpty()) { + if (cmHandleIdToNcmpServiceCmHandles.isEmpty()) { break; } } + return cmHandleIdToNcmpServiceCmHandles; } - private boolean moduleNameQuery(final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, - final Collection<DataNodeIdentifier> amalgamatedQueryResultIdentifiers, - final Map<DataNodeIdentifier, DataNode> amalgamatedQueryResults) { - boolean firstQuery = true; - if (!getModuleNames(cmHandleQueryServiceParameters.getCmHandleQueryParameters()).isEmpty()) { - final Collection<String> anchorNames = cpsAdminPersistenceService.queryAnchors("NFP-Operational", - getModuleNames(cmHandleQueryServiceParameters.getCmHandleQueryParameters())) - .parallelStream().map(Anchor::getName).collect(Collectors.toList()); - - getAllCmHandles().forEach(dataNode -> { - if (anchorNames.contains(dataNode.getLeaves().get("id").toString())) { - final DataNodeIdentifier dataNodeIdentifier = - jsonObjectMapper.convertToValueType(dataNode, DataNodeIdentifier.class); - amalgamatedQueryResultIdentifiers.add(dataNodeIdentifier); - amalgamatedQueryResults.put(dataNodeIdentifier, dataNode); - } - }); - - firstQuery = false; + private Map<String, NcmpServiceCmHandle> combineWithModuleNameQuery( + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + final Map<String, NcmpServiceCmHandle> previousQueryResult) { + final Collection<String> moduleNamesForQuery = + getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); + if (moduleNamesForQuery.isEmpty()) { + return previousQueryResult; + } + final Collection<String> cmHandleIdsByModuleName = getNamesOfAnchorsWithGivenModules(moduleNamesForQuery); + if (cmHandleIdsByModuleName.isEmpty()) { + return Collections.emptyMap(); + } + final Map<String, NcmpServiceCmHandle> queryResult = new HashMap<>(cmHandleIdsByModuleName.size()); + if (previousQueryResult == NO_QUERY_EXECUTED) { + cmHandleIdsByModuleName.forEach(cmHandleId -> + queryResult.put(cmHandleId, createNcmpServiceCmHandle( + getDataNode("/dmi-registry/cm-handles[@id='" + cmHandleId + "']"))) + ); + return queryResult; } - return firstQuery; + previousQueryResult.keySet().retainAll(cmHandleIdsByModuleName); + queryResult.putAll(previousQueryResult); + return queryResult; + } + + private Set<String> getNamesOfAnchorsWithGivenModules(final Collection<String> moduleNamesForQuery) { + final Collection<Anchor> anchors = + cpsAdminPersistenceService.queryAnchors("NFP-Operational", moduleNamesForQuery); + return anchors.parallelStream().map(Anchor::getName).collect(Collectors.toSet()); + } + + private Map<String, NcmpServiceCmHandle> collectDataNodesToNcmpServiceCmHandles( + final Collection<DataNode> dataNodes) { + final Map<String, NcmpServiceCmHandle> cmHandleIdToNcmpServiceCmHandle = new HashMap<>(); + dataNodes.forEach(dataNode -> { + final NcmpServiceCmHandle ncmpServiceCmHandle = createNcmpServiceCmHandle(dataNode); + cmHandleIdToNcmpServiceCmHandle.put(ncmpServiceCmHandle.getCmHandleId(), ncmpServiceCmHandle); + }); + return cmHandleIdToNcmpServiceCmHandle; } private List<Map<String, String>> getConditions(final List<ConditionProperties> conditionProperties, @@ -155,13 +194,13 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm return Collections.emptyList(); } - private List<String> getModuleNames(final List<ConditionProperties> conditionProperties) { + private Collection<String> getModuleNamesForQuery(final List<ConditionProperties> conditionProperties) { final List<String> result = new ArrayList<>(); getConditions(conditionProperties, MODULE_QUERY_NAME).parallelStream().forEach( - conditionProperty -> { - validateModuleNameConditionProperties(conditionProperty); - result.add(conditionProperty.get("moduleName")); - } + conditionProperty -> { + validateModuleNameConditionProperties(conditionProperty); + result.add(conditionProperty.get("moduleName")); + } ); return result; } @@ -172,12 +211,28 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm return result; } - private Collection<DataNode> getAllCmHandles() { - return getDataNodes("//public-properties/ancestor::cm-handles"); + private Set<NcmpServiceCmHandle> getAllCmHandles() { + return getDataNode("/dmi-registry").getChildDataNodes().stream() + .map(this::createNcmpServiceCmHandle).collect(Collectors.toSet()); + } + + private Set<String> getAllCmHandleIds() { + return cpsAdminPersistenceService.getAnchors("NFP-Operational") + .parallelStream().map(Anchor::getName).collect(Collectors.toSet()); + } + + private List<DataNode> queryDataNodes(final String cpsPath) { + return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + cpsPath, INCLUDE_ALL_DESCENDANTS); + } + + private DataNode getDataNode(final String cpsPath) { + return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + cpsPath, INCLUDE_ALL_DESCENDANTS); } - private List<DataNode> getDataNodes(final String cmHandlePath) { - return cpsDataPersistenceService.queryDataNodes( - NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandlePath, INCLUDE_ALL_DESCENDANTS); + private NcmpServiceCmHandle createNcmpServiceCmHandle(final DataNode dataNode) { + return convertYangModelCmHandleToNcmpServiceCmHandle(YangDataConverter + .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString())); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java index f8cab4f1c..044a5a44f 100755 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java @@ -41,7 +41,6 @@ import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.api.CpsAdminService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsModuleService; import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService; @@ -51,8 +50,8 @@ import org.onap.cps.ncmp.api.impl.operations.DmiOperations; import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.inventory.CmHandleState; +import org.onap.cps.ncmp.api.inventory.CompositeState; import org.onap.cps.ncmp.api.inventory.InventoryPersistence; -import org.onap.cps.ncmp.api.inventory.sync.ModuleSyncService; import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse; import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError; @@ -64,6 +63,7 @@ import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.onap.cps.spi.exceptions.DataValidationException; import org.onap.cps.spi.exceptions.SchemaSetNotFoundException; import org.onap.cps.spi.model.CmHandleQueryServiceParameters; +import org.onap.cps.spi.model.ModuleDefinition; import org.onap.cps.spi.model.ModuleReference; import org.onap.cps.utils.CpsValidator; import org.onap.cps.utils.JsonObjectMapper; @@ -83,14 +83,10 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService private final CpsModuleService cpsModuleService; - private final CpsAdminService cpsAdminService; - private final NetworkCmProxyDataServicePropertyHandler networkCmProxyDataServicePropertyHandler; private final InventoryPersistence inventoryPersistence; - private final ModuleSyncService moduleSyncService; - private final NetworkCmProxyCmHandlerQueryService networkCmProxyCmHandlerQueryService; @Override @@ -158,6 +154,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService return cpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId); } + @Override + public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) { + CpsValidator.validateNameCharacters(cmHandleId); + return inventoryPersistence.getModuleDefinitionsByCmHandleId(cmHandleId); + } + /** * Retrieve cm handles with details for the given query parameters. * @@ -166,16 +168,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService */ @Override public Set<NcmpServiceCmHandle> executeCmHandleSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) { - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); validateCmHandleQueryParameters(cmHandleQueryServiceParameters); - return networkCmProxyCmHandlerQueryService.queryCmHandles(cmHandleQueryServiceParameters).stream() - .map(dataNode -> YangDataConverter - .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString())) - .map(YangDataConverter::convertYangModelCmHandleToNcmpServiceCmHandle).collect(Collectors.toSet()); + return networkCmProxyCmHandlerQueryService.queryCmHandles(cmHandleQueryServiceParameters); } /** @@ -186,8 +184,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService */ @Override public Set<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) { - return executeCmHandleSearch(cmHandleQueryApiParameters).stream().map(NcmpServiceCmHandle::getCmHandleId) - .collect(Collectors.toSet()); + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( + cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); + + validateCmHandleQueryParameters(cmHandleQueryServiceParameters); + + return networkCmProxyCmHandlerQueryService.queryCmHandleIds(cmHandleQueryServiceParameters); } /** @@ -221,6 +223,18 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService } /** + * Get cm handle composite state for a given cm handle id. + * + * @param cmHandleId cm handle identifier + * @return cm handle state + */ + @Override + public CompositeState getCmHandleCompositeState(final String cmHandleId) { + CpsValidator.validateNameCharacters(cmHandleId); + return inventoryPersistence.getYangModelCmHandle(cmHandleId).getCompositeState(); + } + + /** * THis method registers a cm handle and initiates modules sync. * * @param dmiPluginRegistration dmi plugin registration information. @@ -231,13 +245,14 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = new ArrayList<>(); try { cmHandleRegistrationResponses = dmiPluginRegistration.getCreatedCmHandles().stream() - .map(cmHandle -> - YangModelCmHandle.toYangModelCmHandle( + .map(cmHandle -> { + setCompositeStateToAdvised(cmHandle); + return YangModelCmHandle.toYangModelCmHandle( dmiPluginRegistration.getDmiPlugin(), dmiPluginRegistration.getDmiDataPlugin(), dmiPluginRegistration.getDmiModelPlugin(), - CmHandleState.ADVISED, - cmHandle) + cmHandle); + } ) .map(this::registerNewCmHandle) .collect(Collectors.toList()); @@ -250,6 +265,13 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService return cmHandleRegistrationResponses; } + private void setCompositeStateToAdvised(final NcmpServiceCmHandle ncmpServiceCmHandle) { + final CompositeState compositeState = new CompositeState(); + compositeState.setCmHandleState(CmHandleState.ADVISED); + compositeState.setLastUpdateTimeNow(); + ncmpServiceCmHandle.setCompositeState(compositeState); + } + protected List<CmHandleRegistrationResponse> parseAndRemoveCmHandlesInDmiRegistration( final List<String> tobeRemovedCmHandles) { final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventConsumer.java index 4e5c57ba5..a9e7164fd 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventConsumer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/async/NcmpAsyncRequestResponseEventConsumer.java @@ -24,6 +24,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent; import org.onap.cps.ncmp.event.model.NcmpAsyncRequestResponseEvent; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; @@ -33,6 +34,7 @@ import org.springframework.stereotype.Component; @Component @Slf4j @RequiredArgsConstructor +@ConditionalOnProperty(name = "notification.enabled", havingValue = "true", matchIfMissing = true) public class NcmpAsyncRequestResponseEventConsumer { private final NcmpAsyncRequestResponseEventProducer ncmpAsyncRequestResponseEventProducer; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/NcmpEventsService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/NcmpEventsService.java index 6804ac0f0..7b5ceb57a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/NcmpEventsService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/NcmpEventsService.java @@ -47,17 +47,23 @@ public class NcmpEventsService { @Value("${app.ncmp.events.topic:ncmp-events}") private String topicName; + @Value("${notification.enabled:true}") + private boolean notificationsEnabled; + /** * Publish the NcmpEvent to the public topic. * * @param cmHandleId Cm Handle Id */ public void publishNcmpEvent(final String cmHandleId) { - - final NcmpServiceCmHandle ncmpServiceCmHandle = YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle( - inventoryPersistence.getYangModelCmHandle(cmHandleId)); - final NcmpEvent ncmpEvent = ncmpEventsCreator.populateNcmpEvent(cmHandleId, ncmpServiceCmHandle); - ncmpEventsPublisher.publishEvent(topicName, cmHandleId, ncmpEvent); - + if (notificationsEnabled) { + final NcmpServiceCmHandle ncmpServiceCmHandle = + YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle( + inventoryPersistence.getYangModelCmHandle(cmHandleId)); + final NcmpEvent ncmpEvent = ncmpEventsCreator.populateNcmpEvent(cmHandleId, ncmpServiceCmHandle); + ncmpEventsPublisher.publishEvent(topicName, cmHandleId, ncmpEvent); + } else { + log.debug("Notifications disabled."); + } } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/YangDataConverter.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/YangDataConverter.java index 82ea00eb3..95ff48a9c 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/YangDataConverter.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/YangDataConverter.java @@ -78,8 +78,7 @@ public class YangDataConverter { (String) cmHandleDataNode.getLeaves().get("dmi-service-name"), (String) cmHandleDataNode.getLeaves().get("dmi-data-service-name"), (String) cmHandleDataNode.getLeaves().get("dmi-model-service-name"), - ncmpServiceCmHandle.getCompositeState().getCmHandleState(), - ncmpServiceCmHandle + ncmpServiceCmHandle ); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java index 5b719054a..65e03f1f9 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java @@ -34,9 +34,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService; -import org.onap.cps.ncmp.api.inventory.CmHandleState; import org.onap.cps.ncmp.api.inventory.CompositeState; -import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder; import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle; import org.onap.cps.utils.CpsValidator; @@ -70,8 +68,6 @@ public class YangModelCmHandle { @JsonProperty("public-properties") private List<Property> publicProperties; - private static final CompositeStateBuilder compositeStateBuilder = new CompositeStateBuilder(); - /** * Create a yangModelCmHandle. * @@ -84,7 +80,6 @@ public class YangModelCmHandle { public static YangModelCmHandle toYangModelCmHandle(final String dmiServiceName, final String dmiDataServiceName, final String dmiModelServiceName, - final CmHandleState cmHandleState, final NcmpServiceCmHandle ncmpServiceCmHandle) { CpsValidator.validateNameCharacters(ncmpServiceCmHandle.getCmHandleId()); final YangModelCmHandle yangModelCmHandle = new YangModelCmHandle(); @@ -95,8 +90,7 @@ public class YangModelCmHandle { yangModelCmHandle.setDmiProperties(asYangModelCmHandleProperties(ncmpServiceCmHandle.getDmiProperties())); yangModelCmHandle.setPublicProperties(asYangModelCmHandleProperties( ncmpServiceCmHandle.getPublicProperties())); - compositeStateBuilder.withCmHandleState(cmHandleState); - yangModelCmHandle.setCompositeState(compositeStateBuilder.build()); + yangModelCmHandle.setCompositeState(ncmpServiceCmHandle.getCompositeState()); return yangModelCmHandle; } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeState.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeState.java index df303b5da..e8fcaabe9 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeState.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeState.java @@ -94,7 +94,7 @@ public class CompositeState { public static class Operational { @JsonProperty("sync-state") - private SyncState syncState; + private DataStoreSyncState dataStoreSyncState; @JsonProperty("last-sync-time") private String lastSyncTime; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeStateBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeStateBuilder.java index f4d96389d..a0fc0c3a9 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeStateBuilder.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CompositeStateBuilder.java @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Bell Canada - * Copyright (C) 2022 Nordix Foundation. + * Modifications Copyright (C) 2022 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -94,13 +94,14 @@ public class CompositeStateBuilder { /** * To use attributes for creating {@link CompositeState}. * - * @param syncState for the locked state + * @param dataStoreSyncState for the locked state * @param lastSyncTime for the locked state * @return CompositeStateBuilder */ - public CompositeStateBuilder withOperationalDataStores(final SyncState syncState, final String lastSyncTime) { + public CompositeStateBuilder withOperationalDataStores(final DataStoreSyncState dataStoreSyncState, + final String lastSyncTime) { this.datastores = DataStores.builder().operationalDataStore( - Operational.builder().syncState(syncState).lastSyncTime(lastSyncTime).build()).build(); + Operational.builder().dataStoreSyncState(dataStoreSyncState).lastSyncTime(lastSyncTime).build()).build(); return this; } @@ -111,22 +112,18 @@ public class CompositeStateBuilder { * @return CompositeState */ public CompositeStateBuilder fromDataNode(final DataNode dataNode) { - this.cmHandleState = CmHandleState.valueOf((String) dataNode.getLeaves() - .get("cm-handle-state")); + this.cmHandleState = CmHandleState.valueOf((String) dataNode.getLeaves() + .get("cm-handle-state")); + this.lastUpdatedTime = (String) dataNode.getLeaves().get("last-update-time"); for (final DataNode stateChildNode : dataNode.getChildDataNodes()) { if (stateChildNode.getXpath().endsWith("/lock-reason")) { - this.lockReason = new LockReason(LockReasonCategory.valueOf( - (String) stateChildNode.getLeaves().get("reason")), - (String) stateChildNode.getLeaves().get("details")); + this.lockReason = getLockReason(stateChildNode); } if (stateChildNode.getXpath().endsWith("/datastores")) { for (final DataNode dataStoreNodes : stateChildNode.getChildDataNodes()) { Operational operationalDataStore = null; if (dataStoreNodes.getXpath().contains("/operational")) { - operationalDataStore = Operational.builder() - .syncState(SyncState.valueOf((String) dataStoreNodes.getLeaves().get("sync-state"))) - .lastSyncTime((String) dataStoreNodes.getLeaves().get("last-sync-time")) - .build(); + operationalDataStore = getOperationalDataStore(dataStoreNodes); } this.datastores = DataStores.builder().operationalDataStore(operationalDataStore).build(); } @@ -135,4 +132,18 @@ public class CompositeStateBuilder { return this; } + private Operational getOperationalDataStore(final DataNode dataStoreNodes) { + return Operational.builder() + .dataStoreSyncState(DataStoreSyncState.valueOf((String) dataStoreNodes.getLeaves().get("sync-state"))) + .lastSyncTime((String) dataStoreNodes.getLeaves().get("last-sync-time")) + .build(); + } + + private LockReason getLockReason(final DataNode stateChildNode) { + final boolean isLockReasonExists = stateChildNode.getLeaves().containsKey("reason"); + return new LockReason(isLockReasonExists + ? LockReasonCategory.valueOf((String) stateChildNode.getLeaves().get("reason")) + : null, (String) stateChildNode.getLeaves().get("details")); + } + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/SyncState.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/DataStoreSyncState.java index 9c7a47618..4dbedf5f1 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/SyncState.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/DataStoreSyncState.java @@ -20,6 +20,6 @@ package org.onap.cps.ncmp.api.inventory; -public enum SyncState { +public enum DataStoreSyncState { SYNCHRONIZED, UNSYNCHRONIZED, NONE_REQUESTED } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java index 1985bd959..d47da6c0c 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java @@ -21,15 +21,20 @@ package org.onap.cps.ncmp.api.inventory; +import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; + import java.time.OffsetDateTime; +import java.util.Collection; import java.util.List; import lombok.RequiredArgsConstructor; import org.onap.cps.api.CpsDataService; +import org.onap.cps.api.CpsModuleService; import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.DataNode; +import org.onap.cps.spi.model.ModuleDefinition; import org.onap.cps.utils.CpsValidator; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.stereotype.Component; @@ -42,15 +47,17 @@ public class InventoryPersistence { private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry"; - private static final String XPATH_TO_CM_HANDLE = "/dmi-registry/cm-handles[@id='" + "%s" + "']"; + private String xpathCmHandle = "/dmi-registry/cm-handles[@id='" + "%s" + "']"; + + private static final String ANCESTOR_CM_HANDLES = "\"]/ancestor::cm-handles"; private final JsonObjectMapper jsonObjectMapper; private final CpsDataService cpsDataService; - private final CpsDataPersistenceService cpsDataPersistenceService; + private final CpsModuleService cpsModuleService; - private static final CompositeStateBuilder compositeStateBuilder = new CompositeStateBuilder(); + private final CpsDataPersistenceService cpsDataPersistenceService; /** * Get the Cm Handle Composite State from the data node. @@ -60,9 +67,9 @@ public class InventoryPersistence { */ public CompositeState getCmHandleState(final String cmHandleId) { final DataNode stateAsDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - String.format(XPATH_TO_CM_HANDLE, cmHandleId) + "/state", + String.format(xpathCmHandle, cmHandleId) + "/state", FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); - return compositeStateBuilder.fromDataNode(stateAsDataNode).build(); + return new CompositeStateBuilder().fromDataNode(stateAsDataNode).build(); } /** @@ -75,7 +82,7 @@ public class InventoryPersistence { final String cmHandleJsonData = String.format("{\"state\":%s}", jsonObjectMapper.asJsonString(compositeState)); cpsDataService.replaceNodeTree(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - String.format(XPATH_TO_CM_HANDLE, cmHandleId), + String.format(xpathCmHandle, cmHandleId), cmHandleJsonData, OffsetDateTime.now()); } @@ -88,7 +95,7 @@ public class InventoryPersistence { public List<DataNode> getCmHandlesByState(final CmHandleState cmHandleState) { return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, "//state[@cm-handle-state=\"" - + cmHandleState + "\"]/ancestor::cm-handles", + + cmHandleState + ANCESTOR_CM_HANDLES, FetchDescendantsOption.OMIT_DESCENDANTS); } @@ -96,11 +103,13 @@ public class InventoryPersistence { * Method to return data nodes representing the cm handles. * * @param cpsPath cps path for which the cmHandle is requested + * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes * @return a list of data nodes representing the cm handles. */ - public List<DataNode> getCmHandleDataNodesByCpsPath(final String cpsPath) { + public List<DataNode> getCmHandleDataNodesByCpsPath(final String cpsPath, + final FetchDescendantsOption fetchDescendantsOption) { return cpsDataPersistenceService.queryDataNodes( - NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cpsPath, FetchDescendantsOption.OMIT_DESCENDANTS); + NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cpsPath, fetchDescendantsOption); } /** @@ -111,21 +120,21 @@ public class InventoryPersistence { */ public List<DataNode> getCmHandlesByIdAndState(final String cmHandleId, final CmHandleState cmHandleState) { return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, - NCMP_DMI_REGISTRY_ANCHOR, "//cm-handles[@id='" + cmHandleId + "']/state[@cm-handle-state=\"" - + cmHandleState + "\"]/ancestor::cm-handles", - FetchDescendantsOption.OMIT_DESCENDANTS); + NCMP_DMI_REGISTRY_ANCHOR, "//cm-handles[@id='" + cmHandleId + "']/state[@cm-handle-state=\"" + + cmHandleState + ANCESTOR_CM_HANDLES, + FetchDescendantsOption.OMIT_DESCENDANTS); } /** * Method which returns cm handles by the operational sync state of cm handle. - * @param syncState sync state + * @param dataStoreSyncState sync state * @return a list of cm handles */ - public List<DataNode> getCmHandlesByOperationalSyncState(final SyncState syncState) { + public List<DataNode> getCmHandlesByOperationalSyncState(final DataStoreSyncState dataStoreSyncState) { return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, - NCMP_DMI_REGISTRY_ANCHOR, "//state/datastores" - + "/operational[@sync-state=\"" + syncState + "\"]/ancestor::cm-handles", - FetchDescendantsOption.OMIT_DESCENDANTS); + NCMP_DMI_REGISTRY_ANCHOR, "//state/datastores" + + "/operational[@sync-state=\"" + dataStoreSyncState + ANCESTOR_CM_HANDLES, + FetchDescendantsOption.OMIT_DESCENDANTS); } /** @@ -138,11 +147,21 @@ public class InventoryPersistence { return YangDataConverter.convertCmHandleToYangModel(getCmHandleDataNode(cmHandleId), cmHandleId); } + /** + * Method to return module definitions by cmHandleId. + * + * @param cmHandleId cm handle ID + * @return a collection of module definitions (moduleName, revision and yang resource content) + */ + public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) { + return cpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId); + } + private DataNode getCmHandleDataNode(final String cmHandle) { return cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - String.format(XPATH_TO_CM_HANDLE, cmHandle), + String.format(xpathCmHandle, cmHandle), FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); } -} +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdog.java index 553db65dd..adfa33ad8 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/DataSyncWatchdog.java @@ -21,13 +21,14 @@ package org.onap.cps.ncmp.api.inventory.sync; import java.time.OffsetDateTime; +import java.util.function.Consumer; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsDataService; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.inventory.CompositeState; +import org.onap.cps.ncmp.api.inventory.DataStoreSyncState; import org.onap.cps.ncmp.api.inventory.InventoryPersistence; -import org.onap.cps.ncmp.api.inventory.SyncState; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -60,15 +61,21 @@ public class DataSyncWatchdog { } else { cpsDataService.saveData("NFP-Operational", cmHandleId, resourceData, OffsetDateTime.now()); + setSyncStateToSynchronized().accept(compositeState); + inventoryPersistence.saveCmHandleState(cmHandleId, compositeState); } + unSynchronizedReadyCmHandle = syncUtils.getAnUnSynchronizedReadyCmHandle(); + } + log.debug("No Cm-Handles currently found in an READY State and Operational Sync State is UNSYNCHRONIZED"); + } + + private Consumer<CompositeState> setSyncStateToSynchronized() { + return compositeState -> { compositeState.setLastUpdateTimeNow(); compositeState.getDataStores() .setOperationalDataStore(CompositeState.Operational.builder() - .syncState(SyncState.SYNCHRONIZED) + .dataStoreSyncState(DataStoreSyncState.SYNCHRONIZED) .lastSyncTime(CompositeState.nowInSyncTimeFormat()).build()); - inventoryPersistence.saveCmHandleState(cmHandleId, compositeState); - unSynchronizedReadyCmHandle = syncUtils.getAnUnSynchronizedReadyCmHandle(); - } - log.debug("No Cm-Handles currently found in an READY State and Operational Sync State is UNSYNCHRONIZED"); + }; } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncService.java index 58e2bf345..c574aa61d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncService.java @@ -32,6 +32,8 @@ import org.onap.cps.api.CpsAdminService; import org.onap.cps.api.CpsModuleService; import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; +import org.onap.cps.spi.CascadeDeleteAllowed; +import org.onap.cps.spi.exceptions.SchemaSetNotFoundException; import org.onap.cps.spi.model.ModuleReference; import org.springframework.stereotype.Service; @@ -83,4 +85,21 @@ public class ModuleSyncService { schemaSetAndAnchorName); } + /** + * Deletes the SchemaSet for provided cmHandle if the SchemaSet Exists. + * + * @param yangModelCmHandle the yang model of cm handle. + */ + public void deleteSchemaSetIfExists(final YangModelCmHandle yangModelCmHandle) { + final String schemaSetAndAnchorName = yangModelCmHandle.getId(); + try { + cpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetAndAnchorName, + CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED); + log.debug("SchemaSet for {} has been deleted. Ready to be recreated.", schemaSetAndAnchorName); + } catch (final SchemaSetNotFoundException e) { + log.debug("No SchemaSet for {}. Assuming CmHandle has not been previously Module Synced.", + schemaSetAndAnchorName); + } + } + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java index c920649b8..6ec44197d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java @@ -22,12 +22,13 @@ package org.onap.cps.ncmp.api.inventory.sync; import java.util.List; +import java.util.function.Consumer; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.inventory.CmHandleState; import org.onap.cps.ncmp.api.inventory.CompositeState; -import org.onap.cps.ncmp.api.inventory.CompositeState.LockReason; +import org.onap.cps.ncmp.api.inventory.DataStoreSyncState; import org.onap.cps.ncmp.api.inventory.InventoryPersistence; import org.onap.cps.ncmp.api.inventory.LockReasonCategory; import org.springframework.scheduling.annotation.Scheduled; @@ -54,18 +55,16 @@ public class ModuleSyncWatchdog { final String cmHandleId = advisedCmHandle.getId(); final CompositeState compositeState = inventoryPersistence.getCmHandleState(cmHandleId); try { + moduleSyncService.deleteSchemaSetIfExists(advisedCmHandle); moduleSyncService.syncAndCreateSchemaSetAndAnchor(advisedCmHandle); - compositeState.setCmHandleState(CmHandleState.READY); + setCompositeStateToReadyWithInitialDataStoreSyncState().accept(compositeState); } catch (final Exception e) { - compositeState.setCmHandleState(CmHandleState.LOCKED); + setCompositeStateToLocked().accept(compositeState); syncUtils.updateLockReasonDetailsAndAttempts(compositeState, - LockReasonCategory.LOCKED_MISBEHAVING, - e.getMessage()); + LockReasonCategory.LOCKED_MISBEHAVING, e.getMessage()); } - compositeState.setLastUpdateTimeNow(); inventoryPersistence.saveCmHandleState(cmHandleId, compositeState); - log.info("{} is now in {} state", cmHandleId, - advisedCmHandle.getCompositeState().getCmHandleState()); + log.debug("{} is now in {} state", cmHandleId, compositeState.getCmHandleState().name()); advisedCmHandle = syncUtils.getAnAdvisedCmHandle(); } log.debug("No Cm-Handles currently found in an ADVISED state"); @@ -75,16 +74,46 @@ public class ModuleSyncWatchdog { * Execute Cm Handle poll which changes the cm handle state from 'LOCKED' to 'ADVISED'. */ @Scheduled(fixedDelayString = "${timers.locked-modules-sync.sleep-time-ms:300000}") - public void executeLockedMisbehavingCmHandlePoll() { + public void executeLockedCmHandlePoll() { final List<YangModelCmHandle> lockedMisbehavingCmHandles = syncUtils.getLockedMisbehavingYangModelCmHandles(); - for (final YangModelCmHandle lockedMisbehavingModelCmHandle: lockedMisbehavingCmHandles) { - final CompositeState updatedCompositeState = lockedMisbehavingModelCmHandle.getCompositeState(); - updatedCompositeState.setCmHandleState(CmHandleState.ADVISED); - updatedCompositeState.setLastUpdateTimeNow(); - updatedCompositeState.setLockReason(LockReason.builder() - .details(updatedCompositeState.getLockReason().getDetails()).build()); - log.debug("Locked misbehaving cm handle {} is being recycled", lockedMisbehavingModelCmHandle.getId()); - inventoryPersistence.saveCmHandleState(lockedMisbehavingModelCmHandle.getId(), updatedCompositeState); + for (final YangModelCmHandle moduleSyncFailedCmHandle : lockedMisbehavingCmHandles) { + final CompositeState compositeState = moduleSyncFailedCmHandle.getCompositeState(); + final boolean isReadyForRetry = syncUtils.isReadyForRetry(compositeState); + if (isReadyForRetry) { + setCompositeStateToAdvisedAndRetainOldLockReasonDetails(compositeState); + log.debug("Locked misbehaving cm handle {} is being recycled", moduleSyncFailedCmHandle.getId()); + inventoryPersistence.saveCmHandleState(moduleSyncFailedCmHandle.getId(), compositeState); + } } } + + private Consumer<CompositeState> setCompositeStateToLocked() { + return compositeState -> { + compositeState.setCmHandleState(CmHandleState.LOCKED); + compositeState.setLastUpdateTimeNow(); + }; + } + + private Consumer<CompositeState> setCompositeStateToReadyWithInitialDataStoreSyncState() { + return compositeState -> { + compositeState.setCmHandleState(CmHandleState.READY); + final CompositeState.Operational operational = CompositeState.Operational.builder() + .dataStoreSyncState(DataStoreSyncState.UNSYNCHRONIZED) + .lastSyncTime(CompositeState.nowInSyncTimeFormat()) + .build(); + final CompositeState.DataStores dataStores = CompositeState.DataStores.builder() + .operationalDataStore(operational) + .build(); + compositeState.setDataStores(dataStores); + }; + } + + private void setCompositeStateToAdvisedAndRetainOldLockReasonDetails(final CompositeState compositeState) { + compositeState.setCmHandleState(CmHandleState.ADVISED); + compositeState.setLastUpdateTimeNow(); + final String oldLockReasonDetails = compositeState.getLockReason().getDetails(); + final CompositeState.LockReason lockReason = CompositeState.LockReason.builder() + .details(oldLockReasonDetails).build(); + compositeState.setLockReason(lockReason); + } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java index b5456ab14..0c3af6aae 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java @@ -24,6 +24,9 @@ package org.onap.cps.ncmp.api.inventory.sync; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; import java.security.SecureRandom; +import java.time.Duration; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -40,9 +43,10 @@ import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.inventory.CmHandleState; import org.onap.cps.ncmp.api.inventory.CompositeState; +import org.onap.cps.ncmp.api.inventory.DataStoreSyncState; import org.onap.cps.ncmp.api.inventory.InventoryPersistence; import org.onap.cps.ncmp.api.inventory.LockReasonCategory; -import org.onap.cps.ncmp.api.inventory.SyncState; +import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.model.DataNode; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.http.ResponseEntity; @@ -88,7 +92,7 @@ public class SyncUtils { */ public YangModelCmHandle getAnUnSynchronizedReadyCmHandle() { final List<DataNode> unSynchronizedCmHandles = inventoryPersistence - .getCmHandlesByOperationalSyncState(SyncState.UNSYNCHRONIZED); + .getCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED); if (unSynchronizedCmHandles.isEmpty()) { return null; } @@ -111,7 +115,8 @@ public class SyncUtils { */ public List<YangModelCmHandle> getLockedMisbehavingYangModelCmHandles() { final List<DataNode> lockedCmHandleAsDataNodeList = inventoryPersistence.getCmHandleDataNodesByCpsPath( - "//lock-reason[@reason=\"LOCKED_MISBEHAVING\"]/ancestor::cm-handles"); + "//lock-reason[@reason=\"LOCKED_MISBEHAVING\"]/ancestor::cm-handles", + FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS); return lockedCmHandleAsDataNodeList.stream() .map(cmHandle -> YangDataConverter.convertCmHandleToYangModel(cmHandle, cmHandle.getLeaves().get("id").toString())).collect(Collectors.toList()); @@ -138,6 +143,28 @@ public class SyncUtils { .lockReasonCategory(lockReasonCategory).build()); } + + /** + * Check if the retry mechanism should attempt to unlock the cm handle based on the last update time. + * + * @param compositeState the composite state currently in the locked state + * @return if the retry mechanism should be attempted + */ + public boolean isReadyForRetry(final CompositeState compositeState) { + int timeUntilNextAttempt = 1; + final OffsetDateTime time = + OffsetDateTime.parse(compositeState.getLastUpdateTime(), + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")); + final Matcher matcher = retryAttemptPattern.matcher(compositeState.getLockReason().getDetails()); + if (matcher.find()) { + timeUntilNextAttempt = (int) Math.pow(2, Integer.parseInt(matcher.group(1))); + } else { + log.debug("First Attempt: no current attempts found."); + } + final int timeSinceLastAttempt = (int) Duration.between(time, OffsetDateTime.now()).toMinutes(); + return timeSinceLastAttempt > timeUntilNextAttempt; + } + /** * Get the Resourece Data from Node through DMI Passthrough service. * diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy index 46c866282..c121ce076 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy @@ -20,7 +20,6 @@ package org.onap.cps.ncmp.api.impl -import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService import org.onap.cps.spi.CpsAdminPersistenceService import org.onap.cps.spi.CpsDataPersistenceService @@ -28,7 +27,6 @@ import org.onap.cps.spi.model.Anchor import org.onap.cps.spi.model.CmHandleQueryServiceParameters import org.onap.cps.spi.model.ConditionProperties import org.onap.cps.spi.model.DataNode -import org.onap.cps.utils.JsonObjectMapper import spock.lang.Specification import java.util.stream.Collectors @@ -39,8 +37,7 @@ class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification { def cpsAdminPersistenceService = Mock(CpsAdminPersistenceService) NetworkCmProxyCmHandlerQueryService objectUnderTest = new NetworkCmProxyCmHandlerQueryServiceImpl( - cpsDataPersistenceService, cpsAdminPersistenceService, new JsonObjectMapper(new ObjectMapper()) - ) + cpsDataPersistenceService, cpsAdminPersistenceService) def 'Retrieve cm handles with public properties when #scenario.'() { given: 'a condition property' @@ -51,16 +48,19 @@ class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification { cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) and: 'mock services' mockResponses() - when: 'the service is invoked' - def returnedCmHandles = objectUnderTest.queryCmHandles(cmHandleQueryParameters) - then: 'the correct expected cm handles are returned' - returnedCmHandles.stream().map(d -> d.leaves.get('id').toString()).collect(Collectors.toSet()) == expectedCmHandleIds as Set + when: 'a query is execute (with and without Data)' + def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters) + then: 'the correct expected cm handles ids are returned' + returnedCmHandlesJustIds == expectedCmHandleIds as Set + and: 'the correct cm handle data objects are returned' + returnedCmHandlesWithData.stream().map(dataNode -> dataNode.cmHandleId).collect(Collectors.toSet()) == expectedCmHandleIds as Set where: 'the following data is used' - scenario | publicProperties || expectedCmHandleIds - 'single matching property' | [['Contact' : 'newemailforstore@bookstore.com']] || ['PNFDemo', 'PNFDemo2', 'PNFDemo4'] - 'public property dont match' | [['wont_match' : 'wont_match']] || [] - '2 properties, only one match (and)' | [['Contact' : 'newemailforstore@bookstore.com'], ['Contact2': 'newemailforstore2@bookstore.com']] || ['PNFDemo4'] - '2 properties, no match (and)' | [['Contact' : 'newemailforstore@bookstore.com'], ['Contact2': '']] || [] + scenario | publicProperties || expectedCmHandleIds + 'single property matches' | [['Contact' : 'newemailforstore@bookstore.com']] || ['PNFDemo1', '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': '']] || [] } def 'Retrieve cm handles with module names when #scenario.'() { @@ -73,15 +73,17 @@ class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification { and: 'mock services' mockResponses() when: 'the service is invoked' - def returnedCmHandles = objectUnderTest.queryCmHandles(cmHandleQueryParameters) + def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters) then: 'the correct expected cm handles are returned' - returnedCmHandles.stream().map(d -> d.leaves.get('id').toString()).collect(Collectors.toSet()) == expectedCmHandleIds as Set + returnedCmHandlesJustIds == expectedCmHandleIds as Set + returnedCmHandlesWithData.stream().map(dataNode -> dataNode.cmHandleId).collect(Collectors.toSet()) == expectedCmHandleIds as Set where: 'the following data is used' - scenario | moduleNames || expectedCmHandleIds - 'single matching module name' | [['moduleName' : 'MODULE-NAME-001']] || ['PNFDemo3', 'PNFDemo', 'PNFDemo2'] - 'module name dont match' | [['moduleName' : 'MODULE-NAME-004']] || [] - '2 module names, only one match (and)' | [['moduleName' : 'MODULE-NAME-002'], ['moduleName': 'MODULE-NAME-003']] || ['PNFDemo4'] - '2 module names, no match (and)' | [['moduleName' : 'MODULE-NAME-002'], ['moduleName': 'MODULE-NAME-004']] || [] + scenario | moduleNames || expectedCmHandleIds + 'single matching module name' | [['moduleName' : 'MODULE-NAME-001']] || ['PNFDemo3', 'PNFDemo1', 'PNFDemo2'] + 'module name dont match' | [['moduleName' : 'MODULE-NAME-004']] || [] + '2 module names, only one match' | [['moduleName' : 'MODULE-NAME-002'], ['moduleName': 'MODULE-NAME-003']] || ['PNFDemo4'] + '2 module names, no matches' | [['moduleName' : 'MODULE-NAME-002'], ['moduleName': 'MODULE-NAME-004']] || [] } def 'Retrieve cm handles with combined queries when #scenario.'() { @@ -97,12 +99,14 @@ class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification { and: 'mock services' mockResponses() when: 'the service is invoked' - def returnedCmHandles = objectUnderTest.queryCmHandles(cmHandleQueryParameters) + def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters) then: 'the correct expected cm handles are returned' - returnedCmHandles.stream().map(d -> d.leaves.get('id').toString()).collect(Collectors.toSet()) == expectedCmHandleIds as Set + returnedCmHandlesJustIds == expectedCmHandleIds as Set + returnedCmHandlesWithData.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == expectedCmHandleIds as Set where: 'the following data is used' scenario | moduleNames | publicProperties || expectedCmHandleIds - 'particularly intersect' | [['moduleName' : 'MODULE-NAME-001']] | [['Contact' : 'newemailforstore@bookstore.com']] || ['PNFDemo2', 'PNFDemo'] + 'particularly intersect' | [['moduleName' : 'MODULE-NAME-001']] | [['Contact' : 'newemailforstore@bookstore.com']] || ['PNFDemo1', 'PNFDemo2'] 'empty intersect' | [['moduleName' : 'MODULE-NAME-004']] | [['Contact' : 'newemailforstore@bookstore.com']] || [] 'total intersect' | [['moduleName' : 'MODULE-NAME-002']] | [['Contact2' : 'newemailforstore2@bookstore.com']] || ['PNFDemo4'] } @@ -112,38 +116,39 @@ class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification { mockResponses() when: 'the service is invoked' def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def returnedCmHandles = objectUnderTest.queryCmHandles(cmHandleQueryParameters) + def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters) then: 'the correct expected cm handles are returned' - returnedCmHandles.stream().map(d -> d.leaves.get('id').toString()).collect(Collectors.toSet()) == ['PNFDemo', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'] as Set + returnedCmHandlesJustIds == ['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'] as Set + returnedCmHandlesWithData.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'] as Set } void mockResponses() { - def pNFDemo = new DataNode(xpath: 'cmHandle/id[\'PNFDemo\']', leaves: ['id':'PNFDemo']) - def pNFDemo2 = new DataNode(xpath: 'cmHandle/id[\'PNFDemo2\']', leaves: ['id':'PNFDemo2']) - def pNFDemo3 = new DataNode(xpath: 'cmHandle/id[\'PNFDemo3\']', leaves: ['id':'PNFDemo3']) - def pNFDemo4 = new DataNode(xpath: 'cmHandle/id[\'PNFDemo4\']', leaves: ['id':'PNFDemo4']) + def pNFDemo1 = new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'PNFDemo1\']', leaves: ['id':'PNFDemo1']) + def pNFDemo2 = new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'PNFDemo2\']', leaves: ['id':'PNFDemo2']) + def pNFDemo3 = new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'PNFDemo3\']', leaves: ['id':'PNFDemo3']) + def pNFDemo4 = new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'PNFDemo4\']', leaves: ['id':'PNFDemo4']) + def dmiRegistry = new DataNode(xpath: '/dmi-registry', childDataNodes: [pNFDemo1, pNFDemo2, pNFDemo3, pNFDemo4]) + + cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\'Contact\' and @value=\'newemailforstore@bookstore.com\']/ancestor::cm-handles', _) >> [pNFDemo1, pNFDemo2, pNFDemo4] + cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\'wont_match\' and @value=\'wont_match\']/ancestor::cm-handles', _) >> [] + cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\'Contact2\' and @value=\'newemailforstore2@bookstore.com\']/ancestor::cm-handles', _) >> [pNFDemo4] + cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\'Contact2\' and @value=\'\']/ancestor::cm-handles', _) >> [] + + cpsDataPersistenceService.getDataNode(_, _, '/dmi-registry', _) >> dmiRegistry - cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\'Contact\' and @value=\'newemailforstore@bookstore.com\']/ancestor::cm-handles', _) - >> [pNFDemo, pNFDemo2, pNFDemo4] - cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\'wont_match\' and @value=\'wont_match\']/ancestor::cm-handles', _) - >> [] - cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\'Contact2\' and @value=\'newemailforstore2@bookstore.com\']/ancestor::cm-handles', _) - >> [pNFDemo4] - cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\'Contact2\' and @value=\'\']/ancestor::cm-handles', _) - >> [] - cpsDataPersistenceService.queryDataNodes(_, _, '//public-properties/ancestor::cm-handles', _) - >> [pNFDemo, pNFDemo2, pNFDemo3, pNFDemo4] - cpsDataPersistenceService.queryDataNodes(_, _, '//cm-handles[@id=\'PNFDemo\']', _) >> [pNFDemo] - cpsDataPersistenceService.queryDataNodes(_, _, '//cm-handles[@id=\'PNFDemo2\']', _) >> [pNFDemo2] - cpsDataPersistenceService.queryDataNodes(_, _, '//cm-handles[@id=\'PNFDemo3\']', _) >> [pNFDemo3] - cpsDataPersistenceService.queryDataNodes(_, _, '//cm-handles[@id=\'PNFDemo4\']', _) >> [pNFDemo4] + cpsDataPersistenceService.getDataNode(_, _, '/dmi-registry/cm-handles[@id=\'PNFDemo1\']', _) >> pNFDemo1 + cpsDataPersistenceService.getDataNode(_, _, '/dmi-registry/cm-handles[@id=\'PNFDemo2\']', _) >> pNFDemo2 + cpsDataPersistenceService.getDataNode(_, _, '/dmi-registry/cm-handles[@id=\'PNFDemo3\']', _) >> pNFDemo3 + cpsDataPersistenceService.getDataNode(_, _, '/dmi-registry/cm-handles[@id=\'PNFDemo4\']', _) >> pNFDemo4 - cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-001']) >> [new Anchor(name: 'PNFDemo2'), new Anchor(name: 'PNFDemo3'), new Anchor(name: 'PNFDemo')] + cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-001']) >> [new Anchor(name: 'PNFDemo1'), new Anchor(name: 'PNFDemo2'), new Anchor(name: 'PNFDemo3')] cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-004']) >> [] cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-003', 'MODULE-NAME-002']) >> [new Anchor(name: 'PNFDemo4')] cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-002', 'MODULE-NAME-003']) >> [new Anchor(name: 'PNFDemo4')] cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-004', 'MODULE-NAME-002']) >> [] cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-002', 'MODULE-NAME-004']) >> [] cpsAdminPersistenceService.queryAnchors(_, ['MODULE-NAME-002']) >> [new Anchor(name: 'PNFDemo2'), new Anchor(name: 'PNFDemo4')] + cpsAdminPersistenceService.getAnchors(_) >> [new Anchor(name: 'PNFDemo1'), new Anchor(name: 'PNFDemo2'), new Anchor(name: 'PNFDemo3'), new Anchor(name: 'PNFDemo4')] } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy index 5357c42b7..02e6419c3 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy @@ -23,7 +23,6 @@ package org.onap.cps.ncmp.api.impl import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService -import org.onap.cps.api.CpsAdminService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService import org.onap.cps.ncmp.api.impl.exception.DmiRequestException @@ -32,7 +31,6 @@ import org.onap.cps.ncmp.api.inventory.InventoryPersistence import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse import org.onap.cps.ncmp.api.models.DmiPluginRegistration import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import org.onap.cps.ncmp.api.inventory.sync.ModuleSyncService import org.onap.cps.spi.exceptions.AlreadyDefinedException import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.exceptions.DataValidationException @@ -59,11 +57,9 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def mockCpsDataService = Mock(CpsDataService) def mockCpsModuleService = Mock(CpsModuleService) def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) - def mockCpsAdminService = Mock(CpsAdminService) def mockDmiDataOperations = Mock(DmiDataOperations) def mockNetworkCmProxyDataServicePropertyHandler = Mock(NetworkCmProxyDataServicePropertyHandler) def mockInventoryPersistence = Mock(InventoryPersistence) - def mockModuleSyncService = Mock(ModuleSyncService) def stubbedNetworkCmProxyCmHandlerQueryService = Stub(NetworkCmProxyCmHandlerQueryService) def noTimestamp = null def objectUnderTest = getObjectUnderTest() @@ -164,9 +160,14 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { } and: 'save list elements is invoked with the expected parameters' interaction { - def expectedJsonData = """{"cm-handles":[{"id":"cmhandle","dmi-service-name":"my-server","state":{"cm-handle-state":"ADVISED"},"additional-properties":$expectedDmiProperties,"public-properties":$expectedPublicProperties}]}""" 1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry', - '/dmi-registry', expectedJsonData, noTimestamp) + '/dmi-registry', _, noTimestamp) >> { + args -> { + assert args[3].startsWith('{"cm-handles":[{"id":"cmhandle","dmi-service-name":"my-server","state":{"cm-handle-state":"ADVISED","last-update-time":"20') + assert args[3].contains(expectedDmiProperties) + assert args[3].contains(expectedPublicProperties) + } + } } where: scenario | dmiProperties | publicProperties || expectedDmiProperties | expectedPublicProperties @@ -347,7 +348,6 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def getObjectUnderTest() { return Spy(new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, - mockCpsModuleService, mockCpsAdminService, mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, - mockModuleSyncService, stubbedNetworkCmProxyCmHandlerQueryService)) + mockCpsModuleService, mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, stubbedNetworkCmProxyCmHandlerQueryService)) } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy index d58fe6a7c..2e333b507 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy @@ -27,16 +27,18 @@ import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.CompositeState import org.onap.cps.ncmp.api.inventory.InventoryPersistence +import org.onap.cps.ncmp.api.inventory.LockReasonCategory +import org.onap.cps.ncmp.api.inventory.DataStoreSyncState import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters import org.onap.cps.ncmp.api.models.ConditionApiProperties import org.onap.cps.ncmp.api.models.DmiPluginRegistration import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import org.onap.cps.spi.exceptions.DataValidationException -import org.onap.cps.ncmp.api.inventory.sync.ModuleSyncService import org.onap.cps.spi.model.CmHandleQueryServiceParameters -import org.onap.cps.spi.model.ConditionProperties import spock.lang.Shared +import java.util.stream.Collectors + import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE @@ -44,7 +46,6 @@ import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum import org.onap.cps.utils.JsonObjectMapper import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.api.CpsAdminService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations @@ -58,12 +59,10 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { def mockCpsDataService = Mock(CpsDataService) def mockCpsModuleService = Mock(CpsModuleService) - def mockCpsAdminService = Mock(CpsAdminService) def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) def mockDmiDataOperations = Mock(DmiDataOperations) def nullNetworkCmProxyDataServicePropertyHandler = null def mockInventoryPersistence = Mock(InventoryPersistence) - def mockModuleSyncService = Mock(ModuleSyncService) def mockDmiPluginRegistration = Mock(DmiPluginRegistration) def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandlerQueryService) @@ -75,8 +74,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id') def objectUnderTest = new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, - mockCpsModuleService, mockCpsAdminService, nullNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, - mockModuleSyncService, mockCpsCmHandlerQueryService) + mockCpsModuleService, nullNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCpsCmHandlerQueryService) def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']" @@ -169,19 +167,29 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { def 'Get a cm handle.'() { given: 'the system returns a yang modelled cm handle' def dmiServiceName = 'some service name' + def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, + lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MISBEHAVING).details("lock misbehaving details").build(), + lastUpdateTime: 'some-timestamp', + dataSyncEnabled: false, + dataStores: dataStores()) def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')] def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')] - def compositeState = new CompositeState(cmHandleState: 'ADVISED') def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle when: 'getting cm handle details for a given cm handle id from ncmp service' def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle') - then: 'the result returns the correct data' + then: 'the result is a ncmpServiceCmHandle' + result.class == NcmpServiceCmHandle.class + and: 'the cm handle contains the cm handle id' result.cmHandleId == 'some-cm-handle' + and: 'the cm handle contains the DMI Properties' result.dmiProperties ==[ Book:'Romance Novel' ] + and: 'the cm handle contains the public Properties' result.publicProperties == [ "Public Book":'Public Romance Novel' ] - result.compositeState.cmHandleState == CmHandleState.ADVISED + and: 'the cm handle contains the cm handle composite state' + result.compositeState == compositeState + } def 'Get a cm handle with an invalid id.'() { @@ -207,7 +215,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { } def 'Get cm handle public properties with an invalid id.'() { - when: 'getting cm handle details for a given cm handle id with an invalid name' + when: 'getting cm handle public properties for a given cm handle id with an invalid name' objectUnderTest.getCmHandlePublicProperties('invalid cm handle with spaces') then: 'an exception is thrown' thrown(DataValidationException) @@ -215,6 +223,33 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { 0 * mockInventoryPersistence.getYangModelCmHandle(*_) } + def 'Get cm handle composite state'() { + given: 'a yang modelled cm handle' + def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, + lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MISBEHAVING).details("lock misbehaving details").build(), + lastUpdateTime: 'some-timestamp', + dataSyncEnabled: false, + dataStores: dataStores()) + def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] + def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] + def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) + and: 'the system returns this yang modelled cm handle' + 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle + when: 'getting cm handle composite state for a given cm handle id from ncmp service' + def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle') + then: 'the result returns the correct data' + result == compositeState + } + + def 'Get cm handle composite state with an invalid id.'() { + when: 'getting cm handle composite state for a given cm handle id with an invalid name' + objectUnderTest.getCmHandleCompositeState('invalid cm handle with spaces') + then: 'an exception is thrown' + thrown(DataValidationException) + and: 'the yang model cm handle retriever is not invoked' + 0 * mockInventoryPersistence.getYangModelCmHandle(_) + } + def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() { given: 'cpsDataService returns valid datanode' mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', @@ -238,10 +273,11 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { when: 'parse and create cm handle in dmi registration then sync module' objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(mockDmiPluginRegistration) then: 'validate params for creating anchor and list elements' - 1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry', - '/dmi-registry', '{"cm-handles":[{"id":"some-cm-handle-id",' + - '"state":{"cm-handle-state":"ADVISED"},' - + '"additional-properties":[],"public-properties":[]}]}', null) + 1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, null) >> { + args -> { + assert args[3].startsWith('{"cm-handles":[{"id":"some-cm-handle-id","state":{"cm-handle-state":"ADVISED","last-update-time":"20') + } + } } def 'Execute cm handle id search'() { @@ -251,17 +287,45 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { conditionApiProperties.conditionName = 'hasAllModules' conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] - and: 'valid CmHandleQueryParameters input' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = new ConditionProperties() - conditionProperties.conditionName = 'hasAllModules' - conditionProperties.conditionParameters = [[moduleName: 'module-name-1']] - cmHandleQueryParameters.cmHandleQueryParameters = [conditionProperties] and: 'query cm handle method return with a data node list' - mockCpsCmHandlerQueryService.queryCmHandles(cmHandleQueryParameters) >> [new DataNode(leaves: [id: 'cm-handle-id-1'])] + mockCpsCmHandlerQueryService.queryCmHandleIds( + spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) + >> ['cm-handle-id-1'] when: 'execute cm handle search is called' def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters) then: 'result is the same collection as returned by the CPS Data Service' assert result == ['cm-handle-id-1'] as Set } + + def 'Getting module definitions.'() { + when: 'get module definitions method is called with a valid cm handle ID' + objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle') + then: 'CPS module services is invoked once' + 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle') + } + + + def dataStores() { + CompositeState.DataStores.builder() + .operationalDataStore(CompositeState.Operational.builder() + .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED) + .lastSyncTime('some-timestamp').build()).build() + } + + def 'Execute cm handle search'() { + given: 'valid CmHandleQueryApiParameters input' + def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() + def conditionApiProperties = new ConditionApiProperties() + conditionApiProperties.conditionName = 'hasAllModules' + conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] + cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] + and: 'query cm handle method return with a data node list' + mockCpsCmHandlerQueryService.queryCmHandles( + spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) + >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')] + when: 'execute cm handle search is called' + def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters) + then: 'result is the same collection as returned by the CPS Data Service' + assert result.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/NcmpEventsServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/NcmpEventsServiceSpec.groovy index e265fef05..52806a867 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/NcmpEventsServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/NcmpEventsServiceSpec.groovy @@ -30,17 +30,25 @@ class NcmpEventsServiceSpec extends Specification { def mockInventoryPersistence = Mock(InventoryPersistence) def mockNcmpEventsPublisher = Mock(NcmpEventsPublisher) - def mockNcmpEventsMapper = Mock(NcmpEventsCreator) + def mockNcmpEventsCreator = Mock(NcmpEventsCreator) - def objectUnderTest = new NcmpEventsService(mockInventoryPersistence, mockNcmpEventsPublisher, mockNcmpEventsMapper) + def objectUnderTest = new NcmpEventsService(mockInventoryPersistence, mockNcmpEventsPublisher, mockNcmpEventsCreator) - def 'Create and Publish event for #operation'() { + def 'Create and Publish ncmp event where events are #scenario'() { given: 'a cm handle id and operation and responses are mocked' mockResponses('test-cm-handle-id', 'test-topic') + and: 'notifications enabled is #notificationsEnabled' + objectUnderTest.notificationsEnabled = notificationsEnabled when: 'service is called to publish ncmp event' objectUnderTest.publishNcmpEvent('test-cm-handle-id') - then: 'no exception is thrown' - noExceptionThrown() + then: 'creator is called #expectedTimesMethodCalled times' + expectedTimesMethodCalled * mockNcmpEventsCreator.populateNcmpEvent('test-cm-handle-id', _) + and: 'publisher is called #expectedTimesMethodCalled times' + expectedTimesMethodCalled * mockNcmpEventsPublisher.publishEvent(*_) + where: 'the following values are used' + scenario | notificationsEnabled|| expectedTimesMethodCalled + 'enabled' | true || 1 + 'disabled' | false || 0 } def mockResponses(cmHandleId, topicName) { @@ -50,9 +58,8 @@ class NcmpEventsServiceSpec extends Specification { def ncmpServiceCmhandle = YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle(yangModelCmHandle) mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle - mockNcmpEventsMapper.populateNcmpEvent(cmHandleId, ncmpServiceCmhandle) >> ncmpEvent + mockNcmpEventsCreator.populateNcmpEvent(cmHandleId, ncmpServiceCmhandle) >> ncmpEvent mockNcmpEventsPublisher.publishEvent(topicName, cmHandleId, ncmpEvent) >> {} } - } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateBuilderSpec.groovy index 60fec6f86..867094859 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateBuilderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateBuilderSpec.groovy @@ -47,11 +47,11 @@ class CompositeStateBuilderSpec extends Specification { def "Composite State Specification"() { when: 'using composite state builder ' def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.ADVISED) - .withLockReason(LockReasonCategory.LOCKED_MISBEHAVING,"").withOperationalDataStores(SyncState.UNSYNCHRONIZED, + .withLockReason(LockReasonCategory.LOCKED_MISBEHAVING,"").withOperationalDataStores(DataStoreSyncState.UNSYNCHRONIZED, formattedDateAndTime.toString()).withLastUpdatedTime(formattedDateAndTime).build() then: 'it matches expected cm handle state and data store sync state' assert compositeState.cmHandleState == CmHandleState.ADVISED - assert compositeState.dataStores.operationalDataStore.syncState == SyncState.UNSYNCHRONIZED + assert compositeState.dataStores.operationalDataStore.dataStoreSyncState == DataStoreSyncState.UNSYNCHRONIZED } def "Build composite state from DataNode "() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy index 0a6f8c350..bf42fbfee 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CompositeStateSpec.groovy @@ -54,7 +54,7 @@ class CompositeStateSpec extends Specification { def dataStores() { DataStores.builder().operationalDataStore(Operational.builder() - .syncState(SyncState.NONE_REQUESTED) + .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED) .lastSyncTime(formattedDateAndTime.toString()).build()) .build() } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy index 8c23b302c..638af32e2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy @@ -23,11 +23,13 @@ package org.onap.cps.ncmp.api.inventory import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsDataService +import org.onap.cps.api.CpsModuleService import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.DataNode +import org.onap.cps.spi.model.ModuleDefinition import org.onap.cps.utils.JsonObjectMapper import spock.lang.Shared import spock.lang.Specification @@ -45,10 +47,11 @@ class InventoryPersistenceSpec extends Specification { def mockCpsDataService = Mock(CpsDataService) - def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService) + def mockCpsModuleService = Mock(CpsModuleService) + def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService) - def objectUnderTest = new InventoryPersistence(spiedJsonObjectMapper, mockCpsDataService, mockCpsDataPersistenceService) + def objectUnderTest = new InventoryPersistence(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService, mockCpsDataPersistenceService) def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) @@ -178,7 +181,7 @@ class InventoryPersistenceSpec extends Specification { mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin', 'ncmp-dmi-registry', '//state/datastores/operational[@sync-state="'+'UNSYNCHRONIZED'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes when: 'get cm handles by operational sync state as UNSYNCHRONIZED is invoked' - def result = objectUnderTest.getCmHandlesByOperationalSyncState(SyncState.UNSYNCHRONIZED) + def result = objectUnderTest.getCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED) then: 'the returned result is a list of data nodes returned by cps data service' assert result == sampleDataNodes } @@ -189,12 +192,22 @@ class InventoryPersistenceSpec extends Specification { def cpsPath = '//cps-path' and: 'cps data service returns a valid data node' mockCpsDataPersistenceService.queryDataNodes('NCMP-Admin', 'ncmp-dmi-registry', - cpsPath, OMIT_DESCENDANTS) + cpsPath, INCLUDE_ALL_DESCENDANTS) >> Arrays.asList(cmHandleDataNode) when: 'get cm handles by cps path is invoked' - def result = objectUnderTest.getCmHandleDataNodesByCpsPath(cpsPath) + def result = objectUnderTest.getCmHandleDataNodesByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS) then: 'the returned result is a list of data nodes returned by cps data service' assert result.contains(cmHandleDataNode) } + def 'Get module definitions'() { + given: 'cps module service returns a collection of module definitions' + def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')] + mockCpsModuleService.getModuleDefinitionsByAnchorName('NFP-Operational','some-cmHandle-Id') >> moduleDefinitions + when: 'get module definitions by cmHandle is invoked' + def result = objectUnderTest.getModuleDefinitionsByCmHandleId('some-cmHandle-Id') + then: 'the returned result are the same module definitions as returned from the module service' + assert result == moduleDefinitions + } + } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/DataSyncSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/DataSyncSpec.groovy index b062635db..20880ca3b 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/DataSyncSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/DataSyncSpec.groovy @@ -25,7 +25,7 @@ import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.CompositeState import org.onap.cps.ncmp.api.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.inventory.SyncState +import org.onap.cps.ncmp.api.inventory.DataStoreSyncState import spock.lang.Specification class DataSyncSpec extends Specification { @@ -94,7 +94,7 @@ class DataSyncSpec extends Specification { def cmHandleState = CmHandleState.READY def compositeState = new CompositeState(cmHandleState: cmHandleState) compositeState.setDataStores(CompositeState.DataStores.builder() - .operationalDataStore(CompositeState.Operational.builder().syncState(SyncState.SYNCHRONIZED) + .operationalDataStore(CompositeState.Operational.builder().dataStoreSyncState(DataStoreSyncState.SYNCHRONIZED) .build()).build()) return compositeState } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncServiceSpec.groovy index 8050a571a..6a2fbe8e7 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncServiceSpec.groovy @@ -1,5 +1,5 @@ /* - * ============LICENSE_START======================================================= + * ============LICENSE_START======================================================= * Copyright (C) 2022 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,10 @@ import org.onap.cps.api.CpsModuleService import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.inventory.CmHandleState +import org.onap.cps.ncmp.api.inventory.CompositeState import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.spi.CascadeDeleteAllowed +import org.onap.cps.spi.exceptions.SchemaSetNotFoundException import org.onap.cps.spi.model.ModuleReference import spock.lang.Specification @@ -45,7 +48,7 @@ class ModuleSyncServiceSpec extends Specification { def ncmpServiceCmHandle = new NcmpServiceCmHandle() def dmiServiceName = 'some service name' ncmpServiceCmHandle.cmHandleId = 'cmHandleId-1' - def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '' , '', CmHandleState.ADVISED, ncmpServiceCmHandle) + def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '', '', ncmpServiceCmHandle) and: 'DMI operations returns some module references' def moduleReferences = [ new ModuleReference(moduleName:'module1',revision:'1'), new ModuleReference(moduleName:'module2',revision:'2') ] @@ -68,6 +71,44 @@ class ModuleSyncServiceSpec extends Specification { 'no new module' | [['module1' : '1'], ['module2' : '2']] | [] | [:] | [new ModuleReference(moduleName:'module1',revision:'1'), new ModuleReference(moduleName:'module2',revision:'2')] } + def 'Delete Schema Set for CmHandle' () { + given: 'a CmHandle in the advised state' + def cmHandle = new YangModelCmHandle(id: 'some-cmhandle-id', compositeState: new CompositeState(cmHandleState: CmHandleState.ADVISED)) + and: 'the Schema Set exists for the CmHandle' + 1 * mockCpsModuleService.deleteSchemaSet(_ as String, 'some-cmhandle-id', + CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED) + when: 'delete schema set if exists is called' + objectUnderTest.deleteSchemaSetIfExists(cmHandle) + then: 'there are no exceptions' + noExceptionThrown() + } + + def 'Delete a non-existing Schema Set for CmHandle' () { + given: 'a CmHandle in the advised state' + def cmHandle = new YangModelCmHandle(id: 'some-cmhandle-id', compositeState: new CompositeState(cmHandleState: CmHandleState.ADVISED)) + and: 'the DB throws an exception because its Schema Set does not exist' + 1 * mockCpsModuleService.deleteSchemaSet(_ as String, 'some-cmhandle-id', + CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED) >> { throw new SchemaSetNotFoundException('some-dataspace-name', 'some-cmhandle-id') } + when: 'delete schema set if exists is called' + objectUnderTest.deleteSchemaSetIfExists(cmHandle) + then: 'there are no exceptions' + noExceptionThrown() + } + + def 'Delete Schema Set for CmHandle with other exception' () { + given: 'a CmHandle in the advised state' + def cmHandle = new YangModelCmHandle(id: 'some-cmhandle-id', compositeState: new CompositeState(cmHandleState: CmHandleState.ADVISED)) + and: 'an exception other than SchemaSetNotFoundException is thrown' + UnsupportedOperationException unsupportedOperationException = new UnsupportedOperationException(); + 1 * mockCpsModuleService.deleteSchemaSet(_ as String, 'some-cmhandle-id', + CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED) >> { throw unsupportedOperationException } + when: 'delete schema set if exists is called' + objectUnderTest.deleteSchemaSetIfExists(cmHandle) + then: 'an exception is thrown' + def result = thrown(UnsupportedOperationException) + result == unsupportedOperationException + } + def toModuleReference(moduleReferenceAsMap) { def moduleReferences = [].withDefault { [:] } moduleReferenceAsMap.forEach(property -> diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy index 614783efd..b7eb133bf 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy @@ -29,7 +29,7 @@ import org.onap.cps.ncmp.api.inventory.LockReasonCategory import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder import spock.lang.Specification -class ModuleSyncSpec extends Specification { +class ModuleSyncWatchdogSpec extends Specification { def mockInventoryPersistence = Mock(InventoryPersistence) @@ -53,6 +53,8 @@ class ModuleSyncSpec extends Specification { objectUnderTest.executeAdvisedCmHandlePoll() then: 'the inventory persistence cm handle returns a composite state for the first cm handle' 1 * mockInventoryPersistence.getCmHandleState('some-cm-handle') >> compositeState1 + and: 'module sync service deletes schema set of cm handle if it exists' + 1 * mockModuleSyncService.deleteSchemaSetIfExists(yangModelCmHandle1) and: 'module sync service syncs the first cm handle and creates a schema set' 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle1) and: 'the composite state cm handle state is now READY' @@ -90,16 +92,25 @@ class ModuleSyncSpec extends Specification { } - def 'Schedule a Cm-Handle Sync for LOCKED with reason LOCKED_MISBEHAVING Cm-Handles '() { + def 'Schedule a Cm-Handle Sync for LOCKED with reason LOCKED_MISBEHAVING Cm-Handles with #scenario'() { given: 'cm handles in an locked state' def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED) - .withLockReason(LockReasonCategory.LOCKED_MISBEHAVING, '').build() + .withLockReason(LockReasonCategory.LOCKED_MISBEHAVING, '').withLastUpdatedTimeNow().build() def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', compositeState: compositeState) and: 'sync utilities return a cm handle twice' mockSyncUtils.getLockedMisbehavingYangModelCmHandles() >> [yangModelCmHandle, yangModelCmHandle] + and: 'inventory persistence returns the composite state of the cm handle' + mockInventoryPersistence.getCmHandleState(yangModelCmHandle.getId()) >> compositeState + and: 'sync utils retry locked cm handle returns #isReadyForRetry' + mockSyncUtils.isReadyForRetry(compositeState) >>> isReadyForRetry when: 'module sync poll is executed' - objectUnderTest.executeLockedMisbehavingCmHandlePoll() + objectUnderTest.executeLockedCmHandlePoll() then: 'the first cm handle is updated to state "ADVISED" from "READY"' - 2 * mockInventoryPersistence.saveCmHandleState(yangModelCmHandle.id, compositeState) + expectedNumberOfInvocationsToSaveCmHandleState * mockInventoryPersistence.saveCmHandleState(yangModelCmHandle.id, compositeState) + where: + scenario | isReadyForRetry || expectedNumberOfInvocationsToSaveCmHandleState + 'retry locked cm handle once' | [true, false] || 1 + 'retry locked cm handle twice' | [true, true] || 2 + 'do not retry locked cm handle' | [false, false] || 0 } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy index 14f201575..051172ca2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy @@ -27,9 +27,11 @@ import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations import org.onap.cps.ncmp.api.impl.operations.DmiOperations import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.CompositeState +import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder +import org.onap.cps.ncmp.api.inventory.DataStoreSyncState import org.onap.cps.ncmp.api.inventory.InventoryPersistence import org.onap.cps.ncmp.api.inventory.LockReasonCategory -import org.onap.cps.ncmp.api.inventory.SyncState +import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.DataNode import org.onap.cps.utils.JsonObjectMapper import org.springframework.http.HttpStatus @@ -37,6 +39,9 @@ import org.springframework.http.ResponseEntity import spock.lang.Shared import spock.lang.Specification +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter + class SyncUtilsSpec extends Specification{ def mockInventoryPersistence = Mock(InventoryPersistence) @@ -48,6 +53,9 @@ class SyncUtilsSpec extends Specification{ def objectUnderTest = new SyncUtils(mockInventoryPersistence, mockDmiDataOperations, jsonObjectMapper) @Shared + def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.now()) + + @Shared def dataNode = new DataNode(leaves: ['id': 'cm-handle-123']) def 'Get an advised Cm-Handle where ADVISED cm handle #scenario'() { @@ -83,7 +91,8 @@ class SyncUtilsSpec extends Specification{ def 'Get all locked Cm-Handle where Lock Reason is LOCKED_MISBEHAVING cm handle #scenario'() { given: 'the cps (persistence service) returns a collection of data nodes' mockInventoryPersistence.getCmHandleDataNodesByCpsPath( - '//lock-reason[@reason="LOCKED_MISBEHAVING"]/ancestor::cm-handles') >> [dataNode ] + '//lock-reason[@reason="LOCKED_MISBEHAVING"]/ancestor::cm-handles', + FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode ] when: 'get locked Misbehaving cm handle is called' def result = objectUnderTest.getLockedMisbehavingYangModelCmHandles() then: 'the returned cm handle collection is the correct size' @@ -92,9 +101,24 @@ class SyncUtilsSpec extends Specification{ result[0].id == 'cm-handle-123' } + def 'Retry Locked Cm-Handle where the last update time is #scenario'() { + when: 'retry locked cm handle is invoked' + def result = objectUnderTest.isReadyForRetry(new CompositeStateBuilder() + .withLockReason(LockReasonCategory.LOCKED_MISBEHAVING, details) + .withLastUpdatedTime(lastUpdateTime).build()) + then: 'result returns #expectedResult' + result == expectedResult + where: + scenario | lastUpdateTime | details || expectedResult + 'is the first attempt' | '1900-01-01T00:00:00.000+0100' | 'First Attempt' || true + 'is greater than one minute' | '1900-01-01T00:00:00.000+0100' | 'Attempt #1 failed:' || true + 'is less than eight minutes' | formattedDateAndTime | 'Attempt #3 failed:' || false + } + + def 'Get a Cm-Handle where Operational Sync state is UnSynchronized and Cm-handle state is READY and #scenario'() { given: 'the inventory persistence service returns a collection of data nodes' - mockInventoryPersistence.getCmHandlesByOperationalSyncState(SyncState.UNSYNCHRONIZED) >> unSynchronizedDataNodes + mockInventoryPersistence.getCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED) >> unSynchronizedDataNodes mockInventoryPersistence.getCmHandlesByIdAndState("cm-handle-123", CmHandleState.READY) >> readyDataNodes when: 'get advised cm handle is called' objectUnderTest.getAnUnSynchronizedReadyCmHandle() diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy index 3376691ee..5903b1211 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangModelCmHandleSpec.groovy @@ -1,5 +1,5 @@ /* - * ============LICENSE_START======================================================= + * ============LICENSE_START======================================================= * Copyright (C) 2021-2022 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,9 @@ package org.onap.cps.ncmp.api.models import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.inventory.CmHandleState +import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder +import org.onap.cps.ncmp.api.inventory.LockReasonCategory +import org.onap.cps.ncmp.api.inventory.DataStoreSyncState import spock.lang.Specification import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA @@ -35,8 +38,15 @@ class YangModelCmHandleSpec extends Specification { ncmpServiceCmHandle.cmHandleId = 'cm-handle-id01' ncmpServiceCmHandle.dmiProperties = [myDmiProperty:'value1'] ncmpServiceCmHandle.publicProperties = [myPublicProperty:'value2'] + and: 'with a composite state' + def compositeState = new CompositeStateBuilder() + .withCmHandleState(CmHandleState.LOCKED) + .withLastUpdatedTime('some-update-time') + .withLockReason(LockReasonCategory.LOCKED_MISBEHAVING, 'locked other details') + .withOperationalDataStores(DataStoreSyncState.SYNCHRONIZED, 'some-sync-time').build() + ncmpServiceCmHandle.setCompositeState(compositeState) when: 'it is converted to a yang model cm handle' - def objectUnderTest = YangModelCmHandle.toYangModelCmHandle('','','', CmHandleState.ADVISED, ncmpServiceCmHandle) + def objectUnderTest = YangModelCmHandle.toYangModelCmHandle('', '', '', ncmpServiceCmHandle) then: 'the result has the right size' assert objectUnderTest.dmiProperties.size() == 1 and: 'the DMI property in the result has the correct name and value' @@ -45,11 +55,14 @@ class YangModelCmHandleSpec extends Specification { and: 'the public property in the result has the correct name and value' assert objectUnderTest.publicProperties[0].name == 'myPublicProperty' assert objectUnderTest.publicProperties[0].value == 'value2' + and: 'the composite state matches the composite state of the ncmpServiceCmHandle' + objectUnderTest.getCompositeState().cmHandleState == CmHandleState.LOCKED + objectUnderTest.getCompositeState() == ncmpServiceCmHandle.getCompositeState() } def 'Resolve DMI service name: #scenario and #requiredService service require.'() { given: 'a yang model cm handle' - def objectUnderTest = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, dmiDataServiceName, dmiModelServiceName, CmHandleState.ADVISED, new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')) + def objectUnderTest = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, dmiDataServiceName, dmiModelServiceName, new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')) expect: assert objectUnderTest.resolveDmiServiceName(requiredService) == expectedService where: diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy index b3ea3b870..2d993973e 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy @@ -20,7 +20,6 @@ package org.onap.cps.ncmp.api.utils -import org.onap.cps.ncmp.api.inventory.CmHandleState import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING @@ -35,7 +34,7 @@ class DmiServiceUrlBuilderSpec extends Specification { @Shared YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('dmiServiceName', - 'dmiDataServiceName', 'dmiModuleServiceName', CmHandleState.ADVISED , new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id')) + 'dmiDataServiceName', 'dmiModuleServiceName', new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id')) NcmpConfiguration.DmiProperties dmiProperties = new NcmpConfiguration.DmiProperties() |