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.HashMap;
27 import java.util.List;
29 import java.util.UUID;
30 import java.util.stream.Collectors;
31 import lombok.NonNull;
32 import lombok.RequiredArgsConstructor;
33 import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup;
34 import org.onap.policy.clamp.acm.runtime.main.utils.EncryptionUtils;
35 import org.onap.policy.clamp.acm.runtime.supervision.SupervisionAcHandler;
36 import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
37 import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
38 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition;
39 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement;
40 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositions;
41 import org.onap.policy.clamp.models.acm.concepts.DeployState;
42 import org.onap.policy.clamp.models.acm.concepts.LockState;
43 import org.onap.policy.clamp.models.acm.concepts.NodeTemplateState;
44 import org.onap.policy.clamp.models.acm.concepts.ParticipantUtils;
45 import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
46 import org.onap.policy.clamp.models.acm.concepts.SubState;
47 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AcInstanceStateUpdate;
48 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.DeployOrder;
49 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstantiationResponse;
50 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.LockOrder;
51 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.SubOrder;
52 import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvider;
53 import org.onap.policy.clamp.models.acm.persistence.provider.AcInstanceStateResolver;
54 import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
55 import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantProvider;
56 import org.onap.policy.clamp.models.acm.utils.AcmUtils;
57 import org.onap.policy.common.parameters.BeanValidationResult;
58 import org.onap.policy.common.parameters.ObjectValidationResult;
59 import org.onap.policy.common.parameters.ValidationStatus;
60 import org.onap.policy.models.base.PfConceptKey;
61 import org.onap.policy.models.base.PfKey;
62 import org.onap.policy.models.base.PfModelRuntimeException;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65 import org.springframework.data.domain.Pageable;
66 import org.springframework.stereotype.Service;
67 import org.springframework.transaction.annotation.Transactional;
70 * This class is dedicated to the Instantiation of Commissioned automation composition.
74 @RequiredArgsConstructor
75 public class AutomationCompositionInstantiationProvider {
76 private static final String DO_NOT_MATCH = " do not match with ";
77 private static final String ELEMENT_ID_NOT_PRESENT = "Element id not present ";
78 private static final String NOT_VALID_ORDER =
79 "Not valid order %s; DeployState: %s; LockState: %s; SubState: %s; StateChangeResult: %s";
81 private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionInstantiationProvider.class);
83 private final AutomationCompositionProvider automationCompositionProvider;
84 private final AcDefinitionProvider acDefinitionProvider;
85 private final AcInstanceStateResolver acInstanceStateResolver;
86 private final SupervisionAcHandler supervisionAcHandler;
87 private final ParticipantProvider participantProvider;
88 private final AcRuntimeParameterGroup acRuntimeParameterGroup;
89 private final EncryptionUtils encryptionUtils;
92 * Create automation composition.
94 * @param compositionId The UUID of the automation composition definition
95 * @param automationComposition the automation composition
96 * @return the result of the instantiation operation
98 public InstantiationResponse createAutomationComposition(UUID compositionId,
99 AutomationComposition automationComposition) {
100 validateCompositionRequested(compositionId, automationComposition);
101 var checkAutomationCompositionOpt =
102 automationCompositionProvider.findAutomationComposition(automationComposition.getKey().asIdentifier());
103 if (checkAutomationCompositionOpt.isPresent()) {
104 throw new PfModelRuntimeException(Status.BAD_REQUEST,
105 automationComposition.getKey().asIdentifier() + " already defined");
108 var validationResult = validateAutomationComposition(automationComposition);
109 if (!validationResult.isValid()) {
110 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
112 encryptInstanceProperties(automationComposition, compositionId);
113 automationComposition = automationCompositionProvider.createAutomationComposition(automationComposition);
115 return createInstantiationResponse(automationComposition);
118 private InstantiationResponse createInstantiationResponse(AutomationComposition automationComposition) {
119 var response = new InstantiationResponse();
120 response.setInstanceId(automationComposition.getInstanceId());
121 response.setAffectedAutomationComposition(automationComposition.getKey().asIdentifier());
126 * Update automation composition.
128 * @param compositionId The UUID of the automation composition definition
129 * @param automationComposition the automation composition
130 * @return the result of the update
132 public InstantiationResponse updateAutomationComposition(UUID compositionId,
133 AutomationComposition automationComposition) {
134 var instanceId = automationComposition.getInstanceId();
135 var acToUpdate = automationCompositionProvider.getAutomationComposition(instanceId);
136 validateCompositionRequested(compositionId, acToUpdate);
137 if (DeployState.UNDEPLOYED.equals(acToUpdate.getDeployState())) {
138 acToUpdate.setElements(automationComposition.getElements());
139 acToUpdate.setName(automationComposition.getName());
140 acToUpdate.setVersion(automationComposition.getVersion());
141 acToUpdate.setDescription(automationComposition.getDescription());
142 acToUpdate.setDerivedFrom(automationComposition.getDerivedFrom());
143 var validationResult = validateAutomationComposition(acToUpdate);
144 if (!validationResult.isValid()) {
145 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
147 encryptInstanceProperties(acToUpdate, compositionId);
148 automationComposition = automationCompositionProvider.updateAutomationComposition(acToUpdate);
149 return createInstantiationResponse(automationComposition);
153 var deployOrder = DeployOrder.UPDATE;
154 var subOrder = SubOrder.NONE;
156 if (automationComposition.getCompositionTargetId() != null) {
158 if (Boolean.TRUE.equals(automationComposition.getPrecheck())) {
159 subOrder = SubOrder.MIGRATE_PRECHECK;
160 deployOrder = DeployOrder.NONE;
162 deployOrder = DeployOrder.MIGRATE;
165 var result = acInstanceStateResolver.resolve(deployOrder, LockOrder.NONE, subOrder,
166 acToUpdate.getDeployState(), acToUpdate.getLockState(), acToUpdate.getSubState(),
167 acToUpdate.getStateChangeResult());
168 return switch (result) {
169 case "UPDATE" -> updateDeployedAutomationComposition(automationComposition, acToUpdate);
171 case "MIGRATE" -> migrateAutomationComposition(automationComposition, acToUpdate);
173 case "MIGRATE_PRECHECK" -> migratePrecheckAc(automationComposition, acToUpdate);
175 default -> throw new PfModelRuntimeException(Status.BAD_REQUEST,
176 "Not allowed to " + deployOrder + " in the state " + acToUpdate.getDeployState());
181 * Update deployed AC Element properties.
183 * @param automationComposition the automation composition
184 * @param acToBeUpdated the composition to be updated
185 * @return the result of the update
187 private InstantiationResponse updateDeployedAutomationComposition(
188 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
189 // save copy in case of a rollback
190 automationCompositionProvider.copyAcElementsBeforeUpdate(acToBeUpdated);
192 // Iterate and update the element property values
193 for (var element : automationComposition.getElements().entrySet()) {
194 var elementId = element.getKey();
195 var dbAcElement = acToBeUpdated.getElements().get(elementId);
196 if (dbAcElement == null) {
197 throw new PfModelRuntimeException(Status.BAD_REQUEST, ELEMENT_ID_NOT_PRESENT + elementId);
199 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
202 var validationResult = validateAutomationComposition(acToBeUpdated);
203 if (!validationResult.isValid()) {
204 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
206 updateAcForProperties(acToBeUpdated);
208 var acToPublish = new AutomationComposition(acToBeUpdated);
210 encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionId());
212 automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
213 // Publish property update event to the participants
214 supervisionAcHandler.update(acToPublish);
215 return createInstantiationResponse(automationComposition);
218 private InstantiationResponse migrateAutomationComposition(
219 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
221 if (!DeployState.DEPLOYED.equals(acToBeUpdated.getDeployState())) {
222 throw new PfModelRuntimeException(Status.BAD_REQUEST,
223 "Not allowed to migrate in the state " + acToBeUpdated.getDeployState());
225 // make copy for rollback
226 automationCompositionProvider.copyAcElementsBeforeUpdate(acToBeUpdated);
228 // Iterate and update the element property values
229 var elementsRemoved = updateElementsProperties(automationComposition, acToBeUpdated);
230 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionTargetId());
232 updateAcForMigration(acToBeUpdated, acDefinition);
234 var acToPublish = new AutomationComposition(acToBeUpdated);
236 encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionTargetId());
238 var ac = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
239 elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
241 // Publish migrate event to the participants
242 supervisionAcHandler.migrate(acToPublish);
243 return createInstantiationResponse(ac);
246 private void updateAcForMigration(AutomationComposition acToBeUpdated,
247 AutomationCompositionDefinition acDefinition) {
248 AcmUtils.setCascadedState(acToBeUpdated, DeployState.MIGRATING, LockState.LOCKED);
249 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
250 var stage = ParticipantUtils.getFirstStage(acToBeUpdated, acDefinition.getServiceTemplate());
251 acToBeUpdated.setPhase(stage);
254 private void updateAcForProperties(AutomationComposition acToBeUpdated) {
255 AcmUtils.setCascadedState(acToBeUpdated, DeployState.UPDATING, acToBeUpdated.getLockState());
256 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
259 private List<UUID> getElementRemoved(AutomationComposition acFromDb, AutomationComposition acFromMigration) {
260 return acFromDb.getElements().keySet().stream()
261 .filter(id -> acFromMigration.getElements().get(id) == null).toList();
264 void checkCompatibility(PfConceptKey newDefinition, PfConceptKey dbElementDefinition,
266 var compatibility = newDefinition.getCompatibility(dbElementDefinition);
267 if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) {
268 throw new PfModelRuntimeException(Status.BAD_REQUEST,
269 dbElementDefinition + " is not compatible with " + newDefinition);
271 if (PfKey.Compatibility.MAJOR.equals(compatibility) || PfKey.Compatibility.MINOR
272 .equals(compatibility)) {
273 LOGGER.warn("Migrate {}: Version {} has {} compatibility with {} ", instanceId, newDefinition,
274 compatibility, dbElementDefinition);
278 private InstantiationResponse migratePrecheckAc(
279 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
281 acToBeUpdated.setPrecheck(true);
282 var copyAc = new AutomationComposition(acToBeUpdated);
283 // Iterate and update the element property values
284 updateElementsProperties(automationComposition, copyAc);
286 // Publish migrate event to the participants
287 supervisionAcHandler.migratePrecheck(copyAc);
289 AcmUtils.setCascadedState(acToBeUpdated, DeployState.DEPLOYED, LockState.LOCKED,
290 SubState.MIGRATION_PRECHECKING);
291 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
293 return createInstantiationResponse(automationCompositionProvider.updateAutomationComposition(acToBeUpdated));
296 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition) {
297 return validateAutomationComposition(automationComposition, automationComposition.getCompositionId());
301 * Validate AutomationComposition.
303 * @param automationComposition AutomationComposition to validate
304 * @param compositionId the composition id
305 * @return the result of validation
307 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition,
308 UUID compositionId) {
310 var result = new BeanValidationResult("AutomationComposition", automationComposition);
311 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
312 if (acDefinitionOpt.isEmpty()) {
313 result.addResult(new ObjectValidationResult("ServiceTemplate", compositionId, ValidationStatus.INVALID,
314 "Commissioned automation composition definition not found"));
317 if (!AcTypeState.PRIMED.equals(acDefinitionOpt.get().getState())) {
318 result.addResult(new ObjectValidationResult("ServiceTemplate.state", acDefinitionOpt.get().getState(),
319 ValidationStatus.INVALID, "Commissioned automation composition definition not primed"));
322 var participantIds = acDefinitionOpt.get().getElementStateMap().values().stream()
323 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
325 participantProvider.verifyParticipantState(participantIds);
327 result.addResult(AcmUtils.validateAutomationComposition(automationComposition,
328 acDefinitionOpt.get().getServiceTemplate(),
329 acRuntimeParameterGroup.getAcmParameters().getToscaCompositionName()));
331 result.addResult(automationCompositionProvider.validateElementIds(automationComposition));
333 if (result.isValid()) {
334 for (var element : automationComposition.getElements().values()) {
335 var name = element.getDefinition().getName();
336 var participantId = acDefinitionOpt.get().getElementStateMap().get(name).getParticipantId();
337 element.setParticipantId(participantId);
345 private void encryptInstanceProperties(AutomationComposition automationComposition, UUID compositionId) {
346 if (encryptionUtils.encryptionEnabled()) {
347 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
348 acDefinitionOpt.ifPresent(acDefinition
349 -> encryptionUtils.findAndEncryptSensitiveData(acDefinition, automationComposition));
354 * Get Automation Composition.
356 * @param compositionId The UUID of the automation composition definition
357 * @param instanceId The UUID of the automation composition instance
358 * @return the Automation Composition
360 @Transactional(readOnly = true)
361 public AutomationComposition getAutomationComposition(@NonNull UUID compositionId, UUID instanceId) {
362 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
363 if (!compositionId.equals(automationComposition.getCompositionId())
364 && !compositionId.equals(automationComposition.getCompositionTargetId())) {
365 throw new PfModelRuntimeException(Status.BAD_REQUEST,
366 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
368 return automationComposition;
372 * Delete the automation composition with the given name and version.
374 * @param compositionId The UUID of the automation composition definition
375 * @param instanceId The UUID of the automation composition instance
376 * @return the result of the deletion
378 public InstantiationResponse deleteAutomationComposition(UUID compositionId, UUID instanceId) {
379 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
380 var acDefinition = getAcDefinition(compositionId, automationComposition);
381 var result = acInstanceStateResolver.resolve(DeployOrder.DELETE,
383 automationComposition.getDeployState(), automationComposition.getLockState(),
384 automationComposition.getSubState(), automationComposition.getStateChangeResult());
385 if (!DeployOrder.DELETE.name().equals(result)) {
386 var msg = String.format(NOT_VALID_ORDER, DeployOrder.DELETE,
387 automationComposition.getDeployState(), automationComposition.getLockState(),
388 automationComposition.getSubState(), automationComposition.getStateChangeResult());
389 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);
391 supervisionAcHandler.delete(automationComposition, acDefinition);
392 return createInstantiationResponse(automationComposition);
396 * Get the requested automation compositions.
398 * @param name the name of the automation composition to get, null for all automation compositions
399 * @param version the version of the automation composition to get, null for all automation compositions
400 * @param pageable the Pageable
401 * @return the automation compositions
403 @Transactional(readOnly = true)
404 public AutomationCompositions getAutomationCompositions(@NonNull final UUID compositionId,
405 final String name, final String version,
406 @NonNull final Pageable pageable) {
407 var automationCompositions = new AutomationCompositions();
408 automationCompositions.setAutomationCompositionList(
409 automationCompositionProvider.getAutomationCompositions(compositionId, name, version, pageable));
411 return automationCompositions;
415 * Handle Composition Instance State.
417 * @param compositionId the compositionId
418 * @param instanceId the instanceId
419 * @param acInstanceStateUpdate the AcInstanceStateUpdate
421 public void compositionInstanceState(UUID compositionId, UUID instanceId,
422 @Valid AcInstanceStateUpdate acInstanceStateUpdate) {
423 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
424 var acDefinition = getAcDefinition(compositionId, automationComposition);
425 var result = acInstanceStateResolver.resolve(acInstanceStateUpdate.getDeployOrder(),
426 acInstanceStateUpdate.getLockOrder(), acInstanceStateUpdate.getSubOrder(),
427 automationComposition.getDeployState(), automationComposition.getLockState(),
428 automationComposition.getSubState(), automationComposition.getStateChangeResult());
431 supervisionAcHandler.deploy(automationComposition, acDefinition);
435 supervisionAcHandler.undeploy(automationComposition, acDefinition);
439 supervisionAcHandler.lock(automationComposition, acDefinition);
443 supervisionAcHandler.unlock(automationComposition, acDefinition);
447 supervisionAcHandler.prepare(automationComposition, acDefinition);
451 supervisionAcHandler.review(automationComposition);
455 var msg = String.format(NOT_VALID_ORDER, acInstanceStateUpdate,
456 automationComposition.getDeployState(), automationComposition.getLockState(),
457 automationComposition.getSubState(), automationComposition.getStateChangeResult());
458 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);
463 * Rollback AC Instance.
465 * @param instanceId the instanceId
466 * @return the instantiation response
468 public InstantiationResponse rollback(UUID instanceId) {
469 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
470 var automationCompositionToRollback =
471 automationCompositionProvider.getAutomationCompositionRollback(instanceId.toString());
473 if (DeployState.DEPLOYED.equals(automationComposition.getDeployState())
474 && SubState.NONE.equals(automationComposition.getSubState())
475 && StateChangeResult.NO_ERROR.equals(automationComposition.getStateChangeResult())) {
476 automationComposition.setCompositionId(UUID.fromString(automationCompositionToRollback.getCompositionId()));
477 Map<UUID, AutomationCompositionElement> elements = new HashMap<>();
478 automationCompositionToRollback.getElements().forEach((String uuid, Object acElement) ->
479 elements.put(UUID.fromString(uuid), (AutomationCompositionElement) acElement));
480 automationComposition.setElements(elements);
481 automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
482 AcmUtils.setCascadedState(automationComposition, DeployState.MIGRATION_REVERTING, LockState.LOCKED);
483 automationCompositionProvider.updateAutomationComposition(automationComposition);
484 return createInstantiationResponse(automationComposition);
486 throw new PfModelRuntimeException(Status.BAD_REQUEST, "Invalid state for rollback");
490 private List<UUID> updateElementsProperties(AutomationComposition automationComposition,
491 AutomationComposition acToBeUpdated) {
492 for (var element : automationComposition.getElements().entrySet()) {
493 var elementId = element.getKey();
494 var dbAcElement = acToBeUpdated.getElements().get(elementId);
495 // Add additional elements if present for migration
496 if (dbAcElement == null) {
497 LOGGER.info("New Ac element {} added in Migration", elementId);
498 acToBeUpdated.getElements().put(elementId, element.getValue());
500 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
501 var newDefinition = element.getValue().getDefinition().asConceptKey();
502 var dbElementDefinition = dbAcElement.getDefinition().asConceptKey();
503 checkCompatibility(newDefinition, dbElementDefinition, automationComposition.getInstanceId());
504 dbAcElement.setDefinition(element.getValue().getDefinition());
507 // Remove element which is not present in the new Ac instance
508 var elementsRemoved = getElementRemoved(acToBeUpdated, automationComposition);
509 elementsRemoved.forEach(uuid -> acToBeUpdated.getElements().remove(uuid));
511 var validationResult =
512 validateAutomationComposition(acToBeUpdated, automationComposition.getCompositionTargetId());
513 if (!validationResult.isValid()) {
514 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
516 acToBeUpdated.setCompositionTargetId(automationComposition.getCompositionTargetId());
517 return elementsRemoved;
520 private static void validateCompositionRequested(UUID compositionId,
521 AutomationComposition automationComposition) {
522 if (!compositionId.equals(automationComposition.getCompositionId())) {
523 throw new PfModelRuntimeException(Status.BAD_REQUEST,
524 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
528 private AutomationCompositionDefinition getAcDefinition(UUID compositionId,
529 AutomationComposition automationComposition) {
530 validateCompositionRequested(compositionId, automationComposition);
531 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
533 var participantIds = acDefinition.getElementStateMap().values().stream()
534 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
536 participantProvider.verifyParticipantState(participantIds);