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