diff options
author | 2024-01-11 12:53:26 +0100 | |
---|---|---|
committer | 2024-02-01 09:37:09 +0000 | |
commit | 0f6f416f45d5ad0e9bba67d459858995196f7a73 (patch) | |
tree | b2fedbb61706817393dfe2bd118616da005820dc /cps-ncmp-service/src/test/groovy | |
parent | 0f6a966b363e5347de2d44b1527d19b4cf2825a6 (diff) |
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 <levente.csanyi@est.tech>
Diffstat (limited to 'cps-ncmp-service/src/test/groovy')
5 files changed, 234 insertions, 51 deletions
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 f565ede39..59bf9420c 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<String, Object>) 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<String, TrustLevel>, 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 4f7b72698..c1af902a0 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<String, Object>) def stubTrustLevelPerDmiPlugin = Stub(Map<String, TrustLevel>) 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 2a15e6c1a..f94c34c58 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<ILoggingEvent>) - - @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 000000000..71c5293e2 --- /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<String, String> cmHandleIdPerAlternateId + @Autowired + private Map<String, String> 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 000000000..0a2962e98 --- /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<String, String>() + def cmHandlePerAlternateId = new HashMap<String, String>() + def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandleQueryService) + + def objectUnderTest = new CmHandleIdMapper(alternateIdPerCmHandle, cmHandlePerAlternateId, mockCpsCmHandlerQueryService) + + def logger = Spy(ListAppender<ILoggingEvent>) + + 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 |