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.PfConceptKey;
59 import org.onap.policy.models.base.PfKey;
60 import org.onap.policy.models.base.PfModelRuntimeException;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63 import org.springframework.data.domain.Pageable;
64 import org.springframework.stereotype.Service;
65 import org.springframework.transaction.annotation.Transactional;
68 * This class is dedicated to the Instantiation of Commissioned automation composition.
72 @RequiredArgsConstructor
73 public class AutomationCompositionInstantiationProvider {
74 private static final String DO_NOT_MATCH = " do not match with ";
75 private static final String ELEMENT_ID_NOT_PRESENT = "Element id not present ";
76 private static final String NOT_VALID_ORDER =
77 "Not valid order %s; DeployState: %s; LockState: %s; SubState: %s; StateChangeResult: %s";
79 private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionInstantiationProvider.class);
81 private final AutomationCompositionProvider automationCompositionProvider;
82 private final AcDefinitionProvider acDefinitionProvider;
83 private final AcInstanceStateResolver acInstanceStateResolver;
84 private final SupervisionAcHandler supervisionAcHandler;
85 private final ParticipantProvider participantProvider;
86 private final AcRuntimeParameterGroup acRuntimeParameterGroup;
87 private final EncryptionUtils encryptionUtils;
90 * Create automation composition.
92 * @param compositionId The UUID of the automation composition definition
93 * @param automationComposition the automation composition
94 * @return the result of the instantiation operation
96 public InstantiationResponse createAutomationComposition(UUID compositionId,
97 AutomationComposition automationComposition) {
98 validateCompositionRequested(compositionId, automationComposition);
99 var checkAutomationCompositionOpt =
100 automationCompositionProvider.findAutomationComposition(automationComposition.getKey().asIdentifier());
101 if (checkAutomationCompositionOpt.isPresent()) {
102 throw new PfModelRuntimeException(Status.BAD_REQUEST,
103 automationComposition.getKey().asIdentifier() + " already defined");
106 var validationResult = validateAutomationComposition(automationComposition);
107 if (!validationResult.isValid()) {
108 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
110 encryptInstanceProperties(automationComposition, compositionId);
111 automationComposition = automationCompositionProvider.createAutomationComposition(automationComposition);
113 return createInstantiationResponse(automationComposition);
116 private InstantiationResponse createInstantiationResponse(AutomationComposition automationComposition) {
117 var response = new InstantiationResponse();
118 response.setInstanceId(automationComposition.getInstanceId());
119 response.setAffectedAutomationComposition(automationComposition.getKey().asIdentifier());
124 * Update automation composition.
126 * @param compositionId The UUID of the automation composition definition
127 * @param automationComposition the automation composition
128 * @return the result of the update
130 public InstantiationResponse updateAutomationComposition(UUID compositionId,
131 AutomationComposition automationComposition) {
132 var instanceId = automationComposition.getInstanceId();
133 var acToUpdate = automationCompositionProvider.getAutomationComposition(instanceId);
134 validateCompositionRequested(compositionId, acToUpdate);
135 if (DeployState.UNDEPLOYED.equals(acToUpdate.getDeployState())) {
136 acToUpdate.setElements(automationComposition.getElements());
137 acToUpdate.setName(automationComposition.getName());
138 acToUpdate.setVersion(automationComposition.getVersion());
139 acToUpdate.setDescription(automationComposition.getDescription());
140 acToUpdate.setDerivedFrom(automationComposition.getDerivedFrom());
141 var validationResult = validateAutomationComposition(acToUpdate);
142 if (!validationResult.isValid()) {
143 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
145 encryptInstanceProperties(acToUpdate, compositionId);
146 automationComposition = automationCompositionProvider.updateAutomationComposition(acToUpdate);
147 return createInstantiationResponse(automationComposition);
151 var deployOrder = DeployOrder.UPDATE;
152 var subOrder = SubOrder.NONE;
154 if (automationComposition.getCompositionTargetId() != null) {
156 if (Boolean.TRUE.equals(automationComposition.getPrecheck())) {
157 subOrder = SubOrder.MIGRATE_PRECHECK;
158 deployOrder = DeployOrder.NONE;
160 deployOrder = DeployOrder.MIGRATE;
163 var result = acInstanceStateResolver.resolve(deployOrder, LockOrder.NONE, subOrder,
164 acToUpdate.getDeployState(), acToUpdate.getLockState(), acToUpdate.getSubState(),
165 acToUpdate.getStateChangeResult());
166 return switch (result) {
167 case "UPDATE" -> updateDeployedAutomationComposition(automationComposition, acToUpdate);
169 case "MIGRATE" -> migrateAutomationComposition(automationComposition, acToUpdate);
171 case "MIGRATE_PRECHECK" -> migratePrecheckAc(automationComposition, acToUpdate);
173 default -> throw new PfModelRuntimeException(Status.BAD_REQUEST,
174 "Not allowed to " + deployOrder + " in the state " + acToUpdate.getDeployState());
179 * Update deployed AC Element properties.
181 * @param automationComposition the automation composition
182 * @param acToBeUpdated the composition to be updated
183 * @return the result of the update
185 private InstantiationResponse updateDeployedAutomationComposition(
186 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
187 // save copy in case of a rollback
188 automationCompositionProvider.copyAcElementsBeforeUpdate(acToBeUpdated);
190 // Iterate and update the element property values
191 for (var element : automationComposition.getElements().entrySet()) {
192 var elementId = element.getKey();
193 var dbAcElement = acToBeUpdated.getElements().get(elementId);
194 if (dbAcElement == null) {
195 throw new PfModelRuntimeException(Status.BAD_REQUEST, ELEMENT_ID_NOT_PRESENT + elementId);
197 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
200 var validationResult = validateAutomationComposition(acToBeUpdated);
201 if (!validationResult.isValid()) {
202 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
204 updateAcForProperties(acToBeUpdated);
206 var acToPublish = new AutomationComposition(acToBeUpdated);
208 encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionId());
210 automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
211 // Publish property update event to the participants
212 supervisionAcHandler.update(acToPublish);
213 return createInstantiationResponse(automationComposition);
216 private InstantiationResponse migrateAutomationComposition(
217 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
219 if (!DeployState.DEPLOYED.equals(acToBeUpdated.getDeployState())) {
220 throw new PfModelRuntimeException(Status.BAD_REQUEST,
221 "Not allowed to migrate in the state " + acToBeUpdated.getDeployState());
223 // make copy for rollback
224 automationCompositionProvider.copyAcElementsBeforeUpdate(acToBeUpdated);
226 // Iterate and update the element property values
227 var elementsRemoved = updateElementsProperties(automationComposition, acToBeUpdated);
228 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionTargetId());
230 updateAcForMigration(acToBeUpdated, acDefinition, DeployState.MIGRATING);
232 var acToPublish = new AutomationComposition(acToBeUpdated);
234 encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionTargetId());
236 var ac = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
237 elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
239 // Publish migrate event to the participants
240 supervisionAcHandler.migrate(acToPublish);
241 return createInstantiationResponse(ac);
244 private void updateAcForMigration(AutomationComposition acToBeUpdated,
245 AutomationCompositionDefinition acDefinition, DeployState deployState) {
246 AcmUtils.setCascadedState(acToBeUpdated, deployState, LockState.LOCKED);
247 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
248 var stage = ParticipantUtils.getFirstStage(acToBeUpdated, acDefinition.getServiceTemplate());
249 acToBeUpdated.setPhase(stage);
252 private void updateAcForProperties(AutomationComposition acToBeUpdated) {
253 AcmUtils.setCascadedState(acToBeUpdated, DeployState.UPDATING, acToBeUpdated.getLockState());
254 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
257 private List<UUID> getElementRemoved(AutomationComposition acFromDb, AutomationComposition acFromMigration) {
258 return acFromDb.getElements().keySet().stream()
259 .filter(id -> acFromMigration.getElements().get(id) == null).toList();
262 void checkCompatibility(PfConceptKey newDefinition, PfConceptKey dbElementDefinition,
264 var compatibility = newDefinition.getCompatibility(dbElementDefinition);
265 if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) {
266 throw new PfModelRuntimeException(Status.BAD_REQUEST,
267 dbElementDefinition + " is not compatible with " + newDefinition);
269 if (PfKey.Compatibility.MAJOR.equals(compatibility) || PfKey.Compatibility.MINOR
270 .equals(compatibility)) {
271 LOGGER.warn("Migrate {}: Version {} has {} compatibility with {} ", instanceId, newDefinition,
272 compatibility, dbElementDefinition);
276 private InstantiationResponse migratePrecheckAc(
277 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
279 acToBeUpdated.setPrecheck(true);
280 var copyAc = new AutomationComposition(acToBeUpdated);
281 // Iterate and update the element property values
282 updateElementsProperties(automationComposition, copyAc);
284 // Publish migrate event to the participants
285 supervisionAcHandler.migratePrecheck(copyAc);
287 AcmUtils.setCascadedState(acToBeUpdated, DeployState.DEPLOYED, LockState.LOCKED,
288 SubState.MIGRATION_PRECHECKING);
289 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
291 return createInstantiationResponse(automationCompositionProvider.updateAutomationComposition(acToBeUpdated));
294 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition) {
295 return validateAutomationComposition(automationComposition, automationComposition.getCompositionId());
299 * Validate AutomationComposition.
301 * @param automationComposition AutomationComposition to validate
302 * @param compositionId the composition id
303 * @return the result of validation
305 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition,
306 UUID compositionId) {
308 var result = new BeanValidationResult("AutomationComposition", automationComposition);
309 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
310 if (acDefinitionOpt.isEmpty()) {
311 result.addResult(new ObjectValidationResult("ServiceTemplate", compositionId, ValidationStatus.INVALID,
312 "Commissioned automation composition definition not found"));
315 if (!AcTypeState.PRIMED.equals(acDefinitionOpt.get().getState())) {
316 result.addResult(new ObjectValidationResult("ServiceTemplate.state", acDefinitionOpt.get().getState(),
317 ValidationStatus.INVALID, "Commissioned automation composition definition not primed"));
320 var participantIds = acDefinitionOpt.get().getElementStateMap().values().stream()
321 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
323 participantProvider.verifyParticipantState(participantIds);
325 result.addResult(AcmUtils.validateAutomationComposition(automationComposition,
326 acDefinitionOpt.get().getServiceTemplate(),
327 acRuntimeParameterGroup.getAcmParameters().getToscaCompositionName()));
329 result.addResult(automationCompositionProvider.validateElementIds(automationComposition));
331 if (result.isValid()) {
332 for (var element : automationComposition.getElements().values()) {
333 var name = element.getDefinition().getName();
334 var participantId = acDefinitionOpt.get().getElementStateMap().get(name).getParticipantId();
335 element.setParticipantId(participantId);
343 private void encryptInstanceProperties(AutomationComposition automationComposition, UUID compositionId) {
344 if (encryptionUtils.encryptionEnabled()) {
345 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
346 acDefinitionOpt.ifPresent(acDefinition
347 -> encryptionUtils.findAndEncryptSensitiveData(acDefinition, automationComposition));
352 * Get Automation Composition.
354 * @param compositionId The UUID of the automation composition definition
355 * @param instanceId The UUID of the automation composition instance
356 * @return the Automation Composition
358 @Transactional(readOnly = true)
359 public AutomationComposition getAutomationComposition(@NonNull UUID compositionId, UUID instanceId) {
360 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
361 if (!compositionId.equals(automationComposition.getCompositionId())
362 && !compositionId.equals(automationComposition.getCompositionTargetId())) {
363 throw new PfModelRuntimeException(Status.BAD_REQUEST,
364 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
366 return automationComposition;
370 * Delete the automation composition with the given name and version.
372 * @param compositionId The UUID of the automation composition definition
373 * @param instanceId The UUID of the automation composition instance
374 * @return the result of the deletion
376 public InstantiationResponse deleteAutomationComposition(UUID compositionId, UUID instanceId) {
377 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
378 var acDefinition = getAcDefinition(compositionId, automationComposition);
379 var result = acInstanceStateResolver.resolve(DeployOrder.DELETE,
381 automationComposition.getDeployState(), automationComposition.getLockState(),
382 automationComposition.getSubState(), automationComposition.getStateChangeResult());
383 if (!DeployOrder.DELETE.name().equals(result)) {
384 var msg = String.format(NOT_VALID_ORDER, DeployOrder.DELETE,
385 automationComposition.getDeployState(), automationComposition.getLockState(),
386 automationComposition.getSubState(), automationComposition.getStateChangeResult());
387 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);
389 supervisionAcHandler.delete(automationComposition, acDefinition);
390 return createInstantiationResponse(automationComposition);
394 * Get the requested automation compositions.
396 * @param name the name of the automation composition to get, null for all automation compositions
397 * @param version the version of the automation composition to get, null for all automation compositions
398 * @param pageable the Pageable
399 * @return the automation compositions
401 @Transactional(readOnly = true)
402 public AutomationCompositions getAutomationCompositions(@NonNull final UUID compositionId,
403 final String name, final String version,
404 @NonNull final Pageable pageable) {
405 var automationCompositions = new AutomationCompositions();
406 automationCompositions.setAutomationCompositionList(
407 automationCompositionProvider.getAutomationCompositions(compositionId, name, version, pageable));
409 return automationCompositions;
413 * Handle Composition Instance State.
415 * @param compositionId the compositionId
416 * @param instanceId the instanceId
417 * @param acInstanceStateUpdate the AcInstanceStateUpdate
419 public void compositionInstanceState(UUID compositionId, UUID instanceId,
420 @Valid AcInstanceStateUpdate acInstanceStateUpdate) {
421 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
422 var acDefinition = getAcDefinition(compositionId, automationComposition);
423 var result = acInstanceStateResolver.resolve(acInstanceStateUpdate.getDeployOrder(),
424 acInstanceStateUpdate.getLockOrder(), acInstanceStateUpdate.getSubOrder(),
425 automationComposition.getDeployState(), automationComposition.getLockState(),
426 automationComposition.getSubState(), automationComposition.getStateChangeResult());
429 supervisionAcHandler.deploy(automationComposition, acDefinition);
433 supervisionAcHandler.undeploy(automationComposition, acDefinition);
437 supervisionAcHandler.lock(automationComposition, acDefinition);
441 supervisionAcHandler.unlock(automationComposition, acDefinition);
445 supervisionAcHandler.prepare(automationComposition, acDefinition);
449 supervisionAcHandler.review(automationComposition);
453 var msg = String.format(NOT_VALID_ORDER, acInstanceStateUpdate,
454 automationComposition.getDeployState(), automationComposition.getLockState(),
455 automationComposition.getSubState(), automationComposition.getStateChangeResult());
456 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);
461 * Rollback AC Instance.
463 * @param compositionId The UUID of the automation composition definition
464 * @param instanceId The UUID of the automation composition instance
466 public void rollback(UUID compositionId, UUID instanceId) {
467 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
468 validateCompositionRequested(compositionId, automationComposition);
470 if (!DeployOrder.MIGRATION_REVERT.name().equals(acInstanceStateResolver.resolve(
471 DeployOrder.MIGRATION_REVERT, LockOrder.NONE,
472 SubOrder.NONE, automationComposition.getDeployState(), automationComposition.getLockState(),
473 automationComposition.getSubState(), automationComposition.getStateChangeResult()))) {
474 throw new PfModelRuntimeException(Status.BAD_REQUEST, "Invalid state for rollback");
477 var automationCompositionToRollback =
478 automationCompositionProvider.getAutomationCompositionRollback(instanceId);
479 var acToBeUpdated = new AutomationComposition(automationComposition);
480 acToBeUpdated.setCompositionTargetId(automationCompositionToRollback.getCompositionId());
481 acToBeUpdated.setElements(automationCompositionToRollback.getElements().values().stream()
482 .collect(Collectors.toMap(AutomationCompositionElement::getId, AutomationCompositionElement::new)));
484 var acDefinition = acDefinitionProvider.getAcDefinition(acToBeUpdated.getCompositionTargetId());
485 var validationResult =
486 validateAutomationComposition(acToBeUpdated, acToBeUpdated.getCompositionTargetId());
487 if (!validationResult.isValid()) {
488 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
491 updateAcForMigration(acToBeUpdated, acDefinition, DeployState.MIGRATION_REVERTING);
492 var elementsRemoved = getElementRemoved(automationComposition, acToBeUpdated);
493 automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
494 elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
495 supervisionAcHandler.migrate(acToBeUpdated);
498 private List<UUID> updateElementsProperties(AutomationComposition automationComposition,
499 AutomationComposition acToBeUpdated) {
500 for (var element : automationComposition.getElements().entrySet()) {
501 var elementId = element.getKey();
502 var dbAcElement = acToBeUpdated.getElements().get(elementId);
503 // Add additional elements if present for migration
504 if (dbAcElement == null) {
505 LOGGER.info("New Ac element {} added in Migration", elementId);
506 acToBeUpdated.getElements().put(elementId, element.getValue());
508 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
509 var newDefinition = element.getValue().getDefinition().asConceptKey();
510 var dbElementDefinition = dbAcElement.getDefinition().asConceptKey();
511 checkCompatibility(newDefinition, dbElementDefinition, automationComposition.getInstanceId());
512 dbAcElement.setDefinition(element.getValue().getDefinition());
515 // Remove element which is not present in the new Ac instance
516 var elementsRemoved = getElementRemoved(acToBeUpdated, automationComposition);
517 elementsRemoved.forEach(uuid -> acToBeUpdated.getElements().remove(uuid));
519 var validationResult =
520 validateAutomationComposition(acToBeUpdated, automationComposition.getCompositionTargetId());
521 if (!validationResult.isValid()) {
522 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
524 acToBeUpdated.setCompositionTargetId(automationComposition.getCompositionTargetId());
525 return elementsRemoved;
528 private static void validateCompositionRequested(UUID compositionId,
529 AutomationComposition automationComposition) {
530 if (!compositionId.equals(automationComposition.getCompositionId())) {
531 throw new PfModelRuntimeException(Status.BAD_REQUEST,
532 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
536 private AutomationCompositionDefinition getAcDefinition(UUID compositionId,
537 AutomationComposition automationComposition) {
538 validateCompositionRequested(compositionId, automationComposition);
539 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
541 var participantIds = acDefinition.getElementStateMap().values().stream()
542 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
544 participantProvider.verifyParticipantState(participantIds);