Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent Jenkins hang with unresponsive web servers #7

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@
<version>3.1</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.4</version>
</dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.4</version>
</dependency>
</dependencies>

<repositories>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@ public SiteMonitorDescriptor() {
}

/**
* For testing
*/
protected SiteMonitorDescriptor(
List<Integer> mSuccessResponseCodes,
Integer mTimeout) {
super(SiteMonitorRecorder.class);
this.mValidator = new SiteMonitorValidator();
this.mSuccessResponseCodes = mSuccessResponseCodes;
this.mTimeout = mTimeout;
}

/**
* @return the plugin's display name, used in the job's build drop down list
*/
@Override
Expand Down
98 changes: 85 additions & 13 deletions src/main/java/hudson/plugins/sitemonitor/SiteMonitorRecorder.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@
import javax.net.ssl.X509TrustManager;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.codec.binary.Base64;

/**
Expand All @@ -59,6 +68,9 @@
*/
public class SiteMonitorRecorder extends Recorder {

private static final Logger LOGGER = Logger
.getLogger(SiteMonitorRecorder.class.getName());

/**
* 1 sec = 1000 msecs .
*/
Expand Down Expand Up @@ -131,6 +143,51 @@ public boolean verify(String arg0, SSLSession arg1) {
}
}

public class StatusResponse {
public Status status = null;
public Integer responseCode = null;
}

private StatusResponse checkURL(Site site, SiteMonitorDescriptor descriptor) throws Exception {
StatusResponse response = new StatusResponse();
HttpURLConnection connection = null;

LOGGER.log(Level.FINE,"Checking URL {}",site.getUrl());

connection = getConnection(site.getUrl());
connection.setConnectTimeout(descriptor.getTimeout()
* MILLISECS_IN_SECS);
connection.setReadTimeout(descriptor.getTimeout()
* MILLISECS_IN_SECS);
response.responseCode = connection.getResponseCode();

List<Integer> successResponseCodes = descriptor
.getSuccessResponseCodes();
if (successResponseCodes.contains(response.responseCode)) {
response.status = Status.UP;
} else {
response.status = Status.ERROR;
}

return response;
}

private ExecutorService exec = null;

public synchronized Future<StatusResponse> launchCheck(final Site site, final SiteMonitorDescriptor descriptor) throws IOException {
if (exec == null) {
exec = Executors.newCachedThreadPool();
// exec = Executors.newSingleThreadExecutor();
}
return exec.submit(new Callable<StatusResponse>() {

public StatusResponse call() throws Exception {
return checkURL(site, descriptor);
}

});
}

/**
* Performs the web site monitoring by checking the response code of the
* site's URL.
Expand All @@ -157,32 +214,47 @@ public final boolean perform(final AbstractBuild<?, ?> build,
boolean hasFailure = false;
for (Site site : mSites) {

LOGGER.log(Level.FINE, "Checking site {}", site.getUrl());

Integer responseCode = null;
Status status;
String note = "";
HttpURLConnection connection = null;

StatusResponse response;

Future<StatusResponse> future = null;

try {
connection = getConnection(site.getUrl());
connection.setConnectTimeout(descriptor.getTimeout()
* MILLISECS_IN_SECS);
responseCode = connection.getResponseCode();

List<Integer> successResponseCodes = descriptor
.getSuccessResponseCodes();
if (successResponseCodes.contains(responseCode)) {
status = Status.UP;
} else {
status = Status.ERROR;
try {
future = launchCheck(site, descriptor);
LOGGER.log(Level.FINE, "Waiting for future for {}", site.getUrl());
response = future.get(descriptor.getTimeout()+1, TimeUnit.SECONDS);
status = response.status;
responseCode = response.responseCode;
} catch (ExecutionException ee) {
LOGGER.log(Level.WARNING, "ExecutionException in checking", ee);
status = Status.DOWN;
if (ee.getCause() instanceof Exception) {
throw (Exception)ee.getCause();
}
throw ee;
}
} catch (TimeoutException toe) {
listener.getLogger().println("Timeout: " + toe);
status = Status.DOWN;
} catch (SocketTimeoutException ste) {
listener.getLogger().println(ste + " - " + ste.getMessage());
listener.getLogger().println("Socket Timeout: " + ste + " - " + ste.getMessage());
status = Status.DOWN;
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Exception in checking", e);
note = e + " - " + e.getMessage();
listener.getLogger().println(note);
status = Status.EXCEPTION;
} finally {
if (future != null) {
LOGGER.log(Level.FINER, "Cancelling future {}", future);
future.cancel(true);
}
if (connection != null) {
connection.disconnect();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
URL=URL
Delete=\u524a\u9664
Add\ URL...=URL\u3092\u8ffd\u52a0...
URL=URL
Delete=\u524a\u9664
Add\ URL...=URL\u3092\u8ffd\u52a0...
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Site\ Monitor=\u30b5\u30a4\u30c8\u30e2\u30cb\u30bf\u30fc
Success\ Response\ Codes=\u6210\u529f\u6642\u30b9\u30c6\u30fc\u30bf\u30b9\u30b3\u30fc\u30c9
Timeout\ in\ seconds=\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8(\u79d2)
Site\ Monitor=\u30b5\u30a4\u30c8\u30e2\u30cb\u30bf\u30fc
Success\ Response\ Codes=\u6210\u529f\u6642\u30b9\u30c6\u30fc\u30bf\u30b9\u30b3\u30fc\u30c9
Timeout\ in\ seconds=\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8(\u79d2)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Site\ Monitor\ Report=\u30b5\u30a4\u30c8\u30e2\u30cb\u30bf\u30fc\u30ec\u30dd\u30fc\u30c8
URL=URL
Response\ Code=\u30b9\u30c6\u30fc\u30bf\u30b9\u30b3\u30fc\u30c9
Status=\u30b9\u30c6\u30fc\u30bf\u30b9
Site\ Monitor\ Report=\u30b5\u30a4\u30c8\u30e2\u30cb\u30bf\u30fc\u30ec\u30dd\u30fc\u30c8
URL=URL
Response\ Code=\u30b9\u30c6\u30fc\u30bf\u30b9\u30b3\u30fc\u30c9
Status=\u30b9\u30c6\u30fc\u30bf\u30b9
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Site\ Monitor\ Report=\u30b5\u30a4\u30c8\u30e2\u30cb\u30bf\u30fc\u30ec\u30dd\u30fc\u30c8
Site\ Monitor\ Report=\u30b5\u30a4\u30c8\u30e2\u30cb\u30bf\u30fc\u30ec\u30dd\u30fc\u30c8
4 changes: 2 additions & 2 deletions src/main/webapp/successresponsecodes_ja.html
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Webサイトが、アップ状態 かつ エラーなしの状態であることを示すHTTPステータスコードの値を、カンマ区切りで記述します。
詳細情報は、<a href="http://ja.wikipedia.org/wiki/HTTP%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89">HTTPステータスコード一覧</a>で確認できます。
Webサイトが、アップ状態 かつ エラーなしの状態であることを示すHTTPステータスコードの値を、カンマ区切りで記述します。
詳細情報は、<a href="http://ja.wikipedia.org/wiki/HTTP%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89">HTTPステータスコード一覧</a>で確認できます。
2 changes: 1 addition & 1 deletion src/main/webapp/url_ja.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
監視対象のWebサイトのURLアドレスを記述します。 URLは http:// または https:// から記述してください。
監視対象のWebサイトのURLアドレスを記述します。 URLは http:// または https:// から記述してください。
107 changes: 107 additions & 0 deletions src/test/java/hudson/plugins/sitemonitor/SiteMonitorRecorderTest.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package hudson.plugins.sitemonitor;

import hudson.plugins.sitemonitor.SiteMonitorRecorder;
import hudson.plugins.sitemonitor.SiteMonitorRecorder.StatusResponse;
import hudson.plugins.sitemonitor.model.Site;
import hudson.plugins.sitemonitor.model.Status;

import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import junit.framework.TestCase;

Expand All @@ -19,4 +27,103 @@ public void testGetSitesShouldGiveExpectedSites() {
builder = new SiteMonitorRecorder(sites);
assertEquals(sites, builder.getSites());
}

private class MockSMD extends SiteMonitorDescriptor {
public MockSMD(List<Integer> okCodes, Integer timeout) {
super(okCodes, timeout);
}
}

public void testLaunch() throws Exception {
List<Site> sites = new ArrayList<Site>();
Site site0 = new Site("http://hudson-ci.org");
sites.add(site0);
builder = new SiteMonitorRecorder(sites);

List<Integer> okCodes = Arrays.asList(200);
Integer timeout;
SiteMonitorDescriptor descriptor;

timeout = 1;
descriptor = new MockSMD(okCodes, timeout);

try {
Future<StatusResponse> f = builder.launchCheck(site0, descriptor);
StatusResponse s = f.get(1, TimeUnit.MICROSECONDS);
} catch (Exception e) {
assertTrue("Expected exception", e instanceof TimeoutException);
}

timeout = 30;
descriptor = new MockSMD(okCodes, timeout);

Future<StatusResponse> f = builder.launchCheck(site0, descriptor);
StatusResponse s = f.get(5, TimeUnit.MINUTES);
assertTrue("got result within 5 minutes", true);

}

public void testLaunch_badUrl() throws Exception {
List<Site> sites = new ArrayList<Site>();
Site site0 = new Site("http://hudson-ci.org/bogusURL");
sites.add(site0);
builder = new SiteMonitorRecorder(sites);

List<Integer> okCodes = Arrays.asList(200);
Integer timeout;
SiteMonitorDescriptor descriptor;

timeout = 1;
descriptor = new MockSMD(okCodes, timeout);

try {
Future<StatusResponse> f = builder.launchCheck(site0, descriptor);
StatusResponse s = f.get(1, TimeUnit.MICROSECONDS);
assertFalse("got result from bad URL", true);
} catch (Exception e) {
assertTrue("Expected exception", e instanceof TimeoutException);
}

timeout = 30;
descriptor = new MockSMD(okCodes, timeout);

Future<StatusResponse> f = builder.launchCheck(site0, descriptor);
StatusResponse s = f.get(5, TimeUnit.MINUTES);
assertTrue("got result within 5 minutes", true);

}

public void testLaunch_badHost() throws Exception {
List<Site> sites = new ArrayList<Site>();
Site site0 = new Site("http://no.such.host.hudson-ci.org/");
sites.add(site0);
builder = new SiteMonitorRecorder(sites);

List<Integer> okCodes = Arrays.asList(200);
Integer timeout;
SiteMonitorDescriptor descriptor;

timeout = 1;
descriptor = new MockSMD(okCodes, timeout);

try {
Future<StatusResponse> f = builder.launchCheck(site0, descriptor);
StatusResponse s = f.get(1, TimeUnit.MICROSECONDS);
} catch (Exception e) {
assertTrue("Expected exception", e instanceof TimeoutException);
}

timeout = 30;
descriptor = new MockSMD(okCodes, timeout);

try {
Future<StatusResponse> f = builder.launchCheck(site0, descriptor);
StatusResponse s = f.get(5, TimeUnit.MINUTES);
assertFalse("got result from bad URL", true);
} catch (Exception e) {
assertTrue("Expected exception", e instanceof ExecutionException);
assertTrue("Expected cause", e.getCause() instanceof UnknownHostException);
}
}

}