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