59373e89ba83285f0865100eb8b34adbb5152434
[policy/clamp.git] /
1 /*-
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
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
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.
17  *
18  * SPDX-License-Identifier: Apache-2.0
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.clamp.acm.runtime.instantiation;
23
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;
65
66 /**
67  * This class is dedicated to the Instantiation of Commissioned automation composition.
68  */
69 @Service
70 @Transactional
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";
77
78     private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionInstantiationProvider.class);
79
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;
87
88     /**
89      * Create automation composition.
90      *
91      * @param compositionId         The UUID of the automation composition definition
92      * @param automationComposition the automation composition
93      * @return the result of the instantiation operation
94      */
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");
103         }
104
105         var validationResult = validateAutomationComposition(automationComposition);
106         if (!validationResult.isValid()) {
107             throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
108         }
109         encryptInstanceProperties(automationComposition, compositionId);
110         automationComposition = automationCompositionProvider.createAutomationComposition(automationComposition);
111
112         return createInstantiationResponse(automationComposition);
113     }
114
115     private InstantiationResponse createInstantiationResponse(AutomationComposition automationComposition) {
116         var response = new InstantiationResponse();
117         response.setInstanceId(automationComposition.getInstanceId());
118         response.setAffectedAutomationComposition(automationComposition.getKey().asIdentifier());
119         return response;
120     }
121
122     /**
123      * Update automation composition.
124      *
125      * @param compositionId         The UUID of the automation composition definition
126      * @param automationComposition the automation composition
127      * @return the result of the update
128      */
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());
143             }
144             encryptInstanceProperties(acToUpdate, compositionId);
145             automationComposition = automationCompositionProvider.updateAutomationComposition(acToUpdate);
146             return createInstantiationResponse(automationComposition);
147
148         }
149
150         var deployOrder = DeployOrder.UPDATE;
151         var subOrder = SubOrder.NONE;
152
153         if (automationComposition.getCompositionTargetId() != null) {
154
155             if (Boolean.TRUE.equals(automationComposition.getPrecheck())) {
156                 subOrder = SubOrder.MIGRATE_PRECHECK;
157                 deployOrder = DeployOrder.NONE;
158             } else {
159                 deployOrder = DeployOrder.MIGRATE;
160             }
161         }
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);
167
168             case "MIGRATE" -> migrateAutomationComposition(automationComposition, acToUpdate);
169
170             case "MIGRATE_PRECHECK" -> migratePrecheckAc(automationComposition, acToUpdate);
171
172             default -> throw new PfModelRuntimeException(Status.BAD_REQUEST,
173                 "Not allowed to " + deployOrder + " in the state " + acToUpdate.getDeployState());
174         };
175     }
176
177     /**
178      * Update deployed AC Element properties.
179      *
180      * @param automationComposition the automation composition
181      * @param acToBeUpdated         the composition to be updated
182      * @return the result of the update
183      */
184     private InstantiationResponse updateDeployedAutomationComposition(
185         AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
186         // save copy in case of a rollback
187         automationCompositionProvider.copyAcElementsBeforeUpdate(acToBeUpdated);
188
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);
195             }
196             AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
197         }
198
199         var validationResult = validateAutomationComposition(acToBeUpdated);
200         if (!validationResult.isValid()) {
201             throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
202         }
203         updateAcForProperties(acToBeUpdated);
204
205         var acToPublish = new AutomationComposition(acToBeUpdated);
206
207         encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionId());
208
209         automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
210         // Publish property update event to the participants
211         supervisionAcHandler.update(acToPublish);
212         return createInstantiationResponse(automationComposition);
213     }
214
215     private InstantiationResponse migrateAutomationComposition(
216         AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
217
218         if (!DeployState.DEPLOYED.equals(acToBeUpdated.getDeployState())) {
219             throw new PfModelRuntimeException(Status.BAD_REQUEST,
220                 "Not allowed to migrate in the state " + acToBeUpdated.getDeployState());
221         }
222         // make copy for rollback
223         automationCompositionProvider.copyAcElementsBeforeUpdate(acToBeUpdated);
224
225         // Iterate and update the element property values
226         var elementsRemoved = updateElementsProperties(automationComposition, acToBeUpdated);
227         var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionTargetId());
228
229         updateAcForMigration(acToBeUpdated, acDefinition);
230
231         var acToPublish = new AutomationComposition(acToBeUpdated);
232
233         encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionTargetId());
234
235         var ac = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
236         elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
237
238         // Publish migrate event to the participants
239         supervisionAcHandler.migrate(acToPublish);
240         return createInstantiationResponse(ac);
241     }
242
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);
249     }
250
251     private void updateAcForProperties(AutomationComposition acToBeUpdated) {
252         AcmUtils.setCascadedState(acToBeUpdated, DeployState.UPDATING, acToBeUpdated.getLockState());
253         acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
254     }
255
256     private List<UUID> getElementRemoved(AutomationComposition acFromDb, AutomationComposition acFromMigration) {
257         return acFromDb.getElements().keySet().stream()
258             .filter(id -> acFromMigration.getElements().get(id) == null).toList();
259     }
260
261     void checkCompatibility(PfConceptKey newDefinition, PfConceptKey dbElementDefinition,
262                             UUID instanceId) {
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);
267         }
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);
272         }
273     }
274
275     private InstantiationResponse migratePrecheckAc(
276         AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
277
278         acToBeUpdated.setPrecheck(true);
279         var copyAc = new AutomationComposition(acToBeUpdated);
280         // Iterate and update the element property values
281         updateElementsProperties(automationComposition, copyAc);
282
283         // Publish migrate event to the participants
284         supervisionAcHandler.migratePrecheck(copyAc);
285
286         AcmUtils.setCascadedState(acToBeUpdated, DeployState.DEPLOYED, LockState.LOCKED,
287             SubState.MIGRATION_PRECHECKING);
288         acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
289
290         return createInstantiationResponse(automationCompositionProvider.updateAutomationComposition(acToBeUpdated));
291     }
292
293     private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition) {
294         return validateAutomationComposition(automationComposition, automationComposition.getCompositionId());
295     }
296
297     /**
298      * Validate AutomationComposition.
299      *
300      * @param automationComposition AutomationComposition to validate
301      * @param compositionId         the composition id
302      * @return the result of validation
303      */
304     private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition,
305                                                                UUID compositionId) {
306
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"));
312             return result;
313         }
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"));
317             return result;
318         }
319         var participantIds = acDefinitionOpt.get().getElementStateMap().values().stream()
320             .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
321
322         participantProvider.verifyParticipantState(participantIds);
323
324         result.addResult(AcmUtils.validateAutomationComposition(automationComposition,
325             acDefinitionOpt.get().getServiceTemplate(),
326             acRuntimeParameterGroup.getAcmParameters().getToscaCompositionName()));
327
328         result.addResult(automationCompositionProvider.validateElementIds(automationComposition));
329
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);
335             }
336         }
337
338         return result;
339     }
340
341
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));
347         }
348     }
349
350     /**
351      * Get Automation Composition.
352      *
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
356      */
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);
364         }
365         return automationComposition;
366     }
367
368     /**
369      * Delete the automation composition with the given name and version.
370      *
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
374      */
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,
379             null, null,
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);
387         }
388         supervisionAcHandler.delete(automationComposition, acDefinition);
389         return createInstantiationResponse(automationComposition);
390     }
391
392     /**
393      * Get the requested automation compositions.
394      *
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
399      */
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));
407
408         return automationCompositions;
409     }
410
411     /**
412      * Handle Composition Instance State.
413      *
414      * @param compositionId         the compositionId
415      * @param instanceId            the instanceId
416      * @param acInstanceStateUpdate the AcInstanceStateUpdate
417      */
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());
426         switch (result) {
427             case "DEPLOY":
428                 supervisionAcHandler.deploy(automationComposition, acDefinition);
429                 break;
430
431             case "UNDEPLOY":
432                 supervisionAcHandler.undeploy(automationComposition, acDefinition);
433                 break;
434
435             case "LOCK":
436                 supervisionAcHandler.lock(automationComposition, acDefinition);
437                 break;
438
439             case "UNLOCK":
440                 supervisionAcHandler.unlock(automationComposition, acDefinition);
441                 break;
442
443             case "PREPARE":
444                 supervisionAcHandler.prepare(automationComposition, acDefinition);
445                 break;
446
447             case "REVIEW":
448                 supervisionAcHandler.review(automationComposition);
449                 break;
450
451             default:
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);
456         }
457     }
458
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());
468             } else {
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());
474             }
475         }
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));
479
480         var validationResult =
481             validateAutomationComposition(acToBeUpdated, automationComposition.getCompositionTargetId());
482         if (!validationResult.isValid()) {
483             throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
484         }
485         acToBeUpdated.setCompositionTargetId(automationComposition.getCompositionTargetId());
486         return elementsRemoved;
487     }
488
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);
494         }
495     }
496
497     private AutomationCompositionDefinition getAcDefinition(UUID compositionId,
498                                                             AutomationComposition automationComposition) {
499         validateCompositionRequested(compositionId, automationComposition);
500         var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
501
502         var participantIds = acDefinition.getElementStateMap().values().stream()
503             .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
504
505         participantProvider.verifyParticipantState(participantIds);
506         return acDefinition;
507     }
508 }