From 0f6f416f45d5ad0e9bba67d459858995196f7a73 Mon Sep 17 00:00:00 2001 From: leventecsanyi Date: Thu, 11 Jan 2024 12:53:26 +0100 Subject: Introduce Hazelcast for alternateId-cmHandle relation - added new Hazelcast config - added cache initialization in CmHandleIdMapper - added unit tests - updated deployment.rst - refactored cache updating Issue-ID: CPS-1988 Change-Id: Iea6f884e584bf8cea8612ddbced4329e783c60a5 Signed-off-by: leventecsanyi --- .../api/NetworkCmProxyCmHandleQueryService.java | 9 +- .../NetworkCmProxyCmHandleQueryServiceImpl.java | 13 +-- .../api/impl/NetworkCmProxyDataServiceImpl.java | 21 +++- .../NetworkCmProxyDataServicePropertyHandler.java | 17 ++- .../embeddedcache/AlternateIdCacheConfig.java | 59 +++++++++++ .../cps/ncmp/api/impl/utils/CmHandleIdMapper.java | 89 ++++++++++++++++ ...rkCmProxyDataServiceImplRegistrationSpec.groovy | 16 ++- .../impl/NetworkCmProxyDataServiceImplSpec.groovy | 10 +- ...orkCmProxyDataServicePropertyHandlerSpec.groovy | 83 +++++++-------- .../AlternateIdCacheConfigSpec.groovy | 59 +++++++++++ .../api/impl/utils/CmHandleIdMapperSpec.groovy | 117 +++++++++++++++++++++ docs/deployment.rst | 48 +++++---- 12 files changed, 450 insertions(+), 91 deletions(-) create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfig.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapper.java create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfigSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapperSpec.groovy diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandleQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandleQueryService.java index f29fd687b0..06522f80cf 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandleQueryService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandleQueryService.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,4 +61,11 @@ public interface NetworkCmProxyCmHandleQueryService { * @return collection of cm handles */ Collection queryCmHandles(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); + + /** + * Query and return all cm handle objects. + * + * @return collection of cm handles + */ + Collection getAllCmHandles(); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceImpl.java index 1f6c94869b..6f92a9efec 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceImpl.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -97,6 +97,12 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH return getNcmpServiceCmHandles(cmHandleIds); } + @Override + public Collection getAllCmHandles() { + final DataNode dataNode = inventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT).iterator().next(); + return dataNode.getChildDataNodes().stream().map(this::createNcmpServiceCmHandle).collect(Collectors.toSet()); + } + private Collection queryCmHandlesByDmiPlugin( final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { final Map dmiPropertyQueryPairs = @@ -216,11 +222,6 @@ public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmH return Collections.emptyList(); } - private Collection getAllCmHandles() { - final DataNode dataNode = inventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT).iterator().next(); - return dataNode.getChildDataNodes().stream().map(this::createNcmpServiceCmHandle).collect(Collectors.toSet()); - } - private Collection getAllCmHandleIds() { final DataNode dataNode = inventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT, DIRECT_CHILDREN_ONLY) .iterator().next(); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java index 469d75ab94..3fa0504549 100755 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 highstreet technologies GmbH - * Modifications Copyright (C) 2021-2023 Nordix Foundation + * 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. @@ -48,6 +48,7 @@ import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.onap.cps.api.CpsDataService; import org.onap.cps.ncmp.api.NcmpResponseStatus; import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService; @@ -65,6 +66,7 @@ import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations; import org.onap.cps.ncmp.api.impl.operations.OperationType; import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel; import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager; +import org.onap.cps.ncmp.api.impl.utils.CmHandleIdMapper; import org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions; import org.onap.cps.ncmp.api.impl.utils.InventoryQueryConditions; import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; @@ -104,10 +106,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService private final IMap moduleSyncStartedOnCmHandles; private final Map trustLevelPerDmiPlugin; private final TrustLevelManager trustLevelManager; + private final CmHandleIdMapper cmHandleIdMapper; @Override public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule( final DmiPluginRegistration dmiPluginRegistration) { + cacheAlternateIds(dmiPluginRegistration.getCreatedCmHandles()); dmiPluginRegistration.validateDmiPluginRegistration(); final DmiPluginRegistrationResponse dmiPluginRegistrationResponse = new DmiPluginRegistrationResponse(); @@ -367,10 +371,17 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService yangModelCmHandles.removeIf(yangModelCmHandle -> notDeletedCmHandles.contains(yangModelCmHandle.getId())); updateCmHandleStateBatch(yangModelCmHandles, CmHandleState.DELETED); + removeEntriesFromAlternateIdCache(yangModelCmHandles); return cmHandleRegistrationResponses; } + private void removeEntriesFromAlternateIdCache(final Collection yangModelCmHandles) { + for (final YangModelCmHandle yangModelCmHandle : yangModelCmHandles) { + cmHandleIdMapper.removeMapping(yangModelCmHandle.getId()); + } + } + protected List parseAndProcessUpgradedCmHandlesInRegistration( final DmiPluginRegistration dmiPluginRegistration) { @@ -527,4 +538,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService } } + private void cacheAlternateIds(final Collection ncmpServiceCmHandles) { + for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) { + if (!StringUtils.isEmpty(ncmpServiceCmHandle.getAlternateId())) { + cmHandleIdMapper.addMapping(ncmpServiceCmHandle.getCmHandleId(), ncmpServiceCmHandle.getAlternateId()); + } + } + } + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java index 15209328fb..13b3fcafb1 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java @@ -43,9 +43,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.onap.cps.api.CpsDataService; import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.api.impl.utils.CmHandleIdMapper; import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse; @@ -67,6 +67,7 @@ public class NetworkCmProxyDataServicePropertyHandler { private final InventoryPersistence inventoryPersistence; private final CpsDataService cpsDataService; private final JsonObjectMapper jsonObjectMapper; + private final CmHandleIdMapper cmHandleIdMapper; /** * Iterates over incoming ncmpServiceCmHandles and update the dataNodes based on the updated attributes. @@ -105,19 +106,15 @@ public class NetworkCmProxyDataServicePropertyHandler { private void updateAlternateId(final DataNode existingCmHandleDataNode, final NcmpServiceCmHandle ncmpServiceCmHandle) { final String newAlternateId = ncmpServiceCmHandle.getAlternateId(); - if (!StringUtils.isEmpty(newAlternateId)) { - final String existingAlternateId = (String) existingCmHandleDataNode.getLeaves().get("alternate-id"); - if (StringUtils.isEmpty(existingAlternateId)) { + if (cmHandleIdMapper.addMapping(ncmpServiceCmHandle.getCmHandleId(), newAlternateId)) { + try { final YangModelCmHandle yangModelCmHandle = YangDataConverter.convertCmHandleToYangModel(existingCmHandleDataNode, ncmpServiceCmHandle.getCmHandleId()); setAndUpdateAlternateId(yangModelCmHandle, newAlternateId); - } else { - if (!newAlternateId.equals(existingAlternateId)) { - log.warn("Unable to update alternateId for cmHandle {}. " - + "Value for alternateId has been set previously.", - ncmpServiceCmHandle.getCmHandleId()); - } + } catch (final Exception e) { + cmHandleIdMapper.removeMapping(ncmpServiceCmHandle.getCmHandleId()); + throw e; } } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfig.java new file mode 100644 index 0000000000..a69d144766 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfig.java @@ -0,0 +1,59 @@ +/* + * ============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.api.impl.config.embeddedcache; + +import com.hazelcast.config.MapConfig; +import java.util.Map; +import org.onap.cps.cache.HazelcastCacheConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AlternateIdCacheConfig extends HazelcastCacheConfig { + + private static final MapConfig alternateIdPerCmHandleIdMapConfig = + createMapConfig("alternateIdPerCmHandleIdMapConfig"); + + private static final MapConfig cmHandleIdPerAlternateIdMapConfig = + createMapConfig("cmHandleIdPerAlternateIdMapConfig"); + + /** + * Distributed instance of alternate id cache containing the alternate id per cm handle id. + * + * @return the cached map. + */ + @Bean + public Map alternateIdPerCmHandleId() { + return createHazelcastInstance("hazelcastInstanceAlternateIdPerCmHandleIdMap", + alternateIdPerCmHandleIdMapConfig).getMap("alternateIdPerCmHandleId"); + } + + /** + * Distributed instance of alternate id cache containing the cm handle id per alternate id. + * + * @return the cached map. + */ + @Bean + public Map cmHandleIdPerAlternateId() { + return createHazelcastInstance("hazelcastInstanceCmHandleIdPerAlternateIdMap", + cmHandleIdPerAlternateIdMapConfig).getMap("cmHandleIdPerAlternateId"); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapper.java new file mode 100644 index 0000000000..8175fb5e74 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapper.java @@ -0,0 +1,89 @@ +/* + * ============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.api.impl.utils; + +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +@RequiredArgsConstructor +public class CmHandleIdMapper { + + private final Map alternateIdPerCmHandleId; + private final Map cmHandleIdPerAlternateId; + private final NetworkCmProxyCmHandleQueryService networkCmProxyCmHandleQueryService; + + private boolean cacheIsInitialized = false; + + public String cmHandleIdToAlternateId(final String cmHandleId) { + initializeCache(); + return alternateIdPerCmHandleId.get(cmHandleId); + } + + public String alternateIdToCmHandleId(final String alternateId) { + initializeCache(); + return cmHandleIdPerAlternateId.get(alternateId); + } + + public boolean addMapping(final String cmHandleId, final String alternateId) { + initializeCache(); + return addMappingWithValidation(cmHandleId, alternateId); + } + + + private boolean addMappingWithValidation(final String cmHandleId, final String alternateId) { + if (alternateIdPerCmHandleId.containsKey(cmHandleId)) { + final String originalAlternateId = alternateIdPerCmHandleId.get(cmHandleId); + if (!originalAlternateId.equals(alternateId)) { + log.warn("Alternate id update ignored, cannot update cm handle {}, already has an alternate id of {}", + cmHandleId, originalAlternateId); + } + return false; + } + if (StringUtils.isBlank(alternateId)) { + return false; + } + alternateIdPerCmHandleId.put(cmHandleId, alternateId); + cmHandleIdPerAlternateId.put(alternateId, cmHandleId); + return true; + } + + public void removeMapping(final String cmHandleId) { + final String alternateId = alternateIdPerCmHandleId.remove(cmHandleId); + cmHandleIdPerAlternateId.remove(alternateId); + } + + private void initializeCache() { + if (!cacheIsInitialized) { + networkCmProxyCmHandleQueryService.getAllCmHandles().forEach(cmHandle -> + addMappingWithValidation(cmHandle.getCmHandleId(), cmHandle.getAlternateId()) + ); + log.info("Alternate ID cache initialized from DB with {} cm handle/alternate id pairs ", + alternateIdPerCmHandleId.size()); + cacheIsInitialized = true; + } + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy index f565ede394..59bf9420cf 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * Copyright (C) 2021-2024 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ package org.onap.cps.ncmp.api.impl import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager +import org.onap.cps.ncmp.api.impl.utils.CmHandleIdMapper import org.onap.cps.ncmp.api.models.UpgradedCmHandles import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND @@ -71,6 +72,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { def mockModuleSyncStartedOnCmHandles = Mock(IMap) def trustLevelPerDmiPlugin = [:] def mockTrustLevelManager = Mock(TrustLevelManager) + def mockCmHandleIdMapper = Mock(CmHandleIdMapper) def objectUnderTest = getObjectUnderTest() def 'DMI Registration: Create, Update, Delete & Upgrade operations are processed in the right order'() { @@ -432,11 +434,21 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { 'an unexpected exception' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' } + def 'Adding data to alternate id caches.'() { + given: 'a registration with three CM Handles to be created' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + createdCmHandles: [new NcmpServiceCmHandle(cmHandleId: 'cmhandle1', alternateId: 'my-alternate-id-1')]) + when: 'the DMI plugin registration happens' + objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'the new alternate id is added to the cache' + 1 * mockCmHandleIdMapper.addMapping('cmhandle1', 'my-alternate-id-1') + } + def getObjectUnderTest() { return Spy(new NetworkCmProxyDataServiceImpl(spiedJsonObjectMapper, mockDmiDataOperations, mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCmHandleQueries, stubbedNetworkCmProxyCmHandlerQueryService, mockLcmEventsCmHandleStateHandler, mockCpsDataService, - mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin, mockTrustLevelManager)) + mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin as Map, mockTrustLevelManager, mockCmHandleIdMapper)) } def addPersistedYangModelCmHandles(ids) { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy index 4f7b726985..c1af902a08 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation + * 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. @@ -22,6 +22,9 @@ */ package org.onap.cps.ncmp.api.impl + +import org.onap.cps.ncmp.api.impl.utils.CmHandleIdMapper + import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR @@ -78,6 +81,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { def stubModuleSyncStartedOnCmHandles = Stub(IMap) def stubTrustLevelPerDmiPlugin = Stub(Map) def mockTrustLevelManager = Mock(TrustLevelManager) + def mockCmHandleIdMapper = Mock(CmHandleIdMapper) def NO_TOPIC = null def NO_REQUEST_ID = null @@ -97,7 +101,9 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { mockCpsDataService, stubModuleSyncStartedOnCmHandles, stubTrustLevelPerDmiPlugin, - mockTrustLevelManager) + mockTrustLevelManager, + mockCmHandleIdMapper + ) def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']" diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy index 2a15e6c1af..f94c34c589 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy @@ -22,13 +22,9 @@ package org.onap.cps.ncmp.api.impl -import ch.qos.logback.classic.Level -import ch.qos.logback.classic.Logger -import ch.qos.logback.classic.spi.ILoggingEvent + import com.fasterxml.jackson.databind.ObjectMapper -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach -import org.slf4j.LoggerFactory +import org.onap.cps.ncmp.api.impl.utils.CmHandleIdMapper import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR @@ -37,7 +33,6 @@ import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status -import ch.qos.logback.core.read.ListAppender import org.onap.cps.api.CpsDataService import org.onap.cps.utils.JsonObjectMapper import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence @@ -53,20 +48,9 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { def mockInventoryPersistence = Mock(InventoryPersistence) def mockCpsDataService = Mock(CpsDataService) def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - def logger = Spy(ListAppender) - - @BeforeEach - void setup() { - ((Logger) LoggerFactory.getLogger(NetworkCmProxyDataServicePropertyHandler.class)).addAppender(logger); - logger.start(); - } + def mockCmHandleIdMapper = Mock(CmHandleIdMapper) - @AfterEach - void teardown() { - ((Logger) LoggerFactory.getLogger(NetworkCmProxyDataServicePropertyHandler.class)).detachAndStopAllAppenders(); - } - - def objectUnderTest = new NetworkCmProxyDataServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper) + def objectUnderTest = new NetworkCmProxyDataServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockCmHandleIdMapper) def static cmHandleId = 'myHandle1' def static cmHandleXpath = "/dmi-registry/cm-handles[@id='${cmHandleId}']" @@ -198,36 +182,41 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification { 2 * mockInventoryPersistence.replaceListContent(cmHandleXpath,_) } - def 'Update CM Handle Alternate ID when #scenario'() { - given: 'an existing cm handle with alternate id #existingAlternateId' - DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['alternate-id': oldAlternateId]) - and: 'an update request with an alternate id #newAlternateId' - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: newAlternateId) - when: 'update data node leaves is called with the update request' + def 'Update CM Handle Alternate ID with #scenario'() { + given: 'an existing cm handle' + DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath) + and: 'an update request with an alternate id' + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1' ) + when: 'update alternate id method is called with the update request' objectUnderTest.updateAlternateId(existingCmHandleDataNode, ncmpServiceCmHandle) then: 'the update node leaves method is invoked as many times as expected' - numberOfInvocations * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _) >> { args -> - assert args[3].contains(newAlternateId) - } - and: 'correct information is logged' - def lastLoggingEvent = logger.list[0] - if (expectLogWarning) { - assert lastLoggingEvent.level == Level.WARN - assert lastLoggingEvent.formattedMessage.contains('Unable') - } else if (numberOfInvocations == 1) { - assert lastLoggingEvent.level == Level.INFO - assert lastLoggingEvent.formattedMessage.contains('Updating alternateId') - } else { - assert lastLoggingEvent == null - } + callsToDataService * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _) >> + { args -> + assert args[3].contains('alt-1') + } + mockCmHandleIdMapper.addMapping(cmHandleId, 'alt-1') >> isNewMapping where: 'following updates are attempted' - scenario | oldAlternateId | newAlternateId || numberOfInvocations | expectLogWarning - 'old alternate id null' | null | 'new' || 1 | false - 'old alternate id empty' | '' | 'new' || 1 | false - 'old alternate id not empty' | 'old' | 'new' || 0 | true - 'same alternate id' | 'old' | 'old' || 0 | false - 'empty new alternate id' | 'old' | '' || 0 | false - 'null new alternate id' | 'old' | null || 0 | false + scenario | isNewMapping || callsToDataService + 'new alternate id ' | true || 1 + 'existing alternate id' | false || 0 + } + + def 'Alternate ID removed from cache when persisting fails.'() { + given: 'an existing data node and an update request with an alternate id' + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1') + DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['alternate-id': null]) + and: 'a new mapping is added' + mockCmHandleIdMapper.addMapping(cmHandleId, 'alt-1') >> true + and: 'but an exception occurs while saving' + def originalException = new NullPointerException('some exception') + mockCpsDataService.updateNodeLeaves(*_) >> { throw originalException } + when: 'updating of alternate id called' + objectUnderTest.updateAlternateId(existingCmHandleDataNode, ncmpServiceCmHandle) + then: 'the original exception is thrown up' + def thrownException = thrown(NullPointerException) + assert thrownException == originalException + and: 'the mapping is removed from the cache' + 1 * mockCmHandleIdMapper.removeMapping(cmHandleId) } def convertToProperties(expectedPropertiesAfterUpdateAsMap) { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfigSpec.groovy new file mode 100644 index 0000000000..71c5293e27 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/AlternateIdCacheConfigSpec.groovy @@ -0,0 +1,59 @@ +/* + * ============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.api.impl.config.embeddedcache + +import com.hazelcast.core.Hazelcast +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import spock.lang.Specification + +@SpringBootTest(classes = [AlternateIdCacheConfig]) +class AlternateIdCacheConfigSpec extends Specification { + + @Autowired + private Map cmHandleIdPerAlternateId + @Autowired + private Map alternateIdPerCmHandleId + + def 'Embedded (hazelcast) cache for alternate id - cm handle id caches.'() { + expect: 'system is able to create an instance of the Alternate ID Cache' + assert null != cmHandleIdPerAlternateId + assert null != alternateIdPerCmHandleId + and: 'there are at least 2 instances' + assert Hazelcast.allHazelcastInstances.size() > 1 + and: 'Alternate ID Caches are present' + assert Hazelcast.allHazelcastInstances.name.contains('hazelcastInstanceAlternateIdPerCmHandleIdMap') + && Hazelcast.allHazelcastInstances.name.contains('hazelcastInstanceCmHandleIdPerAlternateIdMap') + } + + def 'Verify configs of the alternate id distributed objects.'(){ + when: 'retrieving the map config of module set tag' + def alternateIdConfig = Hazelcast.getHazelcastInstanceByName('hazelcastInstanceAlternateIdPerCmHandleIdMap').config + def alternateIdMapConfig = alternateIdConfig.mapConfigs.get('alternateIdPerCmHandleIdMapConfig') + def cmHandleIdConfig = Hazelcast.getHazelcastInstanceByName('hazelcastInstanceCmHandleIdPerAlternateIdMap').config + def cmHandleIdIdMapConfig = cmHandleIdConfig.mapConfigs.get('cmHandleIdPerAlternateIdMapConfig') + then: 'the map configs have the correct number of backups' + assert alternateIdMapConfig.backupCount == 3 + assert alternateIdMapConfig.asyncBackupCount == 3 + assert cmHandleIdIdMapConfig.backupCount == 3 + assert cmHandleIdIdMapConfig.asyncBackupCount == 3 + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapperSpec.groovy new file mode 100644 index 0000000000..0a2962e98f --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleIdMapperSpec.groovy @@ -0,0 +1,117 @@ +/* + * ============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.api.impl.utils + +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.core.read.ListAppender +import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.slf4j.LoggerFactory +import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService +import spock.lang.Specification + +class CmHandleIdMapperSpec extends Specification { + + def alternateIdPerCmHandle = new HashMap() + def cmHandlePerAlternateId = new HashMap() + def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandleQueryService) + + def objectUnderTest = new CmHandleIdMapper(alternateIdPerCmHandle, cmHandlePerAlternateId, mockCpsCmHandlerQueryService) + + def logger = Spy(ListAppender) + + def setup() { + ((Logger) LoggerFactory.getLogger(CmHandleIdMapper.class)).addAppender(logger) + logger.start() + mockCpsCmHandlerQueryService.getAllCmHandles() >> [] + assert objectUnderTest.addMapping('my cmhandle id', 'my alternate id') + } + + void cleanup() { + ((Logger) LoggerFactory.getLogger(CmHandleIdMapper.class)).detachAndStopAllAppenders() + } + + def 'Checking entries in the cache.'() { + expect: 'the alternate id can be converted to cmhandle id' + assert objectUnderTest.alternateIdToCmHandleId('my alternate id') == 'my cmhandle id' + and: 'the cmhandle id can be converted to alternate id' + assert objectUnderTest.cmHandleIdToAlternateId('my cmhandle id') == 'my alternate id' + } + + def 'Attempt adding #scenario alternate id.'() { + expect: 'cmhandle id - alternate id mapping fails' + assert objectUnderTest.addMapping('ch-1', alternateId) == false + and: 'alternate id looked up by cmhandle id unsuccessfully' + assert objectUnderTest.cmHandleIdToAlternateId('ch-1') == null + where: 'alternate id has an invalid value' + scenario | alternateId + 'empty' | '' + 'blank' | ' ' + 'null' | null + } + + def 'Remove an entry from the cache.'() { + when: 'removing an entry' + objectUnderTest.removeMapping('my cmhandle id') + then: 'converting alternate id returns null' + assert objectUnderTest.alternateIdToCmHandleId('my alternate id') == null + and: 'converting cmhandle id returns null' + assert objectUnderTest.cmHandleIdToAlternateId('my cmhandle id') == null + } + + def 'Cannot update existing alternate id.'() { + given: 'attempt to update an existing alternate id' + objectUnderTest.addMapping('my cmhandle id', 'other id') + expect: 'still returns the original alternate id' + assert objectUnderTest.cmHandleIdToAlternateId('my cmhandle id') == 'my alternate id' + and: 'converting other alternate id returns null' + assert objectUnderTest.alternateIdToCmHandleId('other id') == null + and: 'a warning is logged with the original alternate id' + def lastLoggingEvent = logger.list[1] + assert lastLoggingEvent.level == Level.WARN + assert lastLoggingEvent.formattedMessage.contains('my alternate id') + } + + def 'Update existing alternate id with the same value.'() { + expect: 'update an existing alternate id with the same value returns false (no update)' + assert objectUnderTest.addMapping('my cmhandle id', 'my alternate id') == false + and: 'conversion still returns the original alternate id' + assert objectUnderTest.cmHandleIdToAlternateId('my cmhandle id') == 'my alternate id' + } + + def 'Initializing cache #scenario.'() { + when: 'the cache is (re-)initialized' + objectUnderTest.cacheIsInitialized = false + objectUnderTest.initializeCache() + then: 'the alternate id can be converted to cmhandle id' + assert objectUnderTest.alternateIdToCmHandleId('alt-1') == convertedCmHandleId + and: 'the cm handle id can be converted to alternate id' + assert objectUnderTest.cmHandleIdToAlternateId('ch-1') == convertedAlternatId + and: 'the query service is called to get the initial data' + 1 * mockCpsCmHandlerQueryService.getAllCmHandles() >> persistedCmHandles + where: 'the initial data has a cm handle #scenario' + scenario | persistedCmHandles || convertedAlternatId | convertedCmHandleId + 'with alternate id' | [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: 'alt-1')] || 'alt-1' | 'ch-1' + 'without alternate id' | [new NcmpServiceCmHandle(cmHandleId: 'ch-1')] || null | null + 'with blank alternate id' | [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: ' ')] || null | null + } +} \ No newline at end of file diff --git a/docs/deployment.rst b/docs/deployment.rst index 7ba163d574..ca7824d6dc 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -1,6 +1,6 @@ .. This work is licensed under a Creative Commons Attribution 4.0 International License. .. http://creativecommons.org/licenses/by/4.0 -.. Copyright (C) 2021-2022 Nordix Foundation +.. Copyright (C) 2021-2024 Nordix Foundation .. Modifications Copyright (C) 2021 Bell Canada. .. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING @@ -316,24 +316,28 @@ These instances require some additional ports to be available. The default range Below are the list of distributed datastructures that we have. -+--------------+---------------------------------+----------------------------------------------------------+ -| Component | Datastructure name | Use | -+==============+=================================+==========================================================+ -| cps-core | anchorDataCache | Used to resolve prefix for the container name. | -+--------------+---------------------------------+----------------------------------------------------------+ -| cps-ncmp | moduleSyncStartedOnCmHandles | Watchdog process to register cmHandles. | -+--------------+---------------------------------+----------------------------------------------------------+ -| cps-ncmp | dataSyncSemaphores | Watchdog process to sync data from the nodes. | -+--------------+---------------------------------+----------------------------------------------------------+ -| cps-ncmp | moduleSyncWorkQueue | Queue used internally for workers to pick the task. | -+--------------+---------------------------------+----------------------------------------------------------+ -| cps-ncmp | untrustworthyCmHandlesSet | Stores untrustworthy cmHandles whose TrustLevel is NONE. | -+--------------+---------------------------------+----------------------------------------------------------+ -| cps-ncmp | trustLevelPerDmiPlugin | Stores the TrustLevel for the dmi-plugins. | -+--------------+---------------------------------+----------------------------------------------------------+ -| cps-ncmp | moduleSetTagCacheMapConfig | Stores the Module Set Tags for cmHandles. | -+--------------+---------------------------------+----------------------------------------------------------+ -| cps-ncmp | cmSubscriptionEventCache | Stores and tracks CmSubscription requests. | -+--------------+---------------------------------+----------------------------------------------------------+ - -Total number of caches : 8 ++--------------+------------------------------------+-----------------------------------------------------------+ +| Component | Datastructure name | Use | ++==============+====================================+===========================================================+ +| cps-core | anchorDataCache | Used to resolve prefix for the container name. | ++--------------+------------------------------------+-----------------------------------------------------------+ +| cps-ncmp | moduleSyncStartedOnCmHandles | Watchdog process to register cm handles. | ++--------------+------------------------------------+-----------------------------------------------------------+ +| cps-ncmp | dataSyncSemaphores | Watchdog process to sync data from the nodes. | ++--------------+------------------------------------+-----------------------------------------------------------+ +| cps-ncmp | moduleSyncWorkQueue | Queue used internally for workers to pick the task. | ++--------------+------------------------------------+-----------------------------------------------------------+ +| cps-ncmp | untrustworthyCmHandlesSet | Stores untrustworthy cm handles whose trust level is NONE.| ++--------------+------------------------------------+-----------------------------------------------------------+ +| cps-ncmp | trustLevelPerDmiPlugin | Stores the trust level for the dmi-plugins. | ++--------------+------------------------------------+-----------------------------------------------------------+ +| cps-ncmp | moduleSetTagCacheMapConfig | Stores the module set tags for cm handles. | ++--------------+------------------------------------+-----------------------------------------------------------+ +| cps-ncmp | cmSubscriptionEventCache | Stores and tracks cm notification subscription requests. | ++--------------+------------------------------------+-----------------------------------------------------------+ +| cps-ncmp | alternateIdPerCmHandleId | Stores the alternate id for each cm handle id. | ++--------------+------------------------------------+-----------------------------------------------------------+ +| cps-ncmp | cmHandleIdPerAlternateId | Stores the cm handle id for each alternate id. | ++--------------+------------------------------------+-----------------------------------------------------------+ + +Total number of caches : 10 -- cgit 1.2.3-korg