6c560e7faff9c18a29a4624681750dc7df9e8ee0
[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.participant.intermediary.handler;
23
24
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.UUID;
29 import lombok.RequiredArgsConstructor;
30 import org.onap.policy.clamp.acm.participant.intermediary.api.CompositionElementDto;
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.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
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 = cacheProvider.getAutomationComposition(
124                         updateMsg.getAutomationCompositionId());
125                 automationComposition.setDeployState(DeployState.UPDATING);
126                 var acCopy = new AutomationComposition(automationComposition);
127                 updateExistingElementsOnThisParticipant(updateMsg.getAutomationCompositionId(), participantDeploy,
128                         DeployState.UPDATING);
129
130                 callParticipantUpdateProperty(updateMsg.getMessageId(), participantDeploy.getAcElementList(), acCopy);
131             }
132         }
133     }
134
135     /**
136      * Handle a automation composition Deploy message.
137      *
138      * @param deployMsg the Deploy message
139      */
140     public void handleAutomationCompositionDeploy(AutomationCompositionDeploy deployMsg) {
141
142         if (deployMsg.getParticipantUpdatesList().isEmpty()) {
143             LOGGER.warn("No AutomationCompositionElement deploy in message {}", deployMsg.getAutomationCompositionId());
144             return;
145         }
146
147         for (var participantDeploy : deployMsg.getParticipantUpdatesList()) {
148             if (cacheProvider.getParticipantId().equals(participantDeploy.getParticipantId())) {
149                 if (deployMsg.isFirstStartPhase()) {
150                     cacheProvider.initializeAutomationComposition(deployMsg.getCompositionId(),
151                             deployMsg.getAutomationCompositionId(), participantDeploy);
152                 }
153                 callParticipanDeploy(deployMsg.getMessageId(), participantDeploy.getAcElementList(),
154                         deployMsg.getStartPhase(), deployMsg.getAutomationCompositionId());
155             }
156         }
157     }
158
159     private void callParticipanDeploy(UUID messageId, List<AcElementDeploy> acElementDeployList,
160             Integer startPhaseMsg, 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
166                 .getCommonProperties(automationComposition.getCompositionId(), element.getDefinition());
167             int startPhase = ParticipantUtils.findStartPhase(compositionInProperties);
168             if (startPhaseMsg.equals(startPhase)) {
169                 var compositionElement = cacheProvider.createCompositionElementDto(
170                         automationComposition.getCompositionId(), element, compositionInProperties);
171                 var instanceElement = new InstanceElementDto(instanceId, elementDeploy.getId(),
172                     elementDeploy.getToscaServiceTemplateFragment(),
173                     elementDeploy.getProperties(), 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 = cacheProvider.getInstanceElementDtoMap(
183             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 updateExistingElementsOnThisParticipant(UUID instanceId, ParticipantDeploy participantDeploy,
192         DeployState deployState) {
193         var acElementList = cacheProvider.getAutomationComposition(instanceId).getElements();
194         for (var element : participantDeploy.getAcElementList()) {
195             var acElement = acElementList.get(element.getId());
196             if (acElement == null && deployState.equals(DeployState.MIGRATING)) {
197                 var newElement = new AutomationCompositionElement();
198                 newElement.setId(element.getId());
199                 newElement.setParticipantId(participantDeploy.getParticipantId());
200                 newElement.setDefinition(element.getDefinition());
201                 newElement.setDeployState(deployState);
202                 newElement.setSubState(SubState.NONE);
203                 newElement.setLockState(LockState.LOCKED);
204                 newElement.setProperties(element.getProperties());
205
206                 acElementList.put(element.getId(), newElement);
207                 LOGGER.info("New Ac Element with id {} is added in Migration", element.getId());
208             } else if (acElement != null) {
209                 AcmUtils.recursiveMerge(acElement.getProperties(), element.getProperties());
210                 acElement.setDeployState(deployState);
211                 acElement.setSubState(SubState.NONE);
212                 acElement.setDefinition(element.getDefinition());
213             }
214         }
215         if (deployState.equals(DeployState.MIGRATING)) {
216             // Check for missing elements and remove them from cache
217             List<UUID> elementsToRemove = findElementsToRemove(participantDeploy.getAcElementList(), acElementList);
218             for (UUID key : elementsToRemove) {
219                 acElementList.remove(key);
220                 LOGGER.info("Element with id {} is removed in Migration", key);
221             }
222         }
223     }
224
225     private List<UUID> findElementsToRemove(List<AcElementDeploy> acElementDeployList, Map<UUID,
226             AutomationCompositionElement> acElementList) {
227         List<UUID> elementsToRemove = new ArrayList<>();
228         for (var elementInCache : acElementList.entrySet()) {
229             boolean found = false;
230             for (var element : acElementDeployList) {
231                 if (element.getId().equals(elementInCache.getValue().getId())) {
232                     found = true;
233                     break;
234                 }
235             }
236             if (!found) {
237                 elementsToRemove.add(elementInCache.getKey());
238             }
239         }
240         return elementsToRemove;
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         var serviceTemplateFragment = cacheProvider
255                 .getServiceTemplateFragmentMap().get(automationComposition.getCompositionId());
256         for (var element : automationComposition.getElements().values()) {
257             var compositionInProperties = cacheProvider
258                 .getCommonProperties(automationComposition.getCompositionId(), element.getDefinition());
259             int startPhase = ParticipantUtils.findStartPhase(compositionInProperties);
260             if (startPhaseMsg.equals(startPhase)) {
261                 element.setDeployState(DeployState.UNDEPLOYING);
262                 var compositionElement = cacheProvider.createCompositionElementDto(
263                         automationComposition.getCompositionId(), element, compositionInProperties);
264                 var instanceElement = new InstanceElementDto(automationComposition.getInstanceId(), element.getId(),
265                         serviceTemplateFragment, element.getProperties(), element.getOutProperties());
266                 listener.undeploy(messageId, compositionElement, instanceElement);
267             }
268         }
269     }
270
271     private void handleDeleteState(UUID messageId, final AutomationComposition automationComposition,
272             Integer startPhaseMsg) {
273         automationComposition.setDeployState(DeployState.DELETING);
274         var serviceTemplateFragment = cacheProvider
275                 .getServiceTemplateFragmentMap().get(automationComposition.getCompositionId());
276         for (var element : automationComposition.getElements().values()) {
277             var compositionInProperties = cacheProvider
278                 .getCommonProperties(automationComposition.getCompositionId(), element.getDefinition());
279             int startPhase = ParticipantUtils.findStartPhase(compositionInProperties);
280             if (startPhaseMsg.equals(startPhase)) {
281                 element.setDeployState(DeployState.DELETING);
282                 element.setSubState(SubState.NONE);
283                 var compositionElement = cacheProvider.createCompositionElementDto(
284                         automationComposition.getCompositionId(), element, compositionInProperties);
285                 var instanceElement = new InstanceElementDto(automationComposition.getInstanceId(), element.getId(),
286                         serviceTemplateFragment, element.getProperties(), element.getOutProperties());
287                 listener.delete(messageId, compositionElement, instanceElement);
288             }
289         }
290     }
291
292     /**
293      * Handles AutomationComposition Migration.
294      *
295      * @param migrationMsg the AutomationCompositionMigration
296      */
297     public void handleAutomationCompositionMigration(AutomationCompositionMigration migrationMsg) {
298         if (migrationMsg.getAutomationCompositionId() == null || migrationMsg.getCompositionTargetId() == null) {
299             return;
300         }
301
302         var automationComposition = cacheProvider.getAutomationComposition(migrationMsg.getAutomationCompositionId());
303         if (automationComposition == null) {
304             LOGGER.debug("Automation composition {} does not use this participant",
305                     migrationMsg.getAutomationCompositionId());
306             return;
307         }
308         var acCopy = new AutomationComposition(automationComposition);
309         automationComposition.setCompositionTargetId(migrationMsg.getCompositionTargetId());
310         automationComposition.setDeployState(DeployState.MIGRATING);
311         for (var participantDeploy : migrationMsg.getParticipantUpdatesList()) {
312             if (cacheProvider.getParticipantId().equals(participantDeploy.getParticipantId())) {
313
314                 updateExistingElementsOnThisParticipant(migrationMsg.getAutomationCompositionId(), participantDeploy,
315                     DeployState.MIGRATING);
316
317                 callParticipantMigrate(migrationMsg.getMessageId(), participantDeploy.getAcElementList(),
318                     acCopy, migrationMsg.getCompositionTargetId());
319             }
320         }
321     }
322
323     private void callParticipantMigrate(UUID messageId, List<AcElementDeploy> acElements,
324             AutomationComposition acCopy, UUID compositionTargetId) {
325         var compositionElementMap = cacheProvider.getCompositionElementDtoMap(acCopy);
326         var instanceElementMap = cacheProvider.getInstanceElementDtoMap(acCopy);
327         var automationComposition = cacheProvider.getAutomationComposition(acCopy.getInstanceId());
328         var compositionElementTargetMap = cacheProvider.getCompositionElementDtoMap(automationComposition,
329             compositionTargetId);
330         var instanceElementMigrateMap = cacheProvider.getInstanceElementDtoMap(automationComposition);
331
332         // Call migrate for newly added and updated elements
333         for (var acElement : acElements) {
334             if (instanceElementMap.get(acElement.getId()) == null) {
335                 var compositionDto = new CompositionElementDto(acElement.getId(), acElement.getDefinition(),
336                         Map.of(), Map.of(), true, false);
337                 var instanceDto = new InstanceElementDto(acCopy.getInstanceId(), acElement.getId(),
338                         new ToscaServiceTemplate(), Map.of(), Map.of(), true, false);
339
340                 listener.migrate(messageId, compositionDto,
341                         compositionElementTargetMap.get(acElement.getId()),
342                         instanceDto, instanceElementMigrateMap.get(acElement.getId()));
343             } else {
344                 listener.migrate(messageId, compositionElementMap.get(acElement.getId()),
345                         compositionElementTargetMap.get(acElement.getId()),
346                         instanceElementMap.get(acElement.getId()), instanceElementMigrateMap.get(acElement.getId()));
347             }
348         }
349         // Call migrate for removed elements
350         List<UUID> removedElements = findElementsToRemove(acElements, acCopy.getElements());
351         for (var elementId : removedElements) {
352             var compositionDtoTarget = new CompositionElementDto(elementId, acCopy.getElements().get(elementId)
353                     .getDefinition(), Map.of(), Map.of(), false, true);
354             var instanceDtoTarget = new InstanceElementDto(acCopy.getInstanceId(), elementId,
355                     new ToscaServiceTemplate(), Map.of(), Map.of(), false, true);
356
357             listener.migrate(messageId, compositionElementMap.get(elementId),
358                     compositionDtoTarget,
359                     instanceElementMap.get(elementId), instanceDtoTarget);
360         }
361     }
362 }