Recursive updates of the properties 97/137597/1
authorFrancescoFioraEst <francesco.fiora@est.tech>
Tue, 16 Jan 2024 09:39:06 +0000 (09:39 +0000)
committerFrancescoFioraEst <francesco.fiora@est.tech>
Tue, 26 Mar 2024 17:00:53 +0000 (17:00 +0000)
Merge properties during update and migrate.

Issue-ID: POLICY-4951
Change-Id: I0c9a896a5abb8331937a73d7e39fbce2d87b415f
Signed-off-by: FrancescoFioraEst <francesco.fiora@est.tech>
models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java
models/src/test/java/org/onap/policy/clamp/models/acm/utils/AcmUtilsTest.java
models/src/test/java/org/onap/policy/clamp/models/acm/utils/CommonTestData.java
participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandler.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java

index 504d3ef..1155bd4 100644 (file)
 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<String, Object> map1, Map<String, Object> map2) {
+        Deque<Pair<Map<String, Object>, Map<String, Object>>> 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<Object> list1, List<Object> 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);
+            }
+        }
+    }
 }
index bc8741e..1561533 100644 (file)
@@ -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<String, Object> map = CommonTestData.getObject(oldProperties, Map.class);
+        Map<String, Object> mapMigrate = CommonTestData.getObject(newProperties, Map.class);
+
+        AcmUtils.recursiveMerge(map, mapMigrate);
+        assertEquals("default", ((Map<String, Object>) map.get("chart")).get("namespace"));
+        assertEquals("acm-starter-new", ((Map<String, Object>) map.get("chart")).get("releaseName"));
+        assertNotNull(((Map<String, Object>) map.get("chart")).get("chartId"));
+        assertNull(((Map<String, Object>) 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<String, Object> map = CommonTestData.getObject(oldProperties, Map.class);
+        Map<String, Object> mapMigrate = CommonTestData.getObject(newProperties, Map.class);
+
+        AcmUtils.recursiveMerge(map, mapMigrate);
+        assertEquals("http://{{address}}:30800", map.get("baseUrl"));
+        assertEquals("application/json", ((Map<String, Object>) map.get("httpHeaders")).get("Content-Type"));
+        var configurationEntities = (List<Object>) map.get("configurationEntities");
+        var subMap = (Map<String, Object>) configurationEntities.get(0);
+        assertEquals("onap.policy.clamp.ac.starter",
+                ((Map<String, Object>) subMap.get("configurationEntityId")).get("name"));
+        assertThat((List<Object>) subMap.get("restSequence")).isNotEmpty();
+        assertEquals("90", subMap.get("myParameterToUpdate"));
+        assertNull(subMap.get("myParameterToRemove"));
+        assertEquals("I am new", subMap.get("myParameter"));
+    }
 }
index 03a3fb1..131c8ee 100644 (file)
@@ -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> T getObject(String yaml, Class<T> clazz) {
+        try {
+            return YAML_TRANSLATOR.decode(yaml, clazz);
+        } catch (CoderException e) {
+            fail("Cannot decode " + yaml);
+            return null;
+        }
+    }
+
     /**
      * Get new AutomationCompositionElementDefinition.
      *
index fefa637..3f3d575 100644 (file)
@@ -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());
         }
index 84920dd..4c22db9 100644 (file)
@@ -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");