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