Add automatic sync up support in ACM-models 46/141546/5
authorFrancescoFioraEst <francesco.fiora@est.tech>
Thu, 17 Jul 2025 12:38:22 +0000 (13:38 +0100)
committerFrancescoFioraEst <francesco.fiora@est.tech>
Mon, 21 Jul 2025 14:32:27 +0000 (15:32 +0100)
Issue-ID: POLICY-5416
Change-Id: I71cc4c49ce7dd80cd994b8416f32107fc2dfd867
Signed-off-by: FrancescoFioraEst <francesco.fiora@est.tech>
26 files changed:
models/src/main/java/org/onap/policy/clamp/models/acm/concepts/AutomationComposition.java
models/src/main/java/org/onap/policy/clamp/models/acm/concepts/AutomationCompositionDefinition.java
models/src/main/java/org/onap/policy/clamp/models/acm/concepts/ParticipantRestartAc.java
models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/AutomationCompositionMigration.java
models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessage.java
models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessageType.java
models/src/main/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantReqSync.java [new file with mode: 0644]
models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationComposition.java
models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionDefinition.java
models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AcDefinitionProvider.java
models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProvider.java
models/src/main/java/org/onap/policy/clamp/models/acm/persistence/provider/ParticipantProvider.java
models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java
models/src/test/java/org/onap/policy/clamp/models/acm/concepts/AutomationCompositionTest.java
models/src/test/java/org/onap/policy/clamp/models/acm/messages/kafka/participant/ParticipantMessageTest.java
models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionTest.java
models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AcDefinitionProviderTest.java
models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProviderTest.java
models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/ParticipantProviderTest.java
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProvider.java
runtime-acm/src/main/resources/db/changelog/1702/upgrade/0100-automationcomposition.sql [new file with mode: 0644]
runtime-acm/src/main/resources/db/changelog/1702/upgrade/0200-automationcompositiondefinition.sql [new file with mode: 0644]
runtime-acm/src/main/resources/db/changelog/changelog-1702.yaml [new file with mode: 0644]
runtime-acm/src/main/resources/db/changelog/db.changelog-master.yaml
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/AutomationCompositionInstantiationProviderTest.java
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/rest/InstantiationControllerTest.java

index 61610bb..0d643a3 100644 (file)
@@ -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<Aut
 
     private StateChangeResult stateChangeResult;
 
+    private UUID revisionId = UUID.randomUUID();
+
     /**
      * Copy contructor, does a deep copy.
      *
@@ -81,6 +83,7 @@ public class AutomationComposition extends ToscaEntity implements Comparable<Aut
         this.subState = otherAutomationComposition.subState;
         this.elements = PfUtils.mapMap(otherAutomationComposition.elements, AutomationCompositionElement::new);
         this.stateChangeResult = otherAutomationComposition.stateChangeResult;
+        this.revisionId = otherAutomationComposition.revisionId;
     }
 
     @Override
index 0fee98b..4c79399 100644 (file)
@@ -53,6 +53,8 @@ public class AutomationCompositionDefinition {
     // Map used to store prime state with key as NodeTemplate Name and value as NodeTemplateState
     private Map<String, NodeTemplateState> 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;
     }
 }
index 5d4b8ac..4ccd240 100644 (file)
@@ -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<AcElementRestart> 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);
     }
 }
index ee05a95..acb299e 100644 (file)
@@ -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<ParticipantDeploy> participantUpdatesList = new ArrayList<>();
 
index f8aea94..f1d0f9b 100644 (file)
@@ -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<UUID> 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);
     }
 }
index 7e19f6f..8d511a6 100644 (file)
@@ -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 (file)
index 0000000..a971849
--- /dev/null
@@ -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);
+    }
+}
index df8bf03..95437a4 100644 (file)
@@ -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<AutomationComposition>, Comparable<JpaAutomationComposition> {
 
@@ -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<JpaAutomationCompositionElement> 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);
     }
 }
index cbf21e6..fce5782 100644 (file)
@@ -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<JpaNodeTemplateState> 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;
-    }
 }
index f8e3a74..accae2e 100644 (file)
@@ -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");
+        }
+    }
 }
index d12d78d..9c2a2ac 100644 (file)
@@ -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<AutomationComposition> 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);
+        }
+    }
 }
index 7715ccc..b854f11 100644 (file)
@@ -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);
+    }
 }
index f1b7973..5bcac7e 100644 (file)
@@ -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());
index 99c980c..f7b9788 100644 (file)
@@ -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);
index c6386a5..2fbfdf7 100644 (file)
@@ -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() {
index 38153d4..3c9daae 100644 (file)
@@ -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
index 57d8441..ed2f6e9 100644 (file)
@@ -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));
+    }
 }
index 6ddf965..caf4f31 100644 (file)
@@ -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");
+    }
 }
index a3d7b79..54aa8be 100644 (file)
@@ -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);
+    }
 }
index dfeb7f3..389f555 100644 (file)
@@ -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 (file)
index 0000000..d349ca2
--- /dev/null
@@ -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 (file)
index 0000000..1e94848
--- /dev/null
@@ -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 (file)
index 0000000..1b2d960
--- /dev/null
@@ -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
index 7b8a7a6..8b20bd3 100644 (file)
@@ -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
index 8ecf450..a0e028e 100644 (file)
@@ -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
+}
index fe1775f..d214e73 100644 (file)
@@ -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);
     }