diff options
44 files changed, 1016 insertions, 683 deletions
diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy index b998a16b2d..be7f449326 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy @@ -32,7 +32,7 @@ import groovy.json.JsonSlurper import org.mapstruct.factory.Mappers import org.onap.cps.TestUtils import org.onap.cps.events.EventsPublisher -import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl import org.onap.cps.ncmp.api.inventory.models.CompositeState import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.api.inventory.models.TrustLevel @@ -91,7 +91,7 @@ class NetworkCmProxyControllerSpec extends Specification { NetworkCmProxyFacade mockNetworkCmProxyFacade = Mock() @SpringBean - NetworkCmProxyInventoryFacade mockNetworkCmProxyInventoryFacade = Mock() + NetworkCmProxyInventoryFacadeImpl mockNetworkCmProxyInventoryFacade = Mock() @SpringBean AlternateIdMatcher mockAlternateIdMatcher = Mock() diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy index 59307640ef..9d79922478 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy @@ -23,7 +23,7 @@ package org.onap.cps.ncmp.rest.controller import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.TestUtils -import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration @@ -55,7 +55,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { MockMvc mvc @SpringBean - NetworkCmProxyInventoryFacade mockNetworkCmProxyInventoryFacade = Mock() + NetworkCmProxyInventoryFacadeImpl mockNetworkCmProxyInventoryFacade = Mock() @SpringBean NcmpRestInputMapper ncmpRestInputMapper = Mock() diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy index 26060a02d2..aad04a18ae 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy @@ -31,7 +31,7 @@ import org.onap.cps.ncmp.api.exceptions.DmiRequestException import org.onap.cps.ncmp.api.exceptions.PayloadTooLargeException import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException import org.onap.cps.ncmp.api.exceptions.ServerNcmpException -import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl import org.onap.cps.ncmp.impl.data.NcmpCachedResourceRequestHandler import org.onap.cps.ncmp.impl.data.NcmpPassthroughResourceRequestHandler import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade @@ -76,7 +76,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { NetworkCmProxyFacade mockNetworkCmProxyFacade = Mock() @SpringBean - NetworkCmProxyInventoryFacade mockNetworkCmProxyInventoryFacade = Mock() + NetworkCmProxyInventoryFacadeImpl mockNetworkCmProxyInventoryFacade = Mock() @SpringBean InventoryPersistence mockInventoryPersistence = Mock() diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java index 64bec06fec..9bfb775d55 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java @@ -1,10 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021 highstreet technologies GmbH - * Modifications Copyright (C) 2021-2024 Nordix Foundation - * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2021-2022 Bell Canada - * Modifications Copyright (C) 2023 TechMahindra Ltd. + * Copyright (C) 2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,47 +20,18 @@ package org.onap.cps.ncmp.api.inventory; -import static org.onap.cps.ncmp.impl.inventory.CmHandleQueryParametersValidator.validateCmHandleQueryParameters; - import java.util.Collection; -import java.util.Collections; import java.util.Map; -import lombok.RequiredArgsConstructor; import org.onap.cps.api.model.ModuleDefinition; import org.onap.cps.api.model.ModuleReference; -import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException; import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; import org.onap.cps.ncmp.api.inventory.models.CompositeState; import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration; import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistrationResponse; import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; -import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService; -import org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationService; -import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; -import org.onap.cps.ncmp.impl.inventory.ParameterizedCmHandleQueryService; -import org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions; -import org.onap.cps.ncmp.impl.inventory.models.InventoryQueryConditions; -import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; -import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager; -import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher; -import org.onap.cps.ncmp.impl.utils.YangDataConverter; -import org.onap.cps.utils.JsonObjectMapper; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class NetworkCmProxyInventoryFacade { - - private final CmHandleRegistrationService cmHandleRegistrationService; - private final CmHandleQueryService cmHandleQueryService; - private final ParameterizedCmHandleQueryService parameterizedCmHandleQueryService; - private final InventoryPersistence inventoryPersistence; - private final JsonObjectMapper jsonObjectMapper; - private final TrustLevelManager trustLevelManager; - private final AlternateIdMatcher alternateIdMatcher; - +public interface NetworkCmProxyInventoryFacade { /** * Registration of Created, Removed, Updated or Upgraded CM Handles. @@ -72,9 +39,7 @@ public class NetworkCmProxyInventoryFacade { * @param dmiPluginRegistration Dmi Plugin Registration details * @return dmiPluginRegistrationResponse */ - public DmiPluginRegistrationResponse updateDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) { - return cmHandleRegistrationService.updateDmiRegistration(dmiPluginRegistration); - } + DmiPluginRegistrationResponse updateDmiRegistration(final DmiPluginRegistration dmiPluginRegistration); /** * Get all cm handle references by DMI plugin identifier. @@ -84,10 +49,8 @@ public class NetworkCmProxyInventoryFacade { * cm handle id (false) or alternate id (true) * @return collection of cm handle references */ - public Collection<String> getAllCmHandleReferencesByDmiPluginIdentifier(final String dmiPluginIdentifier, - final boolean outputAlternateId) { - return cmHandleQueryService.getCmHandleReferencesByDmiPluginIdentifier(dmiPluginIdentifier, outputAlternateId); - } + Collection<String> getAllCmHandleReferencesByDmiPluginIdentifier(final String dmiPluginIdentifier, + final boolean outputAlternateId); /** * Get all cm handle IDs by various properties. @@ -97,14 +60,9 @@ public class NetworkCmProxyInventoryFacade { * cm handle id (false) or alternate id (true) * @return collection of cm handle references */ - public Collection<String> executeParameterizedCmHandleIdSearch( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final boolean outputAlternateId) { - validateCmHandleQueryParameters(cmHandleQueryServiceParameters, InventoryQueryConditions.ALL_CONDITION_NAMES); - - return parameterizedCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters, - outputAlternateId); - } - + Collection<String> executeParameterizedCmHandleIdSearch(final CmHandleQueryServiceParameters + cmHandleQueryServiceParameters, + final boolean outputAlternateId); /** * Retrieve module references for the given cm handle reference. @@ -112,14 +70,7 @@ public class NetworkCmProxyInventoryFacade { * @param cmHandleReference cm handle or alternate id identifier * @return a collection of modules names and revisions */ - public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleReference) { - try { - final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); - return inventoryPersistence.getYangResourcesModuleReferences(cmHandleId); - } catch (final CmHandleNotFoundException cmHandleNotFoundException) { - return Collections.emptyList(); - } - } + Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleReference); /** * Retrieve module definitions for the given cm handle. @@ -127,14 +78,7 @@ public class NetworkCmProxyInventoryFacade { * @param cmHandleReference cm handle or alternate id identifier * @return a collection of module definition (moduleName, revision and yang resource content) */ - public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleReference(final String cmHandleReference) { - try { - final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); - return inventoryPersistence.getModuleDefinitionsByCmHandleId(cmHandleId); - } catch (final CmHandleNotFoundException cmHandleNotFoundException) { - return Collections.emptyList(); - } - } + Collection<ModuleDefinition> getModuleDefinitionsByCmHandleReference(final String cmHandleReference); /** * Get module definitions for the given parameters. @@ -144,16 +88,9 @@ public class NetworkCmProxyInventoryFacade { * @param moduleRevision the revision of the module * @return list of module definitions (module name, revision, yang resource content) */ - public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(final String cmHandleReference, - final String moduleName, - final String moduleRevision) { - try { - final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); - return inventoryPersistence.getModuleDefinitionsByCmHandleAndModule(cmHandleId, moduleName, moduleRevision); - } catch (final CmHandleNotFoundException cmHandleNotFoundException) { - return Collections.emptyList(); - } - } + Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(final String cmHandleReference, + final String moduleName, + final String moduleRevision); /** * Retrieve cm handles with details for the given query parameters. @@ -161,16 +98,7 @@ public class NetworkCmProxyInventoryFacade { * @param cmHandleQueryApiParameters cm handle query parameters * @return cm handles with details */ - public Collection<NcmpServiceCmHandle> executeCmHandleSearch( - final CmHandleQueryApiParameters cmHandleQueryApiParameters) { - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( - cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); - validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES); - final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles = - parameterizedCmHandleQueryService.queryCmHandles(cmHandleQueryServiceParameters); - trustLevelManager.applyEffectiveTrustLevels(ncmpServiceCmHandles); - return ncmpServiceCmHandles; - } + Collection<NcmpServiceCmHandle> executeCmHandleSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters); /** * Retrieve cm handle ids for the given query parameters. @@ -179,14 +107,8 @@ public class NetworkCmProxyInventoryFacade { * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) * @return cm handle ids */ - public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters, - final boolean outputAlternateId) { - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( - cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); - validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES); - return parameterizedCmHandleQueryService.queryCmHandleReferenceIds(cmHandleQueryServiceParameters, - outputAlternateId); - } + Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters, + final boolean outputAlternateId); /** * Set the data sync enabled flag, along with the data sync state @@ -195,9 +117,7 @@ public class NetworkCmProxyInventoryFacade { * @param cmHandleId cm handle id * @param dataSyncEnabledTargetValue data sync enabled flag */ - public void setDataSyncEnabled(final String cmHandleId, final Boolean dataSyncEnabledTargetValue) { - cmHandleRegistrationService.setDataSyncEnabled(cmHandleId, dataSyncEnabledTargetValue); - } + void setDataSyncEnabled(final String cmHandleId, final Boolean dataSyncEnabledTargetValue); /** * Retrieve cm handle details for a given cm handle reference. @@ -205,13 +125,7 @@ public class NetworkCmProxyInventoryFacade { * @param cmHandleReference cm handle or alternate identifier * @return cm handle details */ - public NcmpServiceCmHandle getNcmpServiceCmHandle(final String cmHandleReference) { - final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); - final NcmpServiceCmHandle ncmpServiceCmHandle = YangDataConverter.toNcmpServiceCmHandle( - inventoryPersistence.getYangModelCmHandle(cmHandleId)); - trustLevelManager.applyEffectiveTrustLevel(ncmpServiceCmHandle); - return ncmpServiceCmHandle; - } + NcmpServiceCmHandle getNcmpServiceCmHandle(final String cmHandleReference); /** * Get cm handle public properties for a given cm handle or alternate id. @@ -219,11 +133,7 @@ public class NetworkCmProxyInventoryFacade { * @param cmHandleReference cm handle or alternate identifier * @return cm handle public properties */ - public Map<String, String> getCmHandlePublicProperties(final String cmHandleReference) { - final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); - final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId); - return YangDataConverter.toPropertiesMap(yangModelCmHandle.getPublicProperties()); - } + Map<String, String> getCmHandlePublicProperties(final String cmHandleReference); /** * Get cm handle composite state for a given cm handle id. @@ -231,9 +141,5 @@ public class NetworkCmProxyInventoryFacade { * @param cmHandleReference cm handle or alternate identifier * @return cm handle state */ - public CompositeState getCmHandleCompositeState(final String cmHandleReference) { - final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); - return inventoryPersistence.getYangModelCmHandle(cmHandleId).getCompositeState(); - } - + CompositeState getCmHandleCompositeState(final String cmHandleReference); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/NetworkCmProxyInventoryFacadeImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/NetworkCmProxyInventoryFacadeImpl.java new file mode 100644 index 0000000000..118c2bba70 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/NetworkCmProxyInventoryFacadeImpl.java @@ -0,0 +1,169 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 highstreet technologies GmbH + * Modifications Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2021-2022 Bell Canada + * Modifications Copyright (C) 2023 TechMahindra Ltd. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl; + +import static org.onap.cps.ncmp.impl.inventory.CmHandleQueryParametersValidator.validateCmHandleQueryParameters; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.onap.cps.api.model.ModuleDefinition; +import org.onap.cps.api.model.ModuleReference; +import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException; +import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade; +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters; +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; +import org.onap.cps.ncmp.api.inventory.models.CompositeState; +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration; +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistrationResponse; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService; +import org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationService; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.impl.inventory.ParameterizedCmHandleQueryService; +import org.onap.cps.ncmp.impl.inventory.models.CmHandleQueryConditions; +import org.onap.cps.ncmp.impl.inventory.models.InventoryQueryConditions; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager; +import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher; +import org.onap.cps.ncmp.impl.utils.YangDataConverter; +import org.onap.cps.utils.JsonObjectMapper; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class NetworkCmProxyInventoryFacadeImpl implements NetworkCmProxyInventoryFacade { + + private final CmHandleRegistrationService cmHandleRegistrationService; + private final CmHandleQueryService cmHandleQueryService; + private final ParameterizedCmHandleQueryService parameterizedCmHandleQueryService; + private final InventoryPersistence inventoryPersistence; + private final JsonObjectMapper jsonObjectMapper; + private final TrustLevelManager trustLevelManager; + private final AlternateIdMatcher alternateIdMatcher; + + @Override + public DmiPluginRegistrationResponse updateDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) { + return cmHandleRegistrationService.updateDmiRegistration(dmiPluginRegistration); + } + + @Override + public Collection<String> getAllCmHandleReferencesByDmiPluginIdentifier(final String dmiPluginIdentifier, + final boolean outputAlternateId) { + return cmHandleQueryService.getCmHandleReferencesByDmiPluginIdentifier(dmiPluginIdentifier, outputAlternateId); + } + + @Override + public Collection<String> executeParameterizedCmHandleIdSearch( + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final boolean outputAlternateId) { + validateCmHandleQueryParameters(cmHandleQueryServiceParameters, InventoryQueryConditions.ALL_CONDITION_NAMES); + + return parameterizedCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters, + outputAlternateId); + } + + @Override + public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleReference) { + try { + final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); + return inventoryPersistence.getYangResourcesModuleReferences(cmHandleId); + } catch (final CmHandleNotFoundException cmHandleNotFoundException) { + return Collections.emptyList(); + } + } + + @Override + public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleReference(final String cmHandleReference) { + try { + final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); + return inventoryPersistence.getModuleDefinitionsByCmHandleId(cmHandleId); + } catch (final CmHandleNotFoundException cmHandleNotFoundException) { + return Collections.emptyList(); + } + } + + @Override + public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(final String cmHandleReference, + final String moduleName, + final String moduleRevision) { + try { + final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); + return inventoryPersistence.getModuleDefinitionsByCmHandleAndModule(cmHandleId, moduleName, moduleRevision); + } catch (final CmHandleNotFoundException cmHandleNotFoundException) { + return Collections.emptyList(); + } + } + + @Override + public Collection<NcmpServiceCmHandle> executeCmHandleSearch( + final CmHandleQueryApiParameters cmHandleQueryApiParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( + cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); + validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES); + final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles = + parameterizedCmHandleQueryService.queryCmHandles(cmHandleQueryServiceParameters); + trustLevelManager.applyEffectiveTrustLevels(ncmpServiceCmHandles); + return ncmpServiceCmHandles; + } + + @Override + public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters, + final boolean outputAlternateId) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( + cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); + validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES); + return parameterizedCmHandleQueryService.queryCmHandleReferenceIds(cmHandleQueryServiceParameters, + outputAlternateId); + } + + @Override + public void setDataSyncEnabled(final String cmHandleId, final Boolean dataSyncEnabledTargetValue) { + cmHandleRegistrationService.setDataSyncEnabled(cmHandleId, dataSyncEnabledTargetValue); + } + + @Override + public NcmpServiceCmHandle getNcmpServiceCmHandle(final String cmHandleReference) { + final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); + final NcmpServiceCmHandle ncmpServiceCmHandle = YangDataConverter.toNcmpServiceCmHandle( + inventoryPersistence.getYangModelCmHandle(cmHandleId)); + trustLevelManager.applyEffectiveTrustLevel(ncmpServiceCmHandle); + return ncmpServiceCmHandle; + } + + @Override + public Map<String, String> getCmHandlePublicProperties(final String cmHandleReference) { + final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); + final YangModelCmHandle yangModelCmHandle = inventoryPersistence.getYangModelCmHandle(cmHandleId); + return YangDataConverter.toPropertiesMap(yangModelCmHandle.getPublicProperties()); + } + + @Override + public CompositeState getCmHandleCompositeState(final String cmHandleReference) { + final String cmHandleId = alternateIdMatcher.getCmHandleId(cmHandleReference); + return inventoryPersistence.getYangModelCmHandle(cmHandleId).getCompositeState(); + } + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/CpsAndNcmpLockConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/CpsAndNcmpLockConfig.java new file mode 100644 index 0000000000..61cf939a51 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/CpsAndNcmpLockConfig.java @@ -0,0 +1,48 @@ +/* + * ============LICENSE_START======================================================== + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.cache; + +import com.hazelcast.config.MapConfig; +import com.hazelcast.map.IMap; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CpsAndNcmpLockConfig extends HazelcastCacheConfig { + + // Lock names for different use cases ( to be used as cpsAndNcmpLock keys) + public static final String MODULE_SYNC_WORK_QUEUE_LOCK_NAME = "workQueueLock"; + + private static final MapConfig cpsAndNcmpLockMapConfig = createMapConfig("cpsAndNcmpLockConfig"); + + /** + * Distributed instance used for locking purpose for various use cases in cps-and-ncmp. + * The key of the map entry is name of the lock and should be based on the use case we are locking. + * + * @return configured map of lock object to have distributed coordination. + */ + @Bean + public IMap<String, String> cpsAndNcmpLock() { + return getOrCreateHazelcastInstance(cpsAndNcmpLockMapConfig).getMap("cpsAndNcmpLock"); + } + + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfig.java index 770dde1c08..75007e2e35 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfig.java @@ -23,6 +23,7 @@ package org.onap.cps.ncmp.impl.cache; import com.hazelcast.config.Config; import com.hazelcast.config.MapConfig; import com.hazelcast.config.NamedConfig; +import com.hazelcast.config.NearCacheConfig; import com.hazelcast.config.QueueConfig; import com.hazelcast.config.RestEndpointGroup; import com.hazelcast.config.SetConfig; @@ -94,6 +95,21 @@ public class HazelcastCacheConfig { return mapConfig; } + protected static MapConfig createMapConfigWithTimeToLiveInSeconds(final String configName, + final int timeToLiveInSeconds) { + final MapConfig mapConfig = new MapConfig(configName); + mapConfig.setBackupCount(1); + mapConfig.setTimeToLiveSeconds(timeToLiveInSeconds); + return mapConfig; + } + + protected static MapConfig createNearCacheMapConfig(final String configName) { + final MapConfig mapConfig = new MapConfig(configName); + mapConfig.setBackupCount(1); + mapConfig.setNearCacheConfig(new NearCacheConfig(configName)); + return mapConfig; + } + protected static QueueConfig createQueueConfig(final String configName) { final QueueConfig commonQueueConfig = new QueueConfig(configName); commonQueueConfig.setBackupCount(1); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionComparator.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionComparator.java index d7f15a2c72..99c5695850 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionComparator.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cmnotificationsubscription/ncmp/CmSubscriptionComparator.java @@ -46,24 +46,20 @@ public class CmSubscriptionComparator { final List<DmiCmSubscriptionPredicate> existingDmiCmSubscriptionPredicates) { final List<DmiCmSubscriptionPredicate> newDmiCmSubscriptionPredicates = new ArrayList<>(); - for (final DmiCmSubscriptionPredicate dmiCmSubscriptionPredicate : existingDmiCmSubscriptionPredicates) { - final Set<String> targetCmHandleIds = new HashSet<>(); final Set<String> xpaths = new HashSet<>(); final DatastoreType datastoreType = dmiCmSubscriptionPredicate.getDatastoreType(); - for (final String cmHandleId : dmiCmSubscriptionPredicate.getTargetCmHandleIds()) { for (final String xpath : dmiCmSubscriptionPredicate.getXpaths()) { if (!cmSubscriptionPersistenceService.isOngoingCmSubscription(datastoreType, cmHandleId, xpath)) { - xpaths.add(xpath); targetCmHandleIds.add(cmHandleId); + xpaths.add(xpath); } } } - populateValidDmiSubscriptionPredicates(targetCmHandleIds, xpaths, datastoreType, newDmiCmSubscriptionPredicates); } @@ -73,7 +69,7 @@ public class CmSubscriptionComparator { private void populateValidDmiSubscriptionPredicates(final Set<String> targetCmHandleIds, final Set<String> xpaths, final DatastoreType datastoreType, final List<DmiCmSubscriptionPredicate> dmiCmSubscriptionPredicates) { - if (!(targetCmHandleIds.isEmpty() || xpaths.isEmpty())) { + if (!targetCmHandleIds.isEmpty()) { final DmiCmSubscriptionPredicate dmiCmSubscriptionPredicate = new DmiCmSubscriptionPredicate(targetCmHandleIds, datastoreType, xpaths); dmiCmSubscriptionPredicates.add(dmiCmSubscriptionPredicate); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java index af4331893d..38105329d1 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutor.java @@ -26,9 +26,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.net.UnknownHostException; import java.time.Duration; import java.time.temporal.ChronoUnit; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.concurrent.TimeoutException; import lombok.RequiredArgsConstructor; @@ -68,6 +68,10 @@ public class PolicyExecutor { @Value("${ncmp.policy-executor.httpclient.all-services.readTimeoutInSeconds:30}") private long readTimeoutInSeconds; + private static final String CHANGE_REQUEST_FORMAT = "cm-legacy"; + private static final String PERMISSION_BASE_PATH = "operation-permission"; + private static final String REQUEST_PATH = "permissions"; + @Qualifier("policyExecutorWebClient") private final WebClient policyExecutorWebClient; @@ -110,38 +114,33 @@ public class PolicyExecutor { } } - private Map<String, Object> getSingleRequestAsMap(final YangModelCmHandle yangModelCmHandle, - final OperationType operationType, - final String resourceIdentifier, - final String changeRequestAsJson) { - final Map<String, Object> data = new HashMap<>(4); - data.put("cmHandleId", yangModelCmHandle.getId()); - data.put("resourceIdentifier", resourceIdentifier); - data.put("targetIdentifier", yangModelCmHandle.getAlternateId()); + private Map<String, Object> getSingleOperationAsMap(final YangModelCmHandle yangModelCmHandle, + final OperationType operationType, + final String resourceIdentifier, + final String changeRequestAsJson) { + final Map<String, Object> operationAsMap = new HashMap<>(5); + operationAsMap.put("operation", operationType.getOperationName()); + operationAsMap.put("entityHandleId", yangModelCmHandle.getId()); + operationAsMap.put("resourceIdentifier", resourceIdentifier); + operationAsMap.put("targetIdentifier", yangModelCmHandle.getAlternateId()); if (!OperationType.DELETE.equals(operationType)) { try { final Object changeRequestAsObject = objectMapper.readValue(changeRequestAsJson, Object.class); - data.put("cmChangeRequest", changeRequestAsObject); + operationAsMap.put("changeRequest", changeRequestAsObject); } catch (final JsonProcessingException e) { throw new NcmpException("Cannot convert Change Request data to Object", "Invalid Json: " + changeRequestAsJson); } } - final Map<String, Object> request = new HashMap<>(2); - request.put("schema", getAssociatedPolicyDataSchemaName(operationType)); - request.put("data", data); - return request; - } - - private static String getAssociatedPolicyDataSchemaName(final OperationType operationType) { - return "urn:cps:org.onap.cps.ncmp.policy-executor.ncmp-" + operationType.getOperationName() + "-schema:1.0.0"; + return operationAsMap; } - private Object createBodyAsObject(final List<Object> requests) { - final Map<String, Object> bodyAsMap = new HashMap<>(2); - bodyAsMap.put("decisionType", "allow"); - bodyAsMap.put("requests", requests); - return bodyAsMap; + private Object createBodyAsObject(final Map<String, Object> operationAsMap) { + final Collection<Map<String, Object>> operations = Collections.singletonList(operationAsMap); + final Map<String, Object> permissionRequestAsMap = new HashMap<>(2); + permissionRequestAsMap.put("changeRequestFormat", CHANGE_REQUEST_FORMAT); + permissionRequestAsMap.put("operations", operations); + return permissionRequestAsMap; } private ResponseEntity<JsonNode> getPolicyExecutorResponse(final YangModelCmHandle yangModelCmHandle, @@ -149,17 +148,16 @@ public class PolicyExecutor { final String authorization, final String resourceIdentifier, final String changeRequestAsJson) { - final Map<String, Object> requestAsMap = getSingleRequestAsMap(yangModelCmHandle, + final Map<String, Object> operationAsMap = getSingleOperationAsMap(yangModelCmHandle, operationType, resourceIdentifier, changeRequestAsJson); - final Object bodyAsObject = createBodyAsObject(Collections.singletonList(requestAsMap)); + final Object bodyAsObject = createBodyAsObject(operationAsMap); final UrlTemplateParameters urlTemplateParameters = RestServiceUrlTemplateBuilder.newInstance() - .fixedPathSegment("execute") - .createUrlTemplateParameters(String.format("%s:%s", serverAddress, serverPort), - "policy-executor/api"); + .fixedPathSegment(REQUEST_PATH) + .createUrlTemplateParameters(String.format("%s:%s", serverAddress, serverPort), PERMISSION_BASE_PATH); return policyExecutorWebClient.post() .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables()) @@ -172,23 +170,23 @@ public class PolicyExecutor { } private static void processSuccessResponse(final JsonNode responseBody) { - final String decisionId = responseBody.path("decisionId").asText("unknown id"); - final String decision = responseBody.path("decision").asText("unknown"); + final String id = responseBody.path("id").asText("unknown id"); + final String permissionResult = responseBody.path("permissionResult").asText("unknown"); final String messageFromPolicyExecutor = responseBody.path("message").asText(); - processDecision(decisionId, decision, messageFromPolicyExecutor, NO_ERROR); + processDecision(id, permissionResult, messageFromPolicyExecutor, NO_ERROR); } - private static void processDecision(final String decisionId, - final String decision, + private static void processDecision(final String id, + final String permissionResult, final String details, final Throwable optionalCauseOfError) { - log.trace("Policy Executor decision id: {} ", decisionId); - if ("allow".equals(decision)) { + log.trace("Policy Executor Decision id: {} ", id); + if ("allow".equals(permissionResult)) { log.trace("Operation allowed."); } else { - log.warn("Policy Executor decision: {}", decision); + log.warn("Policy Executor permission result: {}", permissionResult); log.warn("Policy Executor message: {}", details); - final String message = "Operation not allowed. Decision id " + decisionId + " : " + decision; + final String message = "Operation not allowed. Decision id " + id + " : " + permissionResult; throw new PolicyExecutorException(message, details, optionalCauseOfError); } } @@ -196,6 +194,7 @@ public class PolicyExecutor { private void processException(final RuntimeException runtimeException) { if (runtimeException instanceof WebClientResponseException) { final WebClientResponseException webClientResponseException = (WebClientResponseException) runtimeException; + log.warn("HTTP Error Message: {}", webClientResponseException.getMessage()); final int httpStatusCode = webClientResponseException.getStatusCode().value(); processFallbackResponse("Policy Executor returned HTTP Status code " + httpStatusCode + ".", webClientResponseException); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java index daac63fce4..ed5e703eef 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java @@ -319,9 +319,8 @@ public class CmHandleRegistrationService { // CPS-1239 Robustness cleaning of in progress cache private void removeDeletedCmHandleFromModuleSyncMap(final String cmHandleId) { - if (moduleSyncStartedOnCmHandles.remove(cmHandleId) != null) { - log.debug("{} removed from in progress map", cmHandleId); - } + moduleSyncStartedOnCmHandles.removeAsync(cmHandleId); + log.debug("{} will be removed asynchronously from in progress map", cmHandleId); } private List<CmHandleRegistrationResponse> upgradeCmHandles(final Map<YangModelCmHandle, CmHandleState> diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java index c97b284bf1..9450805eaf 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java @@ -125,9 +125,8 @@ public class ModuleSyncTasks { } private void removeResetCmHandleFromModuleSyncMap(final String resetCmHandleId) { - if (moduleSyncStartedOnCmHandles.remove(resetCmHandleId) != null) { - log.info("{} removed from in progress map", resetCmHandleId); - } + moduleSyncStartedOnCmHandles.removeAsync(resetCmHandleId); + log.info("{} will be removed asynchronously from in progress map", resetCmHandleId); } private static boolean isCmHandleInAdvisedState(final YangModelCmHandle yangModelCmHandle) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java index 74bef43d0b..5b71a8af70 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java @@ -21,13 +21,14 @@ package org.onap.cps.ncmp.impl.inventory.sync; +import static org.onap.cps.ncmp.impl.cache.CpsAndNcmpLockConfig.MODULE_SYNC_WORK_QUEUE_LOCK_NAME; + import com.hazelcast.map.IMap; import java.util.Collection; import java.util.HashSet; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.Lock; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -46,7 +47,7 @@ public class ModuleSyncWatchdog { private final IMap<String, Object> moduleSyncStartedOnCmHandles; private final ModuleSyncTasks moduleSyncTasks; private final AsyncTaskExecutor asyncTaskExecutor; - private final Lock workQueueLock; + private final IMap<String, String> cpsAndNcmpLock; private final Sleeper sleeper; private static final int MODULE_SYNC_BATCH_SIZE = 100; @@ -90,14 +91,16 @@ public class ModuleSyncWatchdog { * So it can be tested without the queue being emptied immediately as the main public method does. */ public void populateWorkQueueIfNeeded() { - if (moduleSyncWorkQueue.isEmpty() && workQueueLock.tryLock()) { + if (moduleSyncWorkQueue.isEmpty() && cpsAndNcmpLock.tryLock(MODULE_SYNC_WORK_QUEUE_LOCK_NAME)) { + log.info("Lock acquired by thread : {}", Thread.currentThread().getName()); try { populateWorkQueue(); if (moduleSyncWorkQueue.isEmpty()) { setPreviouslyLockedCmHandlesToAdvised(); } } finally { - workQueueLock.unlock(); + cpsAndNcmpLock.unlock(MODULE_SYNC_WORK_QUEUE_LOCK_NAME); + log.info("Lock released by thread : {}", Thread.currentThread().getName()); } } } @@ -139,8 +142,7 @@ public class ModuleSyncWatchdog { log.info("nextBatchCandidates size : {}", nextBatchCandidates.size()); for (final String cmHandleId : nextBatchCandidates) { final boolean alreadyAddedToInProgressMap = VALUE_FOR_HAZELCAST_IN_PROGRESS_MAP.equals( - moduleSyncStartedOnCmHandles.putIfAbsent(cmHandleId, VALUE_FOR_HAZELCAST_IN_PROGRESS_MAP, - SynchronizationCacheConfig.MODULE_SYNC_STARTED_TTL_SECS, TimeUnit.SECONDS)); + moduleSyncStartedOnCmHandles.putIfAbsent(cmHandleId, VALUE_FOR_HAZELCAST_IN_PROGRESS_MAP)); if (alreadyAddedToInProgressMap) { log.info("module sync for {} already in progress by other instance", cmHandleId); } else { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java index 671e791ac2..d6ac242b30 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java @@ -26,7 +26,6 @@ import com.hazelcast.config.QueueConfig; import com.hazelcast.config.SetConfig; import com.hazelcast.map.IMap; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.locks.Lock; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.impl.cache.HazelcastCacheConfig; import org.springframework.context.annotation.Bean; @@ -43,11 +42,11 @@ public class SynchronizationCacheConfig extends HazelcastCacheConfig { public static final int DATA_SYNC_SEMAPHORE_TTL_SECS = 1800; private static final QueueConfig commonQueueConfig = createQueueConfig("defaultQueueConfig"); - private static final MapConfig moduleSyncStartedConfig = createMapConfig("moduleSyncStartedConfig"); + private static final MapConfig moduleSyncStartedConfig = + createMapConfigWithTimeToLiveInSeconds("moduleSyncStartedConfig", MODULE_SYNC_STARTED_TTL_SECS); private static final MapConfig dataSyncSemaphoresConfig = createMapConfig("dataSyncSemaphoresConfig"); private static final SetConfig moduleSetTagsBeingProcessedConfig = createSetConfig("moduleSetTagsBeingProcessedConfig"); - private static final String LOCK_NAME_FOR_WORK_QUEUE = "workQueueLock"; /** * Module Sync Distributed Queue Instance. @@ -89,21 +88,4 @@ public class SynchronizationCacheConfig extends HazelcastCacheConfig { public ISet<String> moduleSetTagsBeingProcessed() { return getOrCreateHazelcastInstance(moduleSetTagsBeingProcessedConfig).getSet("moduleSetTagsBeingProcessed"); } - - /** - * Retrieves a distributed lock used to control access to the work queue for module synchronization. - * This lock ensures that the population and modification of the work queue are thread-safe and - * protected from concurrent access across different nodes in the distributed system. - * The lock guarantees that only one instance of the application can populate or modify the - * module sync work queue at a time, preventing race conditions and potential data inconsistencies. - * The lock is obtained using the Hazelcast CP Subsystem's {@link Lock}, which provides - * strong consistency guarantees for distributed operations. - * - * @return a {@link Lock} instance used for synchronizing access to the work queue. - */ - @Bean - public Lock workQueueLock() { - // TODO Method below does not use commonQueueConfig for creating lock (Refactor later) - return getOrCreateHazelcastInstance(commonQueueConfig).getCPSubsystem().getLock(LOCK_NAME_FOR_WORK_QUEUE); - } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfig.java index f9ad3ff937..779024bf9f 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelCacheConfig.java @@ -33,8 +33,8 @@ public class TrustLevelCacheConfig extends HazelcastCacheConfig { public static final String TRUST_LEVEL_PER_DMI_PLUGIN = "trustLevelPerDmiPlugin"; public static final String TRUST_LEVEL_PER_CM_HANDLE = "trustLevelPerCmHandle"; - private static final MapConfig trustLevelPerCmHandleIdCacheConfig = - createMapConfig("trustLevelPerCmHandleCacheConfig"); + private static final MapConfig trustLevelPerCmHandleIdNearCacheConfig = + createNearCacheMapConfig("trustLevelPerCmHandleCacheConfig"); private static final MapConfig trustLevelPerDmiPluginCacheConfig = createMapConfig("trustLevelPerDmiPluginCacheConfig"); @@ -46,7 +46,7 @@ public class TrustLevelCacheConfig extends HazelcastCacheConfig { */ @Bean(TRUST_LEVEL_PER_CM_HANDLE) public IMap<String, TrustLevel> trustLevelPerCmHandleId() { - return getOrCreateHazelcastInstance(trustLevelPerCmHandleIdCacheConfig).getMap(TRUST_LEVEL_PER_CM_HANDLE); + return getOrCreateHazelcastInstance(trustLevelPerCmHandleIdNearCacheConfig).getMap(TRUST_LEVEL_PER_CM_HANDLE); } /** diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManager.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManager.java index b61e53854e..f68bb3b543 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManager.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManager.java @@ -64,7 +64,7 @@ public class TrustLevelManager { public void registerDmiPlugin(final DmiPluginRegistration dmiPluginRegistration) { final String dmiServiceName = DmiServiceNameResolver.resolveDmiServiceName(RequiredDmiService.DATA, dmiPluginRegistration); - trustLevelPerDmiPlugin.put(dmiServiceName, TrustLevel.COMPLETE); + trustLevelPerDmiPlugin.putAsync(dmiServiceName, TrustLevel.COMPLETE); } /** @@ -73,24 +73,22 @@ public class TrustLevelManager { * @param cmHandlesToBeCreated a list of cmHandles being created */ public void registerCmHandles(final Map<String, TrustLevel> cmHandlesToBeCreated) { + final Map<String, TrustLevel> trustLevelPerCmHandleIdForCache = new HashMap<>(); for (final Map.Entry<String, TrustLevel> entry : cmHandlesToBeCreated.entrySet()) { final String cmHandleId = entry.getKey(); - if (trustLevelPerCmHandleId.containsKey(cmHandleId)) { - log.warn("Cm handle: {} already registered", cmHandleId); - } else { - TrustLevel initialTrustLevel = entry.getValue(); - if (initialTrustLevel == null) { - initialTrustLevel = TrustLevel.COMPLETE; - } - trustLevelPerCmHandleId.put(cmHandleId, initialTrustLevel); - if (TrustLevel.NONE.equals(initialTrustLevel)) { - cmAvcEventPublisher.publishAvcEvent(cmHandleId, + TrustLevel initialTrustLevel = entry.getValue(); + if (initialTrustLevel == null) { + initialTrustLevel = TrustLevel.COMPLETE; + } + trustLevelPerCmHandleIdForCache.put(cmHandleId, initialTrustLevel); + if (TrustLevel.NONE.equals(initialTrustLevel)) { + cmAvcEventPublisher.publishAvcEvent(cmHandleId, AVC_CHANGED_ATTRIBUTE_NAME, AVC_NO_OLD_VALUE, initialTrustLevel.name()); - } } } + trustLevelPerCmHandleId.putAllAsync(trustLevelPerCmHandleIdForCache); } /** @@ -105,7 +103,7 @@ public class TrustLevelManager { final Collection<String> affectedCmHandleIds, final TrustLevel newDmiTrustLevel) { final TrustLevel oldDmiTrustLevel = trustLevelPerDmiPlugin.get(dmiServiceName); - trustLevelPerDmiPlugin.put(dmiServiceName, newDmiTrustLevel); + trustLevelPerDmiPlugin.putAsync(dmiServiceName, newDmiTrustLevel); for (final String affectedCmHandleId : affectedCmHandleIds) { final TrustLevel cmHandleTrustLevel = trustLevelPerCmHandleId.get(affectedCmHandleId); final TrustLevel oldEffectiveTrustLevel = cmHandleTrustLevel.getEffectiveTrustLevel(oldDmiTrustLevel); @@ -131,7 +129,7 @@ public class TrustLevelManager { final TrustLevel oldEffectiveTrustLevel = oldCmHandleTrustLevel.getEffectiveTrustLevel(dmiTrustLevel); final TrustLevel newEffectiveTrustLevel = newCmHandleTrustLevel.getEffectiveTrustLevel(dmiTrustLevel); - trustLevelPerCmHandleId.put(cmHandleId, newCmHandleTrustLevel); + trustLevelPerCmHandleId.putAsync(cmHandleId, newCmHandleTrustLevel); sendAvcNotificationIfRequired(cmHandleId, oldEffectiveTrustLevel, newEffectiveTrustLevel); } @@ -175,9 +173,7 @@ public class TrustLevelManager { */ public void removeCmHandles(final Collection<String> cmHandleIds) { for (final String cmHandleId : cmHandleIds) { - if (trustLevelPerCmHandleId.remove(cmHandleId) == null) { - log.debug("Removed Cm handle: {} is not in trust level cache", cmHandleId); - } + trustLevelPerCmHandleId.removeAsync(cmHandleId); } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy index 33dcf5d623..9423246134 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy @@ -75,7 +75,7 @@ class PolicyExecutorSpec extends Specification { def 'Permission check with "allow" decision.'() { given: 'allow response' - mockResponse([decision:'allow'], HttpStatus.OK) + mockResponse([permissionResult:'allow'], HttpStatus.OK) when: 'permission is checked for an operation' objectUnderTest.checkPermission(new YangModelCmHandle(), operationType, 'my credentials','my resource',someValidJson) then: 'system logs the operation is allowed' @@ -88,7 +88,7 @@ class PolicyExecutorSpec extends Specification { def 'Permission check with "other" decision (not allowed).'() { given: 'other response' - mockResponse([decision:'other', decisionId:123, message:'I dont like Mondays' ], HttpStatus.OK) + mockResponse([permissionResult:'other', id:123, message:'I dont like Mondays' ], HttpStatus.OK) when: 'permission is checked for an operation' objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource',someValidJson) then: 'Policy Executor exception is thrown' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy index d19081cee5..1c8a19a3bd 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy @@ -21,6 +21,9 @@ package org.onap.cps.ncmp.impl.inventory +import com.hazelcast.config.Config +import com.hazelcast.core.Hazelcast +import com.hazelcast.instance.impl.HazelcastInstanceFactory import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsQueryService import org.onap.cps.impl.utils.CpsValidator @@ -39,12 +42,11 @@ class CmHandleQueryServiceImplSpec extends Specification { def mockCpsQueryService = Mock(CpsQueryService) def mockCpsDataService = Mock(CpsDataService) - def trustLevelPerDmiPlugin = [:] - def trustLevelPerCmHandleId = [ 'PNFDemo': TrustLevel.COMPLETE, 'PNFDemo2': TrustLevel.NONE, 'PNFDemo4': TrustLevel.NONE ] + def trustLevelPerDmiPlugin = HazelcastInstanceFactory.getOrCreateHazelcastInstance(new Config('hazelcastInstanceName')).getMap('trustLevelPerDmiPlugin') + def trustLevelPerCmHandleId = HazelcastInstanceFactory.getOrCreateHazelcastInstance(new Config('hazelcastInstanceName')).getMap('trustLevelPerCmHandleId') def mockCpsValidator = Mock(CpsValidator) def objectUnderTest = new CmHandleQueryServiceImpl(mockCpsDataService, mockCpsQueryService, trustLevelPerDmiPlugin, trustLevelPerCmHandleId, mockCpsValidator) - def static sampleDataNodes = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']"), new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-2']")] @@ -56,6 +58,16 @@ class CmHandleQueryServiceImplSpec extends Specification { def static pnfDemo4 = createDataNode('PNFDemo4') def static pnfDemo5 = createDataNode('PNFDemo5') + def setup() { + trustLevelPerCmHandleId.put("PNFDemo", TrustLevel.COMPLETE) + trustLevelPerCmHandleId.put("PNFDemo2", TrustLevel.NONE) + trustLevelPerCmHandleId.put("PNFDemo4", TrustLevel.NONE) + } + + def cleanupSpec() { + Hazelcast.getHazelcastInstanceByName('hazelcastInstanceName').shutdown() + } + def 'Query CmHandles with public properties query pair.'() { given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' mockResponses() diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy index 67778fc0a3..0706a1e19f 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy @@ -64,7 +64,7 @@ class CmHandleRegistrationServiceSpec extends Specification { def objectUnderTest = Spy(new CmHandleRegistrationService( mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCpsDataService, mockLcmEventsCmHandleStateHandler, - mockModuleSyncStartedOnCmHandles, mockTrustLevelManager, mockAlternateIdChecker)) + mockModuleSyncStartedOnCmHandles as IMap<String, Object>, mockTrustLevelManager, mockAlternateIdChecker)) def setup() { // always accept all cm handles @@ -86,14 +86,14 @@ class CmHandleRegistrationServiceSpec extends Specification { mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: CmHandleState.READY)) and: 'cm handle is in READY state' mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> true + and: 'cm handles is present in in-progress map' + mockModuleSyncStartedOnCmHandles.containsKey('cmhandle-2') >> true when: 'registration is processed' objectUnderTest.updateDmiRegistration(dmiRegistration) then: 'cm-handles are removed first' 1 * objectUnderTest.processRemovedCmHandles(*_) and: 'de-registered cm handle entry is removed from in progress map' - 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle-2') - then: 'cm-handles are created' - 1 * objectUnderTest.processCreatedCmHandles(*_) + 1 * mockModuleSyncStartedOnCmHandles.removeAsync('cmhandle-2') then: 'cm-handles are updated' 1 * objectUnderTest.processUpdatedCmHandles(*_) 1 * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> [] @@ -310,6 +310,9 @@ class CmHandleRegistrationServiceSpec extends Specification { given: 'a registration with three cm-handles to be deleted' def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', removedCmHandles: ['cmhandle1', 'cmhandle2', 'cmhandle3']) + and: 'cm handles to be deleted in the progress map' + mockModuleSyncStartedOnCmHandles.containsKey("cmhandle1") >> true + mockModuleSyncStartedOnCmHandles.containsKey("cmhandle3") >> true and: 'cm-handle deletion fails on batch' mockInventoryPersistence.deleteDataNodes(_) >> { throw new RuntimeException("Failed") } and: 'cm-handle deletion is successful for 1st and 3rd; failed for 2nd' @@ -321,11 +324,11 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'a response is received for all cm-handles' response.removedCmHandles.size() == 3 and: 'successfully de-registered cm handle 1 is removed from in progress map' - 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle1') + 1 * mockModuleSyncStartedOnCmHandles.removeAsync('cmhandle1') and: 'successfully de-registered cm handle 3 is removed from in progress map even though it was already being removed' - 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle3') >> 'already in progress' + 1 * mockModuleSyncStartedOnCmHandles.removeAsync('cmhandle3') and: 'failed de-registered cm handle entries should NOT be removed from in progress map' - 0 * mockModuleSyncStartedOnCmHandles.remove('cmhandle2') + 0 * mockModuleSyncStartedOnCmHandles.removeAsync('cmhandle2') and: '1st and 3rd cm-handle deletes successfully' with(response.removedCmHandles[0]) { assert it.status == Status.SUCCESS @@ -349,7 +352,6 @@ class CmHandleRegistrationServiceSpec extends Specification { }) and: 'No cm handles state updates for "upgraded cm handles"' 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([:]) - } def 'Remove CmHandle Error Handling: Schema Set Deletion failed'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy index de69927d36..c1a8589c48 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -26,6 +26,8 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService +import org.onap.cps.api.exceptions.DataNodeNotFoundException +import org.onap.cps.api.exceptions.DataValidationException import org.onap.cps.impl.utils.CpsValidator import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException import org.onap.cps.ncmp.api.inventory.models.CompositeState @@ -85,7 +87,7 @@ class InventoryPersistenceImplSpec extends Specification { def alternateId2 = 'another-alternate-id' def xpath2 = "/dmi-registry/cm-handles[@id='another-cm-handle']" - def dataNode = new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]) + def dataNode = new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: leaves) @Shared def childDataNodesForCmHandleWithAllProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]), @@ -100,7 +102,7 @@ class InventoryPersistenceImplSpec extends Specification { @Shared def childDataNodesForCmHandleWithState = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/state", leaves: ['cm-handle-state': 'ADVISED'])] - def "Retrieve CmHandle using datanode with #scenario."() { + def 'Retrieve CmHandle using datanode with #scenario.'() { given: 'the cps data service returns a data node from the DMI registry' def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves) mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode] @@ -127,7 +129,7 @@ class InventoryPersistenceImplSpec extends Specification { 'with state details' | childDataNodesForCmHandleWithState || [] || [] || CmHandleState.ADVISED } - def "Handling missing service names as null."() { + def 'Handling missing service names as null.'() { given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes' def dataNode = new DataNode(childDataNodes:[], leaves: ['id':cmHandleId]) mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode] @@ -141,17 +143,28 @@ class InventoryPersistenceImplSpec extends Specification { 1 * mockCpsValidator.validateNameCharacters(cmHandleId) } - def "Retrieve multiple YangModelCmHandles using cm handle ids"() { + def 'Retrieve multiple YangModelCmHandles using cm handle ids'() { given: 'the cps data service returns 2 data nodes from the DMI registry' def dataNodes = [new DataNode(xpath: xpath, leaves: ['id': cmHandleId]), new DataNode(xpath: xpath2, leaves: ['id': cmHandleId2])] mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes - when: 'retrieving the yang modelled cm handle' + when: 'retrieving the yang modelled cm handles' def results = objectUnderTest.getYangModelCmHandles([cmHandleId, cmHandleId2]) - then: 'verify both have returned and cmhandleIds are correct' + then: 'verify both have returned and cm handle Ids are correct' assert results.size() == 2 assert results.id.containsAll([cmHandleId, cmHandleId2]) } + def 'YangModelCmHandles are not returned for invalid cm handle ids'() { + given: 'invalid cm handle id throws a data validation exception' + mockCpsValidator.validateNameCharacters('Invalid Cm Handle Id') >> {throw new DataValidationException('','')} + and: 'empty collection is returned as no valid cm handle ids are given' + mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, [] , INCLUDE_ALL_DESCENDANTS) >> [] + when: 'retrieving the yang modelled cm handles' + def results = objectUnderTest.getYangModelCmHandles(['Invalid Cm Handle Id']) + then: 'no YangModelCmHandle is returned' + assert results.size() == 0 + } + def "Retrieve multiple YangModelCmHandles using cm handle references"() { given: 'the cps data service returns 2 data nodes from the DMI registry' def dataNodes = [new DataNode(xpath: xpath, leaves: ['id': cmHandleId, 'alternate-id':alternateId]), new DataNode(xpath: xpath2, leaves: ['id': cmHandleId2,'alternate-id':alternateId2])] @@ -337,11 +350,15 @@ class InventoryPersistenceImplSpec extends Specification { assert thrownException.getDetails().contains('No cm handles found with reference alternate id') } - def 'Get multiple yang model cm handles by alternate ids, passing empty collection'() { - when: 'getting the yang model cm handle for no alternate ids' - objectUnderTest.getYangModelCmHandleByAlternateIds([]) - then: 'query service is not invoked' - 0 * mockCmHandleQueries.queryNcmpRegistryByCpsPath(_, _) + def 'Get multiple yang model cm handles by alternate ids #scenario'() { + when: 'getting the yang model cm handle with a empty/populated collection of alternate Ids' + objectUnderTest.getYangModelCmHandleByAlternateIds(alternateIdCollection) + then: 'query service invoked when needed' + expectedInvocations * mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [dataNode] + where: 'collections are either empty or populated with alternate ids' + scenario | alternateIdCollection || expectedInvocations + 'empty collection' | [] || 0 + 'populated collection' | ['alt'] || 1 } def 'Get CM handle ids for CM Handles that has given module names'() { @@ -384,10 +401,24 @@ class InventoryPersistenceImplSpec extends Specification { 1 * mockCpsDataService.deleteDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ['xpath1', 'xpath2'], NO_TIMESTAMP); } - def 'Check if cm handle exists for a given cm handle id'() { + def 'CM handle exists'() { given: 'data service returns a datanode with correct cm handle id' mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode] expect: 'cm handle exists for given cm handle id' - assert true == objectUnderTest.isExistingCmHandleId('some-cm-handle') + assert true == objectUnderTest.isExistingCmHandleId(cmHandleId) + } + + def 'CM handle does not exist, empty dataNode collection returned'() { + given: 'data service returns an empty datanode' + mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [] + expect: 'false is returned for non-existent cm handle' + assert false == objectUnderTest.isExistingCmHandleId(cmHandleId) + } + + def 'CM handle does not exist, exception thrown'() { + given: 'data service throws an exception' + mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, "/dmi-registry/cm-handles[@id='non-existent-cm-handle']", INCLUDE_ALL_DESCENDANTS) >> {throw new DataNodeNotFoundException('','')} + expect: 'false is returned for non-existent cm handle' + assert false == objectUnderTest.isExistingCmHandleId('non-existent-cm-handle') } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy index ae2554762f..c62a87f5c3 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy @@ -24,7 +24,7 @@ package org.onap.cps.ncmp.impl.inventory import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.api.model.ConditionProperties import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters import org.onap.cps.ncmp.api.inventory.models.CompositeState @@ -32,12 +32,12 @@ import org.onap.cps.ncmp.api.inventory.models.ConditionApiProperties import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.api.inventory.models.TrustLevel +import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher -import org.onap.cps.api.model.ConditionProperties import org.onap.cps.utils.JsonObjectMapper import spock.lang.Specification @@ -50,7 +50,7 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { def mockInventoryPersistence = Mock(InventoryPersistence) def mockTrustLevelManager = Mock(TrustLevelManager) def mockAlternateIdMatcher = Mock(AlternateIdMatcher) - def objectUnderTest = new NetworkCmProxyInventoryFacade(mockCmHandleRegistrationService, mockCmHandleQueryService, mockParameterizedCmHandleQueryService, mockInventoryPersistence, spiedJsonObjectMapper, mockTrustLevelManager, mockAlternateIdMatcher) + def objectUnderTest = new NetworkCmProxyInventoryFacadeImpl(mockCmHandleRegistrationService, mockCmHandleQueryService, mockParameterizedCmHandleQueryService, mockInventoryPersistence, spiedJsonObjectMapper, mockTrustLevelManager, mockAlternateIdMatcher) def 'Update DMI Registration'() { given: 'an (updated) dmi plugin registration' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy index 97c2488bc3..c7fe45db90 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy @@ -194,7 +194,7 @@ class ModuleSyncTasksSpec extends Specification { def loggingEvent = getLoggingEvent() assert loggingEvent.level == Level.INFO and: 'the log indicates the cm handle entry is removed successfully' - assert loggingEvent.formattedMessage == 'ch-1 removed from in progress map' + assert loggingEvent.formattedMessage == 'ch-1 will be removed asynchronously from in progress map' } def 'Sync and upgrade CM handle if in upgrade state for #scenario'() { @@ -214,18 +214,6 @@ class ModuleSyncTasksSpec extends Specification { 'module upgrade failed' | MODULE_UPGRADE_FAILED } - - def 'Remove non-existing cm handle id from hazelcast map'() { - given: 'hazelcast map does not contains cm handle id' - def result = moduleSyncStartedOnCmHandles.get('non-existing-cm-handle') - assert result == null - when: 'remove cm handle entry from hazelcast map' - objectUnderTest.removeResetCmHandleFromModuleSyncMap('non-existing-cm-handle') - then: 'no event is logged' - def loggingEvent = getLoggingEvent() - assert loggingEvent == null - } - def cmHandleByIdAndState(cmHandleId, cmHandleState) { return new YangModelCmHandle(id: cmHandleId, compositeState: new CompositeState(cmHandleState: cmHandleState)) } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy index 4cf07e4c24..a9b88c2d3b 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,11 +44,11 @@ class ModuleSyncWatchdogSpec extends Specification { def spiedAsyncTaskExecutor = Spy(AsyncTaskExecutor) - def mockWorkQueueLock = Mock(Lock) + def mockCpsAndNcmpLock = Mock(IMap<String,String>) def spiedSleeper = Spy(Sleeper) - def objectUnderTest = new ModuleSyncWatchdog(mockModuleOperationsUtils, moduleSyncWorkQueue , mockModuleSyncStartedOnCmHandles, mockModuleSyncTasks, spiedAsyncTaskExecutor, mockWorkQueueLock, spiedSleeper) + def objectUnderTest = new ModuleSyncWatchdog(mockModuleOperationsUtils, moduleSyncWorkQueue , mockModuleSyncStartedOnCmHandles, mockModuleSyncTasks, spiedAsyncTaskExecutor, mockCpsAndNcmpLock, spiedSleeper) void setup() { spiedAsyncTaskExecutor.setupThreadPool() @@ -59,14 +59,16 @@ class ModuleSyncWatchdogSpec extends Specification { mockModuleOperationsUtils.getAdvisedCmHandleIds() >> createCmHandleIds(numberOfAdvisedCmHandles) and: 'module sync utilities returns no failed (locked) cm handles' mockModuleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> [] - and: 'the work queue is not locked' - mockWorkQueueLock.tryLock() >> true + and: 'the work queue can be locked' + mockCpsAndNcmpLock.tryLock('workQueueLock') >> true and: 'the executor has enough available threads' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 3 when: ' module sync is started' objectUnderTest.moduleSyncAdvisedCmHandles() then: 'it performs #expectedNumberOfTaskExecutions tasks' expectedNumberOfTaskExecutions * spiedAsyncTaskExecutor.executeTask(*_) + and: 'the executing thread is unlocked' + 1 * mockCpsAndNcmpLock.unlock('workQueueLock') where: 'the following parameter are used' scenario | numberOfAdvisedCmHandles || expectedNumberOfTaskExecutions 'none at all' | 0 || 0 @@ -80,8 +82,8 @@ class ModuleSyncWatchdogSpec extends Specification { def 'Module sync cm handles starts with no available threads.'() { given: 'module sync utilities returns a advise cm handles' mockModuleOperationsUtils.getAdvisedCmHandleIds() >> createCmHandleIds(1) - and: 'the work queue is not locked' - mockWorkQueueLock.tryLock() >> true + and: 'the work queue can be locked' + mockCpsAndNcmpLock.tryLock('workQueueLock') >> true and: 'the executor first has no threads but has one thread on the second attempt' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >>> [ 0, 1 ] when: ' module sync is started' @@ -93,8 +95,8 @@ class ModuleSyncWatchdogSpec extends Specification { def 'Module sync advised cm handle already handled by other thread.'() { given: 'module sync utilities returns an advised cm handle' mockModuleOperationsUtils.getAdvisedCmHandleIds() >> createCmHandleIds(1) - and: 'the work queue is not locked' - mockWorkQueueLock.tryLock() >> true + and: 'the work queue can be locked' + mockCpsAndNcmpLock.tryLock('workQueueLock') >> true and: 'the executor has a thread available' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 1 and: 'the semaphore cache indicates the cm handle is already being processed' @@ -131,16 +133,18 @@ class ModuleSyncWatchdogSpec extends Specification { def 'Module Sync Locking.'() { given: 'module sync utilities returns an advised cm handle' mockModuleOperationsUtils.getAdvisedCmHandleIds() >> createCmHandleIds(1) - and: 'can lock is : #canLock' - mockWorkQueueLock.tryLock() >> canLock + and: 'can be locked is : #canLock' + mockCpsAndNcmpLock.tryLock('workQueueLock') >> canLock when: 'attempt to populate the work queue' objectUnderTest.populateWorkQueueIfNeeded() then: 'the queue remains empty is #expectQueueRemainsEmpty' assert moduleSyncWorkQueue.isEmpty() == expectQueueRemainsEmpty + and: 'unlock is called only when thread is able to enter the critical section' + expectedInvocationToUnlock * mockCpsAndNcmpLock.unlock('workQueueLock') where: 'the following lock states are applied' - canLock | expectQueueRemainsEmpty - false | true - true | false + canLock || expectQueueRemainsEmpty || expectedInvocationToUnlock + false || true || 0 + true || false || 1 } def 'Sleeper gets interrupted.'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDogSpec.groovy index 32f4503005..097387c038 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/DmiPluginTrustLevelWatchDogSpec.groovy @@ -20,6 +20,9 @@ package org.onap.cps.ncmp.impl.inventory.trustlevel +import com.hazelcast.config.Config +import com.hazelcast.core.Hazelcast +import com.hazelcast.instance.impl.HazelcastInstanceFactory import org.onap.cps.ncmp.api.inventory.models.TrustLevel import org.onap.cps.ncmp.impl.dmi.DmiRestClient import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService @@ -32,11 +35,16 @@ class DmiPluginTrustLevelWatchDogSpec extends Specification { def mockDmiRestClient = Mock(DmiRestClient) def mockCmHandleQueryService = Mock(CmHandleQueryService) def mockTrustLevelManager = Mock(TrustLevelManager) - def trustLevelPerDmiPlugin = [:] - + def trustLevelPerDmiPlugin = HazelcastInstanceFactory + .getOrCreateHazelcastInstance(new Config('hazelcastInstanceName')) + .getMap('trustLevelPerDmiPlugin') def objectUnderTest = new DmiPluginTrustLevelWatchDog(mockDmiRestClient, mockCmHandleQueryService, mockTrustLevelManager, trustLevelPerDmiPlugin) + def cleanupSpec() { + Hazelcast.getHazelcastInstanceByName('hazelcastInstanceName').shutdown() + } + def 'watch dmi plugin health status for #dmiHealhStatus'() { given: 'the cache has been initialised and "knows" about dmi-1' trustLevelPerDmiPlugin.put('dmi-1', dmiOldTrustLevel) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy index 1088ca8e06..1ab517cdcf 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy @@ -77,19 +77,6 @@ class TrustLevelManagerSpec extends Specification { assert trustLevelPerCmHandleId.get('ch-2') == TrustLevel.COMPLETE } - def 'Initial cm handle registration where a cm handle is already in the cache'() { - given: 'a trusted cm handle' - def cmHandleModelsToBeCreated = ['ch-1': TrustLevel.NONE] - and: 'the cm handle id already in the cache' - trustLevelPerCmHandleId.put('ch-1', TrustLevel.COMPLETE) - when: 'method to register to the cache is called' - objectUnderTest.registerCmHandles(cmHandleModelsToBeCreated) - then: 'no notification sent' - 0 * mockAttributeValueChangeEventPublisher.publishAvcEvent(*_) - and: 'cm handle cache is not updated' - assert trustLevelPerCmHandleId.get('ch-1') == TrustLevel.COMPLETE - } - def 'Initial cm handle registration with a cm handle that is not trusted'() { given: 'a not trusted cm handle' def cmHandleModelsToBeCreated = ['ch-2': TrustLevel.NONE] diff --git a/cps-rest/docs/openapi/components.yml b/cps-rest/docs/openapi/components.yml index 1db4185330..1a7e4308d9 100644 --- a/cps-rest/docs/openapi/components.yml +++ b/cps-rest/docs/openapi/components.yml @@ -326,7 +326,7 @@ components: dryRunInQuery: name: dry-run in: query - description: Boolean flag to validate data, without persisting it. Default value is set to false. + description: Boolean flag to validate data, without persisting it. Default value is false. required: false schema: type: boolean diff --git a/cps-rest/docs/openapi/cpsData.yml b/cps-rest/docs/openapi/cpsData.yml index 36000fd7d8..178a68fb77 100644 --- a/cps-rest/docs/openapi/cpsData.yml +++ b/cps-rest/docs/openapi/cpsData.yml @@ -31,6 +31,7 @@ listElementByDataspaceAndAnchor: - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/requiredXpathInQuery' + - $ref: 'components.yml#/components/parameters/dryRunInQuery' - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' - $ref: 'components.yml#/components/parameters/contentTypeInHeader' requestBody: @@ -70,6 +71,7 @@ listElementByDataspaceAndAnchor: - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/requiredXpathInQuery' + - $ref: 'components.yml#/components/parameters/dryRunInQuery' - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' - $ref: 'components.yml#/components/parameters/contentTypeInHeader' requestBody: @@ -154,6 +156,7 @@ nodesByDataspaceAndAnchor: - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/xpathInQuery' + - $ref: 'components.yml#/components/parameters/dryRunInQuery' - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' - $ref: 'components.yml#/components/parameters/contentTypeInHeader' requestBody: @@ -214,6 +217,7 @@ nodesByDataspaceAndAnchor: - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' - $ref: 'components.yml#/components/parameters/xpathInQuery' + - $ref: 'components.yml#/components/parameters/dryRunInQuery' - $ref: 'components.yml#/components/parameters/observedTimestampInQuery' - $ref: 'components.yml#/components/parameters/contentTypeInHeader' requestBody: diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java index d460f52415..be552ecc6a 100755 --- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java @@ -102,12 +102,17 @@ public class DataRestController implements CpsDataApi { @Override public ResponseEntity<String> addListElements(final String apiVersion, final String dataspaceName, final String anchorName, final String parentNodeXpath, - final String nodeData, final String observedTimestamp, - final String contentTypeInHeader) { + final String nodeData, final Boolean dryRunEnabled, + final String observedTimestamp, final String contentTypeInHeader) { final ContentType contentType = ContentType.fromString(contentTypeInHeader); - cpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, - nodeData, toOffsetDateTime(observedTimestamp), contentType); - return new ResponseEntity<>(HttpStatus.CREATED); + if (Boolean.TRUE.equals(dryRunEnabled)) { + cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType); + return ResponseEntity.ok().build(); + } else { + cpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, + nodeData, toOffsetDateTime(observedTimestamp), contentType); + } + return ResponseEntity.status(HttpStatus.CREATED).build(); } @Override @@ -151,34 +156,50 @@ public class DataRestController implements CpsDataApi { @Override public ResponseEntity<Object> updateNodeLeaves(final String apiVersion, final String dataspaceName, final String anchorName, final String nodeData, - final String parentNodeXpath, final String observedTimestamp, - final String contentTypeInHeader) { + final String parentNodeXpath, final Boolean dryRunEnabled, + final String observedTimestamp, final String contentTypeInHeader) { final ContentType contentType = ContentType.fromString(contentTypeInHeader); - cpsDataService.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, - nodeData, toOffsetDateTime(observedTimestamp), contentType); - return new ResponseEntity<>(HttpStatus.OK); + if (Boolean.TRUE.equals(dryRunEnabled)) { + cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType); + return ResponseEntity.ok().build(); + } else { + cpsDataService.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, + nodeData, toOffsetDateTime(observedTimestamp), contentType); + } + return ResponseEntity.status(HttpStatus.OK).build(); } @Override public ResponseEntity<Object> replaceNode(final String apiVersion, final String dataspaceName, final String anchorName, final String nodeData, - final String parentNodeXpath, final String observedTimestamp, - final String contentTypeInHeader) { + final String parentNodeXpath, final Boolean dryRunEnabled, + final String observedTimestamp, final String contentTypeInHeader) { final ContentType contentType = ContentType.fromString(contentTypeInHeader); - cpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, - nodeData, toOffsetDateTime(observedTimestamp), contentType); - return new ResponseEntity<>(HttpStatus.OK); + if (Boolean.TRUE.equals(dryRunEnabled)) { + cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData, contentType); + return ResponseEntity.ok().build(); + } else { + cpsDataService.updateDataNodeAndDescendants(dataspaceName, anchorName, parentNodeXpath, + nodeData, toOffsetDateTime(observedTimestamp), contentType); + } + return ResponseEntity.status(HttpStatus.OK).build(); } @Override public ResponseEntity<Object> replaceListContent(final String apiVersion, final String dataspaceName, final String anchorName, final String parentNodeXpath, - final String nodeData, final String observedTimestamp, - final String contentTypeInHeader) { + final String nodeData, final Boolean dryRunEnabled, + final String observedTimestamp, final String contentTypeInHeader) { final ContentType contentType = ContentType.fromString(contentTypeInHeader); - cpsDataService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, - nodeData, toOffsetDateTime(observedTimestamp), contentType); - return new ResponseEntity<>(HttpStatus.OK); + if (Boolean.TRUE.equals(dryRunEnabled)) { + cpsDataService.validateData(dataspaceName, anchorName, parentNodeXpath, nodeData, + ContentType.JSON); + return ResponseEntity.ok().build(); + } else { + cpsDataService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, + nodeData, toOffsetDateTime(observedTimestamp), contentType); + } + return ResponseEntity.status(HttpStatus.OK).build(); } @Override diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy index 915fbdecf5..ca89fafe83 100755 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy @@ -168,16 +168,17 @@ class DataRestControllerSpec extends Specification { given: 'an endpoint to create a node' def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" def parentNodeXpath = '/' + and: 'dryRunEnabled flag is set to true' def dryRunEnabled = 'true' when: 'post is invoked with json data and dry-run flag enabled' def response = - mvc.perform( - post(endpoint) - .contentType(MediaType.APPLICATION_JSON) - .param('xpath', parentNodeXpath) - .param('dry-run', dryRunEnabled) - .content(requestBodyJson) - ).andReturn().response + mvc.perform( + post(endpoint) + .contentType(MediaType.APPLICATION_JSON) + .param('xpath', parentNodeXpath) + .param('dry-run', dryRunEnabled) + .content(requestBodyJson) + ).andReturn().response then: 'a 200 OK response is returned' response.status == HttpStatus.OK.value() then: 'the service was called with correct parameters' @@ -263,6 +264,26 @@ class DataRestControllerSpec extends Specification { 'Content type XML with invalid observed-timestamp' | 'invalid' | MediaType.APPLICATION_XML | requestBodyXml || 0 | HttpStatus.BAD_REQUEST | expectedXmlData | ContentType.XML } + def 'Validate data using Save list elements API'() { + given: 'endpoint to save list elements' + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes" + and: 'dryRunEnabled flag is set to true' + def dryRunEnabled = 'true' + when: 'post request is performed' + def response = + mvc.perform( + post(endpoint) + .contentType(MediaType.APPLICATION_JSON) + .param('xpath', '/') + .content(requestBodyJson) + .param('dry-run', dryRunEnabled) + ).andReturn().response + then: 'a 200 OK response is returned' + response.status == HttpStatus.OK.value() + then: 'the service was called with correct parameters' + 1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON) + } + def 'Get data node with leaves'() { given: 'the service returns data node leaves' def xpath = 'parent-1' @@ -515,6 +536,26 @@ class DataRestControllerSpec extends Specification { 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST } + def 'Validate data using Update a node API'() { + given: 'endpoint to update a node leaves' + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" + and: 'dryRunEnabled flag is set to true' + def dryRunEnabled = 'true' + when: 'patch request is performed' + def response = + mvc.perform( + patch(endpoint) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBodyJson) + .param('xpath', '/') + .param('dry-run', dryRunEnabled) + ).andReturn().response + then: 'a 200 OK response is returned' + response.status == HttpStatus.OK.value() + then: 'the service was called with correct parameters' + 1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON) + } + def 'Replace data node tree: #scenario.'() { given: 'endpoint to replace node' def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" @@ -540,6 +581,26 @@ class DataRestControllerSpec extends Specification { 'XML content: some xpath by parent' | '/some/xpath' | MediaType.APPLICATION_XML || '/some/xpath' | requestBodyXml | expectedXmlData | ContentType.XML } + def 'Validate data using Replace data node API'() { + given: 'endpoint to replace node' + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" + and: 'dryRunEnabled flag is set to true' + def dryRunEnabled = 'true' + when: 'put request is performed' + def response = + mvc.perform( + put(endpoint) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBodyJson) + .param('xpath', '/') + .param('dry-run', dryRunEnabled) + ).andReturn().response + then: 'a 200 OK response is returned' + response.status == HttpStatus.OK.value() + then: 'the service was called with correct parameters' + 1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON) + } + def 'Update data node and descendants with observedTimestamp.'() { given: 'endpoint to replace node' def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" @@ -605,6 +666,26 @@ class DataRestControllerSpec extends Specification { 'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST } + def 'Validate data using Replace list content API'() { + given: 'endpoint to replace list-nodes' + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes" + and: 'dryRunEnabled flag is set to true' + def dryRunEnabled = 'true' + when: 'put request is performed' + def response = + mvc.perform( + put(endpoint) + .contentType(MediaType.APPLICATION_JSON) + .param('xpath', '/') + .content(requestBodyJson) + .param('dry-run', dryRunEnabled) + ).andReturn().response + then: 'a 200 OK response is returned' + response.status == HttpStatus.OK.value() + then: 'the service was called with correct parameters' + 1 * mockCpsDataService.validateData(dataspaceName, anchorName, '/', requestBodyJson, ContentType.JSON) + } + def 'Delete list element #scenario.'() { when: 'list-nodes endpoint is invoked with delete operation' def deleteRequestBuilder = delete("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes") diff --git a/docs/api/swagger/cps/openapi.yaml b/docs/api/swagger/cps/openapi.yaml index 7a300207cf..c84609b638 100644 --- a/docs/api/swagger/cps/openapi.yaml +++ b/docs/api/swagger/cps/openapi.yaml @@ -1354,6 +1354,15 @@ paths: schema: default: / type: string + - description: "Boolean flag to validate data, without persisting it. Default\ + \ value is false." + in: query + name: dry-run + required: false + schema: + default: false + example: false + type: boolean - description: observed-timestamp in: query name: observed-timestamp @@ -1474,7 +1483,7 @@ paths: default: / type: string - description: "Boolean flag to validate data, without persisting it. Default\ - \ value is set to false." + \ value is false." in: query name: dry-run required: false @@ -1610,6 +1619,15 @@ paths: schema: default: / type: string + - description: "Boolean flag to validate data, without persisting it. Default\ + \ value is false." + in: query + name: dry-run + required: false + schema: + default: false + example: false + type: boolean - description: observed-timestamp in: query name: observed-timestamp @@ -1804,6 +1822,15 @@ paths: required: true schema: type: string + - description: "Boolean flag to validate data, without persisting it. Default\ + \ value is false." + in: query + name: dry-run + required: false + schema: + default: false + example: false + type: boolean - description: observed-timestamp in: query name: observed-timestamp @@ -1920,6 +1947,15 @@ paths: required: true schema: type: string + - description: "Boolean flag to validate data, without persisting it. Default\ + \ value is false." + in: query + name: dry-run + required: false + schema: + default: false + example: false + type: boolean - description: observed-timestamp in: query name: observed-timestamp @@ -2623,17 +2659,9 @@ components: - application/json - application/xml type: string - observedTimestampInQuery: - description: observed-timestamp - in: query - name: observed-timestamp - required: false - schema: - example: 2021-03-21T00:10:34.030-0100 - type: string dryRunInQuery: description: "Boolean flag to validate data, without persisting it. Default\ - \ value is set to false." + \ value is false." in: query name: dry-run required: false @@ -2641,6 +2669,14 @@ components: default: false example: false type: boolean + observedTimestampInQuery: + description: observed-timestamp + in: query + name: observed-timestamp + required: false + schema: + example: 2021-03-21T00:10:34.030-0100 + type: string requiredXpathInQuery: description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" examples: diff --git a/docs/api/swagger/policy-executor/openapi.yaml b/docs/api/swagger/policy-executor/openapi.yaml index 1248c0d08b..ba341b2d57 100644 --- a/docs/api/swagger/policy-executor/openapi.yaml +++ b/docs/api/swagger/policy-executor/openapi.yaml @@ -18,229 +18,179 @@ openapi: 3.0.3 info: - description: Allows NCMP to execute a policy defined by a third party implementation - before proceeding with a CM operation - title: Policy Executor - version: 1.0.0 + title: Operation permission API + description: "Allows a client application to execute a permission request defined by a third party implementation before proceeding with an operation. As an example, a permission can be requested before performing any configuration management operation." + version: 1.0.0-alpha.1+1 + contact: + name: CPS team + url: https://lf-onap.atlassian.net/wiki/spaces/DW/pages/16442177/Configuration+Persistence+Service+Developer+s+Landing+Page + email: cpsteam@est.tech + license: + name: Copyright (C) 2024 Nordix Foundation + x-audience: external-partner + x-api-id: c7fc2f5b-16bd-4bcb-8ac8-ea8d543fcc15 +tags: + - name: Operation permission + description: "Initiate a permission request on an operation." servers: -- url: / + - url: http://{hostname}/operation-permission/v1 security: -- bearerAuth: [] -tags: -- description: Execute all your policies - name: policy-executor + - bearerAuth: [] paths: - /policy-executor/api/v1/{action}: + /permissions: post: - description: Fire a Policy action - operationId: executePolicyAction + description: "Initiate permission request" + operationId: initiatePermissionRequest parameters: - - description: Bearer token may be used to identify client as part of a policy - explode: false - in: header - name: Authorization - required: false - schema: - type: string - style: simple - - description: "The policy action. Currently supported options: 'execute'" - explode: false - in: path - name: action - required: true - schema: - example: execute - type: string - style: simple + - name: Content-Type + description: This specifies the media type of the request sent by the client to the server + in: header + required: true + schema: + type: string + default: application/json + - name: Accept + description: Indicates the response media type accepted by the client. + in: header + required: false + schema: + type: string + default: application/json + - description: Bearer token may be used to identify client as part of a policy + explode: false + in: header + name: Authorization + required: false + schema: + type: string + style: simple requestBody: content: application/json: schema: - $ref: '#/components/schemas/PolicyExecutionRequest' - description: The action request body + $ref: '#/components/schemas/PermissionRequest' + description: "The permission request body" required: true responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/PolicyExecutionResponse' - description: Successful policy execution - "400": + '200': + description: "OK" content: application/json: - example: - status: 400 - message: Bad Request - details: The provided request is not valid schema: - $ref: '#/components/schemas/ErrorMessage' - description: Bad request - "401": - content: - application/json: - example: - status: 401 - message: Unauthorized request - details: This request is unauthorized - schema: - $ref: '#/components/schemas/ErrorMessage' - description: Unauthorized request - "403": - content: - application/json: - example: - status: 403 - message: Request Forbidden - details: This request is forbidden - schema: - $ref: '#/components/schemas/ErrorMessage' - description: Request forbidden - "500": - content: - application/json: - example: - status: 500 - message: Internal Server Error - details: Internal server error occurred - schema: - $ref: '#/components/schemas/ErrorMessage' - description: Internal server error + $ref: '#/components/schemas/PermissionResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '500': + $ref: '#/components/responses/InternalServerError' tags: - - policy-executor + - Operation permission components: - parameters: - actionInPath: - description: "The policy action. Currently supported options: 'execute'" - explode: false - in: path - name: action - required: true - schema: - example: execute - type: string - style: simple - authorizationInHeader: - description: Bearer token may be used to identify client as part of a policy - explode: false - in: header - name: Authorization - required: false - schema: - type: string - style: simple - responses: - BadRequest: - content: - application/json: - example: - status: 400 - message: Bad Request - details: The provided request is not valid - schema: - $ref: '#/components/schemas/ErrorMessage' - description: Bad request - Unauthorized: - content: - application/json: - example: - status: 401 - message: Unauthorized request - details: This request is unauthorized - schema: - $ref: '#/components/schemas/ErrorMessage' - description: Unauthorized request - Forbidden: - content: - application/json: - example: - status: 403 - message: Request Forbidden - details: This request is forbidden - schema: - $ref: '#/components/schemas/ErrorMessage' - description: Request forbidden - InternalServerError: - content: - application/json: - example: - status: 500 - message: Internal Server Error - details: Internal server error occurred - schema: - $ref: '#/components/schemas/ErrorMessage' - description: Internal server error - NotImplemented: - content: - application/json: - example: - status: 501 - message: Not Implemented - details: Method not implemented - schema: - $ref: '#/components/schemas/ErrorMessage' - description: Method not (yet) implemented + securitySchemes: + bearerAuth: + type: http + description: "Bearer token (from a client),used by policies to identify the client" + scheme: bearer schemas: ErrorMessage: + type: object + title: Error properties: status: type: string - message: + title: type: string details: type: string - title: Error - type: object - Request: + Operation: example: - schema: org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0 - data: "{}" + operation: update + entityHandleId: ABCD123450d7A822AB27B386829FD9E12 + resourceIdentifier: ManagedElement=Kista/GNBDUFunction=1/UECC=1 + targetIdentifier: MEContext=RadioNode-K6_0001,ManagedElement=RadioNode-K6_0001 + changeRequest: + Cell: + - id: Cell-id + attributes: + administrativeState: UNLOCKED properties: - schema: - description: The schema for the data in this request. The schema name should - include the type of operation - example: org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0 + operation: + description: Currently supported operations are 'create', 'update', 'patch', 'delete'. For other possible operation types see the client documentation. + example: update + type: string + entityHandleId: + description: A unique identifier for the network element. + example: ABCD123450d7A822AB27B386829FD9E12 type: string - data: - description: The data related to the request. The format of the object is - determined by the schema + resourceIdentifier: + description: Identifies the object in the node model. Currently supported separators are '/' and ','. For other possible format see the client documentation. + example: ManagedElement=Kista/GNBDUFunction=1/UECC=1 + type: string + targetIdentifier: + description: FDN of the target node. Currently supported separators are '/' and ','. For other possible format see the client documentation. + example: MEContext=RadioNode-K6_0001/ManagedElement=RadioNode-K6_0001 + type: string + changeRequest: + description: All the information that is required to identify which parameters and attributes of the network is changing. + example: + Cell: + - id: Cell-id + attributes: + administrativeState: UNLOCKED type: object required: - - data - - schema + - operation + - targetIdentifier type: object - PolicyExecutionRequest: + PermissionRequest: example: - decisionType: allow - requests: - - schema: org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0 - data: "{}" - - schema: org.onap.cps.ncmp.policy-executor:ncmp-create-schema:1.0.0 - data: "{}" + permissionId: 550e8400-e29b-41d4-a716-446655440000 + changeRequestFormat: cm-legacy + operations: + - operation: update + entityHandleId: ABCD123450d7A822AB27B386829FD9E12 + resourceIdentifier: ManagedElement=Kista/GNBDUFunction=1/UECC=1 + targetIdentifier: MEContext=RadioNode-K6_0001/ManagedElement=RadioNode-K6_0001 + changeRequest: + Cell: + - id: Cell-id + attributes: + administrativeState: UNLOCKED + - operation: delete + entityHandleId: DCBA123450d7A822AB27B386829FD9E12 + resourceIdentifier: ManagedElement=Kista/GNBDUFunction=1/UECC=1 + targetIdentifier: MEContext=RadioNode-K6_0002/ManagedElement=RadioNode-K6_0002 properties: - decisionType: - description: "The type of decision. Currently supported options: 'allow'" - example: allow + permissionId: + description: Unique ID for the permission request (for auditing purposes) + example: 550e8400-e29b-41d4-a716-446655440000 + type: string + changeRequestFormat: + description: Format of the change request. Currently supported 'cm-legacy'. For other possible formats see the client documentation. + example: cm-legacy type: string - requests: + operations: items: - $ref: '#/components/schemas/Request' + $ref: '#/components/schemas/Operation' type: array required: - - decisionType - - requests + - operations + - changeRequestFormat type: object - PolicyExecutionResponse: + PermissionResponse: example: - decision: deny - decisionId: 550e8400-e29b-41d4-a716-446655440000 - message: Object locked due to recent change + id: 550e8400-e29b-41d4-a716-446655440000 + permissionResult: deny + message: Object locked due to recent changes properties: - decisionId: - description: Unique ID for the decision (for auditing purposes) + id: + description: Unique ID for the permission request (for auditing purposes) example: 550e8400-e29b-41d4-a716-446655440000 type: string - decision: + permissionResult: description: "The decision outcome. Currently supported values: 'allow','deny'" example: deny type: string @@ -249,13 +199,50 @@ components: example: Object locked due to recent change type: string required: - - decision - - decisionId - - message + - id + - permissionResult + - message type: object - securitySchemes: - bearerAuth: - description: "Bearer token (from client that called CPS-NCMP),used by policies\ - \ to identify the client" - scheme: bearer - type: http + + responses: + BadRequest: + description: "Bad Request" + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: '400' + title: "Bad Request" + details: "The provided request is not valid" + Unauthorized: + description: "Unauthorized request" + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: '401' + title: "Unauthorized request" + details: "This request is unauthorized" + Forbidden: + description: "Forbidden" + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: '403' + title: "Request Forbidden" + details: "This request is forbidden" + + InternalServerError: + description: "Internal Server Error" + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ErrorMessage' + example: + status: '500' + title: "Internal Server Error" + details: "Internal server error occurred" diff --git a/docs/ncmp-data-operation.rst b/docs/ncmp-data-operation.rst index 3352e03cf0..f2f3a476bb 100644 --- a/docs/ncmp-data-operation.rst +++ b/docs/ncmp-data-operation.rst @@ -21,6 +21,13 @@ For all data operations on cm handle(s), we have a post endpoint: - When asynchronous (with topic) operations are executed, a request id (UUID) will be returned. +**Note.** The client topic is validated to ensure it adheres to Kafka's topic naming conventions. Additionally, if a client specifies a topic that does not exist, the request might be delayed. To enable a fail-fast mechanism, the max.block.ms parameter can be adjusted to define the maximum duration the request is allowed to block. The parameter is 60000ms by default but can be set to a lower value. + +.. code:: bash + + spring.kafka.producer.properties.max.block.ms: <value_in_ms> + + Request Body ============ diff --git a/docs/release-notes.rst b/docs/release-notes.rst index d60c2c0d73..9c825e4d35 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -38,7 +38,6 @@ Release Data Bug Fixes --------- - Features -------- diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy index 0725fe82d0..16b4460492 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy @@ -22,6 +22,7 @@ package org.onap.cps.integration.base import com.hazelcast.collection.ISet +import com.hazelcast.map.IMap import okhttp3.mockwebserver.MockWebServer import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService @@ -30,7 +31,7 @@ import org.onap.cps.api.CpsModuleService import org.onap.cps.api.CpsQueryService import org.onap.cps.integration.DatabaseTestContainer import org.onap.cps.integration.KafkaTestContainer -import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade @@ -108,7 +109,7 @@ abstract class CpsIntegrationSpecBase extends Specification { NetworkCmProxyFacade networkCmProxyFacade @Autowired - NetworkCmProxyInventoryFacade NetworkCmProxyInventoryFacade + NetworkCmProxyInventoryFacadeImpl NetworkCmProxyInventoryFacade @Autowired NetworkCmProxyQueryService networkCmProxyQueryService @@ -123,6 +124,9 @@ abstract class CpsIntegrationSpecBase extends Specification { BlockingQueue<String> moduleSyncWorkQueue @Autowired + IMap<String, String> cpsAndNcmpLock + + @Autowired JsonObjectMapper jsonObjectMapper @Autowired diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy index b08d1c1548..f1e5449476 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy @@ -46,22 +46,22 @@ class PolicyDispatcher extends Dispatcher { return new MockResponse().setResponseCode(401) } - if (recordedRequest.path != '/policy-executor/api/v1/execute') { + if (recordedRequest.path != '/operation-permission/v1/permissions') { return new MockResponse().setResponseCode(400) } def body = objectMapper.readValue(recordedRequest.getBody().readUtf8(), Map.class) - def targetIdentifier = body.get('requests').get(0).get('data').get('targetIdentifier') + def targetIdentifier = body.get('operations').get(0).get('targetIdentifier') def responseAsMap = [:] - responseAsMap.put('decisionId',1) + responseAsMap.put('id',1) if (targetIdentifier == "mock slow response") { TimeUnit.SECONDS.sleep(2) // One second more then configured readTimeoutInSeconds } if (allowAll || targetIdentifier == 'fdn1') { - responseAsMap.put('decision','allow') + responseAsMap.put('permissionResult','allow') responseAsMap.put('message','') } else { - responseAsMap.put('decision','deny from mock server (dispatcher)') + responseAsMap.put('permissionResult','deny from mock server (dispatcher)') responseAsMap.put('message','I only like fdn1') } def responseAsString = objectMapper.writeValueAsString(responseAsMap) diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy index ffcba025e8..e9fac48676 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy @@ -25,7 +25,7 @@ import org.apache.kafka.common.serialization.StringDeserializer import org.onap.cps.integration.KafkaTestContainer import org.onap.cps.integration.base.CpsIntegrationSpecBase import org.onap.cps.ncmp.api.NcmpResponseStatus -import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle @@ -38,7 +38,7 @@ import java.time.Duration class CmHandleCreateSpec extends CpsIntegrationSpecBase { - NetworkCmProxyInventoryFacade objectUnderTest + NetworkCmProxyInventoryFacadeImpl objectUnderTest def uniqueId = 'ch-unique-id-for-create-test' static KafkaConsumer kafkaConsumer diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy index 67011f811b..f2593ce587 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy @@ -22,14 +22,14 @@ package org.onap.cps.integration.functional.ncmp import org.onap.cps.integration.base.CpsIntegrationSpecBase import org.onap.cps.ncmp.api.NcmpResponseStatus -import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle class CmHandleUpdateSpec extends CpsIntegrationSpecBase { - NetworkCmProxyInventoryFacade objectUnderTest + NetworkCmProxyInventoryFacadeImpl objectUnderTest def setup() { objectUnderTest = networkCmProxyInventoryFacade diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy index a5e3daf289..5ce5658c1b 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy @@ -21,7 +21,7 @@ package org.onap.cps.integration.functional.ncmp import org.onap.cps.integration.base.CpsIntegrationSpecBase -import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration import org.onap.cps.ncmp.api.inventory.models.UpgradedCmHandles @@ -31,7 +31,7 @@ import spock.util.concurrent.PollingConditions class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { - NetworkCmProxyInventoryFacade objectUnderTest + NetworkCmProxyInventoryFacadeImpl objectUnderTest def cmHandleId = 'ch-1' def cmHandleIdWithExistingModuleSetTag = 'ch-2' diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/ModuleSyncWatchdogIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/ModuleSyncWatchdogIntegrationSpec.groovy index 43bcbdb4f4..a6e56ab22d 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/ModuleSyncWatchdogIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/ModuleSyncWatchdogIntegrationSpec.groovy @@ -151,6 +151,15 @@ class ModuleSyncWatchdogIntegrationSpec extends CpsIntegrationSpecBase { } } + def populateQueueWithoutDelayCallable = () -> { + try { + objectUnderTest.populateWorkQueueIfNeeded() + return 'task acquired the lock first' + } catch (InterruptedException e) { + e.printStackTrace() + } + } + def populateQueueWithDelay = () -> { try { Thread.sleep(10) diff --git a/k6-tests/ncmp/common/utils.js b/k6-tests/ncmp/common/utils.js index b937b4beb3..8e68edd438 100644 --- a/k6-tests/ncmp/common/utils.js +++ b/k6-tests/ncmp/common/utils.js @@ -24,6 +24,7 @@ export const testConfig = JSON.parse(open(`../config/${__ENV.TEST_PROFILE}.json` export const KAFKA_BOOTSTRAP_SERVERS = testConfig.hosts.kafkaBootstrapServer; export const NCMP_BASE_URL = testConfig.hosts.ncmpBaseUrl; export const DMI_PLUGIN_URL = testConfig.hosts.dmiStubUrl; +export const CONTAINER_UP_TIME_IN_SECONDS = testConfig.hosts.containerUpTimeInSeconds; export const LEGACY_BATCH_TOPIC_NAME = 'legacy_batch_topic'; export const TOTAL_CM_HANDLES = 20000; export const REGISTRATION_BATCH_SIZE = 100; @@ -89,21 +90,21 @@ export function makeCustomSummaryReport(testResults, scenarioConfig) { makeSummaryCsvLine('0', 'HTTP request failures for all tests', 'rate of failed requests', 'http_req_failed', 0, testResults, scenarioConfig), makeSummaryCsvLine('1', 'Registration of CM-handles', 'CM-handles/second', 'cmhandles_created_per_second', 50, testResults, scenarioConfig), makeSummaryCsvLine('2', 'De-registration of CM-handles', 'CM-handles/second', 'cmhandles_deleted_per_second', 100, testResults, scenarioConfig), - makeSummaryCsvLine('3a', 'CM-handle ID search with No filter', 'milliseconds', 'id_search_nofilter_duration', 500, testResults, scenarioConfig), - makeSummaryCsvLine('3b', 'CM-handle ID search with Module filter', 'milliseconds', 'id_search_module_duration', 500, testResults, scenarioConfig), - makeSummaryCsvLine('3c', 'CM-handle ID search with Property filter', 'milliseconds', 'id_search_property_duration', 1200, testResults, scenarioConfig), - makeSummaryCsvLine('3d', 'CM-handle ID search with Cps Path filter', 'milliseconds', 'id_search_cpspath_duration', 1200, testResults, scenarioConfig), - makeSummaryCsvLine('3e', 'CM-handle ID search with Trust Level filter', 'milliseconds', 'id_search_trustlevel_duration', 4200, testResults, scenarioConfig), - makeSummaryCsvLine('4a', 'CM-handle search with No filter', 'milliseconds', 'cm_search_nofilter_duration', 8000, testResults, scenarioConfig), - makeSummaryCsvLine('4b', 'CM-handle search with Module filter', 'milliseconds', 'cm_search_module_duration', 10000, testResults, scenarioConfig), - makeSummaryCsvLine('4c', 'CM-handle search with Property filter', 'milliseconds', 'cm_search_property_duration', 10000, testResults, scenarioConfig), - makeSummaryCsvLine('4d', 'CM-handle search with Cps Path filter', 'milliseconds', 'cm_search_cpspath_duration', 10000, testResults, scenarioConfig), - makeSummaryCsvLine('4e', 'CM-handle search with Trust Level filter', 'milliseconds', 'cm_search_trustlevel_duration', 13000, testResults, scenarioConfig), - makeSummaryCsvLine('5a', 'NCMP overhead for Synchronous single CM-handle pass-through read', 'milliseconds', 'ncmp_overhead_passthrough_read', 30, testResults, scenarioConfig), - makeSummaryCsvLine('5b', 'NCMP overhead for Synchronous single CM-handle pass-through read with alternate id', 'milliseconds', 'ncmp_overhead_passthrough_read_alt_id', 60, testResults, scenarioConfig), - makeSummaryCsvLine('6a', 'NCMP overhead for Synchronous single CM-handle pass-through write', 'milliseconds', 'ncmp_overhead_passthrough_write', 30, testResults, scenarioConfig), - makeSummaryCsvLine('6b', 'NCMP overhead for Synchronous single CM-handle pass-through write with alternate id', 'milliseconds', 'ncmp_overhead_passthrough_write_alt_id', 60, testResults, scenarioConfig), - makeSummaryCsvLine('7', 'Legacy batch read operation', 'events/second', 'legacy_batch_read_cmhandles_per_second', 1500, testResults, scenarioConfig), + makeSummaryCsvLine('3a', 'CM-handle ID search with No filter', 'milliseconds', 'id_search_nofilter_duration', 300, testResults, scenarioConfig), + makeSummaryCsvLine('3b', 'CM-handle ID search with Module filter', 'milliseconds', 'id_search_module_duration', 300, testResults, scenarioConfig), + makeSummaryCsvLine('3c', 'CM-handle ID search with Property filter', 'milliseconds', 'id_search_property_duration', 750, testResults, scenarioConfig), + makeSummaryCsvLine('3d', 'CM-handle ID search with Cps Path filter', 'milliseconds', 'id_search_cpspath_duration', 750, testResults, scenarioConfig), + makeSummaryCsvLine('3e', 'CM-handle ID search with Trust Level filter', 'milliseconds', 'id_search_trustlevel_duration', 3000, testResults, scenarioConfig), + makeSummaryCsvLine('4a', 'CM-handle search with No filter', 'milliseconds', 'cm_search_nofilter_duration', 3000, testResults, scenarioConfig), + makeSummaryCsvLine('4b', 'CM-handle search with Module filter', 'milliseconds', 'cm_search_module_duration', 4000, testResults, scenarioConfig), + makeSummaryCsvLine('4c', 'CM-handle search with Property filter', 'milliseconds', 'cm_search_property_duration', 4500, testResults, scenarioConfig), + makeSummaryCsvLine('4d', 'CM-handle search with Cps Path filter', 'milliseconds', 'cm_search_cpspath_duration', 4500, testResults, scenarioConfig), + makeSummaryCsvLine('4e', 'CM-handle search with Trust Level filter', 'milliseconds', 'cm_search_trustlevel_duration', 7000, testResults, scenarioConfig), + makeSummaryCsvLine('5a', 'NCMP overhead for Synchronous single CM-handle pass-through read', 'milliseconds', 'ncmp_overhead_passthrough_read', 20, testResults, scenarioConfig), + makeSummaryCsvLine('5b', 'NCMP overhead for Synchronous single CM-handle pass-through read with alternate id', 'milliseconds', 'ncmp_overhead_passthrough_read_alt_id', 40, testResults, scenarioConfig), + makeSummaryCsvLine('6a', 'NCMP overhead for Synchronous single CM-handle pass-through write', 'milliseconds', 'ncmp_overhead_passthrough_write', 20, testResults, scenarioConfig), + makeSummaryCsvLine('6b', 'NCMP overhead for Synchronous single CM-handle pass-through write with alternate id', 'milliseconds', 'ncmp_overhead_passthrough_write_alt_id', 40, testResults, scenarioConfig), + makeSummaryCsvLine('7', 'Legacy batch read operation', 'events/second', 'legacy_batch_read_cmhandles_per_second', 300, testResults, scenarioConfig), ]; return summaryCsvLines.join('\n') + '\n'; } diff --git a/k6-tests/ncmp/config/endurance.json b/k6-tests/ncmp/config/endurance.json index d215d0a1e1..d4893a45cc 100644 --- a/k6-tests/ncmp/config/endurance.json +++ b/k6-tests/ncmp/config/endurance.json @@ -2,7 +2,8 @@ "hosts": { "ncmpBaseUrl": "http://localhost:8884", "dmiStubUrl": "http://ncmp-dmi-plugin-demo-and-csit-stub:8092", - "kafkaBootstrapServer": "localhost:9093" + "kafkaBootstrapServer": "localhost:9093", + "containerUpTimeInSeconds": 420 }, "scenarios": { "passthrough_read_scenario": { diff --git a/k6-tests/ncmp/config/kpi.json b/k6-tests/ncmp/config/kpi.json index 742321f709..eed041de85 100644 --- a/k6-tests/ncmp/config/kpi.json +++ b/k6-tests/ncmp/config/kpi.json @@ -2,93 +2,140 @@ "hosts": { "ncmpBaseUrl": "http://localhost:8883", "dmiStubUrl": "http://ncmp-dmi-plugin-demo-and-csit-stub:8092", - "kafkaBootstrapServer": "localhost:9092" + "kafkaBootstrapServer": "localhost:9092", + "containerUpTimeInSeconds": 300 }, "scenarios": { "passthrough_read_scenario": { - "executor": "constant-vus", + "executor": "constant-arrival-rate", "exec": "passthroughReadScenario", - "vus": 2, - "duration": "15m" + "rate": 5, + "timeUnit": "1s", + "duration": "15m", + "preAllocatedVUs": 5, + "startTime": "0ms" }, "passthrough_read_alt_id_scenario": { - "executor": "constant-vus", + "executor": "constant-arrival-rate", "exec": "passthroughReadAltIdScenario", - "vus": 2, - "duration": "15m" + "rate": 5, + "timeUnit": "1s", + "duration": "15m", + "preAllocatedVUs": 5, + "startTime": "200ms" }, + "passthrough_write_scenario": { - "executor": "constant-vus", + "executor": "constant-arrival-rate", "exec": "passthroughWriteScenario", - "vus": 2, - "duration": "15m" + "rate": 5, + "timeUnit": "1s", + "duration": "15m", + "preAllocatedVUs": 5, + "startTime": "400ms" }, "passthrough_write_alt_id_scenario": { - "executor": "constant-vus", + "executor": "constant-arrival-rate", "exec": "passthroughWriteAltIdScenario", - "vus": 2, - "duration": "15m" + "rate": 5, + "timeUnit": "1s", + "duration": "15m", + "preAllocatedVUs": 5, + "startTime": "600ms" }, + "cm_handle_id_search_nofilter_scenario": { - "executor": "constant-vus", + "executor": "constant-arrival-rate", "exec": "cmHandleIdSearchNoFilterScenario", - "vus": 1, - "duration": "15m" - }, - "cm_handle_search_nofilter_scenario": { - "executor": "constant-vus", - "exec": "cmHandleSearchNoFilterScenario", - "vus": 1, - "duration": "15m" + "rate": 1, + "timeUnit": "2s", + "duration": "15m", + "preAllocatedVUs": 1, + "startTime": "0ms" }, "cm_handle_id_search_module_scenario": { - "executor": "constant-vus", + "executor": "constant-arrival-rate", "exec": "cmHandleIdSearchModuleScenario", - "vus": 1, - "duration": "15m" - }, - "cm_handle_search_module_scenario": { - "executor": "constant-vus", - "exec": "cmHandleSearchModuleScenario", - "vus": 1, - "duration": "15m" + "rate": 1, + "timeUnit": "2s", + "duration": "15m", + "preAllocatedVUs": 1, + "startTime": "400ms" }, "cm_handle_id_search_property_scenario": { - "executor": "constant-vus", + "executor": "constant-arrival-rate", "exec": "cmHandleIdSearchPropertyScenario", - "vus": 1, - "duration": "15m" - }, - "cm_handle_search_property_scenario": { - "executor": "constant-vus", - "exec": "cmHandleSearchPropertyScenario", - "vus": 1, - "duration": "15m" + "rate": 1, + "timeUnit": "2s", + "duration": "15m", + "preAllocatedVUs": 1, + "startTime": "800ms" }, "cm_handle_id_search_cpspath_scenario": { - "executor": "constant-vus", + "executor": "constant-arrival-rate", "exec": "cmHandleIdSearchCpsPathScenario", - "vus": 1, - "duration": "15m" - }, - "cm_handle_search_cpspath_scenario": { - "executor": "constant-vus", - "exec": "cmHandleSearchCpsPathScenario", - "vus": 1, - "duration": "15m" + "rate": 1, + "timeUnit": "2s", + "duration": "15m", + "preAllocatedVUs": 1, + "startTime": "1200ms" }, "cm_handle_id_search_trustlevel_scenario": { - "executor": "constant-vus", + "executor": "constant-arrival-rate", "exec": "cmHandleIdSearchTrustLevelScenario", - "vus": 1, - "duration": "15m" + "rate": 1, + "timeUnit": "2s", + "duration": "15m", + "preAllocatedVUs": 1, + "startTime": "1600ms" + }, + + "cm_handle_search_nofilter_scenario": { + "executor": "constant-arrival-rate", + "exec": "cmHandleSearchNoFilterScenario", + "rate": 1, + "timeUnit": "15s", + "duration": "15m", + "preAllocatedVUs": 1, + "startTime": "0s" + }, + "cm_handle_search_module_scenario": { + "executor": "constant-arrival-rate", + "exec": "cmHandleSearchModuleScenario", + "rate": 1, + "timeUnit": "15s", + "duration": "15m", + "preAllocatedVUs": 1, + "startTime": "3s" + }, + "cm_handle_search_property_scenario": { + "executor": "constant-arrival-rate", + "exec": "cmHandleSearchPropertyScenario", + "rate": 1, + "timeUnit": "15s", + "duration": "15m", + "preAllocatedVUs": 1, + "startTime": "6s" + }, + "cm_handle_search_cpspath_scenario": { + "executor": "constant-arrival-rate", + "exec": "cmHandleSearchCpsPathScenario", + "rate": 1, + "timeUnit": "15s", + "duration": "15m", + "preAllocatedVUs": 1, + "startTime": "9s" }, "cm_handle_search_trustlevel_scenario": { - "executor": "constant-vus", + "executor": "constant-arrival-rate", "exec": "cmHandleSearchTrustLevelScenario", - "vus": 1, - "duration": "15m" + "rate": 1, + "timeUnit": "15s", + "duration": "15m", + "preAllocatedVUs": 1, + "startTime": "12s" }, + "legacy_batch_produce_scenario": { "executor": "shared-iterations", "exec": "legacyBatchProduceScenario", @@ -122,4 +169,4 @@ "cm_search_trustlevel_duration": ["avg <= 15000"], "legacy_batch_read_cmhandles_per_second": ["avg >= 150"] } -}
\ No newline at end of file +} diff --git a/k6-tests/ncmp/ncmp-test-runner.js b/k6-tests/ncmp/ncmp-test-runner.js index e33ff1852a..9ab326c44c 100644 --- a/k6-tests/ncmp/ncmp-test-runner.js +++ b/k6-tests/ncmp/ncmp-test-runner.js @@ -18,14 +18,14 @@ * ============LICENSE_END========================================================= */ -import { check } from 'k6'; +import { check, sleep } from 'k6'; import { Trend } from 'k6/metrics'; import { Reader } from 'k6/x/kafka'; import { TOTAL_CM_HANDLES, READ_DATA_FOR_CM_HANDLE_DELAY_MS, WRITE_DATA_FOR_CM_HANDLE_DELAY_MS, makeCustomSummaryReport, makeBatchOfCmHandleIds, LEGACY_BATCH_THROUGHPUT_TEST_BATCH_SIZE, REGISTRATION_BATCH_SIZE, LEGACY_BATCH_THROUGHPUT_TEST_NUMBER_OF_REQUESTS, KAFKA_BOOTSTRAP_SERVERS, - LEGACY_BATCH_TOPIC_NAME, testConfig + LEGACY_BATCH_TOPIC_NAME, CONTAINER_UP_TIME_IN_SECONDS, testConfig } from './common/utils.js'; import { createCmHandles, deleteCmHandles, waitForAllCmHandlesToBeReady } from './common/cmhandle-crud.js'; import { executeCmHandleSearch, executeCmHandleIdSearch } from './common/search-base.js'; @@ -97,6 +97,8 @@ export function teardown() { const totalDeregistrationTimeInSeconds = (endTimeInMillis - startTimeInMillis) / 1000.0; cmHandlesDeletedPerSecondTrend.add(DEREGISTERED_CM_HANDLES / totalDeregistrationTimeInSeconds); + + sleep(CONTAINER_UP_TIME_IN_SECONDS); } export function passthroughReadScenario() { diff --git a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java index 88073c0a0f..aef27a6389 100644 --- a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java +++ b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubController.java @@ -20,72 +20,66 @@ package org.onap.cps.policyexecutor.stub.controller; -import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.policyexecutor.stub.api.PolicyExecutorApi; -import org.onap.cps.policyexecutor.stub.model.NcmpDelete; -import org.onap.cps.policyexecutor.stub.model.PolicyExecutionRequest; -import org.onap.cps.policyexecutor.stub.model.PolicyExecutionResponse; -import org.onap.cps.policyexecutor.stub.model.Request; +import org.onap.cps.policyexecutor.stub.api.OperationPermissionApi; +import org.onap.cps.policyexecutor.stub.model.Operation; +import org.onap.cps.policyexecutor.stub.model.PermissionRequest; +import org.onap.cps.policyexecutor.stub.model.PermissionResponse; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController +@RequestMapping("/operation-permission/v1") @RequiredArgsConstructor @Slf4j -public class PolicyExecutorStubController implements PolicyExecutorApi { +public class PolicyExecutorStubController implements OperationPermissionApi { private final Sleeper sleeper; - private final ObjectMapper objectMapper; private static final Pattern ERROR_CODE_PATTERN = Pattern.compile("(\\d{3})"); private int decisionCounter = 0; private static int slowResponseTimeInSeconds = 40; @Override - public ResponseEntity<PolicyExecutionResponse> executePolicyAction( - final String action, - final PolicyExecutionRequest policyExecutionRequest, - final String authorization) { - log.info("Stub Policy Executor Invoked (only supports 'delete' operations)"); - if (policyExecutionRequest.getRequests().isEmpty()) { + public ResponseEntity<PermissionResponse> initiatePermissionRequest(final String contentType, + final PermissionRequest permissionRequest, + final String accept, + final String authorization) { + log.info("Stub Policy Executor Invoked"); + if (permissionRequest.getOperations().isEmpty()) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } - final Request firstRequest = policyExecutionRequest.getRequests().iterator().next(); - log.info("1st Request Schema:{}", firstRequest.getSchema()); - if (firstRequest.getSchema().contains("ncmp-delete-schema:1.0.0")) { - return handleNcmpDeleteSchema(firstRequest); + final Operation firstOperation = permissionRequest.getOperations().iterator().next(); + log.info("1st Operation: {}", firstOperation.getOperation()); + if (!"delete".equals(firstOperation.getOperation()) && firstOperation.getChangeRequest() == null) { + log.warn("Change Request is required for " + firstOperation.getOperation() + " operations"); + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } - log.warn("This stub only supports 'delete' operations"); - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + return handleOperation(firstOperation); } - private ResponseEntity<PolicyExecutionResponse> handleNcmpDeleteSchema(final Request request) { - final NcmpDelete ncmpDelete = objectMapper.convertValue(request.getData(), NcmpDelete.class); - - final String targetIdentifier = ncmpDelete.getTargetIdentifier(); - - if (targetIdentifier == null) { - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); - } + private ResponseEntity<PermissionResponse> handleOperation(final Operation operation) { + final String targetIdentifier = operation.getTargetIdentifier(); final Matcher matcher = ERROR_CODE_PATTERN.matcher(targetIdentifier); if (matcher.find()) { final int errorCode = Integer.parseInt(matcher.group(1)); + log.warn("Stub is mocking an error response, code: " + errorCode); return new ResponseEntity<>(HttpStatusCode.valueOf(errorCode)); } return createPolicyExecutionResponse(targetIdentifier); } - private ResponseEntity<PolicyExecutionResponse> createPolicyExecutionResponse(final String targetIdentifier) { - final String decisionId = String.valueOf(++decisionCounter); - final String decision; + private ResponseEntity<PermissionResponse> createPolicyExecutionResponse(final String targetIdentifier) { + final String id = String.valueOf(++decisionCounter); + final String permissionResult; final String message; if (targetIdentifier.toLowerCase(Locale.getDefault()).contains("slow")) { try { @@ -96,17 +90,14 @@ public class PolicyExecutorStubController implements PolicyExecutorApi { } } if (targetIdentifier.toLowerCase(Locale.getDefault()).contains("cps-is-great")) { - decision = "allow"; + permissionResult = "allow"; message = "All good"; } else { - decision = "deny"; + permissionResult = "deny"; message = "Only FDNs containing 'cps-is-great' are allowed"; } - log.info("Decision: {} ({})", decision, message); - final PolicyExecutionResponse policyExecutionResponse = - new PolicyExecutionResponse(decisionId, decision, message); - - return ResponseEntity.ok(policyExecutionResponse); + log.info("Decision: {} ({})", permissionResult, message); + return ResponseEntity.ok(new PermissionResponse(id, permissionResult, message)); } } diff --git a/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy index 44460daa7e..75bd676b2f 100644 --- a/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy +++ b/policy-executor-stub/src/test/groovy/org/onap/cps/policyexecutor/stub/controller/PolicyExecutorStubControllerSpec.groovy @@ -21,10 +21,9 @@ package org.onap.cps.policyexecutor.stub.controller import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.policyexecutor.stub.model.NcmpDelete -import org.onap.cps.policyexecutor.stub.model.PolicyExecutionRequest -import org.onap.cps.policyexecutor.stub.model.PolicyExecutionResponse -import org.onap.cps.policyexecutor.stub.model.Request +import org.onap.cps.policyexecutor.stub.model.Operation +import org.onap.cps.policyexecutor.stub.model.PermissionRequest +import org.onap.cps.policyexecutor.stub.model.PermissionResponse import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest @@ -47,14 +46,14 @@ class PolicyExecutorStubControllerSpec extends Specification { @SpringBean Sleeper sleeper = Spy() - def url = '/policy-executor/api/v1/some-action' + def url = '/operation-permission/v1/permissions' def setup() { PolicyExecutorStubController.slowResponseTimeInSeconds = 1 } - def 'Execute policy action.'() { - given: 'a policy execution request with target: #targetIdentifier' + def 'Permission request with #targetIdentifier.'() { + given: 'a permission request with target: #targetIdentifier' def requestBody = createRequestBody(targetIdentifier) when: 'request is posted' def response = mockMvc.perform(post(url) @@ -66,19 +65,19 @@ class PolicyExecutorStubControllerSpec extends Specification { assert response.status == HttpStatus.OK.value() and: 'the response body has the expected decision details' def responseBody = response.contentAsString - def policyExecutionResponse = objectMapper.readValue(responseBody, PolicyExecutionResponse.class) - assert policyExecutionResponse.decisionId == expectedDecsisonId - assert policyExecutionResponse.decision == expectedDecision - assert policyExecutionResponse.message == expectedMessage + def permissionResponse = objectMapper.readValue(responseBody, PermissionResponse.class) + assert permissionResponse.id == expectedId + assert permissionResponse.permissionResult == expectedResult + assert permissionResponse.message == expectedMessage where: 'the following targets are used' - targetIdentifier || expectedDecsisonId | expectedDecision | expectedMessage - 'some fdn' || '1' | 'deny' | "Only FDNs containing 'cps-is-great' are allowed" - 'fdn with cps-is-great' || '2' | 'allow' | 'All good' - 'slow' || '3' | 'deny' | "Only FDNs containing 'cps-is-great' are allowed" + targetIdentifier || expectedId | expectedResult | expectedMessage + 'some fdn' || '1' | 'deny' | "Only FDNs containing 'cps-is-great' are allowed" + 'fdn with cps-is-great' || '2' | 'allow' | 'All good' + 'slow' || '3' | 'deny' | "Only FDNs containing 'cps-is-great' are allowed" } - def 'Execute policy action with a HTTP error code.'() { - given: 'a policy execution request with a target fdn with a 3-digit error code' + def 'Permission request with a HTTP error code.'() { + given: 'a permission request with a target fdn with a 3-digit error code' def requestBody = createRequestBody('target with error code 418') when: 'request is posted' def response = mockMvc.perform(post(url) @@ -90,8 +89,8 @@ class PolicyExecutorStubControllerSpec extends Specification { assert response.status == 418 } - def 'Execute policy action without authorization header.'() { - given: 'a valid policy execution request' + def 'Permission request without authorization header.'() { + given: 'a valid permission request' def requestBody = createRequestBody('some target') when: 'request is posted without authorization header' def response = mockMvc.perform(post(url) @@ -102,10 +101,10 @@ class PolicyExecutorStubControllerSpec extends Specification { assert response.status == HttpStatus.OK.value() } - def 'Execute policy action with no requests.'() { - given: 'a policy execution request' - def policyExecutionRequest = new PolicyExecutionRequest('some decision type', []) - def requestBody = objectMapper.writeValueAsString(policyExecutionRequest) + def 'Permission request with no operations.'() { + given: 'a permission request with no operations' + def permissionRequest = new PermissionRequest('some decision type', []) + def requestBody = objectMapper.writeValueAsString(permissionRequest) when: 'request is posted' def response = mockMvc.perform(post(url) .header('Authorization','some string') @@ -116,8 +115,8 @@ class PolicyExecutorStubControllerSpec extends Specification { assert response.status == HttpStatus.BAD_REQUEST.value() } - def 'Execute policy action with invalid json for request data.'() { - when: 'request is posted' + def 'Request with invalid json for request data.'() { + when: 'request with invalid json is posted' def response = mockMvc.perform(post(url) .header('Authorization','some string') .contentType(MediaType.APPLICATION_JSON) @@ -127,8 +126,8 @@ class PolicyExecutorStubControllerSpec extends Specification { assert response.status == HttpStatus.BAD_REQUEST.value() } - def 'Execute policy action with interrupted exception during slow response.'() { - given: 'a policy execution request with target: "slow"' + def 'Permission request with interrupted exception during slow response.'() { + given: 'a permission request with target: "slow" (stub will be slow)' def requestBody = createRequestBody('slow') sleeper.haveALittleRest(_) >> { throw new InterruptedException() } when: 'request is posted' @@ -140,9 +139,9 @@ class PolicyExecutorStubControllerSpec extends Specification { noExceptionThrown() } - def 'Execute policy action with missing or invalid attributes.'() { - given: 'a policy execution request with decisionType=#decisionType, schema=#schema, targetIdentifier=#targetIdentifier' - def requestBody = createRequestBody(decisionType, schema, targetIdentifier) + def 'Permission request with missing or invalid attributes.'() { + given: 'Permission request with operation=#operation and targetIdentifier=#targetIdentifier' + def requestBody = createRequestBody(operation, targetIdentifier, changeRequest) when: 'request is posted' def response = mockMvc.perform(post(url) .header('Authorization','something') @@ -152,22 +151,22 @@ class PolicyExecutorStubControllerSpec extends Specification { then: 'response status as expected' assert response.status == expectedStatus.value() where: 'following parameters are used' - decisionType | schema | targetIdentifier || expectedStatus - 'something' | 'ncmp-delete-schema:1.0.0' | 'something' || HttpStatus.OK - null | 'ncmp-delete-schema:1.0.0' | 'something' || HttpStatus.BAD_REQUEST - 'something' | 'other schema' | 'something' || HttpStatus.BAD_REQUEST - 'something' | 'ncmp-delete-schema:1.0.0' | null || HttpStatus.BAD_REQUEST + operation | targetIdentifier | changeRequest || expectedStatus + 'delete' | 'something' | null || HttpStatus.OK + 'other' | 'something' | '{}' || HttpStatus.OK + 'delete' | null | null || HttpStatus.BAD_REQUEST + 'other' | 'something' | null || HttpStatus.BAD_REQUEST } - def createRequestBody(decisionType, schema, targetIdentifier) { - def ncmpDelete = new NcmpDelete(targetIdentifier: targetIdentifier) - def request = new Request(schema, ncmpDelete) - def policyExecutionRequest = new PolicyExecutionRequest(decisionType, [request]) - return objectMapper.writeValueAsString(policyExecutionRequest) + def createRequestBody(targetIdentifier) { + return createRequestBody('delete', targetIdentifier, '{}') } - def createRequestBody(targetIdentifier) { - return createRequestBody('some decision type', 'ncmp-delete-schema:1.0.0', targetIdentifier) + def createRequestBody(operationName, targetIdentifier, changeRequest) { + def operation = new Operation(operationName, targetIdentifier) + operation.setChangeRequest(changeRequest) + def permissionRequest = new PermissionRequest('cm-legacy', [operation]) + return objectMapper.writeValueAsString(permissionRequest) } } |