/*-
* ============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.
private StateChangeResult stateChangeResult;
+ private UUID revisionId = UUID.randomUUID();
+
/**
* Copy contructor, does a deep copy.
*
this.subState = otherAutomationComposition.subState;
this.elements = PfUtils.mapMap(otherAutomationComposition.elements, AutomationCompositionElement::new);
this.stateChangeResult = otherAutomationComposition.stateChangeResult;
+ this.revisionId = otherAutomationComposition.revisionId;
}
@Override
// 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.
*
this.lastMsg = otherAcmDefinition.lastMsg;
this.elementStateMap = PfUtils.mapMap(otherAcmDefinition.elementStateMap, NodeTemplateState::new);
this.stateChangeResult = otherAcmDefinition.stateChangeResult;
+ this.revisionId = otherAcmDefinition.revisionId;
}
}
/*-
* ============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.
private DeployState deployState;
private LockState lockState;
private StateChangeResult stateChangeResult;
+ private UUID revisionId;
private List<AcElementRestart> acElementList = new ArrayList<>();
this.deployState = copyConstructor.deployState;
this.lockState = copyConstructor.lockState;
this.stateChangeResult = copyConstructor.stateChangeResult;
+ this.revisionId = copyConstructor.revisionId;
this.acElementList = PfUtils.mapList(copyConstructor.acElementList, AcElementRestart::new);
}
}
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<>();
/*-
* ============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.
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;
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.
*
* @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);
}
}
/*-
* ============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.
/**
* 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
}
--- /dev/null
+/*-
+ * ============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);
+ }
+}
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;
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;
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Data
@EqualsAndHashCode(callSuper = false)
+@NoArgsConstructor
public class JpaAutomationComposition extends Validated
implements PfAuthorative<AutomationComposition>, Comparable<JpaAutomationComposition> {
@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.
this.subState = copyConcept.subState;
this.description = copyConcept.description;
this.stateChangeResult = copyConcept.stateChangeResult;
+ this.revisionId = copyConcept.revisionId;
this.elements = PfUtils.mapList(copyConcept.elements, JpaAutomationCompositionElement::new);
}
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());
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 =
if (result != 0) {
return result;
}
+ result = ObjectUtils.compare(revisionId, other.revisionId);
+ if (result != 0) {
+ return result;
+ }
return PfUtils.compareObjects(elements, other.elements);
}
}
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;
@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<>();
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();
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());
public JpaAutomationCompositionDefinition() {
super();
}
-
- @Override
- public BeanValidationResult validate(@NonNull String fieldName) {
- var result = super.validate(fieldName);
- if (!result.isValid()) {
- return result;
- }
-
- return result;
- }
}
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");
+ }
+ }
}
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;
@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;
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.
}
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);
+ }
+ }
}
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;
}
}
}
+
+ /**
+ * 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);
+ }
}
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());
/*-
* ============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.
var ac2 = new AutomationComposition();
ac2.setElements(new LinkedHashMap<>());
+ ac2.setRevisionId(ac0.getRevisionId());
// @formatter:off
assertThatThrownBy(() -> ac2.setCompositionId(null)). isInstanceOf(NullPointerException.class);
/*-
* ============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.
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;
// 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() {
/*-
* ============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.
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";
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
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;
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));
+ }
}
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;
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;
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;
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
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");
+ }
}
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;
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;
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);
+ }
}
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;
*/
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()) {
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());
.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) {
*/
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,
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());
}
}
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;
}
}
--- /dev/null
+/*
+ * ============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';
--- /dev/null
+/*
+ * ============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';
--- /dev/null
+# ============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
file: db/changelog/changelog-1700.yaml
- include:
file: db/changelog/changelog-1701.yaml
+ - include:
+ file: db/changelog/changelog-1702.yaml
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;
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;
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);
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))
provider.compositionInstanceState(compositionId, instanceId, acInstanceStateUpdate);
verify(supervisionAcHandler).review(any(AutomationComposition.class));
}
-}
\ No newline at end of file
+}
assertThat(automationCompositionsFromDb.getAutomationCompositionList()).hasSize(1);
var acFromDb = automationCompositionsFromDb.getAutomationCompositionList().get(0);
automationComposition.setLastMsg(acFromDb.getLastMsg());
+ automationComposition.setRevisionId(acFromDb.getRevisionId());
assertEquals(automationComposition, acFromDb);
}