a031cfaf621b5d7e9942946b92893c4502bac149
[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.runtime.supervision;
23
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.UUID;
29 import javax.ws.rs.core.Response;
30 import lombok.AllArgsConstructor;
31 import org.apache.commons.collections4.CollectionUtils;
32 import org.onap.policy.clamp.controlloop.common.exception.ControlLoopException;
33 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoop;
34 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopElementAck;
35 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopInfo;
36 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopState;
37 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.Participant;
38 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ParticipantHealthStatus;
39 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ParticipantState;
40 import org.onap.policy.clamp.controlloop.models.controlloop.persistence.provider.ControlLoopProvider;
41 import org.onap.policy.clamp.controlloop.models.controlloop.persistence.provider.ParticipantProvider;
42 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ControlLoopAck;
43 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantDeregister;
44 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantMessage;
45 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantRegister;
46 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantStatus;
47 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantUpdateAck;
48 import org.onap.policy.clamp.controlloop.runtime.monitoring.MonitoringProvider;
49 import org.onap.policy.clamp.controlloop.runtime.supervision.comm.ControlLoopStateChangePublisher;
50 import org.onap.policy.clamp.controlloop.runtime.supervision.comm.ControlLoopUpdatePublisher;
51 import org.onap.policy.clamp.controlloop.runtime.supervision.comm.ParticipantDeregisterAckPublisher;
52 import org.onap.policy.clamp.controlloop.runtime.supervision.comm.ParticipantRegisterAckPublisher;
53 import org.onap.policy.clamp.controlloop.runtime.supervision.comm.ParticipantUpdatePublisher;
54 import org.onap.policy.models.base.PfModelException;
55 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
56 import org.onap.policy.models.tosca.authorative.concepts.ToscaNodeType;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59 import org.springframework.stereotype.Component;
60
61 /**
62  * This class handles supervision of control loop instances, so only one object of this type should be built at a time.
63  *
64  * <p/>
65  * It is effectively a singleton that is started at system start.
66  */
67 @Component
68 @AllArgsConstructor
69 public class SupervisionHandler {
70     private static final Logger LOGGER = LoggerFactory.getLogger(SupervisionHandler.class);
71
72     private static final String CONTROL_LOOP_CANNOT_TRANSITION_FROM_STATE = "Control loop can't transition from state ";
73     private static final String CONTROL_LOOP_IS_ALREADY_IN_STATE = "Control loop is already in state ";
74     private static final String TO_STATE = " to state ";
75     private static final String AND_TRANSITIONING_TO_STATE = " and transitioning to state ";
76
77     private final ControlLoopProvider controlLoopProvider;
78     private final ParticipantProvider participantProvider;
79     private final MonitoringProvider monitoringProvider;
80
81     // Publishers for participant communication
82     private final ControlLoopUpdatePublisher controlLoopUpdatePublisher;
83     private final ControlLoopStateChangePublisher controlLoopStateChangePublisher;
84     private final ParticipantRegisterAckPublisher participantRegisterAckPublisher;
85     private final ParticipantDeregisterAckPublisher participantDeregisterAckPublisher;
86     private final ParticipantUpdatePublisher participantUpdatePublisher;
87
88     /**
89      * Supervision trigger called when a command is issued on control loops.
90      *
91      * <p/>
92      * Causes supervision to start or continue supervision on the control loops in question.
93      *
94      * @param controlLoopIdentifierList the control loops for which the supervision command has been issued
95      * @throws ControlLoopException on supervision triggering exceptions
96      */
97     public void triggerControlLoopSupervision(List<ToscaConceptIdentifier> controlLoopIdentifierList)
98             throws ControlLoopException {
99
100         LOGGER.debug("triggering control loop supervision on control loops {}", controlLoopIdentifierList);
101
102         if (CollectionUtils.isEmpty(controlLoopIdentifierList)) {
103             // This is just to force throwing of the exception in certain circumstances.
104             exceptionOccured(Response.Status.NOT_ACCEPTABLE, "The list of control loops for supervision is empty");
105         }
106
107         for (ToscaConceptIdentifier controlLoopId : controlLoopIdentifierList) {
108             try {
109                 var controlLoop = controlLoopProvider.getControlLoop(controlLoopId);
110
111                 superviseControlLoop(controlLoop);
112
113                 controlLoopProvider.updateControlLoop(controlLoop);
114             } catch (PfModelException pfme) {
115                 throw new ControlLoopException(pfme.getErrorResponse().getResponseCode(), pfme.getMessage(), pfme);
116             }
117         }
118     }
119
120     /**
121      * Handle a ParticipantStatus message from a participant.
122      *
123      * @param participantStatusMessage the ParticipantStatus message received from a participant
124      */
125     @MessageIntercept
126     public void handleParticipantMessage(ParticipantStatus participantStatusMessage) {
127         LOGGER.debug("Participant Status received {}", participantStatusMessage);
128         try {
129             superviseParticipant(participantStatusMessage);
130         } catch (PfModelException | ControlLoopException svExc) {
131             LOGGER.warn("error supervising participant {}", participantStatusMessage.getParticipantId(), svExc);
132             return;
133         }
134
135         try {
136             superviseControlLoops(participantStatusMessage);
137         } catch (PfModelException | ControlLoopException svExc) {
138             LOGGER.warn("error supervising participant {}", participantStatusMessage.getParticipantId(), svExc);
139         }
140     }
141
142     /**
143      * Handle a ParticipantRegister message from a participant.
144      *
145      * @param participantRegisterMessage the ParticipantRegister message received from a participant
146      */
147     public void handleParticipantMessage(ParticipantRegister participantRegisterMessage) {
148         LOGGER.debug("Participant Register received {}", participantRegisterMessage);
149         try {
150             checkParticipant(participantRegisterMessage, ParticipantState.UNKNOWN, ParticipantHealthStatus.UNKNOWN);
151         } catch (PfModelException | ControlLoopException svExc) {
152             LOGGER.warn("error saving participant {}", participantRegisterMessage.getParticipantId(), svExc);
153         }
154         participantRegisterAckPublisher.send(participantRegisterMessage.getMessageId(),
155                 participantRegisterMessage.getParticipantId(), participantRegisterMessage.getParticipantType());
156     }
157
158     /**
159      * Handle a ParticipantDeregister message from a participant.
160      *
161      * @param participantDeregisterMessage the ParticipantDeregister message received from a participant
162      */
163     @MessageIntercept
164     public void handleParticipantMessage(ParticipantDeregister participantDeregisterMessage) {
165         LOGGER.debug("Participant Deregister received {}", participantDeregisterMessage);
166         try {
167             var participantList =
168                     participantProvider.getParticipants(participantDeregisterMessage.getParticipantId().getName(),
169                             participantDeregisterMessage.getParticipantId().getVersion());
170
171             if (participantList != null) {
172                 for (Participant participant : participantList) {
173                     participant.setParticipantState(ParticipantState.TERMINATED);
174                     participant.setHealthStatus(ParticipantHealthStatus.OFF_LINE);
175                 }
176                 participantProvider.updateParticipants(participantList);
177             }
178         } catch (PfModelException pfme) {
179             LOGGER.warn("Model exception occured with participant id {}",
180                     participantDeregisterMessage.getParticipantId());
181         }
182
183         participantDeregisterAckPublisher.send(participantDeregisterMessage.getMessageId());
184     }
185
186     /**
187      * Handle a ParticipantUpdateAck message from a participant.
188      *
189      * @param participantUpdateAckMessage the ParticipantUpdateAck message received from a participant
190      */
191     @MessageIntercept
192     public void handleParticipantMessage(ParticipantUpdateAck participantUpdateAckMessage) {
193         LOGGER.debug("Participant Update Ack received {}", participantUpdateAckMessage);
194         try {
195             var participantList =
196                     participantProvider.getParticipants(participantUpdateAckMessage.getParticipantId().getName(),
197                             participantUpdateAckMessage.getParticipantId().getVersion());
198
199             if (participantList != null) {
200                 for (Participant participant : participantList) {
201                     participant.setParticipantState(participantUpdateAckMessage.getState());
202                 }
203                 participantProvider.updateParticipants(participantList);
204             } else {
205                 LOGGER.warn("Participant not found in database {}", participantUpdateAckMessage.getParticipantId());
206             }
207         } catch (PfModelException pfme) {
208             LOGGER.warn("Model exception occured with participant id {}",
209                     participantUpdateAckMessage.getParticipantId());
210         }
211     }
212
213     /**
214      * Send commissioning update message to dmaap.
215      *
216      */
217     public void handleSendCommissionMessage(Map<String, ToscaNodeType> commonPropertiesMap) {
218         LOGGER.debug("Participant update message being sent {}");
219         participantUpdatePublisher.send(commonPropertiesMap, true);
220     }
221
222     /**
223      * Send decommissioning update message to dmaap.
224      *
225      */
226     public void handleSendDeCommissionMessage() {
227         LOGGER.debug("Participant update message being sent");
228         participantUpdatePublisher.send(Collections.emptyMap(), false);
229     }
230
231     /**
232      * Handle a ControlLoop update acknowledge message from a participant.
233      *
234      * @param controlLoopAckMessage the ControlLoopAck message received from a participant
235      */
236     @MessageIntercept
237     public void handleControlLoopUpdateAckMessage(ControlLoopAck controlLoopAckMessage) {
238         LOGGER.debug("ControlLoop Update Ack message received {}", controlLoopAckMessage);
239         setClElementStateInDb(controlLoopAckMessage);
240     }
241
242     /**
243      * Handle a ControlLoop statechange acknowledge message from a participant.
244      *
245      * @param controlLoopAckMessage the ControlLoopAck message received from a participant
246      */
247     @MessageIntercept
248     public void handleControlLoopStateChangeAckMessage(ControlLoopAck controlLoopAckMessage) {
249         LOGGER.debug("ControlLoop StateChange Ack message received {}", controlLoopAckMessage);
250         setClElementStateInDb(controlLoopAckMessage);
251     }
252
253     private void setClElementStateInDb(ControlLoopAck controlLoopAckMessage) {
254         if (controlLoopAckMessage.getControlLoopResultMap() != null) {
255             try {
256                 var controlLoop = controlLoopProvider.getControlLoop(controlLoopAckMessage.getControlLoopId());
257                 if (controlLoop != null) {
258                     var updated = updateState(controlLoop, controlLoopAckMessage.getControlLoopResultMap().entrySet());
259                     updated |= setPrimed(controlLoop);
260                     if (updated) {
261                         controlLoopProvider.updateControlLoop(controlLoop);
262                     }
263                 } else {
264                     LOGGER.warn("ControlLoop not found in database {}", controlLoopAckMessage.getControlLoopId());
265                 }
266             } catch (PfModelException pfme) {
267                 LOGGER.warn("Model exception occured with ControlLoop Id {}", controlLoopAckMessage.getControlLoopId());
268             }
269         }
270     }
271
272     private boolean updateState(ControlLoop controlLoop,
273             Set<Map.Entry<UUID, ControlLoopElementAck>> controlLoopResultSet) {
274         var updated = false;
275         for (var clElementAck : controlLoopResultSet) {
276             var element = controlLoop.getElements().get(clElementAck.getKey());
277             if (element != null) {
278                 element.setState(clElementAck.getValue().getState());
279                 updated = true;
280             }
281         }
282         return updated;
283     }
284
285     private boolean setPrimed(ControlLoop controlLoop) {
286         var clElements = controlLoop.getElements().values();
287         if (clElements != null) {
288             Boolean primedFlag = true;
289             var checkOpt = controlLoop.getElements().values().stream()
290                     .filter(clElement -> (!clElement.getState().equals(ControlLoopState.PASSIVE)
291                             || !clElement.getState().equals(ControlLoopState.RUNNING)))
292                     .findAny();
293             if (checkOpt.isEmpty()) {
294                 primedFlag = false;
295             }
296             controlLoop.setPrimed(primedFlag);
297             return true;
298         }
299
300         return false;
301     }
302
303     /**
304      * Supervise a control loop, performing whatever actions need to be performed on the control loop.
305      *
306      * @param controlLoop the control loop to supervises
307      * @throws ControlLoopException on supervision errors
308      */
309     private void superviseControlLoop(ControlLoop controlLoop) throws ControlLoopException {
310         switch (controlLoop.getOrderedState()) {
311             case UNINITIALISED:
312                 superviseControlLoopUninitialization(controlLoop);
313                 break;
314
315             case PASSIVE:
316                 superviseControlLoopPassivation(controlLoop);
317                 break;
318
319             case RUNNING:
320                 superviseControlLoopActivation(controlLoop);
321                 break;
322
323             default:
324                 exceptionOccured(Response.Status.NOT_ACCEPTABLE,
325                         "A control loop cannot be commanded to go into state " + controlLoop.getOrderedState().name());
326         }
327     }
328
329     /**
330      * Supervise a control loop uninitialisation, performing whatever actions need to be performed on the control loop,
331      * control loop ordered state is UNINITIALIZED.
332      *
333      * @param controlLoop the control loop to supervises
334      * @throws ControlLoopException on supervision errors
335      */
336     private void superviseControlLoopUninitialization(ControlLoop controlLoop) throws ControlLoopException {
337         switch (controlLoop.getState()) {
338             case UNINITIALISED:
339                 exceptionOccured(Response.Status.NOT_ACCEPTABLE,
340                         CONTROL_LOOP_IS_ALREADY_IN_STATE + controlLoop.getState().name());
341                 break;
342
343             case UNINITIALISED2PASSIVE:
344             case PASSIVE:
345                 controlLoop.setState(ControlLoopState.PASSIVE2UNINITIALISED);
346                 controlLoopStateChangePublisher.send(controlLoop);
347                 break;
348
349             case PASSIVE2UNINITIALISED:
350                 exceptionOccured(Response.Status.NOT_ACCEPTABLE, CONTROL_LOOP_IS_ALREADY_IN_STATE
351                         + controlLoop.getState().name() + AND_TRANSITIONING_TO_STATE + controlLoop.getOrderedState());
352                 break;
353
354             default:
355                 exceptionOccured(Response.Status.NOT_ACCEPTABLE, CONTROL_LOOP_CANNOT_TRANSITION_FROM_STATE
356                         + controlLoop.getState().name() + TO_STATE + controlLoop.getOrderedState());
357                 break;
358         }
359     }
360
361     private void superviseControlLoopPassivation(ControlLoop controlLoop) throws ControlLoopException {
362         switch (controlLoop.getState()) {
363             case PASSIVE:
364                 exceptionOccured(Response.Status.NOT_ACCEPTABLE,
365                         CONTROL_LOOP_IS_ALREADY_IN_STATE + controlLoop.getState().name());
366                 break;
367             case UNINITIALISED:
368                 controlLoop.setState(ControlLoopState.UNINITIALISED2PASSIVE);
369                 controlLoopUpdatePublisher.send(controlLoop);
370                 break;
371
372             case UNINITIALISED2PASSIVE:
373             case RUNNING2PASSIVE:
374                 exceptionOccured(Response.Status.NOT_ACCEPTABLE, CONTROL_LOOP_IS_ALREADY_IN_STATE
375                         + controlLoop.getState().name() + AND_TRANSITIONING_TO_STATE + controlLoop.getOrderedState());
376                 break;
377
378             case RUNNING:
379                 controlLoop.setState(ControlLoopState.RUNNING2PASSIVE);
380                 controlLoopStateChangePublisher.send(controlLoop);
381                 break;
382
383             default:
384                 exceptionOccured(Response.Status.NOT_ACCEPTABLE, CONTROL_LOOP_CANNOT_TRANSITION_FROM_STATE
385                         + controlLoop.getState().name() + TO_STATE + controlLoop.getOrderedState());
386                 break;
387         }
388     }
389
390     private void superviseControlLoopActivation(ControlLoop controlLoop) throws ControlLoopException {
391         switch (controlLoop.getState()) {
392             case RUNNING:
393                 exceptionOccured(Response.Status.NOT_ACCEPTABLE,
394                         CONTROL_LOOP_IS_ALREADY_IN_STATE + controlLoop.getState().name());
395                 break;
396
397             case PASSIVE2RUNNING:
398                 exceptionOccured(Response.Status.NOT_ACCEPTABLE, CONTROL_LOOP_IS_ALREADY_IN_STATE
399                         + controlLoop.getState().name() + AND_TRANSITIONING_TO_STATE + controlLoop.getOrderedState());
400                 break;
401
402             case PASSIVE:
403                 controlLoop.setState(ControlLoopState.PASSIVE2RUNNING);
404                 controlLoopStateChangePublisher.send(controlLoop);
405                 break;
406
407             default:
408                 exceptionOccured(Response.Status.NOT_ACCEPTABLE, CONTROL_LOOP_CANNOT_TRANSITION_FROM_STATE
409                         + controlLoop.getState().name() + TO_STATE + controlLoop.getOrderedState());
410                 break;
411         }
412     }
413
414     private void checkParticipant(ParticipantMessage participantMessage, ParticipantState participantState,
415             ParticipantHealthStatus healthStatus) throws ControlLoopException, PfModelException {
416         if (participantMessage.getParticipantId() == null) {
417             exceptionOccured(Response.Status.NOT_FOUND, "Participant ID on PARTICIPANT_STATUS message is null");
418         }
419         List<Participant> participantList = participantProvider.getParticipants(
420                 participantMessage.getParticipantId().getName(), participantMessage.getParticipantId().getVersion());
421
422         if (CollectionUtils.isEmpty(participantList)) {
423             var participant = new Participant();
424             participant.setName(participantMessage.getParticipantId().getName());
425             participant.setVersion(participantMessage.getParticipantId().getVersion());
426             participant.setDefinition(participantMessage.getParticipantId());
427             participant.setParticipantType(participantMessage.getParticipantType());
428             participant.setParticipantState(participantState);
429             participant.setHealthStatus(healthStatus);
430
431             participantList.add(participant);
432             participantProvider.createParticipants(participantList);
433         } else {
434             for (Participant participant : participantList) {
435                 participant.setParticipantState(participantState);
436                 participant.setHealthStatus(healthStatus);
437             }
438             participantProvider.updateParticipants(participantList);
439         }
440     }
441
442     private void superviseParticipant(ParticipantStatus participantStatusMessage)
443             throws PfModelException, ControlLoopException {
444
445         checkParticipant(participantStatusMessage, participantStatusMessage.getState(),
446                 participantStatusMessage.getHealthStatus());
447
448         monitoringProvider.createParticipantStatistics(List.of(participantStatusMessage.getParticipantStatistics()));
449     }
450
451     private void superviseControlLoops(ParticipantStatus participantStatusMessage)
452             throws PfModelException, ControlLoopException {
453         if (participantStatusMessage.getControlLoopInfoList() != null) {
454             for (ControlLoopInfo clEntry : participantStatusMessage.getControlLoopInfoList()) {
455                 var dbControlLoop =
456                         controlLoopProvider.getControlLoop(new ToscaConceptIdentifier(clEntry.getControlLoopId()));
457                 if (dbControlLoop == null) {
458                     exceptionOccured(Response.Status.NOT_FOUND,
459                             "PARTICIPANT_STATUS control loop not found in database: " + clEntry.getControlLoopId());
460                 }
461                 dbControlLoop.setState(clEntry.getState());
462                 monitoringProvider.createClElementStatistics(
463                         clEntry.getControlLoopStatistics().getClElementStatisticsList().getClElementStatistics());
464             }
465         }
466     }
467
468     private void exceptionOccured(Response.Status status, String reason) throws ControlLoopException {
469         throw new ControlLoopException(status, reason);
470     }
471 }