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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.policy.drools.activestandby;
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;
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().
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:
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.
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!
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.
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";
83 public PmStandbyStateChangeNotifier() {
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;
95 public void handleStateChange() {
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.
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);
106 logger.debug("handleStateChange: previousStandbyStatus = {}; standbyStatus = {}",
107 previousStandbyStatus, standbyStatus);
109 if (standbyStatus == null || standbyStatus.equals(StateManagement.NULL_VALUE)) {
110 logger.debug("handleStateChange: standbyStatus is null; standing down PDP={}", pdpId);
111 standDownPdpNull(pdpId);
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);
118 } else if (standbyStatus.equals(StateManagement.PROVIDING_SERVICE)) {
119 logger.debug("handleStateChange: standbyStatus= {} scheduling activation of PDP={}", standbyStatus,
121 schedulePdpActivation(pdpId, standbyStatus);
124 logger.error("handleStateChange: Unsupported standbyStatus={}; standing down PDP={}", standbyStatus, pdpId);
125 standDownPdpUnsupported(pdpId, standbyStatus);
128 logger.debug("handleStateChange: Exiting");
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={}",
140 isWaitingForActivation = false;
142 logger.debug("handleStateChange: null: cancelling delayActivationTimer.");
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);
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);
163 isWaitingForActivation = false;
165 logger.debug("handleStateChange: HOT_STNDBY || COLD_STANDBY: cancelling delayActivationTimer.");
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(),
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);
186 // UnLock all the endpoints
187 logger.debug("handleStateChange: standbyStatus={}; controllers must be unlocked.", standbyStatus);
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.
194 logger.debug("handleStateChange: PROVIDING_SERVICE isWaitingForActivation= {}",
195 isWaitingForActivation);
197 // Delay activation for 2*pdpUpdateInterval+2000 ms in case of an election handler
199 // You could have multiple election handlers thinking they can take over.
201 // First let's check that the timer has not died
204 if (!isWaitingForActivation) {
205 // Just in case there is an old timer hanging around
206 logger.debug("handleStateChange: PROVIDING_SERVICE cancelling delayActivationTimer.");
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",
216 logger.debug("handleStateChange: PROVIDING_SERVICE delayActivationTimer is "
217 + "waiting for activation.");
220 } catch (Exception e) {
221 logger.warn("handleStateChange: PROVIDING_SERVICE standbyStatus == providingservice caught exception: ",
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;
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);
256 // Only want to lock the endpoints, not the controllers.
257 isWaitingForActivation = false;
259 logger.debug("handleStateChange: unsupported standbystatus: cancelling delayActivationTimer.");
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);
270 private void cancelTimer() {
271 if (delayActivateTimer != null) {
272 delayActivateTimer.cancel();
276 private class DelayActivateClass extends TimerTask {
278 private Object delayActivateLock = new Object();
283 isNowActivating = true;
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;
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);
304 public String getPreviousStandbyStatus() {
305 return previousStandbyStatus;
308 // these may be overridden by junit tests
310 protected PolicyEngine getPolicyEngineManager() {
311 return PolicyEngineConstants.getManager();