Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions core/src/main/java/org/apache/accumulo/core/conf/Property.java
Original file line number Diff line number Diff line change
Expand Up @@ -1564,6 +1564,23 @@ public Property replacedBy() {
return replacedBy;
}

/**
* Gets the defined required properties
*
* @return Set{@literal <Property>}
*/
public static Set<Property> getRequiredProperties() {
return Set.of(Property.INSTANCE_ZK_HOST, Property.INSTANCE_ZK_TIMEOUT, Property.INSTANCE_SECRET,
Property.INSTANCE_VOLUMES, Property.GENERAL_THREADPOOL_SIZE,
Property.GENERAL_DELEGATION_TOKEN_LIFETIME,
Property.GENERAL_DELEGATION_TOKEN_UPDATE_INTERVAL, Property.GENERAL_IDLE_PROCESS_INTERVAL,
Property.GENERAL_LOW_MEM_DETECTOR_INTERVAL, Property.GENERAL_LOW_MEM_DETECTOR_THRESHOLD,
Property.GENERAL_SERVER_LOCK_VERIFICATION_INTERVAL, Property.MANAGER_CLIENTPORT,
Property.TSERV_CLIENTPORT, Property.GC_CYCLE_START, Property.GC_CYCLE_DELAY,
Property.GC_PORT, Property.MONITOR_PORT, Property.TABLE_MAJC_RATIO,
Property.TABLE_SPLIT_THRESHOLD);
}

private void precomputeAnnotations() {
isSensitive =
hasAnnotation(Sensitive.class) || hasPrefixWithAnnotation(getKey(), Sensitive.class);
Expand Down
71 changes: 62 additions & 9 deletions core/src/main/java/org/apache/accumulo/core/conf/PropertyType.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import static java.util.Objects.requireNonNull;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
Expand Down Expand Up @@ -114,7 +116,7 @@ public enum PropertyType {
+ " '5%', '0.2%', '0.0005'.\n"
+ "Examples of invalid fractions/percentages are '', '10 percent', 'Hulk Hogan'"),

PATH("path", x -> true,
PATH("path", new ValidPath(),
"A string that represents a filesystem path, which can be either relative"
+ " or absolute to some directory. The filesystem depends on the property. "
+ "Substitutions of the ACCUMULO_HOME environment variable can be done in the system "
Expand Down Expand Up @@ -155,7 +157,7 @@ public enum PropertyType {
BOOLEAN("boolean", in(false, null, "true", "false"),
"Has a value of either 'true' or 'false' (case-insensitive)"),

URI("uri", x -> true, "A valid URI"),
URI("uri", new ValidUri(), "A valid URI"),

FILENAME_EXT("file name extension", in(true, RFile.EXTENSION),
"One of the currently supported filename extensions for storing table data files. "
Expand Down Expand Up @@ -211,6 +213,46 @@ public boolean isValidFormat(String value) {
return predicate.test(value);
}

/**
* Validate that the provided string is a valid path.
*/
private static class ValidPath implements Predicate<String> {
private static final Logger log = LoggerFactory.getLogger(ValidPath.class);

@Override
public boolean test(String path) {
if (path == null || path.trim().isEmpty()) {
return true;
} else if (new Path(path.trim()).isAbsolute()) {
return true;
Comment thread
Amemeda marked this conversation as resolved.
Outdated
} else if (path.matches("/?[A-Za-z+/?]+")) {
return true;
Comment thread
Amemeda marked this conversation as resolved.
Outdated
}
log.error("provided path is not valid");
return false;
}
}

// SECOND VERSION OF ValidPath, leaving while waiting for clarification on the expected validation
/**
* Validate that the provided string is a valid hadoop path. Path must exist and be a valid
* file/directory
*/
/*
* private static class ValidPath implements Predicate<String> { private static final Logger log =
* LoggerFactory.getLogger(ValidPath.class);
*
* @Override public boolean test(String path) { Configuration conf = new Configuration(); Path
* hadoopPath = new Path(path);
*
* try { FileSystem fs = hadoopPath.getFileSystem(conf); // Check if path exists if
* (fs.exists(hadoopPath)) { // Check if path is a valid directory if
* (fs.getFileStatus(hadoopPath).isFile() || fs.getFileStatus(hadoopPath).isDirectory()) { return
* true; } log.error("provided path is not a file or directory"); return false; }
* log.error("provided path does not exist"); return false; } catch (IOException e) {
* log.error("provided path is not valid"); return false; } } }
*/

Comment thread
Amemeda marked this conversation as resolved.
Outdated
/**
* Validate that the provided string can be parsed into a json object. This implementation uses
* jackson databind because it is less permissive that GSON for what is considered valid. This
Expand Down Expand Up @@ -247,6 +289,24 @@ public boolean test(String value) {
}
}

private static class ValidUri implements Predicate<String> {
private static final Logger log = LoggerFactory.getLogger(ValidUri.class);

@Override
public boolean test(String uri) {
if (uri == null) {
return true;
}
try {
new URI(uri);
return true;
} catch (URISyntaxException e) {
log.error("provided uri string is not valid");
Comment thread
Amemeda marked this conversation as resolved.
return false;
}
}
}

private static class ValidVolumes implements Predicate<String> {
private static final Logger log = LoggerFactory.getLogger(ValidVolumes.class);

Expand Down Expand Up @@ -306,7 +366,6 @@ public boolean test(String type) {
}
}
}

}

private static final Pattern SUFFIX_REGEX = Pattern.compile("\\D*$"); // match non-digits at end
Expand Down Expand Up @@ -413,14 +472,8 @@ public Matches(final Pattern pattern) {

@Override
public boolean test(final String input) {
// TODO when the input is null, it just means that the property wasn't set
// we can add checks for not null for required properties with
// Predicates.and(Predicates.notNull(), ...),
// or we can stop assuming that null is always okay for a Matches predicate, and do that
// explicitly with Predicates.or(Predicates.isNull(), ...)
Comment on lines -416 to -420

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment that was removed suggests that we should not check input == null here, but should use Predicates.or(Predicates.isNull(), ...) for any patterns where we want to allow null.

If we leave the input == null here, then we need to do something like Predicates.and(Predicates.isNull().negate(), ...) for required properties.

I'm not sure if we've done either, or which would be easier to do if we haven't.

@Amemeda Amemeda Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this TODO with Dom, and he said the required properties check in ServerConfigCheckRunner is already doing this/ or something close

image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but the null is still being allowed here in the property type validation. That's kind of my point. We are allowing all nulls to pass through here, and then check them later. The comment that was removed was suggesting that we could do this better by disallowing nulls here.

Consider the following, which roughly represents what we have today:

// implied validation from the type
MY_PROP_ENUM("key", PropertyType.MyType, "description");

The problem here is that PropertyType.MyType.isValidFormat() must return true if it's null, even for required properties, because the type validation doesn't know if the property is required or not.

Consider this alternative instead:

// explicit validation from the type, with an optional nullable; type no longer has to allow nulls
// alternatively, the type always allows nulls, but we explicitly say that it's not null in the explicit validator
MY_PROP_ENUM("key", PropertyType.MyType, PropertyType.MyType::isValidFormat, "description");
MY_PROP_ENUM2("key2", PropertyType.MyType2, Predicate.isNull().or(PropertyType.MyType::isValidFormat), "description");

Alternatively:

// stored the required bit with the property
MY_PROP_ENUM("key", PropertyType.MyType, /* required = */ true, "description");
// modify the PropertyType.isValidFormat()
public boolean isValidFormat(String string, boolean required) {
  // ensure non-null in here before passing to the type-specific predicate to test the non-null format
}

I think the implication here is that the required set needs to be removed, and replaced with either explicit per-property validation, or an extra per-property "required" boolean parameter to track which properties allow null/empty string.

return input == null || pattern.matcher(input).matches();
}

}

public static class PortRange extends Matches {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,7 @@ public boolean runCheck(ServerContext context, ServerOpts opts, boolean fixFiles
}

log.trace("Checking that all required config properties are present");
// there are many properties that should be set (default value or user set), identifying them
// all and checking them here is unrealistic. Some property that is not set but is expected
// will likely result in some sort of failure eventually anyway. We will just check a few
// obvious required properties here.
Set<Property> requiredProps = Set.of(Property.INSTANCE_ZK_HOST, Property.INSTANCE_ZK_TIMEOUT,
Property.INSTANCE_SECRET, Property.INSTANCE_VOLUMES, Property.GENERAL_THREADPOOL_SIZE,
Property.GENERAL_DELEGATION_TOKEN_LIFETIME,
Property.GENERAL_DELEGATION_TOKEN_UPDATE_INTERVAL, Property.GENERAL_IDLE_PROCESS_INTERVAL,
Property.GENERAL_LOW_MEM_DETECTOR_INTERVAL, Property.GENERAL_LOW_MEM_DETECTOR_THRESHOLD,
Property.GENERAL_SERVER_LOCK_VERIFICATION_INTERVAL, Property.MANAGER_CLIENTPORT,
Property.TSERV_CLIENTPORT, Property.GC_CYCLE_START, Property.GC_CYCLE_DELAY,
Property.GC_PORT, Property.MONITOR_PORT, Property.TABLE_MAJC_RATIO,
Property.TABLE_SPLIT_THRESHOLD);
Set<Property> requiredProps = Property.getRequiredProperties();
for (var reqProp : requiredProps) {
var confPropVal = config.get(reqProp);
// already checked that all set properties are valid, just check that it is set then we know
Expand Down
Loading