package com.webobjects.monitor.application.starter; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; import com.webobjects.foundation.NSMutableSet; import com.webobjects.monitor._private.MApplication; import com.webobjects.monitor._private.MHost; import com.webobjects.monitor._private.MInstance; /** * Bounces an application gracefully. It does so by starting at least one inactive instance * per active host (or 10 % of the total active instance count), waiting * until they have started, then refusing sessions for all old instances and * turning scheduling on for all but the number of instances we started * originally. The next effect should be that the new users get the new app, * old instances die in due time and then restart when the sessions stop. * * You must have at least one inactive instance in order to perform a graceful bounce. * * You may or may not need to set ERKillTimer to prevent totally * long-running sessions to keep the app from dying. * * @author ak */ public class GracefulBouncer extends ApplicationStarter { public GracefulBouncer(MApplication app) { super(app); } @Override protected void bounce() throws InterruptedException { NSArray<MInstance> instances = application().instanceArray().immutableClone(); NSMutableArray<MInstance> runningInstances = new NSMutableArray<>(); NSMutableSet<MHost> activeHosts = new NSMutableSet<>(); NSMutableDictionary<MHost, NSMutableArray<MInstance>> inactiveInstancesByHost = new NSMutableDictionary<MHost, NSMutableArray<MInstance>>(); NSMutableDictionary<MHost, NSMutableArray<MInstance>> activeInstancesByHost = new NSMutableDictionary<MHost, NSMutableArray<MInstance>>(); for (MInstance instance : instances) { MHost host = instance.host(); if (instance.isRunning_M()) { runningInstances.addObject(instance); activeHosts.addObject(host); NSMutableArray<MInstance> currentInstances = activeInstancesByHost.objectForKey(host); if (currentInstances == null) { currentInstances = new NSMutableArray<>(); activeInstancesByHost.setObjectForKey(currentInstances, host); } currentInstances.addObject(instance); } else { NSMutableArray<MInstance> currentInstances = inactiveInstancesByHost.objectForKey(host); if (currentInstances == null) { currentInstances = new NSMutableArray<>(); inactiveInstancesByHost.setObjectForKey(currentInstances, host); } currentInstances.addObject(instance); } } if (inactiveInstancesByHost.isEmpty()) { addObjectsFromArrayIfAbsentToErrorMessageArray( new NSArray<>("You must have at least one inactive instance to perform a graceful bounce.")); return; } int numToStartPerHost = 1; if (activeHosts.count() > 0) { numToStartPerHost = (int) (runningInstances.count() / activeHosts.count() * .1); } if (numToStartPerHost < 1) { numToStartPerHost = 1; } boolean useScheduling = true; for (MInstance instance : runningInstances) { useScheduling &= instance.schedulingEnabled() != null && instance.schedulingEnabled().booleanValue(); } NSMutableArray<MInstance> startingInstances = new NSMutableArray<>(); for (int i = 0; i < numToStartPerHost; i++) { for (MHost host : activeHosts) { NSArray<MInstance> inactiveInstances = inactiveInstancesByHost.objectForKey(host); if (inactiveInstances != null && inactiveInstances.count() >= i) { MInstance instance = inactiveInstances.objectAtIndex(i); log("Starting inactive instance " + instance.displayName() + " on host " + host.addressAsString()); startingInstances.addObject(instance); } else { log("Not enough inactive instances on host: " + host.addressAsString()); } } } for (MInstance instance : startingInstances) { if (useScheduling) { instance.setSchedulingEnabled(Boolean.TRUE); } instance.setAutoRecover(Boolean.TRUE); } handler().sendUpdateInstancesToWotaskds(startingInstances, activeHosts.allObjects()); handler().sendStartInstancesToWotaskds(startingInstances, activeHosts.allObjects()); boolean waiting = true; // wait until apps have started while (waiting) { handler().startReading(); try { log("Checking for started instances"); handler().getInstanceStatusForHosts(activeHosts.allObjects()); boolean allStarted = true; for (MInstance instance : startingInstances) { allStarted &= instance.isRunning_M(); } if (allStarted) { waiting = false; } else { sleep(10 * 1000); } } finally { handler().endReading(); } } log("Started instances sucessfully"); // turn scheduling off for (MHost host : activeHosts) { NSArray<MInstance> currentInstances = activeInstancesByHost.objectForKey(host); for (MInstance instance : currentInstances) { if (useScheduling) { instance.setSchedulingEnabled(Boolean.FALSE); } instance.setAutoRecover(Boolean.FALSE); } } handler().sendUpdateInstancesToWotaskds(runningInstances, activeHosts.allObjects()); // then start to refuse new sessions for (MHost host : activeHosts) { NSArray<MInstance> currentInstances = activeInstancesByHost.objectForKey(host); for (MInstance instance : currentInstances) { instance.setRefusingNewSessions(true); } } handler().sendRefuseSessionToWotaskds(runningInstances, activeHosts.allObjects(), true); log("Refused new sessions: " + runningInstances); // turn scheduling on again, but only NSMutableArray<MInstance> restarting = new NSMutableArray<>(); for (MHost host : activeHosts) { NSArray<MInstance> currentInstances = activeInstancesByHost.objectForKey(host); for (int i = 0; i < currentInstances.count() - numToStartPerHost; i++) { MInstance instance = currentInstances.objectAtIndex(i); if (useScheduling) { instance.setSchedulingEnabled(Boolean.TRUE); } instance.setAutoRecover(Boolean.TRUE); restarting.addObject(instance); } } handler().sendUpdateInstancesToWotaskds(restarting, activeHosts.allObjects()); log("Started scheduling again: " + restarting); handler().startReading(); try { handler().getInstanceStatusForHosts(activeHosts.allObjects()); log("Finished"); } finally { handler().endReading(); } } }