653d97338af09aff051dcc06d5d07554d48bd446
[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.ArrayList;
27 import java.util.List;
28 import java.util.UUID;
29 import java.util.stream.Collectors;
30 import lombok.NonNull;
31 import lombok.RequiredArgsConstructor;
32 import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup;
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
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         if (automationComposition.getRestarting() != null) {
148             throw new PfModelRuntimeException(Status.BAD_REQUEST, "There is a restarting process, Update not allowed");
149         }
150
151         var deployOrder = DeployOrder.UPDATE;
152         var subOrder = SubOrder.NONE;
153
154         if (automationComposition.getCompositionTargetId() != null) {
155
156             if (Boolean.TRUE.equals(automationComposition.getPrecheck())) {
157                 subOrder = SubOrder.MIGRATE_PRECHECK;
158                 deployOrder = DeployOrder.NONE;
159             } else {
160                 deployOrder = DeployOrder.MIGRATE;
161             }
162         }
163         var result = acInstanceStateResolver.resolve(deployOrder, LockOrder.NONE, subOrder,
164                 acToUpdate.getDeployState(), acToUpdate.getLockState(), acToUpdate.getSubState(),
165                 acToUpdate.getStateChangeResult());
166         return switch (result) {
167             case "UPDATE" -> updateDeployedAutomationComposition(automationComposition, acToUpdate);
168
169             case "MIGRATE" -> migrateAutomationComposition(automationComposition, acToUpdate);
170
171             case "MIGRATE_PRECHECK" -> migratePrecheckAc(automationComposition, acToUpdate);
172
173             default -> throw new PfModelRuntimeException(Status.BAD_REQUEST,
174                     "Not allowed to " + deployOrder + " in the state " + acToUpdate.getDeployState());
175         };
176     }
177
178     /**
179      * Update deployed AC Element properties.
180      *
181      * @param automationComposition the automation composition
182      * @param acToBeUpdated the composition to be updated
183      * @return the result of the update
184      */
185     private InstantiationResponse updateDeployedAutomationComposition(
186             AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
187
188         // Iterate and update the element property values
189         for (var element : automationComposition.getElements().entrySet()) {
190             var elementId = element.getKey();
191             var dbAcElement = acToBeUpdated.getElements().get(elementId);
192             if (dbAcElement == null) {
193                 throw new PfModelRuntimeException(Status.BAD_REQUEST, "Element id not present " + elementId);
194             }
195             AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
196         }
197
198         var validationResult = validateAutomationComposition(acToBeUpdated);
199         if (!validationResult.isValid()) {
200             throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
201         }
202         // Publish property update event to the participants
203         supervisionAcHandler.update(acToBeUpdated);
204
205         automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
206         return createInstantiationResponse(automationComposition);
207     }
208
209     private InstantiationResponse migrateAutomationComposition(
210         AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
211
212         if (!DeployState.DEPLOYED.equals(acToBeUpdated.getDeployState())) {
213             throw new PfModelRuntimeException(Status.BAD_REQUEST,
214                 "Not allowed to migrate in the state " + acToBeUpdated.getDeployState());
215         }
216
217         // Iterate and update the element property values
218         for (var element : automationComposition.getElements().entrySet()) {
219             var elementId = element.getKey();
220             var dbAcElement = acToBeUpdated.getElements().get(elementId);
221             // Add additional elements if present for migration
222             if (dbAcElement == null) {
223                 LOGGER.info("New Ac element {} added in Migration", elementId);
224                 acToBeUpdated.getElements().put(elementId, automationComposition.getElements().get(elementId));
225             } else {
226                 AcmUtils.recursiveMerge(dbAcElement.getProperties(), element.getValue().getProperties());
227                 var newDefinition = element.getValue().getDefinition().asConceptKey();
228                 var dbElementDefinition = dbAcElement.getDefinition().asConceptKey();
229                 checkCompatibility(newDefinition, dbElementDefinition, automationComposition.getInstanceId());
230                 dbAcElement.setDefinition(element.getValue().getDefinition());
231             }
232         }
233         // Remove element which is not present in the new Ac instance
234         List<UUID> elementsRemoved = new ArrayList<>();
235         for (var dbElement : acToBeUpdated.getElements().entrySet()) {
236             var dbElementId = dbElement.getKey();
237             if (automationComposition.getElements().get(dbElementId) == null) {
238                 LOGGER.info("Element with id {} is removed in Migration", dbElementId);
239                 elementsRemoved.add(dbElementId);
240                 automationCompositionProvider.deleteAutomationCompositionElement(dbElementId);
241             }
242         }
243         elementsRemoved.forEach(uuid -> acToBeUpdated.getElements().remove(uuid));
244
245         var validationResult =
246             validateAutomationComposition(acToBeUpdated, automationComposition.getCompositionTargetId());
247         if (!validationResult.isValid()) {
248             throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
249         }
250         acToBeUpdated.setCompositionTargetId(automationComposition.getCompositionTargetId());
251
252         // Publish migrate event to the participants
253         supervisionAcHandler.migrate(acToBeUpdated);
254
255         automationComposition = automationCompositionProvider.updateAutomationComposition(acToBeUpdated);
256         elementsRemoved.forEach(automationCompositionProvider::deleteAutomationCompositionElement);
257         return createInstantiationResponse(automationComposition);
258     }
259
260     void checkCompatibility(PfConceptKey newDefinition, PfConceptKey dbElementDefinition,
261                             UUID instanceId) {
262         var compatibility = newDefinition.getCompatibility(dbElementDefinition);
263         if (PfKey.Compatibility.DIFFERENT.equals(compatibility)) {
264             throw new PfModelRuntimeException(Status.BAD_REQUEST,
265                     dbElementDefinition + " is not compatible with " + newDefinition);
266         }
267         if (PfKey.Compatibility.MAJOR.equals(compatibility) || PfKey.Compatibility.MINOR
268                 .equals(compatibility)) {
269             LOGGER.warn("Migrate {}: Version {} has {} compatibility with {} ", instanceId, newDefinition,
270                     compatibility, dbElementDefinition);
271         }
272     }
273
274     private InstantiationResponse migratePrecheckAc(
275             AutomationComposition automationComposition, AutomationComposition acToBeUpdated) {
276
277         acToBeUpdated.setPrecheck(true);
278         var copyAc = new AutomationComposition(acToBeUpdated);
279         // Iterate and update the element property values
280         for (var element : automationComposition.getElements().entrySet()) {
281             var elementId = element.getKey();
282             var copyElement = copyAc.getElements().get(elementId);
283             // Add additional elements if present for migration
284             if (copyElement == null) {
285                 LOGGER.info("New Ac element {} added in Migration", elementId);
286                 copyAc.getElements().put(elementId, automationComposition.getElements().get(elementId));
287             } else {
288                 AcmUtils.recursiveMerge(copyElement.getProperties(), element.getValue().getProperties());
289                 var newDefinition = element.getValue().getDefinition().asConceptKey();
290                 var copyElementDefinition = copyElement.getDefinition().asConceptKey();
291                 checkCompatibility(newDefinition, copyElementDefinition, automationComposition.getInstanceId());
292                 copyElement.setDefinition(element.getValue().getDefinition());
293             }
294         }
295         // Remove element which is not present in the new Ac instance
296         List<UUID> elementsRemoved = new ArrayList<>();
297         for (var dbElement : copyAc.getElements().entrySet()) {
298             var dbElementId = dbElement.getKey();
299             if (automationComposition.getElements().get(dbElementId) == null) {
300                 LOGGER.info("Element with id {} is removed in Migration", dbElementId);
301                 elementsRemoved.add(dbElementId);
302             }
303         }
304
305         elementsRemoved.forEach(uuid -> copyAc.getElements().remove(uuid));
306
307         var validationResult =
308                 validateAutomationComposition(copyAc, automationComposition.getCompositionTargetId());
309         if (!validationResult.isValid()) {
310             throw new PfModelRuntimeException(Status.BAD_REQUEST, validationResult.getResult());
311         }
312         copyAc.setCompositionTargetId(automationComposition.getCompositionTargetId());
313
314         // Publish migrate event to the participants
315         supervisionAcHandler.migratePrecheck(copyAc);
316
317         AcmUtils.setCascadedState(acToBeUpdated, DeployState.DEPLOYED, LockState.LOCKED,
318             SubState.MIGRATION_PRECHECKING);
319         acToBeUpdated.setStateChangeResult(StateChangeResult.NO_ERROR);
320
321         return createInstantiationResponse(automationCompositionProvider.updateAutomationComposition(acToBeUpdated));
322     }
323
324     private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition) {
325         return validateAutomationComposition(automationComposition, automationComposition.getCompositionId());
326     }
327
328     /**
329      * Validate AutomationComposition.
330      *
331      * @param automationComposition AutomationComposition to validate
332      * @param compositionId the composition id
333      * @return the result of validation
334      */
335     private BeanValidationResult validateAutomationComposition(AutomationComposition automationComposition,
336             UUID compositionId) {
337
338         var result = new BeanValidationResult("AutomationComposition", automationComposition);
339         var acDefinitionOpt = acDefinitionProvider.findAcDefinition(compositionId);
340         if (acDefinitionOpt.isEmpty()) {
341             result.addResult(new ObjectValidationResult("ServiceTemplate", compositionId, ValidationStatus.INVALID,
342                     "Commissioned automation composition definition not found"));
343             return result;
344         }
345         if (!AcTypeState.PRIMED.equals(acDefinitionOpt.get().getState())) {
346             result.addResult(new ObjectValidationResult("ServiceTemplate.state", acDefinitionOpt.get().getState(),
347                     ValidationStatus.INVALID, "Commissioned automation composition definition not primed"));
348             return result;
349         }
350         if (acDefinitionOpt.get().getRestarting() != null) {
351             result.addResult(
352                     new ObjectValidationResult("ServiceTemplate.restarting", acDefinitionOpt.get().getRestarting(),
353                             ValidationStatus.INVALID, "There is a restarting process in composition"));
354             return result;
355         }
356         var participantIds = acDefinitionOpt.get().getElementStateMap().values().stream()
357                 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
358
359         participantProvider.verifyParticipantState(participantIds);
360
361         result.addResult(AcmUtils.validateAutomationComposition(automationComposition,
362                 acDefinitionOpt.get().getServiceTemplate(),
363                 acRuntimeParameterGroup.getAcmParameters().getToscaCompositionName()));
364
365         result.addResult(automationCompositionProvider.validateElementIds(automationComposition));
366
367         if (result.isValid()) {
368             for (var element : automationComposition.getElements().values()) {
369                 var name = element.getDefinition().getName();
370                 var participantId = acDefinitionOpt.get().getElementStateMap().get(name).getParticipantId();
371                 element.setParticipantId(participantId);
372             }
373         }
374
375         return result;
376     }
377
378     /**
379      * Get Automation Composition.
380      *
381      * @param compositionId The UUID of the automation composition definition
382      * @param instanceId The UUID of the automation composition instance
383      * @return the Automation Composition
384      */
385     @Transactional(readOnly = true)
386     public AutomationComposition getAutomationComposition(@NonNull UUID compositionId, UUID instanceId) {
387         var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
388         if (!compositionId.equals(automationComposition.getCompositionId())
389                         && !compositionId.equals(automationComposition.getCompositionTargetId())) {
390             throw new PfModelRuntimeException(Status.BAD_REQUEST,
391                     automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
392         }
393         return automationComposition;
394     }
395
396     /**
397      * Delete the automation composition with the given name and version.
398      *
399      * @param compositionId The UUID of the automation composition definition
400      * @param instanceId The UUID of the automation composition instance
401      * @return the result of the deletion
402      */
403     public InstantiationResponse deleteAutomationComposition(UUID compositionId, UUID instanceId) {
404         var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
405         if (!compositionId.equals(automationComposition.getCompositionId())) {
406             throw new PfModelRuntimeException(Status.BAD_REQUEST,
407                     automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
408         }
409         if (!DeployState.UNDEPLOYED.equals(automationComposition.getDeployState())
410                 && !DeployState.DELETING.equals(automationComposition.getDeployState())) {
411             throw new PfModelRuntimeException(Status.BAD_REQUEST,
412                     "Automation composition state is still " + automationComposition.getDeployState());
413         }
414         if (DeployState.DELETING.equals(automationComposition.getDeployState())
415                 && StateChangeResult.NO_ERROR.equals(automationComposition.getStateChangeResult())) {
416             throw new PfModelRuntimeException(Status.BAD_REQUEST,
417                     "Automation composition state is still " + automationComposition.getDeployState());
418         }
419         if (automationComposition.getRestarting() != null) {
420             throw new PfModelRuntimeException(Status.BAD_REQUEST, "There is a restarting process, Delete not allowed");
421         }
422         var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
423         var participantIds = acDefinition.getElementStateMap().values().stream()
424             .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
425         participantProvider.verifyParticipantState(participantIds);
426         supervisionAcHandler.delete(automationComposition, acDefinition);
427         var response = new InstantiationResponse();
428         response.setInstanceId(automationComposition.getInstanceId());
429         response.setAffectedAutomationComposition(automationComposition.getKey().asIdentifier());
430         return response;
431     }
432
433     /**
434      * Get the requested automation compositions.
435      *
436      * @param name the name of the automation composition to get, null for all automation compositions
437      * @param version the version of the automation composition to get, null for all automation compositions
438      * @return the automation compositions
439      */
440     @Transactional(readOnly = true)
441     public AutomationCompositions getAutomationCompositions(UUID compositionId, String name, String version) {
442         var automationCompositions = new AutomationCompositions();
443         automationCompositions.setAutomationCompositionList(
444                 automationCompositionProvider.getAutomationCompositions(compositionId, name, version));
445
446         return automationCompositions;
447     }
448
449     /**
450      * Handle Composition Instance State.
451      *
452      * @param compositionId the compositionId
453      * @param instanceId the instanceId
454      * @param acInstanceStateUpdate the AcInstanceStateUpdate
455      */
456     public void compositionInstanceState(UUID compositionId, UUID instanceId,
457             @Valid AcInstanceStateUpdate acInstanceStateUpdate) {
458         var automationComposition = automationCompositionProvider.getAutomationComposition(instanceId);
459         if (!compositionId.equals(automationComposition.getCompositionId())) {
460             throw new PfModelRuntimeException(Status.BAD_REQUEST,
461                     automationComposition.getCompositionId() + DO_NOT_MATCH + compositionId);
462         }
463         var acDefinition = acDefinitionProvider.getAcDefinition(automationComposition.getCompositionId());
464
465         var participantIds = acDefinition.getElementStateMap().values().stream()
466                 .map(NodeTemplateState::getParticipantId).collect(Collectors.toSet());
467
468         participantProvider.verifyParticipantState(participantIds);
469         var result = acInstanceStateResolver.resolve(acInstanceStateUpdate.getDeployOrder(),
470                 acInstanceStateUpdate.getLockOrder(), acInstanceStateUpdate.getSubOrder(),
471                 automationComposition.getDeployState(), automationComposition.getLockState(),
472                 automationComposition.getSubState(), automationComposition.getStateChangeResult());
473         switch (result) {
474             case "DEPLOY":
475                 supervisionAcHandler.deploy(automationComposition, acDefinition);
476                 break;
477
478             case "UNDEPLOY":
479                 supervisionAcHandler.undeploy(automationComposition, acDefinition);
480                 break;
481
482             case "LOCK":
483                 supervisionAcHandler.lock(automationComposition, acDefinition);
484                 break;
485
486             case "UNLOCK":
487                 supervisionAcHandler.unlock(automationComposition, acDefinition);
488                 break;
489
490             case "PREPARE":
491                 supervisionAcHandler.prepare(automationComposition);
492                 break;
493
494             case "REVIEW":
495                 supervisionAcHandler.review(automationComposition);
496                 break;
497
498             default:
499                 throw new PfModelRuntimeException(Status.BAD_REQUEST, "Not valid " + acInstanceStateUpdate);
500         }
501     }
502 }