diff options
author | rameshiyer27 <ramesh.murugan.iyer@est.tech> | 2024-07-23 18:08:35 +0100 |
---|---|---|
committer | rameshiyer27 <ramesh.murugan.iyer@est.tech> | 2024-07-26 12:43:25 +0100 |
commit | bcc65d6f4268ff437b162324de13ee53bde47ddd (patch) | |
tree | 3b4fbbb23639d6a006da828d7d9400b1d75d3e56 | |
parent | 37195c69c99b5987f037a018859b4421cbcbadf2 (diff) |
Support add/remove elements in Migration
Issue-ID: POLICY-4917
Change-Id: I0014b4858dd7e6ac76bfa1184d0b90b52e8649f5
Signed-off-by: zrrmmua <ramesh.murugan.iyer@est.tech>
10 files changed, 364 insertions, 51 deletions
diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProvider.java b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProvider.java index 9b736483d..e26a7748f 100644 --- a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProvider.java +++ b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProvider.java @@ -269,6 +269,15 @@ public class AutomationCompositionProvider { } /** + * Delete AutomationCompositionElement. + * + * @param elementId the AutomationCompositionElement Id + */ + public void deleteAutomationCompositionElement(@NonNull final UUID elementId) { + acElementRepository.deleteById(elementId.toString()); + } + + /** * Validate ElementIds. * * @param automationComposition the AutomationComposition diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProviderTest.java b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProviderTest.java index 515dfaa83..c2368fe10 100644 --- a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProviderTest.java +++ b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProviderTest.java @@ -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 = @@ -241,6 +242,18 @@ class AutomationCompositionProviderTest { } @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); var automationCompositionProvider = new AutomationCompositionProvider( diff --git a/participant/participant-impl/participant-impl-simulator/src/main/java/org/onap/policy/clamp/acm/participant/sim/main/handler/AutomationCompositionElementHandlerV2.java b/participant/participant-impl/participant-impl-simulator/src/main/java/org/onap/policy/clamp/acm/participant/sim/main/handler/AutomationCompositionElementHandlerV2.java index 5f866eb0c..7eff849a1 100644 --- a/participant/participant-impl/participant-impl-simulator/src/main/java/org/onap/policy/clamp/acm/participant/sim/main/handler/AutomationCompositionElementHandlerV2.java +++ b/participant/participant-impl/participant-impl-simulator/src/main/java/org/onap/policy/clamp/acm/participant/sim/main/handler/AutomationCompositionElementHandlerV2.java @@ -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 diff --git a/participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/api/CompositionElementDto.java b/participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/api/CompositionElementDto.java index d203f90cb..51800cef1 100644 --- a/participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/api/CompositionElementDto.java +++ b/participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/api/CompositionElementDto.java @@ -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); + + } } diff --git a/participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/api/InstanceElementDto.java b/participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/api/InstanceElementDto.java index 297af6c43..65a83e222 100644 --- a/participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/api/InstanceElementDto.java +++ b/participant/participant-intermediary/src/main/java/org/onap/policy/clamp/acm/participant/intermediary/api/InstanceElementDto.java @@ -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); + } } 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 0f230c0a4..6c560e7fa 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 @@ -21,14 +21,20 @@ 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); } } } diff --git a/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandlerTest.java b/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandlerTest.java index 40e3b1eec..5973508a8 100644 --- a/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandlerTest.java +++ b/participant/participant-intermediary/src/test/java/org/onap/policy/clamp/acm/participant/intermediary/handler/AutomationCompositionHandlerTest.java @@ -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()); } } 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 c732654f2..653d97338 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 @@ -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()) { diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProviderTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProviderTest.java index a7ae99feb..23709728d 100644 --- a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProviderTest.java +++ b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProviderTest.java @@ -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 index 000000000..d74970d4f --- /dev/null +++ b/runtime-acm/src/test/resources/rest/acm/AutomationCompositionMigrate.json @@ -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 |