Merge properties during update and migrate.
Issue-ID: POLICY-4951
Change-Id: I0c9a896a5abb8331937a73d7e39fbce2d87b415f
Signed-off-by: FrancescoFioraEst <francesco.fiora@est.tech>
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;
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;
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);
+ }
+ }
+ }
}
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"));
+ }
}
/*-
* ============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.
}
}
+ /**
+ * 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.
*
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;
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());
}
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");