01c4428c08c3f0ab35b53abecd8346e7418e226b
[policy/clamp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2025 OpenInfra Foundation Europe. All rights reserved.
4  *  Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
5  * ================================================================================
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * SPDX-License-Identifier: Apache-2.0
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.clamp.acm.participant.intermediary.handler;
23
24 import java.util.List;
25 import java.util.Map;
26 import java.util.UUID;
27 import java.util.stream.Collectors;
28 import lombok.RequiredArgsConstructor;
29 import org.onap.policy.clamp.acm.participant.intermediary.api.CompositionElementDto;
30 import org.onap.policy.clamp.acm.participant.intermediary.api.ElementState;
31 import org.onap.policy.clamp.acm.participant.intermediary.api.InstanceElementDto;
32 import org.onap.policy.clamp.acm.participant.intermediary.comm.ParticipantMessagePublisher;
33 import org.onap.policy.clamp.models.acm.concepts.AcElementDeploy;
34 import org.onap.policy.clamp.models.acm.concepts.AutomationComposition;
35 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElement;
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.ParticipantDeploy;
39 import org.onap.policy.clamp.models.acm.concepts.ParticipantUtils;
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.kafka.participant.AutomationCompositionDeploy;
43 import org.onap.policy.clamp.models.acm.messages.kafka.participant.AutomationCompositionDeployAck;
44 import org.onap.policy.clamp.models.acm.messages.kafka.participant.AutomationCompositionMigration;
45 import org.onap.policy.clamp.models.acm.messages.kafka.participant.AutomationCompositionStateChange;
46 import org.onap.policy.clamp.models.acm.messages.kafka.participant.ParticipantMessageType;
47 import org.onap.policy.clamp.models.acm.messages.kafka.participant.PropertiesUpdate;
48 import org.onap.policy.clamp.models.acm.messages.rest.instantiation.DeployOrder;
49 import org.onap.policy.clamp.models.acm.utils.AcmUtils;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52 import org.springframework.stereotype.Component;
53
54 /*
55  * This class is responsible for managing the state of all automation compositions in the participant.
56  */
57 @Component
58 @RequiredArgsConstructor
59 public class AutomationCompositionHandler {
60     private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionHandler.class);
61
62     private final CacheProvider cacheProvider;
63     private final ParticipantMessagePublisher publisher;
64     private final ThreadHandler listener;
65
66     /**
67      * Handle a automation composition state change message.
68      *
69      * @param stateChangeMsg the state change message
70      */
71     public void handleAutomationCompositionStateChange(AutomationCompositionStateChange stateChangeMsg) {
72         if (stateChangeMsg.getAutomationCompositionId() == null) {
73             return;
74         }
75
76         var automationComposition = cacheProvider.getAutomationComposition(stateChangeMsg.getAutomationCompositionId());
77
78         if (automationComposition == null) {
79             if (DeployOrder.DELETE.equals(stateChangeMsg.getDeployOrderedState())) {
80                 var automationCompositionAck = new AutomationCompositionDeployAck(
81                         ParticipantMessageType.AUTOMATION_COMPOSITION_STATECHANGE_ACK);
82                 automationCompositionAck.setParticipantId(cacheProvider.getParticipantId());
83                 automationCompositionAck.setReplicaId(cacheProvider.getReplicaId());
84                 automationCompositionAck.setMessage("Already deleted or never used");
85                 automationCompositionAck.setResult(true);
86                 automationCompositionAck.setStateChangeResult(StateChangeResult.NO_ERROR);
87                 automationCompositionAck.setResponseTo(stateChangeMsg.getMessageId());
88                 automationCompositionAck.setAutomationCompositionId(stateChangeMsg.getAutomationCompositionId());
89                 publisher.sendAutomationCompositionAck(automationCompositionAck);
90             } else {
91                 LOGGER.debug("Automation composition {} does not use this participant",
92                         stateChangeMsg.getAutomationCompositionId());
93             }
94             return;
95         }
96
97         switch (stateChangeMsg.getDeployOrderedState()) {
98             case UNDEPLOY -> handleUndeployState(stateChangeMsg.getMessageId(), automationComposition,
99                     stateChangeMsg.getStartPhase());
100             case DELETE -> handleDeleteState(stateChangeMsg.getMessageId(), automationComposition,
101                     stateChangeMsg.getStartPhase());
102             default ->
103                     LOGGER.error("StateChange message has no state, state is null {}", automationComposition.getKey());
104         }
105     }
106
107     /**
108      * Handle a automation composition properties update message.
109      *
110      * @param updateMsg the properties update message
111      */
112     public void handleAcPropertyUpdate(PropertiesUpdate updateMsg) {
113
114         if (updateMsg.getParticipantUpdatesList().isEmpty()) {
115             LOGGER.warn("No AutomationCompositionElement updates in message {}",
116                     updateMsg.getAutomationCompositionId());
117             return;
118         }
119
120         for (var participantDeploy : updateMsg.getParticipantUpdatesList()) {
121             if (cacheProvider.getParticipantId().equals(participantDeploy.getParticipantId())) {
122                 var automationComposition =
123                         cacheProvider.getAutomationComposition(updateMsg.getAutomationCompositionId());
124                 automationComposition.setDeployState(DeployState.UPDATING);
125                 var acCopy = new AutomationComposition(automationComposition);
126                 updateExistingElementsOnThisParticipant(updateMsg.getAutomationCompositionId(), participantDeploy);
127
128                 callParticipantUpdateProperty(updateMsg.getMessageId(), participantDeploy.getAcElementList(), acCopy);
129             }
130         }
131     }
132
133     /**
134      * Handle a automation composition Deploy message.
135      *
136      * @param deployMsg the Deploy message
137      */
138     public void handleAutomationCompositionDeploy(AutomationCompositionDeploy deployMsg) {
139
140         if (deployMsg.getParticipantUpdatesList().isEmpty()) {
141             LOGGER.warn("No AutomationCompositionElement deploy in message {}", deployMsg.getAutomationCompositionId());
142             return;
143         }
144
145         for (var participantDeploy : deployMsg.getParticipantUpdatesList()) {
146             if (cacheProvider.getParticipantId().equals(participantDeploy.getParticipantId())) {
147                 if (deployMsg.isFirstStartPhase()) {
148                     cacheProvider.initializeAutomationComposition(deployMsg.getCompositionId(),
149                             deployMsg.getAutomationCompositionId(), participantDeploy);
150                 }
151                 callParticipanDeploy(deployMsg.getMessageId(), participantDeploy.getAcElementList(),
152                         deployMsg.getStartPhase(), deployMsg.getAutomationCompositionId());
153             }
154         }
155     }
156
157     private void callParticipanDeploy(UUID messageId, List<AcElementDeploy> acElementDeployList, Integer startPhaseMsg,
158             UUID instanceId) {
159         var automationComposition = cacheProvider.getAutomationComposition(instanceId);
160         automationComposition.setDeployState(DeployState.DEPLOYING);
161         for (var elementDeploy : acElementDeployList) {
162             var element = automationComposition.getElements().get(elementDeploy.getId());
163             var compositionInProperties = cacheProvider.getCommonProperties(automationComposition.getCompositionId(),
164                     element.getDefinition());
165             int startPhase = ParticipantUtils.findStartPhase(compositionInProperties);
166             if (startPhaseMsg.equals(startPhase)) {
167                 var compositionElement =
168                         cacheProvider.createCompositionElementDto(automationComposition.getCompositionId(), element,
169                                 compositionInProperties);
170                 var instanceElement =
171                         new InstanceElementDto(instanceId, elementDeploy.getId(), elementDeploy.getProperties(),
172                                 element.getOutProperties());
173                 listener.deploy(messageId, compositionElement, instanceElement);
174             }
175         }
176     }
177
178     private void callParticipantUpdateProperty(UUID messageId, List<AcElementDeploy> acElements,
179             AutomationComposition acCopy) {
180         var instanceElementDtoMap = cacheProvider.getInstanceElementDtoMap(acCopy);
181         var instanceElementDtoMapUpdated =
182                 cacheProvider.getInstanceElementDtoMap(cacheProvider.getAutomationComposition(acCopy.getInstanceId()));
183         var compositionElementDtoMap = cacheProvider.getCompositionElementDtoMap(acCopy);
184         for (var acElement : acElements) {
185             listener.update(messageId, compositionElementDtoMap.get(acElement.getId()),
186                     instanceElementDtoMap.get(acElement.getId()), instanceElementDtoMapUpdated.get(acElement.getId()));
187         }
188     }
189
190     private void migrateExistingElementsOnThisParticipant(UUID instanceId, UUID compositionTargetId,
191             ParticipantDeploy participantDeploy, int stage) {
192         var automationComposition = cacheProvider.getAutomationComposition(instanceId);
193         var acElementList = automationComposition.getElements();
194         for (var element : participantDeploy.getAcElementList()) {
195             var compositionInProperties =
196                     cacheProvider.getCommonProperties(compositionTargetId, element.getDefinition());
197             var stageSet = ParticipantUtils.findStageSetMigrate(compositionInProperties);
198             if (stageSet.contains(stage)) {
199                 var acElement = acElementList.get(element.getId());
200                 if (acElement == null) {
201                     var newElement = CacheProvider.createAutomationCompositionElement(element);
202                     newElement.setParticipantId(participantDeploy.getParticipantId());
203                     newElement.setDeployState(DeployState.MIGRATING);
204                     newElement.setLockState(LockState.LOCKED);
205                     newElement.setStage(stage);
206
207                     acElementList.put(element.getId(), newElement);
208                     LOGGER.info("New Ac Element with id {} is added in Migration", element.getId());
209                 } else {
210                     AcmUtils.recursiveMerge(acElement.getProperties(), element.getProperties());
211                     acElement.setDeployState(DeployState.MIGRATING);
212                     acElement.setStage(stage);
213                     acElement.setDefinition(element.getDefinition());
214                 }
215             }
216         }
217         // Check for missing elements and remove them from cache
218         var elementsToRemove = findElementsToRemove(participantDeploy.getAcElementList(), acElementList);
219         for (var key : elementsToRemove) {
220             acElementList.remove(key);
221             LOGGER.info("Element with id {} is removed in Migration", key);
222         }
223     }
224
225     private void updateExistingElementsOnThisParticipant(UUID instanceId, ParticipantDeploy participantDeploy) {
226         var acElementList = cacheProvider.getAutomationComposition(instanceId).getElements();
227         for (var element : participantDeploy.getAcElementList()) {
228             var acElement = acElementList.get(element.getId());
229             AcmUtils.recursiveMerge(acElement.getProperties(), element.getProperties());
230             acElement.setDeployState(DeployState.UPDATING);
231             acElement.setSubState(SubState.NONE);
232             acElement.setDefinition(element.getDefinition());
233         }
234     }
235
236     private List<UUID> findElementsToRemove(List<AcElementDeploy> acElementDeployList,
237             Map<UUID, AutomationCompositionElement> acElementList) {
238         var acElementDeploySet = acElementDeployList.stream().map(AcElementDeploy::getId).collect(Collectors.toSet());
239         return acElementList.keySet().stream().filter(id -> !acElementDeploySet.contains(id)).toList();
240     }
241
242     /**
243      * Method to handle when the new state from participant is UNINITIALISED state.
244      *
245      * @param messageId             the messageId
246      * @param automationComposition participant response
247      * @param startPhaseMsg         startPhase from message
248      */
249     private void handleUndeployState(UUID messageId, final AutomationComposition automationComposition,
250             Integer startPhaseMsg) {
251         automationComposition.setCompositionTargetId(null);
252         automationComposition.setDeployState(DeployState.UNDEPLOYING);
253         for (var element : automationComposition.getElements().values()) {
254             var compositionInProperties = cacheProvider.getCommonProperties(automationComposition.getCompositionId(),
255                     element.getDefinition());
256             int startPhase = ParticipantUtils.findStartPhase(compositionInProperties);
257             if (startPhaseMsg.equals(startPhase)) {
258                 element.setDeployState(DeployState.UNDEPLOYING);
259                 var compositionElement =
260                         cacheProvider.createCompositionElementDto(automationComposition.getCompositionId(), element,
261                                 compositionInProperties);
262                 var instanceElement = new InstanceElementDto(automationComposition.getInstanceId(), element.getId(),
263                         element.getProperties(), element.getOutProperties());
264                 listener.undeploy(messageId, compositionElement, instanceElement);
265             }
266         }
267     }
268
269     private void handleDeleteState(UUID messageId, final AutomationComposition automationComposition,
270             Integer startPhaseMsg) {
271         automationComposition.setDeployState(DeployState.DELETING);
272         for (var element : automationComposition.getElements().values()) {
273             var compositionInProperties = cacheProvider.getCommonProperties(automationComposition.getCompositionId(),
274                     element.getDefinition());
275             int startPhase = ParticipantUtils.findStartPhase(compositionInProperties);
276             if (startPhaseMsg.equals(startPhase)) {
277                 element.setDeployState(DeployState.DELETING);
278                 element.setSubState(SubState.NONE);
279                 var compositionElement =
280                         cacheProvider.createCompositionElementDto(automationComposition.getCompositionId(), element,
281                                 compositionInProperties);
282                 var instanceElement = new InstanceElementDto(automationComposition.getInstanceId(), element.getId(),
283                         element.getProperties(), element.getOutProperties());
284                 listener.delete(messageId, compositionElement, instanceElement);
285             }
286         }
287     }
288
289     /**
290      * Handles AutomationComposition Migration.
291      *
292      * @param migrationMsg the AutomationCompositionMigration
293      */
294     public void handleAutomationCompositionMigration(AutomationCompositionMigration migrationMsg) {
295         if (migrationMsg.getAutomationCompositionId() == null || migrationMsg.getCompositionTargetId() == null) {
296             return;
297         }
298
299         var automationComposition = cacheProvider.getAutomationComposition(migrationMsg.getAutomationCompositionId());
300         if (automationComposition == null) {
301             LOGGER.debug("Automation composition {} does not use this participant",
302                     migrationMsg.getAutomationCompositionId());
303             return;
304         }
305         var acCopy = new AutomationComposition(automationComposition);
306         automationComposition.setCompositionTargetId(migrationMsg.getCompositionTargetId());
307         automationComposition.setDeployState(DeployState.MIGRATING);
308         for (var participantDeploy : migrationMsg.getParticipantUpdatesList()) {
309             if (cacheProvider.getParticipantId().equals(participantDeploy.getParticipantId())) {
310
311                 migrateExistingElementsOnThisParticipant(migrationMsg.getAutomationCompositionId(),
312                         migrationMsg.getCompositionTargetId(), participantDeploy, migrationMsg.getStage());
313
314                 callParticipantMigrate(migrationMsg.getMessageId(), participantDeploy.getAcElementList(), acCopy,
315                         migrationMsg.getCompositionTargetId(), migrationMsg.getStage(),
316                         Boolean.TRUE.equals(migrationMsg.getRollback()));
317             }
318         }
319     }
320
321     private void callParticipantMigrate(UUID messageId, List<AcElementDeploy> acElements, AutomationComposition acCopy,
322             UUID compositionTargetId, int stage, boolean rollback) {
323         var compositionElementMap = cacheProvider.getCompositionElementDtoMap(acCopy);
324         var instanceElementMap = cacheProvider.getInstanceElementDtoMap(acCopy);
325         var automationComposition = cacheProvider.getAutomationComposition(acCopy.getInstanceId());
326         var compositionElementTargetMap =
327                 cacheProvider.getCompositionElementDtoMap(automationComposition, compositionTargetId);
328         var instanceElementMigrateMap = cacheProvider.getInstanceElementDtoMap(automationComposition);
329
330         // Call migrate for newly added and updated elements
331         for (var acElement : acElements) {
332             var compositionInProperties =
333                     cacheProvider.getCommonProperties(compositionTargetId, acElement.getDefinition());
334             var stageSet = ParticipantUtils.findStageSetMigrate(compositionInProperties);
335             if (stageSet.contains(stage)) {
336                 if (instanceElementMap.get(acElement.getId()) == null) {
337                     var compositionElementDto =
338                             new CompositionElementDto(acCopy.getCompositionId(), acElement.getDefinition(), Map.of(),
339                                     Map.of(), ElementState.NOT_PRESENT);
340                     var instanceElementDto =
341                             new InstanceElementDto(acCopy.getInstanceId(), acElement.getId(), Map.of(), Map.of(),
342                                     ElementState.NOT_PRESENT);
343                     var compositionElementTargetDto =
344                             CacheProvider.changeStateToNew(compositionElementTargetMap.get(acElement.getId()));
345                     var instanceElementMigrateDto =
346                             CacheProvider.changeStateToNew(instanceElementMigrateMap.get(acElement.getId()));
347
348                     listenerMigrate(messageId, compositionElementDto, compositionElementTargetDto, instanceElementDto,
349                             instanceElementMigrateDto, stage, rollback);
350                 } else {
351                     listenerMigrate(messageId, compositionElementMap.get(acElement.getId()),
352                             compositionElementTargetMap.get(acElement.getId()),
353                             instanceElementMap.get(acElement.getId()), instanceElementMigrateMap.get(acElement.getId()),
354                             stage, rollback);
355                 }
356             }
357         }
358         if (stage == 0) {
359             // Call migrate for removed elements
360             List<UUID> removedElements = findElementsToRemove(acElements, acCopy.getElements());
361             for (var elementId : removedElements) {
362                 var compositionDtoTarget = new CompositionElementDto(compositionTargetId,
363                         acCopy.getElements().get(elementId).getDefinition(), Map.of(), Map.of(), ElementState.REMOVED);
364                 var instanceDtoTarget = new InstanceElementDto(acCopy.getInstanceId(), elementId, Map.of(), Map.of(),
365                         ElementState.REMOVED);
366                 listenerMigrate(messageId, compositionElementMap.get(elementId), compositionDtoTarget,
367                         instanceElementMap.get(elementId), instanceDtoTarget, 0, rollback);
368             }
369         }
370     }
371
372     private void listenerMigrate(UUID messageId, CompositionElementDto compositionElement,
373             CompositionElementDto compositionElementTarget, InstanceElementDto instanceElement,
374             InstanceElementDto instanceElementMigrate, int stage, boolean rollback) {
375         if (rollback) {
376             listener.rollback(messageId, compositionElement, compositionElementTarget, instanceElement,
377                     instanceElementMigrate, stage);
378         } else {
379             listener.migrate(messageId, compositionElement, compositionElementTarget, instanceElement,
380                     instanceElementMigrate, stage);
381         }
382     }
383 }