diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java index 56eba34..6b2a4fd 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java @@ -19,6 +19,9 @@ package org.apache.hadoop.yarn.server.resourcemanager.webapp; import java.io.IOException; +import java.lang.reflect.UndeclaredThrowableException; +import java.security.AccessControlException; +import java.security.PrivilegedExceptionAction; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; @@ -31,19 +34,27 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; import javax.ws.rs.GET; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authorize.AuthorizationException; import org.apache.hadoop.yarn.api.protocolrecords.GetApplicationsRequest; +import org.apache.hadoop.yarn.api.protocolrecords.KillApplicationRequest; +import org.apache.hadoop.yarn.api.protocolrecords.KillApplicationResponse; import org.apache.hadoop.yarn.api.records.ApplicationAccessType; import org.apache.hadoop.yarn.api.records.ApplicationId; import org.apache.hadoop.yarn.api.records.ApplicationReport; @@ -56,6 +67,8 @@ import org.apache.hadoop.yarn.exceptions.YarnRuntimeException; import org.apache.hadoop.yarn.factories.RecordFactory; import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider; +import org.apache.hadoop.yarn.server.resourcemanager.RMAuditLogger; +import org.apache.hadoop.yarn.server.resourcemanager.RMAuditLogger.AuditConstants; import org.apache.hadoop.yarn.server.resourcemanager.RMServerUtils; import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMApp; @@ -66,10 +79,10 @@ import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler; -import org.apache.hadoop.yarn.server.resourcemanager.security.QueueACLsManager; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.AppAttemptInfo; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.AppAttemptsInfo; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.AppInfo; +import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.AppState; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ApplicationStatisticsInfo; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.AppsInfo; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.CapacitySchedulerInfo; @@ -82,7 +95,6 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerInfo; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerTypeInfo; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.StatisticsItemInfo; -import org.apache.hadoop.yarn.server.security.ApplicationACLsManager; import org.apache.hadoop.yarn.util.ConverterUtils; import org.apache.hadoop.yarn.webapp.BadRequestException; import org.apache.hadoop.yarn.webapp.NotFoundException; @@ -584,4 +596,184 @@ public AppAttemptsInfo getAppAttempts(@PathParam("appid") String appId) { return appAttemptsInfo; } + + @GET + @Path("/apps/{appid}/state") + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public AppState getAppState(@Context HttpServletRequest hsr, + @PathParam("appid") String appId) throws AuthorizationException { + init(); + UserGroupInformation callerUGI = getCallerUserGroupInformation(hsr); + String userName = ""; + if (callerUGI != null) { + userName = callerUGI.getUserName(); + } + RMApp app = null; + try { + app = getRMAppForAppId(appId); + } catch (NotFoundException e) { + RMAuditLogger.logFailure(userName, AuditConstants.KILL_APP_REQUEST, + "UNKNOWN", "RMWebService", + "Trying to get state of an absent application " + appId); + throw e; + } + + AppState ret = new AppState(); + ret.setState(app.getState().toString()); + + return ret; + } + + // can't return POJO because we can't control the status code + // it's always set to 200 when we need to allow it to be set + // to 202 + + @PUT + @Path("/apps/{appid}/state") + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + public Response updateAppState(AppState targetState, + @Context HttpServletRequest hsr, @PathParam("appid") String appId) + throws AuthorizationException, YarnException, InterruptedException, + IOException { + + init(); + UserGroupInformation callerUGI = getCallerUserGroupInformation(hsr); + if (callerUGI == null) { + String msg = "Unable to obtain user name, user not authenticated"; + throw new AuthorizationException(msg); + } + + String userName = callerUGI.getUserName(); + RMApp app = null; + try { + app = getRMAppForAppId(appId); + } catch (NotFoundException e) { + RMAuditLogger.logFailure(userName, AuditConstants.KILL_APP_REQUEST, + "UNKNOWN", "RMWebService", "Trying to kill/move an absent application " + + appId); + throw e; + } + + if (!app.getState().toString().equals(targetState.getState())) { + // user is attempting to change state. right we only + // allow users to kill the app + + if (targetState.getState().equals(YarnApplicationState.KILLED.toString())) { + return killApp(app, callerUGI, hsr); + } + throw new BadRequestException("Only '" + + YarnApplicationState.KILLED.toString() + + "' is allowed as a target state."); + } + + AppInfo ret = + new AppInfo(app, hasAccess(app, hsr), hsr.getScheme() + "://"); + return Response.status(Status.OK).entity(ret).build(); + } + + protected Response killApp(RMApp app, UserGroupInformation callerUGI, + HttpServletRequest hsr) throws IOException, InterruptedException { + + if (app == null) { + throw new IllegalArgumentException("app cannot be null"); + } + String userName = callerUGI.getUserName(); + final ApplicationId appid = app.getApplicationId(); + KillApplicationResponse resp = null; + try { + resp = + callerUGI + .doAs(new PrivilegedExceptionAction() { + @Override + public KillApplicationResponse run() throws IOException, + YarnException { + KillApplicationRequest req = + KillApplicationRequest.newInstance(appid); + return rm.getClientRMService().forceKillApplication(req); + } + }); + } catch (UndeclaredThrowableException ue) { + // if the root cause is a permissions issue + // bubble that up to the user + if (ue.getCause() instanceof YarnException) { + YarnException ye = (YarnException) ue.getCause(); + if (ye.getCause() instanceof AccessControlException) { + String appId = app.getApplicationId().toString(); + String msg = + "Unauthorized attempt to kill appid " + appId + + " by remote user " + userName; + RMAuditLogger.logFailure(userName, AuditConstants.KILL_APP_REQUEST, + "UNKNOWN", "RMWebService", "Unauthorized attempt to kill appid " + + appId); + return Response.status(Status.FORBIDDEN).entity(msg).build(); + } else { + throw ue; + } + } else { + throw ue; + } + } + + AppInfo ret = + new AppInfo(app, hasAppAcess(app, callerUGI, + ApplicationAccessType.MODIFY_APP), hsr.getScheme() + "://"); + + if (resp.getIsKillCompleted()) { + RMAuditLogger.logSuccess(userName, AuditConstants.KILL_APP_REQUEST, + "RMWebService", app.getApplicationId()); + } else { + int endIndex = + hsr.getRequestURL().toString().length() - "/state".length(); + String appURL = hsr.getRequestURL().toString().substring(0, endIndex); + return Response.status(Status.ACCEPTED).entity(ret) + .header(HttpHeaders.LOCATION, appURL).build(); + } + + return Response.status(Status.OK).entity(ret).build(); + } + + private RMApp getRMAppForAppId(String appId) { + + if (appId == null || appId.isEmpty()) { + throw new NotFoundException("appId, " + appId + ", is empty or null"); + } + ApplicationId id; + try { + id = ConverterUtils.toApplicationId(recordFactory, appId); + } catch (NumberFormatException e) { + throw new NotFoundException("appId is invalid"); + } + if (id == null) { + throw new NotFoundException("appId is invalid"); + } + RMApp app = rm.getRMContext().getRMApps().get(id); + if (app == null) { + throw new NotFoundException("app with id: " + appId + " not found"); + } + return app; + } + + private UserGroupInformation getCallerUserGroupInformation( + HttpServletRequest hsr) { + + String remoteUser = hsr.getRemoteUser(); + UserGroupInformation callerUGI = null; + if (remoteUser != null) { + callerUGI = UserGroupInformation.createRemoteUser(remoteUser); + } + + return callerUGI; + } + + protected Boolean hasAppAcess(RMApp app, UserGroupInformation callerUGI, + ApplicationAccessType type) { + if (callerUGI != null) { + if (!this.rm.getApplicationACLsManager().checkAccess(callerUGI, type, + app.getUser(), app.getApplicationId())) { + return false; + } + } + return true; + } } diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/AppState.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/AppState.java new file mode 100644 index 0000000..e8f1cc3 --- /dev/null +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/AppState.java @@ -0,0 +1,46 @@ +/** + * 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.hadoop.yarn.server.resourcemanager.webapp.dao; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "appstate") +@XmlAccessorType(XmlAccessType.FIELD) +public class AppState { + + String state; + + public AppState() { + } + + public AppState(String state) { + this.state = state; + } + + public void setState(String state) { + this.state = state; + } + + public String getState() { + return this.state; + } + +} diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesApps.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesApps.java index 45b3803..4d1965f 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesApps.java +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesApps.java @@ -22,17 +22,29 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.IOException; import java.io.StringReader; +import java.io.StringWriter; +import java.util.Arrays; import java.util.Collection; +import java.util.Enumeration; +import java.util.Properties; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.server.AuthenticationFilter; +import org.apache.hadoop.security.authentication.server.PseudoAuthenticationHandler; import org.apache.hadoop.yarn.api.records.ContainerState; import org.apache.hadoop.yarn.api.records.FinalApplicationStatus; +import org.apache.hadoop.yarn.api.records.QueueACL; import org.apache.hadoop.yarn.api.records.YarnApplicationState; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.server.resourcemanager.MockAM; @@ -45,67 +57,139 @@ import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttempt; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttemptState; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacitySchedulerConfiguration; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler; import org.apache.hadoop.yarn.server.resourcemanager.security.QueueACLsManager; +import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.AppState; import org.apache.hadoop.yarn.server.security.ApplicationACLsManager; import org.apache.hadoop.yarn.webapp.GenericExceptionHandler; import org.apache.hadoop.yarn.webapp.WebServicesTestUtils; import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; +import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; +import org.xml.sax.SAXException; import com.google.inject.Guice; import com.google.inject.Injector; +import com.google.inject.Singleton; import com.google.inject.servlet.GuiceServletContextListener; import com.google.inject.servlet.ServletModule; +import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.ClientResponse.Status; import com.sun.jersey.api.client.UniformInterfaceException; import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.json.JSONJAXBContext; +import com.sun.jersey.api.json.JSONMarshaller; import com.sun.jersey.core.util.MultivaluedMapImpl; import com.sun.jersey.guice.spi.container.servlet.GuiceContainer; import com.sun.jersey.test.framework.JerseyTest; import com.sun.jersey.test.framework.WebAppDescriptor; +@RunWith(Parameterized.class) public class TestRMWebServicesApps extends JerseyTest { private static MockRM rm; private static final int CONTAINER_MB = 1024; - private Injector injector = Guice.createInjector(new ServletModule() { + private Injector injector; + private String webserviceUserName = "testuser"; + + public class GuiceServletConfig extends GuiceServletContextListener { + + @Override + protected Injector getInjector() { + return injector; + } + } + + /* + * Helper class to allow testing of RM web services which require authorization + * Add this class as a filter in the Guice injector for the MockRM + * + */ + + @Singleton + public static class TestRMCustomAuthFilter extends AuthenticationFilter { + + @Override + protected Properties getConfiguration(String configPrefix, FilterConfig filterConfig) throws ServletException { + Properties props = new Properties(); + Enumeration names = filterConfig.getInitParameterNames(); + while (names.hasMoreElements()) { + String name = (String) names.nextElement(); + if (name.startsWith(configPrefix)) { + String value = filterConfig.getInitParameter(name); + props.put(name.substring(configPrefix.length()), value); + } + } + props.put(AuthenticationFilter.AUTH_TYPE, "simple"); + props.put(PseudoAuthenticationHandler.ANONYMOUS_ALLOWED, "false"); + return props; + } + + } + + private class TestServletModule extends ServletModule { + public Configuration conf = new Configuration(); + boolean setAuthFilter = false; @Override protected void configureServlets() { bind(JAXBContextResolver.class); bind(RMWebServices.class); bind(GenericExceptionHandler.class); - Configuration conf = new Configuration(); conf.setInt(YarnConfiguration.RM_AM_MAX_ATTEMPTS, - YarnConfiguration.DEFAULT_RM_AM_MAX_ATTEMPTS); - conf.setClass(YarnConfiguration.RM_SCHEDULER, FifoScheduler.class, - ResourceScheduler.class); + YarnConfiguration.DEFAULT_RM_AM_MAX_ATTEMPTS); rm = new MockRM(conf); bind(ResourceManager.class).toInstance(rm); bind(RMContext.class).toInstance(rm.getRMContext()); bind(ApplicationACLsManager.class).toInstance( - rm.getApplicationACLsManager()); + rm.getApplicationACLsManager()); bind(QueueACLsManager.class).toInstance(rm.getQueueACLsManager()); + if(setAuthFilter) { + filter("/*").through(TestRMCustomAuthFilter.class); + } serve("/*").with(GuiceContainer.class); } - }); + } - public class GuiceServletConfig extends GuiceServletContextListener { + private Injector getNoAuthInjector() { + return Guice.createInjector(new TestServletModule() { + @Override + protected void configureServlets() { + super.configureServlets(); + } + }); + } - @Override - protected Injector getInjector() { - return injector; - } + private Injector getSimpleAuthInjector() { + return Guice.createInjector(new TestServletModule() { + @Override + protected void configureServlets() { + setAuthFilter = true; + conf.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, true); + // set the admin acls otherwise all users are considered admins + // and we can't test authorization + conf.setStrings(YarnConfiguration.YARN_ADMIN_ACL, "testuser1"); + super.configureServlets(); + } + }); + } + + @Parameters + public static Collection guiceConfigs() { + return Arrays.asList(new Object[][] { { 0 }, { 1 } }); } @Before @@ -114,12 +198,21 @@ public void setUp() throws Exception { super.setUp(); } - public TestRMWebServicesApps() { + public TestRMWebServicesApps(int run) { super(new WebAppDescriptor.Builder( - "org.apache.hadoop.yarn.server.resourcemanager.webapp") - .contextListenerClass(GuiceServletConfig.class) - .filterClass(com.google.inject.servlet.GuiceFilter.class) - .contextPath("jersey-guice-filter").servletPath("/").build()); + "org.apache.hadoop.yarn.server.resourcemanager.webapp") + .contextListenerClass(GuiceServletConfig.class) + .filterClass(com.google.inject.servlet.GuiceFilter.class) + .contextPath("jersey-guice-filter").servletPath("/").build()); + switch (run) { + case 0: + default: + injector = getNoAuthInjector(); + break; + case 1: + injector = getSimpleAuthInjector(); + break; + } } @Test @@ -158,9 +251,8 @@ public void testAppsXML() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); RMApp app1 = rm.submitApp(CONTAINER_MB, "testwordcount", "user1"); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").accept(MediaType.APPLICATION_XML) + ClientResponse response = + this.constructWebResource("apps").accept(MediaType.APPLICATION_XML) .get(ClientResponse.class); assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType()); String xml = response.getEntity(String.class); @@ -185,10 +277,8 @@ public void testAppsXMLMulti() throws JSONException, Exception { rm.submitApp(2048, "testwordcount2", "user1"); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); - - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").accept(MediaType.APPLICATION_XML) + ClientResponse response = + this.constructWebResource("apps").accept(MediaType.APPLICATION_XML) .get(ClientResponse.class); assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType()); String xml = response.getEntity(String.class); @@ -206,10 +296,9 @@ public void testAppsXMLMulti() throws JSONException, Exception { public void testAppsHelper(String path, RMApp app, String media) throws JSONException, Exception { - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path(path).accept(media).get(ClientResponse.class); + ClientResponse response = + this.constructWebResource(path).accept(media).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); assertEquals("incorrect number of elements", 1, json.length()); @@ -227,10 +316,8 @@ public void testAppsQueryState() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); RMApp app1 = rm.submitApp(CONTAINER_MB); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps") + ClientResponse response = this.constructWebResource("apps") .queryParam("state", YarnApplicationState.ACCEPTED.toString()) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); @@ -254,11 +341,10 @@ public void testAppsQueryStates() throws JSONException, Exception { amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); MultivaluedMapImpl params = new MultivaluedMapImpl(); params.add("states", YarnApplicationState.ACCEPTED.toString()); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParams(params) + ClientResponse response = + this.constructWebResource("apps").queryParams(params) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -270,12 +356,11 @@ public void testAppsQueryStates() throws JSONException, Exception { assertEquals("state not equal to ACCEPTED", "ACCEPTED", array .getJSONObject(0).getString("state")); - r = resource(); params = new MultivaluedMapImpl(); params.add("states", YarnApplicationState.ACCEPTED.toString()); params.add("states", YarnApplicationState.KILLED.toString()); - response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParams(params) + response = + this.constructWebResource("apps").queryParams(params) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); json = response.getEntity(JSONObject.class); @@ -303,11 +388,10 @@ public void testAppsQueryStatesComma() throws JSONException, Exception { amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); MultivaluedMapImpl params = new MultivaluedMapImpl(); params.add("states", YarnApplicationState.ACCEPTED.toString()); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParams(params) + ClientResponse response = + this.constructWebResource("apps").queryParams(params) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -319,12 +403,11 @@ public void testAppsQueryStatesComma() throws JSONException, Exception { assertEquals("state not equal to ACCEPTED", "ACCEPTED", array .getJSONObject(0).getString("state")); - r = resource(); params = new MultivaluedMapImpl(); params.add("states", YarnApplicationState.ACCEPTED.toString() + "," + YarnApplicationState.KILLED.toString()); - response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParams(params) + response = + this.constructWebResource("apps").queryParams(params) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); json = response.getEntity(JSONObject.class); @@ -348,10 +431,8 @@ public void testAppsQueryStatesNone() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); rm.submitApp(CONTAINER_MB); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); - - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps") + ClientResponse response = + this.constructWebResource("apps") .queryParam("states", YarnApplicationState.RUNNING.toString()) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); @@ -367,10 +448,9 @@ public void testAppsQueryStateNone() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); rm.submitApp(CONTAINER_MB); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps") + ClientResponse response = + this.constructWebResource("apps") .queryParam("state", YarnApplicationState.RUNNING.toString()) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); @@ -386,11 +466,9 @@ public void testAppsQueryStatesInvalid() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); rm.submitApp(CONTAINER_MB); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); try { - r.path("ws").path("v1").path("cluster").path("apps") - .queryParam("states", "INVALID_test") + this.constructWebResource("apps").queryParam("states", "INVALID_test") .accept(MediaType.APPLICATION_JSON).get(JSONObject.class); fail("should have thrown exception on invalid state query"); } catch (UniformInterfaceException ue) { @@ -423,11 +501,9 @@ public void testAppsQueryStateInvalid() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); rm.submitApp(CONTAINER_MB); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); try { - r.path("ws").path("v1").path("cluster").path("apps") - .queryParam("state", "INVALID_test") + this.constructWebResource("apps").queryParam("state", "INVALID_test") .accept(MediaType.APPLICATION_JSON).get(JSONObject.class); fail("should have thrown exception on invalid state query"); } catch (UniformInterfaceException ue) { @@ -460,10 +536,8 @@ public void testAppsQueryFinalStatus() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); RMApp app1 = rm.submitApp(CONTAINER_MB); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); - - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParam("finalStatus", FinalApplicationStatus.UNDEFINED.toString()) + ClientResponse response = this.constructWebResource("apps") + .queryParam("finalStatus", FinalApplicationStatus.UNDEFINED.toString()) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -483,10 +557,8 @@ public void testAppsQueryFinalStatusNone() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); rm.submitApp(CONTAINER_MB); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); - - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParam("finalStatus", FinalApplicationStatus.KILLED.toString()) + ClientResponse response = this.constructWebResource("apps") + .queryParam("finalStatus", FinalApplicationStatus.KILLED.toString()) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -501,10 +573,9 @@ public void testAppsQueryFinalStatusInvalid() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); rm.submitApp(CONTAINER_MB); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); try { - r.path("ws").path("v1").path("cluster").path("apps") + this.constructWebResource("apps") .queryParam("finalStatus", "INVALID_test") .accept(MediaType.APPLICATION_JSON).get(JSONObject.class); fail("should have thrown exception on invalid state query"); @@ -541,12 +612,8 @@ public void testAppsQueryUser() throws JSONException, Exception { rm.submitApp(CONTAINER_MB); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); - ClientResponse response = r - .path("ws") - .path("v1") - .path("cluster") - .path("apps") + + ClientResponse response = this.constructWebResource("apps") .queryParam("user", UserGroupInformation.getCurrentUser().getShortUserName()) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); @@ -563,16 +630,17 @@ public void testAppsQueryUser() throws JSONException, Exception { @Test public void testAppsQueryQueue() throws JSONException, Exception { + CapacitySchedulerConfiguration csconf = new CapacitySchedulerConfiguration(); + csconf.setFloat("yarn.scheduler.capacity.root.default.maximum-am-resource-percent", 1.0f); + rm.getResourceScheduler().reinitialize(csconf, rm.getRMContext()); rm.start(); MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); rm.submitApp(CONTAINER_MB); rm.submitApp(CONTAINER_MB); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); - - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParam("queue", "default") + ClientResponse response = + this.constructWebResource("apps").queryParam("queue", "default") .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -591,9 +659,8 @@ public void testAppsQueryLimit() throws JSONException, Exception { rm.submitApp(CONTAINER_MB); rm.submitApp(CONTAINER_MB); rm.submitApp(CONTAINER_MB); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParam("limit", "2") + ClientResponse response = + this.constructWebResource("apps").queryParam("limit", "2") .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -614,9 +681,8 @@ public void testAppsQueryStartBegin() throws JSONException, Exception { rm.submitApp(CONTAINER_MB); rm.submitApp(CONTAINER_MB); rm.submitApp(CONTAINER_MB); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParam("startedTimeBegin", String.valueOf(start)) + ClientResponse response = this.constructWebResource("apps") + .queryParam("startedTimeBegin", String.valueOf(start)) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -637,9 +703,8 @@ public void testAppsQueryStartBeginSome() throws JSONException, Exception { long start = System.currentTimeMillis(); Thread.sleep(1); rm.submitApp(CONTAINER_MB); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParam("startedTimeBegin", String.valueOf(start)) + ClientResponse response = this.constructWebResource("apps") + .queryParam("startedTimeBegin", String.valueOf(start)) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -660,9 +725,8 @@ public void testAppsQueryStartEnd() throws JSONException, Exception { rm.submitApp(CONTAINER_MB); rm.submitApp(CONTAINER_MB); rm.submitApp(CONTAINER_MB); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParam("startedTimeEnd", String.valueOf(end)) + ClientResponse response = this.constructWebResource("apps") + .queryParam("startedTimeEnd", String.valueOf(end)) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -682,9 +746,8 @@ public void testAppsQueryStartBeginEnd() throws JSONException, Exception { long end = System.currentTimeMillis(); Thread.sleep(1); rm.submitApp(CONTAINER_MB); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParam("startedTimeBegin", String.valueOf(start)) + ClientResponse response = this.constructWebResource("apps") + .queryParam("startedTimeBegin", String.valueOf(start)) .queryParam("startedTimeEnd", String.valueOf(end)) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); @@ -714,10 +777,8 @@ public void testAppsQueryFinishBegin() throws JSONException, Exception { 1, ContainerState.COMPLETE); rm.submitApp(CONTAINER_MB); rm.submitApp(CONTAINER_MB); - - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParam("finishedTimeBegin", String.valueOf(start)) + ClientResponse response = this.constructWebResource("apps") + .queryParam("finishedTimeBegin", String.valueOf(start)) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -747,9 +808,8 @@ public void testAppsQueryFinishEnd() throws JSONException, Exception { rm.submitApp(CONTAINER_MB); long end = System.currentTimeMillis(); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParam("finishedTimeEnd", String.valueOf(end)) + ClientResponse response = this.constructWebResource("apps") + .queryParam("finishedTimeEnd", String.valueOf(end)) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -781,9 +841,8 @@ public void testAppsQueryFinishBeginEnd() throws JSONException, Exception { rm.submitApp(CONTAINER_MB); long end = System.currentTimeMillis(); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParam("finishedTimeBegin", String.valueOf(start)) + ClientResponse response = this.constructWebResource("apps") + .queryParam("finishedTimeBegin", String.valueOf(start)) .queryParam("finishedTimeEnd", String.valueOf(end)) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); @@ -816,9 +875,8 @@ public void testAppsQueryAppTypes() throws JSONException, Exception { rm.submitApp(CONTAINER_MB, "", UserGroupInformation.getCurrentUser() .getShortUserName(), null, false, null, 2, null, "NON-YARN"); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParam("applicationTypes", "MAPREDUCE") + ClientResponse response = this.constructWebResource("apps") + .queryParam("applicationTypes", "MAPREDUCE") .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -830,12 +888,10 @@ public void testAppsQueryAppTypes() throws JSONException, Exception { assertEquals("MAPREDUCE", array.getJSONObject(0).getString("applicationType")); - r = resource(); - response = - r.path("ws").path("v1").path("cluster").path("apps") - .queryParam("applicationTypes", "YARN") - .queryParam("applicationTypes", "MAPREDUCE") - .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); + response = this.constructWebResource("apps") + .queryParam("applicationTypes", "YARN") + .queryParam("applicationTypes", "MAPREDUCE") + .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); json = response.getEntity(JSONObject.class); assertEquals("incorrect number of elements", 1, json.length()); @@ -850,11 +906,9 @@ public void testAppsQueryAppTypes() throws JSONException, Exception { && array.getJSONObject(0).getString("applicationType") .equals("MAPREDUCE"))); - r = resource(); - response = - r.path("ws").path("v1").path("cluster").path("apps") - .queryParam("applicationTypes", "YARN,NON-YARN") - .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); + response = this.constructWebResource("apps") + .queryParam("applicationTypes", "YARN,NON-YARN") + .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); json = response.getEntity(JSONObject.class); assertEquals("incorrect number of elements", 1, json.length()); @@ -869,9 +923,8 @@ public void testAppsQueryAppTypes() throws JSONException, Exception { && array.getJSONObject(0).getString("applicationType") .equals("NON-YARN"))); - r = resource(); - response = r.path("ws").path("v1").path("cluster") - .path("apps").queryParam("applicationTypes", "") + response = + this.constructWebResource("apps").queryParam("applicationTypes", "") .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); json = response.getEntity(JSONObject.class); @@ -881,12 +934,10 @@ public void testAppsQueryAppTypes() throws JSONException, Exception { array = apps.getJSONArray("app"); assertEquals("incorrect number of elements", 3, array.length()); - r = resource(); - response = - r.path("ws").path("v1").path("cluster").path("apps") - .queryParam("applicationTypes", "YARN,NON-YARN") - .queryParam("applicationTypes", "MAPREDUCE") - .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); + response = this.constructWebResource("apps") + .queryParam("applicationTypes", "YARN,NON-YARN") + .queryParam("applicationTypes", "MAPREDUCE") + .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); json = response.getEntity(JSONObject.class); assertEquals("incorrect number of elements", 1, json.length()); @@ -895,12 +946,10 @@ public void testAppsQueryAppTypes() throws JSONException, Exception { array = apps.getJSONArray("app"); assertEquals("incorrect number of elements", 3, array.length()); - r = resource(); - response = - r.path("ws").path("v1").path("cluster").path("apps") - .queryParam("applicationTypes", "YARN") - .queryParam("applicationTypes", "") - .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); + response = this.constructWebResource("apps") + .queryParam("applicationTypes", "YARN") + .queryParam("applicationTypes", "") + .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); json = response.getEntity(JSONObject.class); assertEquals("incorrect number of elements", 1, json.length()); @@ -911,11 +960,9 @@ public void testAppsQueryAppTypes() throws JSONException, Exception { assertEquals("YARN", array.getJSONObject(0).getString("applicationType")); - r = resource(); - response = - r.path("ws").path("v1").path("cluster").path("apps") - .queryParam("applicationTypes", ",,, ,, YARN ,, ,") - .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); + response = this.constructWebResource("apps") + .queryParam("applicationTypes", ",,, ,, YARN ,, ,") + .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); json = response.getEntity(JSONObject.class); assertEquals("incorrect number of elements", 1, json.length()); @@ -926,11 +973,9 @@ public void testAppsQueryAppTypes() throws JSONException, Exception { assertEquals("YARN", array.getJSONObject(0).getString("applicationType")); - r = resource(); - response = - r.path("ws").path("v1").path("cluster").path("apps") - .queryParam("applicationTypes", ",,, ,, ,, ,") - .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); + response = this.constructWebResource("apps") + .queryParam("applicationTypes", ",,, ,, ,, ,") + .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); json = response.getEntity(JSONObject.class); assertEquals("incorrect number of elements", 1, json.length()); @@ -939,11 +984,9 @@ public void testAppsQueryAppTypes() throws JSONException, Exception { array = apps.getJSONArray("app"); assertEquals("incorrect number of elements", 3, array.length()); - r = resource(); - response = - r.path("ws").path("v1").path("cluster").path("apps") - .queryParam("applicationTypes", "YARN, ,NON-YARN, ,,") - .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); + response = this.constructWebResource("apps") + .queryParam("applicationTypes", "YARN, ,NON-YARN, ,,") + .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); json = response.getEntity(JSONObject.class); assertEquals("incorrect number of elements", 1, json.length()); @@ -958,12 +1001,10 @@ public void testAppsQueryAppTypes() throws JSONException, Exception { && array.getJSONObject(0).getString("applicationType") .equals("NON-YARN"))); - r = resource(); - response = - r.path("ws").path("v1").path("cluster").path("apps") - .queryParam("applicationTypes", " YARN, , ,,,") - .queryParam("applicationTypes", "MAPREDUCE , ,, ,") - .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); + response = this.constructWebResource("apps") + .queryParam("applicationTypes", " YARN, , ,,,") + .queryParam("applicationTypes", "MAPREDUCE , ,, ,") + .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); json = response.getEntity(JSONObject.class); assertEquals("incorrect number of elements", 1, json.length()); @@ -1004,9 +1045,7 @@ public void testAppStatistics() throws JSONException, Exception { .getShortUserName(), null, false, null, 2, null, "OTHER"); // zero type, zero state - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("appstatistics") + ClientResponse response = this.constructWebResource("appstatistics") .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -1029,9 +1068,7 @@ public void testAppStatistics() throws JSONException, Exception { } // zero type, one state - r = resource(); - response = r.path("ws").path("v1").path("cluster") - .path("appstatistics") + response = this.constructWebResource("appstatistics") .queryParam("states", YarnApplicationState.ACCEPTED.toString()) .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); @@ -1046,9 +1083,7 @@ public void testAppStatistics() throws JSONException, Exception { assertEquals("2", statItems.getJSONObject(0).getString("count")); // one type, zero state - r = resource(); - response = r.path("ws").path("v1").path("cluster") - .path("appstatistics") + response = this.constructWebResource("appstatistics") .queryParam("applicationTypes", "MAPREDUCE") .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); @@ -1072,9 +1107,7 @@ public void testAppStatistics() throws JSONException, Exception { } // two types, zero state - r = resource(); - response = r.path("ws").path("v1").path("cluster") - .path("appstatistics") + response = this.constructWebResource("appstatistics") .queryParam("applicationTypes", "MAPREDUCE,OTHER") .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus()); @@ -1094,9 +1127,7 @@ public void testAppStatistics() throws JSONException, Exception { "org.apache.hadoop.yarn.webapp.BadRequestException", className); // one type, two states - r = resource(); - response = r.path("ws").path("v1").path("cluster") - .path("appstatistics") + response = this.constructWebResource("appstatistics") .queryParam("states", YarnApplicationState.FINISHED.toString() + "," + YarnApplicationState.ACCEPTED.toString()) .queryParam("applicationTypes", "MAPREDUCE") @@ -1120,9 +1151,8 @@ public void testAppStatistics() throws JSONException, Exception { assertEquals("1", statItem2.getString("count")); // invalid state - r = resource(); - response = r.path("ws").path("v1").path("cluster") - .path("appstatistics").queryParam("states", "wrong_state") + response = this.constructWebResource("appstatistics") + .queryParam("states", "wrong_state") .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus()); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); @@ -1182,12 +1212,10 @@ public void testInvalidApp() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); rm.submitApp(CONTAINER_MB); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); try { - r.path("ws").path("v1").path("cluster").path("apps") - .path("application_invalid_12").accept(MediaType.APPLICATION_JSON) - .get(JSONObject.class); + this.constructWebResource("apps", "application_invalid_12") + .accept(MediaType.APPLICATION_JSON).get(JSONObject.class); fail("should have thrown exception on invalid appid"); } catch (UniformInterfaceException ue) { ClientResponse response = ue.getResponse(); @@ -1218,12 +1246,10 @@ public void testNonexistApp() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); rm.submitApp(CONTAINER_MB, "testwordcount", "user1"); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); try { - r.path("ws").path("v1").path("cluster").path("apps") - .path("application_00000_0099").accept(MediaType.APPLICATION_JSON) - .get(JSONObject.class); + this.constructWebResource("apps", "application_00000_0099") + .accept(MediaType.APPLICATION_JSON).get(JSONObject.class); fail("should have thrown exception on invalid appid"); } catch (UniformInterfaceException ue) { ClientResponse response = ue.getResponse(); @@ -1251,9 +1277,9 @@ public void testNonexistApp() throws JSONException, Exception { public void testSingleAppsHelper(String path, RMApp app, String media) throws JSONException, Exception { - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").path(path).accept(media).get(ClientResponse.class); + ClientResponse response = + this.constructWebResource("apps", path).accept(media) + .get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); @@ -1267,9 +1293,8 @@ public void testSingleAppsXML() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); RMApp app1 = rm.submitApp(CONTAINER_MB, "testwordcount", "user1"); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").path(app1.getApplicationId().toString()) + ClientResponse response = + this.constructWebResource("apps", app1.getApplicationId().toString()) .accept(MediaType.APPLICATION_XML).get(ClientResponse.class); assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType()); String xml = response.getEntity(String.class); @@ -1288,7 +1313,7 @@ public void testSingleAppsXML() throws JSONException, Exception { public void verifyAppsXML(NodeList nodes, RMApp app) throws JSONException, Exception { - for (int i = 0; i < nodes.getLength(); i++) { + for (int i = 0; i < nodes.getLength(); i++) { Element element = (Element) nodes.item(i); verifyAppInfoGeneric(app, @@ -1442,12 +1467,10 @@ public void testInvalidAppAttempts() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); rm.submitApp(CONTAINER_MB); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); try { - r.path("ws").path("v1").path("cluster").path("apps") - .path("application_invalid_12").accept(MediaType.APPLICATION_JSON) - .get(JSONObject.class); + this.constructWebResource("apps", "application_invalid_12") + .accept(MediaType.APPLICATION_JSON).get(JSONObject.class); fail("should have thrown exception on invalid appid"); } catch (UniformInterfaceException ue) { ClientResponse response = ue.getResponse(); @@ -1478,12 +1501,10 @@ public void testNonexistAppAttempts() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); rm.submitApp(CONTAINER_MB, "testwordcount", "user1"); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); try { - r.path("ws").path("v1").path("cluster").path("apps") - .path("application_00000_0099").accept(MediaType.APPLICATION_JSON) - .get(JSONObject.class); + this.constructWebResource("apps", "application_00000_0099") + .accept(MediaType.APPLICATION_JSON).get(JSONObject.class); fail("should have thrown exception on invalid appid"); } catch (UniformInterfaceException ue) { ClientResponse response = ue.getResponse(); @@ -1511,10 +1532,9 @@ public void testNonexistAppAttempts() throws JSONException, Exception { public void testAppAttemptsHelper(String path, RMApp app, String media) throws JSONException, Exception { - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").path(path).path("appattempts").accept(media) - .get(ClientResponse.class); + ClientResponse response = + this.constructWebResource("apps", path, "appattempts").accept(media) + .get(ClientResponse.class); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); JSONObject json = response.getEntity(JSONObject.class); assertEquals("incorrect number of elements", 1, json.length()); @@ -1541,11 +1561,10 @@ public void testAppAttemptsXML() throws JSONException, Exception { MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); RMApp app1 = rm.submitApp(CONTAINER_MB, "testwordcount", user); amNodeManager.nodeHeartbeat(true); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("apps").path(app1.getApplicationId().toString()) - .path("appattempts").accept(MediaType.APPLICATION_XML) - .get(ClientResponse.class); + ClientResponse response = + this.constructWebResource("apps", app1.getApplicationId().toString(), + "appattempts").accept(MediaType.APPLICATION_XML) + .get(ClientResponse.class); assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType()); String xml = response.getEntity(String.class); @@ -1612,5 +1631,295 @@ public void verifyAppAttemptInfoGeneric(RMAppAttempt appAttempt, int id, + user)); } -} + private boolean isAuthorizationEnabled() { + return rm.getConfig().getBoolean(YarnConfiguration.YARN_ACL_ENABLE, false); + } + + private WebResource constructWebResource(WebResource r, String... paths) { + WebResource rt = r; + for (String path : paths) { + rt = rt.path(path); + } + if (isAuthorizationEnabled()) { + rt = rt.queryParam("user.name", webserviceUserName); + } + return rt; + } + + private WebResource constructWebResource(String... paths) { + WebResource r = resource(); + WebResource ws = r.path("ws").path("v1").path("cluster"); + return this.constructWebResource(ws, paths); + } + + @Test + public void testSingleAppState() throws Exception { + rm.start(); + MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); + String[] mediaTypes = + { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }; + for (String mediaType : mediaTypes) { + RMApp app = rm.submitApp(CONTAINER_MB, "", webserviceUserName); + amNodeManager.nodeHeartbeat(true); + ClientResponse response = + this + .constructWebResource("apps", app.getApplicationId().toString(), + "state").accept(mediaType).get(ClientResponse.class); + assertEquals(Status.OK, response.getClientResponseStatus()); + if (mediaType == MediaType.APPLICATION_JSON) { + assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); + JSONObject json = response.getEntity(JSONObject.class); + assertEquals("incorrect number of elements", 1, json.length()); + String state = json.getString("state"); + assertEquals("app state incorrect", "ACCEPTED", state); + + } else if (mediaType == MediaType.APPLICATION_XML) { + assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType()); + String xml = response.getEntity(String.class); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader(xml)); + Document dom = db.parse(is); + NodeList nodes = dom.getElementsByTagName("appstate"); + assertEquals("incorrect number of elements", 1, nodes.getLength()); + Element element = (Element) nodes.item(0); + String state = WebServicesTestUtils.getXmlString(element, "state"); + assertEquals("app state incorrect", "ACCEPTED", state); + } + } + rm.stop(); + } + + @Test(timeout = 90000) + public void testSingleAppKill() throws Exception { + rm.start(); + MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); + String[] mediaTypes = + { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }; + MediaType[] contentTypes = + { MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE }; + for (String mediaType : mediaTypes) { + for (MediaType contentType : contentTypes) { + RMApp app = rm.submitApp(CONTAINER_MB, "", webserviceUserName); + amNodeManager.nodeHeartbeat(true); + + ClientResponse response = + this + .constructWebResource("apps", app.getApplicationId().toString(), + "state").accept(mediaType).get(ClientResponse.class); + AppState targetState = + new AppState(YarnApplicationState.KILLED.toString()); + + Object entity; + if (contentType == MediaType.APPLICATION_JSON_TYPE) { + entity = appStateToJSON(targetState); + } else { + entity = targetState; + } + response = + this + .constructWebResource("apps", app.getApplicationId().toString(), + "state").entity(entity, contentType).accept(mediaType) + .put(ClientResponse.class); + + if (!isAuthorizationEnabled()) { + assertEquals(Status.UNAUTHORIZED, response.getClientResponseStatus()); + continue; + } + assertEquals(Status.ACCEPTED, response.getClientResponseStatus()); + if (mediaType == MediaType.APPLICATION_JSON) { + verifyAppKillJson(response, RMAppState.ACCEPTED); + } else { + verifyAppKillXML(response, RMAppState.ACCEPTED); + } + + String locationHeaderValue = + response.getHeaders().getFirst(HttpHeaders.LOCATION); + Client c = Client.create(); + WebResource tmp = c.resource(locationHeaderValue); + if (isAuthorizationEnabled()) { + tmp = tmp.queryParam("user.name", webserviceUserName); + } + response = tmp.get(ClientResponse.class); + assertEquals(Status.OK, response.getClientResponseStatus()); + assertTrue(locationHeaderValue.endsWith("/ws/v1/cluster/apps/" + + app.getApplicationId().toString())); + + while (true) { + Thread.sleep(100); + response = + this + .constructWebResource("apps", + app.getApplicationId().toString(), "state").accept(mediaType) + .entity(entity, contentType).put(ClientResponse.class); + assertTrue((response.getClientResponseStatus() == Status.ACCEPTED) + || (response.getClientResponseStatus() == Status.OK)); + if (response.getClientResponseStatus() == Status.OK) { + if (mediaType == MediaType.APPLICATION_JSON) { + verifyAppKillJson(response, RMAppState.KILLED); + } else { + verifyAppKillXML(response, RMAppState.KILLED); + } + break; + } + } + } + } + rm.stop(); + return; + } + + @Test + public void testSingleAppKillInvalidState() throws Exception { + rm.start(); + MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); + + String[] mediaTypes = + { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }; + MediaType[] contentTypes = + { MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE }; + String[] targetStates = + { YarnApplicationState.FINISHED.toString(), "blah" }; + + for (String mediaType : mediaTypes) { + for (MediaType contentType : contentTypes) { + for (String targetStateString : targetStates) { + RMApp app = rm.submitApp(CONTAINER_MB, "", webserviceUserName); + amNodeManager.nodeHeartbeat(true); + ClientResponse response; + AppState targetState = new AppState(targetStateString); + Object entity; + if (contentType == MediaType.APPLICATION_JSON_TYPE) { + entity = appStateToJSON(targetState); + } else { + entity = targetState; + } + response = + this + .constructWebResource("apps", + app.getApplicationId().toString(), "state") + .entity(entity, contentType).accept(mediaType) + .put(ClientResponse.class); + + if (!isAuthorizationEnabled()) { + assertEquals(Status.UNAUTHORIZED, + response.getClientResponseStatus()); + continue; + } + assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus()); + } + } + } + + rm.stop(); + return; + } + + private static String appStateToJSON(AppState state) throws Exception { + StringWriter sw = new StringWriter(); + JSONJAXBContext ctx = new JSONJAXBContext(AppState.class); + JSONMarshaller jm = ctx.createJSONMarshaller(); + jm.marshallToJSON(state, sw); + return sw.toString(); + } + + protected static void verifyAppKillJson(ClientResponse response, + RMAppState state) throws JSONException { + + assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); + JSONObject json = response.getEntity(JSONObject.class); + assertEquals("incorrect number of elements", 1, json.length()); + JSONObject appObj = json.getJSONObject("app"); + assertEquals("app state incorrect", state.toString(), + appObj.getString("state")); + return; + } + + protected static void verifyAppKillXML(ClientResponse response, + RMAppState appState) throws ParserConfigurationException, IOException, + SAXException { + assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType()); + String xml = response.getEntity(String.class); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + InputSource is = new InputSource(); + is.setCharacterStream(new StringReader(xml)); + Document dom = db.parse(is); + NodeList nodes = dom.getElementsByTagName("app"); + assertEquals("incorrect number of elements", 1, nodes.getLength()); + Element element = (Element) nodes.item(0); + String state = WebServicesTestUtils.getXmlString(element, "state"); + assertEquals("app state incorrect", appState.toString(), state); + return; + } + + @Test(timeout = 30000) + public void testSingleAppKillUnauthorized() throws Exception { + CapacitySchedulerConfiguration csconf = new CapacitySchedulerConfiguration(); + csconf.setAcl("root", QueueACL.ADMINISTER_QUEUE, "someuser"); + csconf.setAcl("root.default", QueueACL.ADMINISTER_QUEUE, "someuser"); + rm.getResourceScheduler().reinitialize(csconf, rm.getRMContext()); + rm.start(); + MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); + + String[] mediaTypes = + { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }; + for (String mediaType : mediaTypes) { + RMApp app = rm.submitApp(CONTAINER_MB, "test", "someuser"); + amNodeManager.nodeHeartbeat(true); + ClientResponse response = + this + .constructWebResource("apps", app.getApplicationId().toString(), + "state").accept(mediaType).get(ClientResponse.class); + AppState info = response.getEntity(AppState.class); + info.setState(YarnApplicationState.KILLED.toString()); + + response = + this + .constructWebResource("apps", app.getApplicationId().toString(), + "state").accept(mediaType) + .entity(info, MediaType.APPLICATION_XML).put(ClientResponse.class); + if (!isAuthorizationEnabled()) { + assertEquals(Status.UNAUTHORIZED, response.getClientResponseStatus()); + } else { + assertEquals(Status.FORBIDDEN, response.getClientResponseStatus()); + } + } + rm.stop(); + return; + + } + + @Test + public void testSingleAppKillInvalidId() throws Exception { + rm.start(); + MockNM amNodeManager = rm.registerNode("127.0.0.1:1234", 2048); + amNodeManager.nodeHeartbeat(true); + String[] testAppIds = { "application_1391705042196_0001", "random_string" }; + for (String testAppId : testAppIds) { + AppState info = new AppState("KILLED"); + ClientResponse response = + this.constructWebResource("apps", testAppId, "state") + .accept(MediaType.APPLICATION_XML) + .entity(info, MediaType.APPLICATION_XML).put(ClientResponse.class); + if (!isAuthorizationEnabled()) { + assertEquals(Status.UNAUTHORIZED, response.getClientResponseStatus()); + continue; + } + assertEquals(Status.NOT_FOUND, response.getClientResponseStatus()); + } + rm.stop(); + return; + } + + @After + @Override + public void tearDown() throws Exception { + if (rm != null) { + rm.stop(); + } + super.tearDown(); + } +} diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/ResourceManagerRest.apt.vm hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/ResourceManagerRest.apt.vm index a8f4f00..c36a204 100644 --- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/ResourceManagerRest.apt.vm +++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/ResourceManagerRest.apt.vm @@ -1564,6 +1564,367 @@ _01_000001 +---+ +* Cluster Application State API + + With the application state API, you can query the state of a submitted app as well kill a running app by modifying the state of a running app using a PUT request with the state set to "KILLED". To perform the PUT operation, authentication has to be setup for the RM web services. In addition, you must be authorized to kill the app. Currently you can only change the state to "KILLED"; an attempt to change the state to any other results in a 400 error response. Examples of the unauthorized and bad request errors are below. When you carry out a successful PUT, the iniital response may be a 202. You can confirm that the app is killed by repeating the PUT request until you get a 200, querying the state using the GET method or querying for app information and checking the state. In the examples below, we repeat the PUT request and get a 200 response. + +** URI + +----- + * http:///ws/v1/cluster/apps/{appid}/state +----- + +** HTTP Operations Supported + +------ + * GET + * PUT +------ + +** Query Parameters Supported + +------ + None +------ + +** Elements of object + + When you make a request for the state of an app, the information returned has the following fields + +*---------------+--------------+-------------------------------+ +|| Item || Data Type || Description | +*---------------+--------------+-------------------------------+ +| state | string | The application state - can be one of "NEW", "NEW_SAVING", "SUBMITTED", "ACCEPTED", "RUNNING", "FINISHED", "FAILED", "KILLED" | +*---------------+--------------+--------------------------------+ + + +** Response Examples + + <> + + HTTP Request + +----- + GET http:///ws/v1/cluster/apps/application_1399397633663_0003/state +----- + + Response Header: + ++---+ +HTTP/1.1 200 OK +Content-Type: application/json +Transfer-Encoding: chunked +Server: Jetty(6.1.26) ++---+ + + Response Body: + ++---+ +{ + "state":"ACCEPTED" +} ++---+ + + HTTP Request + +----- + PUT http:///ws/v1/cluster/apps/application_1399397633663_0003/state +---- + + Request Body: + ++---+ +{ + "state":"KILLED" +} ++---+ + + Response Header: + ++---+ +HTTP/1.1 202 Accepted +Content-Type: application/json +Transfer-Encoding: chunked +Location: http:///ws/v1/cluster/apps/application_1399397633663_0003 +Server: Jetty(6.1.26) ++---+ + + Response Body: + ++---+ +{ + "app" : { + "id" : "application_1399397633663_0003", + "user" : "testuser", + "name" : "", + "queue" : "default", + "state" : "ACCEPTED", + "finalStatus" : "UNDEFINED", + "progress" : 0.0, + "trackingUI" : "UNASSIGNED", + "diagnostics" : "", + "clusterId" : 1399397633663, + "applicationType" : "YARN", + "applicationTags" : "", + "startedTime" : 1399399171088, + "finishedTime" : 0, + "elapsedTime" : 3001, + "amContainerLogs" : "http://127.0.0.1:2/node/containerlogs/container_1399399170898_0001_01_000001/testuser", + "amHostHttpAddress" : "127.0.0.1:2", + "allocatedMB" : 1024, + "allocatedVCores" : 1, + "runningContainers" : 1 + } +} ++---+ + +----- + PUT http:///ws/v1/cluster/apps/application_1399397633663_0003/state +---- + + Request Body: + ++---+ +{ + "state":"KILLED" +} ++---+ + + Response Header: + ++---+ +HTTP/1.1 200 OK +Content-Type: application/json +Transfer-Encoding: chunked +Server: Jetty(6.1.26) ++---+ + + Response Body: + ++---+ +{ + "app" : { + "id" : "application_1399397633663_0003", + "user" : "testuser", + "name" : "", + "queue" : "default", + "state" : "KILLED", + "finalStatus" : "KILLED", + "progress" : 0.0, + "trackingUI" : "History", + "trackingUrl" : "http://host.domain.come:port/cluster/app/application_1399397633663_0003", + "diagnostics" : "Application killed by user.", + "clusterId" : 1399397633663, + "applicationType" : "YARN", + "applicationTags" : "", + "startedTime" : 1399400153117, + "finishedTime" : 1399400155892, + "elapsedTime" : 2775, + "amContainerLogs" : "http://host.domain.com:port/node/containerlogs/container_1399400153011_0001_01_000001/testuser", + "amHostHttpAddress" : "host.domain.com:port", + "allocatedMB" : 0, + "allocatedVCores" : 0, + "runningContainers" : 0 + } +} ++---+ + + <> + + HTTP Request + +----- + GET http:///ws/v1/cluster/apps/application_1399397633663_0003/state +----- + + Response Header: + ++---+ +HTTP/1.1 200 OK +Content-Type: application/xml +Content-Length: 99 +Server: Jetty(6.1.26) ++---+ + + Response Body: + ++---+ + + + ACCEPTED + ++---+ + + HTTP Request + +----- + PUT http:///ws/v1/cluster/apps/application_1399397633663_0003/state +---- + + Request Body: + ++---+ + + + KILLED + ++---+ + + Response Header: + ++---+ +HTTP/1.1 202 Accepted +Content-Type: application/json +Content-Length: 794 +Location: http:///ws/v1/cluster/apps/application_1399397633663_0003 +Server: Jetty(6.1.26) ++---+ + + Response Body: + ++---+ + + + application_1399397633663_0003 + testuser + + default + ACCEPTED + UNDEFINED + 0.0 + UNASSIGNED + + 1399397633663 + YARN + + 1399399176143 + 0 + 2018 + http://host.domain.com:port/node/containerlogs/container_1399399170898_0003_01_000001/testuser + host.domain.com:port + 1024 + 1 + 1 + ++---+ + + HTTP Request + +----- + PUT http:///ws/v1/cluster/apps/application_1399397633663_0003/state +---- + + Request Body: + ++---+ + + + KILLED + ++---+ + + Response Header: + ++---+ +HTTP/1.1 200 OK +Content-Type: application/xml +Content-Length: 917 +Server: Jetty(6.1.26) ++---+ + + Response Body: + ++---+ + + + application_1399397633663_0003 + testuser + + default + KILLED + KILLED + 0.0 + History + http://host.domain.com:port/cluster/app/application_1399397633663_0003 + Application killed by user. + 1399397633663 + YARN + + 1399400158298 + 1399400160315 + 2017 + http://host.domain.com:port/node/containerlogs/container_1399400153011_0003_01_000001/testuser + host.domain.com:port + 0 + 0 + 0 + ++---+ + + <> + + HTTP Request + +----- + PUT http:///ws/v1/cluster/apps/application_1399397633663_0003/state +---- + + Request Body: + ++---+ + + + KILLED + ++---+ + + Response Header: + ++---+ +HTTP/1.1 403 Unauthorized +Content-Type: application/json +Transfer-Encoding: chunked +Server: Jetty(6.1.26) ++---+ + + + <> + + HTTP Request + +----- + PUT http:///ws/v1/cluster/apps/application_1399397633663_0003/state +---- + + Request Body: + ++---+ + + + RUNNING + ++---+ + + Response Header: + ++---+ +HTTP/1.1 400 +Content-Length: 295 +Content-Type: application/xml +Server: Jetty(6.1.26) ++---+ + + Response Body: + ++---+ + + + BadRequestException + java.lang.Exception: Only 'KILLED' is allowed as a target state. + org.apache.hadoop.yarn.webapp.BadRequestException + ++---+ + * Cluster Application Attempts API With the application attempts API, you can obtain a collection of resources that represent an application attempt. When you run a GET operation on this resource, you obtain a collection of App Attempt Objects.