/**
 * 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.scheduler.capacity.placement.converter;

import static org.junit.Assert.*;

import org.apache.hadoop.yarn.server.resourcemanager.placement.MappingRule;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacitySchedulerConfiguration;
import org.junit.Test;

import java.io.IOException;
import java.util.Collection;
import java.util.List;

public class TestLegacyMappingRuleToJson {

  void validateConversion(String legacyUserGroup, String legacyAppName)
      throws IOException {
    //Creating a capacity scheduler config, because this way we can run
    //both the legacy and the JSON rules through the parser engine, and
    //we can check if we get the same mapping rules
    CapacitySchedulerConfiguration conf = new CapacitySchedulerConfiguration();

    //First we configure the capacity scheduler to parse the legacy config
    conf.set(
        CapacitySchedulerConfiguration.MAPPING_RULE_FORMAT,
        CapacitySchedulerConfiguration.MAPPING_RULE_FORMAT_LEGACY);
    conf.set(CapacitySchedulerConfiguration.QUEUE_MAPPING, legacyUserGroup);
    conf.set(CapacitySchedulerConfiguration.QUEUE_MAPPING_NAME, legacyAppName);

    //These are the legacyRules generated by CS, this can be used as a reference
    //we can test the JSON format against these
    List<MappingRule> legacyRules = conf.getMappingRules();

    //Converting the legacy format to JSON
    LegacyMappingRuleToJson converter = new LegacyMappingRuleToJson();
    String json = converter
        .setUserGroupMappingRules(legacyUserGroup)
        .setAppNameMappingRules(legacyAppName)
        .convert();

    //First we configure the capacity scheduler to parse the CONVERTED JSON
    conf.set(
        CapacitySchedulerConfiguration.MAPPING_RULE_FORMAT,
        CapacitySchedulerConfiguration.MAPPING_RULE_FORMAT_JSON);
    conf.set(CapacitySchedulerConfiguration.MAPPING_RULE_JSON, json);

    //These are the rules which are generated from the JSON format
    List<MappingRule> jsonRules = conf.getMappingRules();

    //Sanity check
    assertEquals("Number of rules should mach",
        legacyRules.size(), jsonRules.size());

    //We expect ALL rules to match no matter if it was parsed from legacy format
    //or from JSON
    for (int i = 0; i < legacyRules.size(); i++) {
      assertEquals(
          "Rule #" + i + " should match",
          legacyRules.get(i).toString(),
          jsonRules.get(i).toString());

      assertEquals(
          "Rule #" + i + " fallback should match",
          legacyRules.get(i).getFallback().toString(),
          jsonRules.get(i).getFallback().toString());
    }

  }

  @Test
  public void testApplicationNameMappingConversion() throws IOException {
    String appMapping = String.join(",",
        "namedMatch:simple",
        "namedMatch:root.deep",
        "namedMatch:%application",
        "namedMatch:root.deep.%application",
        "%application:simple",
        "%application:root.deep",
        "%application:%application",
        "%application:root.deep.%application");

    validateConversion("", appMapping);
  }

  @Test
  public void testGroupMappingConversion() throws IOException {
    String groupMapping = String.join(",",
        "g:testers:simple",
        "g:developers:root.very.deep",
        "g:users:%user",
        "g:testers:root.very.deep.%user");

    validateConversion(groupMapping, "");
  }

  @Test
  public void testUserMappingConversion() throws IOException {
    String groupMapping = String.join(",",
        "u:alice:alice",
        "u:beatrix:root.beatrix",
        "u:claire:%primary_group",
        "u:donna:root.deep.%primary_group",
        "u:emily:%secondary_group",
        "u:felicity:root.deep.%secondary_group",
        "u:%user:simple",
        "u:%user:root.deep",
        "u:%user:%primary_group",
        "u:%user:%secondary_group",
        "u:%user:root.deep.%primary_group",
        "u:%user:root.deep.%secondary_group",
        "u:%user:%primary_group.%user",
        "u:%user:root.%primary_group.%user",
        "u:%user:root.deep.%primary_group.%user",
        "u:%user:%secondary_group.%user",
        "u:%user:root.%secondary_group.%user",
        "u:%user:root.deep.%secondary_group.%user",
        "u:%user:%user",
        "u:%user:root.deep.%user");

    validateConversion(groupMapping, "");
  }

  @Test
  public void testTotalConversion() throws IOException {
    String appMapping = String.join(",",
        "namedMatch:simple",
        "namedMatch:root.deep",
        "namedMatch:%application",
        "namedMatch:root.deep.%application",
        "%application:simple",
        "%application:root.deep",
        "%application:%application",
        "%application:root.deep.%application");

    String userGroupMapping = String.join(",",
        "u:alice:alice",
        "u:beatrix:root.beatrix",
        "u:claire:%primary_group",
        "u:donna:root.deep.%primary_group",
        "u:emily:%secondary_group",
        "u:felicity:root.deep.%secondary_group",
        "u:%user:simple",
        "u:%user:root.deep",
        "g:testers:simple",
        "g:developers:root.very.deep",
        "g:users:%user",
        "g:testers:root.very.deep.%user",
        "u:%user:%primary_group",
        "u:%user:%secondary_group",
        "u:%user:root.deep.%primary_group",
        "u:%user:root.deep.%secondary_group",
        "u:%user:%primary_group.%user",
        "u:%user:root.%primary_group.%user",
        "u:%user:root.deep.%primary_group.%user",
        "u:%user:%secondary_group.%user",
        "u:%user:root.%secondary_group.%user",
        "u:%user:root.deep.%secondary_group.%user",
        "u:%user:%user",
        "u:%user:root.%user.something",
        "u:%user:root.deep.%user");

    validateConversion(userGroupMapping, appMapping);
  }

  @Test
  public void testErrorHandling() {
    LegacyMappingRuleToJson converter = new LegacyMappingRuleToJson();
    //Empty converter should return null
    assertNull(converter.convert());

    converter
        .setAppNameMappingRules("")
        .setUserGroupMappingRules("");
    //Empty converter should still return null
    assertNull(converter.convert());

    converter
        .setAppNameMappingRules((Collection<String>)null)
        .setUserGroupMappingRules((Collection<String>)null);
    //Setting nulls should also result in null return.
    assertNull(converter.convert());

    try {
      converter
          .setAppNameMappingRules("%application:")
          .setUserGroupMappingRules("")
          .convert();
      fail("Empty app name mapping part should throw exception");
    } catch (IllegalArgumentException e) {}

    try {
      converter
          .setAppNameMappingRules("%application:sdfsdf:sdfsfd")
          .setUserGroupMappingRules("")
          .convert();
      fail("Incorrect number of app name mapping parts should throw exception");
    } catch (IllegalArgumentException e) {}

    try {
      converter
          .setAppNameMappingRules("")
          .setUserGroupMappingRules("u::root.default")
          .convert();
      fail("Empty user group mapping part should throw exception");
    } catch (IllegalArgumentException e) {}

    try {
      converter
          .setAppNameMappingRules("")
          .setUserGroupMappingRules("u:bob")
          .convert();
      fail("Incorrect number of user group mapping parts should " +
          "throw exception");
    } catch (IllegalArgumentException e) {}

    try {
      converter
          .setAppNameMappingRules("")
          .setUserGroupMappingRules("X:bob:root.bob")
          .convert();
      fail("Invalid user group mapping prefix should throw exception");
    } catch (IllegalArgumentException e) {}
  }
}