AMBARI-14938. Some user-specified auth-to-local rules fail to render when auto generating auth-to-local rules (rlevas)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/a396ff02 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/a396ff02 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/a396ff02 Branch: refs/heads/branch-dev-patch-upgrade Commit: a396ff0203093ba80460657b4f7a4fecfb2b7ed1 Parents: e139ef5 Author: Robert Levas <rle...@hortonworks.com> Authored: Mon Feb 8 13:39:30 2016 -0500 Committer: Robert Levas <rle...@hortonworks.com> Committed: Mon Feb 8 13:39:30 2016 -0500 ---------------------------------------------------------------------- .../server/controller/AuthToLocalBuilder.java | 287 ++++++++++------- .../server/controller/KerberosHelperImpl.java | 15 +- .../controller/AuthToLocalBuilderTest.java | 315 ++++++++++++------- 3 files changed, 381 insertions(+), 236 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/a396ff02/ambari-server/src/main/java/org/apache/ambari/server/controller/AuthToLocalBuilder.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AuthToLocalBuilder.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AuthToLocalBuilder.java index a8fc487..9d6db0a 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AuthToLocalBuilder.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AuthToLocalBuilder.java @@ -18,7 +18,10 @@ package org.apache.ambari.server.controller; +import org.apache.commons.lang.StringUtils; + import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -40,19 +43,18 @@ import java.util.regex.Pattern; * <p/> * Unqualified Principal (only user is specified): * RULE:[1:$1@$0](PRIMARY@REALM)s/.*\/LOCAL_USERNAME/ - * <p> + * <p/> * Additionally, for each realm included in the rule set, generate a default realm rule * in the format: RULE:[1:$1@$0](.*@REALM)s/@.{@literal *}// - * <p> + * <p/> * Ordering guarantees for the generated rule string are as follows: * <ul> - * <li>Rules with the same expected component count are ordered according to match component count</li> - * <li>Rules with different expected component count are ordered according to the default string ordering</li> - * <li>Rules in the form of .*@REALM are ordered after all other rules with the same expected component count</li> + * <li>Rules with the same expected component count are ordered according to match component count</li> + * <li>Rules with different expected component count are ordered according to the default string ordering</li> + * <li>Rules in the form of .*@REALM are ordered after all other rules with the same expected component count</li> * </ul> - * */ -public class AuthToLocalBuilder { +public class AuthToLocalBuilder implements Cloneable { public static final ConcatenationType DEFAULT_CONCATENATION_TYPE = ConcatenationType.NEW_LINES; /** @@ -60,43 +62,62 @@ public class AuthToLocalBuilder { */ private Set<Rule> setRules = new TreeSet<Rule>(); - /** - * A flag indicating whether case insensitive support to the local username has been requested. This will append an //L switch to the generic realm rule + * The default realm. */ - private boolean caseInsensitiveUser; + private final String defaultRealm; /** * A set of additional realm names to reference when generating rules. */ - private Set<String> additionalRealms = new HashSet<String>(); + private final Set<String> additionalRealms; + /** - * Default constructor. Case insensitive support false by default + * A flag indicating whether case insensitive support to the local username has been requested. This will append an //L switch to the generic realm rule + */ + private boolean caseInsensitiveUser; + + /** + * Constructs a new AuthToLocalBuilder. + * + * @param defaultRealm a String declaring the default realm + * @param additionalRealms a String containing a comma-delimited list of realm names + * to incorporate into the generated rule set + * @param caseInsensitiveUserSupport true indicating that case-insensitivity should be enabled; + * false otherwise */ - public AuthToLocalBuilder() { - this(false, null); + public AuthToLocalBuilder(String defaultRealm, String additionalRealms, boolean caseInsensitiveUserSupport) { + this(defaultRealm, splitDelimitedString(additionalRealms), caseInsensitiveUserSupport); } /** * Constructs a new AuthToLocalBuilder. * + * @param defaultRealm a String declaring the default realm + * @param additionalRealms a collection of Strings declaring the set of realm names to + * incorporate into the generated rule set * @param caseInsensitiveUserSupport true indicating that case-insensitivity should be enabled; * false otherwise - * @param additionalRealms a String containing a comma-delimited list of realm names to generate - * default auth-to-local rules for */ - public AuthToLocalBuilder(boolean caseInsensitiveUserSupport, String additionalRealms) { + public AuthToLocalBuilder(String defaultRealm, Collection<String> additionalRealms, boolean caseInsensitiveUserSupport) { + this.defaultRealm = defaultRealm; + + this.additionalRealms = (additionalRealms == null) + ? Collections.<String>emptySet() + : Collections.unmodifiableSet(new HashSet<String>(additionalRealms)); + this.caseInsensitiveUser = caseInsensitiveUserSupport; + } - if ((additionalRealms != null) && !additionalRealms.isEmpty()) { - for (String realm : additionalRealms.split("\\s*(?:\\r?\\n|,)\\s*")) { - realm = realm.trim(); - if (!realm.isEmpty()) { - this.additionalRealms.add(realm); - } - } - } + @Override + public Object clone() throws CloneNotSupportedException { + AuthToLocalBuilder copy = (AuthToLocalBuilder) super.clone(); + + /* **** Copy mutable members **** */ + copy.setRules = new TreeSet<Rule>(setRules); + + return copy; } /** @@ -106,15 +127,13 @@ public class AuthToLocalBuilder { * @param authToLocalRules config property value containing the existing rules */ public void addRules(String authToLocalRules) { - if (authToLocalRules != null && ! authToLocalRules.isEmpty()) { + if (!StringUtils.isEmpty(authToLocalRules)) { String[] rules = authToLocalRules.split("RULE:|DEFAULT"); for (String r : rules) { r = r.trim(); - if (! r.isEmpty()) { + if (!r.isEmpty()) { Rule rule = createRule(r); setRules.add(rule); - // ensure that a default rule is added for each realm - addDefaultRealmRule(rule.getPrincipal()); } } } @@ -139,9 +158,7 @@ public class AuthToLocalBuilder { * @throws IllegalArgumentException if the provided principal doesn't contain a realm element */ public void addRule(String principal, String localUsername) { - if ((principal != null) && (localUsername != null) && - !principal.isEmpty() && !localUsername.isEmpty()) { - + if (!StringUtils.isEmpty(principal) && !StringUtils.isEmpty(localUsername)) { Principal p = new Principal(principal); if (p.getRealm() == null) { throw new IllegalArgumentException( @@ -158,13 +175,12 @@ public class AuthToLocalBuilder { * Generates the auth_to_local rules used by configuration settings such as core-site/auth_to_local. * <p/> * Each rule is concatenated using the default ConcatenationType, like calling - * {@link #generate(String, ConcatenationType)} with {@link #DEFAULT_CONCATENATION_TYPE} + * {@link #generate(ConcatenationType)} with {@link #DEFAULT_CONCATENATION_TYPE} * - * @param realm a string declaring the realm to use in rule set * @return a string containing the generated auth-to-local rule set */ - public String generate(String realm) { - return generate(realm, null); + public String generate() { + return generate(null); } /** @@ -175,14 +191,15 @@ public class AuthToLocalBuilder { * If the concatenation type is <code>null</code>, the default concatenation type is assumed - * see {@link #DEFAULT_CONCATENATION_TYPE}. * - * @param realm a string declaring the realm to use in rule set * @param concatenationType the concatenation type to use to generate the rule set string * @return a string containing the generated auth-to-local rule set */ - public String generate(String realm, ConcatenationType concatenationType) { + public String generate(ConcatenationType concatenationType) { StringBuilder builder = new StringBuilder(); // ensure that a default rule is added for this realm - setRules.add(createDefaultRealmRule(realm)); + if (!StringUtils.isEmpty(defaultRealm)) { + setRules.add(createDefaultRealmRule(defaultRealm)); + } // ensure that a default realm rule is added for the specified additional realms for (String additionalRealm : additionalRealms) { @@ -233,11 +250,11 @@ public class AuthToLocalBuilder { * Add a default realm rule for the realm associated with a principal. * If the realm is null or is a wildcard ".*" then no rule id added. * - * @param principal principal which contains the realm + * @param principal principal which contains the realm */ private void addDefaultRealmRule(Principal principal) { String realm = principal.getRealm(); - if (realm != null && ! realm.equals(".*")) { + if (realm != null && !realm.equals(".*")) { setRules.add(createDefaultRealmRule(realm)); } } @@ -245,9 +262,8 @@ public class AuthToLocalBuilder { /** * Create a rule that expects 2 components in the principal and ignores hostname in the comparison. * - * @param principal principal - * @param localUser local user - * + * @param principal principal + * @param localUser local user * @return a new rule that ignores hostname in the comparison */ private Rule createHostAgnosticRule(Principal principal, String localUser) { @@ -262,44 +278,47 @@ public class AuthToLocalBuilder { /** * Create a default rule for a realm which matches all principals with 1 component and the same realm. * - * @param realm realm that the rule is being created for - * - * @return a new default realm rule + * @param realm realm that the rule is being created for + * @return a new default realm rule */ private Rule createDefaultRealmRule(String realm) { String caseSensitivityRule = caseInsensitiveUser ? "/L" : ""; return new Rule(new Principal(String.format(".*@%s", realm)), - 1, 1, String.format("RULE:[1:$1@$0](.*@%s)s/@.*//" + caseSensitivityRule, realm)); + 1, 1, String.format("RULE:[1:$1@$0](.*@%s)s/@.*//" + caseSensitivityRule, realm)); } /** * Create a rule from an existing string representation. - * @param rule string representation of a rule * - * @return a new rule which matches the provided string representation + * @param rule string representation of a rule + * @return a new rule which matches the provided string representation */ private Rule createRule(String rule) { return new Rule(rule.startsWith("RULE:") ? rule : String.format("RULE:%s", rule)); } /** - * Creates and returns a deep copy of this AuthToLocalBuilder. + * Given a comma or line delimited list of strings, returns a collection of non-empty strings. * - * @return a deep copy of this AuthToLocalBuilder + * @param string a string to split + * @return an array of non-empty strings or null if the source string is empty or null */ - public AuthToLocalBuilder copy() { - AuthToLocalBuilder copy = new AuthToLocalBuilder(); + private static Collection<String> splitDelimitedString(String string) { + Collection<String> collection = null; - // TODO: This needs to be done in a loop rather than use Set.addAll because there may be an issue - // TODO: with the Rule.compareTo method? - for(Rule rule:setRules) { - copy.setRules.add(rule); + if (!StringUtils.isEmpty(string)) { + collection = new HashSet<String>(); + + for (String realm : string.split("\\s*(?:\\r?\\n|,)\\s*")) { + realm = realm.trim(); + if (!realm.isEmpty()) { + collection.add(realm); + } + } } - copy.caseInsensitiveUser = this.caseInsensitiveUser; - copy.additionalRealms.addAll(this.additionalRealms); - return copy; + return collection; } @@ -311,7 +330,7 @@ public class AuthToLocalBuilder { * pattern used to parse existing rules */ private static final Pattern PATTERN_RULE_PARSE = - Pattern.compile("RULE:\\s*\\[\\s*(\\d)\\s*:\\s*(.+?)(?:@(.+?))??\\s*\\]\\s*\\((.+?)\\)\\s*([^\\\\\\n]*)(.|\\n)*"); + Pattern.compile("RULE:\\s*\\[\\s*(\\d)\\s*:\\s*(.+?)(?:@(.+?))??\\s*\\]\\s*\\((.+?)\\)\\s*s/(.*?)/(.*?)/([a-zA-Z]*)(?:.|\n)*"); /** * associated principal @@ -336,10 +355,10 @@ public class AuthToLocalBuilder { /** * Constructor. * - * @param principal principal - * @param expectedComponentCount number of components needed by a principal to match - * @param matchComponentCount number of components which are included in the rule evaluation - * @param rule string representation of the rule + * @param principal principal + * @param expectedComponentCount number of components needed by a principal to match + * @param matchComponentCount number of components which are included in the rule evaluation + * @param rule string representation of the rule */ public Rule(Principal principal, int expectedComponentCount, int matchComponentCount, String rule) { this.principal = principal; @@ -351,12 +370,12 @@ public class AuthToLocalBuilder { /** * Constructor. * - * @param rule string representation of the rule + * @param rule string representation of the rule */ public Rule(String rule) { //this.rule = rule; Matcher m = PATTERN_RULE_PARSE.matcher(rule); - if (! m.matches()) { + if (!m.matches()) { throw new IllegalArgumentException("Invalid rule: " + rule); } expectedComponentCount = Integer.valueOf(m.group(1)); @@ -365,18 +384,20 @@ public class AuthToLocalBuilder { matchComponentCount = (matchPattern.startsWith("$") ? matchPattern.substring(1) : matchPattern). - split("\\$").length; + split("\\$").length; String patternRealm = m.group(3); principal = new Principal(m.group(4)); - String replacementRule = m.group(5); + String replacementPattern = m.group(5); + String replacementReplacement = m.group(6); + String replacementModifier = m.group(7); if (patternRealm != null) { - this.rule = String.format("RULE:[%d:%s@%s](%s)%s", + this.rule = String.format("RULE:[%d:%s@%s](%s)s/%s/%s/%s", expectedComponentCount, matchPattern, patternRealm, - principal.toString(), replacementRule); + principal.toString(), replacementPattern, replacementReplacement, replacementModifier); } else { - this.rule = String.format("RULE:[%d:%s](%s)%s", + this.rule = String.format("RULE:[%d:%s](%s)s/%s/%s/%s", expectedComponentCount, matchPattern, - principal.toString(), replacementRule); + principal.toString(), replacementPattern, replacementReplacement, replacementModifier); } } @@ -422,55 +443,52 @@ public class AuthToLocalBuilder { /** * Compares rules. - * <p> + * <p/> * For rules with different expected component counts, the default string comparison is used. * For rules with the same expected component count rules are ordered so that rules with a higher * match component count occur first. - * <p> + * <p/> * For rules with the same expected component count, default realm rules in the form of * .*@myRealm.com are ordered last. * - * @param other the other rule to compare - * + * @param other the other rule to compare * @return a negative integer, zero, or a positive integer as this object is less than, - * equal to, or greater than the specified object + * equal to, or greater than the specified object */ @Override public int compareTo(Rule other) { - Principal thatPrincipal = other.getPrincipal(); - //todo: better implementation that recursively evaluates realm and all components - if (expectedComponentCount != other.getExpectedComponentCount()) { - return rule.compareTo(other.rule); - } else { - if (matchComponentCount != other.getMatchComponentCount()) { - return other.getMatchComponentCount() - matchComponentCount; - } else { - if (principal.equals(thatPrincipal)) { - return rule.compareTo(other.rule); + int retVal = expectedComponentCount - other.getExpectedComponentCount(); + + if (retVal == 0) { + retVal = other.getMatchComponentCount() - matchComponentCount; + + if (retVal == 0) { + Principal otherPrincipal = other.getPrincipal(); + if (principal.equals(otherPrincipal)) { + retVal = rule.compareTo(other.rule); } else { // check for wildcard realms '.*' String realm = principal.getRealm(); - String thatRealm = thatPrincipal.getRealm(); - if (realm == null ? thatRealm != null : ! realm.equals(thatRealm)) { - if (realm != null && realm.equals(".*")) { - return 1; - } else if (thatRealm != null && thatRealm.equals(".*")) { - return -1; + String otherRealm = otherPrincipal.getRealm(); + retVal = compareValueWithWildcards(realm, otherRealm); + + if (retVal == 0) { + for (int i = 1; i <= matchComponentCount; i++) { + // check for wildcard component + String component1 = principal.getComponent(1); + String otherComponent1 = otherPrincipal.getComponent(1); + retVal = compareValueWithWildcards(component1, otherComponent1); + + if (retVal != 0) { + break; + } } } - // check for wildcard component 1 - String component1 = principal.getComponent(1); - String thatComponent1 = thatPrincipal.getComponent(1); - if (component1 != null && component1.equals(".*")) { - return 1; - } else if(thatComponent1 != null && thatComponent1.equals(".*")) { - return -1; - } else { - return rule.compareTo(other.rule); - } } } } + + return retVal; } @Override @@ -482,6 +500,42 @@ public class AuthToLocalBuilder { public int hashCode() { return rule.hashCode(); } + + /** + * Compares 2 strings for use in compareTo methods but orders <code>null</code>s first and wildcards last. + * <p/> + * Rules: + * <ul> + * <li><code>null</code> is ordered before any other string except for <code>null</code>, which is considered be equal</li> + * <li><code>.*</code> is ordered after any other string except for <code>.*</code>, which is considered equal</li> + * <li>All other values are order based on the result of {@link String#compareTo(String)}</li> + * </ul> + * + * @param s1 the first string to be compared. + * @param s2 the second string to be compared. + * @return a negative integer, zero, or a positive integer as the first argument is less than, + * equal to, or greater than the second. + * @see Comparable#compareTo(Object) + */ + private int compareValueWithWildcards(String s1, String s2) { + if (s1 == null) { + if (s2 == null) { + return 0; + } else { + return -1; + } + } else if (s2 == null) { + return 1; + } else if (s1.equals(s2)) { + return 0; + } else if (s1.equals(".*")) { + return 1; + } else if (s2.equals(".*")) { + return -1; + } else { + return s1.compareTo(s2); + } + } } /** @@ -512,7 +566,7 @@ public class AuthToLocalBuilder { /** * Constructor. * - * @param principal string representation of the principal + * @param principal string representation of the principal */ public Principal(String principal) { this.principal = principal; @@ -547,7 +601,6 @@ public class AuthToLocalBuilder { * Uses the range 1-n to match the notation used in the rule. * * @param position position of the component in the range 1-n - * * @return the component at the specified location or null */ public String getComponent(int position) { @@ -574,16 +627,20 @@ public class AuthToLocalBuilder { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } Principal principal1 = (Principal) o; return components.equals(principal1.components) && - principal.equals(principal1.principal) && - !(realm != null ? - !realm.equals(principal1.realm) : - principal1.realm != null); + principal.equals(principal1.principal) && + !(realm != null ? + !realm.equals(principal1.realm) : + principal1.realm != null); } @@ -622,10 +679,10 @@ public class AuthToLocalBuilder { * @return a ConcatenationType */ public static ConcatenationType translate(String value) { - if(value != null) { + if (value != null) { value = value.trim(); - if(!value.isEmpty()) { + if (!value.isEmpty()) { return valueOf(value.toUpperCase()); } } http://git-wip-us.apache.org/repos/asf/ambari/blob/a396ff02/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java index 556bed8..fe1ba46 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java @@ -664,7 +664,7 @@ public class KerberosHelperImpl implements KerberosHelper { String additionalRealms = kerberosDescriptor.getProperty("additional_realms"); // Determine which properties need to be set - AuthToLocalBuilder authToLocalBuilder = new AuthToLocalBuilder(caseInsensitiveUser, additionalRealms); + AuthToLocalBuilder authToLocalBuilder = new AuthToLocalBuilder(realm, additionalRealms, caseInsensitiveUser); addIdentities(authToLocalBuilder, kerberosDescriptor.getIdentities(), null, existingConfigurations); authToLocalProperties = kerberosDescriptor.getAuthToLocalProperties(); @@ -750,7 +750,14 @@ public class KerberosHelperImpl implements KerberosHelper { Matcher m = KerberosDescriptor.AUTH_TO_LOCAL_PROPERTY_SPECIFICATION_PATTERN.matcher(authToLocalProperty); if (m.matches()) { - AuthToLocalBuilder builder = authToLocalBuilder.copy(); + AuthToLocalBuilder builder; + try { + builder = (AuthToLocalBuilder) authToLocalBuilder.clone(); + } catch (CloneNotSupportedException e) { + LOG.error("Failed to clone the AuthToLocalBuilder: " + e.getLocalizedMessage(), e); + throw new AmbariException("Failed to clone the AuthToLocalBuilder: " + e.getLocalizedMessage(), e); + } + String configType = m.group(1); String propertyName = m.group(2); @@ -773,8 +780,8 @@ public class KerberosHelperImpl implements KerberosHelper { kerberosConfigurations.put(configType, kerberosConfiguration); } - kerberosConfiguration.put(propertyName, builder.generate(realm, - AuthToLocalBuilder.ConcatenationType.translate(m.group(3)))); + kerberosConfiguration.put(propertyName, + builder.generate(AuthToLocalBuilder.ConcatenationType.translate(m.group(3)))); } } } http://git-wip-us.apache.org/repos/asf/ambari/blob/a396ff02/ambari-server/src/test/java/org/apache/ambari/server/controller/AuthToLocalBuilderTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/AuthToLocalBuilderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/AuthToLocalBuilderTest.java index 122e632..c88acc1 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/controller/AuthToLocalBuilderTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/AuthToLocalBuilderTest.java @@ -22,6 +22,7 @@ import org.apache.ambari.server.utils.CollectionPresentationUtils; import org.junit.Test; import java.util.Arrays; +import java.util.Collections; import java.util.List; import static org.junit.Assert.*; @@ -30,7 +31,7 @@ public class AuthToLocalBuilderTest { @Test public void testRuleGeneration() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); builder.addRule("nn/_h...@example.com", "hdfs"); // Duplicate principal for secondary namenode, should be filtered out... @@ -46,22 +47,22 @@ public class AuthToLocalBuilderTest { assertEquals( "RULE:[1:$1@$0](foo...@example.com)s/.*/hdfs/\n" + - "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + - "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](h...@example.com)s/.*/hbase/\n" + - "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\n" + - "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/\n" + - "RULE:[2:$1@$0](r...@example.com)s/.*/hbase/\n" + - "DEFAULT", - builder.generate("EXAMPLE.COM")); + "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + + "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](h...@example.com)s/.*/hbase/\n" + + "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\n" + + "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/\n" + + "RULE:[2:$1@$0](r...@example.com)s/.*/hbase/\n" + + "DEFAULT", + builder.generate()); } @Test public void testRuleGeneration_caseInsensitiveSupport() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(true, null); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), true); builder.addRule("nn/_h...@example.com", "hdfs"); // Duplicate principal for secondary namenode, should be filtered out... @@ -76,30 +77,30 @@ public class AuthToLocalBuilderTest { builder.addRule("foo...@example.com", "hdfs"); assertEquals( - "RULE:[1:$1@$0](foo...@example.com)s/.*/hdfs/\n" + - "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*///L\n" + - "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](h...@example.com)s/.*/hbase/\n" + - "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\n" + - "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/\n" + - "RULE:[2:$1@$0](r...@example.com)s/.*/hbase/\n" + - "DEFAULT", - builder.generate("EXAMPLE.COM")); + "RULE:[1:$1@$0](foo...@example.com)s/.*/hdfs/\n" + + "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*///L\n" + + "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](h...@example.com)s/.*/hbase/\n" + + "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\n" + + "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/\n" + + "RULE:[2:$1@$0](r...@example.com)s/.*/hbase/\n" + + "DEFAULT", + builder.generate()); } @Test public void testRuleGeneration_ExistingRules() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); // previously generated non-host specific rules builder.addRule("foo...@example.com", "hdfs"); // doesn't exist in latter generation builder.addRule("hm/_h...@example.com", "hbase"); builder.addRule("nn/_h...@example.com", "hdfs"); - String existingRules = builder.generate("EXAMPLE.COM"); + String existingRules = builder.generate(); - builder = new AuthToLocalBuilder(); + builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); // set previously existing rules builder.addRules(existingRules); @@ -115,32 +116,32 @@ public class AuthToLocalBuilderTest { assertEquals( "RULE:[1:$1@$0](foo...@example.com)s/.*/hdfs/\n" + - "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + - "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](h...@example.com)s/.*/hbase/\n" + - "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\n" + - "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/\n" + - "RULE:[2:$1@$0](r...@example.com)s/.*/hbase/\n" + - "DEFAULT", - builder.generate("EXAMPLE.COM")); + "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + + "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](h...@example.com)s/.*/hbase/\n" + + "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\n" + + "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/\n" + + "RULE:[2:$1@$0](r...@example.com)s/.*/hbase/\n" + + "DEFAULT", + builder.generate()); } @Test public void testRuleGeneration_ExistingRules_existingMoreSpecificRule() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); // previously generated non-host specific rules builder.addRule("foo...@example.com", "hdfs"); builder.addRule("hm/_h...@example.com", "hbase"); builder.addRule("jn/_h...@example.com", "hdfs"); - String existingRules = builder.generate("EXAMPLE.COM"); + String existingRules = builder.generate(); // prepend host specific rule existingRules = "RULE:[2:$1/$2@$0](dn/somehost....@example.com)s/.*/hdfs/\n" + existingRules; // append default realm rule for additional realm existingRules += "\nRULE:[1:$1@$0](.*@OTHER_REALM.COM)s/@.*//"; - builder = new AuthToLocalBuilder(); + builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); // set previously existing rules builder.addRules(existingRules); // more specific host qualifed rule exists for dn @@ -158,29 +159,29 @@ public class AuthToLocalBuilderTest { assertEquals( "RULE:[1:$1@$0](foo...@example.com)s/.*/hdfs/\n" + - "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + - "RULE:[1:$1@$0](.*@OTHER_REALM.COM)s/@.*//\n" + - "RULE:[2:$1/$2@$0](dn/somehost....@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](h...@example.com)s/.*/hbase/\n" + - "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\n" + - "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/\n" + - "RULE:[2:$1@$0](r...@example.com)s/.*/hbase/\n" + - "DEFAULT", - builder.generate("EXAMPLE.COM")); + "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + + "RULE:[1:$1@$0](.*@OTHER_REALM.COM)s/@.*//\n" + + "RULE:[2:$1/$2@$0](dn/somehost....@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](h...@example.com)s/.*/hbase/\n" + + "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\n" + + "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/\n" + + "RULE:[2:$1@$0](r...@example.com)s/.*/hbase/\n" + + "DEFAULT", + builder.generate()); } @Test public void testAddNullExistingRule() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); builder.addRules(null); assertEquals( "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + - "DEFAULT", - builder.generate("EXAMPLE.COM") + "DEFAULT", + builder.generate() ); } @@ -194,7 +195,7 @@ public class AuthToLocalBuilderTest { "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\\\\\\" + "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\\/\\"; - AuthToLocalBuilder builder = new AuthToLocalBuilder(); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); builder.addRules(rules); assertEquals( @@ -205,80 +206,116 @@ public class AuthToLocalBuilderTest { "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\n" + "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + "DEFAULT", - builder.generate("EXAMPLE.COM")); + builder.generate()); } @Test + public void testRuleRegexWithComplexReplacements() { + String rules = + "RULE:[1:$1@$0](foobar@\\QEXAMPLE1.COM\\E$)s/.*@\\QEXAMPLE1.COM\\E$/hdfs/\n" + + "RULE:[1:$1@$0](.*@\\QEXAMPLE1.COM\\E)s/@\\QEXAMPLE1.COM\\E//\n" + + "RULE:[2:$1@$0](.*@\\QEXAMPLE1.COM\\E)s/@\\QEXAMPLE1.COM\\E//"; + + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); + builder.addRules(rules); + + builder.addRule("nn/_h...@example.com", "hdfs"); + builder.addRule("dn/_h...@example.com", "hdfs"); + builder.addRule("jn/_h...@example.com", "hdfs"); + builder.addRule("rm/_h...@example.com", "yarn"); + builder.addRule("jhs/_h...@example.com", "mapred"); + builder.addRule("hm/_h...@example.com", "hbase"); + builder.addRule("rs/_h...@example.com", "hbase"); + builder.addRule("ambari-qa...@example.com", "ambari-qa"); + + assertEquals( + "RULE:[1:$1@$0](ambari-qa...@example.com)s/.*/ambari-qa/\n" + + "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + + "RULE:[1:$1@$0](.*@\\QEXAMPLE1.COM\\E)s/@\\QEXAMPLE1.COM\\E//\n" + + "RULE:[1:$1@$0](foobar@\\QEXAMPLE1.COM\\E$)s/.*@\\QEXAMPLE1.COM\\E$/hdfs/\n" + + "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](h...@example.com)s/.*/hbase/\n" + + "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\n" + + "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/\n" + + "RULE:[2:$1@$0](r...@example.com)s/.*/hbase/\n" + + "RULE:[2:$1@$0](.*@\\QEXAMPLE1.COM\\E)s/@\\QEXAMPLE1.COM\\E//\n" + + "DEFAULT", + builder.generate()); + } + + @Test public void testRulesWithWhitespace() { String rulesWithWhitespace = "RULE: [1:$1@$0](foo...@example.com)s/.*/hdfs/\n" + - "RULE:[ 1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + - "RULE:[2: $1@$0](d...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0 ](h...@example.com)s/.*/hbase/\n" + - "RULE:[2:$1@$0] (j...@example.com)s/.*/mapred/\n" + - "RULE:[2:$1@$0](j...@example.com) s/.*/hdfs/\n"; + "RULE:[ 1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + + "RULE:[2: $1@$0](d...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0 ](h...@example.com)s/.*/hbase/\n" + + "RULE:[2:$1@$0] (j...@example.com)s/.*/mapred/\n" + + "RULE:[2:$1@$0](j...@example.com) s/.*/hdfs/\n"; - AuthToLocalBuilder builder = new AuthToLocalBuilder(); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); builder.addRules(rulesWithWhitespace); assertEquals( "RULE:[1:$1@$0](foo...@example.com)s/.*/hdfs/\n" + - "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + - "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](h...@example.com)s/.*/hbase/\n" + - "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\n" + - "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + - "DEFAULT", - builder.generate("EXAMPLE.COM")); + "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + + "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](h...@example.com)s/.*/hbase/\n" + + "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\n" + + "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + + "DEFAULT", + builder.generate()); } @Test public void testExistingRuleWithNoRealm() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); builder.addRules("RULE:[1:$1](foobar)s/.*/hdfs/"); assertEquals( "RULE:[1:$1](foobar)s/.*/hdfs/\n" + - "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + - "DEFAULT", - builder.generate("EXAMPLE.COM")); + "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + + "DEFAULT", + builder.generate()); } @Test public void testExistingRuleWithNoRealm2() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); builder.addRules("RULE:[1:$1/$2](foobar/someHost)s/.*/hdfs/"); assertEquals( "RULE:[1:$1/$2](foobar/someHost)s/.*/hdfs/\n" + - "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + - "DEFAULT", - builder.generate("EXAMPLE.COM")); + "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + + "DEFAULT", + builder.generate()); } - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void testAddNewRuleWithNoRealm() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); builder.addRule("someUser", "hdfs"); } - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void testAddNewRuleWithNoRealm2() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); builder.addRule("someUser/someHost", "hdfs"); } @Test public void testExistingWildcardRealm() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); builder.addRules("RULE:[2:$1@$0]([rn]m@.*)s/.*/yarn/\n" + - "RULE:[2:$1@$0]([nd]n@.*)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](.*@EXAMPLE.COM)s/.*/yarn/\n" + - "DEFAULT"); + "RULE:[2:$1@$0]([nd]n@.*)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](.*@EXAMPLE.COM)s/.*/yarn/\n" + + "DEFAULT"); builder.addRule("nn/_h...@example.com", "hdfs"); builder.addRule("jn/_h...@example.com", "hdfs"); @@ -287,18 +324,18 @@ public class AuthToLocalBuilderTest { // other rules with the same number of expected principal components assertEquals( "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + - "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/\n" + - "RULE:[2:$1@$0](.*@EXAMPLE.COM)s/.*/yarn/\n" + - "RULE:[2:$1@$0]([nd]n@.*)s/.*/hdfs/\n" + - "RULE:[2:$1@$0]([rn]m@.*)s/.*/yarn/\n" + - "DEFAULT", - builder.generate("EXAMPLE.COM")); + "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](.*@EXAMPLE.COM)s/.*/yarn/\n" + + "RULE:[2:$1@$0]([nd]n@.*)s/.*/hdfs/\n" + + "RULE:[2:$1@$0]([rn]m@.*)s/.*/yarn/\n" + + "DEFAULT", + builder.generate()); } @Test - public void testCopy() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(); + public void testClone() throws CloneNotSupportedException { + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); builder.addRule("nn/_h...@example.com", "hdfs"); builder.addRule("dn/_h...@example.com", "hdfs"); @@ -310,16 +347,18 @@ public class AuthToLocalBuilderTest { builder.addRule("foo...@example.com", "hdfs"); - AuthToLocalBuilder copy = builder.copy(); - + AuthToLocalBuilder copy = (AuthToLocalBuilder) builder.clone(); assertNotSame(builder, copy); - assertEquals(copy.generate("EXAMPLE.COM"), builder.generate("EXAMPLE.COM")); + assertEquals(builder.generate(), copy.generate()); + // Ensure that mutable fields do not change the copy when changed in the original + builder.addRule("u...@example.com", "hdfs"); + assertTrue(!copy.generate().equals(builder.generate())); } @Test public void testAdditionalRealms() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(false, "REALM2,REALM3, REALM1 "); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", "REALM2,REALM3, REALM1 ", false); builder.addRules( "RULE:[1:$1@$0](.*@FOOBAR.COM)s/@.*//\n" + @@ -334,26 +373,26 @@ public class AuthToLocalBuilderTest { builder.addRule("rs/_h...@example.com", "hbase"); // Depends on hashing, string representation can be different - List<String> rules = Arrays.asList(new String[]{"RULE:[1:$1@$0](.*@FOOBAR.COM)s/@.*//", - "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//", - "RULE:[1:$1@$0](.*@REALM2)s/@.*//", - "RULE:[1:$1@$0](.*@REALM1)s/@.*//", - "RULE:[1:$1@$0](.*@REALM3)s/@.*//", - "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/", - "RULE:[2:$1@$0](h...@example.com)s/.*/hbase/", - "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/", - "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/", - "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/", - "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/", - "RULE:[2:$1@$0](r...@example.com)s/.*/hbase/", - "DEFAULT"}); - assertTrue(CollectionPresentationUtils.isStringPermutationOfCollection(builder.generate("EXAMPLE.COM"), rules, - "\n", 0, 0)); + List<String> rules = Arrays.asList("RULE:[1:$1@$0](.*@FOOBAR.COM)s/@.*//", + "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//", + "RULE:[1:$1@$0](.*@REALM2)s/@.*//", + "RULE:[1:$1@$0](.*@REALM1)s/@.*//", + "RULE:[1:$1@$0](.*@REALM3)s/@.*//", + "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/", + "RULE:[2:$1@$0](h...@example.com)s/.*/hbase/", + "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/", + "RULE:[2:$1@$0](j...@example.com)s/.*/hdfs/", + "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/", + "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/", + "RULE:[2:$1@$0](r...@example.com)s/.*/hbase/", + "DEFAULT"); + assertTrue(CollectionPresentationUtils.isStringPermutationOfCollection(builder.generate(), rules, + "\n", 0, 0)); } @Test public void testAdditionalRealms_Null() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(false, null); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", Collections.<String>emptyList(), false); builder.addRule("nn/_h...@example.com", "hdfs"); builder.addRule("dn/_h...@example.com", "hdfs"); @@ -373,12 +412,12 @@ public class AuthToLocalBuilderTest { "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/\n" + "RULE:[2:$1@$0](r...@example.com)s/.*/hbase/\n" + "DEFAULT", - builder.generate("EXAMPLE.COM")); + builder.generate()); } @Test public void testAdditionalRealms_Empty() { - AuthToLocalBuilder builder = new AuthToLocalBuilder(false, ""); + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", "", false); builder.addRule("nn/_h...@example.com", "hdfs"); builder.addRule("dn/_h...@example.com", "hdfs"); @@ -398,6 +437,48 @@ public class AuthToLocalBuilderTest { "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/\n" + "RULE:[2:$1@$0](r...@example.com)s/.*/hbase/\n" + "DEFAULT", - builder.generate("EXAMPLE.COM")); + builder.generate()); + } + + @Test + public void testUseCase() { + AuthToLocalBuilder builder = new AuthToLocalBuilder("EXAMPLE.COM", "FOOBAR.COM,HW.HDP,BAZ.NET", false); + + String existingRules = + "RULE:[1:$1@$0](.*@BAZ.NET)s/@.*//\n" + + "RULE:[1:$1@$0](accumulo...@example.com)s/.*/accumulo/\n" + + "RULE:[1:$1@$0](ambari-qa...@example.com)s/.*/ambari-qa/\n" + + "RULE:[1:$1@$0](hbase...@example.com)s/.*/hbase/\n" + + "RULE:[1:$1@$0](hdfs...@example.com)s/.*/hdfs/\n" + + "RULE:[1:$1@$0](spark...@example.com)s/.*/spark/\n" + + "RULE:[1:$1@$0](tracer...@example.com)s/.*/accumulo/\n" + + "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\n" + + "RULE:[1:$1@$0](.*@FOOBAR.COM)s/@.*//\n" + + "RULE:[1:$1@$0](.*@HW.HDP)s/@.*//\n" + + "RULE:[2:$1@$0](accum...@example.com)s/.*/accumulo/\n" + + "RULE:[2:$1@$0](amshb...@example.com)s/.*/ams/\n" + + "RULE:[2:$1@$0](am...@example.com)s/.*/ams/\n" + + "RULE:[2:$1@$0](d...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](fal...@example.com)s/.*/falcon/\n" + + "RULE:[2:$1@$0](hb...@example.com)s/.*/hbase/\n" + + "RULE:[2:$1@$0](h...@example.com)s/.*/hive/\n" + + "RULE:[2:$1@$0](j...@example.com)s/.*/mapred/\n" + + "RULE:[2:$1@$0](n...@example.com)s/.*/yarn/\n" + + "RULE:[2:$1@$0](n...@example.com)s/.*/hdfs/\n" + + "RULE:[2:$1@$0](oo...@example.com)s/.*/oozie/\n" + + "RULE:[2:$1@$0](r...@example.com)s/.*/yarn/\n" + + "RULE:[2:$1@$0](y...@example.com)s/.*/yarn/\n" + + "DEFAULT"; + + builder.addRules(existingRules); + + builder.addRule("nn/_h...@example.com", "hdfs"); + builder.addRule("dn/_h...@example.com", "hdfs"); + builder.addRule("rm/_h...@example.com", "yarn"); + builder.addRule("yarn/_h...@example.com", "yarn"); + builder.addRule("kafka/_h...@example.com", null); + builder.addRule("hdfs...@example.com", "hdfs"); + + assertEquals(existingRules, builder.generate()); } } \ No newline at end of file