keith-turner commented on code in PR #6040:
URL: https://github.com/apache/accumulo/pull/6040#discussion_r2714352564
##########
core/src/main/java/org/apache/accumulo/core/clientImpl/TableOperationsHelper.java:
##########
@@ -139,45 +140,7 @@ public static void
checkIteratorConflicts(Map<String,String> props, IteratorSett
EnumSet<IteratorScope> scopes) throws AccumuloException {
checkArgument(setting != null, "setting is null");
checkArgument(scopes != null, "scopes is null");
- for (IteratorScope scope : scopes) {
- String scopeStr =
- String.format("%s%s", Property.TABLE_ITERATOR_PREFIX,
scope.name().toLowerCase());
- String nameStr = String.format("%s.%s", scopeStr, setting.getName());
- String optStr = String.format("%s.opt.", nameStr);
- Map<String,String> optionConflicts = new TreeMap<>();
- for (Entry<String,String> property : props.entrySet()) {
- if (property.getKey().startsWith(scopeStr)) {
Review Comment:
Do you know if this code was duplicated in NamespaceOperationsHelper?
##########
core/src/main/java/org/apache/accumulo/core/iteratorsImpl/IteratorConfigUtil.java:
##########
@@ -57,6 +66,19 @@ public class IteratorConfigUtil {
public static final Comparator<IterInfo> ITER_INFO_COMPARATOR =
Comparator.comparingInt(IterInfo::getPriority);
+ private static final String ITERATOR_PROP_REGEX =
+ ("^" + Property.TABLE_ITERATOR_PREFIX.getKey() + "(" +
Arrays.stream(IteratorScope.values())
Review Comment:
The way these regexes are used for validation they ignore properties with
the iterator prefix that do not have a valid scope. Like maybe
`table.iterator.sacn.fooFilter` would be completely ignored and would not
flagged as a problem. Seems like the old validation code had this same
problem, so this is not a new behavior AFAICT. This should probably be a
follow on issue to look for invalid scopes following the iterator prefix.
##########
core/src/main/java/org/apache/accumulo/core/iteratorsImpl/IteratorConfigUtil.java:
##########
@@ -273,4 +295,216 @@ private static Class<SortedKeyValueIterator<Key,Value>>
loadClass(boolean useAcc
log.trace("Iterator class {} loaded from classpath", iterInfo.className);
return clazz;
}
+
+ public static void checkIteratorConflicts(Map<String,String> props, String
property, String value)
+ throws AccumuloException {
+ if (props.containsKey(property) && props.get(property).equals(value)) {
Review Comment:
Maybe could do the following to avoid two map lookups. Its not exactly the
same though as what is there, it has slightly different behavior around nulls,
so not sure if you want that.
```suggestion
if (Objects.equals(props.get(property), value)) {
```
##########
core/src/main/java/org/apache/accumulo/core/iteratorsImpl/IteratorConfigUtil.java:
##########
@@ -273,4 +295,216 @@ private static Class<SortedKeyValueIterator<Key,Value>>
loadClass(boolean useAcc
log.trace("Iterator class {} loaded from classpath", iterInfo.className);
return clazz;
}
+
+ public static void checkIteratorConflicts(Map<String,String> props, String
property, String value)
+ throws AccumuloException {
+ if (props.containsKey(property) && props.get(property).equals(value)) {
+ // setting a property that already exists (i.e., no change)
+ return;
+ }
+ if (isNonOptionIterProp(property, value)) {
+ String[] iterPropParts = property.split("\\.");
+ IteratorScope scope = IteratorScope.valueOf(iterPropParts[2]);
+ String iterName = iterPropParts[3];
+ String[] priorityAndClass;
+ if ((priorityAndClass = value.split(",")).length == 2) {
+ // given a single property, the only way for the property to be
equivalent to an existing
+ // iterator is if the existing iterator has no options (opts are set
as separate props)
+ IteratorSetting givenIter = new
IteratorSetting(Integer.parseInt(priorityAndClass[0]),
+ iterName, priorityAndClass[1]);
+ checkIteratorConflicts(props, givenIter, EnumSet.of(scope), false);
+ }
+ }
+ }
+
+ public static void checkIteratorConflicts(TableOperations tableOps,
NamespaceOperationsHelper noh,
+ String namespace, String property, String value)
+ throws AccumuloException, AccumuloSecurityException,
NamespaceNotFoundException {
+ var props = noh.getNamespaceProperties(namespace);
+ if (props.containsKey(property) && props.get(property).equals(value)) {
+ // setting a property that already exists (i.e., no change)
+ return;
+ }
+
+ // checking for conflicts in the namespace
+ if (isNonOptionIterProp(property, value)) {
+ String[] iterPropParts = property.split("\\.");
+ IteratorScope scope = IteratorScope.valueOf(iterPropParts[2]);
+ String iterName = iterPropParts[3];
+ String[] priorityAndClass;
+ if ((priorityAndClass = value.split(",")).length == 2) {
+ // given a single property, the only way for the property to be
equivalent to an existing
+ // iterator is if the existing iterator has no options (opts are set
as separate props)
+ IteratorSetting givenIter = new
IteratorSetting(Integer.parseInt(priorityAndClass[0]),
+ iterName, priorityAndClass[1]);
+ checkIteratorConflicts(props, givenIter, EnumSet.of(scope), false);
+ }
+ }
+
+ // checking for conflicts for the tables in the namespace
+ checkIteratorConflictsWithTablesInNamespace(tableOps, namespace, property,
value);
+ }
+
+ public static void
checkIteratorConflictsWithTablesInNamespace(TableOperations tableOps,
+ String namespace, IteratorSetting is, EnumSet<IteratorScope> scopes)
+ throws AccumuloException {
+ Set<String> tablesInNamespace;
+ if (namespace.equals(Namespace.DEFAULT.name())) {
+ tablesInNamespace = tableOps.list().stream().filter(t ->
!t.contains(Namespace.SEPARATOR))
+ .collect(Collectors.toSet());
+ } else {
+ tablesInNamespace = tableOps.list().stream()
+ .filter(t -> t.startsWith(namespace +
Namespace.SEPARATOR)).collect(Collectors.toSet());
+ }
+ try {
+ for (var table : tablesInNamespace) {
+ checkIteratorConflicts(tableOps.getTableProperties(table), is, scopes,
false);
+ }
+ } catch (TableNotFoundException e) {
+ throw new AccumuloException(e);
+ }
+ }
+
+ public static void
checkIteratorConflictsWithTablesInNamespace(TableOperations tableOps,
+ String namespace, String property, String value) throws
AccumuloException {
+ Set<String> tablesInNamespace;
+ if (namespace.equals(Namespace.DEFAULT.name())) {
+ tablesInNamespace = tableOps.list().stream().filter(t ->
!t.contains(Namespace.SEPARATOR))
+ .collect(Collectors.toSet());
+ } else {
+ tablesInNamespace = tableOps.list().stream()
+ .filter(t -> t.startsWith(namespace +
Namespace.SEPARATOR)).collect(Collectors.toSet());
+ }
+ try {
+ for (var table : tablesInNamespace) {
+ checkIteratorConflicts(tableOps.getTableProperties(table), property,
value);
+ }
+ } catch (TableNotFoundException e) {
+ throw new AccumuloException(e);
+ }
+ }
+
+ public static void checkIteratorConflicts(IteratorSetting iterToCheck,
+ EnumSet<IteratorScope> iterScopesToCheck,
+ Map<IteratorScope,List<IteratorSetting>> existingIters, boolean
shouldThrow)
+ throws AccumuloException {
+ // The reason for the 'shouldThrow' var is to prevent newly added 2.x
checks from breaking
+ // existing user code. Just log the problem and proceed. Major version > 2
will always throw
+ for (var scope : iterScopesToCheck) {
+ var existingItersForScope = existingIters.get(scope);
+ if (existingItersForScope == null) {
+ continue;
+ }
+ for (var existingIter : existingItersForScope) {
+ // not a conflict if exactly the same
+ if (iterToCheck.equals(existingIter)) {
+ continue;
+ }
+ if (iterToCheck.getName().equals(existingIter.getName())) {
+ String msg =
+ String.format("iterator name conflict at %s scope. %s conflicts
with existing %s",
+ scope, iterToCheck, existingIter);
+ if (shouldThrow) {
+ throw new AccumuloException(new IllegalArgumentException(msg));
+ } else {
+ log.warn(msg + WARNING_MSG);
+ }
+ }
+ if (iterToCheck.getPriority() == existingIter.getPriority()) {
+ String msg =
+ String.format("iterator priority conflict at %s scope. %s
conflicts with existing %s",
+ scope, iterToCheck, existingIter);
+ if (shouldThrow) {
+ throw new AccumuloException(new IllegalArgumentException(msg));
+ } else {
+ log.warn(msg + WARNING_MSG);
+ }
+ }
+ }
+ }
+ }
+
+ public static void checkIteratorConflicts(Map<String,String> props,
IteratorSetting iterToCheck,
Review Comment:
Reviewing this code and the exsiting code in this area, noticed there are
multiple places that parse and serialize iter config. This code is adding to
that existing pattern. Was wondering if we could avoid this for the new code
and experimented to see what was possible. Came up w/ the following. Its very
clunky, but avoids duplicating code to parse iterator config. Feel free to
ignore this, was just trying to understand what we could do. Can do things in
follow on issues, would like to narrow the set of changes for this rather than
widen them.
```java
public static List<IteratorSetting>
propertiesToIteratorSettings(IteratorScope scope,AccumuloConfiguration config,
Consumer<Map<String, Map<String, String>>> extraOptsConsumer){
Map<String, Map<String, String>> allOptions = new HashMap<>();
List<IterInfo> info = parseIterConf(scope, List.of(), allOptions,
config);
List<IteratorSetting> iterSettings = new ArrayList<>(info.size());
info.forEach(iterInfo -> {
var options = allOptions.remove(iterInfo.getIterName());
var iterSetting =new IteratorSetting(iterInfo.getPriority(),
iterInfo.getIterName(), iterInfo.getClassName());
if(options != null){
iterSetting.addOptions(options);
}
iterSettings.add(iterSetting);
});
if(!allOptions.isEmpty()) {
// these are itertor options w/ no iterator definition
extraOptsConsumer.accept(allOptions);
}
return iterSettings;
}
public static void checkIteratorConflicts(Map<String,String> props,
IteratorSetting iterToCheck,
EnumSet<IteratorScope> iterScopesToCheck) throws AccumuloException {
// parse the props map
Map<IteratorScope,List<IteratorSetting>> existingIters =
new HashMap<>(IteratorScope.values().length);
for(var scope : iterScopesToCheck) {
var iterSettings = propertiesToIteratorSettings(scope, new
ConfigurationCopy(props), extraOpts->{
// TODO this used throw an exception w/ the first extra option it
saw,not its all extra so could be a much larger message which may cause
problems for logging
String msg = String.format("iterator options conflict for %s : %s",
iterToCheck.getName(), extraOpts);
throw new AccumuloException(new IllegalArgumentException(msg));
});
existingIters.put(scope,iterSettings);
}
// check if the given iterator conflicts with any existing iterators
checkIteratorConflicts(iterToCheck, iterScopesToCheck, existingIters);
}
```
This would be a follow on issue, but it would be nice to consolidate
parsing/serializing in the existing code. Maybe its better address this
comprehensively in a PR focused on this single problem rather than partially
here for only new code, not sure.
##########
server/tserver/src/main/java/org/apache/accumulo/tserver/tablet/ScanDataSource.java:
##########
@@ -243,6 +246,32 @@ private SortedKeyValueIterator<Key,Value> createIterator()
} else {
// Scan time iterator options were set, so need to merge those with
pre-parsed table
// iterator options.
+
+ // First ensure the set iterators do not conflict with the existing
table iterators.
+ List<IteratorSetting> picIteratorSettings = null;
+ for (var scanParamIterInfo : scanParams.getSsiList()) {
+ // Quick check for a potential iterator conflict (does not consider
iterator scope).
+ // This avoids the more expensive check method call most of the time.
+ if (pic.getUniqueNames().contains(scanParamIterInfo.getIterName())
+ ||
pic.getUniquePriorities().contains(scanParamIterInfo.getPriority())) {
+ if (picIteratorSettings == null) {
Review Comment:
Nice how this avoid creating and populating this list unless its needed.
##########
core/src/main/java/org/apache/accumulo/core/clientImpl/NamespaceOperationsHelper.java:
##########
@@ -146,45 +147,8 @@ public void checkIteratorConflicts(String namespace,
IteratorSetting setting,
if (!exists(namespace)) {
throw new NamespaceNotFoundException(null, namespace, null);
}
- for (IteratorScope scope : scopes) {
- String scopeStr =
- String.format("%s%s", Property.TABLE_ITERATOR_PREFIX,
scope.name().toLowerCase());
- String nameStr = String.format("%s.%s", scopeStr, setting.getName());
- String optStr = String.format("%s.opt.", nameStr);
- Map<String,String> optionConflicts = new TreeMap<>();
- for (Entry<String,String> property : this.getProperties(namespace)) {
- if (property.getKey().startsWith(scopeStr)) {
- if (property.getKey().equals(nameStr)) {
- throw new AccumuloException(new IllegalArgumentException("iterator
name conflict for "
- + setting.getName() + ": " + property.getKey() + "=" +
property.getValue()));
- }
- if (property.getKey().startsWith(optStr)) {
- optionConflicts.put(property.getKey(), property.getValue());
- }
- if (property.getKey().contains(".opt.")) {
- continue;
- }
- String[] parts = property.getValue().split(",");
- if (parts.length != 2) {
- throw new AccumuloException("Bad value for existing iterator
setting: "
- + property.getKey() + "=" + property.getValue());
- }
- try {
- if (Integer.parseInt(parts[0]) == setting.getPriority()) {
- throw new AccumuloException(new IllegalArgumentException(
- "iterator priority conflict: " + property.getKey() + "=" +
property.getValue()));
- }
- } catch (NumberFormatException e) {
- throw new AccumuloException("Bad value for existing iterator
setting: "
- + property.getKey() + "=" + property.getValue());
- }
Review Comment:
> it is quietly ignored as not being an iterator-related property.
We should not quietly ignore invalid properties under the iterator prefix,
this could be a sign of a typo. If its a typo then the user could expect
something to be working and it silently does not. Would be good to fail or
warn for that. Made a comment elsewhere about the scope, seems this new code
and the old code ignores invalid scopes. Not completely sure about this this
though.
##########
server/manager/src/main/java/org/apache/accumulo/manager/FateServiceHandler.java:
##########
@@ -322,22 +323,27 @@ public void executeFateOperation(TInfo tinfo,
TCredentials c, long opid, FateOpe
Map<String,String> propertiesToSet = new HashMap<>();
Set<String> propertiesToExclude = new HashSet<>();
+ // dest table will have the dest namespace props + src table props:
need to check provided
+ // options to set for conflicts with this
+ var srcTableConfigIterProps =
Review Comment:
Saw two problems w/ this code. First when it subtract namespace config
that could remove props that are in both src table and src namepsace. Second
it does not remove props to exclude. For the first problem we can get only the
src table props, not the merged view. Tried to fix this issues below.
```java
// dest table will have the dest namespace props + src table props:
need to check provided
// options to set for conflicts with this
var iterProps = new
HashMap<>(manager.getContext().getNamespaceConfiguration(namespaceId)
.getAllPropertiesWithPrefix(Property.TABLE_ITERATOR_PREFIX));
// get only the source table props, not the merged view
var srcTableProps =
manager.getContext().getPropStore().get(TablePropKey.of(manager.getContext(),
srcTableId)).asMap();
for (Entry<String,String> entry : options.entrySet()) {
if
(entry.getKey().startsWith(TableOperationsImpl.PROPERTY_EXCLUDE_PREFIX)) {
propertiesToExclude.add(
entry.getKey().substring(TableOperationsImpl.PROPERTY_EXCLUDE_PREFIX.length()));
}
}
// these props will not be cloned
srcTableProps.keySet().removeAll(propertiesToExclude);
//merge src table props into dest namespace props
iterProps.putAll(srcTableProps);
for (Entry<String,String> entry : options.entrySet()) {
if
(entry.getKey().startsWith(TableOperationsImpl.PROPERTY_EXCLUDE_PREFIX)) {
continue;
}
validateTableProperty(entry.getKey(), entry.getValue(), options,
IteratorConfigUtil.gatherIteratorProps(iterProps), tableName,
tableOp);
propertiesToSet.put(entry.getKey(), entry.getValue());
}
```
##########
server/tserver/src/main/java/org/apache/accumulo/tserver/tablet/ScanDataSource.java:
##########
@@ -243,6 +246,32 @@ private SortedKeyValueIterator<Key,Value> createIterator()
} else {
// Scan time iterator options were set, so need to merge those with
pre-parsed table
// iterator options.
+
+ // First ensure the set iterators do not conflict with the existing
table iterators.
+ List<IteratorSetting> picIteratorSettings = null;
+ for (var scanParamIterInfo : scanParams.getSsiList()) {
+ // Quick check for a potential iterator conflict (does not consider
iterator scope).
+ // This avoids the more expensive check method call most of the time.
+ if (pic.getUniqueNames().contains(scanParamIterInfo.getIterName())
+ ||
pic.getUniquePriorities().contains(scanParamIterInfo.getPriority())) {
+ if (picIteratorSettings == null) {
+ picIteratorSettings = new ArrayList<>(pic.getIterInfo().size());
+ for (var picIterInfo : pic.getIterInfo()) {
+ picIteratorSettings.add(
+ getIteratorSetting(picIterInfo,
pic.getOpts().get(picIterInfo.getIterName())));
+ }
+ }
+ try {
+ IteratorConfigUtil.checkIteratorConflicts(
+ getIteratorSetting(scanParamIterInfo,
+
scanParams.getSsio().get(scanParamIterInfo.getIterName())),
+ EnumSet.of(IteratorScope.scan), Map.of(IteratorScope.scan,
picIteratorSettings),
+ false);
+ } catch (AccumuloException e) {
+ throw new IllegalArgumentException(e);
Review Comment:
Will this fail the scan? If so should this warn? If we do warn does that
mean this code is untestable in 2.1?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]