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