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