2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
4 * Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
22 package org.onap.policy.clamp.acm.runtime.instantiation;
24 import jakarta.validation.Valid;
25 import jakarta.ws.rs.core.Response.Status;
26 import java.util.List;
27 import java.util.UUID;
28 import java.util.stream.Collectors;
29 import lombok.NonNull;
30 import lombok.RequiredArgsConstructor;
31 import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup;
32 import org.onap.policy.clamp.acm.runtime.main.utils.EncryptionUtils;
33 import org.onap.policy.clamp.acm.runtime.supervision.SupervisionAcHandler;
34 import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
35 import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
36 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition;
37 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement;
38 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositions;
39 import org.onap.policy.clamp.models.acm.concepts.DeployState;
40 import org.onap.policy.clamp.models.acm.concepts.LockState;
41 import org.onap.policy.clamp.models.acm.concepts.NodeTemplateState;
42 import org.onap.policy.clamp.models.acm.concepts.ParticipantUtils;
43 import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
44 import org.onap.policy.clamp.models.acm.concepts.SubState;
45 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AcInstanceStateUpdate;
46 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.DeployOrder;
47 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstantiationResponse;
48 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.LockOrder;
49 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.SubOrder;
50 import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvider;
51 import org.onap.policy.clamp.models.acm.persistence.provider.AcInstanceStateResolver;
52 import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
53 import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantProvider;
54 import org.onap.policy.clamp.models.acm.utils.AcmUtils;
55 import org.onap.policy.common.parameters.BeanValidationResult;
56 import org.onap.policy.common.parameters.ObjectValidationResult;
57 import org.onap.policy.common.parameters.ValidationStatus;
58 import org.onap.policy.models.base.PfModelRuntimeException;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61 import org.springframework.data.domain.Pageable;
62 import org.springframework.stereotype.Service;
63 import org.springframework.transaction.annotation.Transactional;
66 * This class is dedicated to the Instantiation of Commissioned automation composition.
70 @RequiredArgsConstructor
71 public class AutomationCompositionInstantiationProvider {
72 private static final String DO_NOT_MATCH = " do not match with ";
73 private static final String ELEMENT_ID_NOT_PRESENT = "Element id not present ";
74 private static final String NOT_VALID_ORDER =
75 "Not valid order %s; DeployState: %s; LockState: %s; SubState: %s; StateChangeResult: %s";
77 private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionInstantiationProvider.class);
79 private final AutomationCompositionProvider automationCompositionProvider;
80 private final AcDefinitionProvider acDefinitionProvider;
81 private final AcInstanceStateResolver acInstanceStateResolver;
82 private final SupervisionAcHandler supervisionAcHandler;
83 private final ParticipantProvider participantProvider;
84 private final AcRuntimeParameterGroup acRuntimeParameterGroup;
85 private final EncryptionUtils encryptionUtils;
88 * Create automation composition.
90 * @param compositionId The UUID of the automation composition definition
91 * @param automationComposition the automation composition
92 * @return the result of the instantiation operation
94 public InstantiationResponse createAutomationComposition(UUID compositionId,
95 AutomationComposition automationComposition) {
96 AutomationCompositionProvider.validateInstanceEndpoint(compositionId, automationComposition);
97 automationCompositionProvider.validateNameVersion(automationComposition.getKey().asIdentifier());
99 var validationResult = validateAutomationComposition(automationComposition);
100 if (!validationResult.isValid()) {
101 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
103 encryptInstanceProperties(automationComposition, compositionId);
104 automationComposition = automationCompositionProvider.createAutomationComposition(automationComposition);
106 return createInstantiationResponse(automationComposition);
109 private InstantiationResponse createInstantiationResponse(AutomationComposition automationComposition) {
110 var response = new InstantiationResponse();
111 response.setInstanceId(automationComposition.getInstanceId());
112 response.setAffectedAutomationComposition(automationComposition.getKey().asIdentifier());
117 * Update automation composition.
119 * @param compositionId The UUID of the automation composition definition
120 * @param automationComposition the automation composition
121 * @return the result of the update
123 public InstantiationResponse updateAutomationComposition(UUID compositionId,
124 AutomationComposition automationComposition) {
125 var instanceId = automationComposition.getInstanceId();
126 var acToUpdate = automationCompositionProvider.getAutomationComposition(instanceId);
127 AutomationCompositionProvider.validateInstanceEndpoint(compositionId, acToUpdate);
128 if (DeployState.UNDEPLOYED.equals(acToUpdate.getDeployState())) {
129 acToUpdate.setElements(automationComposition.getElements());
130 acToUpdate.setName(automationComposition.getName());
131 acToUpdate.setVersion(automationComposition.getVersion());
132 acToUpdate.setDescription(automationComposition.getDescription());
133 acToUpdate.setDerivedFrom(automationComposition.getDerivedFrom());
134 var validationResult = validateAutomationComposition(acToUpdate);
135 if (!validationResult.isValid()) {
136 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
138 encryptInstanceProperties(acToUpdate, compositionId);
139 automationComposition = automationCompositionProvider.updateAutomationComposition(acToUpdate);
140 return createInstantiationResponse(automationComposition);
144 var deployOrder = DeployOrder.UPDATE;
145 var subOrder = SubOrder.NONE;
147 if (automationComposition.getCompositionTargetId() != null) {
149 if (Boolean.TRUE.equals(automationComposition.getPrecheck())) {
150 subOrder = SubOrder.MIGRATE_PRECHECK;
151 deployOrder = DeployOrder.NONE;
153 deployOrder = DeployOrder.MIGRATE;
156 var result = acInstanceStateResolver.resolve(deployOrder, LockOrder.NONE, subOrder,
157 acToUpdate.getDeployState(), acToUpdate.getLockState(), acToUpdate.getSubState(),
158 acToUpdate.getStateChangeResult());
159 return switch (result) {
160 case "UPDATE" -> updateDeployedAutomationComposition(automationComposition, acToUpdate);
162 case "MIGRATE" -> migrateAutomationComposition(automationComposition, acToUpdate);
164 case "MIGRATE_PRECHECK" -> migratePrecheckAc(automationComposition, acToUpdate);
166 default -> throw new PfModelRuntimeException(Status.BAD_REQUEST,
167 "Not allowed to " + deployOrder + " in the state " + acToUpdate.getDeployState());
172 * Update deployed AC Element properties.
174 * @param automationComposition the automation composition
175 * @param acToBeUpdated the composition to be updated
176 * @return the result of the update
178 private InstantiationResponse updateDeployedAutomationComposition(
179 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
180 // save copy in case of a rollback
181 automationCompositionProvider.copyAcElementsBeforeUpdate(acToBeUpdated);
183 // Iterate and update the element property values
184 for (var element : automationComposition.getElements().entrySet()) {
185 var elementId = element.getKey();
186 var dbAcElement = acToBeUpdated.getElements().get(elementId);
187 if (dbAcElement == null) {
188 throw new PfModelRuntimeException(Status.BAD_REQUEST, ELEMENT_ID_NOT_PRESENT + elementId);
190 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
193 var validationResult = validateAutomationComposition(acToBeUpdated);
194 if (!validationResult.isValid()) {
195 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
197 updateAcForProperties(acToBeUpdated);
199 var acToPublish = new AutomationComposition(acToBeUpdated);
201 encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionId());
203 automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
204 // Publish property update event to the participants
205 supervisionAcHandler.update(acToPublish);
206 return createInstantiationResponse(automationComposition);
209 private InstantiationResponse migrateAutomationComposition(
210 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
212 if (!DeployState.DEPLOYED.equals(acToBeUpdated.getDeployState())) {
213 throw new PfModelRuntimeException(Status.BAD_REQUEST,
214 "Not allowed to migrate in the state " + acToBeUpdated.getDeployState());
216 // make copy for rollback
217 automationCompositionProvider.copyAcElementsBeforeUpdate(acToBeUpdated);
219 // Iterate and update the element property values
220 var elementsRemoved = updateElementsProperties(automationComposition, acToBeUpdated);
221 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionTargetId());
223 updateAcForMigration(acToBeUpdated, acDefinition, DeployState.MIGRATING);
225 var acToPublish = new AutomationComposition(acToBeUpdated);
227 encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionTargetId());
229 var ac = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
230 elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
232 // Publish migrate event to the participants
233 supervisionAcHandler.migrate(acToPublish);
234 return createInstantiationResponse(ac);
237 private void updateAcForMigration(AutomationComposition acToBeUpdated,
238 AutomationCompositionDefinition acDefinition, DeployState deployState) {
239 AcmUtils.setCascadedState(acToBeUpdated, deployState, LockState.LOCKED);
240 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
241 var stage = ParticipantUtils.getFirstStage(acToBeUpdated, acDefinition.getServiceTemplate());
242 acToBeUpdated.setPhase(stage);
245 private void updateAcForProperties(AutomationComposition acToBeUpdated) {
246 AcmUtils.setCascadedState(acToBeUpdated, DeployState.UPDATING, acToBeUpdated.getLockState());
247 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
250 private List<UUID> getElementRemoved(AutomationComposition acFromDb, AutomationComposition acFromMigration) {
251 return acFromDb.getElements().keySet().stream()
252 .filter(id -> acFromMigration.getElements().get(id) == null).toList();
255 private InstantiationResponse migratePrecheckAc(
256 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
258 acToBeUpdated.setPrecheck(true);
259 var copyAc = new AutomationComposition(acToBeUpdated);
260 // Iterate and update the element property values
261 updateElementsProperties(automationComposition, copyAc);
263 // Publish migrate event to the participants
264 supervisionAcHandler.migratePrecheck(copyAc);
266 AcmUtils.setCascadedState(acToBeUpdated, DeployState.DEPLOYED, LockState.LOCKED,
267 SubState.MIGRATION_PRECHECKING);
268 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
270 return createInstantiationResponse(automationCompositionProvider.updateAutomationComposition(acToBeUpdated));
273 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition) {
274 return validateAutomationComposition(automationComposition, automationComposition.getCompositionId());
278 * Validate AutomationComposition.
280 * @param automationComposition AutomationComposition to validate
281 * @param compositionId the composition id
282 * @return the result of validation
284 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition,
285 UUID compositionId) {
287 var result = new BeanValidationResult("AutomationComposition", automationComposition);
288 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
289 if (acDefinitionOpt.isEmpty()) {
290 result.addResult(new ObjectValidationResult("ServiceTemplate", compositionId, ValidationStatus.INVALID,
291 "Commissioned automation composition definition not found"));
294 if (!AcTypeState.PRIMED.equals(acDefinitionOpt.get().getState())) {
295 result.addResult(new ObjectValidationResult("ServiceTemplate.state", acDefinitionOpt.get().getState(),
296 ValidationStatus.INVALID, "Commissioned automation composition definition not primed"));
299 var participantIds = acDefinitionOpt.get().getElementStateMap().values().stream()
300 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
302 participantProvider.verifyParticipantState(participantIds);
304 result.addResult(AcmUtils.validateAutomationComposition(automationComposition,
305 acDefinitionOpt.get().getServiceTemplate(),
306 acRuntimeParameterGroup.getAcmParameters().getToscaCompositionName()));
308 result.addResult(automationCompositionProvider.validateElementIds(automationComposition));
310 if (result.isValid()) {
311 for (var element : automationComposition.getElements().values()) {
312 var name = element.getDefinition().getName();
313 var participantId = acDefinitionOpt.get().getElementStateMap().get(name).getParticipantId();
314 element.setParticipantId(participantId);
322 private void encryptInstanceProperties(AutomationComposition automationComposition, UUID compositionId) {
323 if (encryptionUtils.encryptionEnabled()) {
324 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
325 acDefinitionOpt.ifPresent(acDefinition
326 -> encryptionUtils.findAndEncryptSensitiveData(acDefinition, automationComposition));
331 * Get Automation Composition.
333 * @param compositionId The UUID of the automation composition definition
334 * @param instanceId The UUID of the automation composition instance
335 * @return the Automation Composition
337 @Transactional(readOnly = true)
338 public AutomationComposition getAutomationComposition(@NonNull UUID compositionId, UUID instanceId) {
339 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
340 if (!compositionId.equals(automationComposition.getCompositionId())
341 && !compositionId.equals(automationComposition.getCompositionTargetId())) {
342 throw new PfModelRuntimeException(Status.BAD_REQUEST,
343 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
345 return automationComposition;
349 * Delete the automation composition with the given name and version.
351 * @param compositionId The UUID of the automation composition definition
352 * @param instanceId The UUID of the automation composition instance
353 * @return the result of the deletion
355 public InstantiationResponse deleteAutomationComposition(UUID compositionId, UUID instanceId) {
356 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
357 var acDefinition = getAcDefinition(compositionId, automationComposition);
358 var result = acInstanceStateResolver.resolve(DeployOrder.DELETE,
360 automationComposition.getDeployState(), automationComposition.getLockState(),
361 automationComposition.getSubState(), automationComposition.getStateChangeResult());
362 if (!DeployOrder.DELETE.name().equals(result)) {
363 var msg = String.format(NOT_VALID_ORDER, DeployOrder.DELETE,
364 automationComposition.getDeployState(), automationComposition.getLockState(),
365 automationComposition.getSubState(), automationComposition.getStateChangeResult());
366 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);
368 supervisionAcHandler.delete(automationComposition, acDefinition);
369 return createInstantiationResponse(automationComposition);
373 * Get the requested automation compositions.
375 * @param name the name of the automation composition to get, null for all automation compositions
376 * @param version the version of the automation composition to get, null for all automation compositions
377 * @param pageable the Pageable
378 * @return the automation compositions
380 @Transactional(readOnly = true)
381 public AutomationCompositions getAutomationCompositions(@NonNull final UUID compositionId,
382 final String name, final String version,
383 @NonNull final Pageable pageable) {
384 var automationCompositions = new AutomationCompositions();
385 automationCompositions.setAutomationCompositionList(
386 automationCompositionProvider.getAutomationCompositions(compositionId, name, version, pageable));
388 return automationCompositions;
392 * Handle Composition Instance State.
394 * @param compositionId the compositionId
395 * @param instanceId the instanceId
396 * @param acInstanceStateUpdate the AcInstanceStateUpdate
398 public void compositionInstanceState(UUID compositionId, UUID instanceId,
399 @Valid AcInstanceStateUpdate acInstanceStateUpdate) {
400 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
401 var acDefinition = getAcDefinition(compositionId, automationComposition);
402 var result = acInstanceStateResolver.resolve(acInstanceStateUpdate.getDeployOrder(),
403 acInstanceStateUpdate.getLockOrder(), acInstanceStateUpdate.getSubOrder(),
404 automationComposition.getDeployState(), automationComposition.getLockState(),
405 automationComposition.getSubState(), automationComposition.getStateChangeResult());
408 supervisionAcHandler.deploy(automationComposition, acDefinition);
412 supervisionAcHandler.undeploy(automationComposition, acDefinition);
416 supervisionAcHandler.lock(automationComposition, acDefinition);
420 supervisionAcHandler.unlock(automationComposition, acDefinition);
424 supervisionAcHandler.prepare(automationComposition, acDefinition);
428 supervisionAcHandler.review(automationComposition);
432 var msg = String.format(NOT_VALID_ORDER, acInstanceStateUpdate,
433 automationComposition.getDeployState(), automationComposition.getLockState(),
434 automationComposition.getSubState(), automationComposition.getStateChangeResult());
435 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);
440 * Rollback AC Instance.
442 * @param compositionId The UUID of the automation composition definition
443 * @param instanceId The UUID of the automation composition instance
445 public void rollback(UUID compositionId, UUID instanceId) {
446 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
447 AutomationCompositionProvider.validateInstanceEndpoint(compositionId, automationComposition);
449 if (!DeployOrder.MIGRATION_REVERT.name().equals(acInstanceStateResolver.resolve(
450 DeployOrder.MIGRATION_REVERT, LockOrder.NONE,
451 SubOrder.NONE, automationComposition.getDeployState(), automationComposition.getLockState(),
452 automationComposition.getSubState(), automationComposition.getStateChangeResult()))) {
453 throw new PfModelRuntimeException(Status.BAD_REQUEST, "Invalid state for rollback");
456 var automationCompositionToRollback =
457 automationCompositionProvider.getAutomationCompositionRollback(instanceId);
458 var acToBeUpdated = new AutomationComposition(automationComposition);
459 acToBeUpdated.setCompositionTargetId(automationCompositionToRollback.getCompositionId());
460 acToBeUpdated.setElements(automationCompositionToRollback.getElements().values().stream()
461 .collect(Collectors.toMap(AutomationCompositionElement::getId, AutomationCompositionElement::new)));
463 var acDefinition = acDefinitionProvider.getAcDefinition(acToBeUpdated.getCompositionTargetId());
464 var validationResult =
465 validateAutomationComposition(acToBeUpdated, acToBeUpdated.getCompositionTargetId());
466 if (!validationResult.isValid()) {
467 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
470 updateAcForMigration(acToBeUpdated, acDefinition, DeployState.MIGRATION_REVERTING);
471 var elementsRemoved = getElementRemoved(automationComposition, acToBeUpdated);
472 automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
473 elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
474 supervisionAcHandler.migrate(acToBeUpdated);
477 private List<UUID> updateElementsProperties(AutomationComposition automationComposition,
478 AutomationComposition acToBeUpdated) {
479 for (var element : automationComposition.getElements().entrySet()) {
480 var elementId = element.getKey();
481 var dbAcElement = acToBeUpdated.getElements().get(elementId);
482 // Add additional elements if present for migration
483 if (dbAcElement == null) {
484 LOGGER.info("New Ac element {} added in Migration", elementId);
485 acToBeUpdated.getElements().put(elementId, element.getValue());
487 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
488 var newDefinition = element.getValue().getDefinition().asConceptKey();
489 var dbElementDefinition = dbAcElement.getDefinition().asConceptKey();
490 AutomationCompositionProvider
491 .checkCompatibility(newDefinition, dbElementDefinition, automationComposition.getInstanceId());
492 dbAcElement.setDefinition(element.getValue().getDefinition());
495 // Remove element which is not present in the new Ac instance
496 var elementsRemoved = getElementRemoved(acToBeUpdated, automationComposition);
497 elementsRemoved.forEach(uuid -> acToBeUpdated.getElements().remove(uuid));
499 var validationResult =
500 validateAutomationComposition(acToBeUpdated, automationComposition.getCompositionTargetId());
501 if (!validationResult.isValid()) {
502 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
504 acToBeUpdated.setCompositionTargetId(automationComposition.getCompositionTargetId());
505 return elementsRemoved;
508 private AutomationCompositionDefinition getAcDefinition(UUID compositionId,
509 AutomationComposition automationComposition) {
510 AutomationCompositionProvider.validateInstanceEndpoint(compositionId, automationComposition);
511 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
512 participantProvider.checkRegisteredParticipant(acDefinition);