From 1cf51a2280fb2f9a0435b8a3f2c64492cb7e6659 Mon Sep 17 00:00:00 2001 From: FrancescoFioraEst Date: Tue, 16 Jan 2024 09:39:06 +0000 Subject: Recursive updates of the properties Merge properties during update and migrate. Issue-ID: POLICY-4951 Change-Id: I0c9a896a5abb8331937a73d7e39fbce2d87b415f Signed-off-by: FrancescoFioraEst --- .../policy/clamp/models/acm/utils/AcmUtils.java | 47 +++++++++++++++ .../clamp/models/acm/utils/AcmUtilsTest.java | 70 ++++++++++++++++++++++ .../clamp/models/acm/utils/CommonTestData.java | 18 +++++- .../handler/AutomationCompositionHandler.java | 3 +- ...AutomationCompositionInstantiationProvider.java | 2 +- 5 files changed, 137 insertions(+), 3 deletions(-) diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java b/models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java index 504d3ef06..1155bd4f4 100644 --- a/models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java +++ b/models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java @@ -21,12 +21,15 @@ package org.onap.policy.clamp.models.acm.utils; import jakarta.ws.rs.core.Response; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; +import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Queue; import java.util.UUID; import java.util.function.Function; import java.util.function.UnaryOperator; @@ -35,6 +38,7 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.tuple.Pair; import org.onap.policy.clamp.models.acm.concepts.AcElementDeploy; import org.onap.policy.clamp.models.acm.concepts.AcElementRestart; import org.onap.policy.clamp.models.acm.concepts.AcTypeState; @@ -443,4 +447,47 @@ public final class AcmUtils { acElementRestart.setOutProperties(PfUtils.mapMap(element.getOutProperties(), UnaryOperator.identity())); return acElementRestart; } + + /** + * Recursive Merge. + * + * @param map1 Map where to merge + * @param map2 Map + */ + public static void recursiveMerge(Map map1, Map map2) { + Deque, Map>> stack = new ArrayDeque<>(); + stack.push(Pair.of(map1, map2)); + while (!stack.isEmpty()) { + var pair = stack.pop(); + var mapLeft = pair.getLeft(); + var mapRight = pair.getRight(); + for (var entryRight : mapRight.entrySet()) { + var valueLeft = mapLeft.get(entryRight.getKey()); + var valueRight = entryRight.getValue(); + if (valueLeft instanceof Map subMapLeft && valueRight instanceof Map subMapRight) { + stack.push(Pair.of(subMapLeft, subMapRight)); + } else if ((valueLeft instanceof List subListLeft && valueRight instanceof List subListRight) + && (subListLeft.size() == subListRight.size())) { + recursiveMerge(subListLeft, subListRight); + } else { + mapLeft.put(entryRight.getKey(), valueRight); + } + } + } + } + + private static void recursiveMerge(List list1, List list2) { + for (var i = 0; i < list1.size(); i++) { + var valueLeft = list1.get(i); + var valueRight = list2.get(i); + if (valueLeft instanceof Map subMapLeft && valueRight instanceof Map subMapRight) { + recursiveMerge(subMapLeft, subMapRight); + } else if ((valueLeft instanceof List subListLeft && valueRight instanceof List subListRight) + && (subListLeft.size() == subListRight.size())) { + recursiveMerge(subListLeft, subListRight); + } else { + list1.set(i, valueRight); + } + } + } } diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/utils/AcmUtilsTest.java b/models/src/test/java/org/onap/policy/clamp/models/acm/utils/AcmUtilsTest.java index bc8741e65..1561533e8 100644 --- a/models/src/test/java/org/onap/policy/clamp/models/acm/utils/AcmUtilsTest.java +++ b/models/src/test/java/org/onap/policy/clamp/models/acm/utils/AcmUtilsTest.java @@ -293,4 +293,74 @@ class AcmUtilsTest { nodeTemplates.put("org.onap.dcae.acm.DCAEMicroserviceAutomationCompositionParticipant", nodeTemplate); return nodeTemplates; } + + @Test + void testRecursiveMergeMap() { + var oldProperties = """ + chart: + chartId: + name: acelement + version: 0.1.0 + namespace: default + releaseName: acm-starter + podName: acm-starter + """; + + var newProperties = """ + chart: + releaseName: acm-starter-new + podName: null + """; + + Map map = CommonTestData.getObject(oldProperties, Map.class); + Map mapMigrate = CommonTestData.getObject(newProperties, Map.class); + + AcmUtils.recursiveMerge(map, mapMigrate); + assertEquals("default", ((Map) map.get("chart")).get("namespace")); + assertEquals("acm-starter-new", ((Map) map.get("chart")).get("releaseName")); + assertNotNull(((Map) map.get("chart")).get("chartId")); + assertNull(((Map) map.get("chart")).get("podName")); + } + + @Test + void testRecursiveMergeList() { + var oldProperties = """ + baseUrl: http://{{address}}:30800 + httpHeaders: + Content-Type: application/json + Authorization: Basic YWNtVXNlcjp6YiFYenRHMzQ= + configurationEntities: + - configurationEntityId: + name: onap.policy.clamp.ac.starter + version: 1.0.0 + restSequence: + - restRequestId: + name: request1 + version: 1.0.1 + myParameterToUpdate: 9 + myParameterToRemove: 8 + """; + + var newProperties = """ + configurationEntities: + - myParameterToUpdate: "90" + myParameterToRemove: null + myParameter: "I am new" + """; + + Map map = CommonTestData.getObject(oldProperties, Map.class); + Map mapMigrate = CommonTestData.getObject(newProperties, Map.class); + + AcmUtils.recursiveMerge(map, mapMigrate); + assertEquals("http://{{address}}:30800", map.get("baseUrl")); + assertEquals("application/json", ((Map) map.get("httpHeaders")).get("Content-Type")); + var configurationEntities = (List) map.get("configurationEntities"); + var subMap = (Map) configurationEntities.get(0); + assertEquals("onap.policy.clamp.ac.starter", + ((Map) subMap.get("configurationEntityId")).get("name")); + assertThat((List) subMap.get("restSequence")).isNotEmpty(); + assertEquals("90", subMap.get("myParameterToUpdate")); + assertNull(subMap.get("myParameterToRemove")); + assertEquals("I am new", subMap.get("myParameter")); + } } diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/utils/CommonTestData.java b/models/src/test/java/org/onap/policy/clamp/models/acm/utils/CommonTestData.java index 03a3fb11a..131c8eefd 100644 --- a/models/src/test/java/org/onap/policy/clamp/models/acm/utils/CommonTestData.java +++ b/models/src/test/java/org/onap/policy/clamp/models/acm/utils/CommonTestData.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation. + * Copyright (C) 2023-2024 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,6 +82,22 @@ public class CommonTestData { } } + /** + * Get Object from string in yaml format. + * + * @param yaml the string in yaml format + * @param clazz the Class of the Object + * @return the Object + */ + public static T getObject(String yaml, Class clazz) { + try { + return YAML_TRANSLATOR.decode(yaml, clazz); + } catch (CoderException e) { + fail("Cannot decode " + yaml); + return null; + } + } + /** * Get new AutomationCompositionElementDefinition. * diff --git a/participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandler.java b/participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandler.java index fefa637da..3f3d5756a 100644 --- a/participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandler.java +++ b/participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandler.java @@ -53,6 +53,7 @@ import org.onap.policy.clamp.models.acm.messages.kafka.participant.PropertiesUpd import org.onap.policy.clamp.models.acm.messages.rest.instantiation.DeployOrder; import org.onap.policy.clamp.models.acm.messages.rest.instantiation.LockOrder; import org.onap.policy.clamp.models.acm.persistence.provider.AcInstanceStateResolver; +import org.onap.policy.clamp.models.acm.utils.AcmUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -307,7 +308,7 @@ public class AutomationCompositionHandler { var acElementList = cacheProvider.getAutomationComposition(instanceId).getElements(); for (var element : participantDeploy.getAcElementList()) { var acElement = acElementList.get(element.getId()); - acElement.getProperties().putAll(element.getProperties()); + AcmUtils.recursiveMerge(acElement.getProperties(), element.getProperties()); acElement.setDeployState(deployState); acElement.setDefinition(element.getDefinition()); } diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java index 84920dd03..4c22db956 100644 --- a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java +++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java @@ -163,7 +163,7 @@ public class AutomationCompositionInstantiationProvider { if (dbAcElement == null) { throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, "Element id not present " + elementId); } - dbAcElement.getProperties().putAll(element.getValue().getProperties()); + AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties()); } if (automationComposition.getRestarting() != null) { throw new PfModelRuntimeException(Status.BAD_REQUEST, "There is a restarting process, Update not allowed"); -- cgit 1.2.3-korg