SLIDER-678: liveness API in REST API. includes test improvements and better exception handling
Project: http://git-wip-us.apache.org/repos/asf/incubator-slider/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-slider/commit/e2388afc Tree: http://git-wip-us.apache.org/repos/asf/incubator-slider/tree/e2388afc Diff: http://git-wip-us.apache.org/repos/asf/incubator-slider/diff/e2388afc Branch: refs/heads/develop Commit: e2388afc210e8d2547a9c7da01e6b25a4d78bed3 Parents: dce1236 Author: Steve Loughran <[email protected]> Authored: Fri Jan 30 19:55:00 2015 +0000 Committer: Steve Loughran <[email protected]> Committed: Fri Jan 30 19:55:45 2015 +0000 ---------------------------------------------------------------------- .../slider/client/rest/BaseRestClient.java | 12 ++++- .../client/rest/SliderApplicationAPI.java | 52 ++++++++++++++---- .../core/exceptions/ExceptionConverter.java | 26 +++++++++ .../application/actions/RestActionStop.java | 3 +- .../agent/rest/AbstractRestTestDelegate.groovy | 2 + .../agent/rest/LowLevelRestTestDelegates.groovy | 3 -- .../rest/RestAPIClientTestDelegates.groovy | 57 +++++++++----------- .../slider/agent/rest/TestStandaloneREST.groovy | 30 ++++------- .../funtest/lifecycle/AgentWebPagesIT.groovy | 17 +++--- 9 files changed, 130 insertions(+), 72 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e2388afc/slider-core/src/main/java/org/apache/slider/client/rest/BaseRestClient.java ---------------------------------------------------------------------- diff --git a/slider-core/src/main/java/org/apache/slider/client/rest/BaseRestClient.java b/slider-core/src/main/java/org/apache/slider/client/rest/BaseRestClient.java index 46be2aa..41cfe9d 100644 --- a/slider-core/src/main/java/org/apache/slider/client/rest/BaseRestClient.java +++ b/slider-core/src/main/java/org/apache/slider/client/rest/BaseRestClient.java @@ -20,9 +20,11 @@ package org.apache.slider.client.rest; import com.google.common.base.Preconditions; import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientHandlerException; import com.sun.jersey.api.client.GenericType; import com.sun.jersey.api.client.UniformInterfaceException; import com.sun.jersey.api.client.WebResource; +import org.apache.slider.core.exceptions.ExceptionConverter; import org.apache.slider.core.restclient.HttpVerb; import org.apache.slider.core.restclient.UgiJerseyBinding; import org.slf4j.Logger; @@ -34,7 +36,7 @@ import java.net.URI; /** - * This is a base class for Jersey Rest clients in Slider. + * This is a base class for Jersey REST clients in Slider. * It supports bonding to an AM and the execution of operations âwith * exceptions uprated to IOExceptions when needed. * <p> @@ -89,6 +91,10 @@ public class BaseRestClient { Preconditions.checkArgument(c != null); resource.accept(MediaType.APPLICATION_JSON_TYPE); return (T) resource.method(method.getVerb(), c); + } catch (ClientHandlerException ex) { + throw ExceptionConverter.convertJerseyException(method.getVerb(), + resource.getURI().toString(), + ex); } catch (UniformInterfaceException ex) { throw UgiJerseyBinding.uprateFaults(method, resource.getURI().toString(), @@ -111,6 +117,10 @@ public class BaseRestClient { Preconditions.checkArgument(t != null); resource.accept(MediaType.APPLICATION_JSON_TYPE); return resource.method(method.getVerb(), t); + } catch (ClientHandlerException ex) { + throw ExceptionConverter.convertJerseyException(method.getVerb(), + resource.getURI().toString(), + ex); } catch (UniformInterfaceException ex) { throw UgiJerseyBinding.uprateFaults(method, resource.getURI().toString(), ex); http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e2388afc/slider-core/src/main/java/org/apache/slider/client/rest/SliderApplicationAPI.java ---------------------------------------------------------------------- diff --git a/slider-core/src/main/java/org/apache/slider/client/rest/SliderApplicationAPI.java b/slider-core/src/main/java/org/apache/slider/client/rest/SliderApplicationAPI.java index a8dfe8f..ee4760e 100644 --- a/slider-core/src/main/java/org/apache/slider/client/rest/SliderApplicationAPI.java +++ b/slider-core/src/main/java/org/apache/slider/client/rest/SliderApplicationAPI.java @@ -60,15 +60,18 @@ public class SliderApplicationAPI extends BaseRestClient { } /** - * Create a resource under the application path - * @param subpath - * @return an resource under the application path + * Create a resource under the application path set up to accept + * JSON + * @param subpath path under application + * @return a resource under the application path */ public WebResource applicationResource(String subpath) { Preconditions.checkArgument(!StringUtils.isEmpty(subpath), "empty path"); Preconditions.checkNotNull(appResource, "Null app resource"); - return appResource.path(subpath); + WebResource resource = appResource.path(subpath); + resource.accept(MediaType.APPLICATION_JSON_TYPE); + return resource; } /** @@ -258,18 +261,49 @@ public class SliderApplicationAPI extends BaseRestClient { } /** - * Ping as a post + * Ping as a GET * @param text text to include * @return the response * @throws IOException on any failure */ public PingResource ping(String text) throws IOException { - WebResource pingOut = applicationResource(ACTION_PING); - pingOut.accept(MediaType.APPLICATION_JSON_TYPE); - pingOut.type(MediaType.APPLICATION_JSON_TYPE); + return pingPost(text); + } + + /** + * Ping as a GET + * @param text text to include + * @return the response + * @throws IOException on any failure + */ + public PingResource pingGet(String text) throws IOException { + WebResource pingResource = applicationResource(ACTION_PING); + pingResource.getUriBuilder().queryParam("body", text); + return pingResource.get(PingResource.class); + } + + /** + * Ping as a POST + * @param text text to include + * @return the response + * @throws IOException on any failure + */ + public PingResource pingPost(String text) throws IOException { + WebResource pingResource = applicationResource(ACTION_PING); + pingResource.type(MediaType.APPLICATION_JSON_TYPE); Form f = new Form(); f.add("text", text); - return pingOut.post(PingResource.class, f); + return pingResource.post(PingResource.class, f); + } + + /** + * Stop the AM (async operation) + * @param text text to include + * @throws IOException on any failure + */ + public void stop(String text) throws IOException { + WebResource resource = applicationResource(ACTION_STOP); + resource.post(text); } /** http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e2388afc/slider-core/src/main/java/org/apache/slider/core/exceptions/ExceptionConverter.java ---------------------------------------------------------------------- diff --git a/slider-core/src/main/java/org/apache/slider/core/exceptions/ExceptionConverter.java b/slider-core/src/main/java/org/apache/slider/core/exceptions/ExceptionConverter.java index e3cd508..02cc64a 100644 --- a/slider-core/src/main/java/org/apache/slider/core/exceptions/ExceptionConverter.java +++ b/slider-core/src/main/java/org/apache/slider/core/exceptions/ExceptionConverter.java @@ -18,6 +18,7 @@ package org.apache.slider.core.exceptions; +import com.sun.jersey.api.client.ClientHandlerException; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.UniformInterfaceException; import org.apache.hadoop.fs.PathAccessDeniedException; @@ -70,4 +71,29 @@ public class ExceptionConverter { ioe.initCause(exception); return ioe; } + + /** + * Handle a client-side Jersey exception. + * <p> + * If there's an inner IOException, return that. + * <p> + * Otherwise: create a new wrapper IOE including verb and target details + * @param verb HTTP Verb used + * @param targetURL URL being targeted + * @param exception original exception + * @return an exception to throw + */ + public static IOException convertJerseyException(String verb, + String targetURL, + ClientHandlerException exception) { + if (exception.getCause() instanceof IOException) { + return (IOException)exception.getCause(); + } else { + IOException ioe = new IOException( + verb + " " + targetURL + " failed: " + exception); + ioe.initCause(exception); + return ioe; + } + } + } http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e2388afc/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/actions/RestActionStop.java ---------------------------------------------------------------------- diff --git a/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/actions/RestActionStop.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/actions/RestActionStop.java index c0e30cc..544f589 100644 --- a/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/actions/RestActionStop.java +++ b/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/actions/RestActionStop.java @@ -51,9 +51,10 @@ public class RestActionStop { "Stopping action %s received at %tc", verb, time); response.text = text; + log.info(text); ActionStopSlider stopSlider = new ActionStopSlider(text, - 500, + 1000, TimeUnit.MILLISECONDS, LauncherExitCodes.EXIT_SUCCESS, FinalApplicationStatus.SUCCEEDED, http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e2388afc/slider-core/src/test/groovy/org/apache/slider/agent/rest/AbstractRestTestDelegate.groovy ---------------------------------------------------------------------- diff --git a/slider-core/src/test/groovy/org/apache/slider/agent/rest/AbstractRestTestDelegate.groovy b/slider-core/src/test/groovy/org/apache/slider/agent/rest/AbstractRestTestDelegate.groovy index 15026e4..9498e2f 100644 --- a/slider-core/src/test/groovy/org/apache/slider/agent/rest/AbstractRestTestDelegate.groovy +++ b/slider-core/src/test/groovy/org/apache/slider/agent/rest/AbstractRestTestDelegate.groovy @@ -26,6 +26,8 @@ import org.apache.slider.test.SliderTestUtils abstract class AbstractRestTestDelegate extends SliderTestUtils { public static final String TEST_GLOBAL_OPTION = "test.global.option" public static final String TEST_GLOBAL_OPTION_PRESENT = "present" + public static final int STOP_WAIT_TIME = 30000 + public static final int STOP_PROBE_INTERVAL = 500 public final boolean enableComplexVerbs AbstractRestTestDelegate(boolean enableComplexVerbs) { http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e2388afc/slider-core/src/test/groovy/org/apache/slider/agent/rest/LowLevelRestTestDelegates.groovy ---------------------------------------------------------------------- diff --git a/slider-core/src/test/groovy/org/apache/slider/agent/rest/LowLevelRestTestDelegates.groovy b/slider-core/src/test/groovy/org/apache/slider/agent/rest/LowLevelRestTestDelegates.groovy index be3de8c..f499d63 100644 --- a/slider-core/src/test/groovy/org/apache/slider/agent/rest/LowLevelRestTestDelegates.groovy +++ b/slider-core/src/test/groovy/org/apache/slider/agent/rest/LowLevelRestTestDelegates.groovy @@ -267,12 +267,9 @@ class LowLevelRestTestDelegates extends AbstractRestTestDelegate { MediaType.TEXT_PLAIN) log.info "Stopped: $outcome" - // await the shutdown - sleep(1000) // now a ping is expected to fail String ping = appendToURL(appmaster, SLIDER_PATH_APPLICATION, ACTION_PING) - URL pingUrl = new URL(ping) repeatUntilSuccess("probe for missing registry entry", this.&probePingFailing, 30000, 500, http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e2388afc/slider-core/src/test/groovy/org/apache/slider/agent/rest/RestAPIClientTestDelegates.groovy ---------------------------------------------------------------------- diff --git a/slider-core/src/test/groovy/org/apache/slider/agent/rest/RestAPIClientTestDelegates.groovy b/slider-core/src/test/groovy/org/apache/slider/agent/rest/RestAPIClientTestDelegates.groovy index 1851dd7..07c5ef0 100644 --- a/slider-core/src/test/groovy/org/apache/slider/agent/rest/RestAPIClientTestDelegates.groovy +++ b/slider-core/src/test/groovy/org/apache/slider/agent/rest/RestAPIClientTestDelegates.groovy @@ -29,6 +29,7 @@ import org.apache.slider.client.rest.SliderApplicationAPI import org.apache.slider.core.conf.ConfTree import org.apache.slider.core.conf.ConfTreeOperations import org.apache.slider.server.appmaster.web.rest.application.ApplicationResource +import org.apache.slider.test.Outcome import javax.ws.rs.core.MediaType @@ -44,8 +45,6 @@ import static org.apache.slider.server.appmaster.web.rest.RestPaths.* @CompileStatic @Slf4j class RestAPIClientTestDelegates extends AbstractRestTestDelegate { - public static final String TEST_GLOBAL_OPTION = "test.global.option" - public static final String TEST_GLOBAL_OPTION_PRESENT = "present" final String appmaster; final String application; @@ -176,14 +175,14 @@ class RestAPIClientTestDelegates extends AbstractRestTestDelegate { def unresolved = fetchTypeList(ConfTree, appmaster, [MODEL_DESIRED_APPCONF, MODEL_DESIRED_RESOURCES]) - assert unresolved[MODEL_DESIRED_APPCONF].components[sam] - [TEST_GLOBAL_OPTION] == null + assert null == + unresolved[MODEL_DESIRED_APPCONF].components[sam][TEST_GLOBAL_OPTION] def resolvedAppconf = appAPI.getResolvedAppconf() - assert resolvedAppconf. - components[sam][TEST_GLOBAL_OPTION] == TEST_GLOBAL_OPTION_PRESENT + assert TEST_GLOBAL_OPTION_PRESENT== + resolvedAppconf. components[sam][TEST_GLOBAL_OPTION] } public void testPing() { @@ -199,39 +198,35 @@ class RestAPIClientTestDelegates extends AbstractRestTestDelegate { * Important: once executed, the AM is no longer there. * This must be the last test in the sequence. */ -/* public void testStop() { - String target = appendToURL(appmaster, SLIDER_PATH_APPLICATION, ACTION_STOP) - describe "Stop URL $target" - URL targetUrl = new URL(target) - def outcome = connectionOperations.execHttpOperation( - HttpVerb.POST, - targetUrl, - new byte[0], - MediaType.TEXT_PLAIN) - log.info "Stopped: $outcome" - - // await the shutdown - sleep(1000) - - // now a ping is expected to fail - String ping = appendToURL(appmaster, SLIDER_PATH_APPLICATION, ACTION_PING) - URL pingUrl = new URL(ping) - repeatUntilSuccess("probe for missing registry entry", - this.&probePingFailing, 30000, 500, - [url: ping], + appAPI.stop("stop") + + repeatUntilSuccess("probe for liveness", + this.&probeForLivenessFailing, STOP_WAIT_TIME, STOP_PROBE_INTERVAL, + [:], true, "AM failed to shut down") { - def pinged = jFetchType(ACTION_PING + "?body=hello", - PingResource - ) - fail("AM didn't shut down; Ping GET= $pinged") + appAPI.getApplicationLiveness() } } -*/ + + /** + * Probe that spins until the liveness query fails + * @param args argument map + * @return the outcome + */ + Outcome probeForLivenessFailing(Map args) { + try { + appAPI.getApplicationLiveness() + return Outcome.Retry + } catch (IOException e) { + // expected + return Outcome.Success + } + } public void testSuiteGetOperations() { http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e2388afc/slider-core/src/test/groovy/org/apache/slider/agent/rest/TestStandaloneREST.groovy ---------------------------------------------------------------------- diff --git a/slider-core/src/test/groovy/org/apache/slider/agent/rest/TestStandaloneREST.groovy b/slider-core/src/test/groovy/org/apache/slider/agent/rest/TestStandaloneREST.groovy index ab5f156..e210812 100644 --- a/slider-core/src/test/groovy/org/apache/slider/agent/rest/TestStandaloneREST.groovy +++ b/slider-core/src/test/groovy/org/apache/slider/agent/rest/TestStandaloneREST.groovy @@ -124,14 +124,14 @@ class TestStandaloneREST extends AgentMiniClusterTestBase { def ugiClient = createUGIJerseyClient(); describe "Proxy SliderRestClient Tests" - RestAPIClientTestDelegates proxySliderRestClient = + RestAPIClientTestDelegates proxySliderRestAPI = new RestAPIClientTestDelegates(proxyAM, ugiClient, proxyComplexVerbs) - proxySliderRestClient.testSuiteGetOperations() + proxySliderRestAPI.testSuiteGetOperations() describe "Direct SliderRestClient Tests" - RestAPIClientTestDelegates directSliderRestClient = + RestAPIClientTestDelegates directSliderRestAPI = new RestAPIClientTestDelegates(directAM, ugiClient, directComplexVerbs) - directSliderRestClient.testSuiteAll() + directSliderRestAPI.testSuiteAll() describe "Proxy Jersey Tests" @@ -169,26 +169,14 @@ class TestStandaloneREST extends AgentMiniClusterTestBase { sliderApplicationApi.resolvedModel sliderApplicationApi.ping("registry located") -/* DISABLED: this client does not pass the tests. - - // http client direct - describe "Proxied Jersey Apache HttpClient" - JerseyTestDelegates proxiedHttpClientJersey = - new JerseyTestDelegates(proxyAM, createJerseyClientHttpClient()) - proxiedHttpClientJersey.testSuiteGetOperations() - - describe "Direct Jersey Apache HttpClient" - JerseyTestDelegates directHttpClientJersey = - new JerseyTestDelegates(directAM, createJerseyClientHttpClient()) - directHttpClientJersey.testSuiteGetOperations() - directHttpClientJersey.testSuiteComplexVerbs() - */ - createJerseyClientHttpClient() // log the metrics to show what's up direct.logCodahaleMetrics(); - // this MUST be the final test - direct.testStop(); + // finally, stop the AM + if (directComplexVerbs) { + describe "Stopping AM via REST API" + directSliderRestAPI.testStop(); + } } /** http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/e2388afc/slider-funtest/src/test/groovy/org/apache/slider/funtest/lifecycle/AgentWebPagesIT.groovy ---------------------------------------------------------------------- diff --git a/slider-funtest/src/test/groovy/org/apache/slider/funtest/lifecycle/AgentWebPagesIT.groovy b/slider-funtest/src/test/groovy/org/apache/slider/funtest/lifecycle/AgentWebPagesIT.groovy index 2cd13dd..a364e63 100644 --- a/slider-funtest/src/test/groovy/org/apache/slider/funtest/lifecycle/AgentWebPagesIT.groovy +++ b/slider-funtest/src/test/groovy/org/apache/slider/funtest/lifecycle/AgentWebPagesIT.groovy @@ -138,14 +138,14 @@ public class AgentWebPagesIT extends AgentCommandTestBase directJerseyTests.testSuiteAll() describe "Proxy SliderRestClient Tests" - RestAPIClientTestDelegates proxySliderRestClient = + RestAPIClientTestDelegates proxySliderRestAPI = new RestAPIClientTestDelegates(proxyAM, ugiClient, proxyComplexVerbs) - proxySliderRestClient.testSuiteAll() + proxySliderRestAPI.testSuiteAll() describe "Direct SliderRestClient Tests" - RestAPIClientTestDelegates directSliderRestClient = + RestAPIClientTestDelegates directSliderRestAPI = new RestAPIClientTestDelegates(directAM, ugiClient, directComplexVerbs) - directSliderRestClient.testSuiteAll() + directSliderRestAPI.testSuiteAll() if (UserGroupInformation.securityEnabled) { describe "Insecure Proxy Tests against a secure cluster" @@ -176,10 +176,15 @@ public class AgentWebPagesIT extends AgentCommandTestBase def sliderApplicationApi = restClientFactory.createSliderApplicationApi(); sliderApplicationApi.desiredModel sliderApplicationApi.resolvedModel - sliderApplicationApi.ping("registry located") + if (proxyComplexVerbs) { + sliderApplicationApi.ping("registry located") + } // finally, stop the AM - direct.testStop(); + if (directComplexVerbs) { + describe "Stopping AM via REST API" + directSliderRestAPI.testStop(); + } } }
