@@ -12,12 +12,16 @@
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
*/
import com.webobjects.appserver.WOActionResults;
import com.webobjects.appserver.WOComponent;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WORedirect;
import com.webobjects.foundation.NSArray;
import com.webobjects.monitor._private.MObject;
import com.webobjects.monitor._private.String_Extensions;

import er.extensions.appserver.ERXRedirect;

public class ConfigurePage extends MonitorComponent {
public String backupNote;
public boolean isAdaptorSettingsSectionVisible = false;
@@ -67,6 +71,9 @@ public WOComponent emailUpdateClicked() {

public String customSchedulerName;

public String adaptorInfoUsername;
public String adaptorInfoPassword;

public String loadSchedulerSelection() {
if ((theApplication != null) && (siteConfig().scheduler() != null)) {
int indexOfScheduler = MObject.loadSchedulerArrayValues.indexOfObject(siteConfig().scheduler());
@@ -129,4 +136,13 @@ public static ConfigurePage create(WOContext context) {
return (ConfigurePage) context.page().pageWithName(ConfigurePage.class.getName());
}

public WOActionResults adaptorInfoLoginClicked() {
String url = siteConfig().woAdaptor() + "/WOAdaptorInfo?" + adaptorInfoUsername + "+" + adaptorInfoPassword;
if (url.startsWith("http://"))
url = url.replaceFirst("http://", "https://");
ERXRedirect redirect = pageWithName(ERXRedirect.class);
redirect.setUrl(url);
return redirect;
}

}
@@ -12,6 +12,7 @@
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
*/
import com.webobjects.appserver.WOActionResults;
import com.webobjects.appserver.WOComponent;
import com.webobjects.appserver.WODirectAction;
import com.webobjects.appserver.WORequest;
@@ -32,6 +33,16 @@ public DirectAction(WORequest aRequest) {
super(aRequest);
}

@Override
public WOActionResults defaultAction() {
if (request().stringFormValueForKey("pw") != null ) {
Main loginPage = (Main) pageWithName(Main.class.getName());
loginPage.setPassword(request().stringFormValueForKey("pw"));
return loginPage.loginClicked();
}
return super.defaultAction();
}

public WOComponent MainAction() {
return pageWithName("Main");
}
@@ -47,15 +47,14 @@ public boolean loginRequired() {
}

public WOComponent loginClicked() {
NSLog.out.appendln("Main.loginClicked: " + password());
if (siteConfig().compareStringWithPassword(password())) {
mySession().setIsLoggedIn(true);
} else {
mySession().addErrorIfAbsent("Incorrect Password");
return pageWithName(Main.class);
}

return ApplicationsPage.create(context());
return pageWithName(ApplicationsPage.class.getName());
}

public String message() {
@@ -16,6 +16,7 @@

import com.webobjects.appserver.WOApplication;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WOSession;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.monitor._private.MApplication;
import com.webobjects.monitor._private.MHost;
@@ -76,7 +77,7 @@ protected MSiteConfig siteConfig() {
public Session mySession() {
return (Session) super.session();
}

public WOTaskdHandler handler() {
return _handler;
}
@@ -91,7 +91,6 @@ public String message() {
_message = errorMessageArray.componentsJoinedByString(", ");
errorMessageArray = new _NSThreadsafeMutableArray(new NSMutableArray<Object>());
}
NSLog.out.appendln("Session.message: " + _message);
return _message;
}

@@ -53,11 +53,11 @@ public void run() {
}
}

public void addObjectsFromArrayIfAbsentToErrorMessageArray(NSArray<String> aErrors) {
public synchronized void addObjectsFromArrayIfAbsentToErrorMessageArray(NSArray<String> aErrors) {
_errors.addObjectsFromArray(aErrors);
}

public NSArray<String> errors() {
public synchronized NSArray<String> errors() {
return _errors.allObjects();
}

@@ -9,12 +9,15 @@
import com.webobjects.monitor._private.MInstance;

/**
* Bounces an application gracefully. It does so by staring at least one new instance
* 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.
*
@@ -53,6 +56,13 @@ protected void bounce() throws InterruptedException {
currentInstances.addObject(instance);
}
}

if (inactiveInstancesByHost.isEmpty()) {
addObjectsFromArrayIfAbsentToErrorMessageArray(
new NSArray<String>("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);
@@ -72,19 +82,18 @@ protected void bounce() throws InterruptedException {
NSArray<MInstance> inactiveInstances = inactiveInstancesByHost.objectForKey(host);
if (inactiveInstances != null && inactiveInstances.count() >= i) {
MInstance instance = inactiveInstances.objectAtIndex(i);
log("Adding instance " + instance.displayName() + " on host " + host.addressAsString());
log("Starting inactive instance " + instance.displayName() + " on host " + host.addressAsString());
startingInstances.addObject(instance);
} else {
log("Not enough instances on host: " + host.addressAsString());
log("Not enough inactive instances on host: " + host.addressAsString());
}
}
}
for (MInstance instance : startingInstances) {
if (useScheduling) {
instance.setSchedulingEnabled(Boolean.TRUE);
} else {
instance.setAutoRecover(Boolean.TRUE);
}
instance.setAutoRecover(Boolean.TRUE);
}
handler().sendUpdateInstancesToWotaskds(startingInstances, activeHosts.allObjects());
handler().sendStartInstancesToWotaskds(startingInstances, activeHosts.allObjects());
@@ -117,9 +126,8 @@ protected void bounce() throws InterruptedException {
for (MInstance instance : currentInstances) {
if (useScheduling) {
instance.setSchedulingEnabled(Boolean.FALSE);
} else {
instance.setAutoRecover(Boolean.FALSE);
}
instance.setAutoRecover(Boolean.FALSE);
}
}

@@ -143,9 +151,8 @@ protected void bounce() throws InterruptedException {
MInstance instance = currentInstances.objectAtIndex(i);
if (useScheduling) {
instance.setSchedulingEnabled(Boolean.TRUE);
} else {
instance.setAutoRecover(Boolean.TRUE);
}
instance.setAutoRecover(Boolean.TRUE);
restarting.addObject(instance);
}
}
@@ -0,0 +1,184 @@
package com.webobjects.monitor.application.starter;

import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.monitor._private.MApplication;
import com.webobjects.monitor._private.MHost;
import com.webobjects.monitor._private.MInstance;

/**
* Bounces an application using a rolling shutdown.
*
* 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 forcefully restarting each instance one at a time until they have all
* been restarted.
*
* You must have at least one inactive instance in order to perform this bounce.
*
* @author johnthuss
*/
public class RollingShutdownBouncer extends ApplicationStarter {

public RollingShutdownBouncer(MApplication app) {
super(app);
}

@Override
protected void bounce() throws InterruptedException {

NSArray<MInstance> instances = application().instanceArray().immutableClone();
NSArray<MInstance> runningInstances = application().runningInstances_M();
NSArray<MHost> activeHosts = (NSArray<MHost>) runningInstances.valueForKeyPath("host.@unique");

NSMutableArray<MInstance> inactiveInstances = instances.mutableClone();
inactiveInstances.removeObjectsInArray(runningInstances);

if (inactiveInstances.isEmpty()) {
addObjectsFromArrayIfAbsentToErrorMessageArray(
new NSArray<String>("You must have at least one inactive instance to perform a rolling shutdown bounce."));
return;
}

int numInstancesToStartPerHost = numInstancesToStartPerHost(runningInstances, activeHosts);
NSArray<MInstance> startingInstances = instancesToStart(inactiveInstances, activeHosts, numInstancesToStartPerHost);

boolean useScheduling = doAllRunningInstancesUseScheduling(runningInstances);
log("Starting inactive instances");
startInstances(startingInstances, activeHosts, useScheduling);

waitForInactiveInstancesToStart(startingInstances, activeHosts);


NSMutableArray<MInstance> restartingInstances = runningInstances.mutableClone();
refuseNewSessions(restartingInstances, activeHosts);

NSMutableArray<MInstance> stoppingInstances = new NSMutableArray<MInstance>();
for (int i = numInstancesToStartPerHost; i > 0; i--) {
if (restartingInstances.isEmpty()) {
break;
}
stoppingInstances.addObject(restartingInstances.removeLastObject());
}

restartInstances(restartingInstances, activeHosts, useScheduling);
stopInstances(stoppingInstances, activeHosts);


handler().startReading();
try {
handler().getInstanceStatusForHosts(activeHosts);
log("Finished");
} finally {
handler().endReading();
}
}

protected int numInstancesToStartPerHost(NSArray<MInstance> runningInstances, NSArray<MHost> activeHosts) {
int numToStartPerHost = 1;
if (activeHosts.count() > 0) {
numToStartPerHost = (int) (runningInstances.count() / activeHosts.count() * .1);
}
if (numToStartPerHost < 1) {
numToStartPerHost = 1;
}
return numToStartPerHost;
}

protected NSArray<MInstance> instancesToStart(NSArray<MInstance> inactiveInstances, NSArray<MHost> activeHosts,
int numInstancesToStartPerHost) {
NSMutableArray<MInstance> startingInstances = new NSMutableArray<MInstance>();
for (int i = 0; i < numInstancesToStartPerHost; i++) {
for (MHost host : activeHosts) {
NSArray<MInstance> inactiveInstancesForHost = MInstance.HOST.eq(host).filtered(inactiveInstances);
if (inactiveInstancesForHost != null && inactiveInstancesForHost.count() >= i) {
MInstance instance = inactiveInstancesForHost.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());
}
}
}
return startingInstances.immutableClone();
}

protected boolean doAllRunningInstancesUseScheduling(NSArray<MInstance> runningInstances) {
boolean useScheduling = true;
for (MInstance instance : runningInstances) {
useScheduling &= instance.schedulingEnabled() != null && instance.schedulingEnabled().booleanValue();
}
return useScheduling;
}

protected void startInstances(NSArray<MInstance> startingInstances, NSArray<MHost> activeHosts, boolean useScheduling) {
for (MInstance instance : startingInstances) {
if (useScheduling) {
instance.setSchedulingEnabled(Boolean.TRUE);
}
instance.setAutoRecover(Boolean.TRUE);
}
handler().sendUpdateInstancesToWotaskds(startingInstances, activeHosts);
handler().sendStartInstancesToWotaskds(startingInstances, activeHosts);
}

protected void waitForInactiveInstancesToStart(NSArray<MInstance> startingInstances, NSArray<MHost> activeHosts)
throws InterruptedException {
boolean waiting = true;

// wait until apps have started
while (waiting) {
handler().startReading();
try {
log("Checking to see if inactive instances have started");
handler().getInstanceStatusForHosts(activeHosts);
boolean allStarted = true;
for (MInstance instance : startingInstances) {
allStarted &= instance.isRunning_M();
}
if (allStarted) {
waiting = false;
} else {
sleep(10 * 1000);
}
} finally {
handler().endReading();
}
}
log("Started inactive instances sucessfully");
}

protected void refuseNewSessions(NSArray<MInstance> restartingInstances, NSArray<MHost> activeHosts) {
for (MInstance instance : restartingInstances) {
instance.setRefusingNewSessions(true);
}
handler().sendRefuseSessionToWotaskds(restartingInstances, activeHosts, true);
}

protected void restartInstances(NSArray<MInstance> runningInstances, NSArray<MHost> activeHosts, boolean useScheduling)
throws InterruptedException {
for (MInstance instance : runningInstances) {
NSArray<MInstance> instanceInArray = new NSArray<MInstance>(instance);
handler().sendStopInstancesToWotaskds(instanceInArray, activeHosts);

sleep(10 * 1000);

handler().sendUpdateInstancesToWotaskds(instanceInArray, activeHosts);

startInstances(instanceInArray, activeHosts, useScheduling);
waitForInactiveInstancesToStart(instanceInArray, activeHosts);
log("Restarted instance " + instance.displayName() + " sucessfully");
}
}

protected void stopInstances(NSMutableArray<MInstance> stoppingInstances, NSArray<MHost> activeHosts) {
for (MInstance instance : stoppingInstances) {
instance.setSchedulingEnabled(Boolean.FALSE);
instance.setAutoRecover(Boolean.FALSE);
}
handler().sendUpdateInstancesToWotaskds(stoppingInstances, activeHosts);
handler().sendStopInstancesToWotaskds(stoppingInstances, activeHosts);
log("Stopped instances " + stoppingInstances.toString() + " sucessfully");
}

}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -377,3 +377,21 @@ td.PushButtonWrapper {
.JumpToField {
width: 250px;
}

.AllBounce {
line-height: 42px;
}

.Deaths {
color: maroon;
font-weight: bold;
}

.HostNotAvailable {
color: maroon;
font-weight: bold;
}

.AdaptorLoginHelp {
font-size: small;
}
@@ -1,6 +1,8 @@
# Properties file for wotaskd
# Overrides the JavaWebObjects.framework Properties

#WOHost=localhost

WOAutoOpenInBrowser=false
WODebuggingEnabled=false
WODirectConnectEnabled=true
@@ -27,4 +29,4 @@ log4j.appender.A1=org.apache.log4j.ConsoleAppender

# A1 uses PatternLayout.
log4j.appender.A1.layout=er.extensions.logging.ERXPatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %-5p %V{u/f} [%t] %C{2} (%F:%L) - %m%n
log4j.appender.A1.layout.ConversionPattern=%d %-5p %V{u/f} [%t] %C{2} (%F:%L) - %m%n
@@ -29,11 +29,16 @@
import com.webobjects.foundation.NSTimestamp;
import com.webobjects.foundation.NSTimestampFormatter;

import er.extensions.eof.ERXKey;

public class MInstance extends MObject {
static NSTimestampFormatter dateFormatter = new NSTimestampFormatter("%m/%d/%Y %H:%M:%S %Z");

static NSTimestampFormatter shutdownFormatter = new NSTimestampFormatter("%a @ %H:00");

public static final ERXKey<MHost> HOST = new ERXKey<MHost>("host");
public static final ERXKey<String> HOST_NAME = new ERXKey<String>("hostName");

/*
* String hostName; Integer id; Integer port; String applicationName;
* Boolean autoRecover; Integer minimumActiveSessionsCount; String path;
@@ -153,7 +153,7 @@ public static String validatedSchedulingType(String value) {
public static Integer validatedSchedulingStartTime(Integer value) {
if (value != null) {
int intVal = value.intValue();
if ( (intVal >= 1) && (intVal <= 24) ) {
if ( (intVal >= 0) && (intVal <= 23) ) {
return value;
}
}
@@ -1243,5 +1243,18 @@ public MInstance instanceWithHostAndPort(String name, InetAddress host, String p
return null;
}

public NSArray instancesWithHostName(String host) {
try {
MHost aHost = hostWithName(host);
if (aHost == null) {
return null;
}
return aHost.instanceArray();
} catch (Exception e) {
log.error("Exception getting instances for host: " + host, e);
}
return null;
}

public int _appIsDeadMultiplier;
}