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.stereotype.Service;
63 import org.springframework.transaction.annotation.Transactional;
66 * This class is dedicated to the Instantiation of Commissioned automation composition.
70 @RequiredArgsConstructor
71 public class AutomationCompositionInstantiationProvider {
72 private static final String DO_NOT_MATCH = " do not match with ";
73 private static final String ELEMENT_ID_NOT_PRESENT = "Element id not present ";
74 private static final String NOT_VALID_ORDER =
75 "Not valid order %s; DeployState: %s; LockState: %s; SubState: %s; StateChangeResult: %s";
77 private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionInstantiationProvider.class);
79 private final AutomationCompositionProvider automationCompositionProvider;
80 private final AcDefinitionProvider acDefinitionProvider;
81 private final AcInstanceStateResolver acInstanceStateResolver;
82 private final SupervisionAcHandler supervisionAcHandler;
83 private final ParticipantProvider participantProvider;
84 private final AcRuntimeParameterGroup acRuntimeParameterGroup;
85 private final EncryptionUtils encryptionUtils;
88 * Create automation composition.
90 * @param compositionId The UUID of the automation composition definition
91 * @param automationComposition the automation composition
92 * @return the result of the instantiation operation
94 public InstantiationResponse createAutomationComposition(UUID compositionId,
95 AutomationComposition automationComposition) {
96 if (!compositionId.equals(automationComposition.getCompositionId())) {
97 throw new PfModelRuntimeException(Status.BAD_REQUEST,
98 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
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 if (!compositionId.equals(acToUpdate.getCompositionId())) {
136 throw new PfModelRuntimeException(Status.BAD_REQUEST,
137 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
139 if (DeployState.UNDEPLOYED.equals(acToUpdate.getDeployState())) {
140 acToUpdate.setElements(automationComposition.getElements());
141 acToUpdate.setName(automationComposition.getName());
142 acToUpdate.setVersion(automationComposition.getVersion());
143 acToUpdate.setDescription(automationComposition.getDescription());
144 acToUpdate.setDerivedFrom(automationComposition.getDerivedFrom());
145 var validationResult = validateAutomationComposition(acToUpdate);
146 if (!validationResult.isValid()) {
147 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
149 encryptInstanceProperties(acToUpdate, compositionId);
150 automationComposition = automationCompositionProvider.updateAutomationComposition(acToUpdate);
151 return createInstantiationResponse(automationComposition);
155 var deployOrder = DeployOrder.UPDATE;
156 var subOrder = SubOrder.NONE;
158 if (automationComposition.getCompositionTargetId() != null) {
160 if (Boolean.TRUE.equals(automationComposition.getPrecheck())) {
161 subOrder = SubOrder.MIGRATE_PRECHECK;
162 deployOrder = DeployOrder.NONE;
164 deployOrder = DeployOrder.MIGRATE;
167 var result = acInstanceStateResolver.resolve(deployOrder, LockOrder.NONE, subOrder,
168 acToUpdate.getDeployState(), acToUpdate.getLockState(), acToUpdate.getSubState(),
169 acToUpdate.getStateChangeResult());
170 return switch (result) {
171 case "UPDATE" -> updateDeployedAutomationComposition(automationComposition, acToUpdate);
173 case "MIGRATE" -> migrateAutomationComposition(automationComposition, acToUpdate);
175 case "MIGRATE_PRECHECK" -> migratePrecheckAc(automationComposition, acToUpdate);
177 default -> throw new PfModelRuntimeException(Status.BAD_REQUEST,
178 "Not allowed to " + deployOrder + " in the state " + acToUpdate.getDeployState());
183 * Update deployed AC Element properties.
185 * @param automationComposition the automation composition
186 * @param acToBeUpdated the composition to be updated
187 * @return the result of the update
189 private InstantiationResponse updateDeployedAutomationComposition(
190 AutomationComposition automationComposition, AutomationComposition 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());
226 // Iterate and update the element property values
227 for (var element : automationComposition.getElements().entrySet()) {
228 var elementId = element.getKey();
229 var dbAcElement = acToBeUpdated.getElements().get(elementId);
230 // Add additional elements if present for migration
231 if (dbAcElement == null) {
232 LOGGER.info("New Ac element {} added in Migration", elementId);
233 acToBeUpdated.getElements().put(elementId, element.getValue());
235 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
236 var newDefinition = element.getValue().getDefinition().asConceptKey();
237 var dbElementDefinition = dbAcElement.getDefinition().asConceptKey();
238 checkCompatibility(newDefinition, dbElementDefinition, automationComposition.getInstanceId());
239 dbAcElement.setDefinition(element.getValue().getDefinition());
242 // Remove element which is not present in the new Ac instance
243 var elementsRemoved = getElementRemoved(acToBeUpdated, automationComposition);
244 elementsRemoved.forEach(uuid -> acToBeUpdated.getElements().remove(uuid));
246 var validationResult =
247 validateAutomationComposition(acToBeUpdated, automationComposition.getCompositionTargetId());
248 if (!validationResult.isValid()) {
249 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
251 acToBeUpdated.setCompositionTargetId(automationComposition.getCompositionTargetId());
252 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionTargetId());
254 updateAcForMigration(acToBeUpdated, acDefinition);
256 var acToPublish = new AutomationComposition(acToBeUpdated);
258 encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionTargetId());
260 var ac = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
261 elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
263 // Publish migrate event to the participants
264 supervisionAcHandler.migrate(acToPublish);
265 return createInstantiationResponse(ac);
268 private void updateAcForMigration(AutomationComposition acToBeUpdated,
269 AutomationCompositionDefinition acDefinition) {
270 AcmUtils.setCascadedState(acToBeUpdated, DeployState.MIGRATING, LockState.LOCKED);
271 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
272 var stage = ParticipantUtils.getFirstStage(acToBeUpdated, acDefinition.getServiceTemplate());
273 acToBeUpdated.setPhase(stage);
276 private void updateAcForProperties(AutomationComposition acToBeUpdated) {
277 AcmUtils.setCascadedState(acToBeUpdated, DeployState.UPDATING, acToBeUpdated.getLockState());
278 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
281 private List<UUID> getElementRemoved(AutomationComposition acFromDb, AutomationComposition acFromMigration) {
282 return acFromDb.getElements().keySet().stream()
283 .filter(id -> acFromMigration.getElements().get(id) == null).toList();
286 void checkCompatibility(PfConceptKey newDefinition, PfConceptKey dbElementDefinition,
288 var compatibility = newDefinition.getCompatibility(dbElementDefinition);
289 if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) {
290 throw new PfModelRuntimeException(Status.BAD_REQUEST,
291 dbElementDefinition + " is not compatible with " + newDefinition);
293 if (PfKey.Compatibility.MAJOR.equals(compatibility) || PfKey.Compatibility.MINOR
294 .equals(compatibility)) {
295 LOGGER.warn("Migrate {}: Version {} has {} compatibility with {} ", instanceId, newDefinition,
296 compatibility, dbElementDefinition);
300 private InstantiationResponse migratePrecheckAc(
301 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
303 acToBeUpdated.setPrecheck(true);
304 var copyAc = new AutomationComposition(acToBeUpdated);
305 // Iterate and update the element property values
306 for (var element : automationComposition.getElements().entrySet()) {
307 var elementId = element.getKey();
308 var copyElement = copyAc.getElements().get(elementId);
309 // Add additional elements if present for migration
310 if (copyElement == null) {
311 LOGGER.info("New Ac element {} added in Migration", elementId);
312 copyAc.getElements().put(elementId, element.getValue());
314 AcmUtils.recursiveMerge(copyElement.getProperties(), element.getValue().getProperties());
315 var newDefinition = element.getValue().getDefinition().asConceptKey();
316 var copyElementDefinition = copyElement.getDefinition().asConceptKey();
317 checkCompatibility(newDefinition, copyElementDefinition, automationComposition.getInstanceId());
318 copyElement.setDefinition(element.getValue().getDefinition());
321 // Remove element which is not present in the new Ac instance
322 var elementsRemoved = getElementRemoved(copyAc, automationComposition);
323 elementsRemoved.forEach(uuid -> copyAc.getElements().remove(uuid));
325 var validationResult =
326 validateAutomationComposition(copyAc, automationComposition.getCompositionTargetId());
327 if (!validationResult.isValid()) {
328 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
330 copyAc.setCompositionTargetId(automationComposition.getCompositionTargetId());
332 // Publish migrate event to the participants
333 supervisionAcHandler.migratePrecheck(copyAc);
335 AcmUtils.setCascadedState(acToBeUpdated, DeployState.DEPLOYED, LockState.LOCKED,
336 SubState.MIGRATION_PRECHECKING);
337 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
339 return createInstantiationResponse(automationCompositionProvider.updateAutomationComposition(acToBeUpdated));
342 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition) {
343 return validateAutomationComposition(automationComposition, automationComposition.getCompositionId());
347 * Validate AutomationComposition.
349 * @param automationComposition AutomationComposition to validate
350 * @param compositionId the composition id
351 * @return the result of validation
353 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition,
354 UUID compositionId) {
356 var result = new BeanValidationResult("AutomationComposition", automationComposition);
357 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
358 if (acDefinitionOpt.isEmpty()) {
359 result.addResult(new ObjectValidationResult("ServiceTemplate", compositionId, ValidationStatus.INVALID,
360 "Commissioned automation composition definition not found"));
363 if (!AcTypeState.PRIMED.equals(acDefinitionOpt.get().getState())) {
364 result.addResult(new ObjectValidationResult("ServiceTemplate.state", acDefinitionOpt.get().getState(),
365 ValidationStatus.INVALID, "Commissioned automation composition definition not primed"));
368 var participantIds = acDefinitionOpt.get().getElementStateMap().values().stream()
369 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
371 participantProvider.verifyParticipantState(participantIds);
373 result.addResult(AcmUtils.validateAutomationComposition(automationComposition,
374 acDefinitionOpt.get().getServiceTemplate(),
375 acRuntimeParameterGroup.getAcmParameters().getToscaCompositionName()));
377 result.addResult(automationCompositionProvider.validateElementIds(automationComposition));
379 if (result.isValid()) {
380 for (var element : automationComposition.getElements().values()) {
381 var name = element.getDefinition().getName();
382 var participantId = acDefinitionOpt.get().getElementStateMap().get(name).getParticipantId();
383 element.setParticipantId(participantId);
391 private void encryptInstanceProperties(AutomationComposition automationComposition, UUID compositionId) {
392 if (encryptionUtils.encryptionEnabled()) {
393 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
394 acDefinitionOpt.ifPresent(acDefinition
395 -> encryptionUtils.findAndEncryptSensitiveData(acDefinition, automationComposition));
400 * Get Automation Composition.
402 * @param compositionId The UUID of the automation composition definition
403 * @param instanceId The UUID of the automation composition instance
404 * @return the Automation Composition
406 @Transactional(readOnly = true)
407 public AutomationComposition getAutomationComposition(@NonNull UUID compositionId, UUID instanceId) {
408 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
409 if (!compositionId.equals(automationComposition.getCompositionId())
410 && !compositionId.equals(automationComposition.getCompositionTargetId())) {
411 throw new PfModelRuntimeException(Status.BAD_REQUEST,
412 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
414 return automationComposition;
418 * Delete the automation composition with the given name and version.
420 * @param compositionId The UUID of the automation composition definition
421 * @param instanceId The UUID of the automation composition instance
422 * @return the result of the deletion
424 public InstantiationResponse deleteAutomationComposition(UUID compositionId, UUID instanceId) {
425 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
426 if (!compositionId.equals(automationComposition.getCompositionId())) {
427 throw new PfModelRuntimeException(Status.BAD_REQUEST,
428 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
430 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
431 var participantIds = acDefinition.getElementStateMap().values().stream()
432 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
433 participantProvider.verifyParticipantState(participantIds);
434 var result = acInstanceStateResolver.resolve(DeployOrder.DELETE,
436 automationComposition.getDeployState(), automationComposition.getLockState(),
437 automationComposition.getSubState(), automationComposition.getStateChangeResult());
438 if (!DeployOrder.DELETE.name().equals(result)) {
439 var msg = String.format(NOT_VALID_ORDER, DeployOrder.DELETE,
440 automationComposition.getDeployState(), automationComposition.getLockState(),
441 automationComposition.getSubState(), automationComposition.getStateChangeResult());
442 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);
444 supervisionAcHandler.delete(automationComposition, acDefinition);
445 return createInstantiationResponse(automationComposition);
449 * Get the requested automation compositions.
451 * @param name the name of the automation composition to get, null for all automation compositions
452 * @param version the version of the automation composition to get, null for all automation compositions
453 * @return the automation compositions
455 @Transactional(readOnly = true)
456 public AutomationCompositions getAutomationCompositions(UUID compositionId, String name, String version) {
457 var automationCompositions = new AutomationCompositions();
458 automationCompositions.setAutomationCompositionList(
459 automationCompositionProvider.getAutomationCompositions(compositionId, name, version));
461 return automationCompositions;
465 * Handle Composition Instance State.
467 * @param compositionId the compositionId
468 * @param instanceId the instanceId
469 * @param acInstanceStateUpdate the AcInstanceStateUpdate
471 public void compositionInstanceState(UUID compositionId, UUID instanceId,
472 @Valid AcInstanceStateUpdate acInstanceStateUpdate) {
473 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
474 if (!compositionId.equals(automationComposition.getCompositionId())) {
475 throw new PfModelRuntimeException(Status.BAD_REQUEST,
476 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
478 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
480 var participantIds = acDefinition.getElementStateMap().values().stream()
481 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
483 participantProvider.verifyParticipantState(participantIds);
484 var result = acInstanceStateResolver.resolve(acInstanceStateUpdate.getDeployOrder(),
485 acInstanceStateUpdate.getLockOrder(), acInstanceStateUpdate.getSubOrder(),
486 automationComposition.getDeployState(), automationComposition.getLockState(),
487 automationComposition.getSubState(), automationComposition.getStateChangeResult());
490 supervisionAcHandler.deploy(automationComposition, acDefinition);
494 supervisionAcHandler.undeploy(automationComposition, acDefinition);
498 supervisionAcHandler.lock(automationComposition, acDefinition);
502 supervisionAcHandler.unlock(automationComposition, acDefinition);
506 supervisionAcHandler.prepare(automationComposition, acDefinition);
510 supervisionAcHandler.review(automationComposition);
514 var msg = String.format(NOT_VALID_ORDER, acInstanceStateUpdate,
515 automationComposition.getDeployState(), automationComposition.getLockState(),
516 automationComposition.getSubState(), automationComposition.getStateChangeResult());
517 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);