66ac0c1dcb1d5caa1223e7cb53f120a01fbdc62c
[policy/clamp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2021-2022 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 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.LinkedHashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.UUID;
30 import java.util.stream.Collectors;
31 import lombok.Getter;
32 import org.onap.policy.clamp.acm.participant.intermediary.api.AutomationCompositionElementListener;
33 import org.onap.policy.clamp.acm.participant.intermediary.comm.ParticipantMessagePublisher;
34 import org.onap.policy.clamp.acm.participant.intermediary.parameters.ParticipantParameters;
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.AutomationCompositionElementAck;
38 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionElementDefinition;
39 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionOrderedState;
40 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositionState;
41 import org.onap.policy.clamp.models.acm.concepts.AutomationCompositions;
42 import org.onap.policy.clamp.models.acm.concepts.ParticipantUpdates;
43 import org.onap.policy.clamp.models.acm.concepts.ParticipantUtils;
44 import org.onap.policy.clamp.models.acm.messages.dmaap.participant.AutomationCompositionAck;
45 import org.onap.policy.clamp.models.acm.messages.dmaap.participant.AutomationCompositionStateChange;
46 import org.onap.policy.clamp.models.acm.messages.dmaap.participant.AutomationCompositionUpdate;
47 import org.onap.policy.clamp.models.acm.messages.dmaap.participant.ParticipantMessageType;
48 import org.onap.policy.models.base.PfModelException;
49 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
50 import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeTemplate;
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 public class AutomationCompositionHandler {
60     private static final Logger LOGGER = LoggerFactory.getLogger(AutomationCompositionHandler.class);
61
62     private final ToscaConceptIdentifier participantType;
63     private final ToscaConceptIdentifier participantId;
64     private final ParticipantMessagePublisher publisher;
65
66     @Getter
67     private final Map<UUID, AutomationComposition> automationCompositionMap = new LinkedHashMap<>();
68
69     @Getter
70     private final Map<UUID, AutomationCompositionElement> elementsOnThisParticipant = new LinkedHashMap<>();
71
72     @Getter
73     private List<AutomationCompositionElementListener> listeners = new ArrayList<>();
74
75     /**
76      * Constructor, set the participant ID and messageSender.
77      *
78      * @param parameters the parameters of the participant
79      * @param publisher the ParticipantMessage Publisher
80      */
81     public AutomationCompositionHandler(ParticipantParameters parameters, ParticipantMessagePublisher publisher) {
82         this.participantType = parameters.getIntermediaryParameters().getParticipantType();
83         this.participantId = parameters.getIntermediaryParameters().getParticipantId();
84         this.publisher = publisher;
85     }
86
87     public void registerAutomationCompositionElementListener(AutomationCompositionElementListener listener) {
88         listeners.add(listener);
89     }
90
91     /**
92      * Handle a automation composition element state change message.
93      *
94      * @param automationCompositionId the automationComposition Id
95      * @param id the automationComposition UUID
96      * @param orderedState the current state
97      * @param newState the ordered state
98      * @return automationCompositionElement the updated automation composition element
99      */
100     public AutomationCompositionElement updateAutomationCompositionElementState(
101         UUID automationCompositionId, UUID id, AutomationCompositionOrderedState orderedState,
102         AutomationCompositionState newState) {
103
104         if (id == null) {
105             LOGGER.warn("Cannot update Automation composition element state, id is null");
106             return null;
107         }
108
109         // Update states of AutomationCompositionElement in automationCompositionMap
110         for (var automationComposition : automationCompositionMap.values()) {
111             var element = automationComposition.getElements().get(id);
112             if (element != null) {
113                 element.setOrderedState(orderedState);
114                 element.setState(newState);
115             }
116             var checkOpt = automationComposition.getElements().values().stream()
117                 .filter(acElement -> !newState.equals(acElement.getState())).findAny();
118             if (checkOpt.isEmpty()) {
119                 automationComposition.setState(newState);
120                 automationComposition.setOrderedState(orderedState);
121             }
122         }
123
124         // Update states of AutomationCompositionElement in elementsOnThisParticipant
125         var acElement = elementsOnThisParticipant.get(id);
126         if (acElement != null) {
127             var automationCompositionStateChangeAck =
128                 new AutomationCompositionAck(ParticipantMessageType.AUTOMATION_COMPOSITION_STATECHANGE_ACK);
129             automationCompositionStateChangeAck.setParticipantId(participantId);
130             automationCompositionStateChangeAck.setParticipantType(participantType);
131             automationCompositionStateChangeAck.setAutomationCompositionId(automationCompositionId);
132             acElement.setOrderedState(orderedState);
133             acElement.setState(newState);
134             automationCompositionStateChangeAck.getAutomationCompositionResultMap().put(acElement.getId(),
135                 new AutomationCompositionElementAck(newState, true,
136                     "Automation composition element {} state changed to {}\", id, newState)"));
137             LOGGER.debug("Automation composition element {} state changed to {}", id, newState);
138             automationCompositionStateChangeAck
139                 .setMessage("AutomationCompositionElement state changed to {} " + newState);
140             automationCompositionStateChangeAck.setResult(true);
141             publisher.sendAutomationCompositionAck(automationCompositionStateChangeAck);
142             return acElement;
143         }
144         return null;
145     }
146
147     /**
148      * Handle a automation composition state change message.
149      *
150      * @param stateChangeMsg the state change message
151      * @param acElementDefinitions the list of AutomationCompositionElementDefinition
152      */
153     public void handleAutomationCompositionStateChange(AutomationCompositionStateChange stateChangeMsg,
154         List<AutomationCompositionElementDefinition> acElementDefinitions) {
155         if (stateChangeMsg.getAutomationCompositionId() == null) {
156             return;
157         }
158
159         var automationComposition = automationCompositionMap.get(stateChangeMsg.getAutomationCompositionId());
160
161         if (automationComposition == null) {
162             var automationCompositionAck =
163                 new AutomationCompositionAck(ParticipantMessageType.AUTOMATION_COMPOSITION_STATECHANGE_ACK);
164             automationCompositionAck.setParticipantId(participantId);
165             automationCompositionAck.setParticipantType(participantType);
166             automationCompositionAck.setMessage("Automation composition " + stateChangeMsg.getAutomationCompositionId()
167                 + " does not use this participant " + participantId);
168             automationCompositionAck.setResult(false);
169             automationCompositionAck.setResponseTo(stateChangeMsg.getMessageId());
170             automationCompositionAck.setAutomationCompositionId(stateChangeMsg.getAutomationCompositionId());
171             publisher.sendAutomationCompositionAck(automationCompositionAck);
172             LOGGER.debug("Automation composition {} does not use this participant",
173                 stateChangeMsg.getAutomationCompositionId());
174             return;
175         }
176
177         handleState(automationComposition, stateChangeMsg.getOrderedState(), stateChangeMsg.getStartPhase(),
178             acElementDefinitions);
179     }
180
181     /**
182      * Method to handle state changes.
183      *
184      * @param automationComposition participant response
185      * @param orderedState automation composition ordered state
186      * @param startPhaseMsg startPhase from message
187      * @param acElementDefinitions the list of AutomationCompositionElementDefinition
188      */
189     private void handleState(final AutomationComposition automationComposition,
190         AutomationCompositionOrderedState orderedState, Integer startPhaseMsg,
191         List<AutomationCompositionElementDefinition> acElementDefinitions) {
192         switch (orderedState) {
193             case UNINITIALISED:
194                 handleUninitialisedState(automationComposition, orderedState, startPhaseMsg, acElementDefinitions);
195                 break;
196             case PASSIVE:
197                 handlePassiveState(automationComposition, orderedState, startPhaseMsg, acElementDefinitions);
198                 break;
199             case RUNNING:
200                 handleRunningState(automationComposition, orderedState, startPhaseMsg, acElementDefinitions);
201                 break;
202             default:
203                 LOGGER.debug("StateChange message has no state, state is null {}",
204                     automationComposition.getKey());
205                 break;
206         }
207     }
208
209     /**
210      * Handle a automation composition update message.
211      *
212      * @param updateMsg the update message
213      * @param acElementDefinitions the list of AutomationCompositionElementDefinition
214      */
215     public void handleAutomationCompositionUpdate(AutomationCompositionUpdate updateMsg,
216         List<AutomationCompositionElementDefinition> acElementDefinitions) {
217
218         if (!updateMsg.appliesTo(participantType, participantId)) {
219             return;
220         }
221
222         if (0 == updateMsg.getStartPhase()) {
223             handleAcUpdatePhase0(updateMsg, acElementDefinitions);
224         } else {
225             handleAcUpdatePhaseN(updateMsg, acElementDefinitions);
226         }
227     }
228
229     private void handleAcUpdatePhase0(AutomationCompositionUpdate updateMsg,
230         List<AutomationCompositionElementDefinition> acElementDefinitions) {
231         var automationComposition = automationCompositionMap.get(updateMsg.getAutomationCompositionId());
232
233         // TODO: Updates to existing AutomationCompositions are not supported yet (Addition/Removal of
234         // AutomationComposition
235         // elements to existing AutomationComposition has to be supported).
236         if (automationComposition != null) {
237             var automationCompositionUpdateAck =
238                 new AutomationCompositionAck(ParticipantMessageType.AUTOMATION_COMPOSITION_UPDATE_ACK);
239             automationCompositionUpdateAck.setParticipantId(participantId);
240             automationCompositionUpdateAck.setParticipantType(participantType);
241
242             automationCompositionUpdateAck.setMessage("Automation composition " + updateMsg.getAutomationCompositionId()
243                 + " already defined on participant " + participantId);
244             automationCompositionUpdateAck.setResult(false);
245             automationCompositionUpdateAck.setResponseTo(updateMsg.getMessageId());
246             automationCompositionUpdateAck.setAutomationCompositionId(updateMsg.getAutomationCompositionId());
247             publisher.sendAutomationCompositionAck(automationCompositionUpdateAck);
248             return;
249         }
250
251         if (updateMsg.getParticipantUpdatesList().isEmpty()) {
252             LOGGER.warn("No AutomationCompositionElement updates in message {}",
253                 updateMsg.getAutomationCompositionId());
254             return;
255         }
256
257         automationComposition = new AutomationComposition();
258         automationComposition.setInstanceId(updateMsg.getAutomationCompositionId());
259         var acElements = storeElementsOnThisParticipant(updateMsg.getParticipantUpdatesList());
260         var acElementMap = prepareAcElementMap(acElements);
261         automationComposition.setElements(acElementMap);
262         automationCompositionMap.put(updateMsg.getAutomationCompositionId(), automationComposition);
263
264         handleAutomationCompositionElementUpdate(acElements, acElementDefinitions, updateMsg.getStartPhase(),
265             updateMsg.getAutomationCompositionId());
266     }
267
268     private void handleAcUpdatePhaseN(AutomationCompositionUpdate updateMsg,
269         List<AutomationCompositionElementDefinition> acElementDefinitions) {
270
271         var acElementList = updateMsg.getParticipantUpdatesList().stream()
272             .flatMap(participantUpdate -> participantUpdate.getAutomationCompositionElementList().stream())
273             .filter(element -> participantType.equals(element.getParticipantType())).collect(Collectors.toList());
274
275         handleAutomationCompositionElementUpdate(acElementList, acElementDefinitions, updateMsg.getStartPhase(),
276             updateMsg.getAutomationCompositionId());
277     }
278
279     private void handleAutomationCompositionElementUpdate(List<AutomationCompositionElement> acElements,
280         List<AutomationCompositionElementDefinition> acElementDefinitions, Integer startPhaseMsg,
281         UUID automationCompositionId) {
282         try {
283             for (var element : acElements) {
284                 var acElementNodeTemplate = getAcElementNodeTemplate(acElementDefinitions, element.getDefinition());
285                 if (acElementNodeTemplate != null) {
286                     int startPhase = ParticipantUtils.findStartPhase(acElementNodeTemplate.getProperties());
287                     if (startPhaseMsg.equals(startPhase)) {
288                         for (var acElementListener : listeners) {
289                             var map = new HashMap<>(acElementNodeTemplate.getProperties());
290                             map.putAll(element.getProperties());
291                             acElementListener.automationCompositionElementUpdate(automationCompositionId, element, map);
292                         }
293                     }
294                 }
295             }
296         } catch (PfModelException e) {
297             LOGGER.debug("Automation composition element update failed {}", automationCompositionId);
298         }
299
300     }
301
302     private ToscaNodeTemplate getAcElementNodeTemplate(
303         List<AutomationCompositionElementDefinition> acElementDefinitions, ToscaConceptIdentifier acElementDefId) {
304
305         for (var acElementDefinition : acElementDefinitions) {
306             if (acElementDefId.getName().contains(acElementDefinition.getAcElementDefinitionId().getName())) {
307                 return acElementDefinition.getAutomationCompositionElementToscaNodeTemplate();
308             }
309         }
310         return null;
311     }
312
313     private List<AutomationCompositionElement> storeElementsOnThisParticipant(
314         List<ParticipantUpdates> participantUpdates) {
315         var acElementList = participantUpdates.stream()
316             .flatMap(participantUpdate -> participantUpdate.getAutomationCompositionElementList().stream())
317             .filter(element -> participantType.equals(element.getParticipantType())).collect(Collectors.toList());
318
319         for (var element : acElementList) {
320             elementsOnThisParticipant.put(element.getId(), element);
321         }
322         return acElementList;
323     }
324
325     private Map<UUID, AutomationCompositionElement> prepareAcElementMap(List<AutomationCompositionElement> acElements) {
326         Map<UUID, AutomationCompositionElement> acElementMap = new LinkedHashMap<>();
327         for (var element : acElements) {
328             acElementMap.put(element.getId(), element);
329         }
330         return acElementMap;
331     }
332
333     /**
334      * Method to handle when the new state from participant is UNINITIALISED state.
335      *
336      * @param automationComposition participant response
337      * @param orderedState orderedState
338      * @param startPhaseMsg startPhase from message
339      * @param acElementDefinitions the list of AutomationCompositionElementDefinition
340      */
341     private void handleUninitialisedState(final AutomationComposition automationComposition,
342         final AutomationCompositionOrderedState orderedState, Integer startPhaseMsg,
343         List<AutomationCompositionElementDefinition> acElementDefinitions) {
344         handleStateChange(automationComposition, orderedState, startPhaseMsg, acElementDefinitions);
345         boolean isAllUninitialised = automationComposition.getElements().values().stream()
346             .filter(element -> !AutomationCompositionState.UNINITIALISED.equals(element.getState())).findAny()
347             .isEmpty();
348         if (isAllUninitialised) {
349             automationCompositionMap.remove(automationComposition.getInstanceId());
350             automationComposition.getElements().values()
351                 .forEach(element -> elementsOnThisParticipant.remove(element.getId()));
352         }
353     }
354
355     /**
356      * Method to handle when the new state from participant is PASSIVE state.
357      *
358      * @param automationComposition participant response
359      * @param orderedState orderedState
360      * @param startPhaseMsg startPhase from message
361      * @param acElementDefinitions the list of AutomationCompositionElementDefinition
362      */
363     private void handlePassiveState(final AutomationComposition automationComposition,
364         final AutomationCompositionOrderedState orderedState, Integer startPhaseMsg,
365         List<AutomationCompositionElementDefinition> acElementDefinitions) {
366         handleStateChange(automationComposition, orderedState, startPhaseMsg, acElementDefinitions);
367     }
368
369     /**
370      * Method to handle when the new state from participant is RUNNING state.
371      *
372      * @param automationComposition participant response
373      * @param orderedState orderedState
374      * @param startPhaseMsg startPhase from message
375      * @param acElementDefinitions the list of AutomationCompositionElementDefinition
376      */
377     private void handleRunningState(final AutomationComposition automationComposition,
378         final AutomationCompositionOrderedState orderedState, Integer startPhaseMsg,
379         List<AutomationCompositionElementDefinition> acElementDefinitions) {
380         handleStateChange(automationComposition, orderedState, startPhaseMsg, acElementDefinitions);
381     }
382
383     /**
384      * Method to update the state of automation composition elements.
385      *
386      * @param automationComposition participant status in memory
387      * @param orderedState orderedState the new ordered state the participant should have
388      * @param startPhaseMsg startPhase from message
389      * @param acElementDefinitions the list of AutomationCompositionElementDefinition
390      */
391     private void handleStateChange(AutomationComposition automationComposition,
392         final AutomationCompositionOrderedState orderedState, Integer startPhaseMsg,
393         List<AutomationCompositionElementDefinition> acElementDefinitions) {
394
395         if (orderedState.equals(automationComposition.getOrderedState())) {
396             var automationCompositionAck =
397                 new AutomationCompositionAck(ParticipantMessageType.AUTOMATION_COMPOSITION_STATECHANGE_ACK);
398             automationCompositionAck.setParticipantId(participantId);
399             automationCompositionAck.setParticipantType(participantType);
400             automationCompositionAck.setMessage("Automation composition is already in state " + orderedState);
401             automationCompositionAck.setResult(false);
402             automationCompositionAck.setAutomationCompositionId(automationComposition.getInstanceId());
403             publisher.sendAutomationCompositionAck(automationCompositionAck);
404             return;
405         }
406
407         automationComposition.getElements().values().stream()
408             .forEach(acElement -> automationCompositionElementStateChange(automationComposition, orderedState,
409                 acElement, startPhaseMsg, acElementDefinitions));
410     }
411
412     private void automationCompositionElementStateChange(AutomationComposition automationComposition,
413         AutomationCompositionOrderedState orderedState, AutomationCompositionElement acElement, Integer startPhaseMsg,
414         List<AutomationCompositionElementDefinition> acElementDefinitions) {
415         var acElementNodeTemplate = getAcElementNodeTemplate(acElementDefinitions, acElement.getDefinition());
416         if (acElementNodeTemplate != null) {
417             int startPhase = ParticipantUtils.findStartPhase(acElementNodeTemplate.getProperties());
418             if (startPhaseMsg.equals(startPhase)) {
419                 for (var acElementListener : listeners) {
420                     try {
421                         acElementListener.automationCompositionElementStateChange(
422                                 automationComposition.getInstanceId(), acElement.getId(), acElement.getState(),
423                                 orderedState);
424                     } catch (PfModelException e) {
425                         LOGGER.debug("Automation composition element update failed {}",
426                                 automationComposition.getInstanceId());
427                     }
428                 }
429             }
430         }
431     }
432
433     /**
434      * Get automation compositions as a {@link ConrolLoops} class.
435      *
436      * @return the automation compositions
437      */
438     public AutomationCompositions getAutomationCompositions() {
439         var automationCompositions = new AutomationCompositions();
440         automationCompositions.setAutomationCompositionList(new ArrayList<>(automationCompositionMap.values()));
441         return automationCompositions;
442     }
443 }