735e3a2a3173ccbd16ef9f31418192f6f870472e
[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 int pdpUpdateInterval;
68     private boolean isWaitingForActivation;
69     private long startTimeWaitingForActivationMs;
70     private long waitInterval;
71     private boolean isNowActivating;
72     private String previousStandbyStatus;
73     private final CurrentTime currentTime = MonitorTime.getInstance();
74     private final Factory timerFactory = Factory.getInstance();
75     public static final String NONE = "none";
76     public static final String UNSUPPORTED = "unsupported";
77     public static final String HOTSTANDBY_OR_COLDSTANDBY = "hotstandby_or_coldstandby";
78
79     /**
80      * Constructor.
81      *
82      */
83     public PmStandbyStateChangeNotifier() {
84         pdpUpdateInterval =
85                 Integer.parseInt(ActiveStandbyProperties.getProperty(ActiveStandbyProperties.PDP_UPDATE_INTERVAL));
86         isWaitingForActivation = false;
87         startTimeWaitingForActivationMs = currentTime.getMillis();
88         // delay the activate so the DesignatedWaiter can run twice - give it an extra 2 seconds
89         waitInterval = 2 * pdpUpdateInterval + 2000L;
90         isNowActivating = false;
91         previousStandbyStatus = PmStandbyStateChangeNotifier.NONE;
92     }
93
94     @Override
95     public void handleStateChange() {
96         /*
97          * A note on synchronization: This method is not synchronized because the caller,
98          * stateManagememt, has synchronize all of its methods. Only one stateManagement operation
99          * can occur at a time. Thus, only one handleStateChange() call will ever be made at a time.
100          */
101         logger.debug("handleStateChange: Entering, message={}, standbyStatus={}", super.getMessage(),
102                         super.getStateManagement().getStandbyStatus());
103         String standbyStatus = super.getStateManagement().getStandbyStatus();
104         String pdpId = ActiveStandbyProperties.getProperty(ActiveStandbyProperties.NODE_NAME);
105
106         logger.debug("handleStateChange: previousStandbyStatus = {}; standbyStatus = {}",
107                 previousStandbyStatus, standbyStatus);
108
109         if (standbyStatus == null || standbyStatus.equals(StateManagement.NULL_VALUE)) {
110             logger.debug("handleStateChange: standbyStatus is null; standing down PDP={}", pdpId);
111             standDownPdpNull(pdpId);
112
113         } else if (standbyStatus.equals(StateManagement.HOT_STANDBY)
114                 || standbyStatus.equals(StateManagement.COLD_STANDBY)) {
115             logger.debug("handleStateChange: standbyStatus={}; standing down PDP={}", standbyStatus, pdpId);
116             standDownPdp(pdpId, standbyStatus);
117
118         } else if (standbyStatus.equals(StateManagement.PROVIDING_SERVICE)) {
119             logger.debug("handleStateChange: standbyStatus= {} scheduling activation of PDP={}", standbyStatus,
120                             pdpId);
121             schedulePdpActivation(pdpId, standbyStatus);
122
123         } else {
124             logger.error("handleStateChange: Unsupported standbyStatus={}; standing down PDP={}", standbyStatus, pdpId);
125             standDownPdpUnsupported(pdpId, standbyStatus);
126         }
127
128         logger.debug("handleStateChange: Exiting");
129     }
130
131     private void standDownPdpNull(String pdpId) {
132         if (previousStandbyStatus.equals(StateManagement.NULL_VALUE)) {
133             // We were just here and did this successfully
134             logger.debug("handleStateChange: "
135                             + "Is returning because standbyStatus is null and was previously 'null'; PDP={}",
136                             pdpId);
137             return;
138         }
139
140         isWaitingForActivation = false;
141         try {
142             logger.debug("handleStateChange: null:  cancelling delayActivationTimer.");
143             cancelTimer();
144             // Only want to lock the endpoints, not the controllers.
145             getPolicyEngineManager().deactivate();
146             // The operation was fully successful, but you cannot assign it a real null value
147             // because later we might try to execute previousStandbyStatus.equals() and get
148             // a null pointer exception.
149             previousStandbyStatus = StateManagement.NULL_VALUE;
150         } catch (Exception e) {
151             logger.warn("handleStateChange: standbyStatus == null caught exception: ", e);
152         }
153     }
154
155     private void standDownPdp(String pdpId, String standbyStatus) {
156         if (previousStandbyStatus.equals(PmStandbyStateChangeNotifier.HOTSTANDBY_OR_COLDSTANDBY)) {
157             // We were just here and did this successfully
158             logger.debug("handleStateChange: Is returning because standbyStatus is {}"
159                             + " and was previously {}; PDP= {}", standbyStatus, previousStandbyStatus, pdpId);
160             return;
161         }
162
163         isWaitingForActivation = false;
164         try {
165             logger.debug("handleStateChange: HOT_STNDBY || COLD_STANDBY:  cancelling delayActivationTimer.");
166             cancelTimer();
167             // Only want to lock the endpoints, not the controllers.
168             getPolicyEngineManager().deactivate();
169             // The operation was fully successful
170             previousStandbyStatus = PmStandbyStateChangeNotifier.HOTSTANDBY_OR_COLDSTANDBY;
171         } catch (Exception e) {
172             logger.warn("handleStateChange: standbyStatus = {} caught exception: {}", standbyStatus, e.getMessage(),
173                     e);
174         }
175     }
176
177     private void schedulePdpActivation(String pdpId, String standbyStatus) {
178         if (previousStandbyStatus.equals(StateManagement.PROVIDING_SERVICE)) {
179             // We were just here and did this successfully
180             logger.debug("handleStateChange: Is returning because standbyStatus is {}"
181                             + "and was previously {}; PDP={}", standbyStatus, previousStandbyStatus, pdpId);
182             return;
183         }
184
185         try {
186             // UnLock all the endpoints
187             logger.debug("handleStateChange: standbyStatus={}; controllers must be unlocked.", standbyStatus);
188             /*
189              * Only endpoints should be unlocked. Controllers have not been locked. Because,
190              * sometimes, it is possible for more than one PDP-D to become active (race
191              * conditions) we need to delay the activation of the topic endpoint interfaces to
192              * give the election algorithm time to resolve the conflict.
193              */
194             logger.debug("handleStateChange: PROVIDING_SERVICE isWaitingForActivation= {}",
195                             isWaitingForActivation);
196
197             // Delay activation for 2*pdpUpdateInterval+2000 ms in case of an election handler
198             // conflict.
199             // You could have multiple election handlers thinking they can take over.
200
201             // First let's check that the timer has not died
202             checkTimerStatus();
203
204             if (!isWaitingForActivation) {
205                 // Just in case there is an old timer hanging around
206                 logger.debug("handleStateChange: PROVIDING_SERVICE cancelling delayActivationTimer.");
207                 cancelTimer();
208                 delayActivateTimer = timerFactory.makeTimer();
209                 // delay the activate so the DesignatedWaiter can run twice
210                 delayActivateTimer.schedule(new DelayActivateClass(), waitInterval);
211                 isWaitingForActivation = true;
212                 startTimeWaitingForActivationMs = currentTime.getMillis();
213                 logger.debug("handleStateChange: PROVIDING_SERVICE scheduling delayActivationTimer in {} ms",
214                                 waitInterval);
215             } else {
216                 logger.debug("handleStateChange: PROVIDING_SERVICE delayActivationTimer is "
217                                 + "waiting for activation.");
218             }
219
220         } catch (Exception e) {
221             logger.warn("handleStateChange: PROVIDING_SERVICE standbyStatus == providingservice caught exception: ",
222                     e);
223         }
224     }
225
226     private void checkTimerStatus() {
227         if (isWaitingForActivation) {
228             logger.debug("handleStateChange: PROVIDING_SERVICE isWaitingForActivation = {}",
229                             isWaitingForActivation);
230             long now = currentTime.getMillis();
231             long waitTimeMs = now - startTimeWaitingForActivationMs;
232             if (waitTimeMs > 3 * waitInterval) {
233                 logger.debug("handleStateChange: PROVIDING_SERVICE looks like the activation wait timer "
234                                 + "may be hung, waitTimeMs = {} and allowable waitInterval = {}"
235                                 + " Checking whether it is currently in activation. isNowActivating = {}",
236                                 waitTimeMs, waitInterval, isNowActivating);
237                 // Now check that it is not currently executing an activation
238                 if (!isNowActivating) {
239                     logger.debug("handleStateChange: PROVIDING_SERVICE looks like the activation "
240                                     + "wait timer died");
241                     // This will assure the timer is cancelled and rescheduled.
242                     isWaitingForActivation = false;
243                 }
244             }
245         }
246     }
247
248     private void standDownPdpUnsupported(String pdpId, String standbyStatus) {
249         if (previousStandbyStatus.equals(PmStandbyStateChangeNotifier.UNSUPPORTED)) {
250             // We were just here and did this successfully
251             logger.debug("handleStateChange: Is returning because standbyStatus is "
252                             + "UNSUPPORTED and was previously {}; PDP={}", previousStandbyStatus, pdpId);
253             return;
254         }
255
256         // Only want to lock the endpoints, not the controllers.
257         isWaitingForActivation = false;
258         try {
259             logger.debug("handleStateChange: unsupported standbystatus:  cancelling delayActivationTimer.");
260             cancelTimer();
261             getPolicyEngineManager().deactivate();
262             // We know the standbystatus is unsupported
263             previousStandbyStatus = PmStandbyStateChangeNotifier.UNSUPPORTED;
264         } catch (Exception e) {
265             logger.warn("handleStateChange: Unsupported standbyStatus = {} " + "caught exception: {} ",
266                     standbyStatus, e.getMessage(), e);
267         }
268     }
269
270     private void cancelTimer() {
271         if (delayActivateTimer != null) {
272             delayActivateTimer.cancel();
273         }
274     }
275
276     private class DelayActivateClass extends TimerTask {
277
278         private Object delayActivateLock = new Object();
279
280
281         @Override
282         public void run() {
283             isNowActivating = true;
284             try {
285                 logger.debug("DelayActivateClass.run: entry");
286                 synchronized (delayActivateLock) {
287                     getPolicyEngineManager().activate();
288                     // The state change fully succeeded
289                     previousStandbyStatus = StateManagement.PROVIDING_SERVICE;
290                     // We want to set this to false here because the activate call can take a while
291                     isWaitingForActivation = false;
292                     isNowActivating = false;
293                 }
294                 logger.debug("DelayActivateClass.run.exit");
295             } catch (Exception e) {
296                 isWaitingForActivation = false;
297                 isNowActivating = false;
298                 logger.warn("DelayActivateClass.run: caught an unexpected exception "
299                         + "calling PolicyEngineConstants.getManager().activate: ", e);
300             }
301         }
302     }
303
304     public String getPreviousStandbyStatus() {
305         return previousStandbyStatus;
306     }
307
308     // these may be overridden by junit tests
309
310     protected PolicyEngine getPolicyEngineManager() {
311         return PolicyEngineConstants.getManager();
312     }
313 }