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 if (!compositionId.equals(automationComposition.getCompositionId())) {
98 throw new PfModelRuntimeException(Status.BAD_REQUEST,
99 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
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 if (!compositionId.equals(acToUpdate.getCompositionId())) {
137 throw new PfModelRuntimeException(Status.BAD_REQUEST,
138 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
140 if (DeployState.UNDEPLOYED.equals(acToUpdate.getDeployState())) {
141 acToUpdate.setElements(automationComposition.getElements());
142 acToUpdate.setName(automationComposition.getName());
143 acToUpdate.setVersion(automationComposition.getVersion());
144 acToUpdate.setDescription(automationComposition.getDescription());
145 acToUpdate.setDerivedFrom(automationComposition.getDerivedFrom());
146 var validationResult = validateAutomationComposition(acToUpdate);
147 if (!validationResult.isValid()) {
148 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
150 encryptInstanceProperties(acToUpdate, compositionId);
151 automationComposition = automationCompositionProvider.updateAutomationComposition(acToUpdate);
152 return createInstantiationResponse(automationComposition);
156 var deployOrder = DeployOrder.UPDATE;
157 var subOrder = SubOrder.NONE;
159 if (automationComposition.getCompositionTargetId() != null) {
161 if (Boolean.TRUE.equals(automationComposition.getPrecheck())) {
162 subOrder = SubOrder.MIGRATE_PRECHECK;
163 deployOrder = DeployOrder.NONE;
165 deployOrder = DeployOrder.MIGRATE;
168 var result = acInstanceStateResolver.resolve(deployOrder, LockOrder.NONE, subOrder,
169 acToUpdate.getDeployState(), acToUpdate.getLockState(), acToUpdate.getSubState(),
170 acToUpdate.getStateChangeResult());
171 return switch (result) {
172 case "UPDATE" -> updateDeployedAutomationComposition(automationComposition, acToUpdate);
174 case "MIGRATE" -> migrateAutomationComposition(automationComposition, acToUpdate);
176 case "MIGRATE_PRECHECK" -> migratePrecheckAc(automationComposition, acToUpdate);
178 default -> throw new PfModelRuntimeException(Status.BAD_REQUEST,
179 "Not allowed to " + deployOrder + " in the state " + acToUpdate.getDeployState());
184 * Update deployed AC Element properties.
186 * @param automationComposition the automation composition
187 * @param acToBeUpdated the composition to be updated
188 * @return the result of the update
190 private InstantiationResponse updateDeployedAutomationComposition(
191 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
193 // Iterate and update the element property values
194 for (var element : automationComposition.getElements().entrySet()) {
195 var elementId = element.getKey();
196 var dbAcElement = acToBeUpdated.getElements().get(elementId);
197 if (dbAcElement == null) {
198 throw new PfModelRuntimeException(Status.BAD_REQUEST, ELEMENT_ID_NOT_PRESENT + elementId);
200 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
203 var validationResult = validateAutomationComposition(acToBeUpdated);
204 if (!validationResult.isValid()) {
205 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
207 updateAcForProperties(acToBeUpdated);
209 var acToPublish = new AutomationComposition(acToBeUpdated);
211 encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionId());
213 automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
214 // Publish property update event to the participants
215 supervisionAcHandler.update(acToPublish);
216 return createInstantiationResponse(automationComposition);
219 private InstantiationResponse migrateAutomationComposition(
220 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
222 if (!DeployState.DEPLOYED.equals(acToBeUpdated.getDeployState())) {
223 throw new PfModelRuntimeException(Status.BAD_REQUEST,
224 "Not allowed to migrate in the state " + acToBeUpdated.getDeployState());
227 // Iterate and update the element property values
228 for (var element : automationComposition.getElements().entrySet()) {
229 var elementId = element.getKey();
230 var dbAcElement = acToBeUpdated.getElements().get(elementId);
231 // Add additional elements if present for migration
232 if (dbAcElement == null) {
233 LOGGER.info("New Ac element {} added in Migration", elementId);
234 acToBeUpdated.getElements().put(elementId, element.getValue());
236 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
237 var newDefinition = element.getValue().getDefinition().asConceptKey();
238 var dbElementDefinition = dbAcElement.getDefinition().asConceptKey();
239 checkCompatibility(newDefinition, dbElementDefinition, automationComposition.getInstanceId());
240 dbAcElement.setDefinition(element.getValue().getDefinition());
243 // Remove element which is not present in the new Ac instance
244 var elementsRemoved = getElementRemoved(acToBeUpdated, automationComposition);
245 elementsRemoved.forEach(uuid -> acToBeUpdated.getElements().remove(uuid));
247 var validationResult =
248 validateAutomationComposition(acToBeUpdated, automationComposition.getCompositionTargetId());
249 if (!validationResult.isValid()) {
250 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
252 acToBeUpdated.setCompositionTargetId(automationComposition.getCompositionTargetId());
253 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionTargetId());
255 updateAcForMigration(acToBeUpdated, acDefinition);
257 var acToPublish = new AutomationComposition(acToBeUpdated);
259 encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionTargetId());
261 var ac = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
262 elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
264 // Publish migrate event to the participants
265 supervisionAcHandler.migrate(acToPublish);
266 return createInstantiationResponse(ac);
269 private void updateAcForMigration(AutomationComposition acToBeUpdated,
270 AutomationCompositionDefinition acDefinition) {
271 AcmUtils.setCascadedState(acToBeUpdated, DeployState.MIGRATING, LockState.LOCKED);
272 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
273 var stage = ParticipantUtils.getFirstStage(acToBeUpdated, acDefinition.getServiceTemplate());
274 acToBeUpdated.setPhase(stage);
277 private void updateAcForProperties(AutomationComposition acToBeUpdated) {
278 AcmUtils.setCascadedState(acToBeUpdated, DeployState.UPDATING, acToBeUpdated.getLockState());
279 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
282 private List<UUID> getElementRemoved(AutomationComposition acFromDb, AutomationComposition acFromMigration) {
283 return acFromDb.getElements().keySet().stream()
284 .filter(id -> acFromMigration.getElements().get(id) == null).toList();
287 void checkCompatibility(PfConceptKey newDefinition, PfConceptKey dbElementDefinition,
289 var compatibility = newDefinition.getCompatibility(dbElementDefinition);
290 if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) {
291 throw new PfModelRuntimeException(Status.BAD_REQUEST,
292 dbElementDefinition + " is not compatible with " + newDefinition);
294 if (PfKey.Compatibility.MAJOR.equals(compatibility) || PfKey.Compatibility.MINOR
295 .equals(compatibility)) {
296 LOGGER.warn("Migrate {}: Version {} has {} compatibility with {} ", instanceId, newDefinition,
297 compatibility, dbElementDefinition);
301 private InstantiationResponse migratePrecheckAc(
302 AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
304 acToBeUpdated.setPrecheck(true);
305 var copyAc = new AutomationComposition(acToBeUpdated);
306 // Iterate and update the element property values
307 for (var element : automationComposition.getElements().entrySet()) {
308 var elementId = element.getKey();
309 var copyElement = copyAc.getElements().get(elementId);
310 // Add additional elements if present for migration
311 if (copyElement == null) {
312 LOGGER.info("New Ac element {} added in Migration", elementId);
313 copyAc.getElements().put(elementId, element.getValue());
315 AcmUtils.recursiveMerge(copyElement.getProperties(), element.getValue().getProperties());
316 var newDefinition = element.getValue().getDefinition().asConceptKey();
317 var copyElementDefinition = copyElement.getDefinition().asConceptKey();
318 checkCompatibility(newDefinition, copyElementDefinition, automationComposition.getInstanceId());
319 copyElement.setDefinition(element.getValue().getDefinition());
322 // Remove element which is not present in the new Ac instance
323 var elementsRemoved = getElementRemoved(copyAc, automationComposition);
324 elementsRemoved.forEach(uuid -> copyAc.getElements().remove(uuid));
326 var validationResult =
327 validateAutomationComposition(copyAc, automationComposition.getCompositionTargetId());
328 if (!validationResult.isValid()) {
329 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
331 copyAc.setCompositionTargetId(automationComposition.getCompositionTargetId());
333 // Publish migrate event to the participants
334 supervisionAcHandler.migratePrecheck(copyAc);
336 AcmUtils.setCascadedState(acToBeUpdated, DeployState.DEPLOYED, LockState.LOCKED,
337 SubState.MIGRATION_PRECHECKING);
338 acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
340 return createInstantiationResponse(automationCompositionProvider.updateAutomationComposition(acToBeUpdated));
343 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition) {
344 return validateAutomationComposition(automationComposition, automationComposition.getCompositionId());
348 * Validate AutomationComposition.
350 * @param automationComposition AutomationComposition to validate
351 * @param compositionId the composition id
352 * @return the result of validation
354 private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition,
355 UUID compositionId) {
357 var result = new BeanValidationResult("AutomationComposition", automationComposition);
358 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
359 if (acDefinitionOpt.isEmpty()) {
360 result.addResult(new ObjectValidationResult("ServiceTemplate", compositionId, ValidationStatus.INVALID,
361 "Commissioned automation composition definition not found"));
364 if (!AcTypeState.PRIMED.equals(acDefinitionOpt.get().getState())) {
365 result.addResult(new ObjectValidationResult("ServiceTemplate.state", acDefinitionOpt.get().getState(),
366 ValidationStatus.INVALID, "Commissioned automation composition definition not primed"));
369 var participantIds = acDefinitionOpt.get().getElementStateMap().values().stream()
370 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
372 participantProvider.verifyParticipantState(participantIds);
374 result.addResult(AcmUtils.validateAutomationComposition(automationComposition,
375 acDefinitionOpt.get().getServiceTemplate(),
376 acRuntimeParameterGroup.getAcmParameters().getToscaCompositionName()));
378 result.addResult(automationCompositionProvider.validateElementIds(automationComposition));
380 if (result.isValid()) {
381 for (var element : automationComposition.getElements().values()) {
382 var name = element.getDefinition().getName();
383 var participantId = acDefinitionOpt.get().getElementStateMap().get(name).getParticipantId();
384 element.setParticipantId(participantId);
392 private void encryptInstanceProperties(AutomationComposition automationComposition, UUID compositionId) {
393 if (encryptionUtils.encryptionEnabled()) {
394 var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
395 acDefinitionOpt.ifPresent(acDefinition
396 -> encryptionUtils.findAndEncryptSensitiveData(acDefinition, automationComposition));
401 * Get Automation Composition.
403 * @param compositionId The UUID of the automation composition definition
404 * @param instanceId The UUID of the automation composition instance
405 * @return the Automation Composition
407 @Transactional(readOnly = true)
408 public AutomationComposition getAutomationComposition(@NonNull UUID compositionId, UUID instanceId) {
409 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
410 if (!compositionId.equals(automationComposition.getCompositionId())
411 && !compositionId.equals(automationComposition.getCompositionTargetId())) {
412 throw new PfModelRuntimeException(Status.BAD_REQUEST,
413 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
415 return automationComposition;
419 * Delete the automation composition with the given name and version.
421 * @param compositionId The UUID of the automation composition definition
422 * @param instanceId The UUID of the automation composition instance
423 * @return the result of the deletion
425 public InstantiationResponse deleteAutomationComposition(UUID compositionId, UUID instanceId) {
426 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
427 if (!compositionId.equals(automationComposition.getCompositionId())) {
428 throw new PfModelRuntimeException(Status.BAD_REQUEST,
429 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
431 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
432 var participantIds = acDefinition.getElementStateMap().values().stream()
433 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
434 participantProvider.verifyParticipantState(participantIds);
435 var result = acInstanceStateResolver.resolve(DeployOrder.DELETE,
437 automationComposition.getDeployState(), automationComposition.getLockState(),
438 automationComposition.getSubState(), automationComposition.getStateChangeResult());
439 if (!DeployOrder.DELETE.name().equals(result)) {
440 var msg = String.format(NOT_VALID_ORDER, DeployOrder.DELETE,
441 automationComposition.getDeployState(), automationComposition.getLockState(),
442 automationComposition.getSubState(), automationComposition.getStateChangeResult());
443 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);
445 supervisionAcHandler.delete(automationComposition, acDefinition);
446 return createInstantiationResponse(automationComposition);
450 * Get the requested automation compositions.
452 * @param name the name of the automation composition to get, null for all automation compositions
453 * @param version the version of the automation composition to get, null for all automation compositions
454 * @param pageable the Pageable
455 * @return the automation compositions
457 @Transactional(readOnly = true)
458 public AutomationCompositions getAutomationCompositions(@NonNull final UUID compositionId,
459 final String name, final String version, @NonNull final Pageable pageable) {
460 var automationCompositions = new AutomationCompositions();
461 automationCompositions.setAutomationCompositionList(
462 automationCompositionProvider.getAutomationCompositions(compositionId, name, version, pageable));
464 return automationCompositions;
468 * Handle Composition Instance State.
470 * @param compositionId the compositionId
471 * @param instanceId the instanceId
472 * @param acInstanceStateUpdate the AcInstanceStateUpdate
474 public void compositionInstanceState(UUID compositionId, UUID instanceId,
475 @Valid AcInstanceStateUpdate acInstanceStateUpdate) {
476 var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
477 if (!compositionId.equals(automationComposition.getCompositionId())) {
478 throw new PfModelRuntimeException(Status.BAD_REQUEST,
479 automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
481 var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
483 var participantIds = acDefinition.getElementStateMap().values().stream()
484 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
486 participantProvider.verifyParticipantState(participantIds);
487 var result = acInstanceStateResolver.resolve(acInstanceStateUpdate.getDeployOrder(),
488 acInstanceStateUpdate.getLockOrder(), acInstanceStateUpdate.getSubOrder(),
489 automationComposition.getDeployState(), automationComposition.getLockState(),
490 automationComposition.getSubState(), automationComposition.getStateChangeResult());
493 supervisionAcHandler.deploy(automationComposition, acDefinition);
497 supervisionAcHandler.undeploy(automationComposition, acDefinition);
501 supervisionAcHandler.lock(automationComposition, acDefinition);
505 supervisionAcHandler.unlock(automationComposition, acDefinition);
509 supervisionAcHandler.prepare(automationComposition, acDefinition);
513 supervisionAcHandler.review(automationComposition);
517 var msg = String.format(NOT_VALID_ORDER, acInstanceStateUpdate,
518 automationComposition.getDeployState(), automationComposition.getLockState(),
519 automationComposition.getSubState(), automationComposition.getStateChangeResult());
520 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);