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