TestNMWebServicesApps.java

/**
 * 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.webapp.WebServicesTestUtils.assertResponseStatusCode;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import javax.ws.rs.BadRequestException;
import javax.ws.rs.NotFoundException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.security.Principal;
import java.util.HashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.http.JettyUtils;
import org.apache.hadoop.util.XMLUtils;
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
import org.apache.hadoop.yarn.api.records.NodeId;
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.NodeManager;
import org.apache.hadoop.yarn.server.nodemanager.NodeManager.NMContext;
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.health.NodeHealthCheckerService;
import org.apache.hadoop.yarn.server.nodemanager.webapp.WebServer.NMWebApp;
import org.apache.hadoop.yarn.server.nodemanager.webapp.dao.AppsInfo;
import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
import org.apache.hadoop.yarn.server.utils.BuilderUtils;
import org.apache.hadoop.yarn.webapp.GenericExceptionHandler;
import org.apache.hadoop.yarn.webapp.JerseyTestBase;
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 org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.jettison.JettisonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.TestProperties;

public class TestNMWebServicesApps extends JerseyTestBase {

  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");

  @Override
  protected javax.ws.rs.core.Application configure() {
    ResourceConfig config = new ResourceConfig();
    config.register(new JerseyBinder());
    config.register(NMWebServices.class);
    config.register(GenericExceptionHandler.class);
    config.register(new JettisonFeature()).register(JAXBContextResolver.class);
    forceSet(TestProperties.CONTAINER_PORT, JERSEY_RANDOM_PORT);
    return config;
  }

  private static class JerseyBinder extends AbstractBinder {

    @Override
    protected void configure() {
      conf.set(YarnConfiguration.NM_LOCAL_DIRS, testRootDir.getAbsolutePath());
      conf.set(YarnConfiguration.NM_LOG_DIRS, testLogDir.getAbsolutePath());
      LocalDirsHandlerService dirsHandler = new LocalDirsHandlerService();
      NodeHealthCheckerService healthChecker = new NodeHealthCheckerService(dirsHandler);
      healthChecker.init(conf);
      dirsHandler = healthChecker.getDiskHandler();
      aclsManager = new ApplicationACLsManager(conf);
      nmContext = new NodeManager.NMContext(null, null, dirsHandler,
          aclsManager, null, false, conf);
      NodeId nodeId = NodeId.newInstance("testhost.foo.com", 9999);
      ((NodeManager.NMContext)nmContext).setNodeId(nodeId);
      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");
        }

        @Override
        public long getVCoresAllocatedForContainers() {
          return new Long("4000");
        }


        @Override
        public boolean isVmemCheckEnabled() {
          return true;
        }

        @Override
        public boolean isPmemCheckEnabled() {
          return true;
        }
      };
      nmWebApp = new NMWebApp(resourceView, aclsManager, dirsHandler);

      nmWebApp = new NMWebApp(resourceView, aclsManager, dirsHandler);
      final HttpServletRequest request = mock(HttpServletRequest.class);
      final HttpServletResponse response = mock(HttpServletResponse.class);
      bind(nmContext).to(Context.class).named("nm");
      bind(nmWebApp).to(WebApp.class).named("webapp");
      bind(request).to(HttpServletRequest.class);
      bind(response).to(HttpServletResponse.class);
      bind(aclsManager).to(ApplicationACLsManager.class);
      bind(dirsHandler).to(LocalDirsHandlerService.class);
      bind(resourceView).to(ResourceView.class).named("view");
    }
  }

  @Before
  public void before() throws Exception {
    testRootDir.mkdirs();
    testLogDir.mkdir();
  }

  @AfterClass
  static public void cleanup() {
    FileUtil.fullyDelete(testRootDir);
    FileUtil.fullyDelete(testLogDir);
  }

  public TestNMWebServicesApps() {
  }

  @Test
  public void testNodeAppsNone() throws JSONException, Exception {
    WebTarget target = targetWithJsonObject();
    Response response = target.path("ws").path("v1").path("node").path("apps")
        .request(MediaType.APPLICATION_JSON).get(Response.class);
    assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
        response.getMediaType().toString());
    JSONObject json = response.readEntity(JSONObject.class);
    assertEquals("apps is not empty", new JSONObject("{apps:\"\"}"), json);
  }

  private HashMap<String, String> addAppContainers(Application app) 
      throws IOException {
    return addAppContainers(app, nmContext);
  }

  private HashMap<String, String> addAppContainers(Application app,
      Context context) throws IOException {
    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);
    context.getContainers().put(container1.getContainerId(), container1);
    context.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 {
    WebTarget target = targetWithJsonObject();
    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);

    Response response = target.path("ws").path("v1").path("node").path(path)
        .request(media).get(Response.class);
    assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
        response.getMediaType().toString());
    JSONObject json = response.readEntity(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 {
    WebTarget target = targetWithJsonObject();
    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);

    Response response = target.path("ws").path("v1").path("node").path("apps")
        .queryParam("user", "mockUser").request(MediaType.APPLICATION_JSON)
        .get(Response.class);
    assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
        response.getMediaType().toString());
    JSONObject json = response.readEntity(JSONObject.class);

    JSONObject info = json.getJSONObject("apps");
    assertEquals("incorrect number of elements", 1, info.length());
    JSONArray appInfo = parseJsonArray(info);
    assertEquals("incorrect number of elements", 1, appInfo.length());
    verifyNodeAppInfo(appInfo.getJSONObject(0), app, hash);
  }

  @Test
  public void testNodeAppsUserNone() throws JSONException, Exception {
    WebTarget target = targetWithJsonObject();
    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);

    Response response = target.path("ws").path("v1").path("node").path("apps")
        .queryParam("user", "george").request(MediaType.APPLICATION_JSON)
        .get(Response.class);
    assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
        response.getMediaType().toString());
    JSONObject json = response.readEntity(JSONObject.class);
    assertEquals("apps is not empty", new JSONObject("{apps:\"\"}"), json);
  }

  @Test
  public void testNodeAppsUserEmpty() throws JSONException, Exception {
    WebTarget target = targetWithJsonObject();
    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 {
      Response response = target
          .path("ws").path("v1").path("node").path("apps").queryParam("user", "")
          .request(MediaType.APPLICATION_JSON).get();
      throw new BadRequestException(response);
    } catch (BadRequestException ue) {
      Response response = ue.getResponse();
      assertResponseStatusCode(Response.Status.BAD_REQUEST, response.getStatusInfo());
      assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
          response.getMediaType().toString());
      JSONObject msg = response.readEntity(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 cause",
          "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 {
    WebTarget target = targetWithJsonObject();
    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);

    Response response = target.path("ws").path("v1").path("node").path("apps")
        .queryParam("state", ApplicationState.RUNNING.toString())
        .request(MediaType.APPLICATION_JSON).get(Response.class);

    assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
        response.getMediaType().toString());
    JSONObject json = response.readEntity(JSONObject.class);

    JSONObject info = json.getJSONObject("apps");
    assertEquals("incorrect number of elements", 1, info.length());
    JSONArray appInfo = parseJsonArray(info);
    assertEquals("incorrect number of elements", 1, appInfo.length());
    verifyNodeAppInfo(appInfo.getJSONObject(0), app2, hash2);

  }

  @Test
  public void testNodeAppsStateNone() throws JSONException, Exception {
    WebTarget target = targetWithJsonObject();
    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);

    Response response = target.path("ws").path("v1").path("node").path("apps")
        .queryParam("state", ApplicationState.INITING.toString())
        .request(MediaType.APPLICATION_JSON).get(Response.class);
    assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
        response.getMediaType().toString());
    JSONObject json = response.readEntity(JSONObject.class);

    assertEquals("apps is not empty", new JSONObject("{apps:\"\"}"), json);
  }

  @Test
  public void testNodeAppsStateInvalid() throws JSONException, Exception {
    WebTarget target = targetWithJsonObject();
    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 {
      Response response = target.path("ws").path("v1").path("node").path("apps")
          .queryParam("state", "FOO_STATE").request(MediaType.APPLICATION_JSON)
          .get(Response.class);
      throw new BadRequestException(response);
    } catch (BadRequestException ue) {
      Response response = ue.getResponse();

      assertResponseStatusCode(Response.Status.BAD_REQUEST, response.getStatusInfo());
      assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
          response.getMediaType().toString());
      JSONObject msg = response.readEntity(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");
      verifyStateInvalidException(message, type, classname);
    }
  }

  // verify the exception object default format is JSON
  @Test
  public void testNodeAppsStateInvalidDefault() throws JSONException, Exception {
    WebTarget target = targetWithJsonObject();
    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 {
      Response response = target.path("ws").path("v1").path("node").path("apps")
          .queryParam("state", "FOO_STATE").request().get();
      throw new BadRequestException(response);
    } catch (BadRequestException ue) {
      Response response = ue.getResponse();

      assertResponseStatusCode(Response.Status.BAD_REQUEST, response.getStatusInfo());
      assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
          response.getMediaType().toString());
      JSONObject msg = response.readEntity(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");
      verifyStateInvalidException(message, type, classname);
    }
  }

  // test that the exception output also returns XML
  @Test
  public void testNodeAppsStateInvalidXML() throws JSONException, Exception {
    WebTarget target = target();
    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 {
      Response response = target.path("ws").path("v1").path("node").path("apps")
          .queryParam("state", "FOO_STATE").request(MediaType.APPLICATION_XML)
          .get();
      throw new BadRequestException(response);
    } catch (BadRequestException ue) {
      Response response = ue.getResponse();

      assertResponseStatusCode(Response.Status.BAD_REQUEST, response.getStatusInfo());
      assertEquals(MediaType.APPLICATION_XML_TYPE + ";" + JettyUtils.UTF_8,
              response.getMediaType().toString());
      String entity = response.readEntity(String.class);

      DocumentBuilderFactory dbf = XMLUtils.newSecureDocumentBuilderFactory();
      DocumentBuilder db = dbf.newDocumentBuilder();
      InputSource is = new InputSource();
      is.setCharacterStream(new StringReader(entity));
      Document dom = db.parse(is);
      NodeList nodes = dom.getElementsByTagName("RemoteException");
      Element element = (Element) nodes.item(0);
      String message = WebServicesTestUtils.getXmlString(element, "message");
      String type = WebServicesTestUtils.getXmlString(element, "exception");
      String classname = WebServicesTestUtils.getXmlString(element, "javaClassName");
      verifyStateInvalidException(message, type, classname);
    }
  }

  private void verifyStateInvalidException(String message, String type,
      String classname) {
    WebServicesTestUtils
        .checkStringContains(
            "exception message",
            "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 Exception {
    testNodeSingleAppHelper(MediaType.APPLICATION_JSON);
  }

  // make sure default is json output
  @Test
  public void testNodeSingleAppsDefault() throws Exception {
    testNodeSingleAppHelper("");
  }

  public void testNodeSingleAppHelper(String media) throws JSONException,
      Exception {
    WebTarget target = targetWithJsonObject();
    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);

    Response response = target.path("ws").path("v1").path("node").path("apps")
        .path(app.getAppId().toString()).request(media).get(Response.class);
    assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
        response.getMediaType().toString());
    JSONObject json = response.readEntity(JSONObject.class);
    verifyNodeAppInfo(json.getJSONObject("app"), app, hash);
  }

  @Test
  public void testNodeSingleAppsSlash() throws JSONException, Exception {
    WebTarget target = targetWithJsonObject();
    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);
    Response response = target.path("ws").path("v1").path("node").path("apps")
        .path(app.getAppId().toString() + "/")
        .request(MediaType.APPLICATION_JSON).get(Response.class);

    assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
        response.getMediaType().toString());
    JSONObject json = response.readEntity(JSONObject.class);
    verifyNodeAppInfo(json.getJSONObject("app"), app, hash);
  }

  @Test
  public void testNodeSingleAppsInvalid() throws JSONException, Exception {
    WebTarget target = targetWithJsonObject();
    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 {
      Response response = target.path("ws").path("v1").path("node").path("apps")
          .path("app_foo_0000").request(MediaType.APPLICATION_JSON).get();
      throw new BadRequestException(response);
    } catch (BadRequestException ue) {
      Response response = ue.getResponse();
      assertResponseStatusCode(Response.Status.BAD_REQUEST, response.getStatusInfo());
      assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
          response.getMediaType().toString());
      JSONObject msg = response.readEntity(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 ApplicationId prefix: " +
          "app_foo_0000. The valid ApplicationId should start with prefix application", message);
      WebServicesTestUtils.checkStringMatch("exception type", "BadRequestException", type);
      WebServicesTestUtils.checkStringMatch("exception classname",
          "org.apache.hadoop.yarn.webapp.BadRequestException", classname);
    }
  }

  @Test
  public void testNodeSingleAppsMissing() throws JSONException, Exception {
    WebTarget target = targetWithJsonObject();
    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 {
      Response response = target.path("ws").path("v1").path("node").path("apps")
          .path("application_1234_0009").request(MediaType.APPLICATION_JSON).get();
      throw new NotFoundException(response);
    } catch (NotFoundException ue) {
      Response response = ue.getResponse();
      assertResponseStatusCode(Response.Status.NOT_FOUND, response.getStatusInfo());
      assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
          response.getMediaType().toString());
      JSONObject msg = response.readEntity(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",
          "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 Exception {
    WebTarget target = target();
    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);

    Response response = target.path("ws").path("v1").path("node").path("apps")
        .request(MediaType.APPLICATION_XML).get(Response.class);
    assertEquals(MediaType.APPLICATION_XML_TYPE + ";" + JettyUtils.UTF_8,
        response.getMediaType().toString());
    String xml = response.readEntity(String.class);
    DocumentBuilderFactory dbf = XMLUtils.newSecureDocumentBuilderFactory();
    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 Exception {
    WebTarget target = target();
    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);

    Response response = target.path("ws").path("v1").path("node").path("apps")
        .path(app.getAppId().toString() + "/")
        .request(MediaType.APPLICATION_XML).get(Response.class);
    assertEquals(MediaType.APPLICATION_XML_TYPE + ";" + JettyUtils.UTF_8,
        response.getMediaType().toString());
    String xml = response.readEntity(String.class);
    DocumentBuilderFactory dbf = XMLUtils.newSecureDocumentBuilderFactory();
    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);
  }

  @Test
  public void testNodeAppsUserFiltering() throws JSONException, Exception {
    Configuration yarnConf = new Configuration();
    yarnConf.setBoolean(YarnConfiguration.FILTER_ENTITY_LIST_BY_USER, true);
    yarnConf.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, true);
    yarnConf.setStrings(YarnConfiguration.YARN_ADMIN_ACL, "admin");
    ApplicationACLsManager aclManager = new ApplicationACLsManager(yarnConf);

    NMContext context = new NodeManager.NMContext(null, null, dirsHandler,
        aclManager, null, false, yarnConf);
    Application app = new MockApp(1);
    context.getApplications().put(app.getAppId(), app);
    addAppContainers(app, context);
    Application app2 = new MockApp("foo", 1234, 2);
    context.getApplications().put(app2.getAppId(), app2);
    addAppContainers(app2, context);

    // User "foo" could only see its own apps/containers.
    NMWebServices webSvc = new NMWebServices(context, null, nmWebApp,
        mock(HttpServletResponse.class));
    HttpServletRequest mockHsr = mockHttpServletRequestByUserName("foo");
    AppsInfo appsInfo = webSvc.getNodeApps(mockHsr, null, null);
    assertEquals(1, appsInfo.getApps().size());

    // Admin could see all apps and containers.
    HttpServletRequest mockHsrAdmin = mockHttpServletRequestByUserName("admin");
    AppsInfo appsInfo2 = webSvc.getNodeApps(mockHsrAdmin, null, null);
    assertEquals(2, appsInfo2.getApps().size());
  }

  private HttpServletRequest mockHttpServletRequestByUserName(String username) {
    HttpServletRequest mockHsr = mock(HttpServletRequest.class);
    when(mockHsr.getRemoteUser()).thenReturn(username);
    Principal principal = mock(Principal.class);
    when(principal.getName()).thenReturn(username);
    when(mockHsr.getUserPrincipal()).thenReturn(principal);
    return mockHsr;
  }

  private JSONArray parseJsonArray(JSONObject info) throws JSONException {
    Object app = info.get("app");
    if (app instanceof JSONObject) {
      JSONObject object = info.getJSONObject("app");
      JSONArray array = new JSONArray();
      array.put(object);
      return array;
    }

    if (app instanceof JSONArray) {
      return info.getJSONArray("app");
    }

    return new JSONArray();
  }
}