1756246b488b541fb15ab2a23a81cb1ab8a10bd7
[policy/drools-pdp.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * feature-active-standby-management
4  * ================================================================================
5  * Copyright (C) 2017-2019, 2021 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.drools.activestandby;
22
23 import java.util.Timer;
24 import java.util.TimerTask;
25 import lombok.Getter;
26 import org.onap.policy.common.im.MonitorTime;
27 import org.onap.policy.common.im.StateChangeNotifier;
28 import org.onap.policy.common.im.StateManagement;
29 import org.onap.policy.common.utils.time.CurrentTime;
30 import org.onap.policy.drools.system.PolicyEngine;
31 import org.onap.policy.drools.system.PolicyEngineConstants;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /*
36  * Some background:
37  *
38  * Originally, there was a "StandbyStateChangeNotifier" that belonged to policy-core, and this class's
39  * handleStateChange() method used to take care of invoking conn.standDownPdp().
40  *
41  * But testing revealed that when a state change to hot standby
42  * occurred from a demote() operation, first the PMStandbyStateChangeNotifier.handleStateChange() method
43  * would be invoked and then the StandbyStateChangeNotifier.handleStateChange() method would be invoked,
44  * and this ordering was creating the following problem:
45  *
46  * When PMStandbyStateChangeNotifier.handleStateChange() was invoked it would take a long time to finish,
47  * because it would result in SingleThreadedUebTopicSource.stop() being invoked, which can potentially do a
48  * 5 second sleep for each controller being stopped.
49  *
50  * Meanwhile, as these controller stoppages and their associated sleeps were occurring, the election handler
51  * would discover the demoted PDP in hotstandby (but still designated!) and promote it, resulting in the
52  * standbyStatus going from hotstandby to providingservice.  So then, by the time that
53  * PMStandbyStateChangeNotifier.handleStateChange() finished its work and
54  * StandbyStateChangeNotifier.handleStateChange() started executing, the standbyStatus was no longer hotstandby
55  * (as effected by the demote), but providingservice (as reset by the election handling logic) and
56  * conn.standDownPdp() would not get called!
57  *
58  * To fix this bug, we consolidated StandbyStateChangeNotifier and PMStandbyStateChangeNotifier,
59  * with the standDownPdp() always
60  * being invoked prior to the TopicEndpoint.manager.lock().  In this way, when the election handling logic is invoked
61  * during the controller stoppages, the PDP is in hotstandby and the standdown occurs.
62  *
63  */
64 public class PmStandbyStateChangeNotifier extends StateChangeNotifier {
65     // get an instance of logger
66     private static final Logger logger = LoggerFactory.getLogger(PmStandbyStateChangeNotifier.class);
67     private Timer delayActivateTimer;
68     private boolean isWaitingForActivation;
69     private long startTimeWaitingForActivationMs;
70     private long waitInterval;
71     private boolean isNowActivating;
72     @Getter
73     private String previousStandbyStatus;
74     private final CurrentTime currentTime = MonitorTime.getInstance();
75     private final Factory timerFactory = Factory.getInstance();
76     public static final String NONE = "none";
77     public static final String UNSUPPORTED = "unsupported";
78     public static final String HOTSTANDBY_OR_COLDSTANDBY = "hotstandby_or_coldstandby";
79
80     /**
81      * Constructor.
82      *
83      */
84     public PmStandbyStateChangeNotifier() {
85         var pdpUpdateInterval =
86             Integer.parseInt(ActiveStandbyProperties.getProperty(ActiveStandbyProperties.PDP_UPDATE_INTERVAL));
87         isWaitingForActivation = false;
88         startTimeWaitingForActivationMs = currentTime.getMillis();
89         // delay the activate so the DesignatedWaiter can run twice - give it an extra 2 seconds
90         waitInterval = 2 * pdpUpdateInterval + 2000L;
91         isNowActivating = false;
92         previousStandbyStatus = PmStandbyStateChangeNotifier.NONE;
93     }
94
95     @Override
96     public void handleStateChange() {
97         /*
98          * A note on synchronization: This method is not synchronized because the caller,
99          * stateManagememt, has synchronize all of its methods. Only one stateManagement operation
100          * can occur at a time. Thus, only one handleStateChange() call will ever be made at a time.
101          */
102         logger.debug("handleStateChange: Entering, message={}, standbyStatus={}", super.getMessage(),
103                         super.getStateManagement().getStandbyStatus());
104         String standbyStatus = super.getStateManagement().getStandbyStatus();
105         String pdpId = ActiveStandbyProperties.getProperty(ActiveStandbyProperties.NODE_NAME);
106
107         logger.debug("handleStateChange: previousStandbyStatus = {}; standbyStatus = {}",
108                 previousStandbyStatus, standbyStatus);
109
110         if (standbyStatus == null || standbyStatus.equals(StateManagement.NULL_VALUE)) {
111             logger.debug("handleStateChange: standbyStatus is null; standing down PDP={}", pdpId);
112             standDownPdpNull(pdpId);
113
114         } else if (standbyStatus.equals(StateManagement.HOT_STANDBY)
115                 || standbyStatus.equals(StateManagement.COLD_STANDBY)) {
116             logger.debug("handleStateChange: standbyStatus={}; standing down PDP={}", standbyStatus, pdpId);
117             standDownPdp(pdpId, standbyStatus);
118
119         } else if (standbyStatus.equals(StateManagement.PROVIDING_SERVICE)) {
120             logger.debug("handleStateChange: standbyStatus= {} scheduling activation of PDP={}", standbyStatus,
121                             pdpId);
122             schedulePdpActivation(pdpId, standbyStatus);
123
124         } else {
125             logger.error("handleStateChange: Unsupported standbyStatus={}; standing down PDP={}", standbyStatus, pdpId);
126             standDownPdpUnsupported(pdpId, standbyStatus);
127         }
128
129         logger.debug("handleStateChange: Exiting");
130     }
131
132     private void standDownPdpNull(String pdpId) {
133         if (previousStandbyStatus.equals(StateManagement.NULL_VALUE)) {
134             // We were just here and did this successfully
135             logger.debug("handleStateChange: "
136                             + "Is returning because standbyStatus is null and was previously 'null'; PDP={}",
137                             pdpId);
138             return;
139         }
140
141         isWaitingForActivation = false;
142         try {
143             logger.debug("handleStateChange: null:  cancelling delayActivationTimer.");
144             cancelTimer();
145             // Only want to lock the endpoints, not the controllers.
146             getPolicyEngineManager().deactivate();
147             // The operation was fully successful, but you cannot assign it a real null value
148             // because later we might try to execute previousStandbyStatus.equals() and get
149             // a null pointer exception.
150             previousStandbyStatus = StateManagement.NULL_VALUE;
151         } catch (Exception e) {
152             logger.warn("handleStateChange: standbyStatus == null caught exception: ", e);
153         }
154     }
155
156     private void standDownPdp(String pdpId, String standbyStatus) {
157         if (previousStandbyStatus.equals(PmStandbyStateChangeNotifier.HOTSTANDBY_OR_COLDSTANDBY)) {
158             // We were just here and did this successfully
159             logger.debug("handleStateChange: Is returning because standbyStatus is {}"
160                             + " and was previously {}; PDP= {}", standbyStatus, previousStandbyStatus, pdpId);
161             return;
162         }
163
164         isWaitingForActivation = false;
165         try {
166             logger.debug("handleStateChange: HOT_STNDBY || COLD_STANDBY:  cancelling delayActivationTimer.");
167             cancelTimer();
168             // Only want to lock the endpoints, not the controllers.
169             getPolicyEngineManager().deactivate();
170             // The operation was fully successful
171             previousStandbyStatus = PmStandbyStateChangeNotifier.HOTSTANDBY_OR_COLDSTANDBY;
172         } catch (Exception e) {
173             logger.warn("handleStateChange: standbyStatus = {} caught exception: {}", standbyStatus, e.getMessage(),
174                     e);
175         }
176     }
177
178     private void schedulePdpActivation(String pdpId, String standbyStatus) {
179         if (previousStandbyStatus.equals(StateManagement.PROVIDING_SERVICE)) {
180             // We were just here and did this successfully
181             logger.debug("handleStateChange: Is returning because standbyStatus is {}"
182                             + "and was previously {}; PDP={}", standbyStatus, previousStandbyStatus, pdpId);
183             return;
184         }
185
186         try {
187             // UnLock all the endpoints
188             logger.debug("handleStateChange: standbyStatus={}; controllers must be unlocked.", standbyStatus);
189             /*
190              * Only endpoints should be unlocked. Controllers have not been locked. Because,
191              * sometimes, it is possible for more than one PDP-D to become active (race
192              * conditions) we need to delay the activation of the topic endpoint interfaces to
193              * give the election algorithm time to resolve the conflict.
194              */
195             logger.debug("handleStateChange: PROVIDING_SERVICE isWaitingForActivation= {}",
196                             isWaitingForActivation);
197
198             // Delay activation for 2*pdpUpdateInterval+2000 ms in case of an election handler
199             // conflict.
200             // You could have multiple election handlers thinking they can take over.
201
202             // First let's check that the timer has not died
203             checkTimerStatus();
204
205             if (!isWaitingForActivation) {
206                 // Just in case there is an old timer hanging around
207                 logger.debug("handleStateChange: PROVIDING_SERVICE cancelling delayActivationTimer.");
208                 cancelTimer();
209                 delayActivateTimer = timerFactory.makeTimer();
210                 // delay the activate so the DesignatedWaiter can run twice
211                 delayActivateTimer.schedule(new DelayActivateClass(), waitInterval);
212                 isWaitingForActivation = true;
213                 startTimeWaitingForActivationMs = currentTime.getMillis();
214                 logger.debug("handleStateChange: PROVIDING_SERVICE scheduling delayActivationTimer in {} ms",
215                                 waitInterval);
216             } else {
217                 logger.debug("handleStateChange: PROVIDING_SERVICE delayActivationTimer is "
218                                 + "waiting for activation.");
219             }
220
221         } catch (Exception e) {
222             logger.warn("handleStateChange: PROVIDING_SERVICE standbyStatus == providingservice caught exception: ",
223                     e);
224         }
225     }
226
227     private void checkTimerStatus() {
228         if (isWaitingForActivation) {
229             logger.debug("handleStateChange: PROVIDING_SERVICE isWaitingForActivation = {}",
230                             isWaitingForActivation);
231             long now = currentTime.getMillis();
232             long waitTimeMs = now - startTimeWaitingForActivationMs;
233             if (waitTimeMs > 3 * waitInterval) {
234                 logger.debug("handleStateChange: PROVIDING_SERVICE looks like the activation wait timer "
235                                 + "may be hung, waitTimeMs = {} and allowable waitInterval = {}"
236                                 + " Checking whether it is currently in activation. isNowActivating = {}",
237                                 waitTimeMs, waitInterval, isNowActivating);
238                 // Now check that it is not currently executing an activation
239                 if (!isNowActivating) {
240                     logger.debug("handleStateChange: PROVIDING_SERVICE looks like the activation "
241                                     + "wait timer died");
242                     // This will assure the timer is cancelled and rescheduled.
243                     isWaitingForActivation = false;
244                 }
245             }
246         }
247     }
248
249     private void standDownPdpUnsupported(String pdpId, String standbyStatus) {
250         if (previousStandbyStatus.equals(PmStandbyStateChangeNotifier.UNSUPPORTED)) {
251             // We were just here and did this successfully
252             logger.debug("handleStateChange: Is returning because standbyStatus is "
253                             + "UNSUPPORTED and was previously {}; PDP={}", previousStandbyStatus, pdpId);
254             return;
255         }
256
257         // Only want to lock the endpoints, not the controllers.
258         isWaitingForActivation = false;
259         try {
260             logger.debug("handleStateChange: unsupported standbystatus:  cancelling delayActivationTimer.");
261             cancelTimer();
262             getPolicyEngineManager().deactivate();
263             // We know the standbystatus is unsupported
264             previousStandbyStatus = PmStandbyStateChangeNotifier.UNSUPPORTED;
265         } catch (Exception e) {
266             logger.warn("handleStateChange: Unsupported standbyStatus = {} " + "caught exception: {} ",
267                     standbyStatus, e.getMessage(), e);
268         }
269     }
270
271     private void cancelTimer() {
272         if (delayActivateTimer != null) {
273             delayActivateTimer.cancel();
274         }
275     }
276
277     private class DelayActivateClass extends TimerTask {
278
279         private Object delayActivateLock = new Object();
280
281
282         @Override
283         public void run() {
284             isNowActivating = true;
285             try {
286                 logger.debug("DelayActivateClass.run: entry");
287                 synchronized (delayActivateLock) {
288                     getPolicyEngineManager().activate();
289                     // The state change fully succeeded
290                     previousStandbyStatus = StateManagement.PROVIDING_SERVICE;
291                     // We want to set this to false here because the activate call can take a while
292                     isWaitingForActivation = false;
293                     isNowActivating = false;
294                 }
295                 logger.debug("DelayActivateClass.run.exit");
296             } catch (Exception e) {
297                 isWaitingForActivation = false;
298                 isNowActivating = false;
299                 logger.warn("DelayActivateClass.run: caught an unexpected exception "
300                         + "calling PolicyEngineConstants.getManager().activate: ", e);
301             }
302         }
303     }
304
305     // these may be overridden by junit tests
306
307     protected PolicyEngine getPolicyEngineManager() {
308         return PolicyEngineConstants.getManager();
309     }
310 }