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