From ed0a6f5386cd9c489732f399ff164a8e6fdfc73b Mon Sep 17 00:00:00 2001 From: John Huss Date: Thu, 10 May 2012 17:03:52 -0500 Subject: [PATCH] Many JavaMonitor bug fixes and improvements Fix scheduling bug causing "midnight" restarts to never happen. Fix "Bounce" to guarantee that the instances it shuts down are configured to restart. Keep showing app detail stats after refreshing the page Add "Rolling Shutdown" bounce type (see comments in code for details) Add ability to target a specific host for direct actions (start, stop, etc). Add ability to skip Monitor login page if password is contained in the URL (?pw=). Warn if entering an ill-advised statistics page password, i.e. not url-friendly. Add commented-out WOHost property to wotaskd and JavaMonitor Properties for easier tweaking Add login form for easily viewing the WOAdaptorInfo page UI Improvements: - Add title to login page: JavaMonitor for WebObjects - Rename "Refresh Now" link to just "Refresh" - Display better error message if bounce cannot be performed - Add information on Help page about "Bounce" - Add link to "Bounce" help information in context - Change "Deaths" links to plain text with a "Details" link next to it - Change "YES" link for available hosts to plain text with a "View Config" link next to it --- .../AppConfigurePage.wo/AppConfigurePage.html | 15 ++ .../AppConfigurePage.wo/AppConfigurePage.wod | 2 + .../AppDetailPage.wo/AppDetailPage.html | 11 +- .../AppDetailPage.wo/AppDetailPage.wod | 8 + .../ConfigurePage.wo/ConfigurePage.html | 24 +++ .../ConfigurePage.wo/ConfigurePage.wod | 17 ++ .../Components/HelpPage.wo/HelpPage.html | 44 ++++- .../Components/HostsPage.wo/HostsPage.html | 2 +- .../JavaMonitor/Components/Main.wo/Main.html | 2 +- Applications/JavaMonitor/Resources/Properties | 2 + .../monitor/application/AdminAction.java | 49 +++-- .../application/AdminApplicationsPage.java | 8 + .../monitor/application/AppDetailPage.java | 14 +- .../monitor/application/ConfigurePage.java | 16 ++ .../monitor/application/DirectAction.java | 11 ++ .../webobjects/monitor/application/Main.java | 3 +- .../monitor/application/MonitorComponent.java | 3 +- .../monitor/application/Session.java | 1 - .../starter/ApplicationStarter.java | 4 +- .../application/starter/GracefulBouncer.java | 25 ++- .../starter/RollingShutdownBouncer.java | 184 ++++++++++++++++++ .../JavaMonitor/WebServerResources/help.png | Bin 0 -> 2180 bytes .../WebServerResources/javamonitor.css | 18 ++ Applications/wotaskd/Resources/Properties | 4 +- .../monitor/_private/MInstance.java | 5 + .../webobjects/monitor/_private/MObject.java | 2 +- .../monitor/_private/MSiteConfig.java | 13 ++ 27 files changed, 442 insertions(+), 45 deletions(-) create mode 100644 Applications/JavaMonitor/Sources/com/webobjects/monitor/application/starter/RollingShutdownBouncer.java create mode 100644 Applications/JavaMonitor/WebServerResources/help.png diff --git a/Applications/JavaMonitor/Components/AppConfigurePage.wo/AppConfigurePage.html b/Applications/JavaMonitor/Components/AppConfigurePage.wo/AppConfigurePage.html index 644f41d0adb..1e62c124f87 100644 --- a/Applications/JavaMonitor/Components/AppConfigurePage.wo/AppConfigurePage.html +++ b/Applications/JavaMonitor/Components/AppConfigurePage.wo/AppConfigurePage.html @@ -218,4 +218,19 @@

Configuring Application ""

+ \ No newline at end of file diff --git a/Applications/JavaMonitor/Components/AppConfigurePage.wo/AppConfigurePage.wod b/Applications/JavaMonitor/Components/AppConfigurePage.wo/AppConfigurePage.wod index 4117909a92b..6ac559de512 100644 --- a/Applications/JavaMonitor/Components/AppConfigurePage.wo/AppConfigurePage.wod +++ b/Applications/JavaMonitor/Components/AppConfigurePage.wo/AppConfigurePage.wod @@ -31,6 +31,7 @@ InstDefaultsForm: WOForm { AppSettingsForm: WOForm { multipleSubmit = true; + id = "appSettingsForm"; } AppName: WOString { @@ -344,6 +345,7 @@ SmtpHostConfigured: WOConditional { StatisticsPasswordField: WOTextField { size = "20"; value = appDefaults.statisticsPassword; + id = "statsPassword"; } String1: WOString { diff --git a/Applications/JavaMonitor/Components/AppDetailPage.wo/AppDetailPage.html b/Applications/JavaMonitor/Components/AppDetailPage.wo/AppDetailPage.html index 4874db12a23..34182f91753 100644 --- a/Applications/JavaMonitor/Components/AppDetailPage.wo/AppDetailPage.html +++ b/Applications/JavaMonitor/Components/AppDetailPage.wo/AppDetailPage.html @@ -23,10 +23,10 @@

This page automatically updates every seconds.

Bouncer:
-

Refresh Now | +

Refresh | - Hide Details - Show Details + Hide Stats + Show Stats

@@ -132,8 +132,9 @@

+ - + (Details) - @@ -222,7 +223,7 @@

- Bounce + Bounce  Clear Deaths diff --git a/Applications/JavaMonitor/Components/AppDetailPage.wo/AppDetailPage.wod b/Applications/JavaMonitor/Components/AppDetailPage.wo/AppDetailPage.wod index fb9ab5eb76e..280c6c9bba8 100644 --- a/Applications/JavaMonitor/Components/AppDetailPage.wo/AppDetailPage.wod +++ b/Applications/JavaMonitor/Components/AppDetailPage.wo/AppDetailPage.wod @@ -359,6 +359,14 @@ HasBouncer: WOConditional { Bouncer : WOString { value = currentBouncer.status; } +BounceHelpLink : WOHyperlink { + pageName = "HelpPage"; + fragmentIdentifier = "18"; +} +HelpImage : WOImage { + filename = "help.png"; + style = "vertical-align: middle;"; +} Observer : AjaxObserveField { action = selectOne; diff --git a/Applications/JavaMonitor/Components/ConfigurePage.wo/ConfigurePage.html b/Applications/JavaMonitor/Components/ConfigurePage.wo/ConfigurePage.html index f0f5cd1e938..c220988d423 100644 --- a/Applications/JavaMonitor/Components/ConfigurePage.wo/ConfigurePage.html +++ b/Applications/JavaMonitor/Components/ConfigurePage.wo/ConfigurePage.html @@ -7,6 +7,30 @@

HTTP Adaptor URL

URL to Adaptor:

+ +
+

View site statistics and WOAdaptor information

+

The username and password are set in the WOAdaptor configuration file, typically +"/System/Library/WebObjects/Adaptors/Apache2.2/apache.conf" +using the directives WebObjectsAdminUsername and WebObjectsAdminPassword.

+ + + + + + + + + + + + + + +
Username:
Password:
+
+
+
diff --git a/Applications/JavaMonitor/Components/ConfigurePage.wo/ConfigurePage.wod b/Applications/JavaMonitor/Components/ConfigurePage.wo/ConfigurePage.wod index 1f3ab2719aa..ea502614364 100644 --- a/Applications/JavaMonitor/Components/ConfigurePage.wo/ConfigurePage.wod +++ b/Applications/JavaMonitor/Components/ConfigurePage.wo/ConfigurePage.wod @@ -142,3 +142,20 @@ BackupButton: WOSubmitButton { action = backupConfiguration; value = "Backup Now"; } + +AdaptorInfoForm: WOForm { + target = "_blank"; +} + +AdaptorInfoUsername: WOTextField { + value = adaptorInfoUsername; +} + +AdaptorInfoPassword: WOTextField { + value = adaptorInfoPassword; +} + +AdaptorInfoSubmit: WOSubmitButton { + action = adaptorInfoLoginClicked; + value = "Log In"; +} diff --git a/Applications/JavaMonitor/Components/HelpPage.wo/HelpPage.html b/Applications/JavaMonitor/Components/HelpPage.wo/HelpPage.html index e95201ac13a..d9cdfe04482 100644 --- a/Applications/JavaMonitor/Components/HelpPage.wo/HelpPage.html +++ b/Applications/JavaMonitor/Components/HelpPage.wo/HelpPage.html @@ -19,9 +19,10 @@

Help

  • What instance defaults and settings are available through Monitor?
  • What application settings are available through Monitor?
  • What is Instance Scheduling?
  • -
  • Monitor's Hosts page
  • -
  • Monitor's Detail View page
  • -
  • Deployment FAQ
  • +
  • What does "Bounce" do to an application?
  • +
  • Monitor's Hosts page
  • +
  • Monitor's Detail View page
  • +
  • Deployment FAQ
  •  

    @@ -100,11 +101,12 @@

    Help

    instance of that application chosen by the adaptor. Load-balancing takes place between instances with the same name (cf WOApplicationName). The default load-balancing - scheme is Random, where an instance is chosen arbitrarily, + scheme is Round Robin, but other load-balancing schemes are available. Round Robin distributes a request to instances one after another. Load Average attempts to balance load to the instances by evenly - distributing the sessions between instances.

    + distributing the sessions between instances. + If using the Random load-balancing scheme, an instance is chosen arbitrarily.


    @@ -819,7 +821,33 @@

    Help


    -

    Monitor's Hosts page (top)
    +

    What does "Bounce" do to an application? (top)
    +

    + +
    +

    "Bounce" restarts 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. +

    +
    + +


    +
    +

    + +
    + +


    +
    +

    + +

    Monitor's Hosts page (top)

    @@ -850,7 +878,7 @@

    Help


    -

    Monitor's Detail View page (top)
    +

    Monitor's Detail View page (top)

    @@ -964,7 +992,7 @@

    Help


    -

    Deployment FAQ (top)
    +

    Deployment FAQ (top)

    diff --git a/Applications/JavaMonitor/Components/HostsPage.wo/HostsPage.html b/Applications/JavaMonitor/Components/HostsPage.wo/HostsPage.html index 0d4df767858..cb56642946c 100644 --- a/Applications/JavaMonitor/Components/HostsPage.wo/HostsPage.html +++ b/Applications/JavaMonitor/Components/HostsPage.wo/HostsPage.html @@ -20,7 +20,7 @@

    Hosts

    - YES + YES | View Config NO diff --git a/Applications/JavaMonitor/Components/Main.wo/Main.html b/Applications/JavaMonitor/Components/Main.wo/Main.html index 598fb191278..dff642885bc 100644 --- a/Applications/JavaMonitor/Components/Main.wo/Main.html +++ b/Applications/JavaMonitor/Components/Main.wo/Main.html @@ -9,7 +9,7 @@ - +

    JavaMonitor for WebObjects

    diff --git a/Applications/JavaMonitor/Resources/Properties b/Applications/JavaMonitor/Resources/Properties index 8d71ee2907e..ae1bc9fa629 100644 --- a/Applications/JavaMonitor/Resources/Properties +++ b/Applications/JavaMonitor/Resources/Properties @@ -1,6 +1,8 @@ # Properties file for JavaMonitor # Overrides the JavaWebObjects.framework Properties +#WOHost=localhost + WOAutoOpenInBrowser=true WODebuggingEnabled=false diff --git a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/AdminAction.java b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/AdminAction.java index 9471f3ebebb..ee90d5df495 100644 --- a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/AdminAction.java +++ b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/AdminAction.java @@ -172,10 +172,11 @@ * * * bouncetype - * graceful | shutdown + * 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. * * @@ -433,6 +434,8 @@ public WOActionResults bounceAction() { } } applicationsPage().bounceShutdown(applications, maxwait); + } else if (bouncetype.equalsIgnoreCase("rolling")) { + applicationsPage().bounceRolling(applications); } else { woresponse.setContent("Unknown bouncetype"); woresponse.setStatus(406); @@ -480,10 +483,10 @@ public void startAction() { applicationsPage().start(instances); } - protected void prepareApplications(NSArray nsarray) { - if (nsarray == null) + 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 = nsarray.objectEnumerator(); enumeration.hasMoreElements();) { + for (Enumeration enumeration = appNames.objectEnumerator(); enumeration.hasMoreElements();) { String s = (String) enumeration.nextElement(); MApplication mapplication = siteConfig().applicationWithName(s); if (mapplication != null) { @@ -495,11 +498,27 @@ protected void prepareApplications(NSArray nsarray) { } } + + 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 nsarray) { - if (nsarray == null) + } + + 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 = nsarray.objectEnumerator(); enumeration.hasMoreElements();) { + for (Enumeration enumeration = appNamesAndNumbers.objectEnumerator(); enumeration.hasMoreElements();) { String s = (String) enumeration.nextElement(); MInstance minstance = siteConfig().instanceWithName(s); if (minstance != null) @@ -528,11 +547,17 @@ public WOActionResults performMonitorActionNamed(String s) { if ("all".equalsIgnoreCase(s1)) { prepareApplications((NSArray) siteConfig().applicationArray().valueForKey("name")); } else { - NSArray nsarray = context().request().formValuesForKey("name"); - if ("app".equalsIgnoreCase(s1)) - prepareApplications(nsarray); - else if ("ins".equalsIgnoreCase(s1)) - prepareInstances(nsarray); + 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); } diff --git a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/AdminApplicationsPage.java b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/AdminApplicationsPage.java index 7866745ac1e..8f02aef9e25 100644 --- a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/AdminApplicationsPage.java +++ b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/AdminApplicationsPage.java @@ -217,6 +217,13 @@ public void bounceShutdown(NSArray applications, int maxwait) { } } + public void bounceRolling(NSArray applications) { + for (MApplication application : applications) { + AppDetailPage page = AppDetailPage.create(context(), application); + page = (AppDetailPage) page.bounceClickedWithRollingBouncer(); + } + } + public WOComponent bounceClicked() { AppDetailPage page = AppDetailPage.create(context(), currentApplication); page = (AppDetailPage) page.bounceClicked(); @@ -326,4 +333,5 @@ public WOComponent addApplicationClicked() { public static WOComponent create(WOContext context) { return WOApplication.application().pageWithName(AdminApplicationsPage.class.getName(), context); } + } \ No newline at end of file diff --git a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/AppDetailPage.java b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/AppDetailPage.java index b7ae2bb84b8..5a30b4f6a4c 100644 --- a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/AppDetailPage.java +++ b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/AppDetailPage.java @@ -31,6 +31,7 @@ import com.webobjects.monitor._private.StatsUtilities; import com.webobjects.monitor.application.starter.ApplicationStarter; import com.webobjects.monitor.application.starter.GracefulBouncer; +import com.webobjects.monitor.application.starter.RollingShutdownBouncer; import com.webobjects.monitor.application.starter.ShutdownBouncer; public class AppDetailPage extends MonitorComponent { @@ -82,6 +83,10 @@ public WOComponent bounceClickedWithShutdownBouncer(int maxwait) { return bounceClickedWithBouncer(new ShutdownBouncer(myApplication(), maxwait)); } + public WOComponent bounceClickedWithRollingBouncer() { + return bounceClickedWithBouncer(new RollingShutdownBouncer(myApplication())); + } + private WOComponent bounceClickedWithBouncer(ApplicationStarter bouncer) { ApplicationStarter old = currentBouncer(); if (old != null) { @@ -554,6 +559,13 @@ public WOComponent schedulingEnableAllClicked() { private WOComponent newDetailPage() { AppDetailPage nextPage = AppDetailPage.create(context(), myApplication()); nextPage.displayGroup.setSelectedObjects(displayGroup.selectedObjects()); + nextPage.showDetailStatistics = this.showDetailStatistics; + if (currentBouncer() != null && + !"Finished".equals(currentBouncer().status()) && + !currentBouncer().errors().isEmpty()) { + mySession().addObjectsFromArrayIfAbsentToErrorMessageArray(currentBouncer().errors()); + session().removeObjectForKey(bouncerName()); + } return nextPage; } @@ -743,7 +755,7 @@ public boolean hasHosts() { } } - public static AppDetailPage create(WOContext context, MApplication currentApplication, NSArray selected) { + public static AppDetailPage create(WOContext context, MApplication currentApplication, NSArray selected) { AppDetailPage page = (AppDetailPage) WOApplication.application().pageWithName(AppDetailPage.class.getName(), context); page.setMyApplication(currentApplication); NSArray instancesArray = currentApplication.instanceArray(); diff --git a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/ConfigurePage.java b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/ConfigurePage.java index 2d903718b50..dfac83abda4 100644 --- a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/ConfigurePage.java +++ b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/ConfigurePage.java @@ -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; + } + } diff --git a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/DirectAction.java b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/DirectAction.java index 3351548d2f8..13680b7a51b 100644 --- a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/DirectAction.java +++ b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/DirectAction.java @@ -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"); } diff --git a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/Main.java b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/Main.java index 1bcd621b636..226f209f3f0 100644 --- a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/Main.java +++ b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/Main.java @@ -47,7 +47,6 @@ public boolean loginRequired() { } public WOComponent loginClicked() { - NSLog.out.appendln("Main.loginClicked: " + password()); if (siteConfig().compareStringWithPassword(password())) { mySession().setIsLoggedIn(true); } else { @@ -55,7 +54,7 @@ public WOComponent loginClicked() { return pageWithName(Main.class); } - return ApplicationsPage.create(context()); + return pageWithName(ApplicationsPage.class.getName()); } public String message() { diff --git a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/MonitorComponent.java b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/MonitorComponent.java index 12dc9c1e3d5..e18e9a2538e 100644 --- a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/MonitorComponent.java +++ b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/MonitorComponent.java @@ -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; } diff --git a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/Session.java b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/Session.java index c2bb4dd70cc..032605f35a4 100644 --- a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/Session.java +++ b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/Session.java @@ -91,7 +91,6 @@ public String message() { _message = errorMessageArray.componentsJoinedByString(", "); errorMessageArray = new _NSThreadsafeMutableArray(new NSMutableArray()); } - NSLog.out.appendln("Session.message: " + _message); return _message; } diff --git a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/starter/ApplicationStarter.java b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/starter/ApplicationStarter.java index 7aa3fb32d35..a3fbbe3cbfb 100644 --- a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/starter/ApplicationStarter.java +++ b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/starter/ApplicationStarter.java @@ -53,11 +53,11 @@ public void run() { } } - public void addObjectsFromArrayIfAbsentToErrorMessageArray(NSArray aErrors) { + public synchronized void addObjectsFromArrayIfAbsentToErrorMessageArray(NSArray aErrors) { _errors.addObjectsFromArray(aErrors); } - public NSArray errors() { + public synchronized NSArray errors() { return _errors.allObjects(); } diff --git a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/starter/GracefulBouncer.java b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/starter/GracefulBouncer.java index aaa207bb98c..86106fc30b9 100644 --- a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/starter/GracefulBouncer.java +++ b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/starter/GracefulBouncer.java @@ -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("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 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); } } diff --git a/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/starter/RollingShutdownBouncer.java b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/starter/RollingShutdownBouncer.java new file mode 100644 index 00000000000..34a27fd97cd --- /dev/null +++ b/Applications/JavaMonitor/Sources/com/webobjects/monitor/application/starter/RollingShutdownBouncer.java @@ -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 instances = application().instanceArray().immutableClone(); + NSArray runningInstances = application().runningInstances_M(); + NSArray activeHosts = (NSArray) runningInstances.valueForKeyPath("host.@unique"); + + NSMutableArray inactiveInstances = instances.mutableClone(); + inactiveInstances.removeObjectsInArray(runningInstances); + + if (inactiveInstances.isEmpty()) { + addObjectsFromArrayIfAbsentToErrorMessageArray( + new NSArray("You must have at least one inactive instance to perform a rolling shutdown bounce.")); + return; + } + + int numInstancesToStartPerHost = numInstancesToStartPerHost(runningInstances, activeHosts); + NSArray startingInstances = instancesToStart(inactiveInstances, activeHosts, numInstancesToStartPerHost); + + boolean useScheduling = doAllRunningInstancesUseScheduling(runningInstances); + log("Starting inactive instances"); + startInstances(startingInstances, activeHosts, useScheduling); + + waitForInactiveInstancesToStart(startingInstances, activeHosts); + + + NSMutableArray restartingInstances = runningInstances.mutableClone(); + refuseNewSessions(restartingInstances, activeHosts); + + NSMutableArray stoppingInstances = new NSMutableArray(); + 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 runningInstances, NSArray activeHosts) { + int numToStartPerHost = 1; + if (activeHosts.count() > 0) { + numToStartPerHost = (int) (runningInstances.count() / activeHosts.count() * .1); + } + if (numToStartPerHost < 1) { + numToStartPerHost = 1; + } + return numToStartPerHost; + } + + protected NSArray instancesToStart(NSArray inactiveInstances, NSArray activeHosts, + int numInstancesToStartPerHost) { + NSMutableArray startingInstances = new NSMutableArray(); + for (int i = 0; i < numInstancesToStartPerHost; i++) { + for (MHost host : activeHosts) { + NSArray 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 runningInstances) { + boolean useScheduling = true; + for (MInstance instance : runningInstances) { + useScheduling &= instance.schedulingEnabled() != null && instance.schedulingEnabled().booleanValue(); + } + return useScheduling; + } + + protected void startInstances(NSArray startingInstances, NSArray 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 startingInstances, NSArray 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 restartingInstances, NSArray activeHosts) { + for (MInstance instance : restartingInstances) { + instance.setRefusingNewSessions(true); + } + handler().sendRefuseSessionToWotaskds(restartingInstances, activeHosts, true); + } + + protected void restartInstances(NSArray runningInstances, NSArray activeHosts, boolean useScheduling) + throws InterruptedException { + for (MInstance instance : runningInstances) { + NSArray instanceInArray = new NSArray(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 stoppingInstances, NSArray 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"); + } + +} diff --git a/Applications/JavaMonitor/WebServerResources/help.png b/Applications/JavaMonitor/WebServerResources/help.png new file mode 100644 index 0000000000000000000000000000000000000000..d825ccbf42d0bd7dbf3e7521552574683161c075 GIT binary patch literal 2180 zcmV-~2z&R5P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2iye$ z5FQv1$~E=?00;z0L_t(o!{t|Na1+-R{_bOUZP}KLZ219x$OiLpWn=SlpeDsUQfg=d zg@#Vk(wY89+x%#0hNQ`)la?R!m867DGnsUzok^1!+R`*IJj#GW12i<)4VWp|#SiRY z3*?t%OO|9wd-wLou4EMx9+Up7X3pHz?%w--_nh;cvlsY(|B(lI+1~Z`oi!2t)qX?7ueHx?D>PAih#TM7e#rqv_nsAUsb!3WakhoPl!&&L}ve zV2pxN0^isqPJY;f(eVgAySNOSRxix;1;SfLmBnYqx>~&VBv7&L)xuRv=bU)u>CJPZ zu>_v~^+~jLcvGM%XW$A0XBGj@z_|IJF$zWr2&L#94B&FtEv%|7!-j?`+okSX>m!*B z2LipP!s&SQb^>|D?C{G^Z*~K~4_|LV*Nrg~TwzdE4n^fq6$M54b}TG+p{}YJm9w32 zkM?A>b#=|O7rY5Zd+fYcJ_^+rL7m7Gbjp!!Wk5cKv{tuCHXcuYzkD) zKnZ~@LqWbH19kKA@VzfDhb==*y{{+?r$6dL&wwA>*L_ASEwt~sV+h>Md+WBW^;Bpo z$M26`0;47{XBId&y^zfOU_^&^EP}z2FoZF!hEi7+mM$!YswByCaK>=p)W4u93|pE! z?uw0jYiAIklz*#fS*0x$j^T38keN`7TA6N=;G7{tQ*rLf5ca>*j(rC&;#con!obL6 znyw|viyYu-N@0wl-SN9EYMX01}2FVQ`9g+`#2-A0G0Qz-hOwnBjr4 z;@nx35)22zpfss#3TA=vG^weIndQ0mOziypBDfq{ihw_?HuXXetbpn#Cp+4$ffD*6`J}RVZ|5&~+VBN<`woiMBx`gal&@ zj2U2tS)eGT7!Sn&07cH6%ozlTRh0>dHGzN?ahh zU49IWPnpk!NjA9&Ld-F_@gaXGI)lK#P%t2+gv*%)LQIwfuw*xF$pu4E9l@ySDUAa+ z1JRVHlEh83HcbdzxmhqI;PnOlGmbRax6LMmfH8`ic?G!k@d*4Akqa{j2qAuR_F`W+$EKoYULmB}VN;*T5CVqVA{f_wj(mbrGcbnjbOaUdqg)pEh+>)BvB@+pOL_&uyj1&=J&1tMHg}XQx zhtGD#27Ti%equKq?`r9|$!nUouJx3z^pxOA?=Z$Er_!XDj5EP#Fph3t1l_&}u6cv- z`NN3EbtH6QdP6tOUF5)%kJTX<$y?s3ITtzyLhDv9sc@{UDS@Z50RCVUBjeFj zf+cgrjJJScNX##pg`FEcXsj(kFcQP=ea8mgJ#>8M^;3J>0EqkfDy9JJ0J3vtH@FwA z+40(UzuvUy(TA%QRbhxsCD8ft7y_XwOiac=2;j)hfHNlp3(K9bX$s=Hz>%|^`u?|1 zcU*1x?XF4R)jMOTL4S~2wLFDYk9A8L8b+s1t8lpo87(Aljm;O zLI6lu76UK^AZpp9b^lWxPDvUtPa{AqaKqYN!W#AaS@b`bDSgq#x6z#d0000 HOST = new ERXKey("host"); + public static final ERXKey HOST_NAME = new ERXKey("hostName"); + /* * String hostName; Integer id; Integer port; String applicationName; * Boolean autoRecover; Integer minimumActiveSessionsCount; String path; diff --git a/Frameworks/Misc/JavaMonitorFramework/Sources/com/webobjects/monitor/_private/MObject.java b/Frameworks/Misc/JavaMonitorFramework/Sources/com/webobjects/monitor/_private/MObject.java index 0d043a4df6f..59649fd71f1 100644 --- a/Frameworks/Misc/JavaMonitorFramework/Sources/com/webobjects/monitor/_private/MObject.java +++ b/Frameworks/Misc/JavaMonitorFramework/Sources/com/webobjects/monitor/_private/MObject.java @@ -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; } } diff --git a/Frameworks/Misc/JavaMonitorFramework/Sources/com/webobjects/monitor/_private/MSiteConfig.java b/Frameworks/Misc/JavaMonitorFramework/Sources/com/webobjects/monitor/_private/MSiteConfig.java index 6f3fbcea28e..949585be74b 100644 --- a/Frameworks/Misc/JavaMonitorFramework/Sources/com/webobjects/monitor/_private/MSiteConfig.java +++ b/Frameworks/Misc/JavaMonitorFramework/Sources/com/webobjects/monitor/_private/MSiteConfig.java @@ -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; }