From 2c3251b1d8ce6d575b4d8ff968f539f64d3983a9 Mon Sep 17 00:00:00 2001 From: emaclee Date: Tue, 8 Apr 2025 12:48:48 +0100 Subject: [PATCH] Use a Hazelcast map of alternate ids to Cm handle ids Issue-ID: CPS-2753 Change-Id: I6f3657bf14d78e412f650ee536e79beefb601c09 Signed-off-by: emaclee --- .../ncmp/impl/cache/AlternateIdCacheConfig.java | 44 +++++++++++++ .../ncmp/impl/inventory/AlternateIdChecker.java | 18 +++--- .../inventory/CmHandleRegistrationService.java | 42 +++++++++++- ...CmHandleRegistrationServicePropertyHandler.java | 9 ++- .../ncmp/impl/inventory/InventoryPersistence.java | 16 ----- .../impl/inventory/InventoryPersistenceImpl.java | 40 +----------- .../impl/inventory/sync/ModuleSyncWatchdog.java | 4 +- .../cps/ncmp/impl/utils/AlternateIdMatcher.java | 36 +++++------ .../cps/ncmp/init/AlternateIdCacheDataLoader.java | 75 ++++++++++++++++++++++ .../impl/inventory/AlternateIdCheckerSpec.groovy | 38 +++++------ ...leRegistrationServicePropertyHandlerSpec.groovy | 8 ++- .../CmHandleRegistrationServiceSpec.groovy | 5 +- .../inventory/InventoryPersistenceImplSpec.groovy | 41 +----------- .../ncmp/impl/utils/AlternateIdMatcherSpec.groovy | 66 +++++++++---------- .../init/AlternateIdCacheDataLoaderSpec.groovy | 49 ++++++++++++++ docs/deployment.rst | 4 +- 16 files changed, 312 insertions(+), 183 deletions(-) create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/AlternateIdCacheConfig.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AlternateIdCacheDataLoader.java create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AlternateIdCacheDataLoaderSpec.groovy diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/AlternateIdCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/AlternateIdCacheConfig.java new file mode 100644 index 0000000000..14936c81d7 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/AlternateIdCacheConfig.java @@ -0,0 +1,44 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * ================================================================================ + * 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 AlternateIdCacheConfig extends HazelcastCacheConfig { + + private static final MapConfig cmHandleIdPerAlternateIdMapConfig = + createMapConfig("cmHandleIdPerAlternateIdMapConfig"); + + /** + * Distributed instance used for mapping alternate id to cm handle id. + * + * @return configured map of cm handle id by alternate id + */ + @Bean + public IMap cmHandleIdPerAlternateId() { + return getOrCreateHazelcastInstance(cmHandleIdPerAlternateIdMapConfig).getMap("cmHandleIdPerAlternateId"); + } + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/AlternateIdChecker.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/AlternateIdChecker.java index a0ca44c9d0..aa7e261e8a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/AlternateIdChecker.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/AlternateIdChecker.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================== - * Copyright (c) 2024 Nordix Foundation. + * Copyright (c) 2024-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,10 @@ package org.onap.cps.ncmp.impl.inventory; +import com.hazelcast.map.IMap; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -29,7 +31,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.onap.cps.api.exceptions.DataNodeNotFoundException; import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; -import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Service @@ -42,6 +44,8 @@ public class AlternateIdChecker { } private final InventoryPersistence inventoryPersistence; + @Qualifier("cmHandleIdPerAlternateId") + private final IMap cmHandleIdPerAlternateId; private static final String NO_CURRENT_ALTERNATE_ID = ""; @@ -54,9 +58,9 @@ public class AlternateIdChecker { * @return collection of cm handles ids which are acceptable */ public Collection getIdsOfCmHandlesWithRejectedAlternateId( - final Collection newNcmpServiceCmHandles, - final Operation operation) { - final Set assignedAlternateIds = getAlternateIdsAlreadyInDb(newNcmpServiceCmHandles); + final Collection newNcmpServiceCmHandles, + final Operation operation) { + final Set assignedAlternateIds = new HashSet<>(getAlternateIdsAlreadyInDb(newNcmpServiceCmHandles)); final Collection rejectedCmHandleIds = new ArrayList<>(); for (final NcmpServiceCmHandle ncmpServiceCmHandle : newNcmpServiceCmHandles) { final String cmHandleId = ncmpServiceCmHandle.getCmHandleId(); @@ -97,9 +101,7 @@ public class AlternateIdChecker { .map(NcmpServiceCmHandle::getAlternateId) .filter(StringUtils::isNotBlank) .collect(Collectors.toSet()); - return inventoryPersistence.getYangModelCmHandleByAlternateIds(alternateIdsToCheck).stream() - .map(YangModelCmHandle::getAlternateId) - .collect(Collectors.toSet()); + return cmHandleIdPerAlternateId.getAll(alternateIdsToCheck).keySet(); } private String getCurrentAlternateId(final Operation operation, final String cmHandleId) { 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 ea8c3ca78d..6153fd679f 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 @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2025 Nordix Foundation + * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021-2022 Bell Canada * Modifications Copyright (C) 2023 TechMahindra Ltd. @@ -64,6 +64,7 @@ import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.ncmp.impl.inventory.sync.ModuleOperationsUtils; import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler; import org.onap.cps.ncmp.impl.inventory.trustlevel.TrustLevelManager; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Slf4j @@ -80,6 +81,8 @@ public class CmHandleRegistrationService { private final IMap moduleSyncStartedOnCmHandles; private final TrustLevelManager trustLevelManager; private final AlternateIdChecker alternateIdChecker; + @Qualifier("cmHandleIdPerAlternateId") + private final IMap cmHandleIdPerAlternateId; /** * Registration of Created, Removed, Updated or Upgraded CM Handles. @@ -135,6 +138,27 @@ public class CmHandleRegistrationService { } } + /** + * Method to add alternate ids to cache by passing in yang model cm handle. + * Note: If alternate id does not exist for given cm handle, + * then the map is populated with cm handle id as both key and value. + * + * @param yangModelCmHandles collection of yang model cm handles + */ + public void addAlternateIdsToCache(final Collection yangModelCmHandles) { + final Map cmHandleIdPerAlternateIdToRegister = new HashMap<>(yangModelCmHandles.size()); + for (final YangModelCmHandle yangModelCmHandle: yangModelCmHandles) { + final String cmHandleId = yangModelCmHandle.getId(); + final String alternateId = yangModelCmHandle.getAlternateId(); + if (StringUtils.isNotBlank(alternateId)) { + cmHandleIdPerAlternateIdToRegister.put(alternateId, cmHandleId); + } else { + cmHandleIdPerAlternateIdToRegister.put(cmHandleId, cmHandleId); + } + } + cmHandleIdPerAlternateId.putAll(cmHandleIdPerAlternateIdToRegister); + } + protected void processRemovedCmHandles(final DmiPluginRegistration dmiPluginRegistration, final DmiPluginRegistrationResponse dmiPluginRegistrationResponse) { final List toBeRemovedCmHandleIds = dmiPluginRegistration.getRemovedCmHandles(); @@ -168,6 +192,7 @@ public class CmHandleRegistrationService { } yangModelCmHandles.removeIf(yangModelCmHandle -> notDeletedCmHandles.contains(yangModelCmHandle.getId())); updateCmHandleStateBatch(yangModelCmHandles, CmHandleState.DELETED); + removeAlternateIdsFromCache(yangModelCmHandles); dmiPluginRegistrationResponse.setRemovedCmHandles(cmHandleRegistrationResponses); } @@ -186,9 +211,11 @@ public class CmHandleRegistrationService { processTrustLevels(ncmpServiceCmHandles, succeededCmHandleIds); } catch (final AlreadyDefinedException alreadyDefinedException) { + log.error("Error while creating CM handles", alreadyDefinedException); failedCmHandleRegistrationResponses.addAll(CmHandleRegistrationResponse.createFailureResponsesFromXpaths( alreadyDefinedException.getAlreadyDefinedObjectNames(), CM_HANDLE_ALREADY_EXIST)); } catch (final Exception exception) { + log.error("Error while creating CM handles", exception); final Collection cmHandleIds = ncmpServiceCmHandles.stream().map(NcmpServiceCmHandle::getCmHandleId).collect(Collectors.toList()); failedCmHandleRegistrationResponses.addAll(CmHandleRegistrationResponse @@ -367,6 +394,7 @@ public class CmHandleRegistrationService { } } lcmEventsCmHandleStateHandler.initiateStateAdvised(yangModelCmHandlesToRegister); + addAlternateIdsToCache(yangModelCmHandlesToRegister); dmiPluginRegistrationResponse.setCreatedCmHandles(cmHandleRegistrationResponses); return succeededCmHandleIds; } @@ -383,4 +411,16 @@ public class CmHandleRegistrationService { ncmpServiceCmHandle.getDataProducerIdentifier()); } + private void removeAlternateIdsFromCache(final Collection yangModelCmHandles) { + for (final YangModelCmHandle yangModelCmHandle: yangModelCmHandles) { + final String cmHandleId = yangModelCmHandle.getId(); + final String alternateId = yangModelCmHandle.getAlternateId(); + if (StringUtils.isNotBlank(alternateId)) { + cmHandleIdPerAlternateId.delete(alternateId); + } else { + cmHandleIdPerAlternateId.delete(cmHandleId); + } + } + } + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandler.java index 7a6cb14d17..47d03c679f 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandler.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandler.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2025 Nordix Foundation + * Copyright (C) 2022-2025 OpenInfra Foundation Europe. All rights reserved. * Modifications Copyright (C) 2022 Bell Canada * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ @@ -33,6 +33,7 @@ import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT; import com.google.common.collect.ImmutableMap; +import com.hazelcast.map.IMap; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Collection; @@ -57,6 +58,7 @@ import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.onap.cps.utils.ContentType; import org.onap.cps.utils.JsonObjectMapper; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Slf4j @@ -70,6 +72,8 @@ public class CmHandleRegistrationServicePropertyHandler { private final CpsDataService cpsDataService; private final JsonObjectMapper jsonObjectMapper; private final AlternateIdChecker alternateIdChecker; + @Qualifier("cmHandleIdPerAlternateId") + private final IMap cmHandleIdPerAlternateId; /** * Iterates over incoming updatedNcmpServiceCmHandles and update the dataNodes based on the updated attributes. @@ -125,9 +129,12 @@ public class CmHandleRegistrationServicePropertyHandler { } private void updateAlternateId(final NcmpServiceCmHandle ncmpServiceCmHandle) { + final String cmHandleId = ncmpServiceCmHandle.getCmHandleId(); final String newAlternateId = ncmpServiceCmHandle.getAlternateId(); if (StringUtils.isNotBlank(newAlternateId)) { setAndUpdateCmHandleField(ncmpServiceCmHandle.getCmHandleId(), "alternate-id", newAlternateId); + cmHandleIdPerAlternateId.delete(cmHandleId); + cmHandleIdPerAlternateId.set(newAlternateId, cmHandleId); } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java index aeeb86592c..29eaf2e407 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java @@ -124,22 +124,6 @@ public interface InventoryPersistence extends NcmpPersistence { Collection getCmHandleDataNodeByCmHandleId(String cmHandleId, FetchDescendantsOption fetchDescendantsOption); - /** - * Get yang model cm handle with the given alternate id. - * - * @param alternateId alternate ID - * @return yang model cm handle - */ - YangModelCmHandle getYangModelCmHandleByAlternateId(String alternateId); - - /** - * Get yang model cm handles for the given batch of alternate ids. - * - * @param alternateIds alternate IDs - * @return yang model cm handles - */ - Collection getYangModelCmHandleByAlternateIds(Collection alternateIds); - /** * Get collection of data nodes of given cm handles. * diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java index 88322903a3..9bbc8b8e42 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java @@ -45,7 +45,6 @@ import org.onap.cps.api.model.DataNode; import org.onap.cps.api.model.ModuleDefinition; import org.onap.cps.api.model.ModuleReference; import org.onap.cps.api.parameters.FetchDescendantsOption; -import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException; import org.onap.cps.ncmp.api.inventory.models.CompositeState; import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; @@ -63,7 +62,6 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv private final CpsModuleService cpsModuleService; private final CpsValidator cpsValidator; - private final CmHandleQueryService cmHandleQueryService; /** * initialize an inventory persistence object. @@ -73,21 +71,17 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv * @param cpsAnchorService cps anchor service instance * @param cpsModuleService cps module service instance * @param cpsDataService cps data service instance - * @param cmHandleQueryService cm handle query service instance */ public InventoryPersistenceImpl(final CpsValidator cpsValidator, final JsonObjectMapper jsonObjectMapper, final CpsAnchorService cpsAnchorService, final CpsModuleService cpsModuleService, - final CpsDataService cpsDataService, - final CmHandleQueryService cmHandleQueryService) { + final CpsDataService cpsDataService) { super(jsonObjectMapper, cpsAnchorService, cpsDataService); this.cpsModuleService = cpsModuleService; this.cpsValidator = cpsValidator; - this.cmHandleQueryService = cmHandleQueryService; } - @Override public CompositeState getCmHandleState(final String cmHandleId) { final DataNode stateAsDataNode = cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, @@ -178,28 +172,6 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv return this.getDataNode(getXPathForCmHandleById(cmHandleId), fetchDescendantsOption); } - @Override - public YangModelCmHandle getYangModelCmHandleByAlternateId(final String alternateId) { - final String cpsPathForCmHandleByAlternateId = getCpsPathForCmHandleByAlternateId(alternateId); - final Collection dataNodes = cmHandleQueryService - .queryNcmpRegistryByCpsPath(cpsPathForCmHandleByAlternateId, OMIT_DESCENDANTS, 1); - if (dataNodes.isEmpty()) { - throw new CmHandleNotFoundException(alternateId); - } - return YangDataConverter.toYangModelCmHandle(dataNodes.iterator().next()); - } - - @Override - public Collection getYangModelCmHandleByAlternateIds(final Collection alternateIds) { - if (alternateIds.isEmpty()) { - return Collections.emptyList(); - } - final String cpsPathForCmHandlesByAlternateIds = getCpsPathForCmHandlesByAlternateIds(alternateIds); - final Collection dataNodes = cmHandleQueryService.queryNcmpRegistryByCpsPath( - cpsPathForCmHandlesByAlternateIds, INCLUDE_ALL_DESCENDANTS, alternateIds.size()); - return YangDataConverter.toYangModelCmHandles(dataNodes); - } - @Override public Collection getCmHandleDataNodes(final Collection cmHandleIds, final FetchDescendantsOption fetchDescendantsOption) { @@ -232,15 +204,6 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv return NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId + "']"; } - private static String getCpsPathForCmHandleByAlternateId(final String alternateId) { - return NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@alternate-id='" + alternateId + "']"; - } - - private static String getCpsPathForCmHandlesByAlternateIds(final Collection alternateIds) { - return alternateIds.stream().collect(Collectors.joining("' or @alternate-id='", - NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@alternate-id='", "']")); - } - private static String createStateJsonData(final String state) { return "{\"state\":" + state + "}"; } @@ -249,7 +212,6 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv return "{\"cm-handles\":" + jsonObjectMapper.asJsonString(yangModelCmHandles) + "}"; } - private Collection getAlternateIdsForCmHandleIds(final Collection cmHandleIds) { final Collection dataNodes = getCmHandleDataNodes(cmHandleIds, OMIT_DESCENDANTS); return dataNodes.stream() 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 62eb514953..12e013f223 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 @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2025 Nordix Foundation + * Copyright (C) 2022-2025 OpenInfra Foundation Europe. All rights reserved. * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,7 @@ import java.util.concurrent.BlockingQueue; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -42,6 +43,7 @@ public class ModuleSyncWatchdog { private final BlockingQueue moduleSyncWorkQueue; private final IMap moduleSyncStartedOnCmHandles; private final ModuleSyncTasks moduleSyncTasks; + @Qualifier("cpsAndNcmpLock") private final IMap cpsAndNcmpLock; private static final int MODULE_SYNC_BATCH_SIZE = 300; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java index b8e4e7feda..3a0201e029 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/AlternateIdMatcher.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2024-2025 Nordix Foundation + * Copyright (C) 2024-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,21 +20,24 @@ package org.onap.cps.ncmp.impl.utils; +import com.hazelcast.map.IMap; import java.util.Map; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException; import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException; -import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; -import org.onap.cps.utils.CpsValidator; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +@Slf4j @Service @RequiredArgsConstructor public class AlternateIdMatcher { - private final InventoryPersistence inventoryPersistence; - private final CpsValidator cpsValidator; + @Qualifier("cmHandleIdPerAlternateId") + private final IMap cmHandleIdPerAlternateId; /** * Get cm handle that matches longest alternate id by removing elements @@ -68,22 +71,15 @@ public class AlternateIdMatcher { * @return cm handle id string */ public String getCmHandleId(final String cmHandleReference) { - if (cpsValidator.isValidName(cmHandleReference)) { - return getCmHandleIdTryingStandardIdFirst(cmHandleReference); - } - return getCmHandleIdByAlternateId(cmHandleReference); - } - - private String getCmHandleIdByAlternateId(final String cmHandleReference) { - // Please note: because of cm handle id validation rules this case does NOT need to try by (standard) id - return inventoryPersistence.getYangModelCmHandleByAlternateId(cmHandleReference).getId(); - } - - private String getCmHandleIdTryingStandardIdFirst(final String cmHandleReference) { - if (inventoryPersistence.isExistingCmHandleId(cmHandleReference)) { - return cmHandleReference; + final String cmHandleId = cmHandleIdPerAlternateId.get(cmHandleReference); + if (cmHandleId == null) { + if (cmHandleIdPerAlternateId.containsValue(cmHandleReference)) { + return cmHandleReference; + } else { + throw new CmHandleNotFoundException(cmHandleReference); + } } - return inventoryPersistence.getYangModelCmHandleByAlternateId(cmHandleReference).getId(); + return cmHandleId; } private String getParentPath(final String path, final String separator) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AlternateIdCacheDataLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AlternateIdCacheDataLoader.java new file mode 100644 index 0000000000..0629c51d70 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AlternateIdCacheDataLoader.java @@ -0,0 +1,75 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * ================================================================================ + * 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.init; + +import static org.onap.cps.api.parameters.FetchDescendantsOption.DIRECT_CHILDREN_ONLY; +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT; + +import com.hazelcast.map.IMap; +import java.util.Collection; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.api.model.DataNode; +import org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationService; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.YangDataConverter; +import org.onap.cps.ncmp.utils.events.NcmpInventoryModelOnboardingFinishedEvent; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AlternateIdCacheDataLoader { + + private final InventoryPersistence inventoryPersistence; + private final CmHandleRegistrationService cmHandleRegistrationService; + + @Qualifier("cmHandleIdPerAlternateId") + private final IMap cmHandleIdPerAlternateId; + + /** + * Method to initialise the Alternate ID Cache by querying the current inventory. + * This method is triggered by NcmpInventoryModelOnboardingFinishedEvent. + * + * @param event the event that triggers the initialization + */ + @EventListener + public void populateCmHandleIdPerAlternateIdMap(final NcmpInventoryModelOnboardingFinishedEvent event) { + if (cmHandleIdPerAlternateId.isEmpty()) { + log.info("Populating Alternate ID map from inventory"); + final Collection dataNodes = inventoryPersistence.getDataNode( + NCMP_DMI_REGISTRY_PARENT, DIRECT_CHILDREN_ONLY).iterator().next().getChildDataNodes(); + final Collection yangModelCmHandles = dataNodes.stream() + .map(YangDataConverter::toYangModelCmHandle).toList(); + addAlternateIdsToCache(yangModelCmHandles); + } + log.info("Alternate ID map has {} entries", cmHandleIdPerAlternateId.size()); + } + + + public void addAlternateIdsToCache(final Collection yangModelCmHandles) { + cmHandleRegistrationService.addAlternateIdsToCache(yangModelCmHandles); + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/AlternateIdCheckerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/AlternateIdCheckerSpec.groovy index aba9bf96bc..d1ced0d8c2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/AlternateIdCheckerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/AlternateIdCheckerSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================== - * Copyright (c) 2024 Nordix Foundation. + * Copyright (c) 2024-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ package org.onap.cps.ncmp.impl.inventory +import com.hazelcast.map.IMap import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.api.exceptions.DataNodeNotFoundException @@ -28,53 +29,52 @@ import spock.lang.Specification class AlternateIdCheckerSpec extends Specification { def mockInventoryPersistenceService = Mock(InventoryPersistence) + def mockCmHandleIdPerAlternateId = Mock(IMap) - def objectUnderTest = new AlternateIdChecker(mockInventoryPersistenceService) + def objectUnderTest = new AlternateIdChecker(mockInventoryPersistenceService, mockCmHandleIdPerAlternateId) + + def setup() { + mockCmHandleIdPerAlternateId.getAll(_) >> [fdnInCache1:'ch-1',fdnInCache2:'ch-2'] + } def 'Check a batch of created cm handles with #scenario.'() { given: 'a batch of 2 new cm handles with alternate ids #alt1 and #alt2' def batch = [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: alt1), new NcmpServiceCmHandle(cmHandleId: 'ch-2', alternateId: alt2)] - and: 'the database already contains cm handle(s) with these alternate ids: #altAlreadyInDb' - mockInventoryPersistenceService.getYangModelCmHandleByAlternateIds(_ as Collection) >> - { args -> args[0].stream().filter(altId -> altAlreadyInDb.contains(altId)).map(altId -> new YangModelCmHandle(alternateId: altId)).toList() } when: 'the batch of new cm handles is checked' def result = objectUnderTest.getIdsOfCmHandlesWithRejectedAlternateId(batch, AlternateIdChecker.Operation.CREATE) then: 'the result contains ids of the rejected cm handles' assert result == expectedRejectedCmHandleIds where: 'the following alternate ids are used' - scenario | alt1 | alt2 | altAlreadyInDb || expectedRejectedCmHandleIds - 'blank alternate ids' | '' | '' | ['dont matter'] || [] - 'null alternate ids' | null | null | ['dont matter'] || [] - 'new alternate ids' | 'fdn1' | 'fdn2' | ['other fdn'] || [] - 'one already used alternate id' | 'fdn1' | 'fdn2' | ['fdn1'] || ['ch-1'] - 'two already used alternate ids' | 'fdn1' | 'fdn2' | ['fdn1', 'fdn2'] || ['ch-1', 'ch-2'] - 'duplicate alternate id in batch' | 'fdn1' | 'fdn1' | ['dont matter'] || ['ch-2'] + scenario | alt1 | alt2 || expectedRejectedCmHandleIds + 'blank alternate ids' | '' | '' || [] + 'null alternate ids' | null | null || [] + 'new alternate ids' | 'newFdn1' | 'newFdn2' || [] + 'one already used alternate id' | 'fdnInCache1'| 'newFdn' || ['ch-1'] + 'two already used alternate ids' | 'fdnInCache1'| 'fdnInCache2'|| ['ch-1', 'ch-2'] + 'duplicate alternate id in batch' | 'newFdn1' | 'newFdn1' || ['ch-2'] } def 'Check a batch of updates to existing cm handles with #scenario.'() { given: 'a batch of 1 existing cm handle to update alternate id to #proposedAlt' def batch = [new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: proposedAlt)] and: 'the database already contains a cm handle with alternate id: #altAlreadyInDb' - mockInventoryPersistenceService.getYangModelCmHandleByAlternateIds(_ as Collection) >> - { args -> args[0].stream().filter(altId -> altAlreadyInDb == altId).map(altId -> new YangModelCmHandle(alternateId: altId)).toList() } mockInventoryPersistenceService.getYangModelCmHandle(_) >> new YangModelCmHandle(alternateId: altAlreadyInDb) when: 'the batch of cm handle updates is checked' def result = objectUnderTest.getIdsOfCmHandlesWithRejectedAlternateId(batch, AlternateIdChecker.Operation.UPDATE) then: 'the result contains ids of the rejected cm handles' assert result == expectedRejectedCmHandleIds where: 'the following parameters are used' - scenario | proposedAlt | altAlreadyInDb || expectedRejectedCmHandleIds - 'no alternate id' | 'fdn1' | '' || [] - 'used the same alternate id' | 'fdn1' | 'fdn1' || [] - 'used different alternate id' | 'otherFdn' | 'fdn1' || ['ch-1'] + scenario | proposedAlt | altAlreadyInDb|| expectedRejectedCmHandleIds + 'no alternate id' | 'newFdn1' | '' || [] + 'used the same alternate id' | 'fdnInCache1'| 'fdnInCache1' || [] + 'used different alternate id' | 'otherFdn' | 'fdnInCache1' || ['ch-1'] } def 'Check update of non-existing cm handle.'() { given: 'a batch of 1 non-existing cm handle to update alternate id' def batch = [new NcmpServiceCmHandle(cmHandleId: 'non-existing', alternateId: 'altId')] and: 'the database does not contain any cm handles' - mockInventoryPersistenceService.getYangModelCmHandleByAlternateIds(_) >> [] mockInventoryPersistenceService.getYangModelCmHandle(_) >> { throwDataNodeNotFoundException() } when: 'the batch of cm handle updates is checked' def result = objectUnderTest.getIdsOfCmHandlesWithRejectedAlternateId(batch, AlternateIdChecker.Operation.UPDATE) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy index 31610edec5..cec3acbb1f 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2025 Nordix Foundation + * Copyright (C) 2022-2025 OpenInfra Foundation Europe. All rights reserved. * Modifications Copyright (C) 2022 Bell Canada * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ @@ -27,6 +27,7 @@ import ch.qos.logback.classic.Logger import ch.qos.logback.classic.spi.ILoggingEvent import ch.qos.logback.core.read.ListAppender import com.fasterxml.jackson.databind.ObjectMapper +import com.hazelcast.map.IMap import org.onap.cps.api.CpsDataService import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.api.exceptions.DataNodeNotFoundException @@ -52,8 +53,9 @@ class CmHandleRegistrationServicePropertyHandlerSpec extends Specification { def mockCpsDataService = Mock(CpsDataService) def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) def mockAlternateIdChecker = Mock(AlternateIdChecker) + def mockCmHandleIdPerAlternateId = Mock(IMap) - def objectUnderTest = new CmHandleRegistrationServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker) + def objectUnderTest = new CmHandleRegistrationServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker, mockCmHandleIdPerAlternateId) def logger = Spy(ListAppender) void setup() { @@ -203,6 +205,8 @@ class CmHandleRegistrationServicePropertyHandlerSpec extends Specification { def 'Update alternate id of existing CM Handle.'() { given: 'cm handles request' def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1')] + and: 'the cm handle per alternate id cache returns a value' + mockCmHandleIdPerAlternateId.get(_) >> 'someId' and: 'a data node found' def dataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId, 'alternate-id': 'alt-1']) mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId, INCLUDE_ALL_DESCENDANTS) >> [dataNode] 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 953e1c7d0e..f99fe2d650 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 @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2025 Nordix Foundation + * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -59,10 +59,11 @@ class CmHandleRegistrationServiceSpec extends Specification { def mockModuleSyncStartedOnCmHandles = Mock(IMap) def mockTrustLevelManager = Mock(TrustLevelManager) def mockAlternateIdChecker = Mock(AlternateIdChecker) + def mockCmHandleIdPerAlternateId = Mock(IMap) def objectUnderTest = Spy(new CmHandleRegistrationService( mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCpsDataService, mockLcmEventsCmHandleStateHandler, - mockModuleSyncStartedOnCmHandles as IMap, mockTrustLevelManager, mockAlternateIdChecker)) + mockModuleSyncStartedOnCmHandles, mockTrustLevelManager, mockAlternateIdChecker, mockCmHandleIdPerAlternateId)) def setup() { // always accept all cm handles 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 bc21360c47..0755554c85 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 @@ -35,11 +35,9 @@ import org.onap.cps.api.model.DataNode import org.onap.cps.api.model.ModuleDefinition import org.onap.cps.api.model.ModuleReference import org.onap.cps.utils.CpsValidator -import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException import org.onap.cps.ncmp.api.inventory.models.CompositeState import org.onap.cps.ncmp.api.inventory.models.CmHandleState import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle -import org.onap.cps.ncmp.impl.utils.YangDataConverter import org.onap.cps.utils.ContentType import org.onap.cps.utils.JsonObjectMapper import spock.lang.Shared @@ -65,11 +63,7 @@ class InventoryPersistenceImplSpec extends Specification { def mockCpsValidator = Mock(CpsValidator) - def mockCmHandleQueries = Mock(CmHandleQueryService) - - def mockYangDataConverter = Mock(YangDataConverter) - - def objectUnderTest = new InventoryPersistenceImpl(mockCpsValidator, spiedJsonObjectMapper, mockCpsAnchorService, mockCpsModuleService, mockCpsDataService, mockCmHandleQueries) + def objectUnderTest = new InventoryPersistenceImpl(mockCpsValidator, spiedJsonObjectMapper, mockCpsAnchorService, mockCpsModuleService, mockCpsDataService) def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) @@ -295,39 +289,6 @@ class InventoryPersistenceImplSpec extends Specification { 1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, expectedXPath, INCLUDE_ALL_DESCENDANTS) } - def 'Get yang model cm handle by alternate id'() { - given: 'expected xPath to get cmHandle data node' - def expectedXPath = '/dmi-registry/cm-handles[@alternate-id=\'alternate id\']' - def expectedDataNode = new DataNode(xpath: expectedXPath, leaves: [id: 'id', alternateId: 'alternate id']) - and: 'query service is invoked with expected xpath' - mockCmHandleQueries.queryNcmpRegistryByCpsPath(expectedXPath, OMIT_DESCENDANTS, _) >> [expectedDataNode] - mockYangDataConverter.toYangModelCmHandle(expectedDataNode) >> new YangModelCmHandle(id: 'id') - expect: 'getting the yang model cm handle' - assert objectUnderTest.getYangModelCmHandleByAlternateId('alternate id') == new YangModelCmHandle(id: 'id') - } - - def 'Attempt to get non existing yang model cm handle by alternate id'() { - given: 'query service is invoked and returns empty collection of data nodes' - mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [] - when: 'getting the yang model cm handle' - objectUnderTest.getYangModelCmHandleByAlternateId('alternate id') - then: 'no data found exception thrown' - def thrownException = thrown(CmHandleNotFoundException) - assert thrownException.getMessage().contains('Cm handle not found') - assert thrownException.getDetails().contains('No cm handles found with reference alternate id') - } - - 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'() { when: 'the method to get cm handles is called' objectUnderTest.getCmHandleReferencesWithGivenModules(['sample-module-name'], false) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy index 098e88a644..984d45e420 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2024-2025 Nordix Foundation + * Copyright (C) 2024-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. @@ -20,22 +20,17 @@ package org.onap.cps.ncmp.impl.utils +import com.hazelcast.map.IMap +import org.onap.cps.ncmp.api.exceptions.CmHandleNotFoundException import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException -import org.onap.cps.ncmp.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle -import org.onap.cps.utils.CpsValidatorImpl -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ContextConfiguration import spock.lang.Specification -@SpringBootTest -@ContextConfiguration(classes = [InventoryPersistence]) class AlternateIdMatcherSpec extends Specification { - def mockInventoryPersistence = Mock(InventoryPersistence) + def mockCmHandleIdPerAlternateId = Mock(IMap) - def objectUnderTest = new AlternateIdMatcher(mockInventoryPersistence, new CpsValidatorImpl()) + def objectUnderTest = new AlternateIdMatcher(mockCmHandleIdPerAlternateId) def 'Finding longest alternate id matches.'() { given: 'a cm handle with alternate id /a/b in the cached map of all cm handles' @@ -44,15 +39,15 @@ class AlternateIdMatcherSpec extends Specification { expect: 'querying for alternate id a matching result found' assert objectUnderTest.getCmHandleByLongestMatchingAlternateId(targetAlternateId, '/', cmHandlePerAlternateId) != null where: 'the following parameters are used' - scenario | targetAlternateId - 'exact match' | '/a/b' - 'parent match' | '/a/b/c' - 'grand parent match' | '/a/b/c/d' - 'trailing separator match' | '/a/b/' - 'trailing hash' | '/a/b#q' - 'trailing hash parent match' | '/a/b/c#q' - 'trailing hash grand parent match' | '/a/b/c/d#q' - 'trailing separator then hash match' | '/a/b/#q' + scenario | targetAlternateId + 'exact match' | '/a/b' + 'parent match' | '/a/b/c' + 'grand parent match' | '/a/b/c/d' + 'trailing separator match' | '/a/b/' + 'trailing hash' | '/a/b#q' + 'trailing hash parent match' | '/a/b/c#q' + 'trailing hash grand parent match' | '/a/b/c/d#q' + 'trailing separator then hash match' | '/a/b/#q' } def 'Attempt to find longest alternate id match without any matches.'() { @@ -71,21 +66,26 @@ class AlternateIdMatcherSpec extends Specification { } def 'Get cm handle id from a cm handle reference that is a #scenario id.' () { - given: 'inventory persistence service confirms the reference exists as an id or not (#isExistingCmHandleId)' - mockInventoryPersistence.isExistingCmHandleId(cmHandleReference) >> isExistingCmHandleId + given: 'cmHandleIdPerAlternateId cache contains the given reference' + mockCmHandleIdPerAlternateId.get(cmHandleReference) >> returnedCacheValue + mockCmHandleIdPerAlternateId.containsValue(cmHandleReference) >> true when: 'getting a cm handle id from the reference' def result = objectUnderTest.getCmHandleId(cmHandleReference) - then: 'a call to find the cm handle by alternate id is only made when needed' - if (isExistingCmHandleId) { - 0 * mockInventoryPersistence.getYangModelCmHandleByAlternateId(*_) - } else { - 1 * mockInventoryPersistence.getYangModelCmHandleByAlternateId(cmHandleReference) >> new YangModelCmHandle(id: 'ch-id-2') - } - and: 'the expected cm handle id is returned' - assert result == expectedCmHandleId + then: 'the expected cm handle id is returned' + assert result == expectedResult where: 'the following parameters are used' - scenario | cmHandleReference | isExistingCmHandleId || expectedCmHandleId - 'standard' | 'ch-id-1' | true || 'ch-id-1' - 'alternate' | 'alt-id=1' | false || 'ch-id-2' + scenario | cmHandleReference| returnedCacheValue|| expectedResult + 'standard' | 'ch-id-1' | null || 'ch-id-1' + 'alternate' | 'alt-id=1' | 'ch-id-2' || 'ch-id-2' } -} + + def 'Get cm handle id when given reference DOES NOT exist in cache.'() { + given: 'cmHandleIdPerAlternateId cache returns null' + mockCmHandleIdPerAlternateId.get('nonExistingId') >> null + when: 'getting a cm handle id from the reference' + objectUnderTest.getCmHandleId('nonExistingId') + then: 'an exception is thrown' + def thrownException = thrown(CmHandleNotFoundException) + assert thrownException.getMessage().contains('Cm handle not found') + } +} \ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AlternateIdCacheDataLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AlternateIdCacheDataLoaderSpec.groovy new file mode 100644 index 0000000000..8941c4c5b3 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AlternateIdCacheDataLoaderSpec.groovy @@ -0,0 +1,49 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * ================================================================================ + * 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.init + +import com.hazelcast.map.IMap +import org.onap.cps.api.model.DataNode +import org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationService +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.utils.events.NcmpInventoryModelOnboardingFinishedEvent +import spock.lang.Specification + +class AlternateIdCacheDataLoaderSpec extends Specification { + + def mockInventoryPersistence = Mock(InventoryPersistence) + def mockCmHandleRegistrationService = Mock(CmHandleRegistrationService) + def mockCmHandleIdPerAlternateId = Mock(IMap) + + def objectUnderTest = new AlternateIdCacheDataLoader(mockInventoryPersistence, mockCmHandleRegistrationService, mockCmHandleIdPerAlternateId) + + def 'Populate cm handle id per alternate id cache.'() { + given: 'cache is empty' + mockCmHandleIdPerAlternateId.isEmpty() >> true + and: 'inventory persistence returns some data nodes' + def childDataNodes = [new DataNode(xpath: "", leaves: ['id': 'ch-1', 'alternate-id': 'alt-1'])] + mockInventoryPersistence.getDataNode(_, _) >> [new DataNode(childDataNodes:childDataNodes, leaves: ['id':''])] + when: 'the method to populate the cache is invoked by the ncmp model onboarding event' + objectUnderTest.populateCmHandleIdPerAlternateIdMap(Mock(NcmpInventoryModelOnboardingFinishedEvent)) + then: 'the cm handle registration service is called once to add ids to cache' + 1 * mockCmHandleRegistrationService.addAlternateIdsToCache(_) + } +} diff --git a/docs/deployment.rst b/docs/deployment.rst index b3a279f92b..990fea1365 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -410,5 +410,7 @@ Below are the list of distributed datastructures that we have. +--------------+------------------------------------+-----------------------------------------------------------+ | cps-ncmp | cpsAndNcmpLock | Cps and NCMP distributed lock for various use cases. | +--------------+------------------------------------+-----------------------------------------------------------+ +| cps-ncmp | cmHandleIdPerAlternateId | Stores cm handle ids per alternate ids. | ++--------------+------------------------------------+-----------------------------------------------------------+ -Total number of caches : 7 +Total number of caches : 8 -- 2.16.6