From: egernug Date: Thu, 13 Nov 2025 11:31:12 +0000 (+0000) Subject: Migrate state data to new cm-handles.cm-handle-state X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F24%2F142424%2F13;p=cps.git Migrate state data to new cm-handles.cm-handle-state - Added new DataMigration class to perform migration - Amended InventoryModelLoader to use migration class - Moved setAndUpdateCmHandleField to InventoryPersistence and made public - Created bulk implementation of setAndUpdateCmHandleField Issue-ID: CPS-3023 Change-Id: Ie6ee5af7d3edb1a4e27de358da1da3624af04f48 Signed-off-by: egernug --- 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 50f5827a3c..dcba2753ff 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 @@ -28,16 +28,11 @@ import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST; import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID; import static org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationServicePropertyHandler.PropertyType.ADDITIONAL_PROPERTY; import static org.onap.cps.ncmp.impl.inventory.CmHandleRegistrationServicePropertyHandler.PropertyType.PUBLIC_PROPERTY; -import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME; -import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR; -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 java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -58,7 +53,6 @@ import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; import org.onap.cps.ncmp.impl.inventory.sync.lcm.CmHandleTransitionPair; import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsHelper; 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; @@ -136,7 +130,10 @@ public class CmHandleRegistrationServicePropertyHandler { final String cmHandleId = ncmpServiceCmHandle.getCmHandleId(); final String newAlternateId = ncmpServiceCmHandle.getAlternateId(); if (StringUtils.isNotBlank(newAlternateId)) { - setAndUpdateCmHandleField(ncmpServiceCmHandle.getCmHandleId(), "alternate-id", newAlternateId); + inventoryPersistence.updateCmHandleField( + ncmpServiceCmHandle.getCmHandleId(), + "alternate-id", + newAlternateId); cmHandleIdPerAlternateId.delete(cmHandleId); cmHandleIdPerAlternateId.set(newAlternateId, cmHandleId); } @@ -160,7 +157,11 @@ public class CmHandleRegistrationServicePropertyHandler { targetDataProducerIdentifier); return; } - setAndUpdateCmHandleField(cmHandleId, "data-producer-identifier", targetDataProducerIdentifier); + + inventoryPersistence.updateCmHandleField( + cmHandleId, + "data-producer-identifier", + targetDataProducerIdentifier); log.debug("dataProducerIdentifier for cmHandle {} updated from {} to {}", cmHandleId, currentDataProducerIdentifier, targetDataProducerIdentifier); sendLcmEventForDataProducerIdentifier(cmHandleId, currentYangModelCmHandle); @@ -243,18 +244,6 @@ public class CmHandleRegistrationServicePropertyHandler { return new DataNodeBuilder().withXpath(xpath).withLeaves(ImmutableMap.copyOf(updatedLeaves)).build(); } - private void setAndUpdateCmHandleField(final String cmHandleIdToUpdate, final String fieldName, - final String newFieldValue) { - final Map> dmiRegistryData = new HashMap<>(1); - final Map cmHandleData = new HashMap<>(2); - cmHandleData.put("id", cmHandleIdToUpdate); - cmHandleData.put(fieldName, newFieldValue); - dmiRegistryData.put("cm-handles", cmHandleData); - cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, - jsonObjectMapper.asJsonString(dmiRegistryData), OffsetDateTime.now(), ContentType.JSON); - log.debug("Updating {} for cmHandle {} with value : {})", fieldName, cmHandleIdToUpdate, newFieldValue); - } - enum PropertyType { ADDITIONAL_PROPERTY("additional-properties"), PUBLIC_PROPERTY("public-properties"); 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 7bd7c8ee1e..f10cc182aa 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 @@ -30,6 +30,7 @@ import org.onap.cps.api.model.ModuleReference; import org.onap.cps.api.parameters.FetchDescendantsOption; import org.onap.cps.ncmp.api.inventory.models.CompositeState; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.models.CmHandleStateUpdate; public interface InventoryPersistence extends NcmpPersistence { @@ -151,4 +152,32 @@ public interface InventoryPersistence extends NcmpPersistence { * @return boolean */ boolean isExistingCmHandleId(String cmHandleId); + + /** + * Updates the specified field of a CM handle with a new value in the DMI registry. + * + * @param cmHandleIdToUpdate the unique identifier of the CM handle to be updated + * @param fieldName the name of the field within the CM handle to be updated + * @param newFieldValue the new value to be set for the + * specified field of the CM handle + */ + void updateCmHandleField(String cmHandleIdToUpdate, String fieldName, String newFieldValue); + + + /** + * Bulk updates the specified fields of a batch of CM handles with a new value in the DMI registry. + * + * @param fieldName the name of the field within the CM handle to be updated + * @param cmHandleIdToValue the CM handle to be updated and new value + */ + void updateCmHandleFields(final String fieldName, final Map cmHandleIdToValue); + + /** + * Method to update a batch of cm handles status to the value in CompositeState. + * + * @param cmHandleStateUpdates the cmHandleId and state change being performed on it + */ + void bulkUpdateCmHandleStates(List cmHandleStateUpdates); + + } 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 f069407cea..213094bf8a 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 @@ -49,6 +49,7 @@ import org.onap.cps.api.parameters.FetchDescendantsOption; 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; +import org.onap.cps.ncmp.impl.models.CmHandleStateUpdate; import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.onap.cps.utils.ContentType; import org.onap.cps.utils.CpsValidator; @@ -200,6 +201,54 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv } } + /** + * Updates the specified field of a CM handle with a new value in the DMI registry. + * + * @param cmHandleId the unique identifier of the CM handle to be updated + * @param fieldName the name of the field within the CM handle to be updated + * @param fieldValue the new value to be set for + * the specified field of the CM handle + */ + @Override + public void updateCmHandleField(final String cmHandleId, final String fieldName, + final String fieldValue) { + updateCmHandleFields(fieldName, Collections.singletonMap(cmHandleId, fieldValue)); + } + + @Override + public void updateCmHandleFields(final String fieldName, final Map newValuePerCmHandleId) { + if (!newValuePerCmHandleId.isEmpty()) { + final Map targetCmHandleStatePerCmHandleId = new HashMap<>(); + final List> targetCmHandleStatesPerCmHandleIds = new ArrayList<>(); + + for (final Map.Entry entry : newValuePerCmHandleId.entrySet()) { + final Map cmHandleData = new HashMap<>(); + cmHandleData.put("id", entry.getKey()); + cmHandleData.put(fieldName, entry.getValue()); + targetCmHandleStatesPerCmHandleIds.add(cmHandleData); + log.debug("Updating {} for cmHandle {} to {}", fieldName, entry.getKey(), entry.getValue()); + } + targetCmHandleStatePerCmHandleId.put("cm-handles", targetCmHandleStatesPerCmHandleIds); + cpsDataService.updateNodeLeaves( + NCMP_DATASPACE_NAME, + NCMP_DMI_REGISTRY_ANCHOR, + NCMP_DMI_REGISTRY_PARENT, + jsonObjectMapper.asJsonString(targetCmHandleStatePerCmHandleId), + OffsetDateTime.now(), + ContentType.JSON); + } + } + + @Override + public void bulkUpdateCmHandleStates(final List cmHandleStateUpdates) { + final Map mappedCmHandleStateUpdates = cmHandleStateUpdates.stream() + .collect(Collectors.toMap( + CmHandleStateUpdate::cmHandleId, + CmHandleStateUpdate::state + )); + updateCmHandleFields("cm-handle-state", mappedCmHandleStateUpdates); + } + private static String getXPathForCmHandleById(final String cmHandleId) { return NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='" + cmHandleId + "']"; } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/models/CmHandleStateUpdate.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/models/CmHandleStateUpdate.java new file mode 100644 index 0000000000..514947b2ea --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/models/CmHandleStateUpdate.java @@ -0,0 +1,23 @@ +/* + * ============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.models; + +public record CmHandleStateUpdate (String cmHandleId, String state) {} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/DataMigration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/DataMigration.java new file mode 100644 index 0000000000..82a117589d --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/DataMigration.java @@ -0,0 +1,87 @@ +/* + * ============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 java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService; +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; +import org.onap.cps.ncmp.impl.models.CmHandleStateUpdate; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class DataMigration { + + public final InventoryPersistence inventoryPersistence; + protected int batchSize = 300; + private final CmHandleQueryService cmHandleQueryService; + private final NetworkCmProxyInventoryFacade networkCmProxyInventoryFacade; + + + /** + * Migration of CompositeState CmHandleState into a new top level attribute. + * One off migration job. + */ + public void migrateInventoryToModelRelease20250722() { + log.info("Inventory data migration started"); + final List cmHandleIds = new ArrayList<>(cmHandleQueryService.getAllCmHandleReferences(false)); + log.info("Number of cm handles to process {}", cmHandleIds.size()); + final int totalCmHandleIds = cmHandleIds.size(); + for (int batchStart = 0; batchStart < totalCmHandleIds; batchStart += batchSize) { + final int batchEnd = Math.min(batchStart + batchSize, cmHandleIds.size()); + final List batchIds = cmHandleIds.subList(batchStart, batchEnd); + migrateBatch(batchIds); + } + log.info("Inventory Cm Handle data migration completed."); + } + + private void migrateBatch(final List cmHandleIds) { + log.debug("Processing batch of {} Cm Handles", cmHandleIds.size()); + final List cmHandleStateUpdates = new ArrayList<>(); + for (final String cmHandleId : cmHandleIds) { + try { + final NcmpServiceCmHandle ncmpServiceCmHandle = + networkCmProxyInventoryFacade.getNcmpServiceCmHandle(cmHandleId); + final String valueFromOldModel = ncmpServiceCmHandle.getCompositeState().getCmHandleState().name(); + cmHandleStateUpdates.add(new CmHandleStateUpdate( + ncmpServiceCmHandle.getCmHandleId(), + valueFromOldModel + )); + } catch (final Exception e) { + log.error("Failed to process CM handle {} state", cmHandleId, e); + } + } + try { + inventoryPersistence.bulkUpdateCmHandleStates(cmHandleStateUpdates); + log.debug("Successfully updated Cm Handles"); + } catch (final Exception e) { + log.error("Failed to perform bulk update for batch", e); + } + } +} + diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java index f20c1c53e7..139fbb70f2 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java @@ -25,10 +25,7 @@ import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.api.CpsAnchorService; -import org.onap.cps.api.CpsDataService; -import org.onap.cps.api.CpsDataspaceService; -import org.onap.cps.api.CpsModuleService; +import org.onap.cps.impl.CpsServicesBundle; import org.onap.cps.init.AbstractModelLoader; import org.onap.cps.init.ModelLoaderLock; import org.onap.cps.init.actuator.ReadinessManager; @@ -43,6 +40,7 @@ import org.springframework.stereotype.Service; @Order(2) public class InventoryModelLoader extends AbstractModelLoader { + private final DataMigration dataMigration; private final ApplicationEventPublisher applicationEventPublisher; private static final String PREVIOUS_SCHEMA_SET_NAME = "dmi-registry-2024-02-23"; @@ -57,15 +55,18 @@ public class InventoryModelLoader extends AbstractModelLoader { * the NCMP inventory model schema sets and managing readiness state during migration. */ public InventoryModelLoader(final ModelLoaderLock modelLoaderLock, - final CpsDataspaceService cpsDataspaceService, - final CpsModuleService cpsModuleService, - final CpsAnchorService cpsAnchorService, - final CpsDataService cpsDataService, + final CpsServicesBundle cpsServicesBundle, final ApplicationEventPublisher applicationEventPublisher, - final ReadinessManager readinessManager) { - super(modelLoaderLock, cpsDataspaceService, cpsModuleService, cpsAnchorService, cpsDataService, + final ReadinessManager readinessManager, + final DataMigration dataMigration) { + super(modelLoaderLock, + cpsServicesBundle.getDataspaceService(), + cpsServicesBundle.getModuleService(), + cpsServicesBundle.getAnchorService(), + cpsServicesBundle.getDataService(), readinessManager); this.applicationEventPublisher = applicationEventPublisher; + this.dataMigration = dataMigration; } @Override @@ -118,13 +119,6 @@ public class InventoryModelLoader extends AbstractModelLoader { log.info("Model Loader #2: Inventory upgraded successfully to model {}", NEW_INVENTORY_SCHEMA_SET_NAME); } - private void performInventoryDataMigration() { - - //1. Load all the cm handles (in batch) - //2. Copy the state and known properties - log.info("Model Loader #2: Inventory module data migration is completed successfully."); - } - private static String toYangFileName(final String schemaSetName) { return INVENTORY_YANG_MODULE_NAME + "@" + getModuleRevision(schemaSetName) + ".yang"; } @@ -136,6 +130,6 @@ public class InventoryModelLoader extends AbstractModelLoader { private void upgradeAndMigrateInventoryModel() { upgradeInventoryModel(); - performInventoryDataMigration(); + dataMigration.migrateInventoryToModelRelease20250722(); } } 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 0c91208319..bf8d8519c4 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 @@ -209,17 +209,14 @@ class CmHandleRegistrationServicePropertyHandlerSpec extends Specification { 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' + mockCmHandleIdPerAlternateId.get(_) >> cmHandleId 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] when: 'cm handle properties is updated' def response = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) - then: 'the update is delegated to cps data service with correct parameters' - 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _, ContentType.JSON) >> - { args -> - assert args[3].contains('alt-1') - } + then: 'the update is delegated to inventory persistence with correct parameters' + 1 * mockInventoryPersistence.updateCmHandleField(cmHandleId, 'alternate-id', 'alt-1') and: 'one successful registration response' response.size() == 1 and: 'the response shows success for the given cm handle id' @@ -245,19 +242,17 @@ class CmHandleRegistrationServicePropertyHandlerSpec extends Specification { } def 'Update CM Handle data producer identifier from #scenario'() { - given: 'an existing cm handle with old data producer identifier' + given: 'an existing cm handle with old data producer identifier' DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId', 'data-producer-identifier': oldDataProducerIdentifier]) - and: 'an update request with a new data producer identifier' + and: 'an update request with a new data producer identifier' def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'cmHandleId', dataProducerIdentifier: 'New Data Producer Identifier') - and: 'the inventory persistence returns updated yang model' + and: 'the inventory persistence returns updated yang model' 1 * mockInventoryPersistence.getYangModelCmHandle('cmHandleId') >> createYangModelCmHandle('cmHandleId', 'New Data Producer Identifier') - when: 'data producer identifier is updated' + when: 'data producer identifier is updated' objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) - then: 'the update node leaves method is invoked once with correct parameters' - 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _, ContentType.JSON) >> { args -> - assert args[3].contains('New Data Producer Identifier') - } - and: 'LCM event is sent' + then: 'the update node leaves method is invoked once with correct parameters' + 1 * mockInventoryPersistence.updateCmHandleField('cmHandleId', 'data-producer-identifier', 'New Data Producer Identifier') + and: 'LCM event is sent' 1 * mockLcmEventsHelper.sendLcmEventBatchAsynchronously({ cmHandleTransitionPairs -> assert cmHandleTransitionPairs[0].targetYangModelCmHandle.dataProducerIdentifier == 'New Data Producer Identifier' }) @@ -294,18 +289,12 @@ class CmHandleRegistrationServicePropertyHandlerSpec extends Specification { when: 'update data producer identifier is called' objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) then: 'the update node leaves method is invoked once with correct parameters' - 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _, ContentType.JSON) >> { args -> - assert args[3].contains('newDataProducerIdentifier') - } + 1 * mockInventoryPersistence.updateCmHandleField('cmHandleId', 'data-producer-identifier', 'newDataProducerIdentifier') and: 'LCM event is sent' 1 * mockLcmEventsHelper.sendLcmEventBatchAsynchronously( { cmHandleTransitionPairs -> assert cmHandleTransitionPairs[0].targetYangModelCmHandle.dataProducerIdentifier == 'newDataProducerIdentifier' assert cmHandleTransitionPairs[0].currentYangModelCmHandle.dataProducerIdentifier == 'oldDataProducerIdentifier' }) - and: 'correct information is logged' - def loggingEvent = logger.list[1] - assert loggingEvent.level == Level.DEBUG - assert loggingEvent.formattedMessage.contains('updated from oldDataProducerIdentifier to newDataProducerIdentifier') } def 'Update CM Handle data producer identifier with null or blank target identifier'() { 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 a1c10aec3a..866ff716d3 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,6 +35,7 @@ import org.onap.cps.api.model.ModuleReference import org.onap.cps.ncmp.api.inventory.models.CmHandleState import org.onap.cps.ncmp.api.inventory.models.CompositeState import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.ncmp.impl.models.CmHandleStateUpdate import org.onap.cps.utils.ContentType import org.onap.cps.utils.CpsValidator import org.onap.cps.utils.JsonObjectMapper @@ -67,15 +68,19 @@ class InventoryPersistenceImplSpec extends Specification { def mockCmHandleIdPerAlternateId = Mock(IMap) - def objectUnderTest = new InventoryPersistenceImpl(mockCpsValidator, spiedJsonObjectMapper, mockCpsAnchorService, mockCpsModuleService, mockCpsDataService, mockCmHandleIdPerAlternateId) + def objectUnderTest = Spy(new InventoryPersistenceImpl(mockCpsValidator, spiedJsonObjectMapper, mockCpsAnchorService, mockCpsModuleService, mockCpsDataService, mockCmHandleIdPerAlternateId)) def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) - def cmHandleId = 'some-cm-handle' + def cmHandleId = 'ch-1' + def updates = [ + new CmHandleStateUpdate("ch-1", "READY"), + new CmHandleStateUpdate("ch-2", "DELETING") + ] def alternateId = 'some-alternate-id' def leaves = ["id":cmHandleId, "alternateId":alternateId,"dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"] - def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']" + def xpath = "/dmi-registry/cm-handles[@id='ch-1']" def cmHandleId2 = 'another-cm-handle' def xpath2 = "/dmi-registry/cm-handles[@id='another-cm-handle']" @@ -87,13 +92,13 @@ class InventoryPersistenceImplSpec extends Specification { new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='myPublicProperty']", leaves: ["name":"myPublicProperty","value":"myPublicValue"])] @Shared - def childDataNodesForCmHandleWithAdditionalProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/additional-properties[@name='myAdditionalProperty']", leaves: ["name":"myAdditionalProperty", "value":"myAdditionalValue"])] + def childDataNodesForCmHandleWithAdditionalProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']/additional-properties[@name='myAdditionalProperty']", leaves: ["name":"myAdditionalProperty", "value":"myAdditionalValue"])] @Shared - def childDataNodesForCmHandleWithPublicProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/public-properties[@name='myPublicProperty']", leaves: ["name":"myPublicProperty","value":"myPublicValue"])] + def childDataNodesForCmHandleWithPublicProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']/public-properties[@name='myPublicProperty']", leaves: ["name":"myPublicProperty","value":"myPublicValue"])] @Shared - def childDataNodesForCmHandleWithState = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/state", leaves: ['cm-handle-state': 'ADVISED'])] + def childDataNodesForCmHandleWithState = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']/state", leaves: ['cm-handle-state': 'ADVISED'])] def 'Retrieve CmHandle using datanode with #scenario.'() { given: 'the cps data service returns a data node from the DMI registry' @@ -161,11 +166,11 @@ class InventoryPersistenceImplSpec extends Specification { def 'Get a Cm Handle Composite State.'() { given: 'a valid cm handle id' - def cmHandleId = 'Some-Cm-Handle' + def cmHandleId = 'ch-1' def dataNode = new DataNode(leaves: ['cm-handle-state': 'ADVISED']) and: 'cps data service returns a valid data node' mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']/state', INCLUDE_ALL_DESCENDANTS) >> [dataNode] + '/dmi-registry/cm-handles[@id=\'ch-1\']/state', INCLUDE_ALL_DESCENDANTS) >> [dataNode] when: 'get cm handle state is invoked' def result = objectUnderTest.getCmHandleState(cmHandleId) then: 'result has returned the correct cm handle state' @@ -176,12 +181,12 @@ class InventoryPersistenceImplSpec extends Specification { def 'Update Cm Handle with #scenario State.'() { given: 'a cm handle and a composite state' - def cmHandleId = 'Some-Cm-Handle' + def cmHandleId = 'ch-1' def compositeState = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime) when: 'update cm handle state is invoked with the #scenario state' objectUnderTest.saveCmHandleState(cmHandleId, compositeState) then: 'update node leaves is invoked with the correct params' - 1 * mockCpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']', expectedJsonData, _ as OffsetDateTime, ContentType.JSON) + 1 * mockCpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@id=\'ch-1\']', expectedJsonData, _ as OffsetDateTime, ContentType.JSON) where: 'the following states are used' scenario | cmHandleState || expectedJsonData 'READY' | CmHandleState.READY || '{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}' @@ -196,21 +201,21 @@ class InventoryPersistenceImplSpec extends Specification { and: 'alternate id cache contains the given cm handle reference' mockCmHandleIdPerAlternateId.containsKey(_) >> true when: 'update cm handle state is invoked with the #scenario state' - def cmHandleStateMap = ['Some-Cm-Handle1' : compositeState1, 'Some-Cm-Handle2' : compositeState2] + def cmHandleStateMap = ['ch-11' : compositeState1, 'ch-12' : compositeState2] objectUnderTest.saveCmHandleStateBatch(cmHandleStateMap) then: 'update node leaves is invoked with the correct params' 1 * mockCpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandlesJsonDataMap, _ as OffsetDateTime, ContentType.JSON) where: 'the following states are used' scenario | cmHandleState || cmHandlesJsonDataMap - 'READY' | CmHandleState.READY || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}'] - 'LOCKED' | CmHandleState.LOCKED || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}'] - 'DELETING' | CmHandleState.DELETING || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}'] + 'READY' | CmHandleState.READY || ['/dmi-registry/cm-handles[@id=\'ch-11\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'ch-12\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}'] + 'LOCKED' | CmHandleState.LOCKED || ['/dmi-registry/cm-handles[@id=\'ch-11\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'ch-12\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}'] + 'DELETING' | CmHandleState.DELETING || ['/dmi-registry/cm-handles[@id=\'ch-11\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'ch-12\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}'] } def 'Update cm handle states when #scenario in alternate id cache.'() { given: 'a map of cm handles composite states' def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, lastUpdateTime: formattedDateAndTime) - def cmHandleStateMap = ['some-cm-handle' : compositeState] + def cmHandleStateMap = ['ch-1' : compositeState] and: 'alternate id cache returns #scenario' mockCmHandleIdPerAlternateId.containsKey(_) >> keyExists mockCmHandleIdPerAlternateId.containsValue(_) >> valueExists @@ -392,5 +397,20 @@ class InventoryPersistenceImplSpec extends Specification { assert result.size() == 2 assert result.id.containsAll([cmHandleId, cmHandleId2]) } + + def 'Update Cm Handle Field.'(){ + when: 'update is called.' + objectUnderTest.updateCmHandleField('ch-1', 'my field', 'my new value') + then: 'call is delegated to updateCmHandleFields' + 1 * objectUnderTest.updateCmHandleFields('my field', ['ch-1':'my new value']) + } + + def 'Bulk update cm handle state.'(){ + when: 'bulk update is called' + objectUnderTest.bulkUpdateCmHandleStates(updates) + then: 'call is made to update the fileds of the cm handle' + 1 * objectUnderTest.updateCmHandleFields('cm-handle-state', ['ch-1':'READY','ch-2':'DELETING']) + } + } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/DataMigrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/DataMigrationSpec.groovy new file mode 100644 index 0000000000..21e752d00a --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/DataMigrationSpec.groovy @@ -0,0 +1,117 @@ +/* + * ============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 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.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.slf4j.LoggerFactory +import spock.lang.Specification +import spock.lang.Subject + +import static org.onap.cps.ncmp.api.inventory.models.CmHandleState.ADVISED +import static org.onap.cps.ncmp.api.inventory.models.CmHandleState.READY + +class DataMigrationSpec extends Specification{ + + def mockCmHandleQueryService = Mock(CmHandleQueryService) + def mockNetworkCmProxyInventoryFacade = Mock(NetworkCmProxyInventoryFacade) + def mockInventoryPersistence = Mock(InventoryPersistence) + def cmHandle1 = new NcmpServiceCmHandle(cmHandleId: 'ch-1', dmiServiceName: 'dmi1', compositeState: new CompositeState(cmHandleState: READY)) + def cmHandle2 = new NcmpServiceCmHandle(cmHandleId: 'ch-2', dmiServiceName: 'dmi1', compositeState: new CompositeState(cmHandleState: ADVISED)) + def cmHandle3 = new NcmpServiceCmHandle(cmHandleId: 'ch-3', dmiServiceName: 'dmi2', compositeState: new CompositeState(cmHandleState: READY)) + + def logger = Spy(ListAppender) + + def setup() { + mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-1') >> cmHandle1 + mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-2') >> cmHandle2 + mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-3') >> cmHandle3 + setupLogger(Level.ERROR) + } + + def cleanup() { + ((Logger) LoggerFactory.getLogger(DataMigration.class)).detachAndStopAllAppenders() + } + + + @Subject + def objectUnderTest = Spy(new DataMigration(mockInventoryPersistence, mockCmHandleQueryService, mockNetworkCmProxyInventoryFacade)) + + def 'CM Handle migration.'() { + given: 'a list of CM handle IDs' + def cmHandleIds = ['ch-1', 'ch-2', 'ch-3'] + mockCmHandleQueryService.getAllCmHandleReferences(false) >> cmHandleIds + when: 'migration is performed' + objectUnderTest.migrateInventoryToModelRelease20250722() + then: 'handles are processed in bulk' + 1 * mockInventoryPersistence.bulkUpdateCmHandleStates({ cmHandleStateUpdates -> + def actualData = cmHandleStateUpdates.collect { [id: it.cmHandleId, state: it.state] } + assert actualData.size() == 3 + assert actualData.containsAll([ + [id: 'ch-1', state: 'READY'], + [id: 'ch-2', state: 'ADVISED'], + [id: 'ch-3', state: 'READY'] + ]) + }) + } + + def 'CM Handle migration with exception for a cm handle in batch.'() { + given: 'a faulty CM handle ID' + def cmHandleIds = ['faultyCmHandle'] + mockCmHandleQueryService.getAllCmHandleReferences(false) >> cmHandleIds + and: 'an exception is thrown when getting cm handle' + mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('faultyCmHandle') >> { throw new RuntimeException('Simulated failure') } + when: 'migration is performed' + objectUnderTest.migrateInventoryToModelRelease20250722() + then: 'migration processes no batches' + 1 * mockInventoryPersistence.bulkUpdateCmHandleStates([]) + } + + def 'Migrate batch with error.'() { + given: 'a cm handle' + def cmHandleIds = ['ch-1'] + mockCmHandleQueryService.getAllCmHandleReferences(false) >> cmHandleIds + and: 'an exception happens updating cm handle states' + mockInventoryPersistence.bulkUpdateCmHandleStates(*_) >> { + throw new RuntimeException('Simulated failure') + } + when: 'migration is performed' + objectUnderTest.migrateInventoryToModelRelease20250722() + then: 'exception is caught and logged' + def loggingEvent = logger.list[0] + assert loggingEvent.level == Level.ERROR + assert loggingEvent.formattedMessage.contains('Failed to perform bulk update for batch') + } + + def setupLogger(level) { + def setupLogger = ((Logger) LoggerFactory.getLogger(DataMigration)) + setupLogger.setLevel(level) + setupLogger.addAppender(logger) + logger.start() + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy index 092ef2c363..9f689344f8 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy @@ -32,6 +32,7 @@ import org.onap.cps.api.model.Dataspace import org.onap.cps.api.model.ModuleDefinition import org.onap.cps.init.ModelLoaderLock import org.onap.cps.init.actuator.ReadinessManager +import org.onap.cps.impl.CpsServicesBundle import org.slf4j.LoggerFactory import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.ApplicationEventPublisher @@ -48,9 +49,17 @@ class InventoryModelLoaderSpec extends Specification { def mockCpsModuleService = Mock(CpsModuleService) def mockCpsDataService = Mock(CpsDataService) def mockCpsAnchorService = Mock(CpsAnchorService) + def cpsServices = new CpsServicesBundle( + mockCpsAdminService, + mockCpsModuleService, + mockCpsAnchorService, + mockCpsDataService + ) + def mockApplicationEventPublisher = Mock(ApplicationEventPublisher) def mockReadinessManager = Mock(ReadinessManager) - def objectUnderTest = new InventoryModelLoader(mockModelLoaderLock, mockCpsAdminService, mockCpsModuleService, mockCpsAnchorService, mockCpsDataService, mockApplicationEventPublisher, mockReadinessManager) + def mockDataMigration = Mock(DataMigration) + def objectUnderTest = new InventoryModelLoader(mockModelLoaderLock, cpsServices, mockApplicationEventPublisher, mockReadinessManager, mockDataMigration) def applicationContext = new AnnotationConfigApplicationContext() @@ -76,6 +85,13 @@ class InventoryModelLoaderSpec extends Specification { applicationContext.close() } + def callPrivatePerformInventoryDataMigration() { + def method = objectUnderTest.class.getDeclaredMethod('upgradeAndMigrateInventoryModel') + method.accessible = true + method.invoke(objectUnderTest) + } + + def 'Onboard subscription model via application ready event.'() { given: 'dataspace is ready for use with default newRevisionEnabled flag' objectUnderTest.newRevisionEnabled = false @@ -138,5 +154,13 @@ class InventoryModelLoaderSpec extends Specification { assert loggingListAppender.list.any { it.message.contains("already installed") } } + def "Perform inventory data migration to Release20250722"() { + when: 'the migration is performed' + callPrivatePerformInventoryDataMigration() + then: 'the call is delegated to the Data Migration service' + 1 * mockDataMigration.migrateInventoryToModelRelease20250722() + } + + } diff --git a/cps-service/src/main/java/org/onap/cps/config/CpsServicesConfig.java b/cps-service/src/main/java/org/onap/cps/config/CpsServicesConfig.java new file mode 100644 index 0000000000..d6029c7bdd --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/config/CpsServicesConfig.java @@ -0,0 +1,42 @@ +/* + * ============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.config; + +import org.onap.cps.api.CpsAnchorService; +import org.onap.cps.api.CpsDataService; +import org.onap.cps.api.CpsDataspaceService; +import org.onap.cps.api.CpsModuleService; +import org.onap.cps.impl.CpsServicesBundle; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CpsServicesConfig { + + @Bean + public CpsServicesBundle cpsServices(final CpsDataspaceService dataspaceService, + final CpsModuleService moduleService, + final CpsAnchorService anchorService, + final CpsDataService dataService) { + return new CpsServicesBundle(dataspaceService, moduleService, anchorService, dataService); + } + +} diff --git a/cps-service/src/main/java/org/onap/cps/impl/CpsServicesBundle.java b/cps-service/src/main/java/org/onap/cps/impl/CpsServicesBundle.java new file mode 100644 index 0000000000..2e764ef36d --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/impl/CpsServicesBundle.java @@ -0,0 +1,39 @@ +/* + * ============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.impl; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.onap.cps.api.CpsAnchorService; +import org.onap.cps.api.CpsDataService; +import org.onap.cps.api.CpsDataspaceService; +import org.onap.cps.api.CpsModuleService; + +@Getter +@RequiredArgsConstructor +public class CpsServicesBundle { + + private final CpsDataspaceService dataspaceService; + private final CpsModuleService moduleService; + private final CpsAnchorService anchorService; + private final CpsDataService dataService; + +} \ No newline at end of file diff --git a/cps-service/src/test/groovy/org/onap/cps/config/CpsServicesConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/config/CpsServicesConfigSpec.groovy new file mode 100644 index 0000000000..9474e523e4 --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/config/CpsServicesConfigSpec.groovy @@ -0,0 +1,53 @@ +/* + * ============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.config + +import org.onap.cps.api.CpsAnchorService +import org.onap.cps.api.CpsDataService +import org.onap.cps.api.CpsDataspaceService +import org.onap.cps.api.CpsModuleService +import org.onap.cps.impl.CpsServicesBundle +import spock.lang.Specification + +class CpsServicesConfigSpec extends Specification { + + def dataspaceService = Mock(CpsDataspaceService) + def moduleService = Mock(CpsModuleService) + def anchorService = Mock(CpsAnchorService) + def dataService = Mock(CpsDataService) + + def 'cpsServices returns bundle wired with given services'() { + given: 'a cps service config' + def objectUnderTest = new CpsServicesConfig() + when: 'cpsServices bean method is invoked' + CpsServicesBundle bundle = objectUnderTest.cpsServices( + dataspaceService, + moduleService, + anchorService, + dataService + ) + then: 'it is wired with the same instances that were passed in' + assert bundle.dataspaceService == dataspaceService + assert bundle.moduleService == moduleService + assert bundle.anchorService == anchorService + assert bundle.dataService == dataService + } +} \ No newline at end of file