eacd07fc962bca232ccf81198a70a7f7c1373c51
[policy/clamp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * Copyright (C) 2021-2025 Nordix Foundation.
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.AutomationCompositions;
37 import org.onap.policy.clamp.models.acm.concepts.DeployState;
38 import org.onap.policy.clamp.models.acm.concepts.LockState;
39 import org.onap.policy.clamp.models.acm.concepts.NodeTemplateState;
40 import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
41 import org.onap.policy.clamp.models.acm.concepts.SubState;
42 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.AcInstanceStateUpdate;
43 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.DeployOrder;
44 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.InstantiationResponse;
45 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.LockOrder;
46 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.SubOrder;
47 import org.onap.policy.clamp.models.acm.persistence.provider.AcDefinitionProvider;
48 import org.onap.policy.clamp.models.acm.persistence.provider.AcInstanceStateResolver;
49 import org.onap.policy.clamp.models.acm.persistence.provider.AutomationCompositionProvider;
50 import org.onap.policy.clamp.models.acm.persistence.provider.ParticipantProvider;
51 import org.onap.policy.clamp.models.acm.utils.AcmUtils;
52 import org.onap.policy.common.parameters.BeanValidationResult;
53 import org.onap.policy.common.parameters.ObjectValidationResult;
54 import org.onap.policy.common.parameters.ValidationStatus;
55 import org.onap.policy.models.base.PfConceptKey;
56 import org.onap.policy.models.base.PfKey;
57 import org.onap.policy.models.base.PfModelRuntimeException;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60 import org.springframework.stereotype.Service;
61 import org.springframework.transaction.annotation.Transactional;
62
63 /**
64  * This class is dedicated to the Instantiation of Commissioned automation composition.
65  */
66 @Service
67 @Transactional
68 @RequiredArgsConstructor
69 public class AutomationCompositionInstantiationProvider {
70     private static final String DO_NOT_MATCH = " do not match with ";
71     private static final String ELEMENT_ID_NOT_PRESENT = "Element id not present ";
72     private static final String NOT_VALID_ORDER =
73             "Not valid order %s; DeployState: %s; LockState: %s; SubState: %s; StateChangeResult: %s";
74
75     private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionInstantiationProvider.class);
76
77     private final AutomationCompositionProvider automationCompositionProvider;
78     private final AcDefinitionProvider acDefinitionProvider;
79     private final AcInstanceStateResolver acInstanceStateResolver;
80     private final SupervisionAcHandler supervisionAcHandler;
81     private final ParticipantProvider participantProvider;
82     private final AcRuntimeParameterGroup acRuntimeParameterGroup;
83     private final EncryptionUtils encryptionUtils;
84
85     /**
86      * Create automation composition.
87      *
88      * @param compositionId The UUID of the automation composition definition
89      * @param automationComposition the automation composition
90      * @return the result of the instantiation operation
91      */
92     public InstantiationResponse createAutomationComposition(UUID compositionId,
93             AutomationComposition automationComposition) {
94         if (!compositionId.equals(automationComposition.getCompositionId())) {
95             throw new PfModelRuntimeException(Status.BAD_REQUEST,
96                     automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
97         }
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         if (!compositionId.equals(acToUpdate.getCompositionId())) {
134             throw new PfModelRuntimeException(Status.BAD_REQUEST,
135                     automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
136         }
137         if (DeployState.UNDEPLOYED.equals(acToUpdate.getDeployState())) {
138             acToUpdate.setElements(automationComposition.getElements());
139             acToUpdate.setName(automationComposition.getName());
140             acToUpdate.setVersion(automationComposition.getVersion());
141             acToUpdate.setDescription(automationComposition.getDescription());
142             acToUpdate.setDerivedFrom(automationComposition.getDerivedFrom());
143             var validationResult = validateAutomationComposition(acToUpdate);
144             if (!validationResult.isValid()) {
145                 throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
146             }
147             encryptInstanceProperties(acToUpdate, compositionId);
148             automationComposition = automationCompositionProvider.updateAutomationComposition(acToUpdate);
149             return createInstantiationResponse(automationComposition);
150
151         }
152
153         var deployOrder = DeployOrder.UPDATE;
154         var subOrder = SubOrder.NONE;
155
156         if (automationComposition.getCompositionTargetId() != null) {
157
158             if (Boolean.TRUE.equals(automationComposition.getPrecheck())) {
159                 subOrder = SubOrder.MIGRATE_PRECHECK;
160                 deployOrder = DeployOrder.NONE;
161             } else {
162                 deployOrder = DeployOrder.MIGRATE;
163             }
164         }
165         var result = acInstanceStateResolver.resolve(deployOrder, LockOrder.NONE, subOrder,
166                 acToUpdate.getDeployState(), acToUpdate.getLockState(), acToUpdate.getSubState(),
167                 acToUpdate.getStateChangeResult());
168         return switch (result) {
169             case "UPDATE" -> updateDeployedAutomationComposition(automationComposition, acToUpdate);
170
171             case "MIGRATE" -> migrateAutomationComposition(automationComposition, acToUpdate);
172
173             case "MIGRATE_PRECHECK" -> migratePrecheckAc(automationComposition, acToUpdate);
174
175             default -> throw new PfModelRuntimeException(Status.BAD_REQUEST,
176                     "Not allowed to " + deployOrder + " in the state " + acToUpdate.getDeployState());
177         };
178     }
179
180     /**
181      * Update deployed AC Element properties.
182      *
183      * @param automationComposition the automation composition
184      * @param acToBeUpdated the composition to be updated
185      * @return the result of the update
186      */
187     private InstantiationResponse updateDeployedAutomationComposition(
188             AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
189
190         // Iterate and update the element property values
191         for (var element : automationComposition.getElements().entrySet()) {
192             var elementId = element.getKey();
193             var dbAcElement = acToBeUpdated.getElements().get(elementId);
194             if (dbAcElement == null) {
195                 throw new PfModelRuntimeException(Status.BAD_REQUEST, ELEMENT_ID_NOT_PRESENT + elementId);
196             }
197             AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
198         }
199
200         var validationResult = validateAutomationComposition(acToBeUpdated);
201         if (!validationResult.isValid()) {
202             throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
203         }
204         // Publish property update event to the participants
205         supervisionAcHandler.update(acToBeUpdated);
206
207         encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionId());
208
209         automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
210         return createInstantiationResponse(automationComposition);
211     }
212
213     private InstantiationResponse migrateAutomationComposition(
214         AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
215
216         if (!DeployState.DEPLOYED.equals(acToBeUpdated.getDeployState())) {
217             throw new PfModelRuntimeException(Status.BAD_REQUEST,
218                 "Not allowed to migrate in the state " + acToBeUpdated.getDeployState());
219         }
220
221         // Iterate and update the element property values
222         for (var element : automationComposition.getElements().entrySet()) {
223             var elementId = element.getKey();
224             var dbAcElement = acToBeUpdated.getElements().get(elementId);
225             // Add additional elements if present for migration
226             if (dbAcElement == null) {
227                 LOGGER.info("New Ac element {} added in Migration", elementId);
228                 acToBeUpdated.getElements().put(elementId, element.getValue());
229             } else {
230                 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
231                 var newDefinition = element.getValue().getDefinition().asConceptKey();
232                 var dbElementDefinition = dbAcElement.getDefinition().asConceptKey();
233                 checkCompatibility(newDefinition, dbElementDefinition, automationComposition.getInstanceId());
234                 dbAcElement.setDefinition(element.getValue().getDefinition());
235             }
236         }
237         // Remove element which is not present in the new Ac instance
238         var elementsRemoved = getElementRemoved(acToBeUpdated, automationComposition);
239         elementsRemoved.forEach(uuid -> acToBeUpdated.getElements().remove(uuid));
240
241         var validationResult =
242                 validateAutomationComposition(acToBeUpdated, automationComposition.getCompositionTargetId());
243         if (!validationResult.isValid()) {
244             throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
245         }
246         acToBeUpdated.setCompositionTargetId(automationComposition.getCompositionTargetId());
247         var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionTargetId());
248         // Publish migrate event to the participants
249         supervisionAcHandler.migrate(acToBeUpdated, acDefinition.getServiceTemplate());
250
251         encryptInstanceProperties(acToBeUpdated, acToBeUpdated.getCompositionTargetId());
252
253         var ac = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
254         elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
255         return createInstantiationResponse(ac);
256     }
257
258     private List<UUID> getElementRemoved(AutomationComposition acFromDb, AutomationComposition acFromMigration) {
259         return acFromDb.getElements().keySet().stream()
260                 .filter(id -> acFromMigration.getElements().get(id) == null).toList();
261     }
262
263     void checkCompatibility(PfConceptKey newDefinition, PfConceptKey dbElementDefinition,
264                             UUID instanceId) {
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);
269         }
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);
274         }
275     }
276
277     private InstantiationResponse migratePrecheckAc(
278             AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
279
280         acToBeUpdated.setPrecheck(true);
281         var copyAc = new AutomationComposition(acToBeUpdated);
282         // Iterate and update the element property values
283         for (var element : automationComposition.getElements().entrySet()) {
284             var elementId = element.getKey();
285             var copyElement = copyAc.getElements().get(elementId);
286             // Add additional elements if present for migration
287             if (copyElement == null) {
288                 LOGGER.info("New Ac element {} added in Migration", elementId);
289                 copyAc.getElements().put(elementId, element.getValue());
290             } else {
291                 AcmUtils.recursiveMerge(copyElement.getProperties(), element.getValue().getProperties());
292                 var newDefinition = element.getValue().getDefinition().asConceptKey();
293                 var copyElementDefinition = copyElement.getDefinition().asConceptKey();
294                 checkCompatibility(newDefinition, copyElementDefinition, automationComposition.getInstanceId());
295                 copyElement.setDefinition(element.getValue().getDefinition());
296             }
297         }
298         // Remove element which is not present in the new Ac instance
299         var elementsRemoved = getElementRemoved(copyAc, automationComposition);
300         elementsRemoved.forEach(uuid -> copyAc.getElements().remove(uuid));
301
302         var validationResult =
303                 validateAutomationComposition(copyAc, automationComposition.getCompositionTargetId());
304         if (!validationResult.isValid()) {
305             throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
306         }
307         copyAc.setCompositionTargetId(automationComposition.getCompositionTargetId());
308
309         // Publish migrate event to the participants
310         supervisionAcHandler.migratePrecheck(copyAc);
311
312         AcmUtils.setCascadedState(acToBeUpdated, DeployState.DEPLOYED, LockState.LOCKED,
313             SubState.MIGRATION_PRECHECKING);
314         acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
315
316         return createInstantiationResponse(automationCompositionProvider.updateAutomationComposition(acToBeUpdated));
317     }
318
319     private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition) {
320         return validateAutomationComposition(automationComposition, automationComposition.getCompositionId());
321     }
322
323     /**
324      * Validate AutomationComposition.
325      *
326      * @param automationComposition AutomationComposition to validate
327      * @param compositionId the composition id
328      * @return the result of validation
329      */
330     private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition,
331             UUID compositionId) {
332
333         var result = new BeanValidationResult("AutomationComposition", automationComposition);
334         var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
335         if (acDefinitionOpt.isEmpty()) {
336             result.addResult(new ObjectValidationResult("ServiceTemplate", compositionId, ValidationStatus.INVALID,
337                     "Commissioned automation composition definition not found"));
338             return result;
339         }
340         if (!AcTypeState.PRIMED.equals(acDefinitionOpt.get().getState())) {
341             result.addResult(new ObjectValidationResult("ServiceTemplate.state", acDefinitionOpt.get().getState(),
342                     ValidationStatus.INVALID, "Commissioned automation composition definition not primed"));
343             return result;
344         }
345         var participantIds = acDefinitionOpt.get().getElementStateMap().values().stream()
346                 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
347
348         participantProvider.verifyParticipantState(participantIds);
349
350         result.addResult(AcmUtils.validateAutomationComposition(automationComposition,
351                 acDefinitionOpt.get().getServiceTemplate(),
352                 acRuntimeParameterGroup.getAcmParameters().getToscaCompositionName()));
353
354         result.addResult(automationCompositionProvider.validateElementIds(automationComposition));
355
356         if (result.isValid()) {
357             for (var element : automationComposition.getElements().values()) {
358                 var name = element.getDefinition().getName();
359                 var participantId = acDefinitionOpt.get().getElementStateMap().get(name).getParticipantId();
360                 element.setParticipantId(participantId);
361             }
362         }
363
364         return result;
365     }
366
367
368     private void encryptInstanceProperties(AutomationComposition automationComposition, UUID compositionId) {
369         if (encryptionUtils.encryptionEnabled()) {
370             var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
371             acDefinitionOpt.ifPresent(acDefinition
372                     -> encryptionUtils.findAndEncryptSensitiveData(acDefinition, automationComposition));
373         }
374     }
375
376     /**
377      * Get Automation Composition.
378      *
379      * @param compositionId The UUID of the automation composition definition
380      * @param instanceId The UUID of the automation composition instance
381      * @return the Automation Composition
382      */
383     @Transactional(readOnly = true)
384     public AutomationComposition getAutomationComposition(@NonNull UUID compositionId, UUID instanceId) {
385         var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
386         if (!compositionId.equals(automationComposition.getCompositionId())
387                         && !compositionId.equals(automationComposition.getCompositionTargetId())) {
388             throw new PfModelRuntimeException(Status.BAD_REQUEST,
389                     automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
390         }
391         return automationComposition;
392     }
393
394     /**
395      * Delete the automation composition with the given name and version.
396      *
397      * @param compositionId The UUID of the automation composition definition
398      * @param instanceId The UUID of the automation composition instance
399      * @return the result of the deletion
400      */
401     public InstantiationResponse deleteAutomationComposition(UUID compositionId, UUID instanceId) {
402         var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
403         if (!compositionId.equals(automationComposition.getCompositionId())) {
404             throw new PfModelRuntimeException(Status.BAD_REQUEST,
405                     automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
406         }
407         if (!DeployState.UNDEPLOYED.equals(automationComposition.getDeployState())
408                 && !DeployState.DELETING.equals(automationComposition.getDeployState())) {
409             throw new PfModelRuntimeException(Status.BAD_REQUEST,
410                     "Automation composition state is still " + automationComposition.getDeployState());
411         }
412         if (DeployState.DELETING.equals(automationComposition.getDeployState())
413                 && StateChangeResult.NO_ERROR.equals(automationComposition.getStateChangeResult())) {
414             throw new PfModelRuntimeException(Status.BAD_REQUEST,
415                     "Automation composition state is still " + automationComposition.getDeployState());
416         }
417         var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
418         var participantIds = acDefinition.getElementStateMap().values().stream()
419             .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
420         participantProvider.verifyParticipantState(participantIds);
421         supervisionAcHandler.delete(automationComposition, acDefinition);
422         var response = new InstantiationResponse();
423         response.setInstanceId(automationComposition.getInstanceId());
424         response.setAffectedAutomationComposition(automationComposition.getKey().asIdentifier());
425         return response;
426     }
427
428     /**
429      * Get the requested automation compositions.
430      *
431      * @param name the name of the automation composition to get, null for all automation compositions
432      * @param version the version of the automation composition to get, null for all automation compositions
433      * @return the automation compositions
434      */
435     @Transactional(readOnly = true)
436     public AutomationCompositions getAutomationCompositions(UUID compositionId, String name, String version) {
437         var automationCompositions = new AutomationCompositions();
438         automationCompositions.setAutomationCompositionList(
439                 automationCompositionProvider.getAutomationCompositions(compositionId, name, version));
440
441         return automationCompositions;
442     }
443
444     /**
445      * Handle Composition Instance State.
446      *
447      * @param compositionId the compositionId
448      * @param instanceId the instanceId
449      * @param acInstanceStateUpdate the AcInstanceStateUpdate
450      */
451     public void compositionInstanceState(UUID compositionId, UUID instanceId,
452             @Valid AcInstanceStateUpdate acInstanceStateUpdate) {
453         var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
454         if (!compositionId.equals(automationComposition.getCompositionId())) {
455             throw new PfModelRuntimeException(Status.BAD_REQUEST,
456                     automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
457         }
458         var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
459
460         var participantIds = acDefinition.getElementStateMap().values().stream()
461                 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
462
463         participantProvider.verifyParticipantState(participantIds);
464         var result = acInstanceStateResolver.resolve(acInstanceStateUpdate.getDeployOrder(),
465                 acInstanceStateUpdate.getLockOrder(), acInstanceStateUpdate.getSubOrder(),
466                 automationComposition.getDeployState(), automationComposition.getLockState(),
467                 automationComposition.getSubState(), automationComposition.getStateChangeResult());
468         switch (result) {
469             case "DEPLOY":
470                 supervisionAcHandler.deploy(automationComposition, acDefinition);
471                 break;
472
473             case "UNDEPLOY":
474                 supervisionAcHandler.undeploy(automationComposition, acDefinition);
475                 break;
476
477             case "LOCK":
478                 supervisionAcHandler.lock(automationComposition, acDefinition);
479                 break;
480
481             case "UNLOCK":
482                 supervisionAcHandler.unlock(automationComposition, acDefinition);
483                 break;
484
485             case "PREPARE":
486                 supervisionAcHandler.prepare(automationComposition);
487                 break;
488
489             case "REVIEW":
490                 supervisionAcHandler.review(automationComposition);
491                 break;
492
493             default:
494                 var msg = String.format(NOT_VALID_ORDER, acInstanceStateUpdate,
495                         automationComposition.getDeployState(), automationComposition.getLockState(),
496                         automationComposition.getSubState(), automationComposition.getStateChangeResult());
497                 throw new PfModelRuntimeException(Status.BAD_REQUEST, msg);
498         }
499     }
500 }