package com.webobjects.monitor.application;
import java.util.Enumeration;
import com.webobjects.appserver.WOActionResults;
import com.webobjects.appserver.WOComponent;
import com.webobjects.appserver.WODirectAction;
import com.webobjects.appserver.WORequest;
import com.webobjects.appserver.WOResponse;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSSet;
import com.webobjects.monitor._private.MApplication;
import com.webobjects.monitor._private.MInstance;
import com.webobjects.monitor._private.MObject;
import com.webobjects.monitor._private.MSiteConfig;
import er.extensions.appserver.ERXHttpStatusCodes;
import er.extensions.appserver.ERXResponse;
/**
*
* The following direct actions were added to Monitor. They might be useful for
* creating scripts to automate deployments of new WO application versions.
* (First time deployments and config changes would still require interactive
* sessions in Monitor.) Each direct action returns a short string (instead of a
* full HTML page) and an HTTP status code indicating whether the respective
* action was executed successfully. If Monitor is password-protected, the
* password must be passed on the URL with the name "pw", (e.g. &pw=foo). If the
* password is missing or incorrect, these direct actions are not permitted to be
* executed.
*
*
* Direct Action |
* Return Values |
* Description |
*
*
* running |
* 'YES', 'NO', or
* error message |
* checks whether instances are running (alive) |
*
*
* stopped |
* checks whether instances have stopped (are dead) |
*
*
* |
*
*
* start |
* 'OK' or
* error message |
* attempts to start instances which have been stopped or are stopping |
*
*
* stop |
* attempts to stops instances which are running or starting |
*
*
* forceQuit |
* stops instances forcefully |
*
*
* |
*
*
* turnAutoRecoverOn |
* 'OK' or
* error message |
* turns Auto Recover on |
*
*
* turnAutoRecoverOff |
* turns Auto Recover off |
*
*
* |
*
*
* turnRefuseNewSessionsOn |
* 'OK' or
* error message |
* turns Refuse New Sessions on |
*
*
* turnRefuseNewSessionsOff |
* turns Refuse New Sessions off |
*
*
* |
*
*
* turnScheduledOn |
* 'OK' or
* error message |
* turns Scheduled on |
*
*
* turnScheduledOff |
* turns Scheduled off |
*
*
* |
*
*
* clearDeaths |
* 'OK' or
* error message |
* sets the number of deaths to 0 |
*
*
* bounce |
* 'OK' or
* error message |
* bounces the application (starts a few instances per hosts, set the rest to refusing sessions and auto-recover) |
*
*
* setAdditionalArgs |
* 'OK' or
* error message |
* updates the instances' additional command line arguments |
*
*
* info |
* JSON or
* error message |
* returns a JSON encoded list of instances with all the data from the app detail page. Add form value info=full to also return the Additional Arguments. |
*
*
*
*
* All direct actions must be invoked with a type:
*
*
* Type |
* Description |
* Requires Names |
*
*
* all |
* all instances of all applications |
* no |
*
*
* app |
* all instances of the specified applications |
* yes |
*
*
* ins |
* all the specified instances |
*
*
*
*
* The direct action 'running' can be invoked with a num argument:
*
*
* Num |
* Description |
*
*
* all / -1 |
* all instances of the application must be running. this is the default if no num argument is set |
*
*
* number |
* a minimum of number instances of the specified application must be running. if there are less instances configured acts like 'all' |
*
*
*
*
* The direct action 'bounce' can be invoked with additional arguments:
*
*
* Argument |
* Value |
* Description |
*
*
* bouncetype |
* graceful | shutdown | rolling |
* graceful bounces the application by starting a few instances per host and setting the rest to refusing sessions
* shutdown bounces the application by stopping all instances and then restarting them (use this if your
* application will migrate the database so the old application will crash)
* rolling will start a few instances per host, then forcefully restart the existing instances one at a time
* The default bouncetype is graceful. |
*
*
* maxwait |
* secs |
* number of seconds to wait for applications to shut down themselves before force quitting the instances.
* The default is 30 seconds. |
*
*
*
*
* The direct action setAdditionalArgs
must be invoked with the following argument:
*
*
* Argument |
* Value |
* Description |
*
*
* args |
* string |
* the additional arguments to be passed to the instance on startup |
*
*
*
*
* Possible status codes:
*
*
* Code |
* Circumstance |
*
*
* 200 (OK) |
* return value is 'OK' or 'YES' |
*
*
* 403 (Unauthorized) |
* Monitor is password protected |
*
*
* 404 (Not Found) |
* one or more of the supplied application or instance names can't be found |
*
*
* 406 (Not Acceptable) |
* an unknown type is supplied, or names are required but missing |
*
*
* 417 (Not Expected) |
* return value is 'NO' |
*
*
* 500 (Error) |
* software defect (please send
* stacktrace from Monitor's log) |
*
*
*
*
* Examples:
*
*
* URL |
* Description |
*
*
*
* .../JavaMonitor.woa/admin/start?type=app&name=AppleStore&name=MemberSite
* |
* Starts all instances of the AppleStore and the MemberSite applications.
* Returns error if any of these applications are unknown to Monitor, OK
* otherwise. |
*
*
* .../JavaMonitor.woa/admin/turnScheduledOff?type=all |
* Turns scheduling off for all instances of all applications, then returns
* OK. |
*
*
*
* .../JavaMonitor.woa/admin/stopped?type=ins&name=AppleStore-4&name=
* MemberSite-8&name=AppleStore-2 |
* Returns YES if the instances 2 and 4 of the AppleStore and instance 8 of
* the MemberSite are all dead. Returns NO if at least one of them has not
* stopped. Returns error if any of these instances are unknown to Monitor. |
*
*
*
*
* A simple deployment script could look as follows:
*
*
* #!/bin/sh
# clean build
ant clean install
# run unit tests
ant test
# stop application
result=`curl -s
http://bigserver:1086/cgi-bin/WebObjects/JavaMonitor.woa/admin/stop\?type=app\&name=MemberSite`
[ "$result" = OK ] || { echo $result; exit 1; }
# deploy new application
scp -rq /Library/WebObjects/Applications/MemberSite.woa
bigserver:/Library/WebObjects/Applications/
# start application
result=`curl -s
http://bigserver:1086/cgi-bin/WebObjects/JavaMonitor.woa/admin/start\?type=app\&name=MemberSite`
[ "$result" = OK ] || { echo $result; exit 1; }
echo "deployment completed"
* |
*
*
*
*
* Invoking direct actions manually:
*
*
* curl -w " (status: %{http_code})\n"
http://bigserver:1086/cgi-bin/WebObjects/JavaMonitor.woa/admin/forceQuit\?type=ins\&name=AppleStore-3
* |
*
*
*
* @author christian@pekeler.org
* @author ak
*
*/
public class AdminAction extends WODirectAction {
public class DirectActionException extends RuntimeException {
public int status;
public DirectActionException(String s, int i) {
super(s);
status = i;
}
}
protected static NSArray supportedActionNames = new NSArray(new String[] { "running", "bounce", "stopped", "start", "stop", "forceQuit", "turnAutoRecoverOn", "turnAutoRecoverOff",
"turnRefuseNewSessionsOn", "turnRefuseNewSessionsOff", "turnScheduledOn", "turnScheduledOff", "turnAutoRecoverOn", "turnAutoRecoverOff", "clearDeaths", "info" });
protected AdminApplicationsPage applicationsPage;
protected NSMutableArray instances;
protected NSMutableArray applications;
private WOTaskdHandler _handler;
public AdminAction(WORequest worequest) {
super(worequest);
instances = new NSMutableArray();
applications = new NSMutableArray();
_handler = new WOTaskdHandler(mySession());
}
public WOComponent MainAction() {
return pageWithName("Main");
}
protected AdminApplicationsPage applicationsPage() {
if (applicationsPage == null)
applicationsPage = new AdminApplicationsPage(context());
return applicationsPage;
}
public WOActionResults infoAction() {
ERXResponse woresponse = new ERXResponse();
String result = "";
for (Enumeration enumeration = instances.objectEnumerator(); enumeration.hasMoreElements();) {
MInstance minstance = (MInstance) enumeration.nextElement();
result += (result.length() == 0 ? "" : ", \n");
result += "{";
result += "\"name\": \"" + minstance.applicationName() + "\", ";
result += "\"id\": \"" + minstance.id() + "\", ";
result += "\"host\": \"" + minstance.hostName() + "\", ";
result += "\"port\": \"" + minstance.port() + "\", ";
result += "\"state\": \"" + MObject.stateArray[minstance.state] + "\", ";
result += "\"deaths\": \"" + minstance.deathCount() + "\", ";
result += "\"refusingNewSessions\": " + minstance.isRefusingNewSessions() + ", ";
result += "\"scheduled\": " + minstance.isScheduled() + ", ";
result += "\"schedulingHourlyStartTime\": " + minstance.schedulingHourlyStartTime() + ", ";
result += "\"schedulingDailyStartTime\": " + minstance.schedulingDailyStartTime() + ", ";
result += "\"schedulingWeeklyStartTime\": " + minstance.schedulingWeeklyStartTime() + ", ";
result += "\"schedulingType\": \"" + minstance.schedulingType() + "\", ";
result += "\"schedulingStartDay\": " + minstance.schedulingStartDay() + ", ";
result += "\"schedulingInterval\": " + minstance.schedulingInterval() + ", ";
result += "\"transactions\": \"" + minstance.transactions() + "\", ";
result += "\"activeSessions\": \"" + minstance.activeSessions() + "\", ";
result += "\"averageIdlePeriod\": \"" + minstance.averageIdlePeriod() + "\", ";
result += "\"avgTransactionTime\": \"" + minstance.avgTransactionTime() + "\",";
result += "\"autoRecover\": \"" + minstance.isAutoRecovering() + "\"";
String infoMode = (String) context().request().formValueForKey("info");
if ("full".equalsIgnoreCase(infoMode)) {
result += ", \"additionalArgs\": \"";
if (minstance.additionalArgs() != null) {
result += minstance.additionalArgs().replace("\"", "\\\"");
}
result += "\"";
}
result += "}";
}
woresponse.appendContentString("[" + result + "]");
return woresponse;
}
public WOActionResults runningAction() {
ERXResponse woresponse = new ERXResponse("YES");
String num = (String) context().request().formValueForKey("num");
int numberOfInstancesRequested = -1;
if (num != null && !num.equals("") && !num.equalsIgnoreCase("all")) {
try {
numberOfInstancesRequested = Integer.valueOf(num).intValue();
if (numberOfInstancesRequested > instances.count()) {
numberOfInstancesRequested = -1;
}
} catch (Exception e) {
// ignore
}
}
int instancesAlive = 0;
for (Enumeration enumeration = instances.objectEnumerator(); enumeration.hasMoreElements();) {
MInstance minstance = (MInstance) enumeration.nextElement();
if (minstance.state == MObject.ALIVE) {
instancesAlive++;
}
}
if ((numberOfInstancesRequested == -1 && instancesAlive < instances.count()) || instancesAlive < numberOfInstancesRequested) {
woresponse.setContent("NO");
woresponse.setStatus(ERXHttpStatusCodes.EXPECTATION_FAILED);
}
return woresponse;
}
public WOActionResults stoppedAction() {
ERXResponse woresponse = new ERXResponse("YES");
for (Enumeration enumeration = instances.objectEnumerator(); enumeration.hasMoreElements();) {
MInstance minstance = (MInstance) enumeration.nextElement();
if (minstance.state == MObject.DEAD)
continue;
woresponse.setContent("NO");
woresponse.setStatus(ERXHttpStatusCodes.EXPECTATION_FAILED);
break;
}
return woresponse;
}
public WOActionResults bounceAction() {
ERXResponse woresponse = new ERXResponse("OK");
String bouncetype = (String) context().request().formValueForKey("bouncetype");
String maxwaitString = (String) context().request().formValueForKey("maxwait");
if (bouncetype == null || bouncetype == "" || bouncetype.equalsIgnoreCase("graceful")) {
applicationsPage().bounceGraceful(applications);
} else if (bouncetype.equalsIgnoreCase("shutdown")) {
int maxwait = 30;
if (maxwaitString != null) {
try {
maxwait = Integer.valueOf(maxwaitString).intValue();
} catch (NumberFormatException e) {
// ignore
}
}
applicationsPage().bounceShutdown(applications, maxwait);
} else if (bouncetype.equalsIgnoreCase("rolling")) {
applicationsPage().bounceRolling(applications);
} else {
woresponse.setContent("Unknown bouncetype");
woresponse.setStatus(ERXHttpStatusCodes.NOT_ACCEPTABLE);
}
return woresponse;
}
public void clearDeathsAction() {
applicationsPage().clearDeaths(instances);
}
public void scheduleTypeAction() {
String scheduleType = (String) context().request().formValueForKey("scheduleType");
if (scheduleType != null && ("HOURLY".equals(scheduleType) || "DAILY".equals(scheduleType) || "WEEKLY".equals(scheduleType)))
applicationsPage().scheduleType(instances, scheduleType);
}
public void hourlyScheduleRangeAction() {
String beginScheduleWindow = (String) context().request().formValueForKey("hourBegin");
String endScheduleWindow = (String) context().request().formValueForKey("hourEnd");
String interval = (String) context().request().formValueForKey("interval");
if (beginScheduleWindow != null && endScheduleWindow != null && interval != null)
applicationsPage().hourlyStartHours(instances, Integer.parseInt(beginScheduleWindow), Integer.parseInt(endScheduleWindow), Integer.parseInt(interval));
}
public void dailyScheduleRangeAction() {
String beginScheduleWindow = (String) context().request().formValueForKey("hourBegin");
String endScheduleWindow = (String) context().request().formValueForKey("hourEnd");
if (beginScheduleWindow != null && endScheduleWindow != null)
applicationsPage().dailyStartHours(instances, Integer.parseInt(beginScheduleWindow), Integer.parseInt(endScheduleWindow));
}
public void weeklyScheduleRangeAction() {
String beginScheduleWindow = (String) context().request().formValueForKey("hourBegin");
String endScheduleWindow = (String) context().request().formValueForKey("hourEnd");
String weekDay = (String) context().request().formValueForKey("weekDay");
if (beginScheduleWindow != null && endScheduleWindow != null && weekDay != null)
applicationsPage().weeklyStartHours(instances, Integer.parseInt(beginScheduleWindow), Integer.parseInt(endScheduleWindow), Integer.parseInt(weekDay));
}
public void setAdditionalArgsAction() {
String arguments = (String) context().request().formValueForKey("args");
if (arguments != null)
applicationsPage().setAdditionalArgs(instances, arguments);
}
public void turnScheduledOnAction() {
applicationsPage().turnScheduledOn(instances);
}
public void turnScheduledOffAction() {
applicationsPage().turnScheduledOff(instances);
}
public void turnRefuseNewSessionsOnAction() {
applicationsPage().turnRefuseNewSessionsOn(instances);
}
public void turnRefuseNewSessionsOffAction() {
applicationsPage().turnRefuseNewSessionsOff(instances);
}
public void turnAutoRecoverOnAction() {
applicationsPage().turnAutoRecoverOn(instances);
}
public void turnAutoRecoverOffAction() {
applicationsPage().turnAutoRecoverOff(instances);
}
public void forceQuitAction() {
applicationsPage().forceQuit(instances);
}
public void stopAction() {
applicationsPage().stop(instances);
}
public void startAction() {
applicationsPage().start(instances);
}
protected void prepareApplications(NSArray appNames) {
if (appNames == null)
throw new DirectActionException("at least one application name needs to be specified for type app", 406);
for (Enumeration enumeration = appNames.objectEnumerator(); enumeration.hasMoreElements();) {
String s = (String) enumeration.nextElement();
MApplication mapplication = siteConfig().applicationWithName(s);
if (mapplication != null) {
applications.addObject(mapplication);
addInstancesForApplication(mapplication);
}
else
throw new DirectActionException("Unknown application " + s, 404);
}
}
protected void prepareApplicationsOnHosts(NSArray appNames, NSArray hostNames) {
if (appNames == null)
throw new DirectActionException("at least one application name needs to be specified for type app", 406);
for (Enumeration enumeration = appNames.objectEnumerator(); enumeration.hasMoreElements();) {
String s = (String) enumeration.nextElement();
MApplication mapplication = siteConfig().applicationWithName(s);
if (mapplication != null) {
NSArray hostInstances = MInstance.HOST_NAME.in(hostNames).filtered(mapplication.instanceArray());
instances.addObjectsFromArray(hostInstances);
}
else
throw new DirectActionException("Unknown application " + s, 404);
}
}
protected void prepareInstances(NSArray appNamesAndNumbers) {
if (appNamesAndNumbers == null)
throw new DirectActionException("at least one instance name needs to be specified for type ins", 406);
for (Enumeration enumeration = appNamesAndNumbers.objectEnumerator(); enumeration.hasMoreElements();) {
String s = (String) enumeration.nextElement();
MInstance minstance = siteConfig().instanceWithName(s);
if (minstance != null)
instances.addObject(minstance);
else
throw new DirectActionException("Unknown instance " + s, 404);
}
}
protected void addInstancesForApplication(MApplication mapplication) {
instances.addObjectsFromArray(mapplication.instanceArray());
}
protected void refreshInformation() {
for (Enumeration enumeration = (new NSSet((NSArray) instances.valueForKey("application"))).objectEnumerator(); enumeration.hasMoreElements();) {
MApplication mapplication = (MApplication) enumeration.nextElement();
@SuppressWarnings("unused")
AppDetailPage dummy = AppDetailPage.create(context(), mapplication);
}
}
public WOActionResults performMonitorActionNamed(String s) {
String s1 = (String) context().request().formValueForKey("type");
if ("all".equalsIgnoreCase(s1)) {
prepareApplications((NSArray) siteConfig().applicationArray().valueForKey("name"));
} else {
NSArray appNames = context().request().formValuesForKey("name");
NSArray hosts = context().request().formValuesForKey("host");
if ("app".equalsIgnoreCase(s1)) {
if (hosts == null || hosts.isEmpty()) {
prepareApplications(appNames);
} else {
prepareApplicationsOnHosts(appNames, hosts);
}
} else if ("ins".equalsIgnoreCase(s1))
prepareInstances(appNames);
else
throw new DirectActionException("Invalid type " + s1, 406);
}
refreshInformation();
_handler.startReading();
try {
WOActionResults woactionresults = super.performActionNamed(s);
return woactionresults;
} finally {
_handler.endReading();
}
}
private MSiteConfig siteConfig() {
return WOTaskdHandler.siteConfig();
}
@Override
public WOActionResults performActionNamed(String s) {
WOResponse woresponse = new ERXResponse();
if (!siteConfig().isPasswordRequired() || siteConfig().compareStringWithPassword(context().request().stringFormValueForKey("pw"))) {
try {
WOActionResults woactionresults = performMonitorActionNamed(s);
if (woactionresults != null && (woactionresults instanceof WOResponse)) {
woresponse = (WOResponse) woactionresults;
} else {
woresponse.setContent("OK");
}
} catch (DirectActionException directactionexception) {
woresponse.setStatus(directactionexception.status);
woresponse.setContent(s + " action failed: " + directactionexception.getMessage());
} catch (Exception throwable) {
woresponse.setStatus(ERXHttpStatusCodes.INTERNAL_ERROR);
woresponse.setContent(s + " action failed: " + throwable.getMessage() + ". See Monitor's log for a stack trace.");
throwable.printStackTrace();
}
} else {
woresponse.setStatus(ERXHttpStatusCodes.FORBIDDEN);
woresponse.setContent("Monitor is password protected - password missing or incorrect.");
}
return woresponse;
}
public Session mySession() {
return (Session) session();
}
}