From: FrancescoFioraEst Date: Thu, 17 Jul 2025 12:38:22 +0000 (+0100) Subject: Add automatic sync up support in ACM-models X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F46%2F141546%2F5;p=policy%2Fclamp.git Add automatic sync up support in ACM-models Issue-ID: POLICY-5416 Change-Id: I71cc4c49ce7dd80cd994b8416f32107fc2dfd867 Signed-off-by: FrancescoFioraEst --- diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/AutomationComposition.java b/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/AutomationComposition.java index 61610bb47..0d643a386 100644 --- a/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/AutomationComposition.java +++ b/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/AutomationComposition.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation. + * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,6 +63,8 @@ public class AutomationComposition extends ToscaEntity implements Comparable elementStateMap = new HashMap<>(); + private UUID revisionId = UUID.randomUUID(); + /** * Copy constructor, does a deep copy. * @@ -65,5 +67,6 @@ public class AutomationCompositionDefinition { this.lastMsg = otherAcmDefinition.lastMsg; this.elementStateMap = PfUtils.mapMap(otherAcmDefinition.elementStateMap, NodeTemplateState::new); this.stateChangeResult = otherAcmDefinition.stateChangeResult; + this.revisionId = otherAcmDefinition.revisionId; } } diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/ParticipantRestartAc.java b/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/ParticipantRestartAc.java index 5d4b8ac77..4ccd24079 100644 --- a/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/ParticipantRestartAc.java +++ b/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/ParticipantRestartAc.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2023-2024 Nordix Foundation. + * Copyright (C) 2023-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,7 @@ public class ParticipantRestartAc { private DeployState deployState; private LockState lockState; private StateChangeResult stateChangeResult; + private UUID revisionId; private List acElementList = new ArrayList<>(); @@ -53,6 +54,7 @@ public class ParticipantRestartAc { this.deployState = copyConstructor.deployState; this.lockState = copyConstructor.lockState; this.stateChangeResult = copyConstructor.stateChangeResult; + this.revisionId = copyConstructor.revisionId; this.acElementList = PfUtils.mapList(copyConstructor.acElementList, AcElementRestart::new); } } diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/AutomationCompositionMigration.java b/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/AutomationCompositionMigration.java index ee05a9521..acb299e18 100644 --- a/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/AutomationCompositionMigration.java +++ b/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/AutomationCompositionMigration.java @@ -34,6 +34,7 @@ import org.onap.policy.clamp.models.acm.concepts.ParticipantDeploy; public class AutomationCompositionMigration extends ParticipantMessage { private UUID compositionTargetId; + private UUID revisionIdCompositionTarget; // A list of updates to AC element properties private List participantUpdatesList = new ArrayList<>(); diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessage.java b/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessage.java index f8aea947b..f1d0f9b44 100644 --- a/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessage.java +++ b/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessage.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation. + * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ package org.onap.policy.clamp.models.acm.messages.kafka.participant; import java.time.Instant; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; import lombok.AccessLevel; import lombok.Getter; @@ -60,6 +62,14 @@ public class ParticipantMessage { private UUID compositionId; + private UUID revisionIdComposition; + private UUID revisionIdInstance; + + /** + * List of participantId that should receive the message. + */ + private Set participantIdList = new HashSet<>(); + /** * Constructor for instantiating a participant message class. * @@ -75,28 +85,40 @@ public class ParticipantMessage { * @param source source from which to copy */ public ParticipantMessage(final ParticipantMessage source) { + this.messageId = source.messageId; + this.timestamp = source.timestamp; this.messageType = source.messageType; this.participantId = source.participantId; this.replicaId = source.replicaId; this.automationCompositionId = source.automationCompositionId; this.compositionId = source.compositionId; + this.revisionIdComposition = source.revisionIdComposition; + this.revisionIdInstance = source.revisionIdInstance; + this.participantIdList = new HashSet<>(source.participantIdList); } /** * Determines if this message applies to this participant type. * - * @param participantId id of the participant to match against - * @param replicaId id of the participant to match against + * @param refParticipantId id of the participant from properties file to match against + * @param refReplicaId id of the replica from properties file to match against * @return {@code true} if this message applies to this participant, {@code false} otherwise */ - public boolean appliesTo(@NonNull final UUID participantId, @NonNull final UUID replicaId) { - // Broadcast message to all participants + public boolean appliesTo(@NonNull final UUID refParticipantId, @NonNull final UUID refReplicaId) { + // Broadcast message to specific list of participants + // or all participants when participantIdList is empty + // filter backward compatible with old ACM-r + if (participantIdList != null && !participantIdList.isEmpty() + && !participantIdList.contains(refParticipantId)) { + return false; + } + // Broadcast message to all participants and all replicas or specific participant and all replicas, + // filter backward compatible with old ACM-r if ((this.participantId == null) - || (participantId.equals(this.participantId) && this.replicaId == null)) { + || (refParticipantId.equals(this.participantId) && this.replicaId == null)) { return true; } - - // Targeted message at this specific participant - return replicaId.equals(this.replicaId); + // Targeted message at a specific participant and replica + return refReplicaId.equals(this.replicaId); } } diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessageType.java b/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessageType.java index 7e19f6f79..8d511a69a 100644 --- a/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessageType.java +++ b/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessageType.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation. + * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -120,5 +120,10 @@ public enum ParticipantMessageType { /** * Used by the acm runtime to ask for a preparation check to participants. */ - AUTOMATION_COMPOSITION_PREPARE + AUTOMATION_COMPOSITION_PREPARE, + + /** + * Used by participant to request for PARTICIPANT_SYNC_MSG message immediately. + */ + PARTICIPANT_REQ_SYNC_MSG } diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantReqSync.java b/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantReqSync.java new file mode 100644 index 000000000..a97184969 --- /dev/null +++ b/models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantReqSync.java @@ -0,0 +1,38 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.clamp.models.acm.messages.kafka.participant; + +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString(callSuper = true) +public class ParticipantReqSync extends ParticipantMessage { + + private UUID compositionTargetId; + + public ParticipantReqSync() { + super(ParticipantMessageType.PARTICIPANT_REQ_SYNC_MSG); + } +} diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationComposition.java b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationComposition.java index df8bf0386..95437a420 100644 --- a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationComposition.java +++ b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationComposition.java @@ -39,6 +39,7 @@ import java.util.List; import java.util.UUID; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import lombok.NonNull; import org.apache.commons.lang3.ObjectUtils; import org.onap.policy.clamp.models.acm.concepts.AutomationComposition; @@ -50,7 +51,6 @@ import org.onap.policy.clamp.models.acm.utils.TimestampHelper; import org.onap.policy.common.parameters.annotations.NotNull; import org.onap.policy.common.parameters.annotations.Valid; import org.onap.policy.models.base.PfAuthorative; -import org.onap.policy.models.base.PfConceptKey; import org.onap.policy.models.base.PfUtils; import org.onap.policy.models.base.Validated; @@ -64,6 +64,7 @@ import org.onap.policy.models.base.Validated; @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) @Data @EqualsAndHashCode(callSuper = false) +@NoArgsConstructor public class JpaAutomationComposition extends Validated implements PfAuthorative, Comparable { @@ -111,43 +112,14 @@ public class JpaAutomationComposition extends Validated @Column private String description; + @Column + @NotNull + private String revisionId; + @NotNull @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn(name = "instanceId", foreignKey = @ForeignKey(name = "ac_element_fk")) - private List<@NotNull @Valid JpaAutomationCompositionElement> elements; - - /** - * The Default Constructor creates a {@link JpaAutomationComposition} object with a null key. - */ - public JpaAutomationComposition() { - this(UUID.randomUUID().toString(), new PfConceptKey(), UUID.randomUUID().toString(), new ArrayList<>(), - DeployState.UNDEPLOYED, LockState.NONE, SubState.NONE); - } - - /** - * The Key Constructor creates a {@link JpaAutomationComposition} object with all mandatory fields. - * - * @param instanceId The UUID of the automation composition instance - * @param key the key - * @param compositionId the TOSCA compositionId of the automation composition definition - * @param elements the elements of the automation composition in participants - * @param deployState the Deploy State - * @param lockState the Lock State - * @param subState the Sub State - */ - public JpaAutomationComposition(@NonNull final String instanceId, @NonNull final PfConceptKey key, - @NonNull final String compositionId, @NonNull final List elements, - @NonNull final DeployState deployState, @NonNull final LockState lockState, - @NonNull final SubState subState) { - this.instanceId = instanceId; - this.name = key.getName(); - this.version = key.getVersion(); - this.compositionId = compositionId; - this.deployState = deployState; - this.lockState = lockState; - this.elements = elements; - this.subState = subState; - } + private List<@NotNull @Valid JpaAutomationCompositionElement> elements = new ArrayList<>(); /** * Copy constructor. @@ -167,6 +139,7 @@ public class JpaAutomationComposition extends Validated this.subState = copyConcept.subState; this.description = copyConcept.description; this.stateChangeResult = copyConcept.stateChangeResult; + this.revisionId = copyConcept.revisionId; this.elements = PfUtils.mapList(copyConcept.elements, JpaAutomationCompositionElement::new); } @@ -197,6 +170,7 @@ public class JpaAutomationComposition extends Validated automationComposition.setSubState(subState); automationComposition.setDescription(description); automationComposition.setStateChangeResult(stateChangeResult); + automationComposition.setRevisionId(UUID.fromString(this.revisionId)); automationComposition.setElements(new LinkedHashMap<>(this.elements.size())); for (var element : this.elements) { automationComposition.getElements().put(UUID.fromString(element.getElementId()), element.toAuthorative()); @@ -221,6 +195,7 @@ public class JpaAutomationComposition extends Validated this.subState = automationComposition.getSubState(); this.description = automationComposition.getDescription(); this.stateChangeResult = automationComposition.getStateChangeResult(); + this.revisionId = automationComposition.getRevisionId().toString(); this.elements = new ArrayList<>(automationComposition.getElements().size()); for (var elementEntry : automationComposition.getElements().entrySet()) { var jpaAutomationCompositionElement = @@ -298,6 +273,10 @@ public class JpaAutomationComposition extends Validated if (result != 0) { return result; } + result = ObjectUtils.compare(revisionId, other.revisionId); + if (result != 0) { + return result; + } return PfUtils.compareObjects(elements, other.elements); } } diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionDefinition.java b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionDefinition.java index cbf21e6fe..fce5782a7 100644 --- a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionDefinition.java +++ b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionDefinition.java @@ -38,13 +38,11 @@ import java.util.Set; import java.util.UUID; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NonNull; import org.onap.policy.clamp.models.acm.concepts.AcTypeState; import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition; import org.onap.policy.clamp.models.acm.concepts.StateChangeResult; import org.onap.policy.clamp.models.acm.document.concepts.DocToscaServiceTemplate; import org.onap.policy.clamp.models.acm.utils.TimestampHelper; -import org.onap.policy.common.parameters.BeanValidationResult; import org.onap.policy.common.parameters.annotations.NotNull; import org.onap.policy.common.parameters.annotations.Pattern; import org.onap.policy.common.parameters.annotations.Valid; @@ -88,6 +86,10 @@ public class JpaAutomationCompositionDefinition extends Validated @NotNull private Timestamp lastMsg; + @Column + @NotNull + private String revisionId; + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "compositionId", foreignKey = @ForeignKey(name = "dt_element_fk")) private Set elements = new HashSet<>(); @@ -105,6 +107,7 @@ public class JpaAutomationCompositionDefinition extends Validated acmDefinition.setState(this.state); acmDefinition.setStateChangeResult(this.stateChangeResult); acmDefinition.setLastMsg(this.lastMsg.toString()); + acmDefinition.setRevisionId(UUID.fromString(this.revisionId)); acmDefinition.setServiceTemplate(this.serviceTemplate.toAuthorative()); for (var element : this.elements) { var key = element.getNodeTemplateId().getName(); @@ -119,6 +122,7 @@ public class JpaAutomationCompositionDefinition extends Validated this.state = copyConcept.getState(); this.stateChangeResult = copyConcept.getStateChangeResult(); this.lastMsg = TimestampHelper.toTimestamp(copyConcept.getLastMsg()); + this.revisionId = copyConcept.getRevisionId().toString(); this.serviceTemplate = new DocToscaServiceTemplate(copyConcept.getServiceTemplate()); setName(this.serviceTemplate.getName()); setVersion(this.serviceTemplate.getVersion()); @@ -138,14 +142,4 @@ public class JpaAutomationCompositionDefinition extends Validated public JpaAutomationCompositionDefinition() { super(); } - - @Override - public BeanValidationResult validate(@NonNull String fieldName) { - var result = super.validate(fieldName); - if (!result.isValid()) { - return result; - } - - return result; - } } diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AcDefinitionProvider.java b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AcDefinitionProvider.java index f8e3a7409..accae2e2e 100644 --- a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AcDefinitionProvider.java +++ b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AcDefinitionProvider.java @@ -229,4 +229,16 @@ public class AcDefinitionProvider { return jpaList.stream().map(JpaAutomationCompositionDefinition::getServiceTemplate) .map(DocToscaServiceTemplate::toAuthorative).toList(); } + + /** + * Check if the Composition Definition is primed. + * + * @param acDefinition the AutomationCompositionDefinition + */ + public static void checkPrimedComposition(AutomationCompositionDefinition acDefinition) { + if (!AcTypeState.PRIMED.equals(acDefinition.getState())) { + throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, + acDefinition.getCompositionId() + " Commissioned automation composition definition not primed"); + } + } } 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 d12d78d16..9c2a2ac21 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 @@ -45,8 +45,12 @@ import org.onap.policy.clamp.models.acm.persistence.repository.AutomationComposi import org.onap.policy.clamp.models.acm.utils.AcmUtils; import org.onap.policy.common.parameters.BeanValidationResult; 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.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.domain.Example; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -60,6 +64,8 @@ import org.springframework.transaction.annotation.Transactional; @Transactional @AllArgsConstructor public class AutomationCompositionProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionProvider.class); + private static final String DO_NOT_MATCH = " do not match with "; private final AutomationCompositionRepository automationCompositionRepository; private final AutomationCompositionElementRepository acElementRepository; @@ -92,19 +98,6 @@ public class AutomationCompositionProvider { return result.stream().map(JpaAutomationComposition::toAuthorative).findFirst(); } - /** - * Find automation composition by automationCompositionId. - * - * @param automationCompositionId the ID of the automation composition to get - * @return the automation composition found - */ - @Transactional(readOnly = true) - public Optional findAutomationComposition( - final ToscaConceptIdentifier automationCompositionId) { - return automationCompositionRepository - .findOne(createExample(null, automationCompositionId.getName(), automationCompositionId.getVersion())) - .map(JpaAutomationComposition::toAuthorative); - } /** * Create automation composition. @@ -284,4 +277,55 @@ public class AutomationCompositionProvider { } return result.get().toAuthorative(); } + + /** + * validate that compositionId into the Instance Endpoint is correct. + * + * @param compositionId the compositionId + * @param automationComposition the AutomationComposition + */ + public static void validateInstanceEndpoint(UUID compositionId, + AutomationComposition automationComposition) { + + if (!compositionId.equals(automationComposition.getCompositionId())) { + throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, + automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId); + } + } + + /** + * Validate that name and version of an AutomationComposition is not already used. + * + * @param acIdentifier the name and version of an AutomationComposition + */ + @Transactional(readOnly = true) + public void validateNameVersion(ToscaConceptIdentifier acIdentifier) { + var acOpt = automationCompositionRepository + .findOne(createExample(null, acIdentifier.getName(), acIdentifier.getVersion())) + .map(JpaAutomationComposition::toAuthorative); + if (acOpt.isPresent()) { + throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, acIdentifier + " already defined"); + } + } + + /** + * Check Compatibility of name version between elements. + * + * @param newDefinition the new name version + * @param dbElementDefinition the name version from db + * @param instanceId the instanceId + */ + public static void checkCompatibility(PfConceptKey newDefinition, PfConceptKey dbElementDefinition, + UUID instanceId) { + var compatibility = newDefinition.getCompatibility(dbElementDefinition); + if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) { + throw new PfModelRuntimeException(Response.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); + } + } } diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/ParticipantProvider.java b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/ParticipantProvider.java index 7715ccc1d..b854f1113 100644 --- a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/ParticipantProvider.java +++ b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/ParticipantProvider.java @@ -31,6 +31,7 @@ import java.util.UUID; import java.util.stream.Collectors; import lombok.NonNull; import lombok.RequiredArgsConstructor; +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.NodeTemplateState; import org.onap.policy.clamp.models.acm.concepts.Participant; @@ -241,4 +242,15 @@ public class ParticipantProvider { } } } + + /** + * Check if the Participant defined into an AutomationCompositionDefinition has been registered. + * + * @param acDefinition the AutomationCompositionDefinition + */ + public void checkRegisteredParticipant(AutomationCompositionDefinition acDefinition) { + var participantIds = acDefinition.getElementStateMap().values().stream() + .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet()); + verifyParticipantState(participantIds); + } } diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java b/models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java index f1b7973ad..5bcac7e3b 100644 --- a/models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java +++ b/models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java @@ -456,6 +456,7 @@ public final class AcmUtils { public static ParticipantRestartAc createAcRestart(AutomationComposition automationComposition, UUID participantId) { var syncAc = new ParticipantRestartAc(); + syncAc.setRevisionId(automationComposition.getRevisionId()); syncAc.setDeployState(automationComposition.getDeployState()); syncAc.setLockState(automationComposition.getLockState()); syncAc.setAutomationCompositionId(automationComposition.getInstanceId()); diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/concepts/AutomationCompositionTest.java b/models/src/test/java/org/onap/policy/clamp/models/acm/concepts/AutomationCompositionTest.java index 99c980c1c..f7b978838 100644 --- a/models/src/test/java/org/onap/policy/clamp/models/acm/concepts/AutomationCompositionTest.java +++ b/models/src/test/java/org/onap/policy/clamp/models/acm/concepts/AutomationCompositionTest.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2021-2023 Nordix Foundation. + * Copyright (C) 2021-2023,2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,6 +70,7 @@ class AutomationCompositionTest { var ac2 = new AutomationComposition(); ac2.setElements(new LinkedHashMap<>()); + ac2.setRevisionId(ac0.getRevisionId()); // @formatter:off assertThatThrownBy(() -> ac2.setCompositionId(null)). isInstanceOf(NullPointerException.class); diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessageTest.java b/models/src/test/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessageTest.java index c6386a571..2fbfdf750 100644 --- a/models/src/test/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessageTest.java +++ b/models/src/test/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessageTest.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation. + * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.onap.policy.clamp.models.acm.messages.kafka.participant.ParticipantMessageUtils.assertSerializable; import java.time.Instant; +import java.util.Set; import java.util.UUID; import org.junit.jupiter.api.Test; import org.onap.policy.clamp.models.acm.utils.CommonTestData; @@ -70,14 +71,33 @@ class ParticipantMessageTest { // ParticipantId matches assertTrue(message.appliesTo(CommonTestData.getParticipantId(), CommonTestData.getReplicaId())); - assertFalse(message.appliesTo(CommonTestData.getRndParticipantId(), CommonTestData.getReplicaId())); + + message.setReplicaId(CommonTestData.getReplicaId()); + assertTrue(message.appliesTo(CommonTestData.getParticipantId(), CommonTestData.getReplicaId())); + + message.setParticipantIdList(Set.of()); + assertTrue(message.appliesTo(CommonTestData.getParticipantId(), CommonTestData.getReplicaId())); + + message.setParticipantIdList(Set.of(CommonTestData.getParticipantId())); + assertTrue(message.appliesTo(CommonTestData.getParticipantId(), CommonTestData.getReplicaId())); + + message.setParticipantId(null); + assertTrue(message.appliesTo(CommonTestData.getParticipantId(), CommonTestData.getReplicaId())); } @Test void testAppliesTo_ParticipantIdNoMatch() { var message = makeMessage(); assertFalse(message.appliesTo(CommonTestData.getRndParticipantId(), CommonTestData.getReplicaId())); - assertTrue(message.appliesTo(CommonTestData.getParticipantId(), CommonTestData.getReplicaId())); + + message.setReplicaId(CommonTestData.getReplicaId()); + assertFalse(message.appliesTo(CommonTestData.getParticipantId(), CommonTestData.getRndParticipantId())); + + message.setReplicaId(null); + message.setParticipantId(null); + message.setParticipantIdList( + Set.of(CommonTestData.getRndParticipantId(), CommonTestData.getRndParticipantId())); + assertFalse(message.appliesTo(CommonTestData.getParticipantId(), CommonTestData.getReplicaId())); } private ParticipantMessage makeMessage() { diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionTest.java b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionTest.java index 38153d488..3c9daae8a 100644 --- a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionTest.java +++ b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionTest.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation. + * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,14 +39,11 @@ 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.utils.TimestampHelper; -import org.onap.policy.models.base.PfConceptKey; /** * Test the{@link JpaAutomationCompositionTest} class. */ class JpaAutomationCompositionTest { - - private static final String NULL_INSTANCE_ID_ERROR = "instanceId is marked .*ull but is null"; private static final String NULL_ERROR = " is marked .*ull but is null"; private static final String INSTANCE_ID = "709c62b3-8918-41b9-a747-d21eb79c6c20"; private static final String COMPOSITION_ID = "709c62b3-8918-41b9-a747-e21eb79c6c41"; @@ -61,43 +58,7 @@ class JpaAutomationCompositionTest { new JpaAutomationComposition((AutomationComposition) null); }).hasMessageMatching("authorativeConcept" + NULL_ERROR); - assertThatThrownBy(() -> { - new JpaAutomationComposition(null, null, null, null, null, null, null); - }).hasMessageMatching(NULL_INSTANCE_ID_ERROR); - - assertThatThrownBy(() -> { - new JpaAutomationComposition(INSTANCE_ID, null, null, new ArrayList<>(), DeployState.UNDEPLOYED, - LockState.LOCKED, SubState.NONE); - }).hasMessageMatching("key" + NULL_ERROR); - - assertThatThrownBy(() -> { - new JpaAutomationComposition(INSTANCE_ID, new PfConceptKey(), null, new ArrayList<>(), - DeployState.UNDEPLOYED, LockState.LOCKED, SubState.NONE); - }).hasMessageMatching("compositionId" + NULL_ERROR); - - assertThatThrownBy(() -> { - new JpaAutomationComposition(INSTANCE_ID, new PfConceptKey(), COMPOSITION_ID, null, - DeployState.UNDEPLOYED, LockState.LOCKED, SubState.NONE); - }).hasMessageMatching("elements" + NULL_ERROR); - - assertThatThrownBy(() -> { - new JpaAutomationComposition(INSTANCE_ID, new PfConceptKey(), COMPOSITION_ID, new ArrayList<>(), - null, LockState.LOCKED, SubState.NONE); - }).hasMessageMatching("deployState" + NULL_ERROR); - - assertThatThrownBy(() -> { - new JpaAutomationComposition(INSTANCE_ID, new PfConceptKey(), COMPOSITION_ID, new ArrayList<>(), - DeployState.UNDEPLOYED, null, SubState.NONE); - }).hasMessageMatching("lockState" + NULL_ERROR); - - assertThatThrownBy(() -> { - new JpaAutomationComposition(INSTANCE_ID, new PfConceptKey(), COMPOSITION_ID, new ArrayList<>(), - DeployState.UNDEPLOYED, LockState.NONE, null); - }).hasMessageMatching("subState" + NULL_ERROR); - assertDoesNotThrow(() -> new JpaAutomationComposition()); - assertDoesNotThrow(() -> new JpaAutomationComposition(INSTANCE_ID, new PfConceptKey(), COMPOSITION_ID, - new ArrayList<>(), DeployState.UNDEPLOYED, LockState.LOCKED, SubState.NONE)); } @Test diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AcDefinitionProviderTest.java b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AcDefinitionProviderTest.java index 57d8441c6..ed2f6e983 100644 --- a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AcDefinitionProviderTest.java +++ b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AcDefinitionProviderTest.java @@ -22,6 +22,7 @@ package org.onap.policy.clamp.models.acm.persistence.provider; 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.mock; import static org.mockito.Mockito.verify; @@ -348,4 +349,15 @@ class AcDefinitionProviderTest { assertThat(result).hasSize(1); assertThat(result.get(0)).isEqualTo(acmDefinition.getServiceTemplate()); } + + @Test + void testCheckPrimedComposition() { + var docServiceTemplate = new DocToscaServiceTemplate(inputServiceTemplate); + var acmDefinition = getAcDefinition(docServiceTemplate); + assertThatThrownBy(() -> AcDefinitionProvider.checkPrimedComposition(acmDefinition)).hasMessage( + acmDefinition.getCompositionId() + " Commissioned automation composition definition not primed"); + + acmDefinition.setState(AcTypeState.PRIMED); + assertDoesNotThrow(() -> AcDefinitionProvider.checkPrimedComposition(acmDefinition)); + } } 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 6ddf965ab..caf4f318c 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 @@ -22,6 +22,7 @@ package org.onap.policy.clamp.models.acm.persistence.provider; 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.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -39,6 +40,7 @@ import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.onap.policy.clamp.models.acm.concepts.AutomationComposition; import org.onap.policy.clamp.models.acm.concepts.AutomationCompositions; import org.onap.policy.clamp.models.acm.concepts.DeployState; import org.onap.policy.clamp.models.acm.concepts.LockState; @@ -51,7 +53,9 @@ import org.onap.policy.clamp.models.acm.persistence.repository.AutomationComposi import org.onap.policy.common.utils.coder.Coder; import org.onap.policy.common.utils.coder.StandardCoder; import org.onap.policy.common.utils.resources.ResourceUtils; +import org.onap.policy.models.base.PfConceptKey; import org.onap.policy.models.base.PfModelRuntimeException; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -178,18 +182,10 @@ class AutomationCompositionProviderTest { var acOpt = automationCompositionProvider.findAutomationComposition(automationComposition.getInstanceId()); assertThat(acOpt).isEmpty(); - acOpt = automationCompositionProvider.findAutomationComposition(automationComposition.getKey().asIdentifier()); - assertThat(acOpt).isEmpty(); - when(automationCompositionRepository.findById(automationComposition.getInstanceId().toString())) - .thenReturn(Optional.of(inputAutomationCompositionsJpa.get(0))); + .thenReturn(Optional.of(inputAutomationCompositionsJpa.get(0))); acOpt = automationCompositionProvider.findAutomationComposition(automationComposition.getInstanceId()); assertEquals(automationComposition, acOpt.get()); - - when(automationCompositionRepository.findOne(Mockito.any())) - .thenReturn(Optional.of(inputAutomationCompositionsJpa.get(0))); - acOpt = automationCompositionProvider.findAutomationComposition(automationComposition.getKey().asIdentifier()); - assertEquals(automationComposition, acOpt.get()); } @Test @@ -349,4 +345,71 @@ class AutomationCompositionProviderTest { assertThrows(PfModelRuntimeException.class, () -> automationCompositionProvider .getAutomationCompositionRollback(compositionId)); } + + @Test + void testVersionCompatibility() { + // Identical + var newDefinition = new PfConceptKey("policy.clamp.element", "1.2.3"); + var oldDefinition = new PfConceptKey("policy.clamp.element", "1.2.3"); + + var instanceId = UUID.randomUUID(); + assertDoesNotThrow(() -> + AutomationCompositionProvider.checkCompatibility(newDefinition, oldDefinition, instanceId)); + + // Patch + newDefinition.setVersion("1.2.4"); + assertDoesNotThrow(() -> + AutomationCompositionProvider.checkCompatibility(newDefinition, oldDefinition, instanceId)); + + // Minor + newDefinition.setVersion("1.3.1"); + assertDoesNotThrow(() -> + AutomationCompositionProvider.checkCompatibility(newDefinition, oldDefinition, instanceId)); + + // Major + newDefinition.setVersion("2.1.1"); + assertDoesNotThrow(() -> + AutomationCompositionProvider.checkCompatibility(newDefinition, oldDefinition, instanceId)); + + // Not compatible + newDefinition.setName("policy.clamp.newElement"); + newDefinition.setVersion("2.2.4"); + assertThatThrownBy(() -> AutomationCompositionProvider + .checkCompatibility(newDefinition, oldDefinition, instanceId)) + .hasMessageContaining("is not compatible"); + } + + @Test + void testValidateNameVersion() { + var automationCompositionRepository = mock(AutomationCompositionRepository.class); + var automationCompositionProvider = new AutomationCompositionProvider( + automationCompositionRepository, mock(AutomationCompositionElementRepository.class), + mock(AutomationCompositionRollbackRepository.class)); + + var acIdentifier = new ToscaConceptIdentifier(); + assertDoesNotThrow(() -> { + automationCompositionProvider.validateNameVersion(acIdentifier); + }); + + when(automationCompositionRepository.findOne(Mockito.any())) + .thenReturn(Optional.of(inputAutomationCompositionsJpa.get(0))); + assertThatThrownBy(() -> { + automationCompositionProvider.validateNameVersion(acIdentifier); + }).hasMessageContaining("already defined"); + } + + @Test + void testValidateInstanceEndpoint() { + var automationComposition = new AutomationComposition(); + automationComposition.setCompositionId(UUID.randomUUID()); + + var compositionId = automationComposition.getCompositionId(); + assertDoesNotThrow(() -> AutomationCompositionProvider + .validateInstanceEndpoint(compositionId, automationComposition)); + + var wrongCompositionId = UUID.randomUUID(); + assertThatThrownBy(() -> AutomationCompositionProvider + .validateInstanceEndpoint(wrongCompositionId, automationComposition)) + .hasMessageContaining("do not match with"); + } } diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/ParticipantProviderTest.java b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/ParticipantProviderTest.java index a3d7b7927..54aa8be8d 100644 --- a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/ParticipantProviderTest.java +++ b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/ParticipantProviderTest.java @@ -32,12 +32,14 @@ import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.onap.policy.clamp.models.acm.concepts.AcTypeState; +import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition; import org.onap.policy.clamp.models.acm.concepts.AutomationCompositions; import org.onap.policy.clamp.models.acm.concepts.NodeTemplateState; import org.onap.policy.clamp.models.acm.concepts.Participant; @@ -54,6 +56,7 @@ import org.onap.policy.common.utils.coder.Coder; import org.onap.policy.common.utils.coder.StandardCoder; import org.onap.policy.common.utils.resources.ResourceUtils; import org.onap.policy.models.base.PfModelRuntimeException; +import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -359,4 +362,25 @@ class ParticipantProviderTest { participantProvider.verifyParticipantState(set); verify(participantRepository, times(2)).getReferenceById(participantId); } + + @Test + void testCheckRegisteredParticipant() { + var jpaParticipant = new JpaParticipant(jpaParticipantList.get(0)); + var participantId = jpaParticipant.getParticipantId(); + var participantRepository = mock(ParticipantRepository.class); + when(participantRepository.getReferenceById(participantId)).thenReturn(jpaParticipant); + + var acDefinition = new AutomationCompositionDefinition(); + var nodeTemplateState = new NodeTemplateState(); + nodeTemplateState.setNodeTemplateId(new ToscaConceptIdentifier("name", "0.0.0")); + nodeTemplateState.setParticipantId(UUID.fromString(participantId)); + acDefinition.setElementStateMap(Map.of(nodeTemplateState.getNodeTemplateId().getName(), nodeTemplateState)); + + var replicaRepository = mock(ParticipantReplicaRepository.class); + var participantProvider = new ParticipantProvider(participantRepository, + mock(AutomationCompositionElementRepository.class), mock(NodeTemplateStateRepository.class), + replicaRepository); + participantProvider.checkRegisteredParticipant(acDefinition); + verify(participantRepository).getReferenceById(participantId); + } } 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 dfeb7f39c..389f555d0 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 @@ -55,8 +55,6 @@ 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; import org.slf4j.LoggerFactory; @@ -95,13 +93,8 @@ public class AutomationCompositionInstantiationProvider { */ public InstantiationResponse createAutomationComposition(UUID compositionId, AutomationComposition automationComposition) { - validateCompositionRequested(compositionId, automationComposition); - var checkAutomationCompositionOpt = - automationCompositionProvider.findAutomationComposition(automationComposition.getKey().asIdentifier()); - if (checkAutomationCompositionOpt.isPresent()) { - throw new PfModelRuntimeException(Status.BAD_REQUEST, - automationComposition.getKey().asIdentifier() + " already defined"); - } + AutomationCompositionProvider.validateInstanceEndpoint(compositionId, automationComposition); + automationCompositionProvider.validateNameVersion(automationComposition.getKey().asIdentifier()); var validationResult = validateAutomationComposition(automationComposition); if (!validationResult.isValid()) { @@ -131,7 +124,7 @@ public class AutomationCompositionInstantiationProvider { AutomationComposition automationComposition) { var instanceId = automationComposition.getInstanceId(); var acToUpdate = automationCompositionProvider.getAutomationComposition(instanceId); - validateCompositionRequested(compositionId, acToUpdate); + AutomationCompositionProvider.validateInstanceEndpoint(compositionId, acToUpdate); if (DeployState.UNDEPLOYED.equals(acToUpdate.getDeployState())) { acToUpdate.setElements(automationComposition.getElements()); acToUpdate.setName(automationComposition.getName()); @@ -259,20 +252,6 @@ public class AutomationCompositionInstantiationProvider { .filter(id -> acFromMigration.getElements().get(id) == null).toList(); } - 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) { @@ -465,7 +444,7 @@ public class AutomationCompositionInstantiationProvider { */ public void rollback(UUID compositionId, UUID instanceId) { var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId); - validateCompositionRequested(compositionId, automationComposition); + AutomationCompositionProvider.validateInstanceEndpoint(compositionId, automationComposition); if (!DeployOrder.MIGRATION_REVERT.name().equals(acInstanceStateResolver.resolve( DeployOrder.MIGRATION_REVERT, LockOrder.NONE, @@ -508,7 +487,8 @@ public class AutomationCompositionInstantiationProvider { AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties()); var newDefinition = element.getValue().getDefinition().asConceptKey(); var dbElementDefinition = dbAcElement.getDefinition().asConceptKey(); - checkCompatibility(newDefinition, dbElementDefinition, automationComposition.getInstanceId()); + AutomationCompositionProvider + .checkCompatibility(newDefinition, dbElementDefinition, automationComposition.getInstanceId()); dbAcElement.setDefinition(element.getValue().getDefinition()); } } @@ -525,23 +505,11 @@ public class AutomationCompositionInstantiationProvider { return elementsRemoved; } - private static void validateCompositionRequested(UUID compositionId, - AutomationComposition automationComposition) { - if (!compositionId.equals(automationComposition.getCompositionId())) { - throw new PfModelRuntimeException(Status.BAD_REQUEST, - automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId); - } - } - private AutomationCompositionDefinition getAcDefinition(UUID compositionId, AutomationComposition automationComposition) { - validateCompositionRequested(compositionId, automationComposition); + AutomationCompositionProvider.validateInstanceEndpoint(compositionId, automationComposition); var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId()); - - var participantIds = acDefinition.getElementStateMap().values().stream() - .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet()); - - participantProvider.verifyParticipantState(participantIds); + participantProvider.checkRegisteredParticipant(acDefinition); return acDefinition; } } diff --git a/runtime-acm/src/main/resources/db/changelog/1702/upgrade/0100-automationcomposition.sql b/runtime-acm/src/main/resources/db/changelog/1702/upgrade/0100-automationcomposition.sql new file mode 100644 index 000000000..d349ca262 --- /dev/null +++ b/runtime-acm/src/main/resources/db/changelog/1702/upgrade/0100-automationcomposition.sql @@ -0,0 +1,21 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +ALTER TABLE automationcomposition + ADD COLUMN revisionId VARCHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; diff --git a/runtime-acm/src/main/resources/db/changelog/1702/upgrade/0200-automationcompositiondefinition.sql b/runtime-acm/src/main/resources/db/changelog/1702/upgrade/0200-automationcompositiondefinition.sql new file mode 100644 index 000000000..1e94848b6 --- /dev/null +++ b/runtime-acm/src/main/resources/db/changelog/1702/upgrade/0200-automationcompositiondefinition.sql @@ -0,0 +1,21 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +ALTER TABLE automationcompositiondefinition + ADD COLUMN revisionId VARCHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; diff --git a/runtime-acm/src/main/resources/db/changelog/changelog-1702.yaml b/runtime-acm/src/main/resources/db/changelog/changelog-1702.yaml new file mode 100644 index 000000000..1b2d96070 --- /dev/null +++ b/runtime-acm/src/main/resources/db/changelog/changelog-1702.yaml @@ -0,0 +1,34 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2025 OpenInfra Foundation Europe. All rights reserved. +# ================================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +databaseChangeLog: + - objectQuotingStrategy: QUOTE_ONLY_RESERVED_WORDS + - changeSet: + author: policy + id: 1702 + preConditions: + not: + columnExists: + tableName: automationcomposition + columnName: revisionId + onFail: MARK_RAN + changes: + - sqlFile: + path: db/changelog/1702/upgrade/0100-automationcomposition.sql + - sqlFile: + path: db/changelog/1702/upgrade/0200-automationcompositiondefinition.sql diff --git a/runtime-acm/src/main/resources/db/changelog/db.changelog-master.yaml b/runtime-acm/src/main/resources/db/changelog/db.changelog-master.yaml index 7b8a7a670..8b20bd3a9 100644 --- a/runtime-acm/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/runtime-acm/src/main/resources/db/changelog/db.changelog-master.yaml @@ -30,3 +30,5 @@ databaseChangeLog: file: db/changelog/changelog-1700.yaml - include: file: db/changelog/changelog-1701.yaml + - include: + file: db/changelog/changelog-1702.yaml 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 8ecf450ed..a0e028eea 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,16 +23,17 @@ 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.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.onap.policy.clamp.acm.runtime.util.CommonTestData.TOSCA_SERVICE_TEMPLATE_YAML; +import jakarta.ws.rs.core.Response.Status; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -62,7 +63,6 @@ import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositi 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.base.PfModelRuntimeException; import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate; @@ -314,32 +314,6 @@ class AutomationCompositionInstantiationProviderTest { 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(), null); - 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 void testInstantiationMigration() { var acDefinitionProvider = mock(AcDefinitionProvider.class); @@ -702,9 +676,9 @@ class AutomationCompositionInstantiationProviderTest { var instantiationResponse = instantiationProvider.createAutomationComposition( automationCompositionCreate.getCompositionId(), automationCompositionCreate); InstantiationUtils.assertInstantiationResponse(instantiationResponse, automationCompositionCreate); - - when(acProvider.findAutomationComposition(automationCompositionCreate.getKey().asIdentifier())) - .thenReturn(Optional.of(automationCompositionCreate)); + var acIdentifier = automationCompositionCreate.getKey().asIdentifier(); + doThrow(new PfModelRuntimeException(Status.BAD_REQUEST, acIdentifier + " already defined")) + .when(acProvider).validateNameVersion(acIdentifier); assertThatThrownBy( () -> instantiationProvider.createAutomationComposition(compositionId, automationCompositionCreate)) @@ -884,4 +858,4 @@ class AutomationCompositionInstantiationProviderTest { provider.compositionInstanceState(compositionId, instanceId, acInstanceStateUpdate); verify(supervisionAcHandler).review(any(AutomationComposition.class)); } -} \ No newline at end of file +} diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/rest/InstantiationControllerTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/rest/InstantiationControllerTest.java index fe1775f97..d214e7355 100644 --- a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/rest/InstantiationControllerTest.java +++ b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/rest/InstantiationControllerTest.java @@ -325,6 +325,7 @@ class InstantiationControllerTest extends CommonRestController { assertThat(automationCompositionsFromDb.getAutomationCompositionList()).hasSize(1); var acFromDb = automationCompositionsFromDb.getAutomationCompositionList().get(0); automationComposition.setLastMsg(acFromDb.getLastMsg()); + automationComposition.setRevisionId(acFromDb.getRevisionId()); assertEquals(automationComposition, acFromDb); }