This is an automated email from the ASF dual-hosted git repository.

domgarguilo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/accumulo.git


The following commit(s) were added to refs/heads/main by this push:
     new 9a5d282746 ResourceGroupId validation/usage improvements (#5893)
9a5d282746 is described below

commit 9a5d282746aeb10180ea51b5c5e686e6e63bdced
Author: Dom G. <[email protected]>
AuthorDate: Mon Sep 22 10:00:55 2025 -0400

    ResourceGroupId validation/usage improvements (#5893)
    
    * removed unneeded public API from ResourceGroupId
    * improved group ID validation methods and exception message
    * improved the usage of ResourceGroupId collections in ClusterConfigParser
    * consolidated ResourceGroupId validation test cases
---
 .../apache/accumulo/core/data/ResourceGroupId.java | 17 ++++--
 .../accumulo/core/data/ResourceGroupIdTest.java    | 24 +++++++--
 .../server/conf/cluster/ClusterConfigParser.java   | 50 ++++++++----------
 .../conf/cluster/ClusterConfigParserTest.java      | 60 +++++++---------------
 4 files changed, 72 insertions(+), 79 deletions(-)

diff --git 
a/core/src/main/java/org/apache/accumulo/core/data/ResourceGroupId.java 
b/core/src/main/java/org/apache/accumulo/core/data/ResourceGroupId.java
index 151a26094a..e2954514fa 100644
--- a/core/src/main/java/org/apache/accumulo/core/data/ResourceGroupId.java
+++ b/core/src/main/java/org/apache/accumulo/core/data/ResourceGroupId.java
@@ -28,7 +28,7 @@ import com.github.benmanes.caffeine.cache.Cache;
 
 public class ResourceGroupId extends AbstractId<ResourceGroupId> {
 
-  public static final Pattern GROUP_NAME_PATTERN = 
Pattern.compile("^[a-zA-Z]+(_?[a-zA-Z0-9])*$");
+  private static final Pattern GROUP_NAME_PATTERN = 
Pattern.compile("^[a-zA-Z]+(_?[a-zA-Z0-9])*$");
 
   // cache is for canonicalization/deduplication of created objects,
   // to limit the number of ResourceGroupId objects in the JVM at any given 
moment
@@ -43,16 +43,23 @@ public class ResourceGroupId extends 
AbstractId<ResourceGroupId> {
 
   private ResourceGroupId(String canonical) {
     super(canonical);
-    if (!GROUP_NAME_PATTERN.matcher(canonical).matches()) {
-      throw new IllegalArgumentException(
-          "Group name: " + canonical + " contains invalid characters");
+    validateGroupName(canonical);
+  }
+
+  /**
+   * @throws IllegalArgumentException if the group name is invalid
+   */
+  private static void validateGroupName(String groupName) {
+    if (!GROUP_NAME_PATTERN.matcher(groupName).matches()) {
+      throw new IllegalArgumentException("Group name '" + groupName
+          + "' is invalid. Valid names must match the pattern: " + 
GROUP_NAME_PATTERN.pattern());
     }
   }
 
   /**
    * Get a ResourceGroupId object for the provided canonical string.
    *
-   * @param canonical table ID string
+   * @param canonical resource group ID string
    * @return ResourceGroupId object
    */
   public static ResourceGroupId of(final String canonical) {
diff --git 
a/core/src/test/java/org/apache/accumulo/core/data/ResourceGroupIdTest.java 
b/core/src/test/java/org/apache/accumulo/core/data/ResourceGroupIdTest.java
index 9906ac2684..3128ac798e 100644
--- a/core/src/test/java/org/apache/accumulo/core/data/ResourceGroupIdTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/data/ResourceGroupIdTest.java
@@ -23,20 +23,38 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import org.junit.jupiter.api.Test;
 
 public class ResourceGroupIdTest {
+
+  @Test
+  public void testValidIds() {
+    ResourceGroupId.of("a");
+    ResourceGroupId.of("b");
+    ResourceGroupId.of("default");
+    ResourceGroupId.of("reg_ular");
+    ResourceGroupId.of("group1");
+    ResourceGroupId.of("validGroup");
+  }
+
   @Test
   public void testIllegalIds() {
     assertThrows(IllegalArgumentException.class, () -> ResourceGroupId.of(""));
     assertThrows(IllegalArgumentException.class, () -> ResourceGroupId.of(" 
"));
     assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("\t"));
+    assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("group1 "));
+    assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("group 1"));
+
     assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("_"));
     assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("9"));
     assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("9group1"));
+    assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("invalid_Group_"));
+
     assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("$"));
     assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("$group1"));
-    assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("group1 "));
-    assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("group 1"));
     assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("gro$up1"));
-    assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("invalid_Group_"));
+    assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("a-b"));
+    assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("a*b"));
+    assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("a?b"));
+    assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("group@name"));
+    assertThrows(IllegalArgumentException.class, () -> 
ResourceGroupId.of("group#name"));
   }
 
 }
diff --git 
a/server/base/src/main/java/org/apache/accumulo/server/conf/cluster/ClusterConfigParser.java
 
b/server/base/src/main/java/org/apache/accumulo/server/conf/cluster/ClusterConfigParser.java
index 4666668e9b..916524ed9f 100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/conf/cluster/ClusterConfigParser.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/conf/cluster/ClusterConfigParser.java
@@ -26,10 +26,10 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -52,14 +52,6 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     justification = "visible for testing")
 public class ClusterConfigParser {
 
-  static void validateGroupNames(List<String> names) {
-    for (String name : names) {
-      if (!ResourceGroupId.GROUP_NAME_PATTERN.matcher(name).matches()) {
-        throw new IllegalArgumentException("Group name: " + name + " contains 
invalid characters");
-      }
-    }
-  }
-
   // visible for testing
   public static SiteConfiguration siteConf = null;
 
@@ -129,10 +121,10 @@ public class ClusterConfigParser {
     }
   }
 
-  private static List<String> parseGroup(Map<String,String> config, String 
prefix) {
+  private static TreeSet<ResourceGroupId> parseGroup(Map<String,String> 
config, String prefix) {
     Preconditions.checkArgument(prefix.equals(COMPACTOR_PREFIX) || 
prefix.equals(SSERVER_PREFIX)
         || prefix.equals(TSERVER_PREFIX));
-    List<String> groups = config.keySet().stream().filter(k -> 
k.startsWith(prefix)).map(k -> {
+    return config.keySet().stream().filter(k -> k.startsWith(prefix)).map(k -> 
{
       int periods = StringUtils.countMatches(k, '.');
       if (periods == 1) {
         return k.substring(prefix.length());
@@ -148,18 +140,17 @@ public class ClusterConfigParser {
       } else {
         throw new IllegalArgumentException("Malformed configuration, has too 
many levels: " + k);
       }
-    }).sorted().distinct().collect(Collectors.toList());
-    validateGroupNames(groups);
-    return groups;
+    }).map(ResourceGroupId::of).collect(Collectors.toCollection(TreeSet::new));
   }
 
-  private static void validateConfiguredGroups(final ServerContext ctx, final 
Set<String> zkGroups,
-      final List<String> configuredGroups, boolean createMissingRG) {
-    for (String cg : configuredGroups) {
+  private static void validateConfiguredGroups(final ServerContext ctx,
+      final Set<ResourceGroupId> zkGroups, final Set<ResourceGroupId> 
configuredGroups,
+      boolean createMissingRG) {
+    for (ResourceGroupId cg : configuredGroups) {
       if (!zkGroups.contains(cg)) {
         if (createMissingRG) {
           try {
-            ctx.resourceGroupOperations().create(ResourceGroupId.of(cg));
+            ctx.resourceGroupOperations().create(cg);
           } catch (AccumuloException | AccumuloSecurityException e) {
             throw new IllegalStateException("Error creating resource group: " 
+ cg, e);
           }
@@ -173,7 +164,7 @@ public class ClusterConfigParser {
   }
 
   public static void outputShellVariables(ServerContext ctx, 
Map<String,String> config,
-      Set<String> zkGroups, boolean createMissingRG, PrintStream out) {
+      Set<ResourceGroupId> zkGroups, boolean createMissingRG, PrintStream out) 
{
 
     // find invalid config sections and point the user to the first one
     config.keySet().stream().filter(VALID_CONFIG_SECTIONS.negate()).findFirst()
@@ -192,7 +183,7 @@ public class ClusterConfigParser {
       }
     }
 
-    List<String> compactorGroups = parseGroup(config, COMPACTOR_PREFIX);
+    var compactorGroups = parseGroup(config, COMPACTOR_PREFIX);
     if (compactorGroups.isEmpty()) {
       throw new IllegalArgumentException(
           "No compactor groups found, at least one compactor group is required 
to compact the system tables.");
@@ -200,8 +191,8 @@ public class ClusterConfigParser {
     validateConfiguredGroups(ctx, zkGroups, compactorGroups, createMissingRG);
     if (!compactorGroups.isEmpty()) {
       out.printf(PROPERTY_FORMAT, "COMPACTOR_GROUPS",
-          compactorGroups.stream().collect(Collectors.joining(" ")));
-      for (String group : compactorGroups) {
+          String.join(" ", 
compactorGroups.stream().map(ResourceGroupId::canonical).toList()));
+      for (ResourceGroupId group : compactorGroups) {
         out.printf(PROPERTY_FORMAT, "COMPACTOR_HOSTS_" + group,
             config.get(COMPACTOR_PREFIX + group + HOSTS_SUFFIX));
         String numCompactors =
@@ -210,18 +201,18 @@ public class ClusterConfigParser {
       }
     }
 
-    List<String> sserverGroups = parseGroup(config, SSERVER_PREFIX);
+    var sserverGroups = parseGroup(config, SSERVER_PREFIX);
     validateConfiguredGroups(ctx, zkGroups, sserverGroups, createMissingRG);
     if (!sserverGroups.isEmpty()) {
       out.printf(PROPERTY_FORMAT, "SSERVER_GROUPS",
-          sserverGroups.stream().collect(Collectors.joining(" ")));
+          String.join(" ", 
sserverGroups.stream().map(ResourceGroupId::canonical).toList()));
       sserverGroups.forEach(ssg -> out.printf(PROPERTY_FORMAT, 
"SSERVER_HOSTS_" + ssg,
           config.get(SSERVER_PREFIX + ssg + HOSTS_SUFFIX)));
       sserverGroups.forEach(ssg -> out.printf(PROPERTY_FORMAT, 
"SSERVERS_PER_HOST_" + ssg,
           config.getOrDefault(SSERVER_PREFIX + ssg + SERVERS_PER_HOST_SUFFIX, 
"1")));
     }
 
-    List<String> tserverGroups = parseGroup(config, TSERVER_PREFIX);
+    var tserverGroups = parseGroup(config, TSERVER_PREFIX);
     if (tserverGroups.isEmpty()) {
       throw new IllegalArgumentException(
           "No tserver groups found, at least one tserver group is required to 
host the system tables.");
@@ -230,7 +221,7 @@ public class ClusterConfigParser {
     AtomicBoolean foundTServer = new AtomicBoolean(false);
     if (!tserverGroups.isEmpty()) {
       out.printf(PROPERTY_FORMAT, "TSERVER_GROUPS",
-          tserverGroups.stream().collect(Collectors.joining(" ")));
+          String.join(" ", 
tserverGroups.stream().map(ResourceGroupId::canonical).toList()));
       tserverGroups.forEach(tsg -> {
         String hosts = config.get(TSERVER_PREFIX + tsg + HOSTS_SUFFIX);
         foundTServer.compareAndSet(false, hosts != null && !hosts.isEmpty());
@@ -264,12 +255,11 @@ public class ClusterConfigParser {
       if (siteConf.getBoolean(Property.INSTANCE_RPC_SASL_ENABLED)) {
         SecurityUtil.serverLogin(siteConf);
       }
-      final Set<String> zkGroups = new HashSet<>();
-      context.resourceGroupOperations().list().forEach(rg -> 
zkGroups.add(rg.canonical()));
-      if (!zkGroups.contains(ResourceGroupId.DEFAULT.canonical())) {
+      final Set<ResourceGroupId> zkGroups = new 
TreeSet<>(context.resourceGroupOperations().list());
+      if (!zkGroups.contains(ResourceGroupId.DEFAULT)) {
         throw new IllegalStateException("Default resource group not found in 
ZooKeeper");
       }
-      boolean createMissingRG = args[0].equals("true") ? true : false;
+      boolean createMissingRG = args[0].equals("true");
       try {
         if (args.length == 3) {
           // Write to a file instead of System.out if provided as an argument
diff --git 
a/server/base/src/test/java/org/apache/accumulo/server/conf/cluster/ClusterConfigParserTest.java
 
b/server/base/src/test/java/org/apache/accumulo/server/conf/cluster/ClusterConfigParserTest.java
index 9d397e8173..e5f0e7a0c9 100644
--- 
a/server/base/src/test/java/org/apache/accumulo/server/conf/cluster/ClusterConfigParserTest.java
+++ 
b/server/base/src/test/java/org/apache/accumulo/server/conf/cluster/ClusterConfigParserTest.java
@@ -30,11 +30,12 @@ import java.io.PrintStream;
 import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.List;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.function.Function;
+import java.util.stream.Stream;
 
 import org.apache.accumulo.core.data.ResourceGroupId;
 import org.apache.accumulo.server.WithTestNames;
@@ -116,11 +117,11 @@ public class ClusterConfigParserTest extends 
WithTestNames {
             
ClusterConfigParser.parseConfiguration(Path.of(configFile.toURI()));
 
         final Path outputFile = Files.createFile(tempDir.resolve(testName()));
-
+        var groups = new HashSet<ResourceGroupId>();
+        groups.add(ResourceGroupId.DEFAULT);
+        Stream.of("q1", "q2", "cheap", 
"highmem").map(ResourceGroupId::of).forEach(groups::add);
         try (var out = Files.newOutputStream(outputFile); var ps = new 
PrintStream(out)) {
-          ClusterConfigParser.outputShellVariables(null, contents,
-              Set.of(ResourceGroupId.DEFAULT.canonical(), "q1", "q2", "cheap", 
"highmem"), false,
-              ps);
+          ClusterConfigParser.outputShellVariables(null, contents, groups, 
false, ps);
         }
 
         return outputFile;
@@ -183,9 +184,8 @@ public class ClusterConfigParserTest extends WithTestNames {
         ClusterConfigParser.parseConfiguration(Path.of(configFile.toURI()));
 
     try (var baos = new ByteArrayOutputStream(); var ps = new 
PrintStream(baos)) {
-      var exception = assertThrows(IllegalArgumentException.class,
-          () -> ClusterConfigParser.outputShellVariables(null, contents,
-              Set.of(ResourceGroupId.DEFAULT.canonical()), false, ps));
+      var exception = assertThrows(IllegalArgumentException.class, () -> 
ClusterConfigParser
+          .outputShellVariables(null, contents, 
Set.of(ResourceGroupId.DEFAULT), false, ps));
       assertTrue(exception.getMessage().contains("vserver"));
     }
   }
@@ -200,9 +200,8 @@ public class ClusterConfigParserTest extends WithTestNames {
         ClusterConfigParser.parseConfiguration(Path.of(configFile.toURI()));
 
     try (var baos = new ByteArrayOutputStream(); var ps = new 
PrintStream(baos)) {
-      var exception =
-          assertThrows(RuntimeException.class, () -> 
ClusterConfigParser.outputShellVariables(null,
-              contents, Set.of(ResourceGroupId.DEFAULT.canonical()), false, 
ps));
+      var exception = assertThrows(RuntimeException.class, () -> 
ClusterConfigParser
+          .outputShellVariables(null, contents, 
Set.of(ResourceGroupId.DEFAULT), false, ps));
       assertTrue(exception.getMessage().contains("bad-group-name"));
     }
   }
@@ -217,9 +216,8 @@ public class ClusterConfigParserTest extends WithTestNames {
         ClusterConfigParser.parseConfiguration(Path.of(configFile.toURI()));
 
     try (var baos = new ByteArrayOutputStream(); var ps = new 
PrintStream(baos)) {
-      var exception = assertThrows(IllegalArgumentException.class,
-          () -> ClusterConfigParser.outputShellVariables(null, contents,
-              Set.of(ResourceGroupId.DEFAULT.canonical()), false, ps));
+      var exception = assertThrows(IllegalArgumentException.class, () -> 
ClusterConfigParser
+          .outputShellVariables(null, contents, 
Set.of(ResourceGroupId.DEFAULT), false, ps));
       assertTrue(exception.getMessage().contains("servers_per_hosts"));
     }
   }
@@ -234,9 +232,8 @@ public class ClusterConfigParserTest extends WithTestNames {
         ClusterConfigParser.parseConfiguration(Path.of(configFile.toURI()));
 
     try (var baos = new ByteArrayOutputStream(); var ps = new 
PrintStream(baos)) {
-      var exception = assertThrows(IllegalArgumentException.class,
-          () -> ClusterConfigParser.outputShellVariables(null, contents,
-              Set.of(ResourceGroupId.DEFAULT.canonical()), false, ps));
+      var exception = assertThrows(IllegalArgumentException.class, () -> 
ClusterConfigParser
+          .outputShellVariables(null, contents, 
Set.of(ResourceGroupId.DEFAULT), false, ps));
       assertTrue(exception.getMessage().contains("too many levels"));
     }
   }
@@ -251,9 +248,8 @@ public class ClusterConfigParserTest extends WithTestNames {
         ClusterConfigParser.parseConfiguration(Path.of(configFile.toURI()));
 
     try (var baos = new ByteArrayOutputStream(); var ps = new 
PrintStream(baos)) {
-      var exception = assertThrows(IllegalArgumentException.class,
-          () -> ClusterConfigParser.outputShellVariables(null, contents,
-              Set.of(ResourceGroupId.DEFAULT.canonical()), false, ps));
+      var exception = assertThrows(IllegalArgumentException.class, () -> 
ClusterConfigParser
+          .outputShellVariables(null, contents, 
Set.of(ResourceGroupId.DEFAULT), false, ps));
       assertTrue(exception.getMessage().contains("No compactor groups found"));
     }
   }
@@ -270,7 +266,7 @@ public class ClusterConfigParserTest extends WithTestNames {
     try (var baos = new ByteArrayOutputStream(); var ps = new 
PrintStream(baos)) {
       var exception = assertThrows(IllegalArgumentException.class,
           () -> ClusterConfigParser.outputShellVariables(null, contents,
-              Set.of(ResourceGroupId.DEFAULT.canonical(), "q1"), false, ps));
+              Set.of(ResourceGroupId.DEFAULT, ResourceGroupId.of("q1")), 
false, ps));
       assertTrue(exception.getMessage().contains("No tserver groups found"));
     }
   }
@@ -285,28 +281,10 @@ public class ClusterConfigParserTest extends 
WithTestNames {
         ClusterConfigParser.parseConfiguration(Path.of(configFile.toURI()));
 
     try (var baos = new ByteArrayOutputStream(); var ps = new 
PrintStream(baos)) {
-      var exception = assertThrows(IllegalArgumentException.class,
-          () -> ClusterConfigParser.outputShellVariables(null, contents,
-              Set.of(ResourceGroupId.DEFAULT.canonical()), false, ps));
+      var exception = assertThrows(IllegalArgumentException.class, () -> 
ClusterConfigParser
+          .outputShellVariables(null, contents, 
Set.of(ResourceGroupId.DEFAULT), false, ps));
       assertTrue(exception.getMessage().contains("Check the format"));
     }
   }
 
-  @Test
-  public void testGroupNamePattern() {
-    ClusterConfigParser.validateGroupNames(List.of("a"));
-    ClusterConfigParser.validateGroupNames(List.of("a", "b"));
-    ClusterConfigParser.validateGroupNames(List.of("default", "reg_ular"));
-    assertThrows(RuntimeException.class,
-        () -> ClusterConfigParser.validateGroupNames(List.of("a1b2c3d4__")));
-    assertThrows(RuntimeException.class,
-        () -> ClusterConfigParser.validateGroupNames(List.of("0abcde")));
-    assertThrows(RuntimeException.class,
-        () -> ClusterConfigParser.validateGroupNames(List.of("a-b")));
-    assertThrows(RuntimeException.class,
-        () -> ClusterConfigParser.validateGroupNames(List.of("a*b")));
-    assertThrows(RuntimeException.class,
-        () -> ClusterConfigParser.validateGroupNames(List.of("a?b")));
-  }
-
 }

Reply via email to