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.function.UnaryOperator;
29 import java.util.stream.Collectors;
30 import lombok.NonNull;
31 import lombok.RequiredArgsConstructor;
32 import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup;
33 import org.onap.policy.clamp.acm.runtime.main.utils.EncryptionUtils;
34 import org.onap.policy.clamp.acm.runtime.supervision.SupervisionAcHandler;
35 import org.onap.policy.clamp.models.acm.concepts.AcTypeState;
36 import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
37 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionDefinition;
38 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement;
39 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositions;
40 import org.onap.policy.clamp.models.acm.concepts.DeployState;
41 import org.onap.policy.clamp.models.acm.concepts.LockState;
42 import org.onap.policy.clamp.models.acm.concepts.NodeTemplateState;
43 import org.onap.policy.clamp.models.acm.concepts.ParticipantUtils;
44 import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
45 import org.onap.policy.clamp.models.acm.concepts.SubState;
46 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AcInstanceStateUpdate;
47 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.DeployOrder;
48 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstantiationResponse;
49 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.LockOrder;
50 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.SubOrder;
51 import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvider;
52 import org.onap.policy.clamp.models.acm.persistence.provider.AcInstanceStateResolver;
53 import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
54 import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantProvider;
55 import org.onap.policy.clamp.models.acm.utils.AcmUtils;
56 import org.onap.policy.common.parameters.BeanValidationResult;
57 import org.onap.policy.common.parameters.ObjectValidationResult;
58 import org.onap.policy.common.parameters.ValidationStatus;
59 import org.onap.policy.models.base.PfConceptKey;
60 import org.onap.policy.models.base.PfKey;
61 import org.onap.policy.models.base.PfModelRuntimeException;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64 import org.springframework.data.domain.Pageable;
65 import org.springframework.stereotype.Service;
66 import org.springframework.transaction.annotation.Transactional;
69 * This class is dedicated to the Instantiation of Commissioned automation composition.
73 @RequiredArgsConstructor
74 public class AutomationCompositionInstantiationProvider {
75 private static final String DO_NOT_MATCH = " do not match with ";
76 private static final String ELEMENT_ID_NOT_PRESENT = "Element id not present ";
77 private static final String NOT_VALID_ORDER =
78 "Not valid order %s; DeployState: %s; LockState: %s; SubState: %s; StateChangeResult: %s";
80 private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionInstantiationProvider.class);
82 private final AutomationCompositionProvider automationCompositionProvider;
83 private final AcDefinitionProvider acDefinitionProvider;
84 private final AcInstanceStateResolver acInstanceStateResolver;
85 private final SupervisionAcHandler supervisionAcHandler;
86 private final ParticipantProvider participantProvider;
87 private final AcRuntimeParameterGroup acRuntimeParameterGroup;
88 private final EncryptionUtils encryptionUtils;
91 * Create automation composition.
93 * @param compositionId The UUID of the automation composition definition
94 * @param automationComposition the automation composition
95 * @return the result of the instantiation operation
97 public InstantiationResponse createAutomationComposition(UUID compositionId,
98 AutomationComposition automationComposition) {
99 validateCompositionRequested(compositionId, automationComposition);
100 var checkAutomationCompositionOpt =
101 automationCompositionProvider.findAutomationComposition(automationComposition.getKey().asIdentifier());
102 if (checkAutomationCompositionOpt.isPresent()) {
103 throw new PfModelRuntimeException(Status.BAD_REQUEST,
104 automationComposition.getKey().asIdentifier() + " already defined");
107 var validationResult = validateAutomationComposition(automationComposition);
108 if (!validationResult.isValid()) {
109 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
111 encryptInstanceProperties(automationComposition, compositionId);
112 automationComposition = automationCompositionProvider.createAutomationComposition(automationComposition);
114 return createInstantiationResponse(automationComposition);
117 private InstantiationResponse createInstantiationResponse(AutomationComposition automationComposition) {
118 var response = new InstantiationResponse();
119 response.setInstanceId(automationComposition.getInstanceId());
120 response.setAffectedAutomationComposition(automationComposition.getKey().asIdentifier());
125 * Update automation composition.
127 * @param compositionId The UUID of the automation composition definition
128 * @param automationComposition the automation composition
129 * @return the result of the update
131 public InstantiationResponse updateAutomationComposition(UUID compositionId,
132 AutomationComposition automationComposition) {
133 var instanceId = automationComposition.getInstanceId();
134 var acToUpdate = automationCompositionProvider.getAutomationComposition(instanceId);
135 validateCompositionRequested(compositionId, acToUpdate);
136 if (DeployState.UNDEPLOYED.equals(acToUpdate.getDeployState())) {
137 acToUpdate.setElements(automationComposition.getElements());
138 acToUpdate.setName(automationComposition.getName());
139 acToUpdate.setVersion(automationComposition.getVersion());
140 acToUpdate.setDescription(automationComposition.getDescription());
141 acToUpdate.setDerivedFrom(automationComposition.getDerivedFrom());
142 var validationResult = validateAutomationComposition(acToUpdate);
143 if (!validationResult.isValid()) {
144 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
146 encryptInstanceProperties(acToUpdate, compositionId);
147 automationComposition = automationCompositionProvider.updateAutomationComposition(acToUpdate);
148 return createInstantiationResponse(automationComposition);
152 var deployOrder = DeployOrder.UPDATE;
153 var subOrder = SubOrder.NONE;
155 if (automationComposition.getCompositionTargetId() != null) {
157 if (Boolean.TRUE.equals(automationComposition.getPrecheck())) {
158 subOrder = SubOrder.MIGRATE_PRECHECK;
159 deployOrder = DeployOrder.NONE;
161 deployOrder = DeployOrder.MIGRATE;
164 var result = acInstanceStateResolver.resolve(deployOrder, LockOrder.NONE, subOrder,
165 acToUpdate.getDeployState(), acToUpdate.getLockState(), acToUpdate.getSubState(),
166 acToUpdate.getStateChangeResult());
167 return switch (result) {
168 case "UPDATE" -> updateDeployedAutomationComposition(automationComposition, acToUpdate);
170 case "MIGRATE" -> migrateAutomationComposition(automationComposition, acToUpdate);
172 case "MIGRATE_PRECHECK" -> migratePrecheckAc(automationComposition, acToUpdate);
174 default -> throw new PfModelRuntimeException(Status.BAD_REQUEST,
175 "Not allowed to " + deployOrder + " in the state " + acToUpdate.getDeployState());
180 * Update deployed AC Element properties.
182 * @param automationComposition the automation composition
183 * @param acToBeUpdated the composition to be updated
184 * @return the result of the update
186 private InstantiationResponse updateDeployedAutomationComposition(
187 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
188 // save copy in case of a rollback
189 automationCompositionProvider.copyAcElementsBeforeUpdate(acToBeUpdated);
191 // Iterate and update the element property values
192 for (var element : automationComposition.getElements().entrySet()) {
193 var elementId = element.getKey();
194 var dbAcElement = acToBeUpdated.getElements().get(elementId);
195 if (dbAcElement == null) {
196 throw new PfModelRuntimeException(Status.BAD_REQUEST, ELEMENT_ID_NOT_PRESENT + elementId);
198 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
201 var validationResult = validateAutomationComposition(acToBeUpdated);
202 if (!validationResult.isValid()) {
203 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
205 updateAcForProperties(acToBeUpdated);
207 var acToPublish = new AutomationComposition(acToBeUpdated);
209 encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionId());
211 automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
212 // Publish property update event to the participants
213 supervisionAcHandler.update(acToPublish);
214 return createInstantiationResponse(automationComposition);
217 private InstantiationResponse migrateAutomationComposition(
218 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
220 if (!DeployState.DEPLOYED.equals(acToBeUpdated.getDeployState())) {
221 throw new PfModelRuntimeException(Status.BAD_REQUEST,
222 "Not allowed to migrate in the state " + acToBeUpdated.getDeployState());
224 // make copy for rollback
225 automationCompositionProvider.copyAcElementsBeforeUpdate(acToBeUpdated);
227 // Iterate and update the element property values
228 var elementsRemoved = updateElementsProperties(automationComposition, acToBeUpdated);
229 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionTargetId());
231 updateAcForMigration(acToBeUpdated, acDefinition);
233 var acToPublish = new AutomationComposition(acToBeUpdated);
235 encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionTargetId());
237 var ac = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
238 elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
240 // Publish migrate event to the participants
241 supervisionAcHandler.migrate(acToPublish);
242 return createInstantiationResponse(ac);
245 private void updateAcForMigration(AutomationComposition acToBeUpdated,
246 AutomationCompositionDefinition acDefinition) {
247 AcmUtils.setCascadedState(acToBeUpdated, DeployState.MIGRATING, LockState.LOCKED);
248 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
249 var stage = ParticipantUtils.getFirstStage(acToBeUpdated, acDefinition.getServiceTemplate());
250 acToBeUpdated.setPhase(stage);
253 private void updateAcForProperties(AutomationComposition acToBeUpdated) {
254 AcmUtils.setCascadedState(acToBeUpdated, DeployState.UPDATING, acToBeUpdated.getLockState());
255 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
258 private List<UUID> getElementRemoved(AutomationComposition acFromDb, AutomationComposition acFromMigration) {
259 return acFromDb.getElements().keySet().stream()
260 .filter(id -> acFromMigration.getElements().get(id) == null).toList();
263 void checkCompatibility(PfConceptKey newDefinition, PfConceptKey dbElementDefinition,
265 var compatibility = newDefinition.getCompatibility(dbElementDefinition);
266 if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) {
267 throw new PfModelRuntimeException(Status.BAD_REQUEST,
268 dbElementDefinition + " is not compatible with " + newDefinition);
270 if (PfKey.Compatibility.MAJOR.equals(compatibility) || PfKey.Compatibility.MINOR
271 .equals(compatibility)) {
272 LOGGER.warn("Migrate {}: Version {} has {} compatibility with {} ", instanceId, newDefinition,
273 compatibility, dbElementDefinition);
277 private InstantiationResponse migratePrecheckAc(
278 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
280 acToBeUpdated.setPrecheck(true);
281 var copyAc = new AutomationComposition(acToBeUpdated);
282 // Iterate and update the element property values
283 updateElementsProperties(automationComposition, copyAc);
285 // Publish migrate event to the participants
286 supervisionAcHandler.migratePrecheck(copyAc);
288 AcmUtils.setCascadedState(acToBeUpdated, DeployState.DEPLOYED, LockState.LOCKED,
289 SubState.MIGRATION_PRECHECKING);
290 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
292 return createInstantiationResponse(automationCompositionProvider.updateAutomationComposition(acToBeUpdated));
295 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition) {
296 return validateAutomationComposition(automationComposition, automationComposition.getCompositionId());
300 * Validate AutomationComposition.
302 * @param automationComposition AutomationComposition to validate
303 * @param compositionId the composition id
304 * @return the result of validation
306 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition,
307 UUID compositionId) {
309 var result = new BeanValidationResult("AutomationComposition", automationComposition);
310 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
311 if (acDefinitionOpt.isEmpty()) {
312 result.addResult(new ObjectValidationResult("ServiceTemplate", compositionId, ValidationStatus.INVALID,
313 "Commissioned automation composition definition not found"));
316 if (!AcTypeState.PRIMED.equals(acDefinitionOpt.get().getState())) {
317 result.addResult(new ObjectValidationResult("ServiceTemplate.state", acDefinitionOpt.get().getState(),
318 ValidationStatus.INVALID, "Commissioned automation composition definition not primed"));
321 var participantIds = acDefinitionOpt.get().getElementStateMap().values().stream()
322 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
324 participantProvider.verifyParticipantState(participantIds);
326 result.addResult(AcmUtils.validateAutomationComposition(automationComposition,
327 acDefinitionOpt.get().getServiceTemplate(),
328 acRuntimeParameterGroup.getAcmParameters().getToscaCompositionName()));
330 result.addResult(automationCompositionProvider.validateElementIds(automationComposition));
332 if (result.isValid()) {
333 for (var element : automationComposition.getElements().values()) {
334 var name = element.getDefinition().getName();
335 var participantId = acDefinitionOpt.get().getElementStateMap().get(name).getParticipantId();
336 element.setParticipantId(participantId);
344 private void encryptInstanceProperties(AutomationComposition automationComposition, UUID compositionId) {
345 if (encryptionUtils.encryptionEnabled()) {
346 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
347 acDefinitionOpt.ifPresent(acDefinition
348 -> encryptionUtils.findAndEncryptSensitiveData(acDefinition, automationComposition));
353 * Get Automation Composition.
355 * @param compositionId The UUID of the automation composition definition
356 * @param instanceId The UUID of the automation composition instance
357 * @return the Automation Composition
359 @Transactional(readOnly = true)
360 public AutomationComposition getAutomationComposition(@NonNull UUID compositionId, UUID instanceId) {
361 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
362 if (!compositionId.equals(automationComposition.getCompositionId())
363 && !compositionId.equals(automationComposition.getCompositionTargetId())) {
364 throw new PfModelRuntimeException(Status.BAD_REQUEST,
365 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
367 return automationComposition;
371 * Delete the automation composition with the given name and version.
373 * @param compositionId The UUID of the automation composition definition
374 * @param instanceId The UUID of the automation composition instance
375 * @return the result of the deletion
377 public InstantiationResponse deleteAutomationComposition(UUID compositionId, UUID instanceId) {
378 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
379 var acDefinition = getAcDefinition(compositionId, automationComposition);
380 var result = acInstanceStateResolver.resolve(DeployOrder.DELETE,
382 automationComposition.getDeployState(), automationComposition.getLockState(),
383 automationComposition.getSubState(), automationComposition.getStateChangeResult());
384 if (!DeployOrder.DELETE.name().equals(result)) {
385 var msg = String.format(NOT_VALID_ORDER, DeployOrder.DELETE,
386 automationComposition.getDeployState(), automationComposition.getLockState(),
387 automationComposition.getSubState(), automationComposition.getStateChangeResult());
388 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);
390 supervisionAcHandler.delete(automationComposition, acDefinition);
391 return createInstantiationResponse(automationComposition);
395 * Get the requested automation compositions.
397 * @param name the name of the automation composition to get, null for all automation compositions
398 * @param version the version of the automation composition to get, null for all automation compositions
399 * @param pageable the Pageable
400 * @return the automation compositions
402 @Transactional(readOnly = true)
403 public AutomationCompositions getAutomationCompositions(@NonNull final UUID compositionId,
404 final String name, final String version,
405 @NonNull final Pageable pageable) {
406 var automationCompositions = new AutomationCompositions();
407 automationCompositions.setAutomationCompositionList(
408 automationCompositionProvider.getAutomationCompositions(compositionId, name, version, pageable));
410 return automationCompositions;
414 * Handle Composition Instance State.
416 * @param compositionId the compositionId
417 * @param instanceId the instanceId
418 * @param acInstanceStateUpdate the AcInstanceStateUpdate
420 public void compositionInstanceState(UUID compositionId, UUID instanceId,
421 @Valid AcInstanceStateUpdate acInstanceStateUpdate) {
422 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
423 var acDefinition = getAcDefinition(compositionId, automationComposition);
424 var result = acInstanceStateResolver.resolve(acInstanceStateUpdate.getDeployOrder(),
425 acInstanceStateUpdate.getLockOrder(), acInstanceStateUpdate.getSubOrder(),
426 automationComposition.getDeployState(), automationComposition.getLockState(),
427 automationComposition.getSubState(), automationComposition.getStateChangeResult());
430 supervisionAcHandler.deploy(automationComposition, acDefinition);
434 supervisionAcHandler.undeploy(automationComposition, acDefinition);
438 supervisionAcHandler.lock(automationComposition, acDefinition);
442 supervisionAcHandler.unlock(automationComposition, acDefinition);
446 supervisionAcHandler.prepare(automationComposition, acDefinition);
450 supervisionAcHandler.review(automationComposition);
454 var msg = String.format(NOT_VALID_ORDER, acInstanceStateUpdate,
455 automationComposition.getDeployState(), automationComposition.getLockState(),
456 automationComposition.getSubState(), automationComposition.getStateChangeResult());
457 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);
462 * Rollback AC Instance.
464 * @param instanceId the instanceId
466 public void rollback(UUID instanceId) {
467 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
468 var automationCompositionToRollback =
469 automationCompositionProvider.getAutomationCompositionRollback(instanceId);
471 if (DeployState.DEPLOYED.equals(automationComposition.getDeployState())
472 && SubState.NONE.equals(automationComposition.getSubState())
473 && StateChangeResult.NO_ERROR.equals(automationComposition.getStateChangeResult())) {
474 automationComposition.setCompositionId(automationCompositionToRollback.getCompositionId());
475 automationComposition.setElements(automationCompositionToRollback.getElements().values().stream()
476 .collect(Collectors.toMap(AutomationCompositionElement::getId, UnaryOperator.identity())));
477 automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
478 AcmUtils.setCascadedState(automationComposition, DeployState.MIGRATION_REVERTING, LockState.LOCKED);
479 automationCompositionProvider.updateAutomationComposition(automationComposition);
481 throw new PfModelRuntimeException(Status.BAD_REQUEST, "Invalid state for rollback");
485 private List<UUID> updateElementsProperties(AutomationComposition automationComposition,
486 AutomationComposition acToBeUpdated) {
487 for (var element : automationComposition.getElements().entrySet()) {
488 var elementId = element.getKey();
489 var dbAcElement = acToBeUpdated.getElements().get(elementId);
490 // Add additional elements if present for migration
491 if (dbAcElement == null) {
492 LOGGER.info("New Ac element {} added in Migration", elementId);
493 acToBeUpdated.getElements().put(elementId, element.getValue());
495 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
496 var newDefinition = element.getValue().getDefinition().asConceptKey();
497 var dbElementDefinition = dbAcElement.getDefinition().asConceptKey();
498 checkCompatibility(newDefinition, dbElementDefinition, automationComposition.getInstanceId());
499 dbAcElement.setDefinition(element.getValue().getDefinition());
502 // Remove element which is not present in the new Ac instance
503 var elementsRemoved = getElementRemoved(acToBeUpdated, automationComposition);
504 elementsRemoved.forEach(uuid -> acToBeUpdated.getElements().remove(uuid));
506 var validationResult =
507 validateAutomationComposition(acToBeUpdated, automationComposition.getCompositionTargetId());
508 if (!validationResult.isValid()) {
509 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
511 acToBeUpdated.setCompositionTargetId(automationComposition.getCompositionTargetId());
512 return elementsRemoved;
515 private static void validateCompositionRequested(UUID compositionId,
516 AutomationComposition automationComposition) {
517 if (!compositionId.equals(automationComposition.getCompositionId())) {
518 throw new PfModelRuntimeException(Status.BAD_REQUEST,
519 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
523 private AutomationCompositionDefinition getAcDefinition(UUID compositionId,
524 AutomationComposition automationComposition) {
525 validateCompositionRequested(compositionId, automationComposition);
526 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
528 var participantIds = acDefinition.getElementStateMap().values().stream()
529 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
531 participantProvider.verifyParticipantState(participantIds);