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.AutomationCompositions;
38 import org.onap.policy.clamp.models.acm.concepts.DeployState;
39 import org.onap.policy.clamp.models.acm.concepts.LockState;
40 import org.onap.policy.clamp.models.acm.concepts.NodeTemplateState;
41 import org.onap.policy.clamp.models.acm.concepts.ParticipantUtils;
42 import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
43 import org.onap.policy.clamp.models.acm.concepts.SubState;
44 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AcInstanceStateUpdate;
45 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.DeployOrder;
46 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstantiationResponse;
47 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.LockOrder;
48 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.SubOrder;
49 import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvider;
50 import org.onap.policy.clamp.models.acm.persistence.provider.AcInstanceStateResolver;
51 import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
52 import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantProvider;
53 import org.onap.policy.clamp.models.acm.utils.AcmUtils;
54 import org.onap.policy.common.parameters.BeanValidationResult;
55 import org.onap.policy.common.parameters.ObjectValidationResult;
56 import org.onap.policy.common.parameters.ValidationStatus;
57 import org.onap.policy.models.base.PfConceptKey;
58 import org.onap.policy.models.base.PfKey;
59 import org.onap.policy.models.base.PfModelRuntimeException;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62 import org.springframework.data.domain.Pageable;
63 import org.springframework.stereotype.Service;
64 import org.springframework.transaction.annotation.Transactional;
67 * This class is dedicated to the Instantiation of Commissioned automation composition.
71 @RequiredArgsConstructor
72 public class AutomationCompositionInstantiationProvider {
73 private static final String DO_NOT_MATCH = " do not match with ";
74 private static final String ELEMENT_ID_NOT_PRESENT = "Element id not present ";
75 private static final String NOT_VALID_ORDER =
76 "Not valid order %s; DeployState: %s; LockState: %s; SubState: %s; StateChangeResult: %s";
78 private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionInstantiationProvider.class);
80 private final AutomationCompositionProvider automationCompositionProvider;
81 private final AcDefinitionProvider acDefinitionProvider;
82 private final AcInstanceStateResolver acInstanceStateResolver;
83 private final SupervisionAcHandler supervisionAcHandler;
84 private final ParticipantProvider participantProvider;
85 private final AcRuntimeParameterGroup acRuntimeParameterGroup;
86 private final EncryptionUtils encryptionUtils;
89 * Create automation composition.
91 * @param compositionId The UUID of the automation composition definition
92 * @param automationComposition the automation composition
93 * @return the result of the instantiation operation
95 public InstantiationResponse createAutomationComposition(UUID compositionId,
96 AutomationComposition automationComposition) {
97 validateCompositionRequested(compositionId, automationComposition);
98 var checkAutomationCompositionOpt =
99 automationCompositionProvider.findAutomationComposition(automationComposition.getKey().asIdentifier());
100 if (checkAutomationCompositionOpt.isPresent()) {
101 throw new PfModelRuntimeException(Status.BAD_REQUEST,
102 automationComposition.getKey().asIdentifier() + " already defined");
105 var validationResult = validateAutomationComposition(automationComposition);
106 if (!validationResult.isValid()) {
107 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
109 encryptInstanceProperties(automationComposition, compositionId);
110 automationComposition = automationCompositionProvider.createAutomationComposition(automationComposition);
112 return createInstantiationResponse(automationComposition);
115 private InstantiationResponse createInstantiationResponse(AutomationComposition automationComposition) {
116 var response = new InstantiationResponse();
117 response.setInstanceId(automationComposition.getInstanceId());
118 response.setAffectedAutomationComposition(automationComposition.getKey().asIdentifier());
123 * Update automation composition.
125 * @param compositionId The UUID of the automation composition definition
126 * @param automationComposition the automation composition
127 * @return the result of the update
129 public InstantiationResponse updateAutomationComposition(UUID compositionId,
130 AutomationComposition automationComposition) {
131 var instanceId = automationComposition.getInstanceId();
132 var acToUpdate = automationCompositionProvider.getAutomationComposition(instanceId);
133 validateCompositionRequested(compositionId, acToUpdate);
134 if (DeployState.UNDEPLOYED.equals(acToUpdate.getDeployState())) {
135 acToUpdate.setElements(automationComposition.getElements());
136 acToUpdate.setName(automationComposition.getName());
137 acToUpdate.setVersion(automationComposition.getVersion());
138 acToUpdate.setDescription(automationComposition.getDescription());
139 acToUpdate.setDerivedFrom(automationComposition.getDerivedFrom());
140 var validationResult = validateAutomationComposition(acToUpdate);
141 if (!validationResult.isValid()) {
142 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
144 encryptInstanceProperties(acToUpdate, compositionId);
145 automationComposition = automationCompositionProvider.updateAutomationComposition(acToUpdate);
146 return createInstantiationResponse(automationComposition);
150 var deployOrder = DeployOrder.UPDATE;
151 var subOrder = SubOrder.NONE;
153 if (automationComposition.getCompositionTargetId() != null) {
155 if (Boolean.TRUE.equals(automationComposition.getPrecheck())) {
156 subOrder = SubOrder.MIGRATE_PRECHECK;
157 deployOrder = DeployOrder.NONE;
159 deployOrder = DeployOrder.MIGRATE;
162 var result = acInstanceStateResolver.resolve(deployOrder, LockOrder.NONE, subOrder,
163 acToUpdate.getDeployState(), acToUpdate.getLockState(), acToUpdate.getSubState(),
164 acToUpdate.getStateChangeResult());
165 return switch (result) {
166 case "UPDATE" -> updateDeployedAutomationComposition(automationComposition, acToUpdate);
168 case "MIGRATE" -> migrateAutomationComposition(automationComposition, acToUpdate);
170 case "MIGRATE_PRECHECK" -> migratePrecheckAc(automationComposition, acToUpdate);
172 default -> throw new PfModelRuntimeException(Status.BAD_REQUEST,
173 "Not allowed to " + deployOrder + " in the state " + acToUpdate.getDeployState());
178 * Update deployed AC Element properties.
180 * @param automationComposition the automation composition
181 * @param acToBeUpdated the composition to be updated
182 * @return the result of the update
184 private InstantiationResponse updateDeployedAutomationComposition(
185 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
186 // save copy in case of a rollback
187 automationCompositionProvider.copyAcElementsBeforeUpdate(acToBeUpdated);
189 // Iterate and update the element property values
190 for (var element : automationComposition.getElements().entrySet()) {
191 var elementId = element.getKey();
192 var dbAcElement = acToBeUpdated.getElements().get(elementId);
193 if (dbAcElement == null) {
194 throw new PfModelRuntimeException(Status.BAD_REQUEST, ELEMENT_ID_NOT_PRESENT + elementId);
196 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
199 var validationResult = validateAutomationComposition(acToBeUpdated);
200 if (!validationResult.isValid()) {
201 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
203 updateAcForProperties(acToBeUpdated);
205 var acToPublish = new AutomationComposition(acToBeUpdated);
207 encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionId());
209 automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
210 // Publish property update event to the participants
211 supervisionAcHandler.update(acToPublish);
212 return createInstantiationResponse(automationComposition);
215 private InstantiationResponse migrateAutomationComposition(
216 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
218 if (!DeployState.DEPLOYED.equals(acToBeUpdated.getDeployState())) {
219 throw new PfModelRuntimeException(Status.BAD_REQUEST,
220 "Not allowed to migrate in the state " + acToBeUpdated.getDeployState());
222 // make copy for rollback
223 automationCompositionProvider.copyAcElementsBeforeUpdate(acToBeUpdated);
225 // Iterate and update the element property values
226 var elementsRemoved = updateElementsProperties(automationComposition, acToBeUpdated);
227 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionTargetId());
229 updateAcForMigration(acToBeUpdated, acDefinition);
231 var acToPublish = new AutomationComposition(acToBeUpdated);
233 encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionTargetId());
235 var ac = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
236 elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
238 // Publish migrate event to the participants
239 supervisionAcHandler.migrate(acToPublish);
240 return createInstantiationResponse(ac);
243 private void updateAcForMigration(AutomationComposition acToBeUpdated,
244 AutomationCompositionDefinition acDefinition) {
245 AcmUtils.setCascadedState(acToBeUpdated, DeployState.MIGRATING, LockState.LOCKED);
246 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
247 var stage = ParticipantUtils.getFirstStage(acToBeUpdated, acDefinition.getServiceTemplate());
248 acToBeUpdated.setPhase(stage);
251 private void updateAcForProperties(AutomationComposition acToBeUpdated) {
252 AcmUtils.setCascadedState(acToBeUpdated, DeployState.UPDATING, acToBeUpdated.getLockState());
253 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
256 private List<UUID> getElementRemoved(AutomationComposition acFromDb, AutomationComposition acFromMigration) {
257 return acFromDb.getElements().keySet().stream()
258 .filter(id -> acFromMigration.getElements().get(id) == null).toList();
261 void checkCompatibility(PfConceptKey newDefinition, PfConceptKey dbElementDefinition,
263 var compatibility = newDefinition.getCompatibility(dbElementDefinition);
264 if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) {
265 throw new PfModelRuntimeException(Status.BAD_REQUEST,
266 dbElementDefinition + " is not compatible with " + newDefinition);
268 if (PfKey.Compatibility.MAJOR.equals(compatibility) || PfKey.Compatibility.MINOR
269 .equals(compatibility)) {
270 LOGGER.warn("Migrate {}: Version {} has {} compatibility with {} ", instanceId, newDefinition,
271 compatibility, dbElementDefinition);
275 private InstantiationResponse migratePrecheckAc(
276 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
278 acToBeUpdated.setPrecheck(true);
279 var copyAc = new AutomationComposition(acToBeUpdated);
280 // Iterate and update the element property values
281 updateElementsProperties(automationComposition, copyAc);
283 // Publish migrate event to the participants
284 supervisionAcHandler.migratePrecheck(copyAc);
286 AcmUtils.setCascadedState(acToBeUpdated, DeployState.DEPLOYED, LockState.LOCKED,
287 SubState.MIGRATION_PRECHECKING);
288 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
290 return createInstantiationResponse(automationCompositionProvider.updateAutomationComposition(acToBeUpdated));
293 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition) {
294 return validateAutomationComposition(automationComposition, automationComposition.getCompositionId());
298 * Validate AutomationComposition.
300 * @param automationComposition AutomationComposition to validate
301 * @param compositionId the composition id
302 * @return the result of validation
304 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition,
305 UUID compositionId) {
307 var result = new BeanValidationResult("AutomationComposition", automationComposition);
308 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
309 if (acDefinitionOpt.isEmpty()) {
310 result.addResult(new ObjectValidationResult("ServiceTemplate", compositionId, ValidationStatus.INVALID,
311 "Commissioned automation composition definition not found"));
314 if (!AcTypeState.PRIMED.equals(acDefinitionOpt.get().getState())) {
315 result.addResult(new ObjectValidationResult("ServiceTemplate.state", acDefinitionOpt.get().getState(),
316 ValidationStatus.INVALID, "Commissioned automation composition definition not primed"));
319 var participantIds = acDefinitionOpt.get().getElementStateMap().values().stream()
320 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
322 participantProvider.verifyParticipantState(participantIds);
324 result.addResult(AcmUtils.validateAutomationComposition(automationComposition,
325 acDefinitionOpt.get().getServiceTemplate(),
326 acRuntimeParameterGroup.getAcmParameters().getToscaCompositionName()));
328 result.addResult(automationCompositionProvider.validateElementIds(automationComposition));
330 if (result.isValid()) {
331 for (var element : automationComposition.getElements().values()) {
332 var name = element.getDefinition().getName();
333 var participantId = acDefinitionOpt.get().getElementStateMap().get(name).getParticipantId();
334 element.setParticipantId(participantId);
342 private void encryptInstanceProperties(AutomationComposition automationComposition, UUID compositionId) {
343 if (encryptionUtils.encryptionEnabled()) {
344 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
345 acDefinitionOpt.ifPresent(acDefinition
346 -> encryptionUtils.findAndEncryptSensitiveData(acDefinition, automationComposition));
351 * Get Automation Composition.
353 * @param compositionId The UUID of the automation composition definition
354 * @param instanceId The UUID of the automation composition instance
355 * @return the Automation Composition
357 @Transactional(readOnly = true)
358 public AutomationComposition getAutomationComposition(@NonNull UUID compositionId, UUID instanceId) {
359 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
360 if (!compositionId.equals(automationComposition.getCompositionId())
361 && !compositionId.equals(automationComposition.getCompositionTargetId())) {
362 throw new PfModelRuntimeException(Status.BAD_REQUEST,
363 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
365 return automationComposition;
369 * Delete the automation composition with the given name and version.
371 * @param compositionId The UUID of the automation composition definition
372 * @param instanceId The UUID of the automation composition instance
373 * @return the result of the deletion
375 public InstantiationResponse deleteAutomationComposition(UUID compositionId, UUID instanceId) {
376 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
377 var acDefinition = getAcDefinition(compositionId, automationComposition);
378 var result = acInstanceStateResolver.resolve(DeployOrder.DELETE,
380 automationComposition.getDeployState(), automationComposition.getLockState(),
381 automationComposition.getSubState(), automationComposition.getStateChangeResult());
382 if (!DeployOrder.DELETE.name().equals(result)) {
383 var msg = String.format(NOT_VALID_ORDER, DeployOrder.DELETE,
384 automationComposition.getDeployState(), automationComposition.getLockState(),
385 automationComposition.getSubState(), automationComposition.getStateChangeResult());
386 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);
388 supervisionAcHandler.delete(automationComposition, acDefinition);
389 return createInstantiationResponse(automationComposition);
393 * Get the requested automation compositions.
395 * @param name the name of the automation composition to get, null for all automation compositions
396 * @param version the version of the automation composition to get, null for all automation compositions
397 * @param pageable the Pageable
398 * @return the automation compositions
400 @Transactional(readOnly = true)
401 public AutomationCompositions getAutomationCompositions(@NonNull final UUID compositionId,
402 final String name, final String version,
403 @NonNull final Pageable pageable) {
404 var automationCompositions = new AutomationCompositions();
405 automationCompositions.setAutomationCompositionList(
406 automationCompositionProvider.getAutomationCompositions(compositionId, name, version, pageable));
408 return automationCompositions;
412 * Handle Composition Instance State.
414 * @param compositionId the compositionId
415 * @param instanceId the instanceId
416 * @param acInstanceStateUpdate the AcInstanceStateUpdate
418 public void compositionInstanceState(UUID compositionId, UUID instanceId,
419 @Valid AcInstanceStateUpdate acInstanceStateUpdate) {
420 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
421 var acDefinition = getAcDefinition(compositionId, automationComposition);
422 var result = acInstanceStateResolver.resolve(acInstanceStateUpdate.getDeployOrder(),
423 acInstanceStateUpdate.getLockOrder(), acInstanceStateUpdate.getSubOrder(),
424 automationComposition.getDeployState(), automationComposition.getLockState(),
425 automationComposition.getSubState(), automationComposition.getStateChangeResult());
428 supervisionAcHandler.deploy(automationComposition, acDefinition);
432 supervisionAcHandler.undeploy(automationComposition, acDefinition);
436 supervisionAcHandler.lock(automationComposition, acDefinition);
440 supervisionAcHandler.unlock(automationComposition, acDefinition);
444 supervisionAcHandler.prepare(automationComposition, acDefinition);
448 supervisionAcHandler.review(automationComposition);
452 var msg = String.format(NOT_VALID_ORDER, acInstanceStateUpdate,
453 automationComposition.getDeployState(), automationComposition.getLockState(),
454 automationComposition.getSubState(), automationComposition.getStateChangeResult());
455 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);
459 private List<UUID> updateElementsProperties(AutomationComposition automationComposition,
460 AutomationComposition acToBeUpdated) {
461 for (var element : automationComposition.getElements().entrySet()) {
462 var elementId = element.getKey();
463 var dbAcElement = acToBeUpdated.getElements().get(elementId);
464 // Add additional elements if present for migration
465 if (dbAcElement == null) {
466 LOGGER.info("New Ac element {} added in Migration", elementId);
467 acToBeUpdated.getElements().put(elementId, element.getValue());
469 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
470 var newDefinition = element.getValue().getDefinition().asConceptKey();
471 var dbElementDefinition = dbAcElement.getDefinition().asConceptKey();
472 checkCompatibility(newDefinition, dbElementDefinition, automationComposition.getInstanceId());
473 dbAcElement.setDefinition(element.getValue().getDefinition());
476 // Remove element which is not present in the new Ac instance
477 var elementsRemoved = getElementRemoved(acToBeUpdated, automationComposition);
478 elementsRemoved.forEach(uuid -> acToBeUpdated.getElements().remove(uuid));
480 var validationResult =
481 validateAutomationComposition(acToBeUpdated, automationComposition.getCompositionTargetId());
482 if (!validationResult.isValid()) {
483 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
485 acToBeUpdated.setCompositionTargetId(automationComposition.getCompositionTargetId());
486 return elementsRemoved;
489 private static void validateCompositionRequested(UUID compositionId,
490 AutomationComposition automationComposition) {
491 if (!compositionId.equals(automationComposition.getCompositionId())) {
492 throw new PfModelRuntimeException(Status.BAD_REQUEST,
493 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
497 private AutomationCompositionDefinition getAcDefinition(UUID compositionId,
498 AutomationComposition automationComposition) {
499 validateCompositionRequested(compositionId, automationComposition);
500 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
502 var participantIds = acDefinition.getElementStateMap().values().stream()
503 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
505 participantProvider.verifyParticipantState(participantIds);