From 9f6acf20c21251eb3fe9d81a5e2b5135edf96120 Mon Sep 17 00:00:00 2001 From: ToineSiebelink Date: Mon, 9 Feb 2026 10:55:01 +0000 Subject: [PATCH] Introduce LCM Event V2 - Add config parameter to enable V2 (optional, defaults to V1 if not provided) - Introduced data holders specific for version 2 - refactor change detector to produce V2 format and then covert to V1 (easier then the other way around, and better if we in the future remove V1)) - Added ChangeDetector Specs specifically focussed on change in (additional)Properties - Refactor to create Payload directly instead of using in between classes. - Renamed ChangeDetector to PayloadFactory Issue-ID: CPS-2975 Change-Id: I1a5abbbfa9ab71d3826d3d089f8a75001b41bd97 Signed-off-by: ToineSiebelink --- cps-application/src/main/resources/application.yml | 1 + .../main/resources/schemas/lcm/lcm-event-v2.json | 4 +- .../sync/lcm/CmHandlePropertyChangeDetector.java | 189 --------------------- .../sync/lcm/CmHandlePropertyUpdates.java | 34 ---- .../inventory/sync/lcm/LcmEventObjectCreator.java | 47 ++--- .../impl/inventory/sync/lcm/LcmEventProducer.java | 48 +++++- .../impl/inventory/sync/lcm/PayloadFactory.java | 161 ++++++++++++++++++ .../inventory/sync/lcm/LcmEventProducerSpec.groovy | 12 +- ...tectorSpec.groovy => PayloadFactorySpec.groovy} | 80 +++++---- 9 files changed, 285 insertions(+), 291 deletions(-) delete mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyChangeDetector.java delete mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyUpdates.java create mode 100644 cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/PayloadFactory.java rename cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/{CmHandlePropertyChangeDetectorSpec.groovy => PayloadFactorySpec.groovy} (67%) diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index f9537b4686..bb41e65b8d 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -112,6 +112,7 @@ app: lcm: events: topic: ${LCM_EVENTS_TOPIC:ncmp-events} + event-schema-version: "v1" dmi: cm-events: topic: ${DMI_CM_EVENTS_TOPIC:dmi-cm-events} diff --git a/cps-ncmp-events/src/main/resources/schemas/lcm/lcm-event-v2.json b/cps-ncmp-events/src/main/resources/schemas/lcm/lcm-event-v2.json index 9ebf453ac1..2831e4b1ac 100644 --- a/cps-ncmp-events/src/main/resources/schemas/lcm/lcm-event-v2.json +++ b/cps-ncmp-events/src/main/resources/schemas/lcm/lcm-event-v2.json @@ -22,14 +22,14 @@ "description": "cmHandle id", "type": "string" }, - "oldProperties": { + "oldValues": { "description": "the old cmHandle properties(values) that have changed or been deleted", "type": "object", "default": null, "existingJavaType": "java.util.Map", "additionalProperties": false }, - "newProperties": { + "newValues": { "description": "cmHandle properties(values) that have been changed or been created", "type": "object", "default": null, diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyChangeDetector.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyChangeDetector.java deleted file mode 100644 index cba35e0ba9..0000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyChangeDetector.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2025-2026 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.inventory.sync.lcm; - -import com.google.common.collect.MapDifference; -import com.google.common.collect.Maps; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; -import org.onap.cps.ncmp.events.lcm.Values; - -/** - * Utility class for examining and determining changes in CM handle properties. - */ -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class CmHandlePropertyChangeDetector { - - /** - * Determines property (update) values for creating a new CM handle. - * - * @param ncmpServiceCmHandle the CM handle being created - * @return CmHandlePropertyUpdates containing new values for the created CM handle - */ - static CmHandlePropertyUpdates determineUpdatesForCreate(final NcmpServiceCmHandle ncmpServiceCmHandle) { - final CmHandlePropertyUpdates cmHandlePropertyUpdates = new CmHandlePropertyUpdates(); - cmHandlePropertyUpdates.setNewValues(new Values()); - cmHandlePropertyUpdates.getNewValues().setDataSyncEnabled(getDataSyncEnabledFlag(ncmpServiceCmHandle)); - cmHandlePropertyUpdates.getNewValues() - .setCmHandleState(mapCmHandleStateToLcmEventCmHandleState(ncmpServiceCmHandle)); - cmHandlePropertyUpdates.getNewValues() - .setCmHandleProperties(List.of(ncmpServiceCmHandle.getPublicProperties())); - return cmHandlePropertyUpdates; - } - - /** - * Determines property updates between current and target CM handle states. - * - * @param currentNcmpServiceCmHandle the current CM handle state - * @param targetNcmpServiceCmHandle the target CM handle state - * @return CmHandlePropertyUpdates containing old and new values for changed properties - */ - static CmHandlePropertyUpdates determineUpdates(final NcmpServiceCmHandle currentNcmpServiceCmHandle, - final NcmpServiceCmHandle targetNcmpServiceCmHandle) { - final boolean hasDataSyncFlagEnabledChanged = - hasDataSyncEnabledFlagChanged(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle); - final boolean hasCmHandleStateChanged = - hasCmHandleStateChanged(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle); - final boolean arePublicCmHandlePropertiesEqual = - arePublicCmHandlePropertiesEqual(currentNcmpServiceCmHandle.getPublicProperties(), - targetNcmpServiceCmHandle.getPublicProperties() - ); - - final CmHandlePropertyUpdates cmHandlePropertyUpdates = new CmHandlePropertyUpdates(); - - if (hasDataSyncFlagEnabledChanged || hasCmHandleStateChanged || (!arePublicCmHandlePropertiesEqual)) { - cmHandlePropertyUpdates.setOldValues(new Values()); - cmHandlePropertyUpdates.setNewValues(new Values()); - } else { - return cmHandlePropertyUpdates; - } - - if (hasDataSyncFlagEnabledChanged) { - setDataSyncEnabledFlag(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle, cmHandlePropertyUpdates); - } - - if (hasCmHandleStateChanged) { - setCmHandleStateChange(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle, cmHandlePropertyUpdates); - } - - if (!arePublicCmHandlePropertiesEqual) { - setPublicCmHandlePropertiesChange(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle, - cmHandlePropertyUpdates); - } - - return cmHandlePropertyUpdates; - - } - - private static void setDataSyncEnabledFlag(final NcmpServiceCmHandle currentNcmpServiceCmHandle, - final NcmpServiceCmHandle targetNcmpServiceCmHandle, - final CmHandlePropertyUpdates cmHandlePropertyUpdates) { - cmHandlePropertyUpdates.getOldValues().setDataSyncEnabled(getDataSyncEnabledFlag(currentNcmpServiceCmHandle)); - cmHandlePropertyUpdates.getNewValues().setDataSyncEnabled(getDataSyncEnabledFlag(targetNcmpServiceCmHandle)); - - } - - private static void setCmHandleStateChange(final NcmpServiceCmHandle currentNcmpServiceCmHandle, - final NcmpServiceCmHandle targetNcmpServiceCmHandle, - final CmHandlePropertyUpdates cmHandlePropertyUpdates) { - cmHandlePropertyUpdates.getOldValues() - .setCmHandleState(mapCmHandleStateToLcmEventCmHandleState(currentNcmpServiceCmHandle)); - cmHandlePropertyUpdates.getNewValues() - .setCmHandleState(mapCmHandleStateToLcmEventCmHandleState(targetNcmpServiceCmHandle)); - } - - private static void setPublicCmHandlePropertiesChange(final NcmpServiceCmHandle currentNcmpServiceCmHandle, - final NcmpServiceCmHandle targetNcmpServiceCmHandle, - final CmHandlePropertyUpdates cmHandlePropertyUpdates) { - - final Map> publicCmHandlePropertiesDifference = - getPublicCmHandlePropertiesDifference(currentNcmpServiceCmHandle.getPublicProperties(), - targetNcmpServiceCmHandle.getPublicProperties() - ); - cmHandlePropertyUpdates.getOldValues() - .setCmHandleProperties(List.of(publicCmHandlePropertiesDifference.get("oldValues"))); - cmHandlePropertyUpdates.getNewValues() - .setCmHandleProperties(List.of(publicCmHandlePropertiesDifference.get("newValues"))); - - } - - private static Values.CmHandleState mapCmHandleStateToLcmEventCmHandleState( - final NcmpServiceCmHandle ncmpServiceCmHandle) { - return Values.CmHandleState.fromValue(ncmpServiceCmHandle.getCompositeState().getCmHandleState().name()); - } - - private static Boolean getDataSyncEnabledFlag(final NcmpServiceCmHandle ncmpServiceCmHandle) { - return ncmpServiceCmHandle.getCompositeState().getDataSyncEnabled(); - } - - private static boolean hasDataSyncEnabledFlagChanged(final NcmpServiceCmHandle currentNcmpServiceCmHandle, - final NcmpServiceCmHandle targetNcmpServiceCmHandle) { - final Boolean currentDataSyncFlag = currentNcmpServiceCmHandle.getCompositeState().getDataSyncEnabled(); - final Boolean targetDataSyncFlag = targetNcmpServiceCmHandle.getCompositeState().getDataSyncEnabled(); - - if (targetDataSyncFlag == null) { - return currentDataSyncFlag != null; - } - - return !targetDataSyncFlag.equals(currentDataSyncFlag); - } - - private static boolean hasCmHandleStateChanged(final NcmpServiceCmHandle currentNcmpServiceCmHandle, - final NcmpServiceCmHandle targetNcmpServiceCmHandle) { - return targetNcmpServiceCmHandle.getCompositeState().getCmHandleState() - != currentNcmpServiceCmHandle.getCompositeState().getCmHandleState(); - } - - private static boolean arePublicCmHandlePropertiesEqual(final Map currentCmHandleProperties, - final Map targetCmHandleProperties) { - if (targetCmHandleProperties.size() != currentCmHandleProperties.size()) { - return false; - } - return targetCmHandleProperties.equals(currentCmHandleProperties); - } - - private static Map> getPublicCmHandlePropertiesDifference( - final Map currentCmHandleProperties, - final Map targetCmHandleProperties) { - final Map> oldAndNewPropertiesDifferenceMap = new HashMap<>(2); - - final MapDifference cmHandlePropertiesDifference = - Maps.difference(targetCmHandleProperties, currentCmHandleProperties); - - final Map oldValues = new HashMap<>(cmHandlePropertiesDifference.entriesOnlyOnRight()); - final Map newValues = new HashMap<>(cmHandlePropertiesDifference.entriesOnlyOnLeft()); - - cmHandlePropertiesDifference.entriesDiffering().keySet().forEach(cmHandlePropertyName -> { - oldValues.put(cmHandlePropertyName, currentCmHandleProperties.get(cmHandlePropertyName)); - newValues.put(cmHandlePropertyName, targetCmHandleProperties.get(cmHandlePropertyName)); - }); - - oldAndNewPropertiesDifferenceMap.put("oldValues", oldValues); - oldAndNewPropertiesDifferenceMap.put("newValues", newValues); - - return oldAndNewPropertiesDifferenceMap; - } - -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyUpdates.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyUpdates.java deleted file mode 100644 index 7a956d1963..0000000000 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyUpdates.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2025-2026 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.inventory.sync.lcm; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.onap.cps.ncmp.events.lcm.Values; - -@NoArgsConstructor -@Getter -@Setter -class CmHandlePropertyUpdates { - private Values oldValues; - private Values newValues; -} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventObjectCreator.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventObjectCreator.java index 85cc286019..28c5dd3dd4 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventObjectCreator.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventObjectCreator.java @@ -31,7 +31,9 @@ import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.events.lcm.LcmEventBase; import org.onap.cps.ncmp.events.lcm.LcmEventV1; +import org.onap.cps.ncmp.events.lcm.LcmEventV2; import org.onap.cps.ncmp.events.lcm.PayloadV1; +import org.onap.cps.ncmp.events.lcm.PayloadV2; import org.onap.cps.ncmp.impl.utils.EventDateTimeFormatter; import org.springframework.stereotype.Service; @@ -58,22 +60,34 @@ public class LcmEventObjectCreator { determineEventType(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle); final LcmEventV1 lcmEventV1 = new LcmEventV1(); populateHeaderDetails(lcmEventV1, cmHandleId, lcmEventType); - final PayloadV1 payloadV1 = new PayloadV1(); - payloadV1.setCmHandleId(cmHandleId); - payloadV1.setAlternateId(targetNcmpServiceCmHandle.getAlternateId()); - payloadV1.setModuleSetTag(targetNcmpServiceCmHandle.getModuleSetTag()); - payloadV1.setDataProducerIdentifier(targetNcmpServiceCmHandle.getDataProducerIdentifier()); - final CmHandlePropertyUpdates cmHandlePropertyUpdates = - determineEventValues(lcmEventType, currentNcmpServiceCmHandle, targetNcmpServiceCmHandle); - payloadV1.setOldValues(cmHandlePropertyUpdates.getOldValues()); - payloadV1.setNewValues(cmHandlePropertyUpdates.getNewValues()); + final PayloadV1 payloadV1 = PayloadFactory.createPayloadV1(lcmEventType, currentNcmpServiceCmHandle, + targetNcmpServiceCmHandle); lcmEventV1.setEvent(payloadV1); return lcmEventV1; } + /** + * Create Lifecycle Management Event Version 2. + * + * @param currentNcmpServiceCmHandle current ncmp service cmhandle + * @param targetNcmpServiceCmHandle target ncmp service cmhandle + * @return Populated LcmEvent Version 2 + */ + public LcmEventV2 createLcmEventV2(final NcmpServiceCmHandle currentNcmpServiceCmHandle, + final NcmpServiceCmHandle targetNcmpServiceCmHandle) { + final LcmEventV2 lcmEventV2 = new LcmEventV2(); + final String cmHandleId = targetNcmpServiceCmHandle.getCmHandleId(); + final LcmEventType lcmEventType = + determineEventType(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle); + populateHeaderDetails(lcmEventV2, cmHandleId, lcmEventType); + final PayloadV2 payloadV2 = PayloadFactory.createPayloadV2(lcmEventType, currentNcmpServiceCmHandle, + targetNcmpServiceCmHandle); + lcmEventV2.setEvent(payloadV2); + return lcmEventV2; + } + private static LcmEventType determineEventType(final NcmpServiceCmHandle currentNcmpServiceCmHandle, final NcmpServiceCmHandle targetNcmpServiceCmHandle) { - if (currentNcmpServiceCmHandle.getCompositeState() == null) { return CREATE; } else if (targetNcmpServiceCmHandle.getCompositeState().getCmHandleState() == DELETED) { @@ -82,19 +96,6 @@ public class LcmEventObjectCreator { return UPDATE; } - private static CmHandlePropertyUpdates determineEventValues(final LcmEventType lcmEventType, - final NcmpServiceCmHandle currentNcmpServiceCmHandle, - final NcmpServiceCmHandle targetNcmpServiceCmHandle) { - if (CREATE == lcmEventType) { - return CmHandlePropertyChangeDetector.determineUpdatesForCreate(targetNcmpServiceCmHandle); - } - if (UPDATE == lcmEventType) { - return CmHandlePropertyChangeDetector.determineUpdates(currentNcmpServiceCmHandle, - targetNcmpServiceCmHandle); - } - return new CmHandlePropertyUpdates(); - } - private void populateHeaderDetails(final LcmEventBase lcmEventBase, final String eventCorrelationId, final LcmEventType lcmEventType) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventProducer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventProducer.java index c0f7370748..0e17a037ec 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventProducer.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventProducer.java @@ -34,6 +34,9 @@ import org.onap.cps.events.EventProducer; import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.events.lcm.LcmEventBase; import org.onap.cps.ncmp.events.lcm.LcmEventV1; +import org.onap.cps.ncmp.events.lcm.LcmEventV2; +import org.onap.cps.ncmp.events.lcm.PayloadV1; +import org.onap.cps.ncmp.events.lcm.PayloadV2; import org.onap.cps.ncmp.events.lcm.Values; import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.springframework.beans.factory.annotation.Value; @@ -61,6 +64,9 @@ public class LcmEventProducer { @Value("${app.lcm.events.topic:ncmp-events}") private String topicName; + @Value("${app.lcm.events.event-schema-version:v1}") + private String eventSchemaVersion; + @Value("${notification.enabled:true}") private boolean notificationsEnabled; @@ -93,14 +99,20 @@ public class LcmEventProducer { private void sendLcmEvent(final NcmpServiceCmHandle currentNcmpServiceCmHandle, final NcmpServiceCmHandle targetNcmpServiceCmHandle) { if (notificationsEnabled) { - final LcmEventV1 lcmEventV1 = lcmEventObjectCreator.createLcmEventV1(currentNcmpServiceCmHandle, - targetNcmpServiceCmHandle); final Timer.Sample timerSample = Timer.start(meterRegistry); + final LcmEventBase lcmEvent; + if (useSchemaV2()) { + lcmEvent = lcmEventObjectCreator.createLcmEventV2(currentNcmpServiceCmHandle, + targetNcmpServiceCmHandle); + } else { + lcmEvent = lcmEventObjectCreator.createLcmEventV1(currentNcmpServiceCmHandle, + targetNcmpServiceCmHandle); + } try { - final Map headersAsMap = extractHeadersAsMap(lcmEventV1); + final Map headersAsMap = extractHeadersAsMap(lcmEvent); final String eventKey = currentNcmpServiceCmHandle.getCmHandleId(); - eventProducer.sendLegacyEvent(topicName, eventKey, headersAsMap, lcmEventV1); - recordMetrics(lcmEventV1, timerSample); + eventProducer.sendLegacyEvent(topicName, eventKey, headersAsMap, lcmEvent); + recordMetrics(lcmEvent, timerSample); } catch (final KafkaException e) { log.error("Unable to send message to topic : {} and cause : {}", topicName, e.getMessage()); } @@ -121,23 +133,41 @@ public class LcmEventProducer { return headersAsMap; } - private void recordMetrics(final LcmEventV1 lcmEventV1, final Timer.Sample timerSample) { + private void recordMetrics(final LcmEventBase lcmEvent, final Timer.Sample timerSample) { final List tags = new ArrayList<>(4); tags.add(METRIC_TAG_CLASS); tags.add(METRIC_TAG_METHOD); - tags.add(createCmHandleStateTag("oldCmHandleState", lcmEventV1.getEvent().getOldValues())); - tags.add(createCmHandleStateTag("newCmHandleState", lcmEventV1.getEvent().getNewValues())); + if (lcmEvent instanceof LcmEventV2) { + final PayloadV2 payloadV2 = ((LcmEventV2) lcmEvent).getEvent(); + tags.add(createCmHandleStateTagForV2("oldCmHandleState", payloadV2.getOldValues())); + tags.add(createCmHandleStateTagForV2("newCmHandleState", payloadV2.getNewValues())); + } else { + final PayloadV1 payloadV1 = ((LcmEventV1) lcmEvent).getEvent(); + tags.add(createCmHandleStateTagForV1("oldCmHandleState", payloadV1.getOldValues())); + tags.add(createCmHandleStateTagForV1("newCmHandleState", payloadV1.getNewValues())); + } timerSample.stop(Timer.builder("cps.ncmp.lcm.events.send") .description("Time taken to send a LCM event") .tags(tags) .register(meterRegistry)); } - private Tag createCmHandleStateTag(final String tagLabel, final Values values) { + private Tag createCmHandleStateTagForV1(final String tagLabel, final Values values) { if (values == null) { return Tag.of(tagLabel, "N/A"); } return Tag.of(tagLabel, values.getCmHandleState().value()); } + private Tag createCmHandleStateTagForV2(final String tagLabel, final Map properties) { + if (properties == null || !properties.containsKey("cmHandleState")) { + return Tag.of(tagLabel, "N/A"); + } + return Tag.of(tagLabel, properties.get("cmHandleState").toString()); + } + + private boolean useSchemaV2() { + return "v2".equals(eventSchemaVersion); + } + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/PayloadFactory.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/PayloadFactory.java new file mode 100644 index 0000000000..3ab8ca1481 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/PayloadFactory.java @@ -0,0 +1,161 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025-2026 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.inventory.sync.lcm; + +import com.google.common.collect.MapDifference; +import com.google.common.collect.Maps; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.onap.cps.ncmp.api.inventory.models.CmHandleState; +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; +import org.onap.cps.ncmp.events.lcm.PayloadV1; +import org.onap.cps.ncmp.events.lcm.PayloadV2; +import org.onap.cps.ncmp.events.lcm.Values; + +/** + * Utility class for examining and identifying changes in CM handle properties. + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class PayloadFactory { + + static PayloadV1 createPayloadV1(final LcmEventType lcmEventType, + final NcmpServiceCmHandle currentNcmpServiceCmHandle, + final NcmpServiceCmHandle targetNcmpServiceCmHandle) { + final PayloadV2 payloadV2 = createPayloadV2(lcmEventType, currentNcmpServiceCmHandle, + targetNcmpServiceCmHandle); + final PayloadV1 payloadV1 = toV1Format(payloadV2); + payloadV1.setAlternateId(targetNcmpServiceCmHandle.getAlternateId()); + payloadV1.setModuleSetTag(targetNcmpServiceCmHandle.getModuleSetTag()); + payloadV1.setDataProducerIdentifier(targetNcmpServiceCmHandle.getDataProducerIdentifier()); + return payloadV1; + } + + static PayloadV2 createPayloadV2(final LcmEventType lcmEventType, + final NcmpServiceCmHandle currentNcmpServiceCmHandle, + final NcmpServiceCmHandle targetNcmpServiceCmHandle) { + final PayloadV2 payloadV2 = switch (lcmEventType) { + case CREATE -> getPayLoadForCreate(targetNcmpServiceCmHandle); + case UPDATE -> identifyChanges(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle); + default -> new PayloadV2(); + }; + payloadV2.setCmHandleId(targetNcmpServiceCmHandle.getCmHandleId()); + return payloadV2; + } + + static PayloadV2 getPayLoadForCreate(final NcmpServiceCmHandle ncmpServiceCmHandle) { + final PayloadV2 payload = new PayloadV2(); + final Map newProperties = new HashMap<>(); + newProperties.put("dataSyncEnabled", ncmpServiceCmHandle.getCompositeState().getDataSyncEnabled()); + newProperties.put("cmHandleState", ncmpServiceCmHandle.getCompositeState().getCmHandleState().name()); + newProperties.putAll(ncmpServiceCmHandle.getPublicProperties()); + payload.setNewValues(newProperties); + return payload; + } + + static PayloadV2 identifyChanges(final NcmpServiceCmHandle currentNcmpServiceCmHandle, + final NcmpServiceCmHandle targetNcmpServiceCmHandle) { + final Boolean currentDataSync = currentNcmpServiceCmHandle.getCompositeState().getDataSyncEnabled(); + final Boolean targetDataSync = targetNcmpServiceCmHandle.getCompositeState().getDataSyncEnabled(); + final boolean dataSyncEnabledChanged = !Objects.equals(currentDataSync, targetDataSync); + + final CmHandleState currentCmHandleState = currentNcmpServiceCmHandle.getCompositeState().getCmHandleState(); + final CmHandleState targetCmHandleState = targetNcmpServiceCmHandle.getCompositeState().getCmHandleState(); + final boolean cmHandleStateChanged = !Objects.equals(currentCmHandleState, targetCmHandleState); + + final Map currentPublicProperties = currentNcmpServiceCmHandle.getPublicProperties(); + final Map targetPublicProperties = targetNcmpServiceCmHandle.getPublicProperties(); + final boolean publicPropertiesChanged = !Objects.equals(currentPublicProperties, targetPublicProperties); + + final PayloadV2 payload = new PayloadV2(); + if (!dataSyncEnabledChanged && !cmHandleStateChanged && !publicPropertiesChanged) { + return payload; + } + + final Map oldProperties = new HashMap<>(); + final Map newProperties = new HashMap<>(); + + if (dataSyncEnabledChanged) { + oldProperties.put("dataSyncEnabled", currentDataSync); + newProperties.put("dataSyncEnabled", targetDataSync); + } + + if (cmHandleStateChanged) { + oldProperties.put("cmHandleState", + currentNcmpServiceCmHandle.getCompositeState().getCmHandleState().name()); + newProperties.put("cmHandleState", + targetNcmpServiceCmHandle.getCompositeState().getCmHandleState().name()); + } + + if (publicPropertiesChanged) { + final MapDifference mapDifference = Maps.difference( + targetNcmpServiceCmHandle.getPublicProperties(), + currentNcmpServiceCmHandle.getPublicProperties()); + + oldProperties.putAll(mapDifference.entriesOnlyOnRight()); + newProperties.putAll(mapDifference.entriesOnlyOnLeft()); + + mapDifference.entriesDiffering().forEach((key, valueDifference) -> { + oldProperties.put(key, valueDifference.rightValue()); + newProperties.put(key, valueDifference.leftValue()); + }); + } + payload.setOldValues(oldProperties); + payload.setNewValues(newProperties); + return payload; + } + + private static PayloadV1 toV1Format(final PayloadV2 payloadV2) { + final PayloadV1 payloadV1 = new PayloadV1(); + payloadV1.setCmHandleId(payloadV2.getCmHandleId()); + if (payloadV2.getOldValues() != null) { + payloadV1.setOldValues(mapToValues(payloadV2.getOldValues())); + } + if (payloadV2.getNewValues() != null) { + payloadV1.setNewValues(mapToValues(payloadV2.getNewValues())); + } + return payloadV1; + } + + private static Values mapToValues(final Map properties) { + final Values values = new Values(); + if (properties.containsKey("dataSyncEnabled")) { + values.setDataSyncEnabled((Boolean) properties.get("dataSyncEnabled")); + } + if (properties.containsKey("cmHandleState")) { + values.setCmHandleState(Values.CmHandleState.fromValue((String) properties.get("cmHandleState"))); + } + final Map publicProperties = new HashMap<>(); + properties.forEach((key, value) -> { + if (!"dataSyncEnabled".equals(key) && !"cmHandleState".equals(key)) { + publicProperties.put(key, (String) value); + } + }); + if (!publicProperties.isEmpty()) { + values.setCmHandleProperties(List.of(publicProperties)); + } + return values; + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventProducerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventProducerSpec.groovy index 8332258b1e..42bb9d7840 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventProducerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventProducerSpec.groovy @@ -43,17 +43,20 @@ class LcmEventProducerSpec extends Specification { new YangModelCmHandle(id: 'ch-1', compositeState: new CompositeState(cmHandleState: ADVISED), additionalProperties: [], publicProperties: []) ) - def 'Create and send lcm event where notifications are #scenario.'() { + def 'Create and send lcm event #eventVersion where notifications are #scenario.'() { given: 'notificationsEnabled is #notificationsEnabled' objectUnderTest.notificationsEnabled = notificationsEnabled + objectUnderTest.eventSchemaVersion = eventVersion when: 'event send for (batch of) 1 cm handle transition pair (new cm handle going to READY)' objectUnderTest.sendLcmEventBatchAsynchronously([cmHandleTransitionPair]) then: 'producer is called #expectedTimesMethodCalled times with correct identifiers' expectedTimesMethodCalled * mockEventProducer.sendLegacyEvent(_, 'ch-1', _, _) >> { args -> { def eventHeaders = args[2] + def event = args[3] assert UUID.fromString(eventHeaders.get('eventId')) != null assert eventHeaders.get('eventCorrelationId') == 'ch-1' + assert event.class.simpleName == expectedEventClass } } and: 'metrics are recorded with correct tags' @@ -65,9 +68,10 @@ class LcmEventProducerSpec extends Specification { assert timer == null } where: 'the following values are used' - scenario | notificationsEnabled || expectedTimesMethodCalled - 'enabled' | true || 1 - 'disabled' | false || 0 + scenario | eventVersion | notificationsEnabled || expectedTimesMethodCalled | expectedEventClass + 'enabled' | 'v1' | true || 1 | 'LcmEventV1' + 'enabled' | 'v2' | true || 1 | 'LcmEventV2' + 'disabled' | 'v1' | false || 0 | 'N/A' } def 'Exception while sending message.'(){ diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyChangeDetectorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/PayloadFactorySpec.groovy similarity index 67% rename from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyChangeDetectorSpec.groovy rename to cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/PayloadFactorySpec.groovy index 20e7b9596a..d711305d8f 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyChangeDetectorSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/PayloadFactorySpec.groovy @@ -26,17 +26,17 @@ import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.events.lcm.Values import spock.lang.Specification -class CmHandlePropertyChangeDetectorSpec extends Specification { - - def 'Determine updates for create operation.'() { +class PayloadFactorySpec extends Specification { + def 'Create payload for create operation.'() { given: 'a new cm handle' - def ncmpServiceCmHandle = new NcmpServiceCmHandle( + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'ch', compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY), publicProperties: ['prop1': 'value1'] ) - when: 'updates are determined for create' - def result = CmHandlePropertyChangeDetector.determineUpdatesForCreate(ncmpServiceCmHandle) + when: 'payload is created for create' + def result = PayloadFactory.createPayloadV1(LcmEventType.CREATE, null, ncmpServiceCmHandle) then: 'new values are populated' + assert result.cmHandleId == 'ch' assert result.newValues.dataSyncEnabled == true assert result.newValues.cmHandleState == Values.CmHandleState.READY assert result.newValues.cmHandleProperties == [['prop1': 'value1']] @@ -44,24 +44,25 @@ class CmHandlePropertyChangeDetectorSpec extends Specification { assert result.oldValues == null } - def 'Determine updates when no changes detected.'() { + def 'Create payload when no changes detected.'() { given: 'current and target cm handles with same properties' def currentCmHandle = new NcmpServiceCmHandle( compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY), publicProperties: ['prop1': 'value1'] ) - def targetCmHandle = new NcmpServiceCmHandle( + def targetCmHandle = new NcmpServiceCmHandle(cmHandleId: 'ch', compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY), publicProperties: ['prop1': 'value1'] ) - when: 'updates are determined' - def result = CmHandlePropertyChangeDetector.determineUpdates(currentCmHandle, targetCmHandle) + when: 'payload is created' + def result = PayloadFactory.createPayloadV1(LcmEventType.UPDATE, currentCmHandle, targetCmHandle) then: 'no updates are detected' + assert result.cmHandleId == 'ch' assert result.oldValues == null assert result.newValues == null } - def 'Determine updates when data sync flag changes.'() { + def 'Create payload when data sync flag changes.'() { given: 'current and target cm handles with different data sync flags' def currentCmHandle = new NcmpServiceCmHandle( compositeState: new CompositeState(dataSyncEnabled: false, cmHandleState: CmHandleState.READY), @@ -71,14 +72,14 @@ class CmHandlePropertyChangeDetectorSpec extends Specification { compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY), publicProperties: [:] ) - when: 'updates are determined' - def result = CmHandlePropertyChangeDetector.determineUpdates(currentCmHandle, targetCmHandle) + when: 'payload is created' + def result = PayloadFactory.createPayloadV1(LcmEventType.UPDATE,currentCmHandle, targetCmHandle) then: 'data sync flag change is detected' assert result.oldValues.dataSyncEnabled == false assert result.newValues.dataSyncEnabled == true } - def 'Determine updates when cm handle state changes.'() { + def 'Create payload when cm handle state changes.'() { given: 'current and target cm handles with different states' def currentCmHandle = new NcmpServiceCmHandle( compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.ADVISED), @@ -88,36 +89,36 @@ class CmHandlePropertyChangeDetectorSpec extends Specification { compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY), publicProperties: [:] ) - when: 'updates are determined' - def result = CmHandlePropertyChangeDetector.determineUpdates(currentCmHandle, targetCmHandle) + when: 'payload is created' + def result = PayloadFactory.createPayloadV1(LcmEventType.UPDATE,currentCmHandle, targetCmHandle) then: 'state change is detected' assert result.oldValues.cmHandleState == Values.CmHandleState.ADVISED assert result.newValues.cmHandleState == Values.CmHandleState.READY } - def 'Determine updates when public properties change.'() { + def 'Create payload when public properties change.'() { given: 'current and target cm handles with different properties' def currentCmHandle = new NcmpServiceCmHandle( compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY), - publicProperties: ['prop1': 'value1', 'prop2': 'value2', 'unchanged': 'sameValue'] + publicProperties: ['prop1': 'old value', 'prop2': 'to be deleted', 'prop4': 'unchanged'] ) def targetCmHandle = new NcmpServiceCmHandle( compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY), - publicProperties: ['prop1': 'newValue1', 'prop3': 'value3', 'unchanged': 'sameValue'] + publicProperties: ['prop1': 'new value', 'prop3': 'new', 'prop4': 'unchanged'] ) - when: 'updates are determined' - def result = CmHandlePropertyChangeDetector.determineUpdates(currentCmHandle, targetCmHandle) + when: 'payload is created' + def result = PayloadFactory.createPayloadV1(LcmEventType.UPDATE,currentCmHandle, targetCmHandle) then: 'property changes are detected' - assert result.oldValues.cmHandleProperties[0]['prop1'] == 'value1' - assert result.oldValues.cmHandleProperties[0]['prop2'] == 'value2' - assert result.newValues.cmHandleProperties[0]['prop1'] == 'newValue1' - assert result.newValues.cmHandleProperties[0]['prop3'] == 'value3' + assert result.oldValues.cmHandleProperties[0]['prop1'] == 'old value' + assert result.oldValues.cmHandleProperties[0]['prop2'] == 'to be deleted' + assert result.newValues.cmHandleProperties[0]['prop1'] == 'new value' + assert result.newValues.cmHandleProperties[0]['prop3'] == 'new' and: 'unchanged property is not included in the result' - assert !result.oldValues.cmHandleProperties[0].containsKey('unchanged') - assert !result.newValues.cmHandleProperties[0].containsKey('unchanged') + assert !result.oldValues.cmHandleProperties[0].containsKey('prop4') + assert !result.newValues.cmHandleProperties[0].containsKey('prop4') } - def 'Determine updates when multiple changes occur.'() { + def 'Create payload when multiple changes occur.'() { given: 'current and target cm handles with multiple differences' def currentCmHandle = new NcmpServiceCmHandle( compositeState: new CompositeState(dataSyncEnabled: false, cmHandleState: CmHandleState.ADVISED), @@ -127,8 +128,8 @@ class CmHandlePropertyChangeDetectorSpec extends Specification { compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY), publicProperties: ['prop1': 'newValue1'] ) - when: 'updates are determined' - def result = CmHandlePropertyChangeDetector.determineUpdates(currentCmHandle, targetCmHandle) + when: 'payload is created' + def result = PayloadFactory.createPayloadV1(LcmEventType.UPDATE,currentCmHandle, targetCmHandle) then: 'all changes are detected' assert result.oldValues.dataSyncEnabled == false assert result.newValues.dataSyncEnabled == true @@ -137,4 +138,23 @@ class CmHandlePropertyChangeDetectorSpec extends Specification { assert result.oldValues.cmHandleProperties[0]['prop1'] == 'value1' assert result.newValues.cmHandleProperties[0]['prop1'] == 'newValue1' } + + def 'Create payload for delete operation.'() { + given: 'a cm handle being deleted' + def currentCmHandle = new NcmpServiceCmHandle( + compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY), + publicProperties: ['prop1': 'value1'] + ) + def targetCmHandle = new NcmpServiceCmHandle(cmHandleId: 'ch', + compositeState: new CompositeState(dataSyncEnabled: false, cmHandleState: CmHandleState.DELETED), + publicProperties: ['prop1': 'value1'] + ) + when: 'payload is created for delete' + def result = PayloadFactory.createPayloadV1(LcmEventType.DELETE, currentCmHandle, targetCmHandle) + then: 'cmHandleId is populated' + assert result.cmHandleId == 'ch' + and: 'no values are populated' + assert result.oldValues == null + assert result.newValues == null + } } -- 2.16.6