56a1ba9b3b294d2dfaa9dd13652511fab2c35259
[policy/clamp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2021 Nordix Foundation.
4  * ================================================================================
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  * SPDX-License-Identifier: Apache-2.0
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.clamp.controlloop.runtime.supervision;
22
23 import java.time.Instant;
24 import java.util.LinkedHashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.UUID;
28 import javax.ws.rs.core.Response;
29 import lombok.AllArgsConstructor;
30 import org.apache.commons.collections4.CollectionUtils;
31 import org.onap.policy.clamp.controlloop.common.exception.ControlLoopException;
32 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoop;
33 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopElement;
34 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopElementDefinition;
35 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.ControlLoopState;
36 import org.onap.policy.clamp.controlloop.models.controlloop.concepts.Participant;
37 import org.onap.policy.clamp.controlloop.models.controlloop.persistence.provider.ControlLoopProvider;
38 import org.onap.policy.clamp.controlloop.models.controlloop.persistence.provider.ParticipantProvider;
39 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ControlLoopStateChange;
40 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ControlLoopUpdate;
41 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantDeregister;
42 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantDeregisterAck;
43 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantMessageType;
44 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantRegister;
45 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantRegisterAck;
46 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantStatus;
47 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantUpdate;
48 import org.onap.policy.clamp.controlloop.models.messages.dmaap.participant.ParticipantUpdateAck;
49 import org.onap.policy.clamp.controlloop.runtime.commissioning.CommissioningProvider;
50 import org.onap.policy.clamp.controlloop.runtime.main.parameters.ClRuntimeParameterGroup;
51 import org.onap.policy.clamp.controlloop.runtime.monitoring.MonitoringProvider;
52 import org.onap.policy.clamp.controlloop.runtime.supervision.comm.ControlLoopStateChangePublisher;
53 import org.onap.policy.clamp.controlloop.runtime.supervision.comm.ControlLoopUpdatePublisher;
54 import org.onap.policy.clamp.controlloop.runtime.supervision.comm.ParticipantDeregisterAckPublisher;
55 import org.onap.policy.clamp.controlloop.runtime.supervision.comm.ParticipantRegisterAckPublisher;
56 import org.onap.policy.clamp.controlloop.runtime.supervision.comm.ParticipantStatusListener;
57 import org.onap.policy.clamp.controlloop.runtime.supervision.comm.ParticipantUpdatePublisher;
58 import org.onap.policy.common.endpoints.event.comm.TopicSink;
59 import org.onap.policy.common.endpoints.listeners.MessageTypeDispatcher;
60 import org.onap.policy.common.utils.services.ServiceManager;
61 import org.onap.policy.common.utils.services.ServiceManagerException;
62 import org.onap.policy.models.base.PfModelException;
63 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66 import org.springframework.stereotype.Component;
67
68 /**
69  * This class handles supervision of control loop instances, so only one object of this type should be built at a time.
70  *
71  * <p/>
72  * It is effectively a singleton that is started at system start.
73  */
74 @Component
75 @AllArgsConstructor
76 public class SupervisionHandler {
77     private static final Logger LOGGER = LoggerFactory.getLogger(SupervisionHandler.class);
78
79     private static final String CONTROL_LOOP_CANNOT_TRANSITION_FROM_STATE = "Control loop can't transition from state ";
80     private static final String CONTROL_LOOP_IS_ALREADY_IN_STATE = "Control loop is already in state ";
81     private static final String TO_STATE = " to state ";
82     private static final String AND_TRANSITIONING_TO_STATE = " and transitioning to state ";
83
84     private final ControlLoopProvider controlLoopProvider;
85     private final ParticipantProvider participantProvider;
86     private final MonitoringProvider monitoringProvider;
87     private final CommissioningProvider commissioningProvider;
88
89     // Publishers for participant communication
90     private final ControlLoopUpdatePublisher controlLoopUpdatePublisher;
91     private final ControlLoopStateChangePublisher controlLoopStateChangePublisher;
92     private final ParticipantRegisterAckPublisher participantRegisterAckPublisher;
93     private final ParticipantDeregisterAckPublisher participantDeregisterAckPublisher;
94     private final ParticipantUpdatePublisher participantUpdatePublisher;
95
96     /**
97      * Supervision trigger called when a command is issued on control loops.
98      *
99      * <p/>
100      * Causes supervision to start or continue supervision on the control loops in question.
101      *
102      * @param controlLoopIdentifierList the control loops for which the supervision command has been issued
103      * @throws ControlLoopException on supervision triggering exceptions
104      */
105     public void triggerControlLoopSupervision(List<ToscaConceptIdentifier> controlLoopIdentifierList)
106             throws ControlLoopException {
107
108         LOGGER.debug("triggering control loop supervision on control loops {}", controlLoopIdentifierList);
109
110         if (CollectionUtils.isEmpty(controlLoopIdentifierList)) {
111             // This is just to force throwing of the exception in certain circumstances.
112             exceptionOccured(Response.Status.NOT_ACCEPTABLE, "The list of control loops for supervision is empty");
113         }
114
115         for (ToscaConceptIdentifier controlLoopId : controlLoopIdentifierList) {
116             try {
117                 var controlLoop = controlLoopProvider.getControlLoop(controlLoopId);
118
119                 superviseControlLoop(controlLoop);
120
121                 controlLoopProvider.updateControlLoop(controlLoop);
122             } catch (PfModelException pfme) {
123                 throw new ControlLoopException(pfme.getErrorResponse().getResponseCode(), pfme.getMessage(), pfme);
124             }
125         }
126     }
127
128     /**
129      * Handle a ParticipantStatus message from a participant.
130      *
131      * @param participantStatusMessage the ParticipantStatus message received from a participant
132      */
133     public void handleParticipantMessage(ParticipantStatus participantStatusMessage) {
134         LOGGER.debug("Participant Status received {}", participantStatusMessage);
135         try {
136             superviseParticipant(participantStatusMessage);
137         } catch (PfModelException | ControlLoopException svExc) {
138             LOGGER.warn("error supervising participant {}", participantStatusMessage.getParticipantId(), svExc);
139             return;
140         }
141
142         try {
143             superviseControlLoops(participantStatusMessage);
144         } catch (PfModelException | ControlLoopException svExc) {
145             LOGGER.warn("error supervising participant {}", participantStatusMessage.getParticipantId(), svExc);
146         }
147     }
148
149     /**
150      * Handle a ParticipantRegister message from a participant.
151      *
152      * @param participantRegisterMessage the ParticipantRegister message received from a participant
153      */
154     public void handleParticipantMessage(ParticipantRegister participantRegisterMessage) {
155         LOGGER.debug("Participant Register received {}", participantRegisterMessage);
156         sendParticipantAckMessage(participantRegisterMessage);
157         sendParticipantUpdate(participantRegisterMessage);
158     }
159
160     /**
161      * Handle a ParticipantDeregister message from a participant.
162      *
163      * @param participantDeregisterMessage the ParticipantDeregister message received from a participant
164      */
165     public void handleParticipantMessage(ParticipantDeregister participantDeregisterMessage) {
166         LOGGER.debug("Participant Deregister received {}", participantDeregisterMessage);
167         sendParticipantAckMessage(participantDeregisterMessage);
168     }
169
170     /**
171      * Handle a ParticipantUpdateAck message from a participant.
172      *
173      * @param participantUpdateAckMessage the ParticipantUpdateAck message received from a participant
174      */
175     public void handleParticipantMessage(ParticipantUpdateAck participantUpdateAckMessage) {
176         LOGGER.debug("Participant Update Ack received {}", participantUpdateAckMessage);
177     }
178
179     /**
180      * Supervise a control loop, performing whatever actions need to be performed on the control loop.
181      *
182      * @param controlLoop the control loop to supervises
183      * @throws PfModelException on accessing models in the database
184      * @throws ControlLoopException on supervision errors
185      */
186     private void superviseControlLoop(ControlLoop controlLoop) throws ControlLoopException, PfModelException {
187         switch (controlLoop.getOrderedState()) {
188             case UNINITIALISED:
189                 superviseControlLoopUninitialization(controlLoop);
190                 break;
191
192             case PASSIVE:
193                 superviseControlLoopPassivation(controlLoop);
194                 break;
195
196             case RUNNING:
197                 superviseControlLoopActivation(controlLoop);
198                 break;
199
200             default:
201                 exceptionOccured(Response.Status.NOT_ACCEPTABLE,
202                         "A control loop cannot be commanded to go into state " + controlLoop.getOrderedState().name());
203         }
204     }
205
206     /**
207      * Supervise a control loop uninitialisation, performing whatever actions need to be performed on the control loop,
208      * control loop ordered state is UNINITIALIZED.
209      *
210      * @param controlLoop the control loop to supervises
211      * @throws ControlLoopException on supervision errors
212      */
213     private void superviseControlLoopUninitialization(ControlLoop controlLoop) throws ControlLoopException {
214         switch (controlLoop.getState()) {
215             case UNINITIALISED:
216                 exceptionOccured(Response.Status.NOT_ACCEPTABLE,
217                         CONTROL_LOOP_IS_ALREADY_IN_STATE + controlLoop.getState().name());
218                 break;
219
220             case UNINITIALISED2PASSIVE:
221             case PASSIVE:
222                 controlLoop.setState(ControlLoopState.PASSIVE2UNINITIALISED);
223                 controlLoopStateChangePublisher.send(controlLoop);
224                 break;
225
226             case PASSIVE2UNINITIALISED:
227                 exceptionOccured(Response.Status.NOT_ACCEPTABLE, CONTROL_LOOP_IS_ALREADY_IN_STATE
228                         + controlLoop.getState().name() + AND_TRANSITIONING_TO_STATE + controlLoop.getOrderedState());
229                 break;
230
231             default:
232                 exceptionOccured(Response.Status.NOT_ACCEPTABLE, CONTROL_LOOP_CANNOT_TRANSITION_FROM_STATE
233                         + controlLoop.getState().name() + TO_STATE + controlLoop.getOrderedState());
234                 break;
235         }
236     }
237
238     private void superviseControlLoopPassivation(ControlLoop controlLoop)
239             throws ControlLoopException, PfModelException {
240         switch (controlLoop.getState()) {
241             case PASSIVE:
242                 exceptionOccured(Response.Status.NOT_ACCEPTABLE,
243                         CONTROL_LOOP_IS_ALREADY_IN_STATE + controlLoop.getState().name());
244                 break;
245             case UNINITIALISED:
246                 controlLoop.setState(ControlLoopState.UNINITIALISED2PASSIVE);
247                 controlLoopUpdatePublisher.send(controlLoop);
248                 break;
249
250             case UNINITIALISED2PASSIVE:
251             case RUNNING2PASSIVE:
252                 exceptionOccured(Response.Status.NOT_ACCEPTABLE, CONTROL_LOOP_IS_ALREADY_IN_STATE
253                         + controlLoop.getState().name() + AND_TRANSITIONING_TO_STATE + controlLoop.getOrderedState());
254                 break;
255
256             case RUNNING:
257                 controlLoop.setState(ControlLoopState.RUNNING2PASSIVE);
258                 controlLoopStateChangePublisher.send(controlLoop);
259                 break;
260
261             default:
262                 exceptionOccured(Response.Status.NOT_ACCEPTABLE, CONTROL_LOOP_CANNOT_TRANSITION_FROM_STATE
263                         + controlLoop.getState().name() + TO_STATE + controlLoop.getOrderedState());
264                 break;
265         }
266     }
267
268     private void superviseControlLoopActivation(ControlLoop controlLoop) throws ControlLoopException {
269         switch (controlLoop.getState()) {
270             case RUNNING:
271                 exceptionOccured(Response.Status.NOT_ACCEPTABLE,
272                         CONTROL_LOOP_IS_ALREADY_IN_STATE + controlLoop.getState().name());
273                 break;
274
275             case PASSIVE2RUNNING:
276                 exceptionOccured(Response.Status.NOT_ACCEPTABLE, CONTROL_LOOP_IS_ALREADY_IN_STATE
277                         + controlLoop.getState().name() + AND_TRANSITIONING_TO_STATE + controlLoop.getOrderedState());
278                 break;
279
280             case PASSIVE:
281                 controlLoop.setState(ControlLoopState.PASSIVE2RUNNING);
282                 controlLoopStateChangePublisher.send(controlLoop);
283                 break;
284
285             default:
286                 exceptionOccured(Response.Status.NOT_ACCEPTABLE, CONTROL_LOOP_CANNOT_TRANSITION_FROM_STATE
287                         + controlLoop.getState().name() + TO_STATE + controlLoop.getOrderedState());
288                 break;
289         }
290     }
291
292     private void sendControlLoopUpdate(ControlLoop controlLoop) throws PfModelException {
293         var controlLoopUpdateMsg = new ControlLoopUpdate();
294         controlLoopUpdateMsg.setControlLoopId(controlLoop.getKey().asIdentifier());
295         controlLoopUpdateMsg.setControlLoop(controlLoop);
296         // TODO: We should look up the correct TOSCA node template here for the control loop
297         // Tiny hack implemented to return the tosca service template entry from the database and be passed onto dmaap
298         controlLoopUpdateMsg.setControlLoopDefinition(commissioningProvider.getToscaServiceTemplate(null, null));
299         controlLoopUpdatePublisher.send(controlLoopUpdateMsg);
300     }
301
302     private void sendControlLoopStateChange(ControlLoop controlLoop) {
303         var clsc = new ControlLoopStateChange();
304         clsc.setControlLoopId(controlLoop.getKey().asIdentifier());
305         clsc.setMessageId(UUID.randomUUID());
306         clsc.setOrderedState(controlLoop.getOrderedState());
307         controlLoopStateChangePublisher.send(clsc);
308     }
309
310     private void sendParticipantUpdate(ParticipantRegister participantRegisterMessage) {
311         var message = new ParticipantUpdate();
312         message.setParticipantId(participantRegisterMessage.getParticipantId());
313         message.setParticipantType(participantRegisterMessage.getParticipantType());
314         message.setTimestamp(Instant.now());
315
316         ControlLoopElementDefinition clDefinition = new ControlLoopElementDefinition();
317         clDefinition.setId(UUID.randomUUID());
318
319         try {
320             clDefinition.setControlLoopElementToscaServiceTemplate(commissioningProvider
321                     .getToscaServiceTemplate(null, null));
322         } catch (PfModelException pfme) {
323             LOGGER.warn("Get of tosca service template failed, cannot send participantupdate", pfme);
324             return;
325         }
326
327         Map<UUID, ControlLoopElementDefinition> controlLoopElementDefinitionMap = new LinkedHashMap<>();
328         controlLoopElementDefinitionMap.put(UUID.randomUUID(), clDefinition);
329
330         Map<ToscaConceptIdentifier, Map<UUID, ControlLoopElementDefinition>>
331             participantDefinitionUpdateMap = new LinkedHashMap<>();
332         participantDefinitionUpdateMap.put(participantRegisterMessage.getParticipantId(),
333                       controlLoopElementDefinitionMap);
334         message.setParticipantDefinitionUpdateMap(participantDefinitionUpdateMap);
335
336         LOGGER.debug("Participant Update sent", message);
337         participantUpdatePublisher.send(message);
338     }
339
340     private void sendParticipantAckMessage(ParticipantRegister participantRegisterMessage) {
341         var message = new ParticipantRegisterAck();
342         message.setResponseTo(participantRegisterMessage.getMessageId());
343         message.setMessage("Participant Register Ack");
344         message.setResult(true);
345         participantRegisterAckPublisher.send(message);
346     }
347
348     private void sendParticipantAckMessage(ParticipantDeregister participantDeregisterMessage) {
349         var message = new ParticipantDeregisterAck();
350         message.setResponseTo(participantDeregisterMessage.getMessageId());
351         message.setMessage("Participant Deregister Ack");
352         message.setResult(true);
353         participantDeregisterAckPublisher.send(message);
354     }
355
356     private void superviseParticipant(ParticipantStatus participantStatusMessage)
357             throws PfModelException, ControlLoopException {
358         if (participantStatusMessage.getParticipantId() == null) {
359             exceptionOccured(Response.Status.NOT_FOUND, "Participant ID on PARTICIPANT_STATUS message is null");
360         }
361
362         List<Participant> participantList =
363                 participantProvider.getParticipants(participantStatusMessage.getParticipantId().getName(),
364                         participantStatusMessage.getParticipantId().getVersion());
365
366         if (CollectionUtils.isEmpty(participantList)) {
367             var participant = new Participant();
368             participant.setName(participantStatusMessage.getParticipantId().getName());
369             participant.setVersion(participantStatusMessage.getParticipantId().getVersion());
370             participant.setDefinition(new ToscaConceptIdentifier("unknown", "0.0.0"));
371             participant.setParticipantState(participantStatusMessage.getState());
372             participant.setHealthStatus(participantStatusMessage.getHealthStatus());
373
374             participantList.add(participant);
375             participantProvider.createParticipants(participantList);
376         } else {
377             for (Participant participant : participantList) {
378                 participant.setParticipantState(participantStatusMessage.getState());
379                 participant.setHealthStatus(participantStatusMessage.getHealthStatus());
380             }
381             participantProvider.updateParticipants(participantList);
382         }
383
384         monitoringProvider.createParticipantStatistics(List.of(participantStatusMessage.getParticipantStatistics()));
385     }
386
387     private void superviseControlLoops(ParticipantStatus participantStatusMessage)
388             throws PfModelException, ControlLoopException {
389         if (CollectionUtils.isEmpty(participantStatusMessage.getControlLoops().getControlLoopList())) {
390             return;
391         }
392
393         for (ControlLoop controlLoop : participantStatusMessage.getControlLoops().getControlLoopList()) {
394             if (controlLoop == null) {
395                 exceptionOccured(Response.Status.NOT_FOUND,
396                         "PARTICIPANT_STATUS message references unknown control loop: " + controlLoop);
397             }
398
399             var dbControlLoop = controlLoopProvider
400                     .getControlLoop(new ToscaConceptIdentifier(controlLoop.getName(), controlLoop.getVersion()));
401             if (dbControlLoop == null) {
402                 exceptionOccured(Response.Status.NOT_FOUND,
403                         "PARTICIPANT_STATUS control loop not found in database: " + controlLoop);
404             }
405
406             for (ControlLoopElement element : controlLoop.getElements().values()) {
407                 ControlLoopElement dbElement = dbControlLoop.getElements().get(element.getId());
408
409                 if (dbElement == null) {
410                     exceptionOccured(Response.Status.NOT_FOUND,
411                             "PARTICIPANT_STATUS message references unknown control loop element: " + element);
412                 }
413
414                 // Replace element entry in the database
415                 dbControlLoop.getElements().put(element.getId(), element);
416             }
417             controlLoopProvider.updateControlLoop(dbControlLoop);
418         }
419
420         for (ControlLoop controlLoop : participantStatusMessage.getControlLoops().getControlLoopList()) {
421             monitoringProvider.createClElementStatistics(controlLoop.getControlLoopElementStatisticsList(controlLoop));
422         }
423     }
424
425     private void exceptionOccured(Response.Status status, String reason) throws ControlLoopException {
426         throw new ControlLoopException(status, reason);
427     }
428 }