mirror of https://github.com/apache/jclouds.git
Factored Jetty testing code into a separate class, and added a derived test for Backoff Retry implementation. This test needs some work to obtain the system's actual retry limit, rather than assuming a constant limit of 5
git-svn-id: http://jclouds.googlecode.com/svn/trunk@854 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
parent
1f64fb1db3
commit
2c4b66a960
|
@ -0,0 +1,160 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009 James Murty <jamurty@gmail.com>
|
||||||
|
*
|
||||||
|
* ====================================================================
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
* ====================================================================
|
||||||
|
*/
|
||||||
|
package org.jclouds.http;
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
import static org.testng.Assert.assertNotNull;
|
||||||
|
import static org.testng.Assert.fail;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.jclouds.http.annotation.RetryHandler;
|
||||||
|
import org.jclouds.http.commands.GetString;
|
||||||
|
import org.jclouds.http.config.JavaUrlHttpFutureCommandClientModule;
|
||||||
|
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
|
||||||
|
import org.mortbay.jetty.Request;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import com.google.inject.Module;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the retry behavior of the default {@link RetryHandler} implementation
|
||||||
|
* {@link BackoffLimitedRetryHandler} to ensure that retries up to the default
|
||||||
|
* limit succeed.
|
||||||
|
*
|
||||||
|
* TODO: Should either explicitly set retry limit or get it from Guice, rather than assuming it's 5.
|
||||||
|
*
|
||||||
|
* @author James Murty
|
||||||
|
*/
|
||||||
|
@Test(sequential = true)
|
||||||
|
public class BackoffLimitedRetryJavaIntegrationTest extends BaseJettyTest {
|
||||||
|
private int beginToFailOnRequestNumber = 0;
|
||||||
|
private int endFailuresOnRequestNumber = 0;
|
||||||
|
private int requestCount = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addConnectionProperties(Properties props) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Module createClientModule() {
|
||||||
|
return new JavaUrlHttpFutureCommandClientModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean failOnRequest(HttpServletRequest request,
|
||||||
|
HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
requestCount++;
|
||||||
|
boolean shouldFail =
|
||||||
|
requestCount >= beginToFailOnRequestNumber
|
||||||
|
&& requestCount <= endFailuresOnRequestNumber;
|
||||||
|
if (shouldFail) {
|
||||||
|
response.sendError(500);
|
||||||
|
((Request) request).setHandled(true);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String submitGetRequest() throws InterruptedException, ExecutionException {
|
||||||
|
GetString get = factory.createGetString("/");
|
||||||
|
assertNotNull(get);
|
||||||
|
client.submit(get);
|
||||||
|
return get.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNoRetriesSuccessful() throws InterruptedException, ExecutionException {
|
||||||
|
beginToFailOnRequestNumber = 1;
|
||||||
|
endFailuresOnRequestNumber = 1;
|
||||||
|
requestCount = 0;
|
||||||
|
|
||||||
|
assertEquals(submitGetRequest().trim(), XML);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSingleRetrySuccessful() throws InterruptedException, ExecutionException {
|
||||||
|
beginToFailOnRequestNumber = 0;
|
||||||
|
endFailuresOnRequestNumber = 1;
|
||||||
|
requestCount = 0;
|
||||||
|
|
||||||
|
assertEquals(submitGetRequest().trim(), XML);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMaximumRetriesSuccessful() throws InterruptedException, ExecutionException {
|
||||||
|
beginToFailOnRequestNumber = 0;
|
||||||
|
endFailuresOnRequestNumber = 5;
|
||||||
|
requestCount = 0;
|
||||||
|
|
||||||
|
assertEquals(submitGetRequest().trim(), XML);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMaximumRetriesExceeded() throws InterruptedException {
|
||||||
|
beginToFailOnRequestNumber = 0;
|
||||||
|
endFailuresOnRequestNumber = 6;
|
||||||
|
requestCount = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
submitGetRequest();
|
||||||
|
fail("Request should not succeed within " + endFailuresOnRequestNumber + " requests");
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
assertEquals(e.getCause().getClass(), HttpResponseException.class);
|
||||||
|
HttpResponseException responseException = (HttpResponseException) e.getCause();
|
||||||
|
assertEquals(responseException.getResponse().getStatusCode(), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInterleavedSuccessesAndFailures() throws InterruptedException, ExecutionException {
|
||||||
|
beginToFailOnRequestNumber = 3;
|
||||||
|
endFailuresOnRequestNumber = 3 + 5; // Force third request to fail completely
|
||||||
|
requestCount = 0;
|
||||||
|
|
||||||
|
assertEquals(submitGetRequest().trim(), XML);
|
||||||
|
assertEquals(submitGetRequest().trim(), XML);
|
||||||
|
|
||||||
|
try {
|
||||||
|
submitGetRequest();
|
||||||
|
fail("Third request should not succeed by attempt number " + requestCount);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
assertEquals(e.getCause().getClass(), HttpResponseException.class);
|
||||||
|
HttpResponseException responseException = (HttpResponseException) e.getCause();
|
||||||
|
assertEquals(responseException.getResponse().getStatusCode(), 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(submitGetRequest().trim(), XML);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,140 +23,24 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.http;
|
package org.jclouds.http;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.jclouds.http.commands.CommandFactory;
|
|
||||||
import org.jclouds.http.commands.GetAndParseSax;
|
import org.jclouds.http.commands.GetAndParseSax;
|
||||||
import org.jclouds.http.commands.GetString;
|
import org.jclouds.http.commands.GetString;
|
||||||
import org.jclouds.http.commands.Head;
|
import org.jclouds.http.commands.Head;
|
||||||
import org.jclouds.http.commands.callables.xml.ParseSax;
|
import org.jclouds.http.commands.callables.xml.ParseSax;
|
||||||
import org.jclouds.http.commands.config.HttpCommandsModule;
|
|
||||||
import org.jclouds.lifecycle.Closer;
|
|
||||||
import org.jclouds.logging.jdk.config.JDKLoggingModule;
|
|
||||||
import org.mortbay.jetty.Handler;
|
|
||||||
import org.mortbay.jetty.Request;
|
|
||||||
import org.mortbay.jetty.Server;
|
|
||||||
import org.mortbay.jetty.handler.AbstractHandler;
|
|
||||||
import org.testng.annotations.AfterTest;
|
|
||||||
import org.testng.annotations.BeforeTest;
|
|
||||||
import org.testng.annotations.Optional;
|
|
||||||
import org.testng.annotations.Parameters;
|
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import com.google.inject.AbstractModule;
|
|
||||||
import com.google.inject.Guice;
|
|
||||||
import com.google.inject.Injector;
|
|
||||||
import com.google.inject.Key;
|
|
||||||
import com.google.inject.Module;
|
|
||||||
import com.google.inject.TypeLiteral;
|
|
||||||
import com.google.inject.name.Names;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* // TODO: Adrian: Document this!
|
* // TODO: Adrian: Document this!
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
*/
|
*/
|
||||||
@Test(threadPoolSize = 10)
|
@Test(threadPoolSize = 10)
|
||||||
public abstract class BaseHttpFutureCommandClientTest {
|
public abstract class BaseHttpFutureCommandClientTest extends BaseJettyTest {
|
||||||
protected static final String XML = "<foo><bar>whoppers</bar></foo>";
|
|
||||||
protected Server server = null;
|
|
||||||
protected CommandFactory factory;
|
|
||||||
protected HttpFutureCommandClient client;
|
|
||||||
protected Injector injector;
|
|
||||||
private Closer closer;
|
|
||||||
|
|
||||||
@BeforeTest
|
|
||||||
@Parameters( { "test-jetty-port" })
|
|
||||||
public void setUpJetty(@Optional("8123") final int testPort)
|
|
||||||
throws Exception {
|
|
||||||
Handler handler = new AbstractHandler() {
|
|
||||||
private AtomicInteger cycle = new AtomicInteger(0);
|
|
||||||
|
|
||||||
public void handle(String target, HttpServletRequest request,
|
|
||||||
HttpServletResponse response, int dispatch)
|
|
||||||
throws IOException, ServletException {
|
|
||||||
if (request.getHeader("test") != null) {
|
|
||||||
response.setContentType("text/plain");
|
|
||||||
response.setStatus(HttpServletResponse.SC_OK);
|
|
||||||
response.getWriter().println("test");
|
|
||||||
} else {
|
|
||||||
if (failEveryTenRequests(request, response))
|
|
||||||
return;
|
|
||||||
response.setContentType("text/xml");
|
|
||||||
response.setStatus(HttpServletResponse.SC_OK);
|
|
||||||
response.getWriter().println(XML);
|
|
||||||
}
|
|
||||||
((Request) request).setHandled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean failEveryTenRequests(HttpServletRequest request,
|
|
||||||
HttpServletResponse response) throws IOException {
|
|
||||||
if (cycle.incrementAndGet() % 10 == 0) {
|
|
||||||
response.sendError(500);
|
|
||||||
((Request) request).setHandled(true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
server = new Server(testPort);
|
|
||||||
server.setHandler(handler);
|
|
||||||
server.start();
|
|
||||||
final Properties properties = new Properties();
|
|
||||||
properties.put(HttpConstants.PROPERTY_HTTP_ADDRESS, "localhost");
|
|
||||||
properties.put(HttpConstants.PROPERTY_HTTP_PORT, testPort + "");
|
|
||||||
properties.put(HttpConstants.PROPERTY_HTTP_SECURE, "false");
|
|
||||||
addConnectionProperties(properties);
|
|
||||||
final List<HttpRequestFilter> filters = new ArrayList<HttpRequestFilter>(
|
|
||||||
1);
|
|
||||||
filters.add(new HttpRequestFilter() {
|
|
||||||
public void filter(HttpRequest request) throws HttpException {
|
|
||||||
if (request.getHeaders().containsKey("filterme")) {
|
|
||||||
request.getHeaders().put("test", "test");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
injector = Guice.createInjector(new AbstractModule() {
|
|
||||||
@Override
|
|
||||||
protected void configure() {
|
|
||||||
Names.bindProperties(binder(), properties);
|
|
||||||
}
|
|
||||||
}, new JDKLoggingModule(), new HttpCommandsModule(),
|
|
||||||
createClientModule(), new AbstractModule() {
|
|
||||||
@Override
|
|
||||||
protected void configure() {
|
|
||||||
bind(new TypeLiteral<List<HttpRequestFilter>>() {
|
|
||||||
}).toInstance(filters);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
factory = injector.getInstance(Key.get(CommandFactory.class));
|
|
||||||
client = injector.getInstance(HttpFutureCommandClient.class);
|
|
||||||
closer = injector.getInstance(Closer.class);
|
|
||||||
assert client != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void addConnectionProperties(Properties props);
|
|
||||||
|
|
||||||
protected abstract Module createClientModule();
|
|
||||||
|
|
||||||
@AfterTest
|
|
||||||
public void tearDownJetty() throws Exception {
|
|
||||||
closer.close();
|
|
||||||
server.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(invocationCount = 50, timeOut = 3000)
|
@Test(invocationCount = 50, timeOut = 3000)
|
||||||
public void testRequestFilter() throws MalformedURLException,
|
public void testRequestFilter() throws MalformedURLException,
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009 Adrian Cole <adrian@jclouds.org>
|
||||||
|
*
|
||||||
|
* ====================================================================
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
* ====================================================================
|
||||||
|
*/
|
||||||
|
package org.jclouds.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.jclouds.http.commands.CommandFactory;
|
||||||
|
import org.jclouds.http.commands.config.HttpCommandsModule;
|
||||||
|
import org.jclouds.lifecycle.Closer;
|
||||||
|
import org.jclouds.logging.jdk.config.JDKLoggingModule;
|
||||||
|
import org.mortbay.jetty.Handler;
|
||||||
|
import org.mortbay.jetty.Request;
|
||||||
|
import org.mortbay.jetty.Server;
|
||||||
|
import org.mortbay.jetty.handler.AbstractHandler;
|
||||||
|
import org.testng.annotations.AfterTest;
|
||||||
|
import org.testng.annotations.BeforeTest;
|
||||||
|
import org.testng.annotations.Optional;
|
||||||
|
import org.testng.annotations.Parameters;
|
||||||
|
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.Guice;
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.google.inject.Key;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import com.google.inject.TypeLiteral;
|
||||||
|
import com.google.inject.name.Names;
|
||||||
|
|
||||||
|
public abstract class BaseJettyTest {
|
||||||
|
protected static final String XML = "<foo><bar>whoppers</bar></foo>";
|
||||||
|
protected Server server = null;
|
||||||
|
protected CommandFactory factory;
|
||||||
|
protected HttpFutureCommandClient client;
|
||||||
|
protected Injector injector;
|
||||||
|
private Closer closer;
|
||||||
|
private AtomicInteger cycle = new AtomicInteger(0);
|
||||||
|
|
||||||
|
@BeforeTest
|
||||||
|
@Parameters( { "test-jetty-port" })
|
||||||
|
public void setUpJetty(@Optional("8123") final int testPort)
|
||||||
|
throws Exception {
|
||||||
|
Handler handler = new AbstractHandler() {
|
||||||
|
|
||||||
|
public void handle(String target, HttpServletRequest request,
|
||||||
|
HttpServletResponse response, int dispatch)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
if (request.getHeader("test") != null) {
|
||||||
|
response.setContentType("text/plain");
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
response.getWriter().println("test");
|
||||||
|
} else {
|
||||||
|
if (failOnRequest(request, response))
|
||||||
|
return;
|
||||||
|
response.setContentType("text/xml");
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
response.getWriter().println(XML);
|
||||||
|
}
|
||||||
|
((Request) request).setHandled(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
server = new Server(testPort);
|
||||||
|
server.setHandler(handler);
|
||||||
|
server.start();
|
||||||
|
final Properties properties = new Properties();
|
||||||
|
properties.put(HttpConstants.PROPERTY_HTTP_ADDRESS, "localhost");
|
||||||
|
properties.put(HttpConstants.PROPERTY_HTTP_PORT, testPort + "");
|
||||||
|
properties.put(HttpConstants.PROPERTY_HTTP_SECURE, "false");
|
||||||
|
addConnectionProperties(properties);
|
||||||
|
final List<HttpRequestFilter> filters = new ArrayList<HttpRequestFilter>(
|
||||||
|
1);
|
||||||
|
filters.add(new HttpRequestFilter() {
|
||||||
|
public void filter(HttpRequest request) throws HttpException {
|
||||||
|
if (request.getHeaders().containsKey("filterme")) {
|
||||||
|
request.getHeaders().put("test", "test");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
injector = Guice.createInjector(new AbstractModule() {
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
Names.bindProperties(binder(), properties);
|
||||||
|
}
|
||||||
|
}, new JDKLoggingModule(), new HttpCommandsModule(),
|
||||||
|
createClientModule(), new AbstractModule() {
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
bind(new TypeLiteral<List<HttpRequestFilter>>() {
|
||||||
|
}).toInstance(filters);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
factory = injector.getInstance(Key.get(CommandFactory.class));
|
||||||
|
client = injector.getInstance(HttpFutureCommandClient.class);
|
||||||
|
closer = injector.getInstance(Closer.class);
|
||||||
|
assert client != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterTest
|
||||||
|
public void tearDownJetty() throws Exception {
|
||||||
|
closer.close();
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract void addConnectionProperties(Properties props);
|
||||||
|
|
||||||
|
protected abstract Module createClientModule();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fails every 10 requests.
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @param response
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected boolean failOnRequest(HttpServletRequest request,
|
||||||
|
HttpServletResponse response) throws IOException {
|
||||||
|
if (cycle.incrementAndGet() % 10 == 0) {
|
||||||
|
response.sendError(500);
|
||||||
|
((Request) request).setHandled(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue