5a0f4989fc9fe33f8627596c653338e2caff36a2
[policy/clamp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2021 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.controlloop.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.controlloop.models.controlloop.concepts.ClElementStatistics;
33 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoop;
34 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopElement;
35 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopElementAck;
36 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopElementDefinition;
37 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopOrderedState;
38 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopState;
39 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoops;
40 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ParticipantUpdates;
41 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ParticipantUtils;
42 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ControlLoopAck;
43 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ControlLoopStateChange;
44 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ControlLoopUpdate;
45 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantMessageType;
46 import org.onap.policy.clamp.controlloop.participant.intermediary.api.ControlLoopElementListener;
47 import org.onap.policy.clamp.controlloop.participant.intermediary.comm.ParticipantMessagePublisher;
48 import org.onap.policy.clamp.controlloop.participant.intermediary.parameters.ParticipantParameters;
49 import org.onap.policy.models.base.PfModelException;
50 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
51 import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeTemplate;
52 import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55 import org.springframework.stereotype.Component;
56
57 /*
58  * This class is responsible for managing the state of all control loops in the participant.
59  */
60 @Component
61 public class ControlLoopHandler {
62     private static final Logger LOGGER = LoggerFactory.getLogger(ControlLoopHandler.class);
63
64     private final ToscaConceptIdentifier participantType;
65     private final ToscaConceptIdentifier participantId;
66     private final ParticipantMessagePublisher publisher;
67
68     @Getter
69     private final Map<ToscaConceptIdentifier, ControlLoop> controlLoopMap = new LinkedHashMap<>();
70
71     @Getter
72     private final Map<UUID, ControlLoopElement> elementsOnThisParticipant = new LinkedHashMap<>();
73
74     @Getter
75     private List<ControlLoopElementListener> listeners = new ArrayList<>();
76
77     /**
78      * Constructor, set the participant ID and messageSender.
79      *
80      * @param parameters the parameters of the participant
81      * @param publisher the ParticipantMessage Publisher
82      */
83     public ControlLoopHandler(ParticipantParameters parameters, ParticipantMessagePublisher publisher) {
84         this.participantType = parameters.getIntermediaryParameters().getParticipantType();
85         this.participantId = parameters.getIntermediaryParameters().getParticipantId();
86         this.publisher = publisher;
87     }
88
89     public void registerControlLoopElementListener(ControlLoopElementListener listener) {
90         listeners.add(listener);
91     }
92
93     /**
94      * Handle a control loop element state change message.
95      *
96      * @param controlLoopId the controlLoop Id
97      * @param id the controlLoop UUID
98      * @param orderedState the current state
99      * @param newState the ordered state
100      * @return controlLoopElement the updated controlloop element
101      */
102     public ControlLoopElement updateControlLoopElementState(ToscaConceptIdentifier controlLoopId, UUID id,
103             ControlLoopOrderedState orderedState, ControlLoopState newState) {
104
105         if (id == null) {
106             LOGGER.warn("Cannot update Control loop element state, id is null");
107             return null;
108         }
109
110         // Update states of ControlLoopElement in controlLoopMap
111         for (var controlLoop : controlLoopMap.values()) {
112             var element = controlLoop.getElements().get(id);
113             if (element != null) {
114                 element.setOrderedState(orderedState);
115                 element.setState(newState);
116             }
117             var checkOpt = controlLoop.getElements().values().stream()
118                     .filter(clElement -> !newState.equals(clElement.getState())).findAny();
119             if (checkOpt.isEmpty()) {
120                 controlLoop.setState(newState);
121                 controlLoop.setOrderedState(orderedState);
122             }
123         }
124
125         // Update states of ControlLoopElement in elementsOnThisParticipant
126         var clElement = elementsOnThisParticipant.get(id);
127         if (clElement != null) {
128             var controlLoopStateChangeAck = new ControlLoopAck(ParticipantMessageType.CONTROLLOOP_STATECHANGE_ACK);
129             controlLoopStateChangeAck.setParticipantId(participantId);
130             controlLoopStateChangeAck.setParticipantType(participantType);
131             controlLoopStateChangeAck.setControlLoopId(controlLoopId);
132             clElement.setOrderedState(orderedState);
133             clElement.setState(newState);
134             controlLoopStateChangeAck.getControlLoopResultMap().put(clElement.getId(), new ControlLoopElementAck(
135                     newState, true, "Control loop element {} state changed to {}\", id, newState)"));
136             LOGGER.debug("Control loop element {} state changed to {}", id, newState);
137             controlLoopStateChangeAck.setMessage("ControlLoopElement state changed to {} " + newState);
138             controlLoopStateChangeAck.setResult(true);
139             publisher.sendControlLoopAck(controlLoopStateChangeAck);
140             return clElement;
141         }
142         return null;
143     }
144
145     /**
146      * Handle a control loop element statistics.
147      *
148      * @param id controlloop element id
149      * @param elementStatistics control loop element Statistics
150      */
151     public void updateControlLoopElementStatistics(UUID id, ClElementStatistics elementStatistics) {
152         var clElement = elementsOnThisParticipant.get(id);
153         if (clElement != null) {
154             elementStatistics.setParticipantId(participantId);
155             elementStatistics.setId(id);
156             clElement.setClElementStatistics(elementStatistics);
157         }
158     }
159
160     /**
161      * Handle a control loop state change message.
162      *
163      * @param stateChangeMsg the state change message
164      * @param clElementDefinitions the list of ControlLoopElementDefinition
165      */
166     public void handleControlLoopStateChange(ControlLoopStateChange stateChangeMsg,
167             List<ControlLoopElementDefinition> clElementDefinitions) {
168         if (stateChangeMsg.getControlLoopId() == null) {
169             return;
170         }
171
172         var controlLoop = controlLoopMap.get(stateChangeMsg.getControlLoopId());
173
174         if (controlLoop == null) {
175             var controlLoopAck = new ControlLoopAck(ParticipantMessageType.CONTROLLOOP_STATECHANGE_ACK);
176             controlLoopAck.setParticipantId(participantId);
177             controlLoopAck.setParticipantType(participantType);
178             controlLoopAck.setMessage("Control loop " + stateChangeMsg.getControlLoopId()
179                     + " does not use this participant " + participantId);
180             controlLoopAck.setResult(false);
181             controlLoopAck.setResponseTo(stateChangeMsg.getMessageId());
182             controlLoopAck.setControlLoopId(stateChangeMsg.getControlLoopId());
183             publisher.sendControlLoopAck(controlLoopAck);
184             LOGGER.debug("Control loop {} does not use this participant", stateChangeMsg.getControlLoopId());
185             return;
186         }
187
188         handleState(controlLoop, stateChangeMsg.getOrderedState(), stateChangeMsg.getStartPhase(),
189                 clElementDefinitions);
190     }
191
192     /**
193      * Method to handle state changes.
194      *
195      * @param controlLoop participant response
196      * @param orderedState controlloop ordered state
197      * @param startPhaseMsg startPhase from message
198      * @param clElementDefinitions the list of ControlLoopElementDefinition
199      */
200     private void handleState(final ControlLoop controlLoop, ControlLoopOrderedState orderedState, Integer startPhaseMsg,
201             List<ControlLoopElementDefinition> clElementDefinitions) {
202         switch (orderedState) {
203             case UNINITIALISED:
204                 handleUninitialisedState(controlLoop, orderedState, startPhaseMsg, clElementDefinitions);
205                 break;
206             case PASSIVE:
207                 handlePassiveState(controlLoop, orderedState, startPhaseMsg, clElementDefinitions);
208                 break;
209             case RUNNING:
210                 handleRunningState(controlLoop, orderedState, startPhaseMsg, clElementDefinitions);
211                 break;
212             default:
213                 LOGGER.debug("StateChange message has no state, state is null {}", controlLoop.getDefinition());
214                 break;
215         }
216     }
217
218     /**
219      * Handle a control loop update message.
220      *
221      * @param updateMsg the update message
222      * @param clElementDefinitions the list of ControlLoopElementDefinition
223      */
224     public void handleControlLoopUpdate(ControlLoopUpdate updateMsg,
225             List<ControlLoopElementDefinition> clElementDefinitions) {
226
227         if (!updateMsg.appliesTo(participantType, participantId)) {
228             return;
229         }
230
231         if (0 == updateMsg.getStartPhase()) {
232             handleClUpdatePhase0(updateMsg, clElementDefinitions);
233         } else {
234             handleClUpdatePhaseN(updateMsg, clElementDefinitions);
235         }
236     }
237
238     private void handleClUpdatePhase0(ControlLoopUpdate updateMsg,
239             List<ControlLoopElementDefinition> clElementDefinitions) {
240         var controlLoop = controlLoopMap.get(updateMsg.getControlLoopId());
241
242         // TODO: Updates to existing ControlLoops are not supported yet (Addition/Removal of ControlLoop
243         // elements to existing ControlLoop has to be supported).
244         if (controlLoop != null) {
245             var controlLoopUpdateAck = new ControlLoopAck(ParticipantMessageType.CONTROLLOOP_UPDATE_ACK);
246             controlLoopUpdateAck.setParticipantId(participantId);
247             controlLoopUpdateAck.setParticipantType(participantType);
248
249             controlLoopUpdateAck.setMessage("Control loop " + updateMsg.getControlLoopId()
250                     + " already defined on participant " + participantId);
251             controlLoopUpdateAck.setResult(false);
252             controlLoopUpdateAck.setResponseTo(updateMsg.getMessageId());
253             controlLoopUpdateAck.setControlLoopId(updateMsg.getControlLoopId());
254             publisher.sendControlLoopAck(controlLoopUpdateAck);
255             return;
256         }
257
258         if (updateMsg.getParticipantUpdatesList().isEmpty()) {
259             LOGGER.warn("No ControlLoopElement updates in message {}", updateMsg.getControlLoopId());
260             return;
261         }
262
263         var clElements = storeElementsOnThisParticipant(updateMsg.getParticipantUpdatesList());
264
265         var clElementMap = prepareClElementMap(clElements);
266         controlLoop = new ControlLoop();
267         controlLoop.setDefinition(updateMsg.getControlLoopId());
268         controlLoop.setElements(clElementMap);
269         controlLoopMap.put(updateMsg.getControlLoopId(), controlLoop);
270
271         handleControlLoopElementUpdate(clElements, clElementDefinitions, updateMsg.getStartPhase(),
272                 updateMsg.getControlLoopId());
273     }
274
275     private void handleClUpdatePhaseN(ControlLoopUpdate updateMsg,
276             List<ControlLoopElementDefinition> clElementDefinitions) {
277
278         var clElementList = updateMsg.getParticipantUpdatesList().stream()
279                 .flatMap(participantUpdate -> participantUpdate.getControlLoopElementList().stream())
280                 .filter(element -> participantType.equals(element.getParticipantType())).collect(Collectors.toList());
281
282         handleControlLoopElementUpdate(clElementList, clElementDefinitions, updateMsg.getStartPhase(),
283                 updateMsg.getControlLoopId());
284     }
285
286     private void handleControlLoopElementUpdate(List<ControlLoopElement> clElements,
287             List<ControlLoopElementDefinition> clElementDefinitions, Integer startPhaseMsg,
288             ToscaConceptIdentifier controlLoopId) {
289         try {
290             for (var element : clElements) {
291                 var clElementNodeTemplate = getClElementNodeTemplate(clElementDefinitions, element.getDefinition());
292                 if (clElementNodeTemplate != null) {
293                     int startPhase = ParticipantUtils.findStartPhase(clElementNodeTemplate.getProperties());
294                     if (startPhaseMsg.equals(startPhase)) {
295                         for (var clElementListener : listeners) {
296                             clElementListener.controlLoopElementUpdate(controlLoopId, element, clElementNodeTemplate);
297                         }
298                     }
299                 }
300             }
301         } catch (PfModelException e) {
302             LOGGER.debug("Control loop element update failed {}", controlLoopId);
303         }
304
305     }
306
307     private ToscaNodeTemplate getClElementNodeTemplate(List<ControlLoopElementDefinition> clElementDefinitions,
308             ToscaConceptIdentifier clElementDefId) {
309
310         for (var clElementDefinition : clElementDefinitions) {
311             if (clElementDefId.getName().contains(clElementDefinition.getClElementDefinitionId().getName())) {
312                 return clElementDefinition.getControlLoopElementToscaNodeTemplate();
313             }
314         }
315         return null;
316     }
317
318     private List<ControlLoopElement> storeElementsOnThisParticipant(List<ParticipantUpdates> participantUpdates) {
319         var clElementList = participantUpdates.stream()
320                 .flatMap(participantUpdate -> participantUpdate.getControlLoopElementList().stream())
321                 .filter(element -> participantType.equals(element.getParticipantType())).collect(Collectors.toList());
322
323         for (var element : clElementList) {
324             elementsOnThisParticipant.put(element.getId(), element);
325         }
326         return clElementList;
327     }
328
329     private Map<UUID, ControlLoopElement> prepareClElementMap(List<ControlLoopElement> clElements) {
330         Map<UUID, ControlLoopElement> clElementMap = new LinkedHashMap<>();
331         for (var element : clElements) {
332             clElementMap.put(element.getId(), element);
333         }
334         return clElementMap;
335     }
336
337     /**
338      * Method to handle when the new state from participant is UNINITIALISED state.
339      *
340      * @param controlLoop participant response
341      * @param orderedState orderedState
342      * @param startPhaseMsg startPhase from message
343      * @param clElementDefinitions the list of ControlLoopElementDefinition
344      */
345     private void handleUninitialisedState(final ControlLoop controlLoop, final ControlLoopOrderedState orderedState,
346             Integer startPhaseMsg, List<ControlLoopElementDefinition> clElementDefinitions) {
347         handleStateChange(controlLoop, orderedState, startPhaseMsg, clElementDefinitions);
348         boolean isAllUninitialised = controlLoop.getElements().values().stream()
349                 .filter(element -> !ControlLoopState.UNINITIALISED.equals(element.getState())).findAny().isEmpty();
350         if (isAllUninitialised) {
351             controlLoopMap.remove(controlLoop.getDefinition());
352             controlLoop.getElements().values().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 controlLoop participant response
360      * @param orderedState orderedState
361      * @param startPhaseMsg startPhase from message
362      * @param clElementDefinitions the list of ControlLoopElementDefinition
363      */
364     private void handlePassiveState(final ControlLoop controlLoop, final ControlLoopOrderedState orderedState,
365             Integer startPhaseMsg, List<ControlLoopElementDefinition> clElementDefinitions) {
366         handleStateChange(controlLoop, orderedState, startPhaseMsg, clElementDefinitions);
367     }
368
369     /**
370      * Method to handle when the new state from participant is RUNNING state.
371      *
372      * @param controlLoop participant response
373      * @param orderedState orderedState
374      * @param startPhaseMsg startPhase from message
375      * @param clElementDefinitions the list of ControlLoopElementDefinition
376      */
377     private void handleRunningState(final ControlLoop controlLoop, final ControlLoopOrderedState orderedState,
378             Integer startPhaseMsg, List<ControlLoopElementDefinition> clElementDefinitions) {
379         handleStateChange(controlLoop, orderedState, startPhaseMsg, clElementDefinitions);
380     }
381
382     /**
383      * Method to update the state of control loop elements.
384      *
385      * @param controlLoop participant status in memory
386      * @param orderedState orderedState the new ordered state the participant should have
387      * @param startPhaseMsg startPhase from message
388      * @param clElementDefinitions the list of ControlLoopElementDefinition
389      */
390     private void handleStateChange(ControlLoop controlLoop, final ControlLoopOrderedState orderedState,
391             Integer startPhaseMsg, List<ControlLoopElementDefinition> clElementDefinitions) {
392
393         if (orderedState.equals(controlLoop.getOrderedState())) {
394             var controlLoopAck = new ControlLoopAck(ParticipantMessageType.CONTROLLOOP_STATECHANGE_ACK);
395             controlLoopAck.setParticipantId(participantId);
396             controlLoopAck.setParticipantType(participantType);
397             controlLoopAck.setMessage("Control loop is already in state " + orderedState);
398             controlLoopAck.setResult(false);
399             controlLoopAck.setControlLoopId(controlLoop.getDefinition());
400             publisher.sendControlLoopAck(controlLoopAck);
401             return;
402         }
403
404         controlLoop.getElements().values().stream().forEach(clElement -> controlLoopElementStateChange(controlLoop,
405                 orderedState, clElement, startPhaseMsg, clElementDefinitions));
406     }
407
408     private void controlLoopElementStateChange(ControlLoop controlLoop, ControlLoopOrderedState orderedState,
409             ControlLoopElement clElement, Integer startPhaseMsg,
410             List<ControlLoopElementDefinition> clElementDefinitions) {
411         var clElementNodeTemplate = getClElementNodeTemplate(clElementDefinitions, clElement.getDefinition());
412         if (clElementNodeTemplate != null) {
413             int startPhase = ParticipantUtils.findStartPhase(clElementNodeTemplate.getProperties());
414             if (startPhaseMsg.equals(startPhase)) {
415                 for (var clElementListener : listeners) {
416                     try {
417                         clElementListener.controlLoopElementStateChange(controlLoop.getDefinition(), clElement.getId(),
418                                 clElement.getState(), orderedState);
419                     } catch (PfModelException e) {
420                         LOGGER.debug("Control loop element update failed {}", controlLoop.getDefinition());
421                     }
422                 }
423             }
424         }
425     }
426
427     /**
428      * Get control loops as a {@link ConrolLoops} class.
429      *
430      * @return the control loops
431      */
432     public ControlLoops getControlLoops() {
433         var controlLoops = new ControlLoops();
434         controlLoops.setControlLoopList(new ArrayList<>(controlLoopMap.values()));
435         return controlLoops;
436     }
437
438     /**
439      * Get properties of a controlloopelement.
440      *
441      * @param id the control loop element id
442      * @return the instance properties
443      */
444     public Map<String, ToscaProperty> getClElementInstanceProperties(UUID id) {
445         Map<String, ToscaProperty> propertiesMap = new HashMap<>();
446         for (var controlLoop : controlLoopMap.values()) {
447             var element = controlLoop.getElements().get(id);
448             if (element != null) {
449                 propertiesMap.putAll(element.getPropertiesMap());
450             }
451         }
452         return propertiesMap;
453     }
454 }