TestRMWebServicesNodeLabels.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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.io.StringWriter;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;

import org.apache.commons.lang3.tuple.Pair;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hadoop.http.JettyUtils;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.server.resourcemanager.MockRM;
import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.LabelsToNodesInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NodeLabelInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NodeLabelsInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NodeToLabelsEntry;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NodeToLabelsInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NodeToLabelsEntryList;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.reader.NodeLabelsInfoReader;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.reader.LabelsToNodesInfoReader;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.reader.NodeToLabelsInfoReader;
import org.apache.hadoop.yarn.webapp.GenericExceptionHandler;
import org.apache.hadoop.yarn.webapp.JerseyTestBase;
import org.apache.hadoop.yarn.webapp.WebServicesTestUtils;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.jettison.JettisonFeature;
import org.glassfish.jersey.jettison.JettisonJaxbContext;
import org.glassfish.jersey.jettison.JettisonMarshaller;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.TestProperties;
public class TestRMWebServicesNodeLabels extends JerseyTestBase {

  private static final int BAD_REQUEST_CODE = 400;

  private static final Logger LOG = LoggerFactory
      .getLogger(TestRMWebServicesNodeLabels.class);
  private static final String NODE_0 = "nid:0";
  private static final String NODE_1 = "nid1:0";
  private static final String NODE_2 = "nid2:0";
  private static final String LABEL_A = "a";
  private static final String LABEL_B = "b";
  private static final String LABEL_X = "x";
  private static final String LABEL_Y = "y";
  private static final String LABEL_Z = "z";
  public static final boolean DEFAULT_NL_EXCLUSIVITY = true;
  private static final String PATH_WS = "ws";
  private static final String PATH_V1 = "v1";
  private static final String PATH_NODES = "nodes";
  private static final String PATH_CLUSTER = "cluster";
  private static final String PATH_REPLACE_NODE_TO_LABELS = "replace-node-to-labels";
  private static final String PATH_LABEL_MAPPINGS = "label-mappings";
  private static final String PATH_GET_LABELS = "get-labels";
  private static final String PATH_REPLACE_LABELS = "replace-labels";
  private static final String PATH_REMOVE_LABELS = "remove-node-labels";
  private static final String PATH_GET_NODE_LABELS = "get-node-labels";
  private static final String PATH_GET_NODE_TO_LABELS = "get-node-to-labels";
  private static final String QUERY_USER_NAME = "user.name";
  private static final String PATH_ADD_NODE_LABELS = "add-node-labels";

  private static MockRM rm;
  private static YarnConfiguration conf;

  private static String userName;
  private static String notUserName;
  private static RMWebServices rmWebService;

  private HttpServletRequest request;
  private ResourceConfig config;

  @Override
  protected Application configure() {
    config = new ResourceConfig();
    config.register(RMWebServices.class);
    config.register(new JerseyBinder());
    config.register(GenericExceptionHandler.class);
    config.register(NodeLabelsInfoReader.class);
    config.register(new JettisonFeature()).register(JAXBContextResolver.class);
    forceSet(TestProperties.CONTAINER_PORT, JERSEY_RANDOM_PORT);
    return config;
  }

  private class JerseyBinder extends AbstractBinder {
    private Configuration conf = new YarnConfiguration();

    @Override
    protected void configure() {
      try {
        userName = UserGroupInformation.getCurrentUser().getShortUserName();
      } catch (IOException ioe) {
        throw new RuntimeException("Unable to get current user name "
            + ioe.getMessage(), ioe);
      }
      notUserName = userName + "abc123";
      conf = new YarnConfiguration();
      conf.set(YarnConfiguration.YARN_ADMIN_ACL, userName);
      rm = new MockRM(conf);
      bind(rm).to(ResourceManager.class).named("rm");
      bind(conf).to(Configuration.class).named("conf");
      rmWebService = new RMWebServices(rm, conf);
      bind(rmWebService).to(RMWebServices.class);
      request = mock(HttpServletRequest.class);
      bind(request).to(HttpServletRequest.class);
      Principal principal = () -> userName;
      when(request.getUserPrincipal()).thenReturn(principal);
      HttpServletResponse response = mock(HttpServletResponse.class);
      bind(response).to(HttpServletResponse.class);
      rmWebService.setResponse(response);
    }
  }

  @Override
  @BeforeEach
  public void setUp() throws Exception {
    super.setUp();
  }

  public TestRMWebServicesNodeLabels() {
  }

  private WebTarget getClusterWebResource() {
    return targetWithJsonObject().
        register(NodeLabelsInfoReader.class).
        register(LabelsToNodesInfoReader.class).
        register(NodeToLabelsInfoReader.class).
        path(PATH_WS).path(PATH_V1).path(PATH_CLUSTER);
  }

  private Response get(String path) {
    return getClusterWebResource()
        .path(path)
        .queryParam(QUERY_USER_NAME, userName)
        .request(MediaType.APPLICATION_JSON).get(Response.class);
  }

  private Response get(String path, MultivaluedMap<String, String> queryParams) {

    WebTarget webTarget = getClusterWebResource()
        .path(path).queryParam(QUERY_USER_NAME, userName);

    for(Map.Entry<String, List<String>> param : queryParams.entrySet()) {
      for (String item : param.getValue()) {
        webTarget = webTarget.queryParam(param.getKey(), item);
      }
    }

    return webTarget.request(MediaType.APPLICATION_JSON).get(Response.class);
  }

  private Response post(String path, String queryUserName, Object payload,
      Class<?> payloadClass) throws Exception {
    return getClusterWebResource()
        .path(path)
        .queryParam(QUERY_USER_NAME, queryUserName)
        .request(MediaType.APPLICATION_JSON)
        .post(Entity.json(toJson(payload, payloadClass)), Response.class);
  }

  private Response post(String path, String queryUserName, Object payload,
      Class<?> payloadClass, MultivaluedMap<String, String> queryParams) throws Exception {

    WebTarget webTarget = getClusterWebResource()
        .path(path).queryParam(QUERY_USER_NAME, queryUserName);

    for(Map.Entry<String, List<String>> param : queryParams.entrySet()) {
      List<String> values = param.getValue();
      for (String value : values) {
        webTarget = webTarget.queryParam(param.getKey(), value);
      }
    }
    return webTarget.request(MediaType.APPLICATION_JSON)
        .post(Entity.entity(toJson(payload, payloadClass),
        MediaType.APPLICATION_JSON), Response.class);
  }

  @Test
  public void testNodeLabels() throws Exception {
    Response response;

    // Add a label
    response = addNodeLabels(Lists.newArrayList(Pair.of(LABEL_A, DEFAULT_NL_EXCLUSIVITY)));
    assertHttp200(response);

    // Verify
    response = getNodeLabels();
    assertApplicationJsonUtf8Response(response);
    assertNodeLabelsInfo(response.readEntity(NodeLabelsInfo.class), Lists.newArrayList(
        Pair.of(LABEL_A, true)));

    // Add another
    response = addNodeLabels(Lists.newArrayList(Pair.of(LABEL_B, false)));
    assertHttp200(response);

    // Verify
    response = getNodeLabels();
    assertApplicationJsonUtf8Response(response);
    // Verify exclusivity for 'b' as false
    assertNodeLabelsInfo(response.readEntity(NodeLabelsInfo.class),
        Lists.newArrayList(
            Pair.of(LABEL_A, true),
            Pair.of(LABEL_B, false)));

    // Add labels to a node
    response = replaceLabelsOnNode(NODE_0, LABEL_A);
    assertHttp200(response);

    // Add labels to another node
    response = replaceLabelsOnNode(NODE_1, LABEL_B);
    assertHttp200(response);

    // Add labels to another node
    response = replaceLabelsOnNode(NODE_2, LABEL_B);
    assertHttp200(response);

    // Verify all, using get-labels-to-Nodes
    response = getNodeLabelMappings();
    assertApplicationJsonUtf8Response(response);
    LabelsToNodesInfo labelsToNodesInfo = response.readEntity(LabelsToNodesInfo.class);
    assertLabelsToNodesInfo(labelsToNodesInfo, 2, Lists.newArrayList(
        Pair.of(Pair.of(LABEL_B, false), Lists.newArrayList(NODE_1, NODE_2)),
        Pair.of(Pair.of(LABEL_A, DEFAULT_NL_EXCLUSIVITY), Lists.newArrayList(NODE_0))
    ));

    // Verify, using get-labels-to-Nodes for specified set of labels
    response = getNodeLabelMappingsByLabels(LABEL_A);
    assertApplicationJsonUtf8Response(response);
    labelsToNodesInfo = response.readEntity(LabelsToNodesInfo.class);
    assertLabelsToNodesInfo(labelsToNodesInfo, 1, Lists.newArrayList(
        Pair.of(Pair.of(LABEL_A, DEFAULT_NL_EXCLUSIVITY), Lists.newArrayList(NODE_0))
    ));

    // Verify
    response = getLabelsOfNode(NODE_0);
    assertApplicationJsonUtf8Response(response);
    assertNodeLabelsInfoContains(response.readEntity(NodeLabelsInfo.class),
        Pair.of(LABEL_A, DEFAULT_NL_EXCLUSIVITY));

    // Replace
    response = replaceLabelsOnNode(NODE_0, LABEL_B);
    assertHttp200(response);

    // Verify
    response = getLabelsOfNode(NODE_0);
    assertApplicationJsonUtf8Response(response);
    assertNodeLabelsInfoContains(response.readEntity(NodeLabelsInfo.class),
        Pair.of(LABEL_B, false));

    // Replace labels using node-to-labels
    response = replaceNodeToLabels(Lists.newArrayList(Pair.of(NODE_0,
        Lists.newArrayList(LABEL_A))));
    assertHttp200(response);

    // Verify, using node-to-labels
    response = getNodeToLabels();
    assertApplicationJsonUtf8Response(response);
    NodeToLabelsInfo nodeToLabelsInfo = response.readEntity(NodeToLabelsInfo.class);
    NodeLabelsInfo nodeLabelsInfo = nodeToLabelsInfo.getNodeToLabels().get(NODE_0);
    assertNodeLabelsSize(nodeLabelsInfo, 1);
    assertNodeLabelsInfoContains(nodeLabelsInfo, Pair.of(LABEL_A, DEFAULT_NL_EXCLUSIVITY));

    // Remove all
    response = replaceLabelsOnNode(NODE_0, "");
    assertHttp200(response);
    // Verify
    response = getLabelsOfNode(NODE_0);
    assertApplicationJsonUtf8Response(response);
    assertNodeLabelsSize(response.readEntity(NodeLabelsInfo.class), 0);

    // Add a label back for auth tests
    response = replaceLabelsOnNode(NODE_0, LABEL_A);
    assertHttp200(response);

    // Verify
    response = getLabelsOfNode(NODE_0);
    assertApplicationJsonUtf8Response(response);
    assertNodeLabelsInfoContains(response.readEntity(NodeLabelsInfo.class),
        Pair.of(LABEL_A, DEFAULT_NL_EXCLUSIVITY));

    // Auth fail replace labels on node
    Principal principal2 = () -> notUserName;
    when(request.getUserPrincipal()).thenReturn(principal2);
    response = replaceLabelsOnNodeWithUserName(NODE_0, notUserName, LABEL_B);
    assertHttp401(response);
    // Verify
    response = getLabelsOfNode(NODE_0);
    assertApplicationJsonUtf8Response(response);
    assertNodeLabelsInfoContains(response.readEntity(NodeLabelsInfo.class),
        Pair.of(LABEL_A, DEFAULT_NL_EXCLUSIVITY));

    // Fail to add a label with wrong user
    response = addNodeLabelsWithUser(Lists.newArrayList(Pair.of("c", DEFAULT_NL_EXCLUSIVITY)),
        notUserName);
    assertHttp401(response);

    // Verify
    response = getNodeLabels();
    assertApplicationJsonUtf8Response(response);
    assertNodeLabelsSize(response.readEntity(NodeLabelsInfo.class), 2);

    // Remove cluster label (succeed, we no longer need it)
    Principal principal3 = () -> userName;
    when(request.getUserPrincipal()).thenReturn(principal3);
    response = removeNodeLabel(LABEL_B);
    assertHttp200(response);
    // Verify
    response = getNodeLabels();
    assertApplicationJsonUtf8Response(response);
    assertNodeLabelsInfo(response.readEntity(NodeLabelsInfo.class),
        Lists.newArrayList(Pair.of(LABEL_A, true)));

    // Remove cluster label with post
    response = removeNodeLabel(LABEL_A);
    assertHttp200(response);
    // Verify
    response = getNodeLabels();
    assertApplicationJsonUtf8Response(response);
    nodeLabelsInfo = response.readEntity(NodeLabelsInfo.class);
    assertEquals(0, nodeLabelsInfo.getNodeLabels().size());

    // Following test cases are to test replace when distributed node label
    // configuration is on
    // Reset for testing : add cluster labels
    response = addNodeLabels(Lists.newArrayList(
        Pair.of(LABEL_X, false), Pair.of(LABEL_Y, false)));
    assertHttp200(response);
    // Reset for testing : Add labels to a node
    response = replaceLabelsOnNode(NODE_0, LABEL_Y);
    assertHttp200(response);

    //setting rmWebService for non-centralized NodeLabel Configuration
    rmWebService.isCentralizedNodeLabelConfiguration = false;

    // Case1 : Replace labels using node-to-labels
    response = replaceNodeToLabels(Lists.newArrayList(Pair.of(NODE_0,
        Lists.newArrayList(LABEL_X))));
    assertHttp404(response);

    // Verify, using node-to-labels that previous operation has failed
    response = getNodeToLabels();
    assertApplicationJsonUtf8Response(response);
    nodeToLabelsInfo = response.readEntity(NodeToLabelsInfo.class);
    nodeLabelsInfo = nodeToLabelsInfo.getNodeToLabels().get(NODE_0);
    assertNodeLabelsSize(nodeLabelsInfo, 1);
    assertNodeLabelsInfoDoesNotContain(nodeLabelsInfo, Pair.of(LABEL_X, false));

    // Case2 : failure to Replace labels using replace-labels
    response = replaceLabelsOnNode(NODE_0, LABEL_X);
    assertHttp404(response);

    // Verify, using node-to-labels that previous operation has failed
    response = getNodeToLabels();
    assertApplicationJsonUtf8Response(response);
    nodeToLabelsInfo = response.readEntity(NodeToLabelsInfo.class);
    nodeLabelsInfo = nodeToLabelsInfo.getNodeToLabels().get(NODE_0);
    assertNodeLabelsSize(nodeLabelsInfo, 1);
    assertNodeLabelsInfoDoesNotContain(nodeLabelsInfo, Pair.of(LABEL_X, false));

    //  Case3 : Remove cluster label should be successful
    response = removeNodeLabel(LABEL_X);
    assertHttp200(response);
    // Verify
    response = getNodeLabels();
    assertApplicationJsonUtf8Response(response);
    assertNodeLabelsInfoAtPosition(response.readEntity(NodeLabelsInfo.class), Pair.of(LABEL_Y,
        false), 0);

    // Remove y
    response = removeNodeLabel(LABEL_Y);
    assertHttp200(response);

    // Verify
    response = getNodeLabels();
    assertApplicationJsonUtf8Response(response);
    assertNodeLabelsSize(response.readEntity(NodeLabelsInfo.class), 0);

    // add a new nodelabel with exclusivity=false
    response = addNodeLabels(Lists.newArrayList(Pair.of(LABEL_Z, false)));
    assertHttp200(response);
    // Verify
    response = getNodeLabels();
    assertApplicationJsonUtf8Response(response);
    assertNodeLabelsInfoAtPosition(response.readEntity(NodeLabelsInfo.class),
        Pair.of(LABEL_Z, false), 0);
    assertNodeLabelsSize(nodeLabelsInfo, 1);
  }

  private void assertLabelsToNodesInfo(LabelsToNodesInfo labelsToNodesInfo, int size,
      List<Pair<Pair<String, Boolean>, List<String>>> nodeLabelsToNodesList) {
    Map<NodeLabelInfo, NodeIDsInfo> labelsToNodes = labelsToNodesInfo.getLabelsToNodes();
    assertNotNull(labelsToNodes, "Labels to nodes mapping should not be null.");
    assertEquals(size, labelsToNodes.size(), "Size of label to nodes mapping is not the expected.");

    for (Pair<Pair<String, Boolean>, List<String>> nodeLabelToNodes : nodeLabelsToNodesList) {
      Pair<String, Boolean> expectedNLData = nodeLabelToNodes.getLeft();
      List<String> expectedNodes = nodeLabelToNodes.getRight();
      NodeLabelInfo expectedNLInfo = new NodeLabelInfo(expectedNLData.getLeft(),
          expectedNLData.getRight());
      NodeIDsInfo actualNodes = labelsToNodes.get(expectedNLInfo);
      assertNotNull(actualNodes, String.format("Node info not found. Expected NodeLabel data: %s",
          expectedNLData));
      for (String expectedNode : expectedNodes) {
        assertTrue(actualNodes.getNodeIDs().contains(expectedNode),
            String.format("Can't find node ID in actual Node IDs list: %s",
            actualNodes.getNodeIDs()));
      }
    }
  }

  private void assertNodeLabelsInfo(NodeLabelsInfo nodeLabelsInfo,
      List<Pair<String, Boolean>> nlInfos) {
    assertEquals(nlInfos.size(), nodeLabelsInfo.getNodeLabels().size());

    for (int i = 0; i < nodeLabelsInfo.getNodeLabelsInfo().size(); i++) {
      Pair<String, Boolean> expected = nlInfos.get(i);
      NodeLabelInfo actual = nodeLabelsInfo.getNodeLabelsInfo().get(i);
      LOG.debug("Checking NodeLabelInfo: {}", actual);
      assertEquals(expected.getLeft(), actual.getName());
      assertEquals(expected.getRight(), actual.getExclusivity());
    }
  }

  private void assertNodeLabelsInfoAtPosition(NodeLabelsInfo nodeLabelsInfo, Pair<String,
      Boolean> nlInfo, int pos) {
    NodeLabelInfo actual = nodeLabelsInfo.getNodeLabelsInfo().get(pos);
    LOG.debug("Checking NodeLabelInfo: {}", actual);
    assertEquals(nlInfo.getLeft(), actual.getName());
    assertEquals(nlInfo.getRight(), actual.getExclusivity());
  }

  private void assertNodeLabelsInfoContains(NodeLabelsInfo nodeLabelsInfo,
      Pair<String, Boolean> nlInfo) {
    NodeLabelInfo nodeLabelInfo = new NodeLabelInfo(nlInfo.getLeft(), nlInfo.getRight());
    assertTrue(nodeLabelsInfo.getNodeLabelsInfo().contains(nodeLabelInfo),
        String.format("Cannot find nodeLabelInfo '%s' among items of node label info list:" +
        " %s", nodeLabelInfo, nodeLabelsInfo.getNodeLabelsInfo()));
  }

  private void assertNodeLabelsInfoDoesNotContain(NodeLabelsInfo nodeLabelsInfo, Pair<String,
      Boolean> nlInfo) {
    NodeLabelInfo nodeLabelInfo = new NodeLabelInfo(nlInfo.getLeft(), nlInfo.getRight());
    assertFalse(nodeLabelsInfo.getNodeLabelsInfo().contains(nodeLabelInfo),
        String.format("Should have not found nodeLabelInfo '%s' among " +
        "items of node label info list: %s", nodeLabelInfo, nodeLabelsInfo.getNodeLabelsInfo()));
  }

  private void assertNodeLabelsSize(NodeLabelsInfo nodeLabelsInfo, int expectedSize) {
    assertEquals(expectedSize, nodeLabelsInfo.getNodeLabelsInfo().size());
  }

  private Response replaceNodeToLabels(List<Pair<String, List<String>>> nodeToLabelInfos)
      throws Exception {
    NodeToLabelsEntryList nodeToLabelsEntries = new NodeToLabelsEntryList();

    for (Pair<String, List<String>> nodeToLabelInfo : nodeToLabelInfos) {
      ArrayList<String> labelList = new ArrayList<>(nodeToLabelInfo.getRight());
      String nodeId = nodeToLabelInfo.getLeft();
      NodeToLabelsEntry nli = new NodeToLabelsEntry(nodeId, labelList);
      nodeToLabelsEntries.getNodeToLabels().add(nli);
    }
    return post(PATH_REPLACE_NODE_TO_LABELS, userName, nodeToLabelsEntries, NodeToLabelsEntryList.class);
  }

  private Response getNodeLabelMappings() {
    return get(PATH_LABEL_MAPPINGS);
  }

  private Response getNodeLabelMappingsByLabels(String... labelNames) {
    MultivaluedMap params = createMultiValuedMap(labelNames);
    return get(PATH_LABEL_MAPPINGS, params);
  }

  private Response replaceLabelsOnNode(String node, String... labelNames) throws Exception {
    return replaceLabelsOnNodeWithUserName(node, userName, labelNames);
  }

  private Response replaceLabelsOnNodeWithUserName(String node,
      String userName, String... labelNames) throws Exception {
    LOG.info("Replacing labels on node '{}', label(s): {}", node, labelNames);
    MultivaluedMap params = createMultiValuedMap(labelNames);
    String path = UriBuilder.fromPath(PATH_NODES).path(node)
        .path(PATH_REPLACE_LABELS).build().toString();
    return post(path, userName, null, null, params);
  }

  private static MultivaluedMap createMultiValuedMap(String[] labelNames) {
    MultivaluedMap<String, String> params = new MultivaluedHashMap();
    for (String labelName : labelNames) {
      params.add("labels", labelName);
    }
    return params;
  }

  private Response removeNodeLabel(String... labelNames) throws Exception {
    MultivaluedMap params = createMultiValuedMap(labelNames);
    return post(PATH_REMOVE_LABELS, userName, null, null, params);
  }

  private Response getLabelsOfNode(String node) {
    String path = UriBuilder.fromPath(PATH_NODES).path(node)
        .path(PATH_GET_LABELS).build().toString();
    return get(path);
  }

  private Response getNodeLabels() {
    return get(PATH_GET_NODE_LABELS);
  }

  private Response getNodeToLabels() {
    return get(PATH_GET_NODE_TO_LABELS);
  }

  private Response addNodeLabels(List<Pair<String, Boolean>> nlInfos) throws Exception {
    return addNodeLabelsInternal(nlInfos, userName);
  }

  private Response addNodeLabelsWithUser(List<Pair<String, Boolean>> nlInfos,
      String userName) throws Exception {
    return addNodeLabelsInternal(nlInfos, userName);
  }

  private Response addNodeLabelsInternal(List<Pair<String, Boolean>> nlInfos,
      String userName) throws Exception {
    NodeLabelsInfo nodeLabelsInfo = new NodeLabelsInfo();
    for (Pair<String, Boolean> nlInfo : nlInfos) {
      NodeLabelInfo nodeLabelInfo = new NodeLabelInfo(nlInfo.getLeft(), nlInfo.getRight());
      nodeLabelsInfo.getNodeLabelsInfo().add(nodeLabelInfo);
    }
    return post(PATH_ADD_NODE_LABELS, userName, nodeLabelsInfo, NodeLabelsInfo.class);
  }

  private void assertApplicationJsonUtf8Response(Response response) {
    assertEquals(MediaType.APPLICATION_JSON_TYPE + ";" + JettyUtils.UTF_8,
        response.getMediaType().toString());
  }

  private void assertHttp200(Response response) {
    assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
  }

  private void assertHttp401(Response response) {
    assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
  }

  private void assertHttp404(Response response) {
    assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus());
  }

  @Test
  public void testLabelInvalidAddition()
      throws Exception {
    // Add a invalid label
    Response response = addNodeLabels(Lists.newArrayList(Pair.of("a&",
        DEFAULT_NL_EXCLUSIVITY)));
    String expectedMessage =
        "label name should only contains"
            + " {0-9, a-z, A-Z, -, _} and should not started with"
            + " {-,_}, now it is= a&";
    validateJsonExceptionContent(response, expectedMessage);
  }

  @Test
  public void testLabelChangeExclusivity()
      throws Exception {
    Response response;
    response = addNodeLabels(Lists.newArrayList(Pair.of("newLabel", DEFAULT_NL_EXCLUSIVITY)));
    assertHttp200(response);
    // new info and change exclusivity
    response = addNodeLabels(Lists.newArrayList(Pair.of("newLabel", false)));
    String expectedMessage =
        "Exclusivity cannot be modified for an existing"
            + " label with : <newLabel:exclusivity=false>";
    validateJsonExceptionContent(response, expectedMessage);
  }

  private void validateJsonExceptionContent(Response response,
      String expectedMessage)
      throws JSONException {
    assertEquals(BAD_REQUEST_CODE, response.getStatus());
    JSONObject msg = response.readEntity(JSONObject.class);
    JSONObject exception = msg.getJSONObject("RemoteException");
    String message = exception.getString("message");
    assertEquals(3, exception.length(), "incorrect number of elements");
    String type = exception.getString("exception");
    String classname = exception.getString("javaClassName");
    WebServicesTestUtils.checkStringMatch("exception type",
        "BadRequestException", type);
    WebServicesTestUtils.checkStringMatch("exception classname",
        "org.apache.hadoop.yarn.webapp.BadRequestException", classname);
    WebServicesTestUtils.checkStringContains("exception message",
        expectedMessage, message);
  }

  @Test
  public void testLabelInvalidReplace()
      throws Exception {
    Response response;
    // replace label which doesn't exist
    response = replaceLabelsOnNode(NODE_0, "idontexist");

    String expectedMessage =
        "Not all labels being replaced contained by known label"
            + " collections, please check, new labels=[idontexist]";
    validateJsonExceptionContent(response, expectedMessage);
  }

  @Test
  public void testLabelInvalidRemove()
      throws Exception {
    Response response;
    response = removeNodeLabel("ireallydontexist");
    String expectedMessage =
        "Node label=ireallydontexist to be"
            + " removed doesn't existed in cluster node labels"
            + " collection.";
    validateJsonExceptionContent(response, expectedMessage);
  }

  @Test
  public void testNodeLabelPartitionInfo() throws Exception {
    Response response;

    // Add a node label
    response = addNodeLabels(Lists.newArrayList(Pair.of(LABEL_A, DEFAULT_NL_EXCLUSIVITY)));
    assertHttp200(response);

    // Verify partition info in get-node-labels
    response = getNodeLabels();
    assertApplicationJsonUtf8Response(response);
    NodeLabelsInfo nodeLabelsInfo = response.readEntity(NodeLabelsInfo.class);
    assertNodeLabelsSize(nodeLabelsInfo, 1);
    for (NodeLabelInfo nl : nodeLabelsInfo.getNodeLabelsInfo()) {
      assertEquals(LABEL_A, nl.getName());
      assertTrue(nl.getExclusivity());
      assertNotNull(nl.getPartitionInfo());
      assertNotNull(nl.getPartitionInfo().getResourceAvailable());
    }

    // Add node label to a node
    response = replaceLabelsOnNode("nodeId:0", LABEL_A);
    assertHttp200(response);

    // Verify partition info in label-mappings
    response = getNodeLabelMappings();
    assertApplicationJsonUtf8Response(response);
    LabelsToNodesInfo labelsToNodesInfo = response.readEntity(LabelsToNodesInfo.class);
    assertLabelsToNodesInfo(labelsToNodesInfo, 1, Lists.newArrayList(
        Pair.of(Pair.of(LABEL_A, DEFAULT_NL_EXCLUSIVITY), Lists.newArrayList("nodeId:0"))
    ));
    NodeIDsInfo nodes = labelsToNodesInfo.getLabelsToNodes().get(new NodeLabelInfo(LABEL_A));
    assertNotNull(nodes.getPartitionInfo());
    assertNotNull(nodes.getPartitionInfo().getResourceAvailable());
  }

  @SuppressWarnings("rawtypes")
  private String toJson(Object obj, Class klass) throws Exception {
    if (obj == null) {
      return null;
    }
    JettisonJaxbContext jettisonJaxbContext = new JettisonJaxbContext(klass);
    JettisonMarshaller jsonMarshaller = jettisonJaxbContext.createJsonMarshaller();
    StringWriter stringWriter = new StringWriter();
    jsonMarshaller.marshallToJSON(obj, stringWriter);
    return stringWriter.toString();
  }
}