Browse Source

Merge -c 1225463 from trunk to branch-0.23 to fix MAPREDUCE-3547.

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/branch-0.23@1225466 13f79535-47bb-0310-9956-ffa450edef68
Arun Murthy 13 năm trước cách đây
mục cha
commit
c0e9c8bf5e
17 tập tin đã thay đổi với 3786 bổ sung914 xóa
  1. 3 0
      hadoop-mapreduce-project/CHANGES.txt
  2. 79 0
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/webapp/WebServicesTestUtils.java
  3. 12 6
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/NMWebServices.java
  4. 13 3
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/dao/ContainerInfo.java
  5. 83 0
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/MockApp.java
  6. 120 0
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/MockContainer.java
  7. 361 0
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServices.java
  8. 607 0
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServicesApps.java
  9. 481 0
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServicesContainers.java
  10. 2 1
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/CapacitySchedulerInfo.java
  11. 4 3
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/CapacitySchedulerQueueInfo.java
  12. 4 3
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/ClusterInfo.java
  13. 7 3
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/MockRM.java
  14. 337 895
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServices.java
  15. 756 0
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesApps.java
  16. 316 0
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesCapacitySched.java
  17. 601 0
      hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesNodes.java

+ 3 - 0
hadoop-mapreduce-project/CHANGES.txt

@@ -87,6 +87,9 @@ Release 0.23.1 - Unreleased
     MAPREDUCE-3391. Making a trivial change to correct a log message in
     DistributedShell app's AM. (Subroto Sanyal via vinodkv)
 
+    MAPREDUCE-3547. Added a bunch of unit tests for the the RM/NM webservices.
+    (Thomas Graves via acmurthy)
+
   OPTIMIZATIONS
 
     MAPREDUCE-3567. Extraneous JobConf objects in AM heap. (Vinod Kumar

+ 79 - 0
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/test/java/org/apache/hadoop/yarn/webapp/WebServicesTestUtils.java

@@ -0,0 +1,79 @@
+/**
+ * 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.webapp;
+
+import static org.junit.Assert.assertTrue;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+public class WebServicesTestUtils {
+
+  public static long getXmlLong(Element element, String name) {
+    String val = getXmlString(element, name);
+    return Long.parseLong(val);
+  }
+
+  public static int getXmlInt(Element element, String name) {
+    String val = getXmlString(element, name);
+    return Integer.parseInt(val);
+  }
+
+  public static Boolean getXmlBoolean(Element element, String name) {
+    String val = getXmlString(element, name);
+    return Boolean.parseBoolean(val);
+  }
+
+  public static float getXmlFloat(Element element, String name) {
+    String val = getXmlString(element, name);
+    return Float.parseFloat(val);
+  }
+
+  public static String getXmlString(Element element, String name) {
+    NodeList id = element.getElementsByTagName(name);
+    Element line = (Element) id.item(0);
+    Node first = line.getFirstChild();
+    // handle empty <key></key>
+    if (first == null) {
+      return "";
+    }
+    String val = first.getNodeValue();
+    if (val == null) {
+      return "";
+    }
+    return val;
+  }
+
+  public static String getXmlAttrString(Element element, String name) {
+    Attr at = element.getAttributeNode(name);
+    if (at != null) {
+      return at.getValue();
+    }
+    return null;
+  }
+
+  public static void checkStringMatch(String print, String expected, String got) {
+    assertTrue(
+        print + " doesn't match, got: " + got + " expected: " + expected,
+        got.matches(expected));
+  }
+
+}

+ 12 - 6
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/NMWebServices.java

@@ -42,6 +42,7 @@ import org.apache.hadoop.yarn.server.nodemanager.webapp.dao.ContainerInfo;
 import org.apache.hadoop.yarn.server.nodemanager.webapp.dao.ContainersInfo;
 import org.apache.hadoop.yarn.server.nodemanager.webapp.dao.NodeInfo;
 import org.apache.hadoop.yarn.util.ConverterUtils;
+import org.apache.hadoop.yarn.webapp.BadRequestException;
 import org.apache.hadoop.yarn.webapp.NotFoundException;
 import org.apache.hadoop.yarn.webapp.WebApp;
 
@@ -92,12 +93,16 @@ public class NMWebServices {
 
       AppInfo appInfo = new AppInfo(entry.getValue());
       if (stateQuery != null && !stateQuery.isEmpty()) {
-        ApplicationState state = ApplicationState.valueOf(stateQuery);
+        ApplicationState.valueOf(stateQuery);
         if (!appInfo.getState().equalsIgnoreCase(stateQuery)) {
           continue;
         }
       }
-      if (userQuery != null && !userQuery.isEmpty()) {
+      if (userQuery != null) {
+        if (userQuery.isEmpty()) {
+          String msg = "Error: You must specify a non-empty string for the user";
+          throw new BadRequestException(msg);
+        }
         if (!appInfo.getUser().toString().equals(userQuery)) {
           continue;
         }
@@ -146,11 +151,12 @@ public class NMWebServices {
   @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
   public ContainerInfo getNodeContainer(@PathParam("containerid") String id) {
     ContainerId containerId = null;
-    containerId = ConverterUtils.toContainerId(id);
-    if (containerId == null) {
-      throw new NotFoundException("container with id, " + id
-          + ", is empty or null");
+    try {
+      containerId = ConverterUtils.toContainerId(id);
+    } catch (Exception e) {
+      throw new BadRequestException("invalid container id, " + id);
     }
+
     Container container = nmContext.getContainers().get(containerId);
     if (container == null) {
       throw new NotFoundException("container with id, " + id + ", not found");

+ 13 - 3
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/dao/ContainerInfo.java

@@ -27,6 +27,7 @@ import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlTransient;
 
 import org.apache.hadoop.yarn.api.records.ContainerStatus;
+import org.apache.hadoop.yarn.api.records.Resource;
 import org.apache.hadoop.yarn.conf.YarnConfiguration;
 import org.apache.hadoop.yarn.server.nodemanager.Context;
 import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
@@ -56,7 +57,7 @@ public class ContainerInfo {
   }
 
   public ContainerInfo(final Context nmContext, final Container container,
-      final String requestUri, final String pathPrefix) {
+       String requestUri, String pathPrefix) {
 
     this.id = container.getContainerID().toString();
     this.nodeId = nmContext.getNodeId().toString();
@@ -71,10 +72,19 @@ public class ContainerInfo {
     }
 
     this.user = container.getUser();
-    this.totalMemoryNeededMB = container.getLaunchContext().getResource()
-        .getMemory();
+    Resource res = container.getLaunchContext().getResource();
+    if (res != null) {
+      this.totalMemoryNeededMB = res.getMemory();
+    }
     this.containerLogsShortLink = ujoin("containerlogs", this.id,
         container.getUser());
+
+    if (requestUri == null) {
+      requestUri = "";
+    }
+    if (pathPrefix == null) {
+      pathPrefix = "";
+    }
     this.containerLogsLink = join(requestUri, pathPrefix,
         this.containerLogsShortLink);
   }

+ 83 - 0
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/MockApp.java

@@ -0,0 +1,83 @@
+/**
+ * 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.nodemanager;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.yarn.api.records.ApplicationId;
+import org.apache.hadoop.yarn.api.records.ContainerId;
+import org.apache.hadoop.yarn.factories.RecordFactory;
+import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.Application;
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.ApplicationEvent;
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.ApplicationState;
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
+import org.apache.hadoop.yarn.util.BuilderUtils;
+
+public class MockApp implements Application {
+
+  final String user;
+  final ApplicationId appId;
+  Map<ContainerId, Container> containers = new HashMap<ContainerId, Container>();
+  ApplicationState appState;
+  Application app;
+
+  public MockApp(int uniqId) {
+    this("mockUser", 1234, uniqId);
+  }
+
+  public MockApp(String user, long clusterTimeStamp, int uniqId) {
+    super();
+    this.user = user;
+    // Add an application and the corresponding containers
+    RecordFactory recordFactory = RecordFactoryProvider
+        .getRecordFactory(new Configuration());
+    this.appId = BuilderUtils.newApplicationId(recordFactory, clusterTimeStamp,
+        uniqId);
+    appState = ApplicationState.NEW;
+  }
+
+  public void setState(ApplicationState state) {
+    this.appState = state;
+  }
+
+  public String getUser() {
+    return user;
+  }
+
+  public Map<ContainerId, Container> getContainers() {
+    return containers;
+  }
+
+  public ApplicationId getAppId() {
+    return appId;
+  }
+
+  public ApplicationState getApplicationState() {
+    return appState;
+  }
+
+  public void handle(ApplicationEvent event) {}
+
+}

+ 120 - 0
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/MockContainer.java

@@ -0,0 +1,120 @@
+/**
+ * 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.nodemanager;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.security.Credentials;
+import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
+import org.apache.hadoop.yarn.api.records.ApplicationId;
+import org.apache.hadoop.yarn.api.records.ContainerId;
+import org.apache.hadoop.yarn.api.records.ContainerLaunchContext;
+import org.apache.hadoop.yarn.api.records.ContainerStatus;
+import org.apache.hadoop.yarn.event.Dispatcher;
+import org.apache.hadoop.yarn.factories.RecordFactory;
+import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerEvent;
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.ContainerState;
+import org.apache.hadoop.yarn.util.BuilderUtils;
+
+public class MockContainer implements Container {
+
+  private ContainerId id;
+  private ContainerState state;
+  private String user;
+  private ContainerLaunchContext launchContext;
+  private final Map<Path, String> resource = new HashMap<Path, String>();
+  private RecordFactory recordFactory;
+
+  public MockContainer(ApplicationAttemptId appAttemptId,
+      Dispatcher dispatcher, Configuration conf, String user,
+      ApplicationId appId, int uniqId) {
+
+    this.user = user;
+    this.recordFactory = RecordFactoryProvider.getRecordFactory(conf);
+    this.id = BuilderUtils.newContainerId(recordFactory, appId, appAttemptId,
+        uniqId);
+    this.launchContext = recordFactory
+        .newRecordInstance(ContainerLaunchContext.class);
+    launchContext.setContainerId(id);
+    launchContext.setUser(user);
+    this.state = ContainerState.NEW;
+
+  }
+
+  public void setState(ContainerState state) {
+    this.state = state;
+  }
+
+  @Override
+  public ContainerId getContainerID() {
+    return id;
+  }
+
+  @Override
+  public String getUser() {
+    return user;
+  }
+
+  @Override
+  public ContainerState getContainerState() {
+    return state;
+  }
+
+  @Override
+  public ContainerLaunchContext getLaunchContext() {
+    return launchContext;
+  }
+
+  @Override
+  public Credentials getCredentials() {
+    return null;
+  }
+
+  @Override
+  public Map<Path, String> getLocalizedResources() {
+    return resource;
+  }
+
+  @Override
+  public ContainerStatus cloneAndGetContainerStatus() {
+    ContainerStatus containerStatus = recordFactory
+        .newRecordInstance(ContainerStatus.class);
+    containerStatus
+        .setState(org.apache.hadoop.yarn.api.records.ContainerState.RUNNING);
+    containerStatus.setContainerId(this.launchContext.getContainerId());
+    containerStatus.setDiagnostics("testing");
+    containerStatus.setExitStatus(0);
+    return containerStatus;
+  }
+
+  @Override
+  public String toString() {
+    return "";
+  }
+
+  @Override
+  public void handle(ContainerEvent event) {
+  }
+
+}

+ 361 - 0
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServices.java

@@ -0,0 +1,361 @@
+/**
+ * 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.nodemanager.webapp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.StringReader;
+
+import javax.ws.rs.core.MediaType;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileUtil;
+import org.apache.hadoop.util.VersionInfo;
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
+import org.apache.hadoop.yarn.server.nodemanager.Context;
+import org.apache.hadoop.yarn.server.nodemanager.LocalDirsHandlerService;
+import org.apache.hadoop.yarn.server.nodemanager.NodeHealthCheckerService;
+import org.apache.hadoop.yarn.server.nodemanager.NodeManager;
+import org.apache.hadoop.yarn.server.nodemanager.ResourceView;
+import org.apache.hadoop.yarn.server.nodemanager.webapp.WebServer.NMWebApp;
+import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
+import org.apache.hadoop.yarn.util.YarnVersionInfo;
+import org.apache.hadoop.yarn.webapp.GenericExceptionHandler;
+import org.apache.hadoop.yarn.webapp.WebApp;
+import org.apache.hadoop.yarn.webapp.WebServicesTestUtils;
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.servlet.GuiceServletContextListener;
+import com.google.inject.servlet.ServletModule;
+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.guice.spi.container.servlet.GuiceContainer;
+import com.sun.jersey.test.framework.JerseyTest;
+import com.sun.jersey.test.framework.WebAppDescriptor;
+
+/**
+ * Test the nodemanager node info web services api's
+ */
+public class TestNMWebServices extends JerseyTest {
+
+  private static Context nmContext;
+  private static ResourceView resourceView;
+  private static ApplicationACLsManager aclsManager;
+  private static LocalDirsHandlerService dirsHandler;
+  private static WebApp nmWebApp;
+
+  private static final File testRootDir = new File("target",
+      TestNMWebServices.class.getSimpleName());
+  private static File testLogDir = new File("target",
+      TestNMWebServices.class.getSimpleName() + "LogDir");
+
+  private Injector injector = Guice.createInjector(new ServletModule() {
+    @Override
+    protected void configureServlets() {
+      nmContext = new NodeManager.NMContext();
+      nmContext.getNodeId().setHost("testhost.foo.com");
+      nmContext.getNodeId().setPort(9999);
+      resourceView = new ResourceView() {
+        @Override
+        public long getVmemAllocatedForContainers() {
+          // 15.5G in bytes
+          return new Long("16642998272");
+        }
+
+        @Override
+        public long getPmemAllocatedForContainers() {
+          // 16G in bytes
+          return new Long("17179869184");
+        }
+      };
+      Configuration conf = new Configuration();
+      conf.set(YarnConfiguration.NM_LOCAL_DIRS, testRootDir.getAbsolutePath());
+      conf.set(YarnConfiguration.NM_LOG_DIRS, testLogDir.getAbsolutePath());
+      NodeHealthCheckerService healthChecker = new NodeHealthCheckerService();
+      healthChecker.init(conf);
+      dirsHandler = healthChecker.getDiskHandler();
+      aclsManager = new ApplicationACLsManager(conf);
+      nmWebApp = new NMWebApp(resourceView, aclsManager, dirsHandler);
+      bind(JAXBContextResolver.class);
+      bind(NMWebServices.class);
+      bind(GenericExceptionHandler.class);
+      bind(Context.class).toInstance(nmContext);
+      bind(WebApp.class).toInstance(nmWebApp);
+      bind(ResourceView.class).toInstance(resourceView);
+      bind(ApplicationACLsManager.class).toInstance(aclsManager);
+      bind(LocalDirsHandlerService.class).toInstance(dirsHandler);
+
+      serve("/*").with(GuiceContainer.class);
+    }
+  });
+
+  public class GuiceServletConfig extends GuiceServletContextListener {
+
+    @Override
+    protected Injector getInjector() {
+      return injector;
+    }
+  }
+
+  @Before
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    testRootDir.mkdirs();
+    testLogDir.mkdir();
+  }
+
+  @AfterClass
+  static public void stop() {
+    FileUtil.fullyDelete(testRootDir);
+    FileUtil.fullyDelete(testLogDir);
+  }
+
+  public TestNMWebServices() {
+    super(new WebAppDescriptor.Builder(
+        "org.apache.hadoop.yarn.server.nodemanager.webapp")
+        .contextListenerClass(GuiceServletConfig.class)
+        .filterClass(com.google.inject.servlet.GuiceFilter.class)
+        .contextPath("jersey-guice-filter").servletPath("/").build());
+  }
+
+  @Test
+  public void testInvalidUri() throws JSONException, Exception {
+    WebResource r = resource();
+    String responseStr = "";
+    try {
+      responseStr = r.path("ws").path("v1").path("node").path("bogus")
+          .accept(MediaType.APPLICATION_JSON).get(String.class);
+      fail("should have thrown exception on invalid uri");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+      assertEquals(Status.NOT_FOUND, response.getClientResponseStatus());
+      WebServicesTestUtils.checkStringMatch(
+          "error string exists and shouldn't", "", responseStr);
+    }
+  }
+
+  @Test
+  public void testInvalidAccept() throws JSONException, Exception {
+    WebResource r = resource();
+    String responseStr = "";
+    try {
+      responseStr = r.path("ws").path("v1").path("node")
+          .accept(MediaType.TEXT_PLAIN).get(String.class);
+      fail("should have thrown exception on invalid uri");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+      assertEquals(Status.INTERNAL_SERVER_ERROR,
+          response.getClientResponseStatus());
+      WebServicesTestUtils.checkStringMatch(
+          "error string exists and shouldn't", "", responseStr);
+    }
+  }
+
+  @Test
+  public void testInvalidUri2() throws JSONException, Exception {
+    WebResource r = resource();
+    String responseStr = "";
+    try {
+      responseStr = r.accept(MediaType.APPLICATION_JSON).get(String.class);
+      fail("should have thrown exception on invalid uri");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+      assertEquals(Status.NOT_FOUND, response.getClientResponseStatus());
+      WebServicesTestUtils.checkStringMatch(
+          "error string exists and shouldn't", "", responseStr);
+    }
+  }
+
+  @Test
+  public void testNode() throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("node")
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyNodeInfo(json);
+  }
+
+  @Test
+  public void testNodeSlash() throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("node/")
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyNodeInfo(json);
+  }
+
+  // make sure default is json output
+  @Test
+  public void testNodeDefault() throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("node")
+        .get(ClientResponse.class);
+
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyNodeInfo(json);
+  }
+
+  @Test
+  public void testNodeInfo() throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("node").path("info")
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyNodeInfo(json);
+  }
+
+  @Test
+  public void testNodeInfoSlash() throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("node")
+        .path("info/").accept(MediaType.APPLICATION_JSON)
+        .get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyNodeInfo(json);
+  }
+
+  // make sure default is json output
+  @Test
+  public void testNodeInfoDefault() throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("node").path("info")
+        .get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyNodeInfo(json);
+  }
+
+  @Test
+  public void testSingleNodesXML() throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("node")
+        .path("info/").accept(MediaType.APPLICATION_XML)
+        .get(ClientResponse.class);
+    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("nodeInfo");
+    assertEquals("incorrect number of elements", 1, nodes.getLength());
+    verifyNodesXML(nodes);
+  }
+
+  public void verifyNodesXML(NodeList nodes) throws JSONException, Exception {
+    for (int i = 0; i < nodes.getLength(); i++) {
+      Element element = (Element) nodes.item(i);
+
+      verifyNodeInfoGeneric(WebServicesTestUtils.getXmlString(element, "id"),
+          WebServicesTestUtils.getXmlString(element, "healthReport"),
+          WebServicesTestUtils.getXmlLong(element,
+              "totalVmemAllocatedContainersMB"),
+          WebServicesTestUtils.getXmlLong(element,
+              "totalPmemAllocatedContainersMB"),
+          WebServicesTestUtils.getXmlLong(element, "lastNodeUpdateTime"),
+          WebServicesTestUtils.getXmlBoolean(element, "nodeHealthy"),
+          WebServicesTestUtils.getXmlString(element, "nodeHostName"),
+          WebServicesTestUtils.getXmlString(element, "hadoopVersionBuiltOn"),
+          WebServicesTestUtils.getXmlString(element, "hadoopBuildVersion"),
+          WebServicesTestUtils.getXmlString(element, "hadoopVersion"),
+          WebServicesTestUtils.getXmlString(element,
+              "nodeManagerVersionBuiltOn"), WebServicesTestUtils.getXmlString(
+              element, "nodeManagerBuildVersion"),
+          WebServicesTestUtils.getXmlString(element, "nodeManagerVersion"));
+    }
+  }
+
+  public void verifyNodeInfo(JSONObject json) throws JSONException, Exception {
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject info = json.getJSONObject("nodeInfo");
+    assertEquals("incorrect number of elements", 13, info.length());
+    verifyNodeInfoGeneric(info.getString("id"), info.getString("healthReport"),
+        info.getLong("totalVmemAllocatedContainersMB"),
+        info.getLong("totalPmemAllocatedContainersMB"),
+        info.getLong("lastNodeUpdateTime"), info.getBoolean("nodeHealthy"),
+        info.getString("nodeHostName"), info.getString("hadoopVersionBuiltOn"),
+        info.getString("hadoopBuildVersion"), info.getString("hadoopVersion"),
+        info.getString("nodeManagerVersionBuiltOn"),
+        info.getString("nodeManagerBuildVersion"),
+        info.getString("nodeManagerVersion"));
+
+  }
+
+  public void verifyNodeInfoGeneric(String id, String healthReport,
+      long totalVmemAllocatedContainersMB, long totalPmemAllocatedContainersMB,
+      long lastNodeUpdateTime, Boolean nodeHealthy, String nodeHostName,
+      String hadoopVersionBuiltOn, String hadoopBuildVersion,
+      String hadoopVersion, String resourceManagerVersionBuiltOn,
+      String resourceManagerBuildVersion, String resourceManagerVersion) {
+
+    WebServicesTestUtils.checkStringMatch("id", "testhost.foo.com:9999", id);
+    WebServicesTestUtils.checkStringMatch("healthReport", "Healthy",
+        healthReport);
+    assertEquals("totalVmemAllocatedContainersMB incorrect", 15872,
+        totalVmemAllocatedContainersMB);
+    assertEquals("totalPmemAllocatedContainersMB incorrect", 16384,
+        totalPmemAllocatedContainersMB);
+    assertTrue("lastNodeUpdateTime incorrect", lastNodeUpdateTime == nmContext
+        .getNodeHealthStatus().getLastHealthReportTime());
+    assertTrue("nodeHealthy isn't true", nodeHealthy);
+    WebServicesTestUtils.checkStringMatch("nodeHostName", "testhost.foo.com",
+        nodeHostName);
+
+    WebServicesTestUtils.checkStringMatch("hadoopVersionBuiltOn",
+        VersionInfo.getDate(), hadoopVersionBuiltOn);
+    WebServicesTestUtils.checkStringMatch("hadoopBuildVersion",
+        VersionInfo.getBuildVersion(), hadoopBuildVersion);
+    WebServicesTestUtils.checkStringMatch("hadoopVersion",
+        VersionInfo.getVersion(), hadoopVersion);
+
+    WebServicesTestUtils.checkStringMatch("resourceManagerVersionBuiltOn",
+        YarnVersionInfo.getDate(), resourceManagerVersionBuiltOn);
+    WebServicesTestUtils.checkStringMatch("resourceManagerBuildVersion",
+        YarnVersionInfo.getBuildVersion(), resourceManagerBuildVersion);
+    WebServicesTestUtils.checkStringMatch("resourceManagerVersion",
+        YarnVersionInfo.getVersion(), resourceManagerVersion);
+  }
+
+}

+ 607 - 0
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServicesApps.java

@@ -0,0 +1,607 @@
+/**
+ * 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.nodemanager.webapp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.StringReader;
+import java.util.HashMap;
+
+import javax.ws.rs.core.MediaType;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileUtil;
+import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
+import org.apache.hadoop.yarn.event.AsyncDispatcher;
+import org.apache.hadoop.yarn.event.Dispatcher;
+import org.apache.hadoop.yarn.server.nodemanager.Context;
+import org.apache.hadoop.yarn.server.nodemanager.LocalDirsHandlerService;
+import org.apache.hadoop.yarn.server.nodemanager.MockApp;
+import org.apache.hadoop.yarn.server.nodemanager.MockContainer;
+import org.apache.hadoop.yarn.server.nodemanager.NodeHealthCheckerService;
+import org.apache.hadoop.yarn.server.nodemanager.NodeManager;
+import org.apache.hadoop.yarn.server.nodemanager.ResourceView;
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.Application;
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.ApplicationState;
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
+import org.apache.hadoop.yarn.server.nodemanager.webapp.WebServer.NMWebApp;
+import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
+import org.apache.hadoop.yarn.util.BuilderUtils;
+import org.apache.hadoop.yarn.webapp.GenericExceptionHandler;
+import org.apache.hadoop.yarn.webapp.WebApp;
+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.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.servlet.GuiceServletContextListener;
+import com.google.inject.servlet.ServletModule;
+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.guice.spi.container.servlet.GuiceContainer;
+import com.sun.jersey.test.framework.JerseyTest;
+import com.sun.jersey.test.framework.WebAppDescriptor;
+
+public class TestNMWebServicesApps extends JerseyTest {
+
+  private static Context nmContext;
+  private static ResourceView resourceView;
+  private static ApplicationACLsManager aclsManager;
+  private static LocalDirsHandlerService dirsHandler;
+  private static WebApp nmWebApp;
+  private static Configuration conf = new Configuration();
+
+  private static final File testRootDir = new File("target",
+      TestNMWebServicesApps.class.getSimpleName());
+  private static File testLogDir = new File("target",
+      TestNMWebServicesApps.class.getSimpleName() + "LogDir");
+
+  private Injector injector = Guice.createInjector(new ServletModule() {
+    @Override
+    protected void configureServlets() {
+      nmContext = new NodeManager.NMContext();
+      nmContext.getNodeId().setHost("testhost.foo.com");
+      nmContext.getNodeId().setPort(9999);
+      resourceView = new ResourceView() {
+        @Override
+        public long getVmemAllocatedForContainers() {
+          // 15.5G in bytes
+          return new Long("16642998272");
+        }
+
+        @Override
+        public long getPmemAllocatedForContainers() {
+          // 16G in bytes
+          return new Long("17179869184");
+        }
+      };
+      conf.set(YarnConfiguration.NM_LOCAL_DIRS, testRootDir.getAbsolutePath());
+      conf.set(YarnConfiguration.NM_LOG_DIRS, testLogDir.getAbsolutePath());
+      NodeHealthCheckerService healthChecker = new NodeHealthCheckerService();
+      healthChecker.init(conf);
+      dirsHandler = healthChecker.getDiskHandler();
+      aclsManager = new ApplicationACLsManager(conf);
+      nmWebApp = new NMWebApp(resourceView, aclsManager, dirsHandler);
+      bind(JAXBContextResolver.class);
+      bind(NMWebServices.class);
+      bind(GenericExceptionHandler.class);
+      bind(Context.class).toInstance(nmContext);
+      bind(WebApp.class).toInstance(nmWebApp);
+      bind(ResourceView.class).toInstance(resourceView);
+      bind(ApplicationACLsManager.class).toInstance(aclsManager);
+      bind(LocalDirsHandlerService.class).toInstance(dirsHandler);
+
+      serve("/*").with(GuiceContainer.class);
+    }
+  });
+
+  public class GuiceServletConfig extends GuiceServletContextListener {
+
+    @Override
+    protected Injector getInjector() {
+      return injector;
+    }
+  }
+
+  @Before
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    testRootDir.mkdirs();
+    testLogDir.mkdir();
+  }
+
+  @AfterClass
+  static public void cleanup() {
+    FileUtil.fullyDelete(testRootDir);
+    FileUtil.fullyDelete(testLogDir);
+  }
+
+  public TestNMWebServicesApps() {
+    super(new WebAppDescriptor.Builder(
+        "org.apache.hadoop.yarn.server.nodemanager.webapp")
+        .contextListenerClass(GuiceServletConfig.class)
+        .filterClass(com.google.inject.servlet.GuiceFilter.class)
+        .contextPath("jersey-guice-filter").servletPath("/").build());
+  }
+
+  @Test
+  public void testNodeAppsNone() throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("node").path("apps")
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("apps isn't NULL", JSONObject.NULL, json.get("apps"));
+  }
+
+  private HashMap<String, String> addAppContainers(Application app) {
+    Dispatcher dispatcher = new AsyncDispatcher();
+    ApplicationAttemptId appAttemptId = BuilderUtils.newApplicationAttemptId(
+        app.getAppId(), 1);
+    Container container1 = new MockContainer(appAttemptId, dispatcher, conf,
+        app.getUser(), app.getAppId(), 1);
+    Container container2 = new MockContainer(appAttemptId, dispatcher, conf,
+        app.getUser(), app.getAppId(), 2);
+    nmContext.getContainers().put(container1.getContainerID(), container1);
+    nmContext.getContainers().put(container2.getContainerID(), container2);
+
+    app.getContainers().put(container1.getContainerID(), container1);
+    app.getContainers().put(container2.getContainerID(), container2);
+    HashMap<String, String> hash = new HashMap<String, String>();
+    hash.put(container1.getContainerID().toString(), container1
+        .getContainerID().toString());
+    hash.put(container2.getContainerID().toString(), container2
+        .getContainerID().toString());
+    return hash;
+  }
+
+  @Test
+  public void testNodeApps() throws JSONException, Exception {
+    testNodeHelper("apps", MediaType.APPLICATION_JSON);
+  }
+
+  @Test
+  public void testNodeAppsSlash() throws JSONException, Exception {
+    testNodeHelper("apps/", MediaType.APPLICATION_JSON);
+  }
+
+  // make sure default is json output
+  @Test
+  public void testNodeAppsDefault() throws JSONException, Exception {
+    testNodeHelper("apps/", "");
+
+  }
+
+  public void testNodeHelper(String path, String media) throws JSONException,
+      Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    HashMap<String, String> hash = addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    HashMap<String, String> hash2 = addAppContainers(app2);
+
+    ClientResponse response = r.path("ws").path("v1").path("node").path(path)
+        .accept(media).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    JSONObject info = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, info.length());
+    JSONArray appInfo = info.getJSONArray("app");
+    assertEquals("incorrect number of elements", 2, appInfo.length());
+    String id = appInfo.getJSONObject(0).getString("id");
+    if (id.matches(app.getAppId().toString())) {
+      verifyNodeAppInfo(appInfo.getJSONObject(0), app, hash);
+      verifyNodeAppInfo(appInfo.getJSONObject(1), app2, hash2);
+    } else {
+      verifyNodeAppInfo(appInfo.getJSONObject(0), app2, hash2);
+      verifyNodeAppInfo(appInfo.getJSONObject(1), app, hash);
+    }
+  }
+
+  @Test
+  public void testNodeAppsUser() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    HashMap<String, String> hash = addAppContainers(app);
+    Application app2 = new MockApp("foo", 1234, 2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    ClientResponse response = r.path("ws").path("v1").path("node").path("apps")
+        .queryParam("user", "mockUser").accept(MediaType.APPLICATION_JSON)
+        .get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+
+    JSONObject info = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, info.length());
+    JSONArray appInfo = info.getJSONArray("app");
+    assertEquals("incorrect number of elements", 1, appInfo.length());
+    verifyNodeAppInfo(appInfo.getJSONObject(0), app, hash);
+  }
+
+  @Test
+  public void testNodeAppsUserNone() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    addAppContainers(app);
+    Application app2 = new MockApp("foo", 1234, 2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    ClientResponse response = r.path("ws").path("v1").path("node").path("apps")
+        .queryParam("user", "george").accept(MediaType.APPLICATION_JSON)
+        .get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("apps is not null", JSONObject.NULL, json.get("apps"));
+  }
+
+  @Test
+  public void testNodeAppsUserEmpty() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    addAppContainers(app);
+    Application app2 = new MockApp("foo", 1234, 2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    try {
+      r.path("ws").path("v1").path("node").path("apps").queryParam("user", "")
+          .accept(MediaType.APPLICATION_JSON).get(JSONObject.class);
+      fail("should have thrown exception on invalid user query");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+
+      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils
+          .checkStringMatch(
+              "exception message",
+              "java.lang.Exception: Error: You must specify a non-empty string for the user",
+              message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "BadRequestException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "org.apache.hadoop.yarn.webapp.BadRequestException", classname);
+    }
+  }
+
+  @Test
+  public void testNodeAppsState() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    addAppContainers(app);
+    MockApp app2 = new MockApp("foo", 1234, 2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    HashMap<String, String> hash2 = addAppContainers(app2);
+    app2.setState(ApplicationState.RUNNING);
+
+    ClientResponse response = r.path("ws").path("v1").path("node").path("apps")
+        .queryParam("state", ApplicationState.RUNNING.toString())
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+
+    JSONObject info = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, info.length());
+    JSONArray appInfo = info.getJSONArray("app");
+    assertEquals("incorrect number of elements", 1, appInfo.length());
+    verifyNodeAppInfo(appInfo.getJSONObject(0), app2, hash2);
+
+  }
+
+  @Test
+  public void testNodeAppsStateNone() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    addAppContainers(app);
+    Application app2 = new MockApp("foo", 1234, 2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    ClientResponse response = r.path("ws").path("v1").path("node").path("apps")
+        .queryParam("state", ApplicationState.INITING.toString())
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+
+    assertEquals("apps is not null", JSONObject.NULL, json.get("apps"));
+  }
+
+  @Test
+  public void testNodeAppsStateInvalid() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    addAppContainers(app);
+    Application app2 = new MockApp("foo", 1234, 2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    try {
+      r.path("ws").path("v1").path("node").path("apps")
+          .queryParam("state", "FOO_STATE").accept(MediaType.APPLICATION_JSON)
+          .get(JSONObject.class);
+      fail("should have thrown exception on invalid user query");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+
+      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils
+          .checkStringMatch(
+              "exception message",
+              "No enum const class org.apache.hadoop.yarn.server.nodemanager.containermanager.application.ApplicationState.FOO_STATE",
+              message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "IllegalArgumentException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "java.lang.IllegalArgumentException", classname);
+    }
+  }
+
+  @Test
+  public void testNodeSingleApps() throws JSONException, Exception {
+    testNodeSingleAppHelper(MediaType.APPLICATION_JSON);
+  }
+
+  // make sure default is json output
+  @Test
+  public void testNodeSingleAppsDefault() throws JSONException, Exception {
+    testNodeSingleAppHelper("");
+  }
+
+  public void testNodeSingleAppHelper(String media) throws JSONException,
+      Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    HashMap<String, String> hash = addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    ClientResponse response = r.path("ws").path("v1").path("node").path("apps")
+        .path(app.getAppId().toString()).accept(media)
+        .get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyNodeAppInfo(json.getJSONObject("app"), app, hash);
+  }
+
+  @Test
+  public void testNodeSingleAppsSlash() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    HashMap<String, String> hash = addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+    ClientResponse response = r.path("ws").path("v1").path("node").path("apps")
+        .path(app.getAppId().toString() + "/")
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyNodeAppInfo(json.getJSONObject("app"), app, hash);
+  }
+
+  @Test
+  public void testNodeSingleAppsInvalid() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    try {
+      r.path("ws").path("v1").path("node").path("apps").path("app_foo_0000")
+          .accept(MediaType.APPLICATION_JSON).get(JSONObject.class);
+      fail("should have thrown exception on invalid user query");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils.checkStringMatch("exception message",
+          "For input string: \"foo\"", message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "NumberFormatException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "java.lang.NumberFormatException", classname);
+    }
+  }
+
+  @Test
+  public void testNodeSingleAppsMissing() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    try {
+      r.path("ws").path("v1").path("node").path("apps")
+          .path("application_1234_0009").accept(MediaType.APPLICATION_JSON)
+          .get(JSONObject.class);
+      fail("should have thrown exception on invalid user query");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+      assertEquals(Status.NOT_FOUND, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils.checkStringMatch("exception message",
+          "java.lang.Exception: app with id application_1234_0009 not found",
+          message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "NotFoundException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "org.apache.hadoop.yarn.webapp.NotFoundException", classname);
+    }
+  }
+
+  @Test
+  public void testNodeAppsXML() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    ClientResponse response = r.path("ws").path("v1").path("node").path("apps")
+        .accept(MediaType.APPLICATION_XML).get(ClientResponse.class);
+    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", 2, nodes.getLength());
+  }
+
+  @Test
+  public void testNodeSingleAppsXML() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    HashMap<String, String> hash = addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    ClientResponse response = r.path("ws").path("v1").path("node").path("apps")
+        .path(app.getAppId().toString() + "/")
+        .accept(MediaType.APPLICATION_XML).get(ClientResponse.class);
+    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());
+    verifyNodeAppInfoXML(nodes, app, hash);
+  }
+
+  public void verifyNodeAppInfoXML(NodeList nodes, Application app,
+      HashMap<String, String> hash) throws JSONException, Exception {
+    for (int i = 0; i < nodes.getLength(); i++) {
+      Element element = (Element) nodes.item(i);
+
+      verifyNodeAppInfoGeneric(app,
+          WebServicesTestUtils.getXmlString(element, "id"),
+          WebServicesTestUtils.getXmlString(element, "state"),
+          WebServicesTestUtils.getXmlString(element, "user"));
+
+      NodeList ids = element.getElementsByTagName("containerids");
+      for (int j = 0; j < ids.getLength(); j++) {
+        Element line = (Element) ids.item(j);
+        Node first = line.getFirstChild();
+        String val = first.getNodeValue();
+        assertEquals("extra containerid: " + val, val, hash.remove(val));
+      }
+      assertTrue("missing containerids", hash.isEmpty());
+    }
+  }
+
+  public void verifyNodeAppInfo(JSONObject info, Application app,
+      HashMap<String, String> hash) throws JSONException, Exception {
+    assertEquals("incorrect number of elements", 4, info.length());
+
+    verifyNodeAppInfoGeneric(app, info.getString("id"),
+        info.getString("state"), info.getString("user"));
+
+    JSONArray containerids = info.getJSONArray("containerids");
+    for (int i = 0; i < containerids.length(); i++) {
+      String id = containerids.getString(i);
+      assertEquals("extra containerid: " + id, id, hash.remove(id));
+    }
+    assertTrue("missing containerids", hash.isEmpty());
+  }
+
+  public void verifyNodeAppInfoGeneric(Application app, String id,
+      String state, String user) throws JSONException, Exception {
+    WebServicesTestUtils.checkStringMatch("id", app.getAppId().toString(), id);
+    WebServicesTestUtils.checkStringMatch("state", app.getApplicationState()
+        .toString(), state);
+    WebServicesTestUtils.checkStringMatch("user", app.getUser().toString(),
+        user);
+  }
+
+}

+ 481 - 0
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/webapp/TestNMWebServicesContainers.java

@@ -0,0 +1,481 @@
+/**
+ * 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.nodemanager.webapp;
+
+import static org.apache.hadoop.yarn.util.StringHelper.ujoin;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.StringReader;
+import java.util.HashMap;
+
+import javax.ws.rs.core.MediaType;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileUtil;
+import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
+import org.apache.hadoop.yarn.event.AsyncDispatcher;
+import org.apache.hadoop.yarn.event.Dispatcher;
+import org.apache.hadoop.yarn.server.nodemanager.Context;
+import org.apache.hadoop.yarn.server.nodemanager.LocalDirsHandlerService;
+import org.apache.hadoop.yarn.server.nodemanager.MockApp;
+import org.apache.hadoop.yarn.server.nodemanager.MockContainer;
+import org.apache.hadoop.yarn.server.nodemanager.NodeHealthCheckerService;
+import org.apache.hadoop.yarn.server.nodemanager.NodeManager;
+import org.apache.hadoop.yarn.server.nodemanager.ResourceView;
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.Application;
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
+import org.apache.hadoop.yarn.server.nodemanager.webapp.WebServer.NMWebApp;
+import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
+import org.apache.hadoop.yarn.util.BuilderUtils;
+import org.apache.hadoop.yarn.util.ConverterUtils;
+import org.apache.hadoop.yarn.webapp.GenericExceptionHandler;
+import org.apache.hadoop.yarn.webapp.WebApp;
+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.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.servlet.GuiceServletContextListener;
+import com.google.inject.servlet.ServletModule;
+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.guice.spi.container.servlet.GuiceContainer;
+import com.sun.jersey.test.framework.JerseyTest;
+import com.sun.jersey.test.framework.WebAppDescriptor;
+
+public class TestNMWebServicesContainers extends JerseyTest {
+
+  private static Context nmContext;
+  private static ResourceView resourceView;
+  private static ApplicationACLsManager aclsManager;
+  private static LocalDirsHandlerService dirsHandler;
+  private static WebApp nmWebApp;
+  private static Configuration conf = new Configuration();
+
+  private static final File testRootDir = new File("target",
+      TestNMWebServicesContainers.class.getSimpleName());
+  private static File testLogDir = new File("target",
+      TestNMWebServicesContainers.class.getSimpleName() + "LogDir");
+
+  private Injector injector = Guice.createInjector(new ServletModule() {
+    @Override
+    protected void configureServlets() {
+      nmContext = new NodeManager.NMContext();
+      nmContext.getNodeId().setHost("testhost.foo.com");
+      nmContext.getNodeId().setPort(9999);
+      resourceView = new ResourceView() {
+        @Override
+        public long getVmemAllocatedForContainers() {
+          // 15.5G in bytes
+          return new Long("16642998272");
+        }
+
+        @Override
+        public long getPmemAllocatedForContainers() {
+          // 16G in bytes
+          return new Long("17179869184");
+        }
+      };
+      conf.set(YarnConfiguration.NM_LOCAL_DIRS, testRootDir.getAbsolutePath());
+      conf.set(YarnConfiguration.NM_LOG_DIRS, testLogDir.getAbsolutePath());
+      NodeHealthCheckerService healthChecker = new NodeHealthCheckerService();
+      healthChecker.init(conf);
+      dirsHandler = healthChecker.getDiskHandler();
+      aclsManager = new ApplicationACLsManager(conf);
+      nmWebApp = new NMWebApp(resourceView, aclsManager, dirsHandler);
+      bind(JAXBContextResolver.class);
+      bind(NMWebServices.class);
+      bind(GenericExceptionHandler.class);
+      bind(Context.class).toInstance(nmContext);
+      bind(WebApp.class).toInstance(nmWebApp);
+      bind(ResourceView.class).toInstance(resourceView);
+      bind(ApplicationACLsManager.class).toInstance(aclsManager);
+      bind(LocalDirsHandlerService.class).toInstance(dirsHandler);
+
+      serve("/*").with(GuiceContainer.class);
+    }
+  });
+
+  public class GuiceServletConfig extends GuiceServletContextListener {
+
+    @Override
+    protected Injector getInjector() {
+      return injector;
+    }
+  }
+
+  @Before
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    testRootDir.mkdirs();
+    testLogDir.mkdir();
+  }
+
+  @AfterClass
+  static public void cleanup() {
+    FileUtil.fullyDelete(testRootDir);
+    FileUtil.fullyDelete(testLogDir);
+  }
+
+  public TestNMWebServicesContainers() {
+    super(new WebAppDescriptor.Builder(
+        "org.apache.hadoop.yarn.server.nodemanager.webapp")
+        .contextListenerClass(GuiceServletConfig.class)
+        .filterClass(com.google.inject.servlet.GuiceFilter.class)
+        .contextPath("jersey-guice-filter").servletPath("/").build());
+  }
+
+  @Test
+  public void testNodeContainersNone() throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("node")
+        .path("containers").accept(MediaType.APPLICATION_JSON)
+        .get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("apps isn't NULL", JSONObject.NULL, json.get("containers"));
+  }
+
+  private HashMap<String, String> addAppContainers(Application app) {
+    Dispatcher dispatcher = new AsyncDispatcher();
+    ApplicationAttemptId appAttemptId = BuilderUtils.newApplicationAttemptId(
+        app.getAppId(), 1);
+    Container container1 = new MockContainer(appAttemptId, dispatcher, conf,
+        app.getUser(), app.getAppId(), 1);
+    Container container2 = new MockContainer(appAttemptId, dispatcher, conf,
+        app.getUser(), app.getAppId(), 2);
+    nmContext.getContainers().put(container1.getContainerID(), container1);
+    nmContext.getContainers().put(container2.getContainerID(), container2);
+
+    app.getContainers().put(container1.getContainerID(), container1);
+    app.getContainers().put(container2.getContainerID(), container2);
+    HashMap<String, String> hash = new HashMap<String, String>();
+    hash.put(container1.getContainerID().toString(), container1
+        .getContainerID().toString());
+    hash.put(container2.getContainerID().toString(), container2
+        .getContainerID().toString());
+    return hash;
+  }
+
+  @Test
+  public void testNodeContainers() throws JSONException, Exception {
+    testNodeHelper("containers", MediaType.APPLICATION_JSON);
+  }
+
+  @Test
+  public void testNodeContainersSlash() throws JSONException, Exception {
+    testNodeHelper("containers/", MediaType.APPLICATION_JSON);
+  }
+
+  // make sure default is json output
+  @Test
+  public void testNodeContainersDefault() throws JSONException, Exception {
+    testNodeHelper("containers/", "");
+
+  }
+
+  public void testNodeHelper(String path, String media) throws JSONException,
+      Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    ClientResponse response = r.path("ws").path("v1").path("node").path(path)
+        .accept(media).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    JSONObject info = json.getJSONObject("containers");
+    assertEquals("incorrect number of elements", 1, info.length());
+    JSONArray conInfo = info.getJSONArray("container");
+    assertEquals("incorrect number of elements", 4, conInfo.length());
+
+    for (int i = 0; i < conInfo.length(); i++) {
+      verifyNodeContainerInfo(
+          conInfo.getJSONObject(i),
+          nmContext.getContainers().get(
+              ConverterUtils.toContainerId(conInfo.getJSONObject(i).getString(
+                  "id"))));
+    }
+  }
+
+  @Test
+  public void testNodeSingleContainers() throws JSONException, Exception {
+    testNodeSingleContainersHelper(MediaType.APPLICATION_JSON);
+  }
+
+  @Test
+  public void testNodeSingleContainersSlash() throws JSONException, Exception {
+    testNodeSingleContainersHelper(MediaType.APPLICATION_JSON);
+  }
+
+  @Test
+  public void testNodeSingleContainersDefault() throws JSONException, Exception {
+    testNodeSingleContainersHelper("");
+  }
+
+  public void testNodeSingleContainersHelper(String media)
+      throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    HashMap<String, String> hash = addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    for (String id : hash.keySet()) {
+      ClientResponse response = r.path("ws").path("v1").path("node")
+          .path("containers").path(id).accept(media).get(ClientResponse.class);
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+      JSONObject json = response.getEntity(JSONObject.class);
+      verifyNodeContainerInfo(json.getJSONObject("container"), nmContext
+          .getContainers().get(ConverterUtils.toContainerId(id)));
+    }
+  }
+
+  @Test
+  public void testSingleContainerInvalid() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+    try {
+      r.path("ws").path("v1").path("node").path("containers")
+          .path("container_foo_1234").accept(MediaType.APPLICATION_JSON)
+          .get(JSONObject.class);
+      fail("should have thrown exception on invalid user query");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils.checkStringMatch("exception message",
+          "java.lang.Exception: invalid container id, container_foo_1234",
+          message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "BadRequestException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "org.apache.hadoop.yarn.webapp.BadRequestException", classname);
+    }
+  }
+
+  @Test
+  public void testSingleContainerInvalid2() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+    try {
+      r.path("ws").path("v1").path("node").path("containers")
+          .path("container_1234_0001").accept(MediaType.APPLICATION_JSON)
+          .get(JSONObject.class);
+      fail("should have thrown exception on invalid user query");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils.checkStringMatch("exception message",
+          "java.lang.Exception: invalid container id, container_1234_0001",
+          message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "BadRequestException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "org.apache.hadoop.yarn.webapp.BadRequestException", classname);
+    }
+  }
+
+  @Test
+  public void testSingleContainerWrong() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+    try {
+      r.path("ws").path("v1").path("node").path("containers")
+          .path("container_1234_0001_01_000005")
+          .accept(MediaType.APPLICATION_JSON).get(JSONObject.class);
+      fail("should have thrown exception on invalid user query");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+      assertEquals(Status.NOT_FOUND, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils
+          .checkStringMatch(
+              "exception message",
+              "java.lang.Exception: container with id, container_1234_0001_01_000005, not found",
+              message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "NotFoundException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "org.apache.hadoop.yarn.webapp.NotFoundException", classname);
+    }
+  }
+
+  @Test
+  public void testNodeSingleContainerXML() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    HashMap<String, String> hash = addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    for (String id : hash.keySet()) {
+      ClientResponse response = r.path("ws").path("v1").path("node")
+          .path("containers").path(id).accept(MediaType.APPLICATION_XML)
+          .get(ClientResponse.class);
+      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("container");
+      assertEquals("incorrect number of elements", 1, nodes.getLength());
+      verifyContainersInfoXML(nodes,
+          nmContext.getContainers().get(ConverterUtils.toContainerId(id)));
+
+    }
+  }
+
+  @Test
+  public void testNodeContainerXML() throws JSONException, Exception {
+    WebResource r = resource();
+    Application app = new MockApp(1);
+    nmContext.getApplications().put(app.getAppId(), app);
+    addAppContainers(app);
+    Application app2 = new MockApp(2);
+    nmContext.getApplications().put(app2.getAppId(), app2);
+    addAppContainers(app2);
+
+    ClientResponse response = r.path("ws").path("v1").path("node")
+        .path("containers").accept(MediaType.APPLICATION_XML)
+        .get(ClientResponse.class);
+    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("container");
+    assertEquals("incorrect number of elements", 4, nodes.getLength());
+  }
+
+  public void verifyContainersInfoXML(NodeList nodes, Container cont)
+      throws JSONException, Exception {
+    for (int i = 0; i < nodes.getLength(); i++) {
+      Element element = (Element) nodes.item(i);
+
+      verifyNodeContainerInfoGeneric(cont,
+          WebServicesTestUtils.getXmlString(element, "id"),
+          WebServicesTestUtils.getXmlString(element, "state"),
+          WebServicesTestUtils.getXmlString(element, "user"),
+          WebServicesTestUtils.getXmlInt(element, "exitCode"),
+          WebServicesTestUtils.getXmlString(element, "diagnostics"),
+          WebServicesTestUtils.getXmlString(element, "nodeId"),
+          WebServicesTestUtils.getXmlInt(element, "totalMemoryNeededMB"),
+          WebServicesTestUtils.getXmlString(element, "containerLogsLink"));
+    }
+  }
+
+  public void verifyNodeContainerInfo(JSONObject info, Container cont)
+      throws JSONException, Exception {
+    assertEquals("incorrect number of elements", 8, info.length());
+
+    verifyNodeContainerInfoGeneric(cont, info.getString("id"),
+        info.getString("state"), info.getString("user"),
+        info.getInt("exitCode"), info.getString("diagnostics"),
+        info.getString("nodeId"), info.getInt("totalMemoryNeededMB"),
+        info.getString("containerLogsLink"));
+  }
+
+  public void verifyNodeContainerInfoGeneric(Container cont, String id,
+      String state, String user, int exitCode, String diagnostics,
+      String nodeId, int totalMemoryNeededMB, String logsLink)
+      throws JSONException, Exception {
+    WebServicesTestUtils.checkStringMatch("id", cont.getContainerID()
+        .toString(), id);
+    WebServicesTestUtils.checkStringMatch("state", cont.getContainerState()
+        .toString(), state);
+    WebServicesTestUtils.checkStringMatch("user", cont.getUser().toString(),
+        user);
+    assertEquals("exitCode wrong", 0, exitCode);
+    WebServicesTestUtils
+        .checkStringMatch("diagnostics", "testing", diagnostics);
+
+    WebServicesTestUtils.checkStringMatch("nodeId", nmContext.getNodeId()
+        .toString(), nodeId);
+    assertEquals("totalMemoryNeededMB wrong", 0, totalMemoryNeededMB);
+    String shortLink = ujoin("containerlogs", cont.getContainerID().toString(),
+        cont.getUser());
+    assertTrue("containerLogsLink wrong", logsLink.contains(shortLink));
+  }
+
+}

+ 2 - 1
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/CapacitySchedulerInfo.java

@@ -26,6 +26,7 @@ import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlTransient;
 import javax.xml.bind.annotation.XmlType;
 
+import org.apache.hadoop.yarn.api.records.QueueState;
 import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CSQueue;
 import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.ParentQueue;
 
@@ -90,7 +91,7 @@ public class CapacitySchedulerInfo extends SchedulerInfo {
       if (max < EPSILON || max > 1f)
         max = 1f;
       float maxCapacity = max * 100;
-      String state = queue.getState().toString();
+      QueueState state = queue.getState();
       CapacitySchedulerQueueInfo info = new CapacitySchedulerQueueInfo(
           capacity, usedCapacity, maxCapacity, queueName, state, queuePath);
 

+ 4 - 3
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/CapacitySchedulerQueueInfo.java

@@ -24,6 +24,7 @@ import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlTransient;
 
+import org.apache.hadoop.yarn.api.records.QueueState;
 import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CSQueue;
 
 @XmlRootElement
@@ -43,14 +44,14 @@ public class CapacitySchedulerQueueInfo {
   protected float usedCapacity;
   protected float maxCapacity;
   protected String queueName;
-  protected String state;
+  protected QueueState state;
   protected ArrayList<CapacitySchedulerQueueInfo> subQueues;
 
   CapacitySchedulerQueueInfo() {
   };
 
   CapacitySchedulerQueueInfo(float cap, float used, float max, String name,
-      String state, String path) {
+      QueueState state, String path) {
     this.capacity = cap;
     this.usedCapacity = used;
     this.maxCapacity = max;
@@ -84,7 +85,7 @@ public class CapacitySchedulerQueueInfo {
   }
 
   public String getQueueState() {
-    return this.state;
+    return this.state.toString();
   }
 
   public String getQueuePath() {

+ 4 - 3
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/ClusterInfo.java

@@ -23,6 +23,7 @@ import javax.xml.bind.annotation.XmlRootElement;
 
 import org.apache.hadoop.util.VersionInfo;
 import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager;
+import org.apache.hadoop.yarn.service.Service.STATE;
 import org.apache.hadoop.yarn.util.YarnVersionInfo;
 
 @XmlRootElement
@@ -31,7 +32,7 @@ public class ClusterInfo {
 
   protected long id;
   protected long startedOn;
-  protected String state;
+  protected STATE state;
   protected String resourceManagerVersion;
   protected String resourceManagerBuildVersion;
   protected String resourceManagerVersionBuiltOn;
@@ -46,7 +47,7 @@ public class ClusterInfo {
     long ts = ResourceManager.clusterTimeStamp;
 
     this.id = ts;
-    this.state = rm.getServiceState().toString();
+    this.state = rm.getServiceState();
     this.startedOn = ts;
     this.resourceManagerVersion = YarnVersionInfo.getVersion();
     this.resourceManagerBuildVersion = YarnVersionInfo.getBuildVersion();
@@ -57,7 +58,7 @@ public class ClusterInfo {
   }
 
   public String getState() {
-    return this.state;
+    return this.state.toString();
   }
 
   public String getRMVersion() {

+ 7 - 3
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/MockRM.java

@@ -87,8 +87,12 @@ public class MockRM extends ResourceManager {
         .newRecord(GetNewApplicationRequest.class));
   }
 
-  // client
   public RMApp submitApp(int masterMemory) throws Exception {
+    return submitApp(masterMemory, "", "");
+  }
+
+  // client
+  public RMApp submitApp(int masterMemory, String name, String user) throws Exception {
     ClientRMProtocol client = getClientRMService();
     GetNewApplicationResponse resp = client.getNewApplication(Records
         .newRecord(GetNewApplicationRequest.class));
@@ -99,8 +103,8 @@ public class MockRM extends ResourceManager {
     ApplicationSubmissionContext sub = Records
         .newRecord(ApplicationSubmissionContext.class);
     sub.setApplicationId(appId);
-    sub.setApplicationName("");
-    sub.setUser("");
+    sub.setApplicationName(name);
+    sub.setUser(user);
     ContainerLaunchContext clc = Records
         .newRecord(ContainerLaunchContext.class);
     Resource capability = Records.newRecord(Resource.class);

+ 337 - 895
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServices.java

@@ -22,34 +22,34 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import java.util.ArrayList;
+import java.io.StringReader;
 
 import javax.ws.rs.core.MediaType;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
 
 import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.security.UserGroupInformation;
-import org.apache.hadoop.yarn.api.records.ContainerStatus;
-import org.apache.hadoop.yarn.api.records.FinalApplicationStatus;
-import org.apache.hadoop.yarn.api.records.NodeHealthStatus;
+import org.apache.hadoop.util.VersionInfo;
 import org.apache.hadoop.yarn.api.records.QueueState;
-import org.apache.hadoop.yarn.server.resourcemanager.MockAM;
-import org.apache.hadoop.yarn.server.resourcemanager.MockNM;
+import org.apache.hadoop.yarn.server.resourcemanager.ClusterMetrics;
 import org.apache.hadoop.yarn.server.resourcemanager.MockRM;
 import org.apache.hadoop.yarn.server.resourcemanager.RMContext;
 import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager;
-import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMApp;
-import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppState;
-import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNode;
-import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNodeImpl;
-import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNodeState;
-import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNodeStatusEvent;
+import org.apache.hadoop.yarn.server.resourcemanager.scheduler.QueueMetrics;
+import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler;
 import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
+import org.apache.hadoop.yarn.service.Service.STATE;
+import org.apache.hadoop.yarn.util.YarnVersionInfo;
 import org.apache.hadoop.yarn.webapp.GenericExceptionHandler;
-import org.codehaus.jettison.json.JSONArray;
+import org.apache.hadoop.yarn.webapp.WebServicesTestUtils;
 import org.codehaus.jettison.json.JSONException;
 import org.codehaus.jettison.json.JSONObject;
 import org.junit.Before;
 import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
 
 import com.google.inject.Guice;
 import com.google.inject.Injector;
@@ -105,997 +105,439 @@ public class TestRMWebServices extends JerseyTest {
   }
 
   @Test
-  public void testCluster() throws JSONException, Exception {
-    WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster")
-        .accept(MediaType.APPLICATION_JSON).get(JSONObject.class);
-    verifyClusterInfo(json);
-  }
-
-  @Test
-  public void testClusterSlash() throws JSONException, Exception {
-    WebResource r = resource();
-    // test with trailing "/" to make sure acts same as without slash
-    JSONObject json = r.path("ws").path("v1").path("cluster/")
-        .accept(MediaType.APPLICATION_JSON).get(JSONObject.class);
-    verifyClusterInfo(json);
-  }
-
-  @Test
-  public void testInfo() throws JSONException, Exception {
-    WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("info")
-        .accept(MediaType.APPLICATION_JSON).get(JSONObject.class);
-    verifyClusterInfo(json);
-  }
-
-  @Test
-  public void testInfoSlash() throws JSONException, Exception {
-    WebResource r = resource();
-    // test with trailing "/" to make sure acts same as without slash
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("info/")
-        .accept(MediaType.APPLICATION_JSON).get(JSONObject.class);
-    verifyClusterInfo(json);
-  }
-
-  public void verifyClusterInfo(JSONObject json) throws JSONException,
-      Exception {
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject clusterinfo = json.getJSONObject("clusterInfo");
-    assertEquals("correct number of elements", 9, clusterinfo.length());
-    String clusterid = clusterinfo.get("id").toString();
-    assertTrue("clusterId doesn't match: " + clusterid, clusterid.toString()
-        .matches("^\\d+"));
-    String startedon = clusterinfo.get("startedOn").toString();
-    assertTrue("startedOn doesn't match: " + startedon,
-        startedon.matches("^\\d+"));
-    String state = clusterinfo.get("state").toString();
-    assertTrue("stated doesn't match: " + state, state.matches("INITED"));
-    String rmVersion = clusterinfo.get("resourceManagerVersion").toString();
-    assertTrue("rm version doesn't match: " + rmVersion,
-        rmVersion.matches(".*"));
-    String rmBuildVersion = clusterinfo.get("resourceManagerBuildVersion")
-        .toString();
-    assertTrue("rm Build version doesn't match: " + rmBuildVersion,
-        rmBuildVersion.matches(".*"));
-    String rmVersionBuiltOn = clusterinfo.get("resourceManagerVersionBuiltOn")
-        .toString();
-    assertTrue(
-        "rm version built on doesn't match: " + rmVersionBuiltOn,
-        rmVersionBuiltOn
-            .matches("^\\w+\\s+\\w+\\s+\\d+\\s+\\d\\d:\\d\\d:\\d\\d\\s+\\w+\\s+\\d\\d\\d\\d"));
-    String hadoopVersion = clusterinfo.get("hadoopVersion").toString();
-    assertTrue("hadoop version doesn't match: " + hadoopVersion,
-        hadoopVersion.matches(".*"));
-    String hadoopBuildVersion = clusterinfo.get("hadoopBuildVersion")
-        .toString();
-    assertTrue("hadoop Build version doesn't match: " + hadoopBuildVersion,
-        hadoopBuildVersion.matches(".*"));
-    String hadoopVersionBuiltOn = clusterinfo.get("hadoopVersionBuiltOn")
-        .toString();
-    assertTrue(
-        "hadoop version built on doesn't match: " + hadoopVersionBuiltOn,
-        hadoopVersionBuiltOn
-            .matches("^\\w+\\s+\\w+\\s+\\d+\\s+\\d\\d:\\d\\d:\\d\\d\\s+\\w+\\s+\\d\\d\\d\\d"));
-  }
-
-  @Test
-  public void testClusterMetrics() throws JSONException, Exception {
-    WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("metrics")
-        .accept(MediaType.APPLICATION_JSON).get(JSONObject.class);
-    verifyClusterMetrics(json);
-  }
-
-  @Test
-  public void testClusterMetricsSlash() throws JSONException, Exception {
-    WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("metrics/")
-        .accept(MediaType.APPLICATION_JSON).get(JSONObject.class);
-    verifyClusterMetrics(json);
-  }
-
-  public void verifyClusterMetrics(JSONObject json) throws JSONException,
-      Exception {
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject clusterinfo = json.getJSONObject("clusterMetrics");
-    assertEquals("correct number of elements", 11, clusterinfo.length());
-    assertEquals("appsSubmitted doesn't match", 0,
-        clusterinfo.getInt("appsSubmitted"));
-    assertEquals("reservedMB doesn't match", 0,
-        clusterinfo.getInt("reservedMB"));
-    assertEquals("availableMB doesn't match", 0,
-        clusterinfo.getInt("availableMB"));
-    assertEquals("allocatedMB doesn't match", 0,
-        clusterinfo.getInt("allocatedMB"));
-    assertEquals("containersAllocated doesn't match", 0,
-        clusterinfo.getInt("containersAllocated"));
-    assertEquals("totalMB doesn't match", 0, clusterinfo.getInt("totalMB"));
-    assertEquals("totalNodes doesn't match", 0,
-        clusterinfo.getInt("totalNodes"));
-    assertEquals("lostNodes doesn't match", 0, clusterinfo.getInt("lostNodes"));
-    assertEquals("unhealthyNodes doesn't match", 0,
-        clusterinfo.getInt("unhealthyNodes"));
-    assertEquals("decommissionedNodes doesn't match", 0,
-        clusterinfo.getInt("decommissionedNodes"));
-    assertEquals("rebootedNodes doesn't match", 0,
-        clusterinfo.getInt("rebootedNodes"));
-  }
-
-  @Test
-  public void testClusterSchedulerFifo() throws JSONException, Exception {
+  public void testInfoXML() throws JSONException, Exception {
     WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("scheduler")
-        .accept(MediaType.APPLICATION_JSON).get(JSONObject.class);
-    verifyClusterSchedulerFifo(json);
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("info").accept("application/xml").get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType());
+    String xml = response.getEntity(String.class);
+    verifyClusterInfoXML(xml);
   }
 
   @Test
-  public void testClusterSchedulerFifoSlash() throws JSONException, Exception {
+  public void testInvalidUri() throws JSONException, Exception {
     WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster")
-        .path("scheduler/").accept(MediaType.APPLICATION_JSON)
-        .get(JSONObject.class);
-    verifyClusterSchedulerFifo(json);
-  }
-
-  public void verifyClusterSchedulerFifo(JSONObject json) throws JSONException,
-      Exception {
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject info = json.getJSONObject("scheduler");
-    assertEquals("correct number of elements", 1, info.length());
-    info = info.getJSONObject("schedulerInfo");
-    assertEquals("correct number of elements", 11, info.length());
-    assertEquals("type doesn't match", "fifoScheduler", info.getString("type"));
-    assertEquals("qstate doesn't match", QueueState.RUNNING.toString(),
-        info.getString("qstate"));
-    assertEquals("capacity doesn't match", 1.0, info.getDouble("capacity"), 0.0);
-    assertEquals("usedCapacity doesn't match", Float.NaN,
-        info.getDouble("usedCapacity"), 0.0);
-    assertEquals("minQueueMemoryCapacity doesn't match", 1024,
-        info.getInt("minQueueMemoryCapacity"));
-    assertEquals("maxQueueMemoryCapacity doesn't match", 10240,
-        info.getInt("maxQueueMemoryCapacity"));
-    assertEquals("maxQueueMemoryCapacity doesn't match", 10240,
-        info.getInt("maxQueueMemoryCapacity"));
-
-  }
-
-  @Test
-  public void testNodes() throws JSONException, Exception {
-    testNodesHelper("nodes");
-  }
-
-  @Test
-  public void testNodesSlash() throws JSONException, Exception {
-    testNodesHelper("nodes/");
-  }
-
-  @Test
-  public void testNodesQueryState() throws JSONException, Exception {
-    WebResource r = resource();
-    MockNM nm1 = rm.registerNode("h1:1234", 5120);
-    MockNM nm2 = rm.registerNode("h2:1235", 5121);
-    rm.sendNodeStarted(nm1);
-    rm.NMwaitForState(nm1.getNodeId(), RMNodeState.RUNNING);
-    rm.NMwaitForState(nm2.getNodeId(), RMNodeState.NEW);
-
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("nodes")
-        .queryParam("state", RMNodeState.RUNNING.toString())
-        .accept("application/json").get(JSONObject.class);
-
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject nodes = json.getJSONObject("nodes");
-    assertEquals("correct number of elements", 1, nodes.length());
-    JSONArray nodeArray = nodes.getJSONArray("node");
-    assertEquals("correct number of elements", 1, nodeArray.length());
-    JSONObject info = nodeArray.getJSONObject(0);
-
-    verifyNodeInfo(info, nm1, RMNodeState.RUNNING);
-  }
+    String responseStr = "";
+    try {
+      responseStr = r.path("ws").path("v1").path("cluster").path("bogus")
+          .accept(MediaType.APPLICATION_JSON).get(String.class);
+      fail("should have thrown exception on invalid uri");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+      assertEquals(Status.NOT_FOUND, response.getClientResponseStatus());
 
-  @Test
-  public void testNodesQueryStateNone() throws JSONException, Exception {
-    WebResource r = resource();
-    rm.registerNode("h1:1234", 5120);
-    rm.registerNode("h2:1235", 5121);
-
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("nodes")
-        .queryParam("state", RMNodeState.DECOMMISSIONED.toString())
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    assertEquals("nodes is not null", JSONObject.NULL, json.get("nodes"));
+      WebServicesTestUtils.checkStringMatch(
+          "error string exists and shouldn't", "", responseStr);
+    }
   }
 
   @Test
-  public void testNodesQueryStateInvalid() throws JSONException, Exception {
+  public void testInvalidUri2() throws JSONException, Exception {
     WebResource r = resource();
-    rm.registerNode("h1:1234", 5120);
-    rm.registerNode("h2:1235", 5121);
-
+    String responseStr = "";
     try {
-      r.path("ws").path("v1").path("cluster").path("nodes")
-          .queryParam("state", "BOGUSSTATE").accept("application/json")
-          .get(JSONObject.class);
-
-      fail("should have thrown exception querying invalid state");
+      responseStr = r.accept(MediaType.APPLICATION_JSON).get(String.class);
+      fail("should have thrown exception on invalid uri");
     } catch (UniformInterfaceException ue) {
       ClientResponse response = ue.getResponse();
-
-      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
-      JSONObject msg = response.getEntity(JSONObject.class);
-      JSONObject exception = msg.getJSONObject("RemoteException");
-      assertEquals("correct number of elements", 3, exception.length());
-      String message = exception.getString("message");
-      String type = exception.getString("exception");
-      String classname = exception.getString("javaClassName");
-      checkStringMatch(
-          "exception message",
-          "No enum const class org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNodeState.BOGUSSTATE",
-          message);
-      checkStringMatch("exception type", "IllegalArgumentException", type);
-      checkStringMatch("exception classname",
-          "java.lang.IllegalArgumentException", classname);
-
-    } finally {
-      rm.stop();
+      assertEquals(Status.NOT_FOUND, response.getClientResponseStatus());
+      WebServicesTestUtils.checkStringMatch(
+          "error string exists and shouldn't", "", responseStr);
     }
   }
 
   @Test
-  public void testNodesQueryHealthy() throws JSONException, Exception {
+  public void testInvalidAccept() throws JSONException, Exception {
     WebResource r = resource();
-    MockNM nm1 = rm.registerNode("h1:1234", 5120);
-    MockNM nm2 = rm.registerNode("h2:1235", 5121);
-    rm.sendNodeStarted(nm1);
-    rm.NMwaitForState(nm1.getNodeId(), RMNodeState.RUNNING);
-    rm.NMwaitForState(nm2.getNodeId(), RMNodeState.NEW);
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("nodes")
-        .queryParam("healthy", "true").accept("application/json")
-        .get(JSONObject.class);
-
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject nodes = json.getJSONObject("nodes");
-    assertEquals("correct number of elements", 1, nodes.length());
-    JSONArray nodeArray = nodes.getJSONArray("node");
-    assertEquals("correct number of elements", 2, nodeArray.length());
+    String responseStr = "";
+    try {
+      responseStr = r.path("ws").path("v1").path("cluster")
+          .accept(MediaType.TEXT_PLAIN).get(String.class);
+      fail("should have thrown exception on invalid uri");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+      assertEquals(Status.INTERNAL_SERVER_ERROR,
+          response.getClientResponseStatus());
+      WebServicesTestUtils.checkStringMatch(
+          "error string exists and shouldn't", "", responseStr);
+    }
   }
 
   @Test
-  public void testNodesQueryHealthyCase() throws JSONException, Exception {
+  public void testCluster() throws JSONException, Exception {
     WebResource r = resource();
-    MockNM nm1 = rm.registerNode("h1:1234", 5120);
-    MockNM nm2 = rm.registerNode("h2:1235", 5121);
-    rm.sendNodeStarted(nm1);
-    rm.NMwaitForState(nm1.getNodeId(), RMNodeState.RUNNING);
-    rm.NMwaitForState(nm2.getNodeId(), RMNodeState.NEW);
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("nodes")
-        .queryParam("healthy", "TRUe").accept("application/json")
-        .get(JSONObject.class);
-
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject nodes = json.getJSONObject("nodes");
-    assertEquals("correct number of elements", 1, nodes.length());
-    JSONArray nodeArray = nodes.getJSONArray("node");
-    assertEquals("correct number of elements", 2, nodeArray.length());
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
 
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterInfo(json);
   }
 
   @Test
-  public void testNodesQueryHealthyAndState() throws JSONException, Exception {
+  public void testClusterSlash() throws JSONException, Exception {
     WebResource r = resource();
-    MockNM nm1 = rm.registerNode("h1:1234", 5120);
-    MockNM nm2 = rm.registerNode("h2:1235", 5121);
-    rm.sendNodeStarted(nm1);
-    rm.NMwaitForState(nm2.getNodeId(), RMNodeState.NEW);
-    rm.NMwaitForState(nm1.getNodeId(), RMNodeState.RUNNING);
-    RMNodeImpl node = (RMNodeImpl) rm.getRMContext().getRMNodes()
-        .get(nm1.getNodeId());
-    NodeHealthStatus nodeHealth = node.getNodeHealthStatus();
-    nodeHealth.setHealthReport("test health report");
-    nodeHealth.setIsNodeHealthy(false);
-    node.handle(new RMNodeStatusEvent(nm1.getNodeId(), nodeHealth,
-        new ArrayList<ContainerStatus>(), null, null));
-    rm.NMwaitForState(nm1.getNodeId(), RMNodeState.UNHEALTHY);
-
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("nodes")
-        .queryParam("healthy", "true")
-        .queryParam("state", RMNodeState.RUNNING.toString())
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    assertEquals("nodes is not null", JSONObject.NULL, json.get("nodes"));
-  }
+    // test with trailing "/" to make sure acts same as without slash
+    ClientResponse response = r.path("ws").path("v1").path("cluster/")
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
 
-  @Test
-  public void testNodesQueryHealthyFalse() throws JSONException, Exception {
-    WebResource r = resource();
-    MockNM nm1 = rm.registerNode("h1:1234", 5120);
-    MockNM nm2 = rm.registerNode("h2:1235", 5121);
-    rm.sendNodeStarted(nm1);
-    rm.NMwaitForState(nm1.getNodeId(), RMNodeState.RUNNING);
-    rm.NMwaitForState(nm2.getNodeId(), RMNodeState.NEW);
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("nodes")
-        .queryParam("healthy", "false").accept("application/json")
-        .get(JSONObject.class);
-
-    assertEquals("correct number of elements", 1, json.length());
-    assertEquals("nodes is not null", JSONObject.NULL, json.get("nodes"));
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterInfo(json);
   }
 
   @Test
-  public void testNodesQueryHealthyInvalid() throws JSONException, Exception {
-    WebResource r = resource();
-    rm.registerNode("h1:1234", 5120);
-    rm.registerNode("h2:1235", 5121);
-
-    try {
-      r.path("ws").path("v1").path("cluster").path("nodes")
-          .queryParam("healthy", "tr").accept("application/json")
-          .get(JSONObject.class);
-      fail("should have thrown exception querying invalid healthy string");
-    } catch (UniformInterfaceException ue) {
-      ClientResponse response = ue.getResponse();
-      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
-      JSONObject msg = response.getEntity(JSONObject.class);
-      JSONObject exception = msg.getJSONObject("RemoteException");
-      assertEquals("correct number of elements", 3, exception.length());
-      String message = exception.getString("message");
-      String type = exception.getString("exception");
-      String classname = exception.getString("javaClassName");
-      checkStringMatch(
-          "exception message",
-          "java.lang.Exception: Error: You must specify either true or false to query on health",
-          message);
-      checkStringMatch("exception type", "BadRequestException", type);
-      checkStringMatch("exception classname",
-          "org.apache.hadoop.yarn.webapp.BadRequestException", classname);
-
-    } finally {
-      rm.stop();
-    }
-  }
-
-  public void testNodesHelper(String path) throws JSONException, Exception {
+  public void testClusterDefault() throws JSONException, Exception {
     WebResource r = resource();
-    MockNM nm1 = rm.registerNode("h1:1234", 5120);
-    MockNM nm2 = rm.registerNode("h2:1235", 5121);
-    JSONObject json = r.path("ws").path("v1").path("cluster").path(path)
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject nodes = json.getJSONObject("nodes");
-    assertEquals("correct number of elements", 1, nodes.length());
-    JSONArray nodeArray = nodes.getJSONArray("node");
-    assertEquals("correct number of elements", 2, nodeArray.length());
-    JSONObject info = nodeArray.getJSONObject(0);
-    String id = info.get("id").toString();
-
-    if (id.matches("h1:1234")) {
-      verifyNodeInfo(info, nm1, RMNodeState.NEW);
-      verifyNodeInfo(nodeArray.getJSONObject(1), nm2, RMNodeState.NEW);
-    } else {
-      verifyNodeInfo(info, nm2, RMNodeState.NEW);
-      verifyNodeInfo(nodeArray.getJSONObject(1), nm1, RMNodeState.NEW);
-    }
-  }
+    // test with trailing "/" to make sure acts same as without slash
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .get(ClientResponse.class);
 
-  @Test
-  public void testSingleNode() throws JSONException, Exception {
-    rm.registerNode("h1:1234", 5120);
-    MockNM nm2 = rm.registerNode("h2:1235", 5121);
-    testSingleNodeHelper("h2:1235", nm2);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterInfo(json);
   }
 
   @Test
-  public void testSingleNodeSlash() throws JSONException, Exception {
-    MockNM nm1 = rm.registerNode("h1:1234", 5120);
-    rm.registerNode("h2:1235", 5121);
-    testSingleNodeHelper("h1:1234/", nm1);
-  }
-
-  public void testSingleNodeHelper(String nodeid, MockNM nm)
-      throws JSONException, Exception {
+  public void testInfo() throws JSONException, Exception {
     WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("nodes")
-        .path(nodeid).accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject info = json.getJSONObject("node");
-    verifyNodeInfo(info, nm, RMNodeState.NEW);
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("info").accept(MediaType.APPLICATION_JSON)
+        .get(ClientResponse.class);
+
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterInfo(json);
   }
 
   @Test
-  public void testNonexistNode() throws JSONException, Exception {
-    rm.registerNode("h1:1234", 5120);
-    rm.registerNode("h2:1235", 5121);
+  public void testInfoSlash() throws JSONException, Exception {
+    // test with trailing "/" to make sure acts same as without slash
     WebResource r = resource();
-    try {
-      r.path("ws").path("v1").path("cluster").path("nodes")
-          .path("node_invalid:99").accept("application/json")
-          .get(JSONObject.class);
-
-      fail("should have thrown exception on non-existent nodeid");
-    } catch (UniformInterfaceException ue) {
-      ClientResponse response = ue.getResponse();
-      assertEquals(Status.NOT_FOUND, response.getClientResponseStatus());
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("info/").accept(MediaType.APPLICATION_JSON)
+        .get(ClientResponse.class);
 
-      JSONObject msg = response.getEntity(JSONObject.class);
-      JSONObject exception = msg.getJSONObject("RemoteException");
-      assertEquals("correct number of elements", 3, exception.length());
-      String message = exception.getString("message");
-      String type = exception.getString("exception");
-      String classname = exception.getString("javaClassName");
-      checkStringMatch("exception message",
-          "java.lang.Exception: nodeId, node_invalid:99, is not found", message);
-      checkStringMatch("exception type", "NotFoundException", type);
-      checkStringMatch("exception classname",
-          "org.apache.hadoop.yarn.webapp.NotFoundException", classname);
-
-    } finally {
-      rm.stop();
-    }
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterInfo(json);
   }
 
   @Test
-  public void testInvalidNode() throws JSONException, Exception {
-    rm.registerNode("h1:1234", 5120);
-    rm.registerNode("h2:1235", 5121);
-
+  public void testInfoDefault() throws JSONException, Exception {
     WebResource r = resource();
-    try {
-      r.path("ws").path("v1").path("cluster").path("nodes")
-          .path("node_invalid_foo").accept("application/json")
-          .get(JSONObject.class);
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("info").get(ClientResponse.class);
 
-      fail("should have thrown exception on non-existent nodeid");
-    } catch (UniformInterfaceException ue) {
-      ClientResponse response = ue.getResponse();
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterInfo(json);
+  }
 
-      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
-      JSONObject msg = response.getEntity(JSONObject.class);
-      JSONObject exception = msg.getJSONObject("RemoteException");
-      assertEquals("correct number of elements", 3, exception.length());
-      String message = exception.getString("message");
-      String type = exception.getString("exception");
-      String classname = exception.getString("javaClassName");
-      checkStringMatch("exception message",
-          "Invalid NodeId \\[node_invalid_foo\\]. Expected host:port", message);
-      checkStringMatch("exception type", "IllegalArgumentException", type);
-      checkStringMatch("exception classname",
-          "java.lang.IllegalArgumentException", classname);
-    } finally {
-      rm.stop();
+  public void verifyClusterInfoXML(String xml) throws JSONException, Exception {
+    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("clusterInfo");
+    assertEquals("incorrect number of elements", 1, nodes.getLength());
+
+    for (int i = 0; i < nodes.getLength(); i++) {
+      Element element = (Element) nodes.item(i);
+
+      verifyClusterGeneric(WebServicesTestUtils.getXmlLong(element, "id"),
+          WebServicesTestUtils.getXmlLong(element, "startedOn"),
+          WebServicesTestUtils.getXmlString(element, "state"),
+          WebServicesTestUtils.getXmlString(element, "hadoopVersionBuiltOn"),
+          WebServicesTestUtils.getXmlString(element, "hadoopBuildVersion"),
+          WebServicesTestUtils.getXmlString(element, "hadoopVersion"),
+          WebServicesTestUtils.getXmlString(element,
+              "resourceManagerVersionBuiltOn"),
+          WebServicesTestUtils.getXmlString(element,
+              "resourceManagerBuildVersion"),
+          WebServicesTestUtils.getXmlString(element, "resourceManagerVersion"));
     }
   }
 
-  public void verifyNodeInfo(JSONObject nodeInfo, MockNM nm,
-      RMNodeState expectedState) throws JSONException, Exception {
-    assertEquals("correct number of elements", 11, nodeInfo.length());
-    String state = nodeInfo.get("state").toString();
+  public void verifyClusterInfo(JSONObject json) throws JSONException,
+      Exception {
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject info = json.getJSONObject("clusterInfo");
+    assertEquals("incorrect number of elements", 9, info.length());
+    verifyClusterGeneric(info.getLong("id"), info.getLong("startedOn"),
+        info.getString("state"), info.getString("hadoopVersionBuiltOn"),
+        info.getString("hadoopBuildVersion"), info.getString("hadoopVersion"),
+        info.getString("resourceManagerVersionBuiltOn"),
+        info.getString("resourceManagerBuildVersion"),
+        info.getString("resourceManagerVersion"));
+
+  }
+
+  public void verifyClusterGeneric(long clusterid, long startedon,
+      String state, String hadoopVersionBuiltOn, String hadoopBuildVersion,
+      String hadoopVersion, String resourceManagerVersionBuiltOn,
+      String resourceManagerBuildVersion, String resourceManagerVersion) {
+
+    assertEquals("clusterId doesn't match: ", ResourceManager.clusterTimeStamp,
+        clusterid);
+    assertEquals("startedOn doesn't match: ", ResourceManager.clusterTimeStamp,
+        startedon);
     assertTrue("stated doesn't match: " + state,
-        state.matches(expectedState.toString()));
-    String rack = nodeInfo.get("rack").toString();
-    assertTrue("rack doesn't match: " + rack, rack.matches("/default-rack"));
-    String healthStatus = nodeInfo.get("healthStatus").toString();
-    assertTrue("healthStatus doesn't match: " + healthStatus,
-        healthStatus.matches("Healthy"));
-    String id = nodeInfo.get("id").toString();
-    assertTrue("id doesn't match, got: " + id + " expected: "
-        + nm.getNodeId().toString(), id.matches(nm.getNodeId().toString()));
-    String nodeHostName = nodeInfo.get("nodeHostName").toString();
-    assertTrue("hostname doesn't match, got: " + nodeHostName + " expected: "
-        + nm.getNodeId().getHost(),
-        nodeHostName.matches(nm.getNodeId().getHost()));
-
-    String nodeHTTPAddress = nodeInfo.get("nodeHTTPAddress").toString();
-    String expectedHttpAddress = nm.getNodeId().getHost() + ":"
-        + nm.getHttpPort();
-    assertTrue("nodeHTTPAddress doesn't match, got: " + nodeHTTPAddress
-        + " expected: " + expectedHttpAddress,
-        nodeHTTPAddress.matches(expectedHttpAddress));
-    // could use this for other checks
-    RMNode node = rm.getRMContext().getRMNodes().get(nm.getNodeId());
-    long lastHealthUpdate = nodeInfo.getLong("lastHealthUpdate");
-    long expectedHealthUpdate = node.getNodeHealthStatus()
-        .getLastHealthReportTime();
-    assertEquals("lastHealthUpdate doesn't match, got: " + lastHealthUpdate
-        + " expected: " + expectedHealthUpdate, expectedHealthUpdate,
-        lastHealthUpdate);
-    String healthReport = nodeInfo.get("healthReport").toString();
-    assertTrue("healthReport doesn't match: " + healthReport,
-        healthReport.matches("Healthy"));
-
-    int numContainers = nodeInfo.getInt("numContainers");
-    assertEquals("numContainers doesn't match: " + numContainers, 0,
-        numContainers);
-
-    long usedMemoryMB = nodeInfo.getLong("usedMemoryMB");
-    assertEquals("usedMemoryMB doesn't match: " + usedMemoryMB, 0, usedMemoryMB);
-
-    long availMemoryMB = nodeInfo.getLong("availMemoryMB");
-    assertEquals("availMemoryMB doesn't match: " + availMemoryMB, 0,
-        availMemoryMB);
-  }
+        state.matches(STATE.INITED.toString()));
 
-  @Test
-  public void testApps() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    RMApp app1 = rm.submitApp(1024);
-    amNodeManager.nodeHeartbeat(true);
-    testAppsHelper("apps", app1);
-    rm.stop();
+    WebServicesTestUtils.checkStringMatch("hadoopVersionBuiltOn",
+        VersionInfo.getDate(), hadoopVersionBuiltOn);
+    WebServicesTestUtils.checkStringMatch("hadoopBuildVersion",
+        VersionInfo.getBuildVersion(), hadoopBuildVersion);
+    WebServicesTestUtils.checkStringMatch("hadoopVersion",
+        VersionInfo.getVersion(), hadoopVersion);
 
+    WebServicesTestUtils.checkStringMatch("resourceManagerVersionBuiltOn",
+        YarnVersionInfo.getDate(), resourceManagerVersionBuiltOn);
+    WebServicesTestUtils.checkStringMatch("resourceManagerBuildVersion",
+        YarnVersionInfo.getBuildVersion(), resourceManagerBuildVersion);
+    WebServicesTestUtils.checkStringMatch("resourceManagerVersion",
+        YarnVersionInfo.getVersion(), resourceManagerVersion);
   }
 
   @Test
-  public void testAppsSlash() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    RMApp app1 = rm.submitApp(1024);
-    amNodeManager.nodeHeartbeat(true);
-    testAppsHelper("apps/", app1);
-    rm.stop();
-
-  }
-
-  public void testAppsHelper(String path, RMApp app) throws JSONException,
-      Exception {
+  public void testClusterMetrics() throws JSONException, Exception {
     WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path(path)
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject apps = json.getJSONObject("apps");
-    assertEquals("correct number of elements", 1, apps.length());
-    JSONArray array = apps.getJSONArray("app");
-    assertEquals("correct number of elements", 1, array.length());
-    verifyAppInfo(array.getJSONObject(0), app);
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("metrics").accept(MediaType.APPLICATION_JSON)
+        .get(ClientResponse.class);
 
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterMetricsJSON(json);
   }
 
   @Test
-  public void testAppsQueryState() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    RMApp app1 = rm.submitApp(1024);
-    amNodeManager.nodeHeartbeat(true);
+  public void testClusterMetricsSlash() throws JSONException, Exception {
     WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("apps")
-        .queryParam("state", RMAppState.ACCEPTED.toString())
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject apps = json.getJSONObject("apps");
-    assertEquals("correct number of elements", 1, apps.length());
-    JSONArray array = apps.getJSONArray("app");
-    assertEquals("correct number of elements", 1, array.length());
-    verifyAppInfo(array.getJSONObject(0), app1);
-    rm.stop();
-  }
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("metrics/").accept(MediaType.APPLICATION_JSON)
+        .get(ClientResponse.class);
 
-  @Test
-  public void testAppsQueryStateNone() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    rm.submitApp(1024);
-    amNodeManager.nodeHeartbeat(true);
-    WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("apps")
-        .queryParam("state", RMAppState.RUNNING.toString())
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    assertEquals("apps is not null", JSONObject.NULL, json.get("apps"));
-    rm.stop();
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterMetricsJSON(json);
   }
 
   @Test
-  public void testAppsQueryStateInvalid() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    rm.submitApp(1024);
-    amNodeManager.nodeHeartbeat(true);
+  public void testClusterMetricsDefault() throws JSONException, Exception {
     WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("metrics").get(ClientResponse.class);
 
-    try {
-      r.path("ws").path("v1").path("cluster").path("apps")
-          .queryParam("state", "INVALID_test").accept("application/json")
-          .get(JSONObject.class);
-      fail("should have thrown exception on invalid state query");
-    } catch (UniformInterfaceException ue) {
-      ClientResponse response = ue.getResponse();
-
-      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
-      JSONObject msg = response.getEntity(JSONObject.class);
-      JSONObject exception = msg.getJSONObject("RemoteException");
-      assertEquals("correct number of elements", 3, exception.length());
-      String message = exception.getString("message");
-      String type = exception.getString("exception");
-      String classname = exception.getString("javaClassName");
-      checkStringMatch(
-          "exception message",
-          "No enum const class org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppState.INVALID_test",
-          message);
-      checkStringMatch("exception type", "IllegalArgumentException", type);
-      checkStringMatch("exception classname",
-          "java.lang.IllegalArgumentException", classname);
-
-    } finally {
-      rm.stop();
-    }
-  }
-
-  @Test
-  public void testAppsQueryUser() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    rm.submitApp(1024);
-    rm.submitApp(1024);
-
-    amNodeManager.nodeHeartbeat(true);
-    WebResource r = resource();
-    JSONObject json = r
-        .path("ws")
-        .path("v1")
-        .path("cluster")
-        .path("apps")
-        .queryParam("user",
-            UserGroupInformation.getCurrentUser().getShortUserName())
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject apps = json.getJSONObject("apps");
-    assertEquals("correct number of elements", 1, apps.length());
-    JSONArray array = apps.getJSONArray("app");
-    assertEquals("correct number of elements", 2, array.length());
-    rm.stop();
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterMetricsJSON(json);
   }
 
   @Test
-  public void testAppsQueryQueue() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    rm.submitApp(1024);
-    rm.submitApp(1024);
-
-    amNodeManager.nodeHeartbeat(true);
+  public void testClusterMetricsXML() throws JSONException, Exception {
     WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("apps")
-        .queryParam("queue", "default").accept("application/json")
-        .get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject apps = json.getJSONObject("apps");
-    assertEquals("correct number of elements", 1, apps.length());
-    JSONArray array = apps.getJSONArray("app");
-    assertEquals("correct number of elements", 2, array.length());
-    rm.stop();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("metrics").accept("application/xml").get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType());
+    String xml = response.getEntity(String.class);
+    verifyClusterMetricsXML(xml);
   }
 
-  @Test
-  public void testAppsQueryLimit() throws JSONException, Exception {
-    rm.start();
-    rm.registerNode("amNM:1234", 2048);
-    rm.submitApp(1024);
-    rm.submitApp(1024);
-    rm.submitApp(1024);
-    WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("apps")
-        .queryParam("limit", "2").accept("application/json")
-        .get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject apps = json.getJSONObject("apps");
-    assertEquals("correct number of elements", 1, apps.length());
-    JSONArray array = apps.getJSONArray("app");
-    assertEquals("correct number of elements", 2, array.length());
-    rm.stop();
+  public void verifyClusterMetricsXML(String xml) throws JSONException,
+      Exception {
+    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("clusterMetrics");
+    assertEquals("incorrect number of elements", 1, nodes.getLength());
+
+    for (int i = 0; i < nodes.getLength(); i++) {
+      Element element = (Element) nodes.item(i);
+
+      verifyClusterMetrics(
+          WebServicesTestUtils.getXmlInt(element, "appsSubmitted"),
+          WebServicesTestUtils.getXmlInt(element, "reservedMB"),
+          WebServicesTestUtils.getXmlInt(element, "availableMB"),
+          WebServicesTestUtils.getXmlInt(element, "allocatedMB"),
+          WebServicesTestUtils.getXmlInt(element, "containersAllocated"),
+          WebServicesTestUtils.getXmlInt(element, "totalMB"),
+          WebServicesTestUtils.getXmlInt(element, "totalNodes"),
+          WebServicesTestUtils.getXmlInt(element, "lostNodes"),
+          WebServicesTestUtils.getXmlInt(element, "unhealthyNodes"),
+          WebServicesTestUtils.getXmlInt(element, "decommissionedNodes"),
+          WebServicesTestUtils.getXmlInt(element, "rebootedNodes"));
+    }
   }
 
-  @Test
-  public void testAppsQueryStartBegin() throws JSONException, Exception {
-    rm.start();
-    long start = System.currentTimeMillis();
-    Thread.sleep(1);
-    rm.registerNode("amNM:1234", 2048);
-    rm.submitApp(1024);
-    rm.submitApp(1024);
-    rm.submitApp(1024);
-    WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("apps")
-        .queryParam("startedTimeBegin", String.valueOf(start))
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject apps = json.getJSONObject("apps");
-    assertEquals("correct number of elements", 1, apps.length());
-    JSONArray array = apps.getJSONArray("app");
-    assertEquals("correct number of elements", 3, array.length());
-    rm.stop();
+  public void verifyClusterMetricsJSON(JSONObject json) throws JSONException,
+      Exception {
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject clusterinfo = json.getJSONObject("clusterMetrics");
+    assertEquals("incorrect number of elements", 11, clusterinfo.length());
+    verifyClusterMetrics(clusterinfo.getInt("appsSubmitted"),
+        clusterinfo.getInt("reservedMB"), clusterinfo.getInt("availableMB"),
+        clusterinfo.getInt("allocatedMB"),
+        clusterinfo.getInt("containersAllocated"),
+        clusterinfo.getInt("totalMB"), clusterinfo.getInt("totalNodes"),
+        clusterinfo.getInt("lostNodes"), clusterinfo.getInt("unhealthyNodes"),
+        clusterinfo.getInt("decommissionedNodes"),
+        clusterinfo.getInt("rebootedNodes"));
   }
 
-  @Test
-  public void testAppsQueryStartBeginSome() throws JSONException, Exception {
-    rm.start();
-    rm.registerNode("amNM:1234", 2048);
-    rm.submitApp(1024);
-    rm.submitApp(1024);
-    long start = System.currentTimeMillis();
-    Thread.sleep(1);
-    rm.submitApp(1024);
-    WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("apps")
-        .queryParam("startedTimeBegin", String.valueOf(start))
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject apps = json.getJSONObject("apps");
-    assertEquals("correct number of elements", 1, apps.length());
-    JSONArray array = apps.getJSONArray("app");
-    assertEquals("correct number of elements", 1, array.length());
-    rm.stop();
+  public void verifyClusterMetrics(int sub, int reservedMB, int availableMB,
+      int allocMB, int containersAlloc, int totalMB, int totalNodes,
+      int lostNodes, int unhealthyNodes, int decommissionedNodes,
+      int rebootedNodes) throws JSONException, Exception {
+
+    ResourceScheduler rs = rm.getResourceScheduler();
+    QueueMetrics metrics = rs.getRootQueueMetrics();
+    ClusterMetrics clusterMetrics = ClusterMetrics.getMetrics();
+    final long MB_IN_GB = 1024;
+
+    long totalMBExpect = (metrics.getReservedGB() * MB_IN_GB)
+        + (metrics.getAvailableGB() * MB_IN_GB)
+        + (metrics.getAllocatedGB() * MB_IN_GB);
+
+    assertEquals("appsSubmitted doesn't match", metrics.getAppsSubmitted(), sub);
+    assertEquals("reservedMB doesn't match",
+        metrics.getReservedGB() * MB_IN_GB, reservedMB);
+    assertEquals("availableMB doesn't match", metrics.getAvailableGB()
+        * MB_IN_GB, availableMB);
+    assertEquals("allocatedMB doesn't match", metrics.getAllocatedGB()
+        * MB_IN_GB, allocMB);
+    assertEquals("containersAllocated doesn't match", 0, containersAlloc);
+    assertEquals("totalMB doesn't match", totalMBExpect, totalMB);
+    assertEquals("totalNodes doesn't match", clusterMetrics.getNumNMs(),
+        totalNodes);
+    assertEquals("lostNodes doesn't match", clusterMetrics.getNumLostNMs(),
+        lostNodes);
+    assertEquals("unhealthyNodes doesn't match",
+        clusterMetrics.getUnhealthyNMs(), unhealthyNodes);
+    assertEquals("decommissionedNodes doesn't match",
+        clusterMetrics.getNumDecommisionedNMs(), decommissionedNodes);
+    assertEquals("rebootedNodes doesn't match",
+        clusterMetrics.getNumRebootedNMs(), rebootedNodes);
   }
 
   @Test
-  public void testAppsQueryStartEnd() throws JSONException, Exception {
-    rm.start();
-    rm.registerNode("amNM:1234", 2048);
-    long end = System.currentTimeMillis();
-    Thread.sleep(1);
-    rm.submitApp(1024);
-    rm.submitApp(1024);
-    rm.submitApp(1024);
+  public void testClusterSchedulerFifo() throws JSONException, Exception {
     WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("apps")
-        .queryParam("startedTimeEnd", String.valueOf(end))
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    assertEquals("apps is not null", JSONObject.NULL, json.get("apps"));
-    rm.stop();
-  }
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("scheduler").accept(MediaType.APPLICATION_JSON)
+        .get(ClientResponse.class);
 
-  @Test
-  public void testAppsQueryStartBeginEnd() throws JSONException, Exception {
-    rm.start();
-    rm.registerNode("amNM:1234", 2048);
-    long start = System.currentTimeMillis();
-    Thread.sleep(1);
-    rm.submitApp(1024);
-    rm.submitApp(1024);
-    long end = System.currentTimeMillis();
-    Thread.sleep(1);
-    rm.submitApp(1024);
-
-    WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("apps")
-        .queryParam("startedTimeBegin", String.valueOf(start))
-        .queryParam("startedTimeEnd", String.valueOf(end))
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject apps = json.getJSONObject("apps");
-    assertEquals("correct number of elements", 1, apps.length());
-    JSONArray array = apps.getJSONArray("app");
-    assertEquals("correct number of elements", 2, array.length());
-    rm.stop();
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterSchedulerFifo(json);
   }
 
   @Test
-  public void testAppsQueryFinishBegin() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    long start = System.currentTimeMillis();
-    Thread.sleep(1);
-    RMApp app1 = rm.submitApp(1024);
-    amNodeManager.nodeHeartbeat(true);
-    // finish App
-    MockAM am = rm
-        .sendAMLaunched(app1.getCurrentAppAttempt().getAppAttemptId());
-    am.registerAppAttempt();
-    am.unregisterAppAttempt();
-    rm.submitApp(1024);
-    rm.submitApp(1024);
-
+  public void testClusterSchedulerFifoSlash() throws JSONException, Exception {
     WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("apps")
-        .queryParam("finishedTimeBegin", String.valueOf(start))
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject apps = json.getJSONObject("apps");
-    assertEquals("correct number of elements", 1, apps.length());
-    JSONArray array = apps.getJSONArray("app");
-    assertEquals("correct number of elements", 1, array.length());
-    rm.stop();
-  }
-
-  @Test
-  public void testAppsQueryFinishEnd() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    RMApp app1 = rm.submitApp(1024);
-    amNodeManager.nodeHeartbeat(true);
-    // finish App
-    MockAM am = rm
-        .sendAMLaunched(app1.getCurrentAppAttempt().getAppAttemptId());
-    am.registerAppAttempt();
-    am.unregisterAppAttempt();
-
-    rm.submitApp(1024);
-    rm.submitApp(1024);
-    long end = System.currentTimeMillis();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("scheduler/").accept(MediaType.APPLICATION_JSON)
+        .get(ClientResponse.class);
 
-    WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("apps")
-        .queryParam("finishedTimeEnd", String.valueOf(end))
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject apps = json.getJSONObject("apps");
-    assertEquals("correct number of elements", 1, apps.length());
-    JSONArray array = apps.getJSONArray("app");
-    assertEquals("correct number of elements", 3, array.length());
-    rm.stop();
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterSchedulerFifo(json);
   }
 
   @Test
-  public void testAppsQueryFinishBeginEnd() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    long start = System.currentTimeMillis();
-    Thread.sleep(1);
-    RMApp app1 = rm.submitApp(1024);
-    amNodeManager.nodeHeartbeat(true);
-    // finish App
-    MockAM am = rm
-        .sendAMLaunched(app1.getCurrentAppAttempt().getAppAttemptId());
-    am.registerAppAttempt();
-    am.unregisterAppAttempt();
-
-    rm.submitApp(1024);
-    rm.submitApp(1024);
-    long end = System.currentTimeMillis();
-
+  public void testClusterSchedulerFifoDefault() throws JSONException, Exception {
     WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("apps")
-        .queryParam("finishedTimeBegin", String.valueOf(start))
-        .queryParam("finishedTimeEnd", String.valueOf(end))
-        .accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    JSONObject apps = json.getJSONObject("apps");
-    assertEquals("correct number of elements", 1, apps.length());
-    JSONArray array = apps.getJSONArray("app");
-    assertEquals("correct number of elements", 1, array.length());
-    rm.stop();
-  }
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("scheduler").get(ClientResponse.class);
 
-  @Test
-  public void testSingleApp() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    RMApp app1 = rm.submitApp(1024);
-    amNodeManager.nodeHeartbeat(true);
-    testSingleAppsHelper(app1.getApplicationId().toString(), app1);
-    rm.stop();
-  }
-
-  @Test
-  public void testSingleAppsSlash() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    RMApp app1 = rm.submitApp(1024);
-    amNodeManager.nodeHeartbeat(true);
-    testSingleAppsHelper(app1.getApplicationId().toString() + "/", app1);
-    rm.stop();
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterSchedulerFifo(json);
   }
 
   @Test
-  public void testInvalidApp() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    rm.submitApp(1024);
-    amNodeManager.nodeHeartbeat(true);
+  public void testClusterSchedulerFifoXML() throws JSONException, Exception {
     WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("scheduler").accept(MediaType.APPLICATION_XML)
+        .get(ClientResponse.class);
 
-    try {
-      r.path("ws").path("v1").path("cluster").path("apps")
-          .path("application_invalid_12").accept("application/json")
-          .get(JSONObject.class);
-      fail("should have thrown exception on invalid appid");
-    } catch (UniformInterfaceException ue) {
-      ClientResponse response = ue.getResponse();
+    assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType());
+    String xml = response.getEntity(String.class);
+    verifySchedulerFifoXML(xml);
+  }
 
-      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
-      JSONObject msg = response.getEntity(JSONObject.class);
-      JSONObject exception = msg.getJSONObject("RemoteException");
-      assertEquals("correct number of elements", 3, exception.length());
-      String message = exception.getString("message");
-      String type = exception.getString("exception");
-      String classname = exception.getString("javaClassName");
-      checkStringMatch("exception message", "For input string: \"invalid\"",
-          message);
-      checkStringMatch("exception type", "NumberFormatException", type);
-      checkStringMatch("exception classname",
-          "java.lang.NumberFormatException", classname);
-
-    } finally {
-      rm.stop();
+  public void verifySchedulerFifoXML(String xml) throws JSONException,
+      Exception {
+    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+    DocumentBuilder db = dbf.newDocumentBuilder();
+    InputSource is = new InputSource();
+    is.setCharacterStream(new StringReader(xml));
+    Document dom = db.parse(is);
+    NodeList nodesSched = dom.getElementsByTagName("scheduler");
+    assertEquals("incorrect number of elements", 1, nodesSched.getLength());
+    NodeList nodes = dom.getElementsByTagName("schedulerInfo");
+    assertEquals("incorrect number of elements", 1, nodes.getLength());
+
+    for (int i = 0; i < nodes.getLength(); i++) {
+      Element element = (Element) nodes.item(i);
+
+      verifyClusterSchedulerFifoGeneric(
+          WebServicesTestUtils.getXmlAttrString(element, "xsi:type"),
+          WebServicesTestUtils.getXmlString(element, "qstate"),
+          WebServicesTestUtils.getXmlFloat(element, "capacity"),
+          WebServicesTestUtils.getXmlFloat(element, "usedCapacity"),
+          WebServicesTestUtils.getXmlInt(element, "minQueueMemoryCapacity"),
+          WebServicesTestUtils.getXmlInt(element, "maxQueueMemoryCapacity"),
+          WebServicesTestUtils.getXmlInt(element, "numNodes"),
+          WebServicesTestUtils.getXmlInt(element, "usedNodeCapacity"),
+          WebServicesTestUtils.getXmlInt(element, "availNodeCapacity"),
+          WebServicesTestUtils.getXmlInt(element, "totalNodeCapacity"),
+          WebServicesTestUtils.getXmlInt(element, "numContainers"));
     }
   }
 
-  @Test
-  public void testNonexistApp() throws JSONException, Exception {
-    rm.start();
-    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
-    rm.submitApp(1024);
-    amNodeManager.nodeHeartbeat(true);
-    WebResource r = resource();
+  public void verifyClusterSchedulerFifo(JSONObject json) throws JSONException,
+      Exception {
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject info = json.getJSONObject("scheduler");
+    assertEquals("incorrect number of elements", 1, info.length());
+    info = info.getJSONObject("schedulerInfo");
+    assertEquals("incorrect number of elements", 11, info.length());
 
-    try {
-      r.path("ws").path("v1").path("cluster").path("apps")
-          .path("application_00000_0099").accept("application/json")
-          .get(JSONObject.class);
-      fail("should have thrown exception on invalid appid");
-    } catch (UniformInterfaceException ue) {
-      ClientResponse response = ue.getResponse();
+    verifyClusterSchedulerFifoGeneric(info.getString("type"),
+        info.getString("qstate"), (float) info.getDouble("capacity"),
+        (float) info.getDouble("usedCapacity"),
+        info.getInt("minQueueMemoryCapacity"),
+        info.getInt("maxQueueMemoryCapacity"), info.getInt("numNodes"),
+        info.getInt("usedNodeCapacity"), info.getInt("availNodeCapacity"),
+        info.getInt("totalNodeCapacity"), info.getInt("numContainers"));
 
-      assertEquals(Status.NOT_FOUND, response.getClientResponseStatus());
-      JSONObject msg = response.getEntity(JSONObject.class);
-      JSONObject exception = msg.getJSONObject("RemoteException");
-      assertEquals("correct number of elements", 3, exception.length());
-      String message = exception.getString("message");
-      String type = exception.getString("exception");
-      String classname = exception.getString("javaClassName");
-      checkStringMatch("exception message",
-          "java.lang.Exception: app with id: application_00000_0099 not found",
-          message);
-      checkStringMatch("exception type", "NotFoundException", type);
-      checkStringMatch("exception classname",
-          "org.apache.hadoop.yarn.webapp.NotFoundException", classname);
-    } finally {
-      rm.stop();
-    }
   }
 
-  public void testSingleAppsHelper(String path, RMApp app)
+  public void verifyClusterSchedulerFifoGeneric(String type, String state,
+      float capacity, float usedCapacity, int minQueueCapacity,
+      int maxQueueCapacity, int numNodes, int usedNodeCapacity,
+      int availNodeCapacity, int totalNodeCapacity, int numContainers)
       throws JSONException, Exception {
-    WebResource r = resource();
-    JSONObject json = r.path("ws").path("v1").path("cluster").path("apps")
-        .path(path).accept("application/json").get(JSONObject.class);
-    assertEquals("correct number of elements", 1, json.length());
-    verifyAppInfo(json.getJSONObject("app"), app);
-  }
-
-  public void verifyAppInfo(JSONObject info, RMApp app) throws JSONException,
-      Exception {
 
-    // 15 because trackingUrl not assigned yet
-    assertEquals("correct number of elements", 15, info.length());
-    String id = info.getString("id");
-    String expectedId = app.getApplicationId().toString();
-    checkStringMatch("id", expectedId, id);
-
-    String user = info.getString("user");
-    String expectedUser = app.getUser();
-    checkStringMatch("user", expectedUser, user);
-
-    checkStringMatch("name", "", info.getString("name"));
-    checkStringMatch("queue", "default", info.getString("queue"));
-    checkStringMatch("state", RMAppState.ACCEPTED.toString(),
-        info.getString("state"));
-    checkStringMatch("finalStatus",
-        FinalApplicationStatus.UNDEFINED.toString(),
-        info.getString("finalStatus"));
-    assertEquals("progress doesn't match", 0, info.getDouble("progress"), 0.0);
-    checkStringMatch("trackingUI", "UNASSIGNED", info.getString("trackingUI"));
-    checkStringMatch("diagnostics", "", info.getString("diagnostics"));
-    assertEquals("clusterId doesn't match", ResourceManager.clusterTimeStamp,
-        info.getLong("clusterId"));
-    assertEquals("startedTime doesn't match", app.getStartTime(),
-        info.getLong("startedTime"));
-    assertEquals("finishedTime doesn't match", app.getFinishTime(),
-        info.getLong("finishedTime"));
-    assertTrue("elapsed time not greater than 0",
-        info.getLong("elapsedTime") > 0);
-    checkStringMatch("amHostHttpAddress", app.getCurrentAppAttempt()
-        .getMasterContainer().getNodeHttpAddress(),
-        info.getString("amHostHttpAddress"));
-    assertTrue("amContainerLogs doesn't match",
-        info.getString("amContainerLogs").startsWith("http://"));
-  }
+    assertEquals("type doesn't match", "fifoScheduler", type);
+    assertEquals("qstate doesn't match", QueueState.RUNNING.toString(), state);
+    assertEquals("capacity doesn't match", 1.0, capacity, 0.0);
+    assertEquals("usedCapacity doesn't match", Float.NaN, usedCapacity, 0.0);
+    assertEquals("minQueueMemoryCapacity doesn't match", 1024, minQueueCapacity);
+    assertEquals("maxQueueMemoryCapacity doesn't match", 10240,
+        maxQueueCapacity);
+    assertEquals("numNodes doesn't match", 0, numNodes);
+    assertEquals("usedNodeCapacity doesn't match", 0, usedNodeCapacity);
+    assertEquals("availNodeCapacity doesn't match", 0, availNodeCapacity);
+    assertEquals("totalNodeCapacity doesn't match", 0, totalNodeCapacity);
+    assertEquals("numContainers doesn't match", 0, numContainers);
 
-  private void checkStringMatch(String print, String expected, String got) {
-    assertTrue(
-        print + " doesn't match, got: " + got + " expected: " + expected,
-        got.matches(expected));
   }
 
 }

+ 756 - 0
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesApps.java

@@ -0,0 +1,756 @@
+/**
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.StringReader;
+
+import javax.ws.rs.core.MediaType;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.yarn.server.resourcemanager.MockAM;
+import org.apache.hadoop.yarn.server.resourcemanager.MockNM;
+import org.apache.hadoop.yarn.server.resourcemanager.MockRM;
+import org.apache.hadoop.yarn.server.resourcemanager.RMContext;
+import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager;
+import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMApp;
+import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppState;
+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.Before;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.servlet.GuiceServletContextListener;
+import com.google.inject.servlet.ServletModule;
+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.guice.spi.container.servlet.GuiceContainer;
+import com.sun.jersey.test.framework.JerseyTest;
+import com.sun.jersey.test.framework.WebAppDescriptor;
+
+public class TestRMWebServicesApps extends JerseyTest {
+
+  private static MockRM rm;
+
+  private Injector injector = Guice.createInjector(new ServletModule() {
+    @Override
+    protected void configureServlets() {
+      bind(JAXBContextResolver.class);
+      bind(RMWebServices.class);
+      bind(GenericExceptionHandler.class);
+      rm = new MockRM(new Configuration());
+      bind(ResourceManager.class).toInstance(rm);
+      bind(RMContext.class).toInstance(rm.getRMContext());
+      bind(ApplicationACLsManager.class).toInstance(
+          rm.getApplicationACLsManager());
+      serve("/*").with(GuiceContainer.class);
+    }
+  });
+
+  public class GuiceServletConfig extends GuiceServletContextListener {
+
+    @Override
+    protected Injector getInjector() {
+      return injector;
+    }
+  }
+
+  @Before
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+  }
+
+  public TestRMWebServicesApps() {
+    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());
+  }
+
+  @Test
+  public void testApps() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    RMApp app1 = rm.submitApp(1024);
+    amNodeManager.nodeHeartbeat(true);
+    testAppsHelper("apps", app1, MediaType.APPLICATION_JSON);
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsSlash() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    RMApp app1 = rm.submitApp(1024);
+    amNodeManager.nodeHeartbeat(true);
+    testAppsHelper("apps/", app1, MediaType.APPLICATION_JSON);
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsDefault() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    RMApp app1 = rm.submitApp(1024);
+    amNodeManager.nodeHeartbeat(true);
+    testAppsHelper("apps/", app1, "");
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsXML() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    RMApp app1 = rm.submitApp(1024, "testwordcount", "user1");
+    amNodeManager.nodeHeartbeat(true);
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("apps").accept(MediaType.APPLICATION_XML)
+        .get(ClientResponse.class);
+    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 nodesApps = dom.getElementsByTagName("apps");
+    assertEquals("incorrect number of elements", 1, nodesApps.getLength());
+    NodeList nodes = dom.getElementsByTagName("app");
+    assertEquals("incorrect number of elements", 1, nodes.getLength());
+    verifyAppsXML(nodes, app1);
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsXMLMulti() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    rm.submitApp(1024, "testwordcount", "user1");
+    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)
+        .get(ClientResponse.class);
+    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 nodesApps = dom.getElementsByTagName("apps");
+    assertEquals("incorrect number of elements", 1, nodesApps.getLength());
+    NodeList nodes = dom.getElementsByTagName("app");
+    assertEquals("incorrect number of elements", 2, nodes.getLength());
+    rm.stop();
+  }
+
+  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);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject apps = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, apps.length());
+    JSONArray array = apps.getJSONArray("app");
+    assertEquals("incorrect number of elements", 1, array.length());
+    verifyAppInfo(array.getJSONObject(0), app);
+
+  }
+
+  @Test
+  public void testAppsQueryState() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    RMApp app1 = rm.submitApp(1024);
+    amNodeManager.nodeHeartbeat(true);
+    WebResource r = resource();
+
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("apps").queryParam("state", RMAppState.ACCEPTED.toString())
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject apps = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, apps.length());
+    JSONArray array = apps.getJSONArray("app");
+    assertEquals("incorrect number of elements", 1, array.length());
+    verifyAppInfo(array.getJSONObject(0), app1);
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsQueryStateNone() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    rm.submitApp(1024);
+    amNodeManager.nodeHeartbeat(true);
+    WebResource r = resource();
+
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("apps").queryParam("state", RMAppState.RUNNING.toString())
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("incorrect number of elements", 1, json.length());
+    assertEquals("apps is not null", JSONObject.NULL, json.get("apps"));
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsQueryStateInvalid() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    rm.submitApp(1024);
+    amNodeManager.nodeHeartbeat(true);
+    WebResource r = resource();
+
+    try {
+      r.path("ws").path("v1").path("cluster").path("apps")
+          .queryParam("state", "INVALID_test")
+          .accept(MediaType.APPLICATION_JSON).get(JSONObject.class);
+      fail("should have thrown exception on invalid state query");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils
+          .checkStringMatch(
+              "exception message",
+              "No enum const class org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppState.INVALID_test",
+              message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "IllegalArgumentException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "java.lang.IllegalArgumentException", classname);
+
+    } finally {
+      rm.stop();
+    }
+  }
+
+  @Test
+  public void testAppsQueryUser() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    rm.submitApp(1024);
+    rm.submitApp(1024);
+
+    amNodeManager.nodeHeartbeat(true);
+    WebResource r = resource();
+    ClientResponse response = r
+        .path("ws")
+        .path("v1")
+        .path("cluster")
+        .path("apps")
+        .queryParam("user",
+            UserGroupInformation.getCurrentUser().getShortUserName())
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject apps = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, apps.length());
+    JSONArray array = apps.getJSONArray("app");
+    assertEquals("incorrect number of elements", 2, array.length());
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsQueryQueue() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    rm.submitApp(1024);
+    rm.submitApp(1024);
+
+    amNodeManager.nodeHeartbeat(true);
+    WebResource r = resource();
+
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("apps").queryParam("queue", "default")
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject apps = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, apps.length());
+    JSONArray array = apps.getJSONArray("app");
+    assertEquals("incorrect number of elements", 2, array.length());
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsQueryLimit() throws JSONException, Exception {
+    rm.start();
+    rm.registerNode("amNM:1234", 2048);
+    rm.submitApp(1024);
+    rm.submitApp(1024);
+    rm.submitApp(1024);
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("apps").queryParam("limit", "2")
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject apps = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, apps.length());
+    JSONArray array = apps.getJSONArray("app");
+    assertEquals("incorrect number of elements", 2, array.length());
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsQueryStartBegin() throws JSONException, Exception {
+    rm.start();
+    long start = System.currentTimeMillis();
+    Thread.sleep(1);
+    rm.registerNode("amNM:1234", 2048);
+    rm.submitApp(1024);
+    rm.submitApp(1024);
+    rm.submitApp(1024);
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("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);
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject apps = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, apps.length());
+    JSONArray array = apps.getJSONArray("app");
+    assertEquals("incorrect number of elements", 3, array.length());
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsQueryStartBeginSome() throws JSONException, Exception {
+    rm.start();
+    rm.registerNode("amNM:1234", 2048);
+    rm.submitApp(1024);
+    rm.submitApp(1024);
+    long start = System.currentTimeMillis();
+    Thread.sleep(1);
+    rm.submitApp(1024);
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("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);
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject apps = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, apps.length());
+    JSONArray array = apps.getJSONArray("app");
+    assertEquals("incorrect number of elements", 1, array.length());
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsQueryStartEnd() throws JSONException, Exception {
+    rm.start();
+    rm.registerNode("amNM:1234", 2048);
+    long end = System.currentTimeMillis();
+    Thread.sleep(1);
+    rm.submitApp(1024);
+    rm.submitApp(1024);
+    rm.submitApp(1024);
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("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);
+    assertEquals("incorrect number of elements", 1, json.length());
+    assertEquals("apps is not null", JSONObject.NULL, json.get("apps"));
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsQueryStartBeginEnd() throws JSONException, Exception {
+    rm.start();
+    rm.registerNode("amNM:1234", 2048);
+    long start = System.currentTimeMillis();
+    Thread.sleep(1);
+    rm.submitApp(1024);
+    rm.submitApp(1024);
+    long end = System.currentTimeMillis();
+    Thread.sleep(1);
+    rm.submitApp(1024);
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("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());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject apps = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, apps.length());
+    JSONArray array = apps.getJSONArray("app");
+    assertEquals("incorrect number of elements", 2, array.length());
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsQueryFinishBegin() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    long start = System.currentTimeMillis();
+    Thread.sleep(1);
+    RMApp app1 = rm.submitApp(1024);
+    amNodeManager.nodeHeartbeat(true);
+    // finish App
+    MockAM am = rm
+        .sendAMLaunched(app1.getCurrentAppAttempt().getAppAttemptId());
+    am.registerAppAttempt();
+    am.unregisterAppAttempt();
+    rm.submitApp(1024);
+    rm.submitApp(1024);
+
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("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);
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject apps = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, apps.length());
+    JSONArray array = apps.getJSONArray("app");
+    assertEquals("incorrect number of elements", 1, array.length());
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsQueryFinishEnd() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    RMApp app1 = rm.submitApp(1024);
+    amNodeManager.nodeHeartbeat(true);
+    // finish App
+    MockAM am = rm
+        .sendAMLaunched(app1.getCurrentAppAttempt().getAppAttemptId());
+    am.registerAppAttempt();
+    am.unregisterAppAttempt();
+
+    rm.submitApp(1024);
+    rm.submitApp(1024);
+    long end = System.currentTimeMillis();
+
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("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);
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject apps = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, apps.length());
+    JSONArray array = apps.getJSONArray("app");
+    assertEquals("incorrect number of elements", 3, array.length());
+    rm.stop();
+  }
+
+  @Test
+  public void testAppsQueryFinishBeginEnd() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    long start = System.currentTimeMillis();
+    Thread.sleep(1);
+    RMApp app1 = rm.submitApp(1024);
+    amNodeManager.nodeHeartbeat(true);
+    // finish App
+    MockAM am = rm
+        .sendAMLaunched(app1.getCurrentAppAttempt().getAppAttemptId());
+    am.registerAppAttempt();
+    am.unregisterAppAttempt();
+
+    rm.submitApp(1024);
+    rm.submitApp(1024);
+    long end = System.currentTimeMillis();
+
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("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());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject apps = json.getJSONObject("apps");
+    assertEquals("incorrect number of elements", 1, apps.length());
+    JSONArray array = apps.getJSONArray("app");
+    assertEquals("incorrect number of elements", 1, array.length());
+    rm.stop();
+  }
+
+  @Test
+  public void testSingleApp() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    RMApp app1 = rm.submitApp(1024, "testwordcount", "user1");
+    amNodeManager.nodeHeartbeat(true);
+    testSingleAppsHelper(app1.getApplicationId().toString(), app1,
+        MediaType.APPLICATION_JSON);
+    rm.stop();
+  }
+
+  @Test
+  public void testSingleAppsSlash() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    RMApp app1 = rm.submitApp(1024);
+    amNodeManager.nodeHeartbeat(true);
+    testSingleAppsHelper(app1.getApplicationId().toString() + "/", app1,
+        MediaType.APPLICATION_JSON);
+    rm.stop();
+  }
+
+  @Test
+  public void testSingleAppsDefault() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    RMApp app1 = rm.submitApp(1024);
+    amNodeManager.nodeHeartbeat(true);
+    testSingleAppsHelper(app1.getApplicationId().toString() + "/", app1, "");
+    rm.stop();
+  }
+
+  @Test
+  public void testInvalidApp() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    rm.submitApp(1024);
+    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);
+      fail("should have thrown exception on invalid appid");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+
+      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils.checkStringMatch("exception message",
+          "For input string: \"invalid\"", message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "NumberFormatException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "java.lang.NumberFormatException", classname);
+
+    } finally {
+      rm.stop();
+    }
+  }
+
+  @Test
+  public void testNonexistApp() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    rm.submitApp(1024, "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);
+      fail("should have thrown exception on invalid appid");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+
+      assertEquals(Status.NOT_FOUND, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils.checkStringMatch("exception message",
+          "java.lang.Exception: app with id: application_00000_0099 not found",
+          message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "NotFoundException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "org.apache.hadoop.yarn.webapp.NotFoundException", classname);
+    } finally {
+      rm.stop();
+    }
+  }
+
+  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);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+
+    assertEquals("incorrect number of elements", 1, json.length());
+    verifyAppInfo(json.getJSONObject("app"), app);
+  }
+
+  @Test
+  public void testSingleAppsXML() throws JSONException, Exception {
+    rm.start();
+    MockNM amNodeManager = rm.registerNode("amNM:1234", 2048);
+    RMApp app1 = rm.submitApp(1024, "testwordcount", "user1");
+    amNodeManager.nodeHeartbeat(true);
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("apps").path(app1.getApplicationId().toString())
+        .accept(MediaType.APPLICATION_XML).get(ClientResponse.class);
+    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());
+    verifyAppsXML(nodes, app1);
+    rm.stop();
+  }
+
+  public void verifyAppsXML(NodeList nodes, RMApp app) throws JSONException,
+      Exception {
+
+     for (int i = 0; i < nodes.getLength(); i++) {
+      Element element = (Element) nodes.item(i);
+
+      verifyAppInfoGeneric(app,
+          WebServicesTestUtils.getXmlString(element, "id"),
+          WebServicesTestUtils.getXmlString(element, "user"),
+          WebServicesTestUtils.getXmlString(element, "name"),
+          WebServicesTestUtils.getXmlString(element, "queue"),
+          WebServicesTestUtils.getXmlString(element, "state"),
+          WebServicesTestUtils.getXmlString(element, "finalStatus"),
+          WebServicesTestUtils.getXmlFloat(element, "progress"),
+          WebServicesTestUtils.getXmlString(element, "trackingUI"),
+          WebServicesTestUtils.getXmlString(element, "diagnostics"),
+          WebServicesTestUtils.getXmlLong(element, "clusterId"),
+          WebServicesTestUtils.getXmlLong(element, "startedTime"),
+          WebServicesTestUtils.getXmlLong(element, "finishedTime"),
+          WebServicesTestUtils.getXmlLong(element, "elapsedTime"),
+          WebServicesTestUtils.getXmlString(element, "amHostHttpAddress"),
+          WebServicesTestUtils.getXmlString(element, "amContainerLogs"));
+    }
+  }
+
+  public void verifyAppInfo(JSONObject info, RMApp app) throws JSONException,
+      Exception {
+
+    // 15 because trackingUrl not assigned yet
+    assertEquals("incorrect number of elements", 15, info.length());
+
+    verifyAppInfoGeneric(app, info.getString("id"), info.getString("user"),
+        info.getString("name"), info.getString("queue"),
+        info.getString("state"), info.getString("finalStatus"),
+        (float) info.getDouble("progress"), info.getString("trackingUI"),
+        info.getString("diagnostics"), info.getLong("clusterId"),
+        info.getLong("startedTime"), info.getLong("finishedTime"),
+        info.getLong("elapsedTime"), info.getString("amHostHttpAddress"),
+        info.getString("amContainerLogs"));
+  }
+
+  public void verifyAppInfoGeneric(RMApp app, String id, String user,
+      String name, String queue, String state, String finalStatus,
+      float progress, String trackingUI, String diagnostics, long clusterId,
+      long startedTime, long finishedTime, long elapsedTime,
+      String amHostHttpAddress, String amContainerLogs) throws JSONException,
+      Exception {
+
+    WebServicesTestUtils.checkStringMatch("id", app.getApplicationId()
+        .toString(), id);
+    WebServicesTestUtils.checkStringMatch("user", app.getUser(), user);
+    WebServicesTestUtils.checkStringMatch("name", app.getName(), name);
+    WebServicesTestUtils.checkStringMatch("queue", app.getQueue(), queue);
+    WebServicesTestUtils.checkStringMatch("state", app.getState().toString(),
+        state);
+    WebServicesTestUtils.checkStringMatch("finalStatus", app
+        .getFinalApplicationStatus().toString(), finalStatus);
+    assertEquals("progress doesn't match", 0, progress, 0.0);
+    WebServicesTestUtils.checkStringMatch("trackingUI", "UNASSIGNED",
+        trackingUI);
+    WebServicesTestUtils.checkStringMatch("diagnostics", app.getDiagnostics()
+        .toString(), diagnostics);
+    assertEquals("clusterId doesn't match", ResourceManager.clusterTimeStamp,
+        clusterId);
+    assertEquals("startedTime doesn't match", app.getStartTime(), startedTime);
+    assertEquals("finishedTime doesn't match", app.getFinishTime(),
+        finishedTime);
+    assertTrue("elapsed time not greater than 0", elapsedTime > 0);
+    WebServicesTestUtils.checkStringMatch("amHostHttpAddress", app
+        .getCurrentAppAttempt().getMasterContainer().getNodeHttpAddress(),
+        amHostHttpAddress);
+    assertTrue("amContainerLogs doesn't match",
+        amContainerLogs.startsWith("http://"));
+  }
+
+}

+ 316 - 0
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesCapacitySched.java

@@ -0,0 +1,316 @@
+/**
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.StringReader;
+
+import javax.ws.rs.core.MediaType;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
+import org.apache.hadoop.yarn.server.resourcemanager.MockRM;
+import org.apache.hadoop.yarn.server.resourcemanager.RMContext;
+import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager;
+import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler;
+import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler;
+import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacitySchedulerConfiguration;
+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.Before;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.servlet.GuiceServletContextListener;
+import com.google.inject.servlet.ServletModule;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
+import com.sun.jersey.test.framework.JerseyTest;
+import com.sun.jersey.test.framework.WebAppDescriptor;
+
+public class TestRMWebServicesCapacitySched extends JerseyTest {
+
+  private static MockRM rm;
+  private CapacitySchedulerConfiguration csConf;
+
+  private Injector injector = Guice.createInjector(new ServletModule() {
+    @Override
+    protected void configureServlets() {
+      bind(JAXBContextResolver.class);
+      bind(RMWebServices.class);
+      bind(GenericExceptionHandler.class);
+      csConf = new CapacitySchedulerConfiguration();
+      csConf.setClass(YarnConfiguration.RM_SCHEDULER, CapacityScheduler.class,
+          ResourceScheduler.class);
+      setupQueueConfiguration(csConf);
+      rm = new MockRM(csConf);
+      bind(ResourceManager.class).toInstance(rm);
+      bind(RMContext.class).toInstance(rm.getRMContext());
+      bind(ApplicationACLsManager.class).toInstance(
+          rm.getApplicationACLsManager());
+      serve("/*").with(GuiceContainer.class);
+    }
+  });
+
+  public class GuiceServletConfig extends GuiceServletContextListener {
+
+    @Override
+    protected Injector getInjector() {
+      return injector;
+    }
+  }
+
+  private static void setupQueueConfiguration(
+      CapacitySchedulerConfiguration conf) {
+
+    // Define top-level queues
+    conf.setQueues(CapacityScheduler.ROOT, new String[] { "a", "b" });
+    conf.setCapacity(CapacityScheduler.ROOT, 100);
+
+    final String A = CapacityScheduler.ROOT + ".a";
+    conf.setCapacity(A, 10);
+    conf.setMaximumCapacity(A, 50);
+
+    final String B = CapacityScheduler.ROOT + ".b";
+    conf.setCapacity(B, 90);
+
+    // Define 2nd-level queues
+    final String A1 = A + ".a1";
+    final String A2 = A + ".a2";
+    conf.setQueues(A, new String[] { "a1", "a2" });
+    conf.setCapacity(A1, 30);
+    conf.setMaximumCapacity(A1, 50);
+
+    conf.setUserLimitFactor(A1, 100.0f);
+    conf.setCapacity(A2, 70);
+    conf.setUserLimitFactor(A2, 100.0f);
+
+    final String B1 = B + ".b1";
+    final String B2 = B + ".b2";
+    final String B3 = B + ".b3";
+    conf.setQueues(B, new String[] { "b1", "b2", "b3" });
+    conf.setCapacity(B1, 50);
+    conf.setUserLimitFactor(B1, 100.0f);
+    conf.setCapacity(B2, 30);
+    conf.setUserLimitFactor(B2, 100.0f);
+    conf.setCapacity(B3, 20);
+    conf.setUserLimitFactor(B3, 100.0f);
+
+  }
+
+  @Before
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+  }
+
+  public TestRMWebServicesCapacitySched() {
+    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());
+  }
+
+  @Test
+  public void testClusterScheduler() throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("scheduler").accept(MediaType.APPLICATION_JSON)
+        .get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterScheduler(json);
+  }
+
+  @Test
+  public void testClusterSchedulerSlash() throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("scheduler/").accept(MediaType.APPLICATION_JSON)
+        .get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterScheduler(json);
+  }
+
+  @Test
+  public void testClusterSchedulerDefault() throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("scheduler").get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    verifyClusterScheduler(json);
+  }
+
+  @Test
+  public void testClusterSchedulerXML() throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("scheduler/").accept(MediaType.APPLICATION_XML)
+        .get(ClientResponse.class);
+    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 scheduler = dom.getElementsByTagName("scheduler");
+    assertEquals("incorrect number of elements", 1, scheduler.getLength());
+    NodeList schedulerInfo = dom.getElementsByTagName("schedulerInfo");
+    assertEquals("incorrect number of elements", 1, schedulerInfo.getLength());
+    verifyClusterSchedulerXML(schedulerInfo);
+  }
+
+  public void verifyClusterSchedulerXML(NodeList nodes) throws Exception {
+
+    for (int i = 0; i < nodes.getLength(); i++) {
+      Element element = (Element) nodes.item(i);
+
+      verifyClusterSchedulerGeneric(
+          WebServicesTestUtils.getXmlAttrString(element, "xsi:type"),
+          WebServicesTestUtils.getXmlFloat(element, "usedCapacity"),
+          WebServicesTestUtils.getXmlFloat(element, "capacity"),
+          WebServicesTestUtils.getXmlFloat(element, "maxCapacity"),
+          WebServicesTestUtils.getXmlString(element, "queueName"));
+
+      NodeList queues = element.getElementsByTagName("queues");
+      for (int j = 0; j < queues.getLength(); j++) {
+        Element qElem = (Element) queues.item(j);
+        String qName = WebServicesTestUtils.getXmlString(qElem, "queueName");
+        String q = CapacityScheduler.ROOT + "." + qName;
+        verifySubQueueXML(qElem, q);
+      }
+    }
+  }
+
+  public void verifySubQueueXML(Element qElem, String q) throws Exception {
+
+    verifySubQueueGeneric(q,
+        WebServicesTestUtils.getXmlFloat(qElem, "usedCapacity"),
+        WebServicesTestUtils.getXmlFloat(qElem, "capacity"),
+        WebServicesTestUtils.getXmlFloat(qElem, "maxCapacity"),
+        WebServicesTestUtils.getXmlString(qElem, "queueName"),
+        WebServicesTestUtils.getXmlString(qElem, "state"));
+
+    NodeList queues = qElem.getElementsByTagName("subQueues");
+    if (queues != null) {
+      for (int j = 0; j < queues.getLength(); j++) {
+        Element subqElem = (Element) queues.item(j);
+        String qName = WebServicesTestUtils.getXmlString(subqElem, "queueName");
+        String q2 = q + "." + qName;
+        verifySubQueueXML(subqElem, q2);
+      }
+    }
+  }
+
+  private void verifyClusterScheduler(JSONObject json) throws JSONException,
+      Exception {
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject info = json.getJSONObject("scheduler");
+    assertEquals("incorrect number of elements", 1, info.length());
+    info = info.getJSONObject("schedulerInfo");
+    assertEquals("incorrect number of elements", 6, info.length());
+    verifyClusterSchedulerGeneric(info.getString("type"),
+        (float) info.getDouble("usedCapacity"),
+        (float) info.getDouble("capacity"),
+        (float) info.getDouble("maxCapacity"), info.getString("queueName"));
+
+    JSONArray arr = info.getJSONArray("queues");
+    assertEquals("incorrect number of elements", 2, arr.length());
+
+    // test subqueues
+    for (int i = 0; i < arr.length(); i++) {
+      JSONObject obj = arr.getJSONObject(i);
+      String q = CapacityScheduler.ROOT + "." + obj.getString("queueName");
+      verifySubQueue(obj, q);
+    }
+  }
+
+  private void verifyClusterSchedulerGeneric(String type, float usedCapacity,
+      float capacity, float maxCapacity, String queueName) throws Exception {
+
+    assertTrue("type doesn't match", "capacityScheduler".matches(type));
+    assertEquals("usedCapacity doesn't match", 0, usedCapacity, 1e-3f);
+    assertEquals("capacity doesn't match", 100, capacity, 1e-3f);
+    assertEquals("maxCapacity doesn't match", 100, maxCapacity, 1e-3f);
+    assertTrue("queueName doesn't match", "root".matches(queueName));
+  }
+
+  private void verifySubQueue(JSONObject info, String q) throws JSONException,
+      Exception {
+    if (info.has("subQueues")) {
+      assertEquals("incorrect number of elements", 6, info.length());
+    } else {
+      assertEquals("incorrect number of elements", 5, info.length());
+    }
+    verifySubQueueGeneric(q, (float) info.getDouble("usedCapacity"),
+        (float) info.getDouble("capacity"),
+        (float) info.getDouble("maxCapacity"), info.getString("queueName"),
+        info.getString("state"));
+
+    if (info.has("subQueues")) {
+      JSONArray arr = info.getJSONArray("subQueues");
+      // test subqueues
+      for (int i = 0; i < arr.length(); i++) {
+        JSONObject obj = arr.getJSONObject(i);
+        String q2 = q + "." + obj.getString("queueName");
+        verifySubQueue(obj, q2);
+      }
+    }
+  }
+
+  private void verifySubQueueGeneric(String q, float usedCapacity,
+      float capacity, float maxCapacity, String qname, String state)
+      throws Exception {
+    String[] qArr = q.split("\\.");
+    assertTrue("q name invalid: " + q, qArr.length > 1);
+    String qshortName = qArr[qArr.length - 1];
+
+    assertEquals("usedCapacity doesn't match", 0, usedCapacity, 1e-3f);
+    assertEquals("capacity doesn't match", csConf.getCapacity(q), capacity,
+        1e-3f);
+    float expectCapacity = csConf.getMaximumCapacity(q);
+    if (CapacitySchedulerConfiguration.UNDEFINED == expectCapacity) {
+      expectCapacity = 100;
+    }
+    assertEquals("maxCapacity doesn't match", expectCapacity, maxCapacity,
+        1e-3f);
+    assertTrue("queueName doesn't match, got: " + qname + " expected: " + q,
+        qshortName.matches(qname));
+    assertTrue("state doesn't match",
+        (csConf.getState(q).toString()).matches(state));
+
+  }
+}

+ 601 - 0
hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesNodes.java

@@ -0,0 +1,601 @@
+/**
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+
+import javax.ws.rs.core.MediaType;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.yarn.api.records.ContainerStatus;
+import org.apache.hadoop.yarn.api.records.NodeHealthStatus;
+import org.apache.hadoop.yarn.server.resourcemanager.MockNM;
+import org.apache.hadoop.yarn.server.resourcemanager.MockRM;
+import org.apache.hadoop.yarn.server.resourcemanager.RMContext;
+import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager;
+import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNode;
+import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNodeImpl;
+import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNodeState;
+import org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNodeStatusEvent;
+import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler;
+import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerNodeReport;
+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.Before;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.servlet.GuiceServletContextListener;
+import com.google.inject.servlet.ServletModule;
+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.guice.spi.container.servlet.GuiceContainer;
+import com.sun.jersey.test.framework.JerseyTest;
+import com.sun.jersey.test.framework.WebAppDescriptor;
+
+public class TestRMWebServicesNodes extends JerseyTest {
+
+  private static MockRM rm;
+
+  private Injector injector = Guice.createInjector(new ServletModule() {
+    @Override
+    protected void configureServlets() {
+      bind(JAXBContextResolver.class);
+      bind(RMWebServices.class);
+      bind(GenericExceptionHandler.class);
+      rm = new MockRM(new Configuration());
+      bind(ResourceManager.class).toInstance(rm);
+      bind(RMContext.class).toInstance(rm.getRMContext());
+      bind(ApplicationACLsManager.class).toInstance(
+          rm.getApplicationACLsManager());
+      serve("/*").with(GuiceContainer.class);
+    }
+  });
+
+  public class GuiceServletConfig extends GuiceServletContextListener {
+
+    @Override
+    protected Injector getInjector() {
+      return injector;
+    }
+  }
+
+  @Before
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+  }
+
+  public TestRMWebServicesNodes() {
+    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());
+  }
+
+  @Test
+  public void testNodes() throws JSONException, Exception {
+    testNodesHelper("nodes", MediaType.APPLICATION_JSON);
+  }
+
+  @Test
+  public void testNodesSlash() throws JSONException, Exception {
+    testNodesHelper("nodes/", MediaType.APPLICATION_JSON);
+  }
+
+  @Test
+  public void testNodesDefault() throws JSONException, Exception {
+    testNodesHelper("nodes/", "");
+  }
+
+  @Test
+  public void testNodesQueryState() throws JSONException, Exception {
+    WebResource r = resource();
+    MockNM nm1 = rm.registerNode("h1:1234", 5120);
+    MockNM nm2 = rm.registerNode("h2:1235", 5121);
+    rm.sendNodeStarted(nm1);
+    rm.NMwaitForState(nm1.getNodeId(), RMNodeState.RUNNING);
+    rm.NMwaitForState(nm2.getNodeId(), RMNodeState.NEW);
+
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("nodes").queryParam("state", RMNodeState.RUNNING.toString())
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject nodes = json.getJSONObject("nodes");
+    assertEquals("incorrect number of elements", 1, nodes.length());
+    JSONArray nodeArray = nodes.getJSONArray("node");
+    assertEquals("incorrect number of elements", 1, nodeArray.length());
+    JSONObject info = nodeArray.getJSONObject(0);
+
+    verifyNodeInfo(info, nm1);
+  }
+
+  @Test
+  public void testNodesQueryStateNone() throws JSONException, Exception {
+    WebResource r = resource();
+    rm.registerNode("h1:1234", 5120);
+    rm.registerNode("h2:1235", 5121);
+
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("nodes")
+        .queryParam("state", RMNodeState.DECOMMISSIONED.toString())
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("incorrect number of elements", 1, json.length());
+    assertEquals("nodes is not null", JSONObject.NULL, json.get("nodes"));
+  }
+
+  @Test
+  public void testNodesQueryStateInvalid() throws JSONException, Exception {
+    WebResource r = resource();
+    rm.registerNode("h1:1234", 5120);
+    rm.registerNode("h2:1235", 5121);
+
+    try {
+      r.path("ws").path("v1").path("cluster").path("nodes")
+          .queryParam("state", "BOGUSSTATE").accept(MediaType.APPLICATION_JSON)
+          .get(JSONObject.class);
+
+      fail("should have thrown exception querying invalid state");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+
+      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils
+          .checkStringMatch(
+              "exception message",
+              "No enum const class org.apache.hadoop.yarn.server.resourcemanager.rmnode.RMNodeState.BOGUSSTATE",
+              message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "IllegalArgumentException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "java.lang.IllegalArgumentException", classname);
+
+    } finally {
+      rm.stop();
+    }
+  }
+
+  @Test
+  public void testNodesQueryHealthy() throws JSONException, Exception {
+    WebResource r = resource();
+    MockNM nm1 = rm.registerNode("h1:1234", 5120);
+    MockNM nm2 = rm.registerNode("h2:1235", 5121);
+    rm.sendNodeStarted(nm1);
+    rm.NMwaitForState(nm1.getNodeId(), RMNodeState.RUNNING);
+    rm.NMwaitForState(nm2.getNodeId(), RMNodeState.NEW);
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("nodes").queryParam("healthy", "true")
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject nodes = json.getJSONObject("nodes");
+    assertEquals("incorrect number of elements", 1, nodes.length());
+    JSONArray nodeArray = nodes.getJSONArray("node");
+    assertEquals("incorrect number of elements", 2, nodeArray.length());
+  }
+
+  @Test
+  public void testNodesQueryHealthyCase() throws JSONException, Exception {
+    WebResource r = resource();
+    MockNM nm1 = rm.registerNode("h1:1234", 5120);
+    MockNM nm2 = rm.registerNode("h2:1235", 5121);
+    rm.sendNodeStarted(nm1);
+    rm.NMwaitForState(nm1.getNodeId(), RMNodeState.RUNNING);
+    rm.NMwaitForState(nm2.getNodeId(), RMNodeState.NEW);
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("nodes").queryParam("healthy", "TRUe")
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("incorrect number of elements", 1, json.length());
+    JSONObject nodes = json.getJSONObject("nodes");
+    assertEquals("incorrect number of elements", 1, nodes.length());
+    JSONArray nodeArray = nodes.getJSONArray("node");
+    assertEquals("incorrect number of elements", 2, nodeArray.length());
+
+  }
+
+  @Test
+  public void testNodesQueryHealthyAndState() throws JSONException, Exception {
+    WebResource r = resource();
+    MockNM nm1 = rm.registerNode("h1:1234", 5120);
+    MockNM nm2 = rm.registerNode("h2:1235", 5121);
+    rm.sendNodeStarted(nm1);
+    rm.NMwaitForState(nm2.getNodeId(), RMNodeState.NEW);
+    rm.NMwaitForState(nm1.getNodeId(), RMNodeState.RUNNING);
+    RMNodeImpl node = (RMNodeImpl) rm.getRMContext().getRMNodes()
+        .get(nm1.getNodeId());
+    NodeHealthStatus nodeHealth = node.getNodeHealthStatus();
+    nodeHealth.setHealthReport("test health report");
+    nodeHealth.setIsNodeHealthy(false);
+    node.handle(new RMNodeStatusEvent(nm1.getNodeId(), nodeHealth,
+        new ArrayList<ContainerStatus>(), null, null));
+    rm.NMwaitForState(nm1.getNodeId(), RMNodeState.UNHEALTHY);
+
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("nodes").queryParam("healthy", "true")
+        .queryParam("state", RMNodeState.RUNNING.toString())
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("incorrect number of elements", 1, json.length());
+    assertEquals("nodes is not null", JSONObject.NULL, json.get("nodes"));
+  }
+
+  @Test
+  public void testNodesQueryHealthyFalse() throws JSONException, Exception {
+    WebResource r = resource();
+    MockNM nm1 = rm.registerNode("h1:1234", 5120);
+    MockNM nm2 = rm.registerNode("h2:1235", 5121);
+    rm.sendNodeStarted(nm1);
+    rm.NMwaitForState(nm1.getNodeId(), RMNodeState.RUNNING);
+    rm.NMwaitForState(nm2.getNodeId(), RMNodeState.NEW);
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("nodes").queryParam("healthy", "false")
+        .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
+    assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+    JSONObject json = response.getEntity(JSONObject.class);
+    assertEquals("incorrect number of elements", 1, json.length());
+    assertEquals("nodes is not null", JSONObject.NULL, json.get("nodes"));
+  }
+
+  @Test
+  public void testNodesQueryHealthyInvalid() throws JSONException, Exception {
+    WebResource r = resource();
+    rm.registerNode("h1:1234", 5120);
+    rm.registerNode("h2:1235", 5121);
+
+    try {
+      r.path("ws").path("v1").path("cluster").path("nodes")
+          .queryParam("healthy", "tr").accept(MediaType.APPLICATION_JSON)
+          .get(JSONObject.class);
+      fail("should have thrown exception querying invalid healthy string");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils
+          .checkStringMatch(
+              "exception message",
+              "java.lang.Exception: Error: You must specify either true or false to query on health",
+              message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "BadRequestException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "org.apache.hadoop.yarn.webapp.BadRequestException", classname);
+
+    } finally {
+      rm.stop();
+    }
+  }
+
+  public void testNodesHelper(String path, String media) throws JSONException,
+      Exception {
+    WebResource r = resource();
+    MockNM nm1 = rm.registerNode("h1:1234", 5120);
+    MockNM nm2 = rm.registerNode("h2:1235", 5121);
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path(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());
+    JSONObject nodes = json.getJSONObject("nodes");
+    assertEquals("incorrect number of elements", 1, nodes.length());
+    JSONArray nodeArray = nodes.getJSONArray("node");
+    assertEquals("incorrect number of elements", 2, nodeArray.length());
+    JSONObject info = nodeArray.getJSONObject(0);
+    String id = info.get("id").toString();
+
+    if (id.matches("h1:1234")) {
+      verifyNodeInfo(info, nm1);
+      verifyNodeInfo(nodeArray.getJSONObject(1), nm2);
+    } else {
+      verifyNodeInfo(info, nm2);
+      verifyNodeInfo(nodeArray.getJSONObject(1), nm1);
+    }
+  }
+
+  @Test
+  public void testSingleNode() throws JSONException, Exception {
+    rm.registerNode("h1:1234", 5120);
+    MockNM nm2 = rm.registerNode("h2:1235", 5121);
+    testSingleNodeHelper("h2:1235", nm2, MediaType.APPLICATION_JSON);
+  }
+
+  @Test
+  public void testSingleNodeSlash() throws JSONException, Exception {
+    MockNM nm1 = rm.registerNode("h1:1234", 5120);
+    rm.registerNode("h2:1235", 5121);
+    testSingleNodeHelper("h1:1234/", nm1, MediaType.APPLICATION_JSON);
+  }
+
+  @Test
+  public void testSingleNodeDefault() throws JSONException, Exception {
+    MockNM nm1 = rm.registerNode("h1:1234", 5120);
+    rm.registerNode("h2:1235", 5121);
+    testSingleNodeHelper("h1:1234/", nm1, "");
+  }
+
+  public void testSingleNodeHelper(String nodeid, MockNM nm, String media)
+      throws JSONException, Exception {
+    WebResource r = resource();
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("nodes").path(nodeid).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());
+    JSONObject info = json.getJSONObject("node");
+    verifyNodeInfo(info, nm);
+  }
+
+  @Test
+  public void testNonexistNode() throws JSONException, Exception {
+    rm.registerNode("h1:1234", 5120);
+    rm.registerNode("h2:1235", 5121);
+    WebResource r = resource();
+    try {
+      r.path("ws").path("v1").path("cluster").path("nodes")
+          .path("node_invalid:99").accept(MediaType.APPLICATION_JSON)
+          .get(JSONObject.class);
+
+      fail("should have thrown exception on non-existent nodeid");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+      assertEquals(Status.NOT_FOUND, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils
+          .checkStringMatch("exception message",
+              "java.lang.Exception: nodeId, node_invalid:99, is not found",
+              message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "NotFoundException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "org.apache.hadoop.yarn.webapp.NotFoundException", classname);
+
+    } finally {
+      rm.stop();
+    }
+  }
+
+  @Test
+  public void testInvalidNode() throws JSONException, Exception {
+    rm.registerNode("h1:1234", 5120);
+    rm.registerNode("h2:1235", 5121);
+
+    WebResource r = resource();
+    try {
+      r.path("ws").path("v1").path("cluster").path("nodes")
+          .path("node_invalid_foo").accept(MediaType.APPLICATION_JSON)
+          .get(JSONObject.class);
+
+      fail("should have thrown exception on non-existent nodeid");
+    } catch (UniformInterfaceException ue) {
+      ClientResponse response = ue.getResponse();
+
+      assertEquals(Status.BAD_REQUEST, response.getClientResponseStatus());
+      assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType());
+      JSONObject msg = response.getEntity(JSONObject.class);
+      JSONObject exception = msg.getJSONObject("RemoteException");
+      assertEquals("incorrect number of elements", 3, exception.length());
+      String message = exception.getString("message");
+      String type = exception.getString("exception");
+      String classname = exception.getString("javaClassName");
+      WebServicesTestUtils.checkStringMatch("exception message",
+          "Invalid NodeId \\[node_invalid_foo\\]. Expected host:port", message);
+      WebServicesTestUtils.checkStringMatch("exception type",
+          "IllegalArgumentException", type);
+      WebServicesTestUtils.checkStringMatch("exception classname",
+          "java.lang.IllegalArgumentException", classname);
+    } finally {
+      rm.stop();
+    }
+  }
+
+  @Test
+  public void testNodesXML() throws JSONException, Exception {
+    rm.start();
+    WebResource r = resource();
+    MockNM nm1 = rm.registerNode("h1:1234", 5120);
+    // MockNM nm2 = rm.registerNode("h2:1235", 5121);
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("nodes").accept(MediaType.APPLICATION_XML)
+        .get(ClientResponse.class);
+    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 nodesApps = dom.getElementsByTagName("nodes");
+    assertEquals("incorrect number of elements", 1, nodesApps.getLength());
+    NodeList nodes = dom.getElementsByTagName("node");
+    assertEquals("incorrect number of elements", 1, nodes.getLength());
+    verifyNodesXML(nodes, nm1);
+    rm.stop();
+  }
+
+  @Test
+  public void testSingleNodesXML() throws JSONException, Exception {
+    rm.start();
+    WebResource r = resource();
+    MockNM nm1 = rm.registerNode("h1:1234", 5120);
+    // MockNM nm2 = rm.registerNode("h2:1235", 5121);
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("nodes").path("h1:1234").accept(MediaType.APPLICATION_XML)
+        .get(ClientResponse.class);
+
+    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("node");
+    assertEquals("incorrect number of elements", 1, nodes.getLength());
+    verifyNodesXML(nodes, nm1);
+    rm.stop();
+  }
+
+  @Test
+  public void testNodes2XML() throws JSONException, Exception {
+    rm.start();
+    WebResource r = resource();
+    rm.registerNode("h1:1234", 5120);
+    rm.registerNode("h2:1235", 5121);
+    ClientResponse response = r.path("ws").path("v1").path("cluster")
+        .path("nodes").accept(MediaType.APPLICATION_XML)
+        .get(ClientResponse.class);
+    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 nodesApps = dom.getElementsByTagName("nodes");
+    assertEquals("incorrect number of elements", 1, nodesApps.getLength());
+    NodeList nodes = dom.getElementsByTagName("node");
+    assertEquals("incorrect number of elements", 2, nodes.getLength());
+    rm.stop();
+  }
+
+  public void verifyNodesXML(NodeList nodes, MockNM nm) throws JSONException,
+      Exception {
+    for (int i = 0; i < nodes.getLength(); i++) {
+      Element element = (Element) nodes.item(i);
+      verifyNodeInfoGeneric(nm,
+          WebServicesTestUtils.getXmlString(element, "state"),
+          WebServicesTestUtils.getXmlString(element, "rack"),
+          WebServicesTestUtils.getXmlString(element, "healthStatus"),
+          WebServicesTestUtils.getXmlString(element, "id"),
+          WebServicesTestUtils.getXmlString(element, "nodeHostName"),
+          WebServicesTestUtils.getXmlString(element, "nodeHTTPAddress"),
+          WebServicesTestUtils.getXmlLong(element, "lastHealthUpdate"),
+          WebServicesTestUtils.getXmlString(element, "healthReport"),
+          WebServicesTestUtils.getXmlInt(element, "numContainers"),
+          WebServicesTestUtils.getXmlLong(element, "usedMemoryMB"),
+          WebServicesTestUtils.getXmlLong(element, "availMemoryMB"));
+    }
+  }
+
+  public void verifyNodeInfo(JSONObject nodeInfo, MockNM nm)
+      throws JSONException, Exception {
+    assertEquals("incorrect number of elements", 11, nodeInfo.length());
+
+    verifyNodeInfoGeneric(nm, nodeInfo.getString("state"),
+        nodeInfo.getString("rack"), nodeInfo.getString("healthStatus"),
+        nodeInfo.getString("id"), nodeInfo.getString("nodeHostName"),
+        nodeInfo.getString("nodeHTTPAddress"),
+        nodeInfo.getLong("lastHealthUpdate"),
+        nodeInfo.getString("healthReport"), nodeInfo.getInt("numContainers"),
+        nodeInfo.getLong("usedMemoryMB"), nodeInfo.getLong("availMemoryMB"));
+
+  }
+
+  public void verifyNodeInfoGeneric(MockNM nm, String state, String rack,
+      String healthStatus, String id, String nodeHostName,
+      String nodeHTTPAddress, long lastHealthUpdate, String healthReport,
+      int numContainers, long usedMemoryMB, long availMemoryMB)
+      throws JSONException, Exception {
+
+    RMNode node = rm.getRMContext().getRMNodes().get(nm.getNodeId());
+    NodeHealthStatus health = node.getNodeHealthStatus();
+    ResourceScheduler sched = rm.getResourceScheduler();
+    SchedulerNodeReport report = sched.getNodeReport(nm.getNodeId());
+
+    WebServicesTestUtils.checkStringMatch("state", node.getState().toString(),
+        state);
+    WebServicesTestUtils.checkStringMatch("rack", node.getRackName(), rack);
+    WebServicesTestUtils.checkStringMatch("healthStatus", "Healthy",
+        healthStatus);
+    WebServicesTestUtils.checkStringMatch("id", nm.getNodeId().toString(), id);
+    WebServicesTestUtils.checkStringMatch("nodeHostName", nm.getNodeId()
+        .getHost(), nodeHostName);
+    WebServicesTestUtils.checkStringMatch("healthReport",
+        String.valueOf(health.getHealthReport()), healthReport);
+    String expectedHttpAddress = nm.getNodeId().getHost() + ":"
+        + nm.getHttpPort();
+    WebServicesTestUtils.checkStringMatch("nodeHTTPAddress",
+        expectedHttpAddress, nodeHTTPAddress);
+
+    long expectedHealthUpdate = health.getLastHealthReportTime();
+    assertEquals("lastHealthUpdate doesn't match, got: " + lastHealthUpdate
+        + " expected: " + expectedHealthUpdate, expectedHealthUpdate,
+        lastHealthUpdate);
+
+    if (report != null) {
+      assertEquals("numContainers doesn't match: " + numContainers,
+          report.getNumContainers(), numContainers);
+      assertEquals("usedMemoryMB doesn't match: " + usedMemoryMB, report
+          .getUsedResource().getMemory(), usedMemoryMB);
+      assertEquals("availMemoryMB doesn't match: " + availMemoryMB, report
+          .getAvailableResource().getMemory(), availMemoryMB);
+    }
+  }
+
+}