40dc901db3ea3f9382d9313a24fe76e9354d578c
[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 id controlloop element id
97      * @param orderedState the current state
98      * @param newState the ordered state
99      * @return controlLoopElement the updated controlloop element
100      */
101     public ControlLoopElement updateControlLoopElementState(ToscaConceptIdentifier controlLoopId, UUID id,
102             ControlLoopOrderedState orderedState, ControlLoopState newState) {
103
104         if (id == null) {
105             LOGGER.warn("Cannot update Control loop element state, id is null");
106             return null;
107         }
108
109         // Update states of ControlLoopElement in controlLoopMap
110         for (var controlLoop : controlLoopMap.values()) {
111             var element = controlLoop.getElements().get(id);
112             if (element != null) {
113                 element.setOrderedState(orderedState);
114                 element.setState(newState);
115             }
116             var checkOpt = controlLoop.getElements().values().stream()
117                     .filter(clElement -> !newState.equals(clElement.getState())).findAny();
118             if (checkOpt.isEmpty()) {
119                 controlLoop.setState(newState);
120                 controlLoop.setOrderedState(orderedState);
121             }
122         }
123
124         // Update states of ControlLoopElement in elementsOnThisParticipant
125         var clElement = elementsOnThisParticipant.get(id);
126         if (clElement != null) {
127             var controlLoopStateChangeAck = new ControlLoopAck(ParticipantMessageType.CONTROLLOOP_STATECHANGE_ACK);
128             controlLoopStateChangeAck.setParticipantId(participantId);
129             controlLoopStateChangeAck.setParticipantType(participantType);
130             controlLoopStateChangeAck.setControlLoopId(controlLoopId);
131             clElement.setOrderedState(orderedState);
132             clElement.setState(newState);
133             controlLoopStateChangeAck.getControlLoopResultMap().put(clElement.getId(), new ControlLoopElementAck(
134                     newState, true, "Control loop element {} state changed to {}\", id, newState)"));
135             LOGGER.debug("Control loop element {} state changed to {}", id, newState);
136             controlLoopStateChangeAck.setMessage("ControlLoopElement state changed to {} " + newState);
137             controlLoopStateChangeAck.setResult(true);
138             publisher.sendControlLoopAck(controlLoopStateChangeAck);
139             return clElement;
140         }
141         return null;
142     }
143
144     /**
145      * Handle a control loop element statistics.
146      *
147      * @param id controlloop element id
148      * @param elementStatistics control loop element Statistics
149      */
150     public void updateControlLoopElementStatistics(UUID id, ClElementStatistics elementStatistics) {
151         var clElement = elementsOnThisParticipant.get(id);
152         if (clElement != null) {
153             elementStatistics.setParticipantId(participantId);
154             elementStatistics.setId(id);
155             clElement.setClElementStatistics(elementStatistics);
156         }
157     }
158
159     /**
160      * Handle a control loop state change message.
161      *
162      * @param stateChangeMsg the state change message
163      */
164     public void handleControlLoopStateChange(ControlLoopStateChange stateChangeMsg) {
165         if (stateChangeMsg.getControlLoopId() == null) {
166             return;
167         }
168
169         var controlLoop = controlLoopMap.get(stateChangeMsg.getControlLoopId());
170
171         if (controlLoop == null) {
172             var controlLoopAck = new ControlLoopAck(ParticipantMessageType.CONTROL_LOOP_STATE_CHANGE);
173             controlLoopAck.setParticipantId(participantId);
174             controlLoopAck.setParticipantType(participantType);
175             controlLoopAck.setMessage("Control loop " + stateChangeMsg.getControlLoopId()
176                     + " does not use this participant " + participantId);
177             controlLoopAck.setResult(false);
178             controlLoopAck.setResponseTo(stateChangeMsg.getMessageId());
179             controlLoopAck.setControlLoopId(stateChangeMsg.getControlLoopId());
180             publisher.sendControlLoopAck(controlLoopAck);
181             LOGGER.debug("Control loop {} does not use this participant", stateChangeMsg.getControlLoopId());
182             return;
183         }
184
185         handleState(controlLoop, stateChangeMsg.getOrderedState());
186     }
187
188     /**
189      * Method to handle state changes.
190      *
191      * @param controlLoop participant response
192      * @param orderedState controlloop ordered state
193      */
194     private void handleState(final ControlLoop controlLoop, ControlLoopOrderedState orderedState) {
195         switch (orderedState) {
196             case UNINITIALISED:
197                 handleUninitialisedState(controlLoop, orderedState);
198                 break;
199             case PASSIVE:
200                 handlePassiveState(controlLoop, orderedState);
201                 break;
202             case RUNNING:
203                 handleRunningState(controlLoop, orderedState);
204                 break;
205             default:
206                 LOGGER.debug("StateChange message has no state, state is null {}", controlLoop.getDefinition());
207                 break;
208         }
209     }
210
211     /**
212      * Handle a control loop update message.
213      *
214      * @param updateMsg the update message
215      */
216     public void handleControlLoopUpdate(ControlLoopUpdate updateMsg,
217             List<ControlLoopElementDefinition> clElementDefinitions) {
218
219         if (!updateMsg.appliesTo(participantType, participantId)) {
220             return;
221         }
222
223         if (0 == updateMsg.getStartPhase()) {
224             handleClUpdatePhase0(updateMsg, clElementDefinitions);
225         } else {
226             handleClUpdatePhaseN(updateMsg, clElementDefinitions);
227         }
228     }
229
230     private void handleClUpdatePhase0(ControlLoopUpdate updateMsg,
231             List<ControlLoopElementDefinition> clElementDefinitions) {
232         var controlLoop = controlLoopMap.get(updateMsg.getControlLoopId());
233
234         // TODO: Updates to existing ControlLoops are not supported yet (Addition/Removal of ControlLoop
235         // elements to existing ControlLoop has to be supported).
236         if (controlLoop != null) {
237             var controlLoopUpdateAck = new ControlLoopAck(ParticipantMessageType.CONTROLLOOP_UPDATE_ACK);
238             controlLoopUpdateAck.setParticipantId(participantId);
239             controlLoopUpdateAck.setParticipantType(participantType);
240
241             controlLoopUpdateAck.setMessage("Control loop " + updateMsg.getControlLoopId()
242                     + " already defined on participant " + participantId);
243             controlLoopUpdateAck.setResult(false);
244             controlLoopUpdateAck.setResponseTo(updateMsg.getMessageId());
245             controlLoopUpdateAck.setControlLoopId(updateMsg.getControlLoopId());
246             publisher.sendControlLoopAck(controlLoopUpdateAck);
247             return;
248         }
249
250         if (updateMsg.getParticipantUpdatesList().isEmpty()) {
251             LOGGER.warn("No ControlLoopElement updates in message {}", updateMsg.getControlLoopId());
252             return;
253         }
254
255         var clElements = storeElementsOnThisParticipant(updateMsg.getParticipantUpdatesList());
256
257         var clElementMap = prepareClElementMap(clElements);
258         controlLoop = new ControlLoop();
259         controlLoop.setDefinition(updateMsg.getControlLoopId());
260         controlLoop.setElements(clElementMap);
261         controlLoopMap.put(updateMsg.getControlLoopId(), controlLoop);
262
263         handleControlLoopElementUpdate(clElements, clElementDefinitions, updateMsg.getStartPhase(),
264                 updateMsg.getControlLoopId());
265     }
266
267     private void handleClUpdatePhaseN(ControlLoopUpdate updateMsg,
268             List<ControlLoopElementDefinition> clElementDefinitions) {
269
270         var clElementList = updateMsg.getParticipantUpdatesList().stream()
271                 .flatMap(participantUpdate -> participantUpdate.getControlLoopElementList().stream())
272                 .filter(element -> participantType.equals(element.getParticipantType())).collect(Collectors.toList());
273
274         handleControlLoopElementUpdate(clElementList, clElementDefinitions, updateMsg.getStartPhase(),
275                 updateMsg.getControlLoopId());
276     }
277
278     private void handleControlLoopElementUpdate(List<ControlLoopElement> clElements,
279             List<ControlLoopElementDefinition> clElementDefinitions, Integer startPhaseMsg,
280             ToscaConceptIdentifier controlLoopId) {
281         try {
282             for (var element : clElements) {
283                 var clElementNodeTemplate = getClElementNodeTemplate(clElementDefinitions, element.getDefinition());
284                 if (clElementNodeTemplate != null) {
285                     int startPhase = ParticipantUtils.findStartPhase(clElementNodeTemplate.getProperties());
286                     if (startPhaseMsg.equals(startPhase)) {
287                         for (var clElementListener : listeners) {
288                             clElementListener.controlLoopElementUpdate(controlLoopId, element, clElementNodeTemplate);
289                         }
290                     }
291                 }
292             }
293         } catch (PfModelException e) {
294             LOGGER.debug("Control loop element update failed {}", controlLoopId);
295         }
296
297     }
298
299     private ToscaNodeTemplate getClElementNodeTemplate(List<ControlLoopElementDefinition> clElementDefinitions,
300             ToscaConceptIdentifier clElementDefId) {
301
302         for (var clElementDefinition : clElementDefinitions) {
303             if (clElementDefId.getName().contains(
304                 clElementDefinition.getClElementDefinitionId().getName())) {
305                 return clElementDefinition.getControlLoopElementToscaNodeTemplate();
306             }
307         }
308         return null;
309     }
310
311     private List<ControlLoopElement> storeElementsOnThisParticipant(List<ParticipantUpdates> participantUpdates) {
312         var clElementList = participantUpdates.stream()
313                 .flatMap(participantUpdate -> participantUpdate.getControlLoopElementList().stream())
314                 .filter(element -> participantType.equals(element.getParticipantType())).collect(Collectors.toList());
315
316         for (var element : clElementList) {
317             elementsOnThisParticipant.put(element.getId(), element);
318         }
319         return clElementList;
320     }
321
322     private Map<UUID, ControlLoopElement> prepareClElementMap(List<ControlLoopElement> clElements) {
323         Map<UUID, ControlLoopElement> clElementMap = new LinkedHashMap<>();
324         for (var element : clElements) {
325             clElementMap.put(element.getId(), element);
326         }
327         return clElementMap;
328     }
329
330     /**
331      * Method to handle when the new state from participant is UNINITIALISED state.
332      *
333      * @param controlLoop participant response
334      * @param orderedState orderedState
335      */
336     private void handleUninitialisedState(final ControlLoop controlLoop, final ControlLoopOrderedState orderedState) {
337         handleStateChange(controlLoop, orderedState);
338         controlLoopMap.remove(controlLoop.getDefinition());
339         controlLoop.getElements().values().forEach(element -> elementsOnThisParticipant.remove(element.getId()));
340     }
341
342     /**
343      * Method to handle when the new state from participant is PASSIVE state.
344      *
345      * @param controlLoop participant response
346      * @param orderedState orderedState
347      */
348     private void handlePassiveState(final ControlLoop controlLoop, final ControlLoopOrderedState orderedState) {
349         handleStateChange(controlLoop, orderedState);
350     }
351
352     /**
353      * Method to handle when the new state from participant is RUNNING state.
354      *
355      * @param controlLoop participant response
356      * @param orderedState orderedState
357      */
358     private void handleRunningState(final ControlLoop controlLoop, final ControlLoopOrderedState orderedState) {
359         handleStateChange(controlLoop, orderedState);
360     }
361
362     /**
363      * Method to update the state of control loop elements.
364      *
365      * @param controlLoop participant status in memory
366      * @param orderedState orderedState the new ordered state the participant should have
367      */
368     private void handleStateChange(ControlLoop controlLoop, final ControlLoopOrderedState orderedState) {
369
370         if (orderedState.equals(controlLoop.getOrderedState())) {
371             var controlLoopAck = new ControlLoopAck(ParticipantMessageType.CONTROL_LOOP_STATE_CHANGE);
372             controlLoopAck.setParticipantId(participantId);
373             controlLoopAck.setParticipantType(participantType);
374             controlLoopAck.setMessage("Control loop is already in state " + orderedState);
375             controlLoopAck.setResult(false);
376             controlLoopAck.setControlLoopId(controlLoop.getDefinition());
377             publisher.sendControlLoopAck(controlLoopAck);
378             return;
379         }
380
381         controlLoop.getElements().values().stream().forEach(clElement -> {
382             for (var clElementListener : listeners) {
383                 try {
384                     clElementListener.controlLoopElementStateChange(controlLoop.getDefinition(), clElement.getId(),
385                             clElement.getState(), orderedState);
386                 } catch (PfModelException e) {
387                     LOGGER.debug("Control loop element update failed {}", controlLoop.getDefinition());
388                 }
389             }
390         });
391     }
392
393     /**
394      * Get control loops as a {@link ConrolLoops} class.
395      *
396      * @return the control loops
397      */
398     public ControlLoops getControlLoops() {
399         var controlLoops = new ControlLoops();
400         controlLoops.setControlLoopList(new ArrayList<>(controlLoopMap.values()));
401         return controlLoops;
402     }
403
404     /**
405      * Get properties of a controlloopelement.
406      *
407      * @param id the control loop element id
408      * @return the instance properties
409      */
410     public Map<String, ToscaProperty> getClElementInstanceProperties(UUID id) {
411         Map<String, ToscaProperty> propertiesMap = new HashMap<>();
412         for (var controlLoop : controlLoopMap.values()) {
413             var element = controlLoop.getElements().get(id);
414             if (element != null) {
415                 propertiesMap.putAll(element.getPropertiesMap());
416             }
417         }
418         return propertiesMap;
419     }
420 }