From 2c4b66a96014f4dec8b25f7c1347692e818a96cb Mon Sep 17 00:00:00 2001 From: jamurty Date: Mon, 25 May 2009 18:33:27 +0000 Subject: [PATCH] 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 --- ...ackoffLimitedRetryJavaIntegrationTest.java | 160 ++++++++++++++++++ .../http/BaseHttpFutureCommandClientTest.java | 118 +------------ .../java/org/jclouds/http/BaseJettyTest.java | 155 +++++++++++++++++ 3 files changed, 316 insertions(+), 117 deletions(-) create mode 100644 core/src/test/java/org/jclouds/http/BackoffLimitedRetryJavaIntegrationTest.java create mode 100644 core/src/test/java/org/jclouds/http/BaseJettyTest.java diff --git a/core/src/test/java/org/jclouds/http/BackoffLimitedRetryJavaIntegrationTest.java b/core/src/test/java/org/jclouds/http/BackoffLimitedRetryJavaIntegrationTest.java new file mode 100644 index 0000000000..9cf419c28e --- /dev/null +++ b/core/src/test/java/org/jclouds/http/BackoffLimitedRetryJavaIntegrationTest.java @@ -0,0 +1,160 @@ +/** + * + * Copyright (C) 2009 James Murty + * + * ==================================================================== + * 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); + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/jclouds/http/BaseHttpFutureCommandClientTest.java b/core/src/test/java/org/jclouds/http/BaseHttpFutureCommandClientTest.java index cf0a598393..f23eb0b802 100644 --- a/core/src/test/java/org/jclouds/http/BaseHttpFutureCommandClientTest.java +++ b/core/src/test/java/org/jclouds/http/BaseHttpFutureCommandClientTest.java @@ -23,140 +23,24 @@ */ package org.jclouds.http; -import java.io.IOException; 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.TimeUnit; 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.GetString; import org.jclouds.http.commands.Head; 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 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! * * @author Adrian Cole */ @Test(threadPoolSize = 10) -public abstract class BaseHttpFutureCommandClientTest { - protected static final String XML = "whoppers"; - 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 filters = new ArrayList( - 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>() { - }).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(); - } +public abstract class BaseHttpFutureCommandClientTest extends BaseJettyTest { @Test(invocationCount = 50, timeOut = 3000) public void testRequestFilter() throws MalformedURLException, diff --git a/core/src/test/java/org/jclouds/http/BaseJettyTest.java b/core/src/test/java/org/jclouds/http/BaseJettyTest.java new file mode 100644 index 0000000000..f5b34e2ef2 --- /dev/null +++ b/core/src/test/java/org/jclouds/http/BaseJettyTest.java @@ -0,0 +1,155 @@ +/** + * + * Copyright (C) 2009 Adrian Cole + * + * ==================================================================== + * 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 = "whoppers"; + 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 filters = new ArrayList( + 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>() { + }).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; + } + +}