--- /dev/null
+/*
+ * ============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<String, String> cmHandleIdPerAlternateId() {
+ return getOrCreateHazelcastInstance(cmHandleIdPerAlternateIdMapConfig).getMap("cmHandleIdPerAlternateId");
+ }
+
+}
/*
* ============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.
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;
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
}
private final InventoryPersistence inventoryPersistence;
+ @Qualifier("cmHandleIdPerAlternateId")
+ private final IMap<String, String> cmHandleIdPerAlternateId;
private static final String NO_CURRENT_ALTERNATE_ID = "";
* @return collection of cm handles ids which are acceptable
*/
public Collection<String> getIdsOfCmHandlesWithRejectedAlternateId(
- final Collection<NcmpServiceCmHandle> newNcmpServiceCmHandles,
- final Operation operation) {
- final Set<String> assignedAlternateIds = getAlternateIdsAlreadyInDb(newNcmpServiceCmHandles);
+ final Collection<NcmpServiceCmHandle> newNcmpServiceCmHandles,
+ final Operation operation) {
+ final Set<String> assignedAlternateIds = new HashSet<>(getAlternateIdsAlreadyInDb(newNcmpServiceCmHandles));
final Collection<String> rejectedCmHandleIds = new ArrayList<>();
for (final NcmpServiceCmHandle ncmpServiceCmHandle : newNcmpServiceCmHandles) {
final String cmHandleId = ncmpServiceCmHandle.getCmHandleId();
.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) {
/*
* ============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.
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
private final IMap<String, Object> moduleSyncStartedOnCmHandles;
private final TrustLevelManager trustLevelManager;
private final AlternateIdChecker alternateIdChecker;
+ @Qualifier("cmHandleIdPerAlternateId")
+ private final IMap<String, String> cmHandleIdPerAlternateId;
/**
* Registration of Created, Removed, Updated or Upgraded CM Handles.
}
}
+ /**
+ * 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<YangModelCmHandle> yangModelCmHandles) {
+ final Map<String, String> 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<String> toBeRemovedCmHandleIds = dmiPluginRegistration.getRemovedCmHandles();
}
yangModelCmHandles.removeIf(yangModelCmHandle -> notDeletedCmHandles.contains(yangModelCmHandle.getId()));
updateCmHandleStateBatch(yangModelCmHandles, CmHandleState.DELETED);
+ removeAlternateIdsFromCache(yangModelCmHandles);
dmiPluginRegistrationResponse.setRemovedCmHandles(cmHandleRegistrationResponses);
}
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<String> cmHandleIds =
ncmpServiceCmHandles.stream().map(NcmpServiceCmHandle::getCmHandleId).collect(Collectors.toList());
failedCmHandleRegistrationResponses.addAll(CmHandleRegistrationResponse
}
}
lcmEventsCmHandleStateHandler.initiateStateAdvised(yangModelCmHandlesToRegister);
+ addAlternateIdsToCache(yangModelCmHandlesToRegister);
dmiPluginRegistrationResponse.setCreatedCmHandles(cmHandleRegistrationResponses);
return succeededCmHandleIds;
}
ncmpServiceCmHandle.getDataProducerIdentifier());
}
+ private void removeAlternateIdsFromCache(final Collection<YangModelCmHandle> 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);
+ }
+ }
+ }
+
}
/*
* ============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.
* ================================================================================
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;
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
private final CpsDataService cpsDataService;
private final JsonObjectMapper jsonObjectMapper;
private final AlternateIdChecker alternateIdChecker;
+ @Qualifier("cmHandleIdPerAlternateId")
+ private final IMap<String, String> cmHandleIdPerAlternateId;
/**
* Iterates over incoming updatedNcmpServiceCmHandles and update the dataNodes based on the updated attributes.
}
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);
}
}
Collection<DataNode> 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<YangModelCmHandle> getYangModelCmHandleByAlternateIds(Collection<String> alternateIds);
-
/**
* Get collection of data nodes of given cm handles.
*
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;
private final CpsModuleService cpsModuleService;
private final CpsValidator cpsValidator;
- private final CmHandleQueryService cmHandleQueryService;
/**
* initialize an inventory persistence object.
* @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,
return this.getDataNode(getXPathForCmHandleById(cmHandleId), fetchDescendantsOption);
}
- @Override
- public YangModelCmHandle getYangModelCmHandleByAlternateId(final String alternateId) {
- final String cpsPathForCmHandleByAlternateId = getCpsPathForCmHandleByAlternateId(alternateId);
- final Collection<DataNode> dataNodes = cmHandleQueryService
- .queryNcmpRegistryByCpsPath(cpsPathForCmHandleByAlternateId, OMIT_DESCENDANTS, 1);
- if (dataNodes.isEmpty()) {
- throw new CmHandleNotFoundException(alternateId);
- }
- return YangDataConverter.toYangModelCmHandle(dataNodes.iterator().next());
- }
-
- @Override
- public Collection<YangModelCmHandle> getYangModelCmHandleByAlternateIds(final Collection<String> alternateIds) {
- if (alternateIds.isEmpty()) {
- return Collections.emptyList();
- }
- final String cpsPathForCmHandlesByAlternateIds = getCpsPathForCmHandlesByAlternateIds(alternateIds);
- final Collection<DataNode> dataNodes = cmHandleQueryService.queryNcmpRegistryByCpsPath(
- cpsPathForCmHandlesByAlternateIds, INCLUDE_ALL_DESCENDANTS, alternateIds.size());
- return YangDataConverter.toYangModelCmHandles(dataNodes);
- }
-
@Override
public Collection<DataNode> getCmHandleDataNodes(final Collection<String> cmHandleIds,
final FetchDescendantsOption fetchDescendantsOption) {
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<String> 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 + "}";
}
return "{\"cm-handles\":" + jsonObjectMapper.asJsonString(yangModelCmHandles) + "}";
}
-
private Collection<String> getAlternateIdsForCmHandleIds(final Collection<String> cmHandleIds) {
final Collection<DataNode> dataNodes = getCmHandleDataNodes(cmHandleIds, OMIT_DESCENDANTS);
return dataNodes.stream()
/*
* ============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");
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;
private final BlockingQueue<String> moduleSyncWorkQueue;
private final IMap<String, Object> moduleSyncStartedOnCmHandles;
private final ModuleSyncTasks moduleSyncTasks;
+ @Qualifier("cpsAndNcmpLock")
private final IMap<String, String> cpsAndNcmpLock;
private static final int MODULE_SYNC_BATCH_SIZE = 300;
/*
* ============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.
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<String, String> cmHandleIdPerAlternateId;
/**
* Get cm handle that matches longest alternate id by removing elements
* @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) {
--- /dev/null
+/*
+ * ============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<String, String> 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<DataNode> dataNodes = inventoryPersistence.getDataNode(
+ NCMP_DMI_REGISTRY_PARENT, DIRECT_CHILDREN_ONLY).iterator().next().getChildDataNodes();
+ final Collection<YangModelCmHandle> yangModelCmHandles = dataNodes.stream()
+ .map(YangDataConverter::toYangModelCmHandle).toList();
+ addAlternateIdsToCache(yangModelCmHandles);
+ }
+ log.info("Alternate ID map has {} entries", cmHandleIdPerAlternateId.size());
+ }
+
+
+ public void addAlternateIdsToCache(final Collection<YangModelCmHandle> yangModelCmHandles) {
+ cmHandleRegistrationService.addAlternateIdsToCache(yangModelCmHandles);
+ }
+
+}
/*
* ============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.
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
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<String>) >>
- { 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<String>) >>
- { 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)
/*
* ============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.
* ================================================================================
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
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<ILoggingEvent>)
void setup() {
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]
/*
* ============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");
def mockModuleSyncStartedOnCmHandles = Mock(IMap<String, Object>)
def mockTrustLevelManager = Mock(TrustLevelManager)
def mockAlternateIdChecker = Mock(AlternateIdChecker)
+ def mockCmHandleIdPerAlternateId = Mock(IMap)
def objectUnderTest = Spy(new CmHandleRegistrationService(
mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCpsDataService, mockLcmEventsCmHandleStateHandler,
- mockModuleSyncStartedOnCmHandles as IMap<String, Object>, mockTrustLevelManager, mockAlternateIdChecker))
+ mockModuleSyncStartedOnCmHandles, mockTrustLevelManager, mockAlternateIdChecker, mockCmHandleIdPerAlternateId))
def setup() {
// always accept all cm handles
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
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))
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)
/*
* ============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.
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'
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.'() {
}
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
--- /dev/null
+/*
+ * ============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(_)
+ }
+}
+--------------+------------------------------------+-----------------------------------------------------------+
| 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