You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@slider.apache.org by st...@apache.org on 2015/01/09 16:34:26 UTC

[05/10] incubator-slider git commit: SLIDER-719 direct tests for PUT/POST/DELETE/HEAD all working with new client lib.

SLIDER-719 direct tests for PUT/POST/DELETE/HEAD all working with new client lib.


Project: http://git-wip-us.apache.org/repos/asf/incubator-slider/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-slider/commit/95d4acb6
Tree: http://git-wip-us.apache.org/repos/asf/incubator-slider/tree/95d4acb6
Diff: http://git-wip-us.apache.org/repos/asf/incubator-slider/diff/95d4acb6

Branch: refs/heads/develop
Commit: 95d4acb672b7a75e8b227e4d792178f83c0a543d
Parents: 372a5b7
Author: Steve Loughran <st...@apache.org>
Authored: Thu Jan 8 15:45:03 2015 +0000
Committer: Steve Loughran <st...@apache.org>
Committed: Thu Jan 8 15:45:03 2015 +0000

----------------------------------------------------------------------
 .../core/restclient/HttpOperationResponse.java  | 29 +++++++
 .../apache/slider/core/restclient/HttpVerb.java | 28 +++++--
 .../restclient/UrlConnectionOperations.java     | 65 +++++++++++++---
 .../rest/application/ApplicationResource.java   | 79 +++++++++++++++-----
 .../application/actions/RestActionPing.java     | 10 ++-
 .../application/resources/PingResource.java     |  5 ++
 .../standalone/TestStandaloneAgentWeb.groovy    | 51 +++++++++++--
 .../apache/slider/test/SliderTestUtils.groovy   | 25 ++++++-
 8 files changed, 243 insertions(+), 49 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/95d4acb6/slider-core/src/main/java/org/apache/slider/core/restclient/HttpOperationResponse.java
----------------------------------------------------------------------
diff --git a/slider-core/src/main/java/org/apache/slider/core/restclient/HttpOperationResponse.java b/slider-core/src/main/java/org/apache/slider/core/restclient/HttpOperationResponse.java
new file mode 100644
index 0000000..a5357a2
--- /dev/null
+++ b/slider-core/src/main/java/org/apache/slider/core/restclient/HttpOperationResponse.java
@@ -0,0 +1,29 @@
+/*
+ * 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.apache.slider.core.restclient;
+
+/**
+ * A response for use as a return value from operations
+ */
+public class HttpOperationResponse {
+  
+  public int responseCode;
+  public String contentType;
+  public byte[] data;
+}

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/95d4acb6/slider-core/src/main/java/org/apache/slider/core/restclient/HttpVerb.java
----------------------------------------------------------------------
diff --git a/slider-core/src/main/java/org/apache/slider/core/restclient/HttpVerb.java b/slider-core/src/main/java/org/apache/slider/core/restclient/HttpVerb.java
index 6767951..c040345 100644
--- a/slider-core/src/main/java/org/apache/slider/core/restclient/HttpVerb.java
+++ b/slider-core/src/main/java/org/apache/slider/core/restclient/HttpVerb.java
@@ -18,26 +18,40 @@
 
 package org.apache.slider.core.restclient;
 
+/**
+ * Http verbs with details on what they support in terms of submit and
+ * response bodies.
+ * <p>
+ * Those verbs which do support bodies in the response MAY NOT return it;
+ * if the response code is 204 then the answer is "no body", but the operation
+ * is considered a success.
+ */
 public enum HttpVerb {
-  GET("GET", false),
-  POST("POST", true),
-  PUT("POST", true),
-  DELETE("DELETE", false),
-  HEAD("HEAD", false);
+  GET("GET", false, true),
+  POST("POST", true, true),
+  PUT("PUT", true, true),
+  DELETE("DELETE", false, true),
+  HEAD("HEAD", false, false);
   
   private final String verb;
   private final boolean hasUploadBody;
+  private final boolean hasResponseBody;
 
-  HttpVerb(String verb, boolean hasUploadBody) {
+  HttpVerb(String verb, boolean hasUploadBody, boolean hasResponseBody) {
     this.verb = verb;
     this.hasUploadBody = hasUploadBody;
+    this.hasResponseBody = hasResponseBody;
   }
 
   public String getVerb() {
     return verb;
   }
 
-  public boolean isHasUploadBody() {
+  public boolean hasUploadBody() {
     return hasUploadBody;
   }
+
+  public boolean hasResponseBody() {
+    return hasResponseBody;
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/95d4acb6/slider-core/src/main/java/org/apache/slider/core/restclient/UrlConnectionOperations.java
----------------------------------------------------------------------
diff --git a/slider-core/src/main/java/org/apache/slider/core/restclient/UrlConnectionOperations.java b/slider-core/src/main/java/org/apache/slider/core/restclient/UrlConnectionOperations.java
index eb5d4a7..4633414 100644
--- a/slider-core/src/main/java/org/apache/slider/core/restclient/UrlConnectionOperations.java
+++ b/slider-core/src/main/java/org/apache/slider/core/restclient/UrlConnectionOperations.java
@@ -30,8 +30,10 @@ import org.apache.hadoop.yarn.webapp.NotFoundException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.ws.rs.core.MediaType;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
 
@@ -45,10 +47,22 @@ public class UrlConnectionOperations extends Configured {
 
   private URLConnectionFactory connectionFactory;
 
+  private boolean useSpnego = false;
+
   public UrlConnectionOperations(Configuration conf) {
     super(conf);
     connectionFactory = URLConnectionFactory
-        .newDefaultURLConnectionFactory(conf);  }
+        .newDefaultURLConnectionFactory(conf);
+  }
+
+
+  public boolean isUseSpnego() {
+    return useSpnego;
+  }
+
+  public void setUseSpnego(boolean useSpnego) {
+    this.useSpnego = useSpnego;
+  }
 
   /**
    * Opens a url with read and connect timeouts
@@ -58,28 +72,59 @@ public class UrlConnectionOperations extends Configured {
    * @return URLConnection
    * @throws IOException
    */
-  public HttpURLConnection openConnection(URL url, boolean spnego) throws
+  public HttpURLConnection openConnection(URL url) throws
       IOException,
       AuthenticationException {
     Preconditions.checkArgument(url.getPort() != 0, "no port");
     HttpURLConnection conn =
-        (HttpURLConnection) connectionFactory.openConnection(url, spnego);
+        (HttpURLConnection) connectionFactory.openConnection(url, useSpnego);
     conn.setUseCaches(false);
     conn.setInstanceFollowRedirects(true);
     return conn;
   }
 
-  public byte[] execGet(URL url, boolean spnego) throws
+  public HttpOperationResponse execGet(URL url) throws
       IOException,
       AuthenticationException {
+    return execHttpOperation(HttpVerb.GET, url, null, "");
+  }
+
+  public HttpOperationResponse execHttpOperation(HttpVerb verb,
+      URL url,
+      byte[] payload,
+      String contentType)
+      throws IOException, AuthenticationException {
     HttpURLConnection conn = null;
+    HttpOperationResponse outcome = new HttpOperationResponse();
     int resultCode;
     byte[] body = null;
-    log.debug("GET {} spnego={}", url, spnego);
+    log.debug("{} {} spnego={}", verb, url, useSpnego);
 
+    boolean doOutput = verb.hasUploadBody();
+    if (doOutput) {
+      Preconditions.checkArgument(payload !=null,
+          "Null payload on a verb which expects one");
+    }
     try {
-      conn = openConnection(url, spnego);
+      conn = openConnection(url);
+      conn.setRequestMethod(verb.getVerb());
+      conn.setDoOutput(doOutput);
+      if (doOutput) {
+        conn.setRequestProperty("Content-Type", contentType);
+      }
+      
+
+      // now do the connection
+      conn.connect();
+      
+      if (doOutput) {
+        OutputStream output = conn.getOutputStream();
+        IOUtils.write(payload, output);
+        output.close();
+      }
+      
       resultCode = conn.getResponseCode();
+      outcome.contentType = conn.getContentType();
       InputStream stream = conn.getErrorStream();
       if (stream == null) {
         stream = conn.getInputStream();
@@ -93,11 +138,11 @@ public class UrlConnectionOperations extends Configured {
 
       }
     } catch (IOException e) {
-      throw NetUtils.wrapException(url.toString(), 
+      throw NetUtils.wrapException(url.toString(),
           url.getPort(), "localhost", 0, e);
 
     } catch (AuthenticationException e) {
-      throw new IOException("From " + url + ": " + e.toString(), e);
+      throw new IOException("From " + url + ": " + e, e);
 
     } finally {
       if (conn != null) {
@@ -105,7 +150,9 @@ public class UrlConnectionOperations extends Configured {
       }
     }
     uprateFaults(url.toString(), resultCode, body);
-    return body;
+    outcome.responseCode = resultCode;
+    outcome.data = body;
+    return outcome;
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/95d4acb6/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/ApplicationResource.java
----------------------------------------------------------------------
diff --git a/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/ApplicationResource.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/ApplicationResource.java
index 1bdf109..af310b1 100644
--- a/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/ApplicationResource.java
+++ b/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/ApplicationResource.java
@@ -46,12 +46,18 @@ import org.slf4j.LoggerFactory;
 
 import javax.inject.Singleton;
 import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
+import javax.ws.rs.HEAD;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
+
+import static javax.ws.rs.core.MediaType.*;
 import javax.ws.rs.core.UriInfo;
 import java.util.ArrayList;
 import java.util.List;
@@ -129,7 +135,7 @@ public class ApplicationResource extends AbstractSliderResource {
 
   @GET
   @Path("/")
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public List<String> getRoot() {
     return ROOT_ENTRIES;
   }
@@ -140,70 +146,70 @@ public class ApplicationResource extends AbstractSliderResource {
    */
   @GET
   @Path(MODEL)
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public List<String> getModel() {
     return MODEL_ENTRIES;
   }
 
   @GET
   @Path(MODEL_DESIRED)
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public AggregateConf getModelDesired() {
     return lookupAggregateConf(MODEL_DESIRED);
   }
   
   @GET
   @Path(MODEL_DESIRED_APPCONF)
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public ConfTree getModelDesiredAppconf() {
     return lookupConfTree(MODEL_DESIRED_APPCONF);
   }
 
   @GET
   @Path(MODEL_DESIRED_RESOURCES)
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public ConfTree getModelDesiredResources() {
     return lookupConfTree(MODEL_DESIRED_RESOURCES);
   }
   
   @GET
   @Path(MODEL_RESOLVED)
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public AggregateConf getModelResolved() {
     return lookupAggregateConf(MODEL_RESOLVED);
   }
 
   @GET
   @Path(MODEL_RESOLVED_APPCONF)
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public ConfTree getModelResolvedAppconf() {
     return lookupConfTree(MODEL_RESOLVED_APPCONF);
   }
 
   @GET
   @Path(MODEL_RESOLVED_RESOURCES)
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public ConfTree getModelResolvedResources() {
     return lookupConfTree(MODEL_RESOLVED_RESOURCES);
   }
   
   @GET
   @Path(LIVE)
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public List<String> getLive() {
     return LIVE_ENTRIES;
   }
 
   @GET
   @Path(LIVE_RESOURCES)
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public Object getLiveResources() {
     return lookupConfTree(LIVE_RESOURCES);
   }
   
   @GET
   @Path(LIVE_CONTAINERS)
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public Map<String, SerializedContainerInformation> getLiveContainers() {
     try {
       return (Map<String, SerializedContainerInformation>)cache.lookup(
@@ -215,7 +221,7 @@ public class ApplicationResource extends AbstractSliderResource {
 
   @GET
   @Path(LIVE_CONTAINERS + "/{containerId}")
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public SerializedContainerInformation getLiveContainer(
       @PathParam("containerId") String containerId) {
     try {
@@ -230,7 +236,7 @@ public class ApplicationResource extends AbstractSliderResource {
 
   @GET
   @Path(LIVE_COMPONENTS)
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public Map<String, SerializedComponentInformation> getLiveComponents() {
     try {
       return (Map<String, SerializedComponentInformation>) cache.lookup(
@@ -242,7 +248,7 @@ public class ApplicationResource extends AbstractSliderResource {
   
   @GET
   @Path(LIVE_COMPONENTS + "/{component}")
-  @Produces({MediaType.APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
   public SerializedComponentInformation getLiveComponent(
       @PathParam("component") String component) {
     try {
@@ -297,9 +303,46 @@ public class ApplicationResource extends AbstractSliderResource {
 
   @GET
   @Path(ACTION_PING)
-  @Produces({MediaType.APPLICATION_JSON})
-  public Object actionPing(@Context HttpServletRequest request,
+  @Produces({APPLICATION_JSON})
+  public Object actionPingGet(@Context HttpServletRequest request,
+      @Context UriInfo uriInfo) {
+    return new RestActionPing().ping(request, uriInfo, "");
+  }
+  
+  @POST
+  @Path(ACTION_PING)
+  @Produces({APPLICATION_JSON})
+  public Object actionPingPost(@Context HttpServletRequest request,
+      @Context UriInfo uriInfo,
+      String body) {
+    return new RestActionPing().ping(request, uriInfo, body);
+  }
+  
+  @PUT
+  @Path(ACTION_PING)
+  @Consumes({TEXT_PLAIN})
+  @Produces({APPLICATION_JSON})
+  public Object actionPingPut(@Context HttpServletRequest request,
+      @Context UriInfo uriInfo,
+      String body) {
+    return new RestActionPing().ping(request, uriInfo, body);
+  }
+  
+  @DELETE
+  @Path(ACTION_PING)
+  @Consumes({APPLICATION_JSON})
+  @Produces({APPLICATION_JSON})
+  public Object actionPingDelete(@Context HttpServletRequest request,
+      @Context UriInfo uriInfo) {
+    return new RestActionPing().ping(request, uriInfo, "");
+  }
+  
+  @HEAD
+  @Path(ACTION_PING)
+  @Produces({APPLICATION_JSON})
+  public Object actionPingHead(@Context HttpServletRequest request,
       @Context UriInfo uriInfo) {
-    return new RestActionPing().ping(request, uriInfo);
+    return new RestActionPing().ping(request, uriInfo, "");
   }
+  
 }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/95d4acb6/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/actions/RestActionPing.java
----------------------------------------------------------------------
diff --git a/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/actions/RestActionPing.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/actions/RestActionPing.java
index 6113e1e..65126ac 100644
--- a/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/actions/RestActionPing.java
+++ b/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/actions/RestActionPing.java
@@ -34,15 +34,17 @@ public class RestActionPing {
   public RestActionPing() {
   }
   
-  public Object ping(@Context HttpServletRequest request,
-      @Context UriInfo uriInfo) {
-    log.info("Ping {}", request.getMethod());
+  public Object ping(HttpServletRequest request, UriInfo uriInfo, String body) {
+    String verb = request.getMethod();
+    log.info("Ping {}", verb);
     PingResource pingResource = new PingResource();
     pingResource.time = System.currentTimeMillis();
+    pingResource.verb = verb;
+    pingResource.body = body;
     String text = 
         String.format(Locale.ENGLISH,
             "Ping verb %s received at %tc",
-            request.getMethod(), pingResource.time);
+            verb, pingResource.time);
     pingResource.text = text;
     return pingResource;
   }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/95d4acb6/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/resources/PingResource.java
----------------------------------------------------------------------
diff --git a/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/resources/PingResource.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/resources/PingResource.java
index 7e5396c..3f67c55 100644
--- a/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/resources/PingResource.java
+++ b/slider-core/src/main/java/org/apache/slider/server/appmaster/web/rest/application/resources/PingResource.java
@@ -26,13 +26,18 @@ import org.codehaus.jackson.map.annotate.JsonSerialize;
 public class PingResource {
   public long time;
   public String text;
+  public String verb;
+  public String body;
 
   @Override
   public String toString() {
+    
     final StringBuilder sb =
         new StringBuilder("PingResource{");
     sb.append("time=").append(time);
+    sb.append(", verb=").append(verb);
     sb.append(", text='").append(text).append('\'');
+    sb.append(", body='").append(body).append('\'');
     sb.append('}');
     return sb.toString();
   }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/95d4acb6/slider-core/src/test/groovy/org/apache/slider/agent/standalone/TestStandaloneAgentWeb.groovy
----------------------------------------------------------------------
diff --git a/slider-core/src/test/groovy/org/apache/slider/agent/standalone/TestStandaloneAgentWeb.groovy b/slider-core/src/test/groovy/org/apache/slider/agent/standalone/TestStandaloneAgentWeb.groovy
index 1018a02..d5be646 100644
--- a/slider-core/src/test/groovy/org/apache/slider/agent/standalone/TestStandaloneAgentWeb.groovy
+++ b/slider-core/src/test/groovy/org/apache/slider/agent/standalone/TestStandaloneAgentWeb.groovy
@@ -29,9 +29,13 @@ import org.apache.slider.api.types.SerializedContainerInformation
 import org.apache.slider.common.params.Arguments
 import org.apache.slider.core.conf.AggregateConf
 import org.apache.slider.core.conf.ConfTree
+import org.apache.slider.core.restclient.HttpOperationResponse
+import org.apache.slider.core.restclient.HttpVerb
 import org.apache.slider.server.appmaster.web.rest.application.ApplicationResource
 import org.apache.slider.server.appmaster.web.rest.application.resources.PingResource
 
+import javax.ws.rs.core.MediaType
+
 import static org.apache.slider.api.ResourceKeys.*
 import static org.apache.slider.api.StatusKeys.*
 import org.apache.slider.client.SliderClient
@@ -51,6 +55,7 @@ class TestStandaloneAgentWeb extends AgentMiniClusterTestBase {
   public static final int WEB_STARTUP_TIME = 30000
   public static final String TEST_GLOBAL_OPTION = "test.global.option"
   public static final String TEST_GLOBAL_OPTION_PRESENT = "present"
+  public static final byte[] NO_BYTES = new byte[0]
 
   @Test
   public void testStandaloneAgentWeb() throws Throwable {
@@ -187,9 +192,11 @@ class TestStandaloneAgentWeb extends AgentMiniClusterTestBase {
     assert amFullInfo.containers[0] == amContainerId
 
     testRESTModel(appmaster)
-    testPing(appmaster)
-    
     
+    // PUT & POST &c must go direct for now
+    String wsroot = appendToURL(realappmaster, SLIDER_CONTEXT_ROOT)
+    testPing(realappmaster)
+
   }
 
   public void testRESTModel(String appmaster) {
@@ -200,14 +207,14 @@ class TestStandaloneAgentWeb extends AgentMiniClusterTestBase {
         ApplicationResource.MODEL_ENTRIES)
 
     def unresolvedConf = fetchType(AggregateConf, appmaster, MODEL_DESIRED)
-    log.info "Unresolved \n$unresolvedConf"
+//    log.info "Unresolved \n$unresolvedConf"
     def unresolvedAppConf = unresolvedConf.appConfOperations
 
     def sam = "slider-appmaster"
     assert unresolvedAppConf.getComponentOpt(sam,
         TEST_GLOBAL_OPTION, "") == ""
     def resolvedConf = fetchType(AggregateConf, appmaster, MODEL_RESOLVED)
-    log.info "Resolved \n$resolvedConf"
+//    log.info "Resolved \n$resolvedConf"
     assert resolvedConf.appConfOperations.getComponentOpt(
         sam, TEST_GLOBAL_OPTION, "") == TEST_GLOBAL_OPTION_PRESENT
 
@@ -239,11 +246,41 @@ class TestStandaloneAgentWeb extends AgentMiniClusterTestBase {
   }
 
   public void testPing(String appmaster) {
-    describe "ping"
-    def pinged = fetchType(PingResource, appmaster, ACTION_PING)
-    log.info "Ping: $pinged"
+    // GET
+    String ping = appendToURL(appmaster, SLIDER_PATH_APPLICATION, ACTION_PING)
+    describe "ping to AM URL $appmaster, ping URL $ping"
+    def pinged = fetchType(PingResource, appmaster,  ACTION_PING +"?body=hello")
+    log.info "Ping GET: $pinged"
     
+    // POST
+    URL pingUrl = new URL(ping)
+
 
+    def message = "hello"
+    pingAction(HttpVerb.POST, pingUrl, message)
+    pingAction(HttpVerb.PUT, pingUrl, message)
+    pingAction(HttpVerb.DELETE, pingUrl, message)
+    pingAction(HttpVerb.HEAD, pingUrl, message)
+
+  }
+
+  public HttpOperationResponse pingAction(HttpVerb verb, URL pingUrl, String payload) {
+    def pinged
+    def outcome = connectionFactory.execHttpOperation(
+        verb,
+        pingUrl,
+        payload.bytes,
+        MediaType.TEXT_PLAIN)
+    byte[] bytes = outcome.data
+    if (verb.hasResponseBody()) {
+      assert bytes.length > 0, "0 bytes from ping $verb.verb"
+      pinged = deser(PingResource, bytes)
+      log.info "Ping $verb.verb: $pinged"
+      assert verb.verb == pinged.verb
+    } else {
+      assert bytes.length == 0, "${bytes.length} bytes of data from ping $verb.verb"
+    }
+    return outcome
   }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/95d4acb6/slider-core/src/test/groovy/org/apache/slider/test/SliderTestUtils.groovy
----------------------------------------------------------------------
diff --git a/slider-core/src/test/groovy/org/apache/slider/test/SliderTestUtils.groovy b/slider-core/src/test/groovy/org/apache/slider/test/SliderTestUtils.groovy
index b496d45..87e3206 100644
--- a/slider-core/src/test/groovy/org/apache/slider/test/SliderTestUtils.groovy
+++ b/slider-core/src/test/groovy/org/apache/slider/test/SliderTestUtils.groovy
@@ -29,7 +29,6 @@ import org.apache.hadoop.conf.Configuration
 import org.apache.hadoop.fs.FileStatus
 import org.apache.hadoop.fs.FileSystem as HadoopFS
 import org.apache.hadoop.fs.Path
-import org.apache.hadoop.hdfs.web.URLConnectionFactory
 import org.apache.hadoop.net.NetUtils
 import org.apache.hadoop.service.ServiceStateException
 import org.apache.hadoop.util.Shell
@@ -596,8 +595,8 @@ class SliderTestUtils extends Assert {
 
     log.info("Fetching HTTP content at " + path);
     URL url = new URL(path)
-    def bytes = connectionFactory.execGet(url, false)
-    String body = new String(bytes)
+    def outcome = connectionFactory.execGet(url)
+    String body = new String(outcome.data)
     return body;
   }
 
@@ -1144,16 +1143,34 @@ class SliderTestUtils extends Assert {
     }
   }
 
+  /**
+   * Get a web page and deserialize the supplied JSON into
+   * an instance of the specific class.
+   * @param clazz class to deserialize to
+   * @param appmaster URL to base AM
+   * @param subpath subpath under AM
+   * @return the parsed data type
+   */
   public <T> T fetchType(
       Class<T> clazz, String appmaster, String subpath) {
-    JsonSerDeser serDeser = new JsonSerDeser(clazz)
 
     def json = getWebPage(
         appmaster,
         RestPaths.SLIDER_PATH_APPLICATION + subpath)
+    return (T) deser(clazz, json);
+  }
+
+  public <T> T deser(Class<T> clazz, String json) {
+    JsonSerDeser serDeser = new JsonSerDeser(clazz)
     T ctree = (T) serDeser.fromJson(json)
     return ctree
   }
+
+  public <T> T deser(Class<T> clazz, byte[] data) {
+    JsonSerDeser serDeser = new JsonSerDeser(clazz)
+    T ctree = (T) serDeser.fromBytes(data)
+    return ctree
+  }
   
   public ConfTreeOperations fetchConfigTree(
       YarnConfiguration conf, String appmaster, String subpath) {