From 03f4ef1d793052574693b759eb591268dfaa66d4 Mon Sep 17 00:00:00 2001 From: egernug Date: Tue, 9 Dec 2025 12:26:42 +0000 Subject: [PATCH] Migrate cm-handles.additional-properties to cm-handles.dmi-properties - Split bulkUpdateCmHandleStatesAndProperties and updateCmHandleFields to perform separate tasks - updateCmHandleFields updates fields in previous use cases - bulkUpdateCmHandleStatesAndProperties only updates fields for migration purposes - Added dmiProperties to CmHandleStateAndDmiPropertiesUpdate - Added convertAdditionalPropertiesToJson to convert additionalProperties to dmiProperties Issue-ID: CPS-3063 Change-Id: I7560a289150147d1cda40e75a41d52c2723316e8 Signed-off-by: egernug --- .../ncmp/impl/inventory/InventoryPersistence.java | 8 +++-- .../impl/inventory/InventoryPersistenceImpl.java | 42 ++++++++++++++-------- ...ateUpdate.java => CmHandleMigrationDetail.java} | 2 +- .../java/org/onap/cps/ncmp/init/DataMigration.java | 22 ++++++++---- .../inventory/InventoryPersistenceImplSpec.groovy | 21 +++++++---- .../onap/cps/ncmp/init/DataMigrationSpec.groovy | 40 ++++++++++++--------- docs/api/swagger/cps/openapi.yaml | 7 ++++ 7 files changed, 94 insertions(+), 48 deletions(-) rename cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/models/{CmHandleStateUpdate.java => CmHandleMigrationDetail.java} (91%) 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 f10cc182aa..db517e0114 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,7 +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; +import org.onap.cps.ncmp.impl.models.CmHandleMigrationDetail; public interface InventoryPersistence extends NcmpPersistence { @@ -175,9 +175,11 @@ public interface InventoryPersistence extends NcmpPersistence { /** * 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 + * @param cmHandleMigrationDetails the cmHandleId and state or dimProperties + * change being performed on it */ - void bulkUpdateCmHandleStates(List cmHandleStateUpdates); + void cmHandleBulkMigrate( + List cmHandleMigrationDetails); } 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 213094bf8a..0072cf8ce1 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,7 +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.models.CmHandleMigrationDetail; import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.onap.cps.utils.ContentType; import org.onap.cps.utils.CpsValidator; @@ -218,35 +218,49 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv @Override public void updateCmHandleFields(final String fieldName, final Map newValuePerCmHandleId) { if (!newValuePerCmHandleId.isEmpty()) { - final Map targetCmHandleStatePerCmHandleId = new HashMap<>(); - final List> targetCmHandleStatesPerCmHandleIds = new ArrayList<>(); - + final List> targetCmHandleFieldChangesPerCmHandleIds = 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); + targetCmHandleFieldChangesPerCmHandleIds.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), + jsonObjectMapper.asJsonString(Map.of("cm-handles", targetCmHandleFieldChangesPerCmHandleIds)), 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); + public void cmHandleBulkMigrate( + final List cmHandleMigrationDetails) { + if (cmHandleMigrationDetails.isEmpty()) { + return; + } + final List> cmHandleMigrationDetailAsMaps = + new ArrayList<>(cmHandleMigrationDetails.size()); + for (final CmHandleMigrationDetail cmHandleMigrationDetail : cmHandleMigrationDetails) { + final Map cmHandleMigrationDetailAsMap = new HashMap<>(); + cmHandleMigrationDetailAsMap.put("id", cmHandleMigrationDetail.cmHandleId()); + cmHandleMigrationDetailAsMap.put("cm-handle-state", cmHandleMigrationDetail.state()); + if (cmHandleMigrationDetail.dmiProperties() != null) { + cmHandleMigrationDetailAsMap.put("dmi-properties", cmHandleMigrationDetail.dmiProperties()); + } + cmHandleMigrationDetailAsMaps.add(cmHandleMigrationDetailAsMap); + } + cpsDataService.updateNodeLeaves( + NCMP_DATASPACE_NAME, + NCMP_DMI_REGISTRY_ANCHOR, + NCMP_DMI_REGISTRY_PARENT, + jsonObjectMapper.asJsonString(Map.of("cm-handles", cmHandleMigrationDetailAsMaps)), + OffsetDateTime.now(), + ContentType.JSON); } private static String getXPathForCmHandleById(final String 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/CmHandleMigrationDetail.java similarity index 91% rename from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/models/CmHandleStateUpdate.java rename to cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/models/CmHandleMigrationDetail.java index 514947b2ea..a0cde8c303 100644 --- 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/CmHandleMigrationDetail.java @@ -20,4 +20,4 @@ package org.onap.cps.ncmp.impl.models; -public record CmHandleStateUpdate (String cmHandleId, String state) {} +public record CmHandleMigrationDetail(String cmHandleId, String state, String dmiProperties) {} 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 index 45e8125d4a..b91eab071e 100644 --- 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 @@ -23,13 +23,15 @@ package org.onap.cps.ncmp.init; import java.util.ArrayList; import java.util.List; +import java.util.Map; 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.onap.cps.ncmp.impl.models.CmHandleMigrationDetail; +import org.onap.cps.utils.JsonObjectMapper; import org.springframework.stereotype.Component; @Slf4j @@ -37,9 +39,10 @@ import org.springframework.stereotype.Component; @RequiredArgsConstructor public class DataMigration { - public final InventoryPersistence inventoryPersistence; + private final InventoryPersistence inventoryPersistence; private final CmHandleQueryService cmHandleQueryService; private final NetworkCmProxyInventoryFacade networkCmProxyInventoryFacade; + private final JsonObjectMapper jsonObjectMapper; /** @@ -61,26 +64,31 @@ public class DataMigration { private void migrateBatch(final List cmHandleIds) { log.debug("Processing batch of {} Cm Handles", cmHandleIds.size()); - final List cmHandleStateUpdates = new ArrayList<>(); + final List cmHandleMigrationDetails = + new ArrayList<>(cmHandleIds.size()); for (final String cmHandleId : cmHandleIds) { try { final NcmpServiceCmHandle ncmpServiceCmHandle = networkCmProxyInventoryFacade.getNcmpServiceCmHandle(cmHandleId); - final String valueFromOldModel = ncmpServiceCmHandle.getCompositeState().getCmHandleState().name(); - cmHandleStateUpdates.add(new CmHandleStateUpdate( + cmHandleMigrationDetails.add(new CmHandleMigrationDetail( ncmpServiceCmHandle.getCmHandleId(), - valueFromOldModel + ncmpServiceCmHandle.getCompositeState().getCmHandleState().name(), + convertAdditionalPropertiesToJson(ncmpServiceCmHandle.getAdditionalProperties()) )); } catch (final Exception e) { log.error("Failed to process CM handle {} state", cmHandleId, e); } } try { - inventoryPersistence.bulkUpdateCmHandleStates(cmHandleStateUpdates); + inventoryPersistence.cmHandleBulkMigrate(cmHandleMigrationDetails); log.debug("Successfully updated Cm Handles"); } catch (final Exception e) { log.error("Failed to perform bulk update for batch", e); } } + + private String convertAdditionalPropertiesToJson(final Map additionalProperties) { + return jsonObjectMapper.asJsonString(additionalProperties); + } } 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 866ff716d3..72dba7ac00 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,7 +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.ncmp.impl.models.CmHandleMigrationDetail import org.onap.cps.utils.ContentType import org.onap.cps.utils.CpsValidator import org.onap.cps.utils.JsonObjectMapper @@ -75,8 +75,8 @@ class InventoryPersistenceImplSpec extends Specification { def cmHandleId = 'ch-1' def updates = [ - new CmHandleStateUpdate("ch-1", "READY"), - new CmHandleStateUpdate("ch-2", "DELETING") + new CmHandleMigrationDetail("ch-1", "READY", "some-dmi-properties"), + new CmHandleMigrationDetail("ch-2", "DELETING", "some-dmi-properties") ] 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"] @@ -402,14 +402,23 @@ class InventoryPersistenceImplSpec extends Specification { 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']) + 1 * objectUnderTest.updateCmHandleFields('my field', ['ch-1':'my new value']) } def 'Bulk update cm handle state.'(){ when: 'bulk update is called' - objectUnderTest.bulkUpdateCmHandleStates(updates) + objectUnderTest.cmHandleBulkMigrate(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']) + 1 * mockCpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, { jsonString -> + jsonString.contains('"cm-handle-state":"READY"') && jsonString.contains('"cm-handle-state":"DELETING"') + }, _, ContentType.JSON) + } + + def 'Bulk update with empty list.'() { + when: 'bulk update is called with empty list' + objectUnderTest.cmHandleBulkMigrate([]) + then: 'no database call is made' + 0 * mockCpsDataService.updateNodeLeaves(*_) } } 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 index 45fb48205c..a32b05feee 100644 --- 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 @@ -24,11 +24,13 @@ 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 com.fasterxml.jackson.databind.ObjectMapper 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.onap.cps.utils.JsonObjectMapper import org.slf4j.LoggerFactory import spock.lang.Specification import spock.lang.Subject @@ -41,16 +43,16 @@ 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 jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + def cmHandleWithAdditionalProperty = new NcmpServiceCmHandle(cmHandleId: 'ch-1', dmiServiceName: 'dmi1', compositeState: new CompositeState(cmHandleState: READY), additionalProperties: [id: '4']) + def cmHandleWithAdditionalPropertyEmpty = new NcmpServiceCmHandle(cmHandleId: 'ch-2', dmiServiceName: 'dmi1', compositeState: new CompositeState(cmHandleState: ADVISED), additionalProperties: [:]) + def cmHandleWithAdditionalPropertyNull = new NcmpServiceCmHandle(cmHandleId: 'ch-3', dmiServiceName: 'dmi2', compositeState: new CompositeState(cmHandleState: READY), additionalProperties: null) def logger = Spy(ListAppender) def setup() { - mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-1') >> cmHandle1 - mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-2') >> cmHandle2 - mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-3') >> cmHandle3 + mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-1') >> cmHandleWithAdditionalProperty + mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-2') >> cmHandleWithAdditionalPropertyEmpty + mockNetworkCmProxyInventoryFacade.getNcmpServiceCmHandle('ch-3') >> cmHandleWithAdditionalPropertyNull setupLogger(Level.ERROR) } @@ -60,7 +62,7 @@ class DataMigrationSpec extends Specification{ @Subject - def objectUnderTest = Spy(new DataMigration(mockInventoryPersistence, mockCmHandleQueryService, mockNetworkCmProxyInventoryFacade)) + def objectUnderTest = Spy(new DataMigration(mockInventoryPersistence, mockCmHandleQueryService, mockNetworkCmProxyInventoryFacade, jsonObjectMapper)) def 'CM Handle migration.'() { given: 'a list of CM handle IDs' @@ -69,14 +71,18 @@ class DataMigrationSpec extends Specification{ when: 'migration is performed' objectUnderTest.migrateInventoryToModelRelease20250722(3) then: 'handles are processed in bulk' - 1 * mockInventoryPersistence.bulkUpdateCmHandleStates({ cmHandleStateUpdates -> - def actualData = cmHandleStateUpdates.collect { [id: it.cmHandleId, state: it.state] } + 1 * mockInventoryPersistence.cmHandleBulkMigrate({ cmHandleStateUpdates -> + def actualData = cmHandleStateUpdates.collect { [id: it.cmHandleId, state: it.state, dmiProperties: it.dmiProperties] } assert actualData.size() == 3 - assert actualData.containsAll([ - [id: 'ch-1', state: 'READY'], - [id: 'ch-2', state: 'ADVISED'], - [id: 'ch-3', state: 'READY'] - ]) + assert actualData[0].id == 'ch-1' + assert actualData[0].state == 'READY' + assert actualData[0].dmiProperties == '{"id":"4"}' + assert actualData[1].id == 'ch-2' + assert actualData[1].state == 'ADVISED' + assert actualData[1].dmiProperties == '{}' + assert actualData[2].id == 'ch-3' + assert actualData[2].state == 'READY' + assert actualData[2].dmiProperties == 'null' }) } @@ -89,7 +95,7 @@ class DataMigrationSpec extends Specification{ when: 'migration is performed' objectUnderTest.migrateInventoryToModelRelease20250722(2) then: 'migration processes no batches' - 1 * mockInventoryPersistence.bulkUpdateCmHandleStates([]) + 1 * mockInventoryPersistence.cmHandleBulkMigrate([]) } def 'Migrate batch with error.'() { @@ -97,7 +103,7 @@ class DataMigrationSpec extends Specification{ def cmHandleIds = ['ch-1'] mockCmHandleQueryService.getAllCmHandleReferences(false) >> cmHandleIds and: 'an exception happens updating cm handle states' - mockInventoryPersistence.bulkUpdateCmHandleStates(*_) >> { + mockInventoryPersistence.cmHandleBulkMigrate(*_) >> { throw new RuntimeException('Simulated failure') } when: 'migration is performed' diff --git a/docs/api/swagger/cps/openapi.yaml b/docs/api/swagger/cps/openapi.yaml index ebf3c63e30..0088b26557 100644 --- a/docs/api/swagger/cps/openapi.yaml +++ b/docs/api/swagger/cps/openapi.yaml @@ -1829,6 +1829,13 @@ paths: schema: type: object description: OK + "204": + content: + application/json: + schema: + example: my-resource + type: string + description: Created "400": content: application/json: -- 2.16.6