Support add/remove elements in Migration 56/138556/6
authorrameshiyer27 <ramesh.murugan.iyer@est.tech>
Tue, 23 Jul 2024 17:08:35 +0000 (18:08 +0100)
committerrameshiyer27 <ramesh.murugan.iyer@est.tech>
Fri, 26 Jul 2024 11:43:25 +0000 (12:43 +0100)
Issue-ID: POLICY-4917
Change-Id: I0014b4858dd7e6ac76bfa1184d0b90b52e8649f5
Signed-off-by: zrrmmua <ramesh.murugan.iyer@est.tech>
models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProvider.java
models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProviderTest.java
participant/participant-impl/participant-impl-simulator/src/main/java/org/onap/policy/clamp/acm/participant/sim/main/handler/AutomationCompositionElementHandlerV2.java
participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/api/CompositionElementDto.java
participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/api/InstanceElementDto.java
participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandler.java
participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandlerTest.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProviderTest.java
runtime-acm/src/test/resources/rest/acm/AutomationCompositionMigrate.json [new file with mode: 0644]

index 9b73648..e26a774 100644 (file)
@@ -268,6 +268,15 @@ public class AutomationCompositionProvider {
         acElementRepository.save(jpaAcElement);
     }
 
+    /**
+     * Delete AutomationCompositionElement.
+     *
+     * @param elementId the AutomationCompositionElement Id
+     */
+    public void deleteAutomationCompositionElement(@NonNull final UUID elementId) {
+        acElementRepository.deleteById(elementId.toString());
+    }
+
     /**
      * Validate ElementIds.
      *
index 515dfaa..c2368fe 100644 (file)
@@ -56,6 +56,7 @@ class AutomationCompositionProviderTest {
 
     private static final String AC_IS_NULL = "automationComposition is marked non-null but is null";
     private static final String ACELEMENT_IS_NULL = "element is marked non-null but is null";
+    private static final String ACELEMENT_ID_IS_NULL = "elementId is marked non-null but is null";
 
     private static final Coder CODER = new StandardCoder();
     private static final String AUTOMATION_COMPOSITION_JSON =
@@ -240,6 +241,18 @@ class AutomationCompositionProviderTest {
         verify(acElementRepository).save(any());
     }
 
+    @Test
+    void testDeleteElementById() {
+        var acElementRepository = mock(AutomationCompositionElementRepository.class);
+        var automationCompositionProvider = new AutomationCompositionProvider(
+                mock(AutomationCompositionRepository.class), acElementRepository);
+        assertThatThrownBy(() -> automationCompositionProvider.deleteAutomationCompositionElement(null))
+                .hasMessageMatching(ACELEMENT_ID_IS_NULL);
+        var elementId = UUID.randomUUID();
+        automationCompositionProvider.deleteAutomationCompositionElement(elementId);
+        verify(acElementRepository).deleteById(elementId.toString());
+    }
+
     @Test
     void testValidateElementIds() {
         var acElementRepository = mock(AutomationCompositionElementRepository.class);
index 5f866eb..7eff849 100644 (file)
@@ -117,7 +117,16 @@ public class AutomationCompositionElementHandlerV2 extends AcElementListenerV2 {
         LOGGER.debug("migrate call compositionElement: {}, compositionElementTarget: {}, instanceElement: {},"
                         + " instanceElementMigrate: {}",
                 compositionElement, compositionElementTarget, instanceElement, instanceElementMigrate);
-        simulatorService.migrate(instanceElement.instanceId(), instanceElement.elementId());
+        if (instanceElement.newElement()) {
+            simulatorService.migrate(instanceElementMigrate.instanceId(), instanceElementMigrate.elementId());
+
+        } else if (instanceElementMigrate.removedElement()) {
+            simulatorService.undeploy(instanceElement.instanceId(), instanceElement.elementId());
+            simulatorService.delete(instanceElement.instanceId(), instanceElement.elementId());
+        } else {
+            simulatorService.migrate(instanceElement.instanceId(), instanceElement.elementId());
+        }
+
     }
 
     @Override
index d203f90..51800ce 100644 (file)
@@ -25,5 +25,12 @@ import java.util.UUID;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
 
 public record CompositionElementDto(UUID compositionId, ToscaConceptIdentifier elementDefinitionId,
-                                    Map<String, Object> inProperties, Map<String, Object> outProperties) {
+                                    Map<String, Object> inProperties, Map<String, Object> outProperties,
+                                    boolean newElement, boolean removedElement) {
+
+    public CompositionElementDto(UUID compositionId, ToscaConceptIdentifier elementDefinitionId,
+                                 Map<String, Object> inProperties, Map<String, Object> outProperties) {
+        this(compositionId, elementDefinitionId, inProperties, outProperties, false, false);
+
+    }
 }
index 297af6c..65a83e2 100644 (file)
@@ -25,5 +25,11 @@ import java.util.UUID;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
 
 public record InstanceElementDto(UUID instanceId, UUID elementId, ToscaServiceTemplate toscaServiceTemplateFragment,
-                                 Map<String, Object> inProperties, Map<String, Object> outProperties) {
+                                 Map<String, Object> inProperties, Map<String, Object> outProperties,
+                                 boolean newElement, boolean removedElement) {
+
+    public InstanceElementDto(UUID instanceId, UUID elementId, ToscaServiceTemplate toscaServiceTemplateFragment,
+                              Map<String, Object> inProperties, Map<String, Object> outProperties) {
+        this(instanceId, elementId, toscaServiceTemplateFragment, inProperties, outProperties, false, false);
+    }
 }
index 0f230c0..6c560e7 100644 (file)
 
 package org.onap.policy.clamp.acm.participant.intermediary.handler;
 
+
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import lombok.RequiredArgsConstructor;
+import org.onap.policy.clamp.acm.participant.intermediary.api.CompositionElementDto;
 import org.onap.policy.clamp.acm.participant.intermediary.api.InstanceElementDto;
 import org.onap.policy.clamp.acm.participant.intermediary.comm.ParticipantMessagePublisher;
 import org.onap.policy.clamp.models.acm.concepts.AcElementDeploy;
 import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
+import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement;
 import org.onap.policy.clamp.models.acm.concepts.DeployState;
+import org.onap.policy.clamp.models.acm.concepts.LockState;
 import org.onap.policy.clamp.models.acm.concepts.ParticipantDeploy;
 import org.onap.policy.clamp.models.acm.concepts.ParticipantUtils;
 import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
@@ -41,6 +47,7 @@ import org.onap.policy.clamp.models.acm.messages.kafka.participant.ParticipantMe
 import org.onap.policy.clamp.models.acm.messages.kafka.participant.PropertiesUpdate;
 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.DeployOrder;
 import org.onap.policy.clamp.models.acm.utils.AcmUtils;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
@@ -186,11 +193,51 @@ public class AutomationCompositionHandler {
         var acElementList = cacheProvider.getAutomationComposition(instanceId).getElements();
         for (var element : participantDeploy.getAcElementList()) {
             var acElement = acElementList.get(element.getId());
-            AcmUtils.recursiveMerge(acElement.getProperties(), element.getProperties());
-            acElement.setDeployState(deployState);
-            acElement.setSubState(SubState.NONE);
-            acElement.setDefinition(element.getDefinition());
+            if (acElement == null && deployState.equals(DeployState.MIGRATING)) {
+                var newElement = new AutomationCompositionElement();
+                newElement.setId(element.getId());
+                newElement.setParticipantId(participantDeploy.getParticipantId());
+                newElement.setDefinition(element.getDefinition());
+                newElement.setDeployState(deployState);
+                newElement.setSubState(SubState.NONE);
+                newElement.setLockState(LockState.LOCKED);
+                newElement.setProperties(element.getProperties());
+
+                acElementList.put(element.getId(), newElement);
+                LOGGER.info("New Ac Element with id {} is added in Migration", element.getId());
+            } else if (acElement != null) {
+                AcmUtils.recursiveMerge(acElement.getProperties(), element.getProperties());
+                acElement.setDeployState(deployState);
+                acElement.setSubState(SubState.NONE);
+                acElement.setDefinition(element.getDefinition());
+            }
+        }
+        if (deployState.equals(DeployState.MIGRATING)) {
+            // Check for missing elements and remove them from cache
+            List<UUID> elementsToRemove = findElementsToRemove(participantDeploy.getAcElementList(), acElementList);
+            for (UUID key : elementsToRemove) {
+                acElementList.remove(key);
+                LOGGER.info("Element with id {} is removed in Migration", key);
+            }
+        }
+    }
+
+    private List<UUID> findElementsToRemove(List<AcElementDeploy> acElementDeployList, Map<UUID,
+            AutomationCompositionElement> acElementList) {
+        List<UUID> elementsToRemove = new ArrayList<>();
+        for (var elementInCache : acElementList.entrySet()) {
+            boolean found = false;
+            for (var element : acElementDeployList) {
+                if (element.getId().equals(elementInCache.getValue().getId())) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                elementsToRemove.add(elementInCache.getKey());
+            }
         }
+        return elementsToRemove;
     }
 
     /**
@@ -282,10 +329,34 @@ public class AutomationCompositionHandler {
             compositionTargetId);
         var instanceElementMigrateMap = cacheProvider.getInstanceElementDtoMap(automationComposition);
 
+        // Call migrate for newly added and updated elements
         for (var acElement : acElements) {
-            listener.migrate(messageId, compositionElementMap.get(acElement.getId()),
-                compositionElementTargetMap.get(acElement.getId()),
-                instanceElementMap.get(acElement.getId()), instanceElementMigrateMap.get(acElement.getId()));
+            if (instanceElementMap.get(acElement.getId()) == null) {
+                var compositionDto = new CompositionElementDto(acElement.getId(), acElement.getDefinition(),
+                        Map.of(), Map.of(), true, false);
+                var instanceDto = new InstanceElementDto(acCopy.getInstanceId(), acElement.getId(),
+                        new ToscaServiceTemplate(), Map.of(), Map.of(), true, false);
+
+                listener.migrate(messageId, compositionDto,
+                        compositionElementTargetMap.get(acElement.getId()),
+                        instanceDto, instanceElementMigrateMap.get(acElement.getId()));
+            } else {
+                listener.migrate(messageId, compositionElementMap.get(acElement.getId()),
+                        compositionElementTargetMap.get(acElement.getId()),
+                        instanceElementMap.get(acElement.getId()), instanceElementMigrateMap.get(acElement.getId()));
+            }
+        }
+        // Call migrate for removed elements
+        List<UUID> removedElements = findElementsToRemove(acElements, acCopy.getElements());
+        for (var elementId : removedElements) {
+            var compositionDtoTarget = new CompositionElementDto(elementId, acCopy.getElements().get(elementId)
+                    .getDefinition(), Map.of(), Map.of(), false, true);
+            var instanceDtoTarget = new InstanceElementDto(acCopy.getInstanceId(), elementId,
+                    new ToscaServiceTemplate(), Map.of(), Map.of(), false, true);
+
+            listener.migrate(messageId, compositionElementMap.get(elementId),
+                    compositionDtoTarget,
+                    instanceElementMap.get(elementId), instanceDtoTarget);
         }
     }
 }
index 40e3b1e..5973508 100644 (file)
@@ -35,6 +35,7 @@ import org.junit.jupiter.api.Test;
 import org.onap.policy.clamp.acm.participant.intermediary.comm.ParticipantMessagePublisher;
 import org.onap.policy.clamp.acm.participant.intermediary.main.parameters.CommonTestData;
 import org.onap.policy.clamp.models.acm.concepts.AcElementDeploy;
+import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElementDefinition;
 import org.onap.policy.clamp.models.acm.concepts.DeployState;
 import org.onap.policy.clamp.models.acm.concepts.ParticipantDeploy;
@@ -203,16 +204,34 @@ class AutomationCompositionHandlerTest {
         var migrationMsg = new AutomationCompositionMigration();
         assertDoesNotThrow(() -> ach.handleAutomationCompositionMigration(migrationMsg));
         var automationComposition = CommonTestData.getTestAutomationCompositionMap().values().iterator().next();
-        migrationMsg.setCompositionTargetId(UUID.randomUUID());
-        migrationMsg.setAutomationCompositionId(automationComposition.getInstanceId());
+
         assertDoesNotThrow(() -> ach.handleAutomationCompositionMigration(migrationMsg));
         when(cacheProvider.getAutomationComposition(automationComposition.getInstanceId()))
                 .thenReturn(automationComposition);
+        when(cacheProvider.getParticipantId()).thenReturn(CommonTestData.getParticipantId());
+
+        Map<ToscaConceptIdentifier, AutomationCompositionElementDefinition> map = new HashMap<>();
         var participantDeploy = new ParticipantDeploy();
+        populateMigrationMsg(automationComposition, migrationMsg, map, participantDeploy);
+        when(cacheProvider.getAcElementsDefinitions())
+                .thenReturn(Map.of(automationComposition.getCompositionId(), map,
+                        migrationMsg.getCompositionTargetId(), map));
+
+        ach.handleAutomationCompositionMigration(migrationMsg);
+        verify(listener, times(automationComposition.getElements().size() + 1))
+                .migrate(any(), any(), any(), any(), any());
+    }
+
+    private void populateMigrationMsg(AutomationComposition automationComposition,
+                                      AutomationCompositionMigration migrationMsg,
+                                      Map<ToscaConceptIdentifier,
+                                              AutomationCompositionElementDefinition> map,
+                                      ParticipantDeploy participantDeploy) {
+
         participantDeploy.setParticipantId(CommonTestData.getParticipantId());
-        when(cacheProvider.getParticipantId()).thenReturn(CommonTestData.getParticipantId());
+        migrationMsg.setCompositionTargetId(UUID.randomUUID());
+        migrationMsg.setAutomationCompositionId(automationComposition.getInstanceId());
         migrationMsg.getParticipantUpdatesList().add(participantDeploy);
-        Map<ToscaConceptIdentifier, AutomationCompositionElementDefinition> map = new HashMap<>();
         for (var element : automationComposition.getElements().values()) {
             var acElementDeploy = new AcElementDeploy();
             acElementDeploy.setProperties(Map.of());
@@ -221,11 +240,14 @@ class AutomationCompositionHandlerTest {
             participantDeploy.getAcElementList().add(acElementDeploy);
             map.put(element.getDefinition(), new AutomationCompositionElementDefinition());
         }
-        when(cacheProvider.getAcElementsDefinitions())
-            .thenReturn(Map.of(automationComposition.getCompositionId(), map,
-                migrationMsg.getCompositionTargetId(), map));
+        // remove an element
+        participantDeploy.getAcElementList().remove(0);
+        // Add a new element
+        var acElementDeploy = new AcElementDeploy();
+        acElementDeploy.setProperties(Map.of());
+        acElementDeploy.setId(UUID.randomUUID());
+        acElementDeploy.setDefinition(new ToscaConceptIdentifier("1.2.3", "policy.clamp.new.element"));
+        participantDeploy.getAcElementList().add(acElementDeploy);
 
-        ach.handleAutomationCompositionMigration(migrationMsg);
-        verify(listener, times(automationComposition.getElements().size())).migrate(any(), any(), any(), any(), any());
     }
 }
index c732654..653d973 100644 (file)
@@ -23,6 +23,8 @@ package org.onap.policy.clamp.acm.runtime.instantiation;
 
 import jakarta.validation.Valid;
 import jakarta.ws.rs.core.Response.Status;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.UUID;
 import java.util.stream.Collectors;
 import lombok.NonNull;
@@ -50,6 +52,7 @@ import org.onap.policy.clamp.models.acm.utils.AcmUtils;
 import org.onap.policy.common.parameters.BeanValidationResult;
 import org.onap.policy.common.parameters.ObjectValidationResult;
 import org.onap.policy.common.parameters.ValidationStatus;
+import org.onap.policy.models.base.PfConceptKey;
 import org.onap.policy.models.base.PfKey;
 import org.onap.policy.models.base.PfModelRuntimeException;
 import org.slf4j.Logger;
@@ -215,23 +218,29 @@ public class AutomationCompositionInstantiationProvider {
         for (var element : automationComposition.getElements().entrySet()) {
             var elementId = element.getKey();
             var dbAcElement = acToBeUpdated.getElements().get(elementId);
+            // Add additional elements if present for migration
             if (dbAcElement == null) {
-                throw new PfModelRuntimeException(Status.BAD_REQUEST, "Element id not present " + elementId);
-            }
-            AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
-            var newDefinition = element.getValue().getDefinition();
-            var compatibility =
-                newDefinition.asConceptKey().getCompatibility(dbAcElement.getDefinition().asConceptKey());
-            if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) {
-                throw new PfModelRuntimeException(Status.BAD_REQUEST,
-                    dbAcElement.getDefinition() + " is not compatible with " + newDefinition);
+                LOGGER.info("New Ac element {} added in Migration", elementId);
+                acToBeUpdated.getElements().put(elementId, automationComposition.getElements().get(elementId));
+            } else {
+                AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
+                var newDefinition = element.getValue().getDefinition().asConceptKey();
+                var dbElementDefinition = dbAcElement.getDefinition().asConceptKey();
+                checkCompatibility(newDefinition, dbElementDefinition, automationComposition.getInstanceId());
+                dbAcElement.setDefinition(element.getValue().getDefinition());
             }
-            if (PfKey.Compatibility.MAJOR.equals(compatibility) || PfKey.Compatibility.MINOR.equals(compatibility)) {
-                LOGGER.warn("Migrate {}: Version {} has {} compatibility with {} ",
-                    automationComposition.getInstanceId(), newDefinition, compatibility, dbAcElement.getDefinition());
+        }
+        // Remove element which is not present in the new Ac instance
+        List<UUID> elementsRemoved = new ArrayList<>();
+        for (var dbElement : acToBeUpdated.getElements().entrySet()) {
+            var dbElementId = dbElement.getKey();
+            if (automationComposition.getElements().get(dbElementId) == null) {
+                LOGGER.info("Element with id {} is removed in Migration", dbElementId);
+                elementsRemoved.add(dbElementId);
+                automationCompositionProvider.deleteAutomationCompositionElement(dbElementId);
             }
-            dbAcElement.setDefinition(element.getValue().getDefinition());
         }
+        elementsRemoved.forEach(uuid -> acToBeUpdated.getElements().remove(uuid));
 
         var validationResult =
             validateAutomationComposition(acToBeUpdated, automationComposition.getCompositionTargetId());
@@ -244,9 +253,24 @@ public class AutomationCompositionInstantiationProvider {
         supervisionAcHandler.migrate(acToBeUpdated);
 
         automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
+        elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
         return createInstantiationResponse(automationComposition);
     }
 
+    void checkCompatibility(PfConceptKey newDefinition, PfConceptKey dbElementDefinition,
+                            UUID instanceId) {
+        var compatibility = newDefinition.getCompatibility(dbElementDefinition);
+        if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) {
+            throw new PfModelRuntimeException(Status.BAD_REQUEST,
+                    dbElementDefinition + " is not compatible with " + newDefinition);
+        }
+        if (PfKey.Compatibility.MAJOR.equals(compatibility) || PfKey.Compatibility.MINOR
+                .equals(compatibility)) {
+            LOGGER.warn("Migrate {}: Version {} has {} compatibility with {} ", instanceId, newDefinition,
+                    compatibility, dbElementDefinition);
+        }
+    }
+
     private InstantiationResponse migratePrecheckAc(
             AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
 
@@ -256,20 +280,30 @@ public class AutomationCompositionInstantiationProvider {
         for (var element : automationComposition.getElements().entrySet()) {
             var elementId = element.getKey();
             var copyElement = copyAc.getElements().get(elementId);
+            // Add additional elements if present for migration
             if (copyElement == null) {
-                throw new PfModelRuntimeException(Status.BAD_REQUEST, "Element id not present " + elementId);
+                LOGGER.info("New Ac element {} added in Migration", elementId);
+                copyAc.getElements().put(elementId, automationComposition.getElements().get(elementId));
+            } else {
+                AcmUtils.recursiveMerge(copyElement.getProperties(), element.getValue().getProperties());
+                var newDefinition = element.getValue().getDefinition().asConceptKey();
+                var copyElementDefinition = copyElement.getDefinition().asConceptKey();
+                checkCompatibility(newDefinition, copyElementDefinition, automationComposition.getInstanceId());
+                copyElement.setDefinition(element.getValue().getDefinition());
             }
-            AcmUtils.recursiveMerge(copyElement.getProperties(), element.getValue().getProperties());
-            var newDefinition = element.getValue().getDefinition();
-            var compatibility =
-                    newDefinition.asConceptKey().getCompatibility(copyElement.getDefinition().asConceptKey());
-            if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) {
-                throw new PfModelRuntimeException(Status.BAD_REQUEST,
-                        copyElement.getDefinition() + " is not compatible with " + newDefinition);
+        }
+        // Remove element which is not present in the new Ac instance
+        List<UUID> elementsRemoved = new ArrayList<>();
+        for (var dbElement : copyAc.getElements().entrySet()) {
+            var dbElementId = dbElement.getKey();
+            if (automationComposition.getElements().get(dbElementId) == null) {
+                LOGGER.info("Element with id {} is removed in Migration", dbElementId);
+                elementsRemoved.add(dbElementId);
             }
-            copyElement.setDefinition(element.getValue().getDefinition());
         }
 
+        elementsRemoved.forEach(uuid -> copyAc.getElements().remove(uuid));
+
         var validationResult =
                 validateAutomationComposition(copyAc, automationComposition.getCompositionTargetId());
         if (!validationResult.isValid()) {
index a7ae99f..2370972 100644 (file)
@@ -23,6 +23,7 @@ package org.onap.policy.clamp.acm.runtime.instantiation;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
@@ -32,6 +33,7 @@ import static org.onap.policy.clamp.acm.runtime.util.CommonTestData.TOSCA_SERVIC
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.UUID;
 import org.junit.jupiter.api.BeforeAll;
@@ -42,9 +44,11 @@ import org.onap.policy.clamp.acm.runtime.util.CommonTestData;
 import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
 import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition;
+import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement;
 import org.onap.policy.clamp.models.acm.concepts.DeployState;
 import org.onap.policy.clamp.models.acm.concepts.LockState;
 import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
+import org.onap.policy.clamp.models.acm.concepts.SubState;
 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AcInstanceStateUpdate;
 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.DeployOrder;
 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.LockOrder;
@@ -54,6 +58,9 @@ import org.onap.policy.clamp.models.acm.persistence.provider.AcInstanceStateReso
 import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
 import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantProvider;
 import org.onap.policy.clamp.models.acm.persistence.provider.ProviderUtils;
+import org.onap.policy.clamp.models.acm.utils.AcmUtils;
+import org.onap.policy.models.base.PfConceptKey;
+import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
 import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate;
 
@@ -65,6 +72,7 @@ class AutomationCompositionInstantiationProviderTest {
     private static final String AC_INSTANTIATION_CREATE_JSON = "src/test/resources/rest/acm/AutomationComposition.json";
     private static final String AC_INSTANTIATION_UPDATE_JSON =
             "src/test/resources/rest/acm/AutomationCompositionUpdate.json";
+    private static final String AC_MIGRATE_JSON = "src/test/resources/rest/acm/AutomationCompositionMigrate.json";
 
     private static final String AC_INSTANTIATION_DEFINITION_NAME_NOT_FOUND_JSON =
             "src/test/resources/rest/acm/AutomationCompositionElementsNotFound.json";
@@ -280,11 +288,110 @@ class AutomationCompositionInstantiationProviderTest {
         var instantiationProvider = new AutomationCompositionInstantiationProvider(acProvider, acDefinitionProvider,
                 new AcInstanceStateResolver(), supervisionAcHandler, participantProvider,
                 mock(AcRuntimeParameterGroup.class));
+        var message =
+                  """
+                  "AutomationComposition" INVALID, item has status INVALID
+                    item "ServiceTemplate.restarting" value "true" INVALID, There is a restarting process in composition
+                  """;
+
         assertThatThrownBy(
                 () -> instantiationProvider.updateAutomationComposition(compositionId, automationCompositionUpdate))
-                        .hasMessageMatching("\"AutomationComposition\" INVALID, item has status INVALID\n"
-                                + "  item \"ServiceTemplate.restarting\" value \"true\" INVALID,"
-                                + " There is a restarting process in composition\n");
+                        .hasMessageMatching(message);
+    }
+
+    @Test
+    void testMigrationAddRemoveElements() {
+        var acDefinitionProvider = mock(AcDefinitionProvider.class);
+        var acDefinition = CommonTestData.createAcDefinition(serviceTemplate, AcTypeState.PRIMED);
+        var compositionId = acDefinition.getCompositionId();
+        when(acDefinitionProvider.findAcDefinition(compositionId)).thenReturn(Optional.of(acDefinition));
+        var instanceId = UUID.randomUUID();
+
+        var automationComposition =
+                InstantiationUtils.getAutomationCompositionFromResource(AC_MIGRATE_JSON, "Crud");
+        automationComposition.setCompositionId(compositionId);
+        automationComposition.setInstanceId(instanceId);
+        automationComposition.setDeployState(DeployState.DEPLOYED);
+        automationComposition.setLockState(LockState.LOCKED);
+        var acProvider = mock(AutomationCompositionProvider.class);
+        when(acProvider.getAutomationComposition(automationComposition.getInstanceId()))
+                .thenReturn(automationComposition);
+
+        var automationCompositionTarget = new AutomationComposition(automationComposition);
+        automationCompositionTarget.setInstanceId(instanceId);
+        automationCompositionTarget.setCompositionId(compositionId);
+        // Add a new element
+        var uuid = UUID.randomUUID();
+        var newElement = new AutomationCompositionElement();
+        newElement.setId(uuid);
+        newElement.setDefinition(new ToscaConceptIdentifier(
+                "org.onap.domain.pmsh.PMSH_OperationalPolicyAutomationCompositionElement", "1.2.3"));
+        newElement.setProperties(Map.of("testVar", "1", "testVar2", "2"));
+        automationCompositionTarget.getElements().put(uuid, newElement);
+
+        //Remove an existing element
+        var elementIdToRemove = UUID.randomUUID();
+        for (var element : automationCompositionTarget.getElements().values()) {
+            if (element.getDefinition().getName()
+                    .equals("org.onap.domain.database.Http_PMSHMicroserviceAutomationCompositionElement")) {
+                elementIdToRemove = element.getId();
+            }
+        }
+        automationCompositionTarget.getElements().remove(elementIdToRemove);
+
+        var acDefinitionTarget = CommonTestData.createAcDefinition(serviceTemplate, AcTypeState.PRIMED);
+        var compositionTargetId = acDefinitionTarget.getCompositionId();
+        automationCompositionTarget.setCompositionTargetId(compositionTargetId);
+        when(acDefinitionProvider.findAcDefinition(compositionTargetId)).thenReturn(Optional.of(acDefinitionTarget));
+        when(acProvider.updateAutomationComposition(any())).thenReturn(automationCompositionTarget);
+
+        var supervisionAcHandler = mock(SupervisionAcHandler.class);
+        var participantProvider = mock(ParticipantProvider.class);
+        var instantiationProvider = new AutomationCompositionInstantiationProvider(acProvider, acDefinitionProvider,
+                new AcInstanceStateResolver(), supervisionAcHandler, participantProvider,
+                new AcRuntimeParameterGroup());
+
+        automationCompositionTarget.setPrecheck(true);
+        var preCheckResponse = instantiationProvider.updateAutomationComposition(compositionId,
+                automationCompositionTarget);
+        verify(supervisionAcHandler).migratePrecheck(any());
+        InstantiationUtils.assertInstantiationResponse(preCheckResponse, automationCompositionTarget);
+
+        automationCompositionTarget.setPrecheck(false);
+        AcmUtils.setCascadedState(automationComposition, DeployState.DEPLOYED, LockState.LOCKED,
+                SubState.NONE);
+        var instantiationResponse = instantiationProvider.updateAutomationComposition(compositionId,
+                        automationCompositionTarget);
+
+        verify(supervisionAcHandler).migrate(any());
+        InstantiationUtils.assertInstantiationResponse(instantiationResponse, automationCompositionTarget);
+
+    }
+
+    @Test
+    void testVersionCompatibility() {
+        var acProvider = mock(AutomationCompositionProvider.class);
+        var acDefinitionProvider = mock(AcDefinitionProvider.class);
+        var supervisionAcHandler = mock(SupervisionAcHandler.class);
+        var participantProvider = mock(ParticipantProvider.class);
+        var newDefinition = new PfConceptKey("policy.clamp.element", "1.2.3");
+        var oldDefinition = new PfConceptKey("policy.clamp.element", "2.2.3");
+
+        var instantiationProvider = new AutomationCompositionInstantiationProvider(acProvider, acDefinitionProvider,
+                new AcInstanceStateResolver(), supervisionAcHandler, participantProvider,
+                new AcRuntimeParameterGroup());
+        var instanceId = UUID.randomUUID();
+        assertDoesNotThrow(() -> {
+            instantiationProvider.checkCompatibility(newDefinition, oldDefinition, instanceId);
+        }, "No exception for major version update");
+
+        // Not compatible
+        newDefinition.setName("policy.clamp.newElement");
+        newDefinition.setVersion("2.2.4");
+
+        assertThatThrownBy(() -> instantiationProvider
+                .checkCompatibility(newDefinition, oldDefinition, instanceId))
+                .hasMessageContaining("is not compatible");
     }
 
     @Test
@@ -396,7 +503,7 @@ class AutomationCompositionInstantiationProviderTest {
 
         var acMigrate = new AutomationComposition(automationComposition);
         acMigrate.setCompositionTargetId(compositionTargetId);
-        automationComposition.getElements().clear();
+        automationComposition.setDeployState(DeployState.DEPLOYING);
 
         var supervisionAcHandler = mock(SupervisionAcHandler.class);
         var participantProvider = mock(ParticipantProvider.class);
@@ -406,7 +513,7 @@ class AutomationCompositionInstantiationProviderTest {
 
         assertThatThrownBy(() -> instantiationProvider
                 .updateAutomationComposition(automationComposition.getCompositionId(), acMigrate))
-                .hasMessageStartingWith("Element id not present");
+                .hasMessageStartingWith("Not allowed to MIGRATE in the state DEPLOYING");
     }
 
     @Test
@@ -433,7 +540,8 @@ class AutomationCompositionInstantiationProviderTest {
 
         var acMigrate = new AutomationComposition(automationComposition);
         acMigrate.setCompositionTargetId(compositionTargetId);
-        automationComposition.getElements().clear();
+        automationComposition.setDeployState(DeployState.DEPLOYING);
+        automationComposition.setPrecheck(true);
 
         var supervisionAcHandler = mock(SupervisionAcHandler.class);
         var participantProvider = mock(ParticipantProvider.class);
@@ -443,7 +551,7 @@ class AutomationCompositionInstantiationProviderTest {
 
         assertThatThrownBy(() -> instantiationProvider
                 .updateAutomationComposition(automationComposition.getCompositionId(), acMigrate))
-                .hasMessageStartingWith("Element id not present");
+                .hasMessageStartingWith("Not allowed to NONE in the state DEPLOYING");
     }
 
     @Test
@@ -635,10 +743,14 @@ class AutomationCompositionInstantiationProviderTest {
         var automationComposition =
                 InstantiationUtils.getAutomationCompositionFromResource(AC_INSTANTIATION_CREATE_JSON, "Crud");
         automationComposition.setCompositionId(compositionId);
+        var message = """
+                      "AutomationComposition" INVALID, item has status INVALID
+                        item "ServiceTemplate.state" value "COMMISSIONED" INVALID, Commissioned automation composition \
+                      definition not primed
+                      """;
+
         assertThatThrownBy(() -> provider.createAutomationComposition(compositionId, automationComposition))
-                .hasMessageMatching("\"AutomationComposition\" INVALID, item has status INVALID\n"
-                        + "  item \"ServiceTemplate.state\" value \"COMMISSIONED\" INVALID,"
-                        + " Commissioned automation composition definition not primed\n");
+                .hasMessageMatching(message);
     }
 
     @Test
diff --git a/runtime-acm/src/test/resources/rest/acm/AutomationCompositionMigrate.json b/runtime-acm/src/test/resources/rest/acm/AutomationCompositionMigrate.json
new file mode 100644 (file)
index 0000000..d74970d
--- /dev/null
@@ -0,0 +1,30 @@
+{
+  "name": "PMSHInstance1",
+  "version": "1.0.1",
+  "compositionId": "709c62b3-8918-41b9-a747-d21eb79c6c40",
+  "deployState": "DEPLOYED",
+  "lockState": "LOCKED",
+  "description": "PMSH automation composition instance 0",
+  "elements": {
+    "709c62b3-8918-41b9-a747-d21eb79c6c21": {
+      "id": "709c62b3-8918-41b9-a747-d21eb79c6c21",
+      "definition": {
+        "name": "org.onap.domain.database.PMSH_K8SMicroserviceAutomationCompositionElement",
+        "version": "1.2.3"
+      },
+      "deployState": "DEPLOYED",
+      "lockState": "LOCKED",
+      "description": "Automation composition element for the K8S microservice for PMSH"
+    },
+    "709c62b3-8918-41b9-a747-d21eb79c6c22": {
+      "id": "709c62b3-8918-41b9-a747-d21eb79c6c22",
+      "definition": {
+        "name": "org.onap.domain.database.Http_PMSHMicroserviceAutomationCompositionElement",
+        "version": "1.2.3"
+      },
+      "deployState": "DEPLOYED",
+      "lockState": "LOCKED",
+      "description": "Automation composition element for the operational policy for Performance Management Subscription Handling"
+    }
+  }
+}
\ No newline at end of file