From 5d0c250bbba2a4e277fd79740cd744fbd1d92139 Mon Sep 17 00:00:00 2001 From: Gergely Pollak Date: Thu, 21 Jan 2021 05:08:43 +0100 Subject: [PATCH] YARN-10585 Create a class which can convert from legacy mapping rule format to the new JSON format Change-Id: I2e3129b4d0f50359ef5022682a72e05a3a6b5efe --- .../placement/MappingRule.java | 6 +- .../placement/MappingRuleActions.java | 5 +- .../converter/LegacyMappingRuleToJson.java | 381 ++++++++++++++++++ .../placement/TestCSMappingPlacementRule.java | 2 +- .../placement/TestMappingRuleActions.java | 5 +- .../TestLegacyMappingRuleToJson.java | 215 ++++++++++ 6 files changed, 608 insertions(+), 6 deletions(-) create mode 100644 hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/placement/converter/LegacyMappingRuleToJson.java create mode 100644 hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/placement/converter/TestLegacyMappingRuleToJson.java diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRule.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRule.java index e61ad95be05..9d67d7815e4 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRule.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRule.java @@ -106,7 +106,11 @@ public static MappingRule createLegacyRule( switch (type) { case USER_MAPPING: - matcher = MappingRuleMatchers.createUserMatcher(source); + if (source.equals("%user")) { + matcher = MappingRuleMatchers.createAllMatcher(); + } else { + matcher = MappingRuleMatchers.createUserMatcher(source); + } break; case GROUP_MAPPING: matcher = MappingRuleMatchers.createUserGroupMatcher(source); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleActions.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleActions.java index 13cdbe832c0..35d7276c78d 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleActions.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/placement/MappingRuleActions.java @@ -96,8 +96,9 @@ public void validate(MappingRuleValidationContext ctx) @Override public String toString() { return "PlaceToQueueAction{" + - "queueName='" + queuePattern + '\'' + - '}'; + "queueName='" + queuePattern + "'," + + "allowCreate=" + allowCreate + + "}"; } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/placement/converter/LegacyMappingRuleToJson.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/placement/converter/LegacyMappingRuleToJson.java new file mode 100644 index 00000000000..0218f315947 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/placement/converter/LegacyMappingRuleToJson.java @@ -0,0 +1,381 @@ +package org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.placement.converter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.hadoop.util.StringUtils; +import org.apache.hadoop.yarn.server.resourcemanager.placement.MappingQueuePath; + +import java.util.Collection; + +public class LegacyMappingRuleToJson { + //Legacy rule parse helper constants + public static final String RULE_PART_DELIMITER = ":"; + public static final String PREFIX_USER_MAPPING = "u"; + public static final String PREFIX_GROUP_MAPPING = "g"; + + //Legacy rule matcher variables + public static final String MATCHER_APPLICATION = "%application"; + public static final String MATCHER_USER = "%user"; + + //Legacy rule mapping variables, which can be used in target queues + public static final String MAPPING_PRIMARY_GROUP = "%primary_group"; + public static final String MAPPING_SECONDARY_GROUP = "%secondary_group"; + public static final String MAPPING_USER = MATCHER_USER; + + //JSON Format match all token (actually only used for users) + public static final String JSON_MATCH_ALL = "*"; + + //Frequently used JSON node names for rule definitions + public static final String JSON_NODE_POLICY = "policy"; + public static final String JSON_NODE_PARENT_QUEUE = "parentQueue"; + public static final String JSON_NODE_CUSTOM_PLACEMENT = "customPlacement"; + public static final String JSON_NODE_MATCHES = "matches"; + + /** + * Our internal object mapper, used to create JSON nodes. + */ + private ObjectMapper objectMapper = new ObjectMapper(); + + /** + * Collection to store the legacy group mapping rule strings. + */ + private Collection userGroupMappingRules; + /** + * Collection to store the legacy application name mapping rule strings. + */ + private Collection applicationNameMappingRules; + + /** + * This setter method is used to set the raw string format of the legacy + * user group mapping rules. This method expect a string formatted just like + * in the configuration file of the Capacity Scheduler. + * eg. u:bob:root.groups.%primary_group,u:%user:root.default + * + * @param rules The string containing ALL the UserGroup mapping rules in + * legacy format + * @return This object for daisy chain support + */ + public LegacyMappingRuleToJson setUserGroupMappingRules(String rules) { + setUserGroupMappingRules(StringUtils.getTrimmedStringCollection(rules)); + return this; + } + + /** + * This setter method is used to set the the user group mapping rules as a + * string collection, where each entry is one rule. + * + * @param rules One rule per entry + * @return This object for daisy chain support + */ + public LegacyMappingRuleToJson setUserGroupMappingRules( + Collection rules) { + userGroupMappingRules = rules; + return this; + } + + /** + * This setter method is used to set the raw string format of the legacy + * application name mapping rules. This method expect a string formatted + * just like in the configuration file of the Capacity Scheduler. + * eg. mapreduce:root.apps.%application,%application:root.default + * + * @param rules The string containing ALL the application name mapping rules + * in legacy format + * @return This object for daisy chain support + */ + public LegacyMappingRuleToJson setAppNameMappingRules(String rules) { + setAppNameMappingRules(StringUtils.getTrimmedStringCollection(rules)); + return this; + } + + /** + * This setter method is used to set the the application name mapping rules as + * a string collection, where each entry is one rule. + * + * @param rules One rule per entry + * @return This object for daisy chain support + */ + public LegacyMappingRuleToJson setAppNameMappingRules( + Collection rules) { + applicationNameMappingRules = rules; + return this; + } + + /** + * This method will do the conversion based on the already set mapping rules. + * First the rules to be converted must be set via setAppNameMappingRules and + * setUserGroupMappingRules methods. + * @return JSON Format of the provided mapping rules, null if no rules are set + */ + public String convert() { + if (userGroupMappingRules == null && applicationNameMappingRules == null) { + return null; + } + + //creating the basic JSON config structure + ObjectNode rootNode = objectMapper.createObjectNode(); + ArrayNode rulesNode = objectMapper.createArrayNode(); + rootNode.set("rules", rulesNode); + + //Processing and adding all the user group mapping rules + if (userGroupMappingRules != null) { + for (String rule : userGroupMappingRules) { + rulesNode.add(convertUserGroupMappingRule(rule)); + } + } + + //Processing and adding all the application name mapping rules + if (applicationNameMappingRules != null) { + for (String rule : applicationNameMappingRules) { + rulesNode.add(convertAppNameMappingRule(rule)); + } + } + + //If there are no converted rules we return null + if (rulesNode.size() == 0) { + return null; + } + + try { + return objectMapper + .writerWithDefaultPrettyPrinter() + .writeValueAsString(rootNode); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + + return null; + } + + /** + * This intermediate helper method is used to process User Group mapping rules + * and invoke the proper mapping rule creation method. + * @param rule The legacy format of the single rule to be converted. + * @return The ObjectNode which can be added to the rules part of the config. + */ + ObjectNode convertUserGroupMappingRule(String rule) { + String[] mapping = splitRule(rule, 3); + String ruleType = mapping[0]; + String ruleMatch = mapping[1]; + String ruleTarget = mapping[2]; + + if (ruleType.equals(PREFIX_USER_MAPPING)) { + return createUserMappingRule(ruleMatch, ruleTarget); + } + + if (ruleType.equals(PREFIX_GROUP_MAPPING)) { + return createGroupMappingRule(ruleMatch, ruleTarget); + } + + throw new IllegalArgumentException( + "User group mapping rule must start with prefix '" + + PREFIX_USER_MAPPING + "' or '" + PREFIX_GROUP_MAPPING + "'"); + } + + /** + * This intermediate helper method is used to process Application name mapping + * rules and invoke the proper mapping rule creation method. + * @param rule The legacy format of the single rule to be converted. + * @return The ObjectNode which can be added to the rules part of the config. + */ + ObjectNode convertAppNameMappingRule(String rule) { + String[] mapping = splitRule(rule, 2); + String ruleMatch = mapping[0]; + String ruleTarget = mapping[1]; + + return createApplicationNameMappingRule(ruleMatch, ruleTarget); + } + /** + * Helper method which splits the rules into parts, and checks if it has + * exactly the required amount of parts, and none of them is empty! + * @param rule The mapping rule to be split + * @param expectedParts The number of expected parts + * @return The split String[] of the parts + * @throws IllegalArgumentException if the number of parts don't match or any + * of them is empty. + */ + private String[] splitRule(String rule, int expectedParts) { + //Splitting + String[] mapping = StringUtils + .getTrimmedStringCollection(rule, RULE_PART_DELIMITER) + .toArray(new String[] {}); + + //Checking for part count + if (mapping.length != expectedParts) { + throw new IllegalArgumentException("Invalid rule '" + rule + + "' expected parts: " + expectedParts + + " actual parts: " + mapping.length); + } + + //Checking for empty parts + for (int i = 0; i < mapping.length; i++) { + if (mapping[i].length() == 0) { + throw new IllegalArgumentException("Invalid rule '" + rule + + "' with empty part, mapping rules must not contain empty parts!"); + } + } + + return mapping; + } + + /** + * This helper method is to create a default rule node for the converter, + * setting fields which are common in all rules. + * @param type The type of the rule can be user/group/application + * @return The object node with the preset fields + */ + private ObjectNode createDefaultRuleNode(String type) { + return objectMapper + .createObjectNode() + .put("type", type) + //All legacy rule fallback to place to default + .put("fallbackResult", "placeDefault") + //All legacy rules allow creation + .put("create", true); + } + + /** + * This method will create the JSON node for a single User Mapping Rule. + * @param match The match part of the rule it can be either an actual user + * name or '%user' to match all users + * @param target The queue to place to user into, some queue path variables + * are supported (%user, %primary_group, %secondary_group). + * @return The ObjectNode which represents the rule + */ + private ObjectNode createUserMappingRule(String match, String target) { + ObjectNode ruleNode = createDefaultRuleNode("user"); + MappingQueuePath targetPath = new MappingQueuePath(target); + + //We have a special token in the JSON format to match all user, replacing + //matcher + if (match.equals(MATCHER_USER)) { + match = JSON_MATCH_ALL; + } + ruleNode.put(JSON_NODE_MATCHES, match); + + switch (targetPath.getLeafName()) { + case MAPPING_USER: + ruleNode.put(JSON_NODE_POLICY, "user"); + if (targetPath.hasParent()) { + //Parsing parent path, to be able to determine the short name of parent + MappingQueuePath targetParentPath = + new MappingQueuePath(targetPath.getParent()); + String parentShortName = targetParentPath.getLeafName(); + + if (parentShortName.equals(MAPPING_PRIMARY_GROUP)) { + //%primary_group.%user mapping + ruleNode.put(JSON_NODE_POLICY, "primaryGroupUser"); + + //Yep, this is confusing. The policy primaryGroupUser actually + // appends the %primary_group.%user to the parent path, so we need to + // remove it from the parent path to avoid duplication. + targetPath = new MappingQueuePath(targetParentPath.getParent(), + targetPath.getLeafName()); + } else if (parentShortName.equals(MAPPING_SECONDARY_GROUP)) { + //%secondary_group.%user mapping + ruleNode.put(JSON_NODE_POLICY, "secondaryGroupUser"); + + //Yep, this is confusing. The policy secondaryGroupUser actually + // appends the %secondary_group.%user to the parent path, so we need + // to remove it from the parent path to avoid duplication. + targetPath = new MappingQueuePath(targetParentPath.getParent(), + targetPath.getLeafName()); + } + + //[parent].%user mapping + } + break; + case MAPPING_PRIMARY_GROUP: + //[parent].%primary_group mapping + ruleNode.put(JSON_NODE_POLICY, "primaryGroup"); + break; + case MAPPING_SECONDARY_GROUP: + //[parent].%secondary_group mapping + ruleNode.put(JSON_NODE_POLICY, "secondaryGroup"); + break; + default: + //static path mapping + ruleNode.put(JSON_NODE_POLICY, "custom"); + ruleNode.put(JSON_NODE_CUSTOM_PLACEMENT, targetPath.getFullPath()); + break; + } + + //if the target queue has a parent part, and the rule can have a parent + //we add it to the node + if (targetPath.hasParent()) { + ruleNode.put(JSON_NODE_PARENT_QUEUE, targetPath.getParent()); + } + + return ruleNode; + } + + /** + * This method will create the JSON node for a single Group Mapping Rule. + * @param match The name of the group to match for + * @param target The queue to place to user into, some queue path variables + * are supported (%user). + * @return The ObjectNode which represents the rule + */ + private ObjectNode createGroupMappingRule(String match, String target) { + ObjectNode ruleNode = createDefaultRuleNode("group"); + MappingQueuePath targetPath = new MappingQueuePath(target); + + //we simply used the source match part all valid legacy matchers are valid + //matchers for the JSON format as well + ruleNode.put(JSON_NODE_MATCHES, match); + + if (targetPath.getLeafName().matches(MATCHER_USER)) { + //g:group:[parent].%user mapping + ruleNode.put(JSON_NODE_POLICY, "user"); + + //if the target queue has a parent part we add it to the node + if (targetPath.hasParent()) { + ruleNode.put(JSON_NODE_PARENT_QUEUE, targetPath.getParent()); + } + } else { + //static path mapping + ruleNode.put(JSON_NODE_POLICY, "custom"); + ruleNode.put(JSON_NODE_CUSTOM_PLACEMENT, targetPath.getFullPath()); + } + + return ruleNode; + } + + + /** + * This method will create the JSON node for a single Application Name + * Mapping Rule. + * @param match The name of the application to match for or %application to + * match all applications + * @param target The queue to place to user into, some queue path variables + * are supported (%application). + * @return The ObjectNode which represents the rule + */ + private ObjectNode createApplicationNameMappingRule( + String match, String target) { + ObjectNode ruleNode = createDefaultRuleNode("application"); + MappingQueuePath targetPath = new MappingQueuePath(target); + + //we simply used the source match part all valid legacy matchers are valid + //matchers for the JSON format as well + ruleNode.put(JSON_NODE_MATCHES, match); + + if (targetPath.getLeafName().matches(MATCHER_APPLICATION)) { + //[parent].%application mapping + ruleNode.put(JSON_NODE_POLICY, "applicationName"); + + //if the target queue has a parent part we add it to the node + if (targetPath.hasParent()) { + ruleNode.put(JSON_NODE_PARENT_QUEUE, targetPath.getParent()); + } + } else { + //static path mapping + ruleNode.put(JSON_NODE_POLICY, "custom"); + ruleNode.put(JSON_NODE_CUSTOM_PLACEMENT, targetPath.getFullPath()); + } + + return ruleNode; + } +} \ No newline at end of file diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestCSMappingPlacementRule.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestCSMappingPlacementRule.java index 6ee7b5df617..703d517ea7d 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestCSMappingPlacementRule.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestCSMappingPlacementRule.java @@ -467,7 +467,7 @@ void assertConfigTestResult(List rules) { assertTrue("Rule's match value should be bob", ruleStr.contains("value='bob'")); assertTrue("Rule's action should be place to queue", ruleStr.contains( - "action=PlaceToQueueAction{queueName='%primary_group'}")); + "action=PlaceToQueueAction{queueName='%primary_group'")); } @Test diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestMappingRuleActions.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestMappingRuleActions.java index 769d051539b..4d4daa13569 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestMappingRuleActions.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/placement/TestMappingRuleActions.java @@ -166,9 +166,10 @@ public void testToStrings() { "%var", "value"); MappingRuleAction reject = new MappingRuleActions.RejectAction(); - assertEquals("PlaceToQueueAction{queueName='queue'}", place.toString()); + assertEquals("PlaceToQueueAction{queueName='queue',allowCreate=true}", + place.toString()); assertEquals("VariableUpdateAction{variableName='%var'" + - ", variableValue='value'}", varUpdate.toString()); + ", variableValue='value'}", varUpdate.toString()); assertEquals("RejectAction", reject.toString()); } } \ No newline at end of file diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/placement/converter/TestLegacyMappingRuleToJson.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/placement/converter/TestLegacyMappingRuleToJson.java new file mode 100644 index 00000000000..5e83884c06b --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/placement/converter/TestLegacyMappingRuleToJson.java @@ -0,0 +1,215 @@ +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.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 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 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.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()); + + 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) {} + } +} \ No newline at end of file -- 2.26.2