This is an automated email from the ASF dual-hosted git repository.
dlmarion pushed a commit to branch 2.1
in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/2.1 by this push:
new 293e304945 Validate that classloader context is valid when property is
set (#3678)
293e304945 is described below
commit 293e30494548d6eca247df9b68cef3c613cd8066
Author: Dave Marion <[email protected]>
AuthorDate: Thu Aug 10 10:13:54 2023 -0400
Validate that classloader context is valid when property is set (#3678)
Added method to ContextClassLoaderFactory that is called when
the table.context.class.loader property is set to validate that the
context name is valid. Default implementation returns true if the
implementation returns a non-null ClassLoader and does not throw
an exception. The new method is also called from
ClassLoaderUtil.getClassLoader(String) and will return the non-context
aware Accumulo ClassLoader if isValid returns false.
Co-authored-by: Ed Coleman <[email protected]>
---
.../accumulo/core/classloader/ClassLoaderUtil.java | 18 +++++++--
.../DefaultContextClassLoaderFactory.java | 19 ++++++++++
.../core/spi/common/ContextClassLoaderFactory.java | 26 +++++++++++++
.../core/classloader/URLClassLoaderFactory.java | 44 +++++++++++++++++-----
.../org/apache/accumulo/server/util/PropUtil.java | 15 +++++---
.../accumulo/server/util/SystemPropUtil.java | 19 ++++++----
.../classloader/vfs/AccumuloVFSClassLoader.java | 2 +-
.../start/classloader/vfs/ContextManager.java | 10 ++++-
.../apache/accumulo/test/shell/ShellServerIT.java | 25 ++++++++++++
9 files changed, 152 insertions(+), 26 deletions(-)
diff --git
a/core/src/main/java/org/apache/accumulo/core/classloader/ClassLoaderUtil.java
b/core/src/main/java/org/apache/accumulo/core/classloader/ClassLoaderUtil.java
index 3a5d0ebda3..8fc7a85cc3 100644
---
a/core/src/main/java/org/apache/accumulo/core/classloader/ClassLoaderUtil.java
+++
b/core/src/main/java/org/apache/accumulo/core/classloader/ClassLoaderUtil.java
@@ -75,13 +75,25 @@ public class ClassLoaderUtil {
@SuppressWarnings("deprecation")
public static ClassLoader getClassLoader(String context) {
- if (context != null && !context.isEmpty()) {
- return FACTORY.getClassLoader(context);
- } else {
+ try {
+ if (FACTORY.isValid(context)) {
+ return FACTORY.getClassLoader(context);
+ } else {
+ return
org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader.getClassLoader();
+ }
+ } catch (RuntimeException e) {
return
org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader.getClassLoader();
}
}
+ public static boolean isValidContext(String context) {
+ try {
+ return FACTORY.isValid(context);
+ } catch (RuntimeException e) {
+ return false;
+ }
+ }
+
public static <U> Class<? extends U> loadClass(String context, String
className,
Class<U> extension) throws ClassNotFoundException {
return getClassLoader(context).loadClass(className).asSubclass(extension);
diff --git
a/core/src/main/java/org/apache/accumulo/core/classloader/DefaultContextClassLoaderFactory.java
b/core/src/main/java/org/apache/accumulo/core/classloader/DefaultContextClassLoaderFactory.java
index 984e86d0b8..695aa3e473 100644
---
a/core/src/main/java/org/apache/accumulo/core/classloader/DefaultContextClassLoaderFactory.java
+++
b/core/src/main/java/org/apache/accumulo/core/classloader/DefaultContextClassLoaderFactory.java
@@ -20,6 +20,7 @@ package org.apache.accumulo.core.classloader;
import static java.util.concurrent.TimeUnit.MINUTES;
+import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
@@ -95,8 +96,26 @@ public class DefaultContextClassLoaderFactory implements
ContextClassLoaderFacto
@SuppressWarnings("deprecation")
@Override
public ClassLoader getClassLoader(String contextName) {
+ if (contextName == null || contextName.isEmpty()) {
+ throw new IllegalArgumentException("contextName must have a value");
+ }
return org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader
.getContextClassLoader(contextName);
}
+ @SuppressWarnings("deprecation")
+ @Override
+ public boolean isValid(String contextName) {
+ if (contextName == null || contextName.isEmpty()) {
+ return false;
+ }
+ try {
+ return
org.apache.accumulo.start.classloader.vfs.AccumuloVFSClassLoader.getContextManager()
+ .isKnownContext(contextName);
+ } catch (IOException e) {
+ LOG.error("Error checking that context is valid, context: " +
contextName, e);
+ return false;
+ }
+ }
+
}
diff --git
a/core/src/main/java/org/apache/accumulo/core/spi/common/ContextClassLoaderFactory.java
b/core/src/main/java/org/apache/accumulo/core/spi/common/ContextClassLoaderFactory.java
index 7f423f3ead..c713653670 100644
---
a/core/src/main/java/org/apache/accumulo/core/spi/common/ContextClassLoaderFactory.java
+++
b/core/src/main/java/org/apache/accumulo/core/spi/common/ContextClassLoaderFactory.java
@@ -18,6 +18,8 @@
*/
package org.apache.accumulo.core.spi.common;
+import org.slf4j.LoggerFactory;
+
/**
* The ContextClassLoaderFactory provides a mechanism for various Accumulo
components to use a
* custom ClassLoader for specific contexts, such as loading table iterators.
This factory is
@@ -65,4 +67,28 @@ public interface ContextClassLoaderFactory {
* @return the class loader for the given contextName
*/
ClassLoader getClassLoader(String contextName);
+
+ /**
+ * Validate that the contextName is supported by the
ContextClassLoaderFactory implementation
+ *
+ * @param contextName the name of the context that represents a class loader
that is managed by
+ * this factory (can be null)
+ * @return true if valid, false otherwise
+ * @since 2.1.2
+ */
+ default boolean isValid(String contextName) {
+ try {
+ ClassLoader cl = getClassLoader(contextName);
+ if (cl == null) {
+ LoggerFactory.getLogger(ContextClassLoaderFactory.class)
+ .info("Null ClassLoader returned for context: {}", contextName);
+ return false;
+ }
+ return true;
+ } catch (RuntimeException e) {
+ LoggerFactory.getLogger(ContextClassLoaderFactory.class).info(
+ "Error thrown getting ClassLoader for context: {}, msg: {}",
contextName, e.getMessage());
+ return false;
+ }
+ }
}
diff --git
a/core/src/test/java/org/apache/accumulo/core/classloader/URLClassLoaderFactory.java
b/core/src/test/java/org/apache/accumulo/core/classloader/URLClassLoaderFactory.java
index c97a7d231e..07ebfc5dfe 100644
---
a/core/src/test/java/org/apache/accumulo/core/classloader/URLClassLoaderFactory.java
+++
b/core/src/test/java/org/apache/accumulo/core/classloader/URLClassLoaderFactory.java
@@ -21,26 +21,52 @@ package org.apache.accumulo.core.classloader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
-import java.util.stream.Stream;
import org.apache.accumulo.core.spi.common.ContextClassLoaderFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
// test implementation
public class URLClassLoaderFactory implements ContextClassLoaderFactory {
+ private static final Logger LOG =
LoggerFactory.getLogger(URLClassLoaderFactory.class);
private static final String COMMA = ",";
+ private URL[] parseURLsFromContextName(String contextName) throws
MalformedURLException {
+ String[] parts = contextName.split(COMMA);
+ URL[] urls = new URL[parts.length];
+ for (int i = 0; i < parts.length; i++) {
+ urls[i] = new URL(parts[i]);
+ }
+ return urls;
+ }
+
@Override
public ClassLoader getClassLoader(String contextName) {
+ if (contextName == null || contextName.isEmpty()) {
+ throw new IllegalArgumentException("contextName must have a value");
+ }
// The context name is the classpath.
- URL[] urls = Stream.of(contextName.split(COMMA)).map(p -> {
- try {
- return new URL(p);
- } catch (MalformedURLException e) {
- throw new IllegalArgumentException("Error creating URL from classpath
segment: " + p, e);
- }
- }).toArray(URL[]::new);
- return URLClassLoader.newInstance(urls);
+ try {
+ URL[] urls = parseURLsFromContextName(contextName);
+ return URLClassLoader.newInstance(urls);
+ } catch (MalformedURLException e) {
+ throw new IllegalArgumentException("Error creating URL from contextName:
" + contextName, e);
+ }
+ }
+
+ @Override
+ public boolean isValid(String contextName) {
+ if (contextName == null || contextName.isEmpty()) {
+ return false;
+ }
+ try {
+ parseURLsFromContextName(contextName);
+ return true;
+ } catch (MalformedURLException e) {
+ LOG.error("Error creating URL from contextName: " + contextName, e);
+ return false;
+ }
}
}
diff --git
a/server/base/src/main/java/org/apache/accumulo/server/util/PropUtil.java
b/server/base/src/main/java/org/apache/accumulo/server/util/PropUtil.java
index ae20ac67c4..85e03ea1c2 100644
--- a/server/base/src/main/java/org/apache/accumulo/server/util/PropUtil.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/util/PropUtil.java
@@ -21,6 +21,7 @@ package org.apache.accumulo.server.util;
import java.util.Collection;
import java.util.Map;
+import org.apache.accumulo.core.classloader.ClassLoaderUtil;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.server.ServerContext;
import org.apache.accumulo.server.conf.store.PropStoreKey;
@@ -38,7 +39,7 @@ public final class PropUtil {
*/
public static void setProperties(final ServerContext context, final
PropStoreKey<?> propStoreKey,
final Map<String,String> properties) throws IllegalArgumentException {
- PropUtil.validateProperties(context, propStoreKey, properties);
+ PropUtil.validateProperties(propStoreKey, properties);
context.getPropStore().putAll(propStoreKey, properties);
}
@@ -50,13 +51,12 @@ public final class PropUtil {
public static void replaceProperties(final ServerContext context,
final PropStoreKey<?> propStoreKey, final long version, final
Map<String,String> properties)
throws IllegalArgumentException {
- PropUtil.validateProperties(context, propStoreKey, properties);
+ PropUtil.validateProperties(propStoreKey, properties);
context.getPropStore().replaceAll(propStoreKey, version, properties);
}
- protected static void validateProperties(final ServerContext context,
- final PropStoreKey<?> propStoreKey, final Map<String,String> properties)
- throws IllegalArgumentException {
+ protected static void validateProperties(final PropStoreKey<?> propStoreKey,
+ final Map<String,String> properties) throws IllegalArgumentException {
for (Map.Entry<String,String> prop : properties.entrySet()) {
if (!Property.isValidProperty(prop.getKey(), prop.getValue())) {
String exceptionMessage = "Invalid property for : ";
@@ -65,6 +65,11 @@ public final class PropUtil {
}
throw new IllegalArgumentException(exceptionMessage + propStoreKey + "
name: "
+ prop.getKey() + ", value: " + prop.getValue());
+ } else if
(prop.getKey().equals(Property.TABLE_CLASSLOADER_CONTEXT.getKey())
+ &&
!Property.TABLE_CLASSLOADER_CONTEXT.getDefaultValue().equals(prop.getValue())
+ && !ClassLoaderUtil.isValidContext(prop.getValue())) {
+ throw new IllegalArgumentException(
+ "Unable to resolve classloader for context: " + prop.getValue());
}
}
}
diff --git
a/server/base/src/main/java/org/apache/accumulo/server/util/SystemPropUtil.java
b/server/base/src/main/java/org/apache/accumulo/server/util/SystemPropUtil.java
index 219a6cee03..0bb2e8a8e3 100644
---
a/server/base/src/main/java/org/apache/accumulo/server/util/SystemPropUtil.java
+++
b/server/base/src/main/java/org/apache/accumulo/server/util/SystemPropUtil.java
@@ -36,17 +36,19 @@ public class SystemPropUtil {
public static void setSystemProperty(ServerContext context, String property,
String value)
throws IllegalArgumentException {
- context.getPropStore().putAll(SystemPropKey.of(context),
- Map.of(validateSystemProperty(property, value), value));
+ final SystemPropKey key = SystemPropKey.of(context);
+ context.getPropStore().putAll(key, Map.of(validateSystemProperty(key,
property, value), value));
}
public static void modifyProperties(ServerContext context, long version,
Map<String,String> properties) throws IllegalArgumentException {
+ final SystemPropKey key = SystemPropKey.of(context);
final Map<String,
- String> checkedProperties = properties.entrySet().stream().collect(
- Collectors.toMap(entry -> validateSystemProperty(entry.getKey(),
entry.getValue()),
+ String> checkedProperties = properties.entrySet().stream()
+ .collect(Collectors.toMap(
+ entry -> validateSystemProperty(key, entry.getKey(),
entry.getValue()),
Map.Entry::getValue));
- context.getPropStore().replaceAll(SystemPropKey.of(context), version,
checkedProperties);
+ context.getPropStore().replaceAll(key, version, checkedProperties);
}
public static void removeSystemProperty(ServerContext context, String
property) {
@@ -64,8 +66,8 @@ public class SystemPropUtil {
context.getPropStore().removeProperties(SystemPropKey.of(context),
List.of(property));
}
- private static String validateSystemProperty(String property, final String
value)
- throws IllegalArgumentException {
+ private static String validateSystemProperty(SystemPropKey key, String
property,
+ final String value) throws IllegalArgumentException {
// Retrieve the replacement name for this property, if there is one.
// Do this before we check if the name is a valid zookeeper name.
final var original = property;
@@ -85,6 +87,9 @@ public class SystemPropUtil {
log.trace("Encountered error setting zookeeper property", iae);
throw iae;
}
+ if (Property.isValidTablePropertyKey(property)) {
+ PropUtil.validateProperties(key, Map.of(property, value));
+ }
// Find the property taking prefix into account
Property foundProp = null;
diff --git
a/start/src/main/java/org/apache/accumulo/start/classloader/vfs/AccumuloVFSClassLoader.java
b/start/src/main/java/org/apache/accumulo/start/classloader/vfs/AccumuloVFSClassLoader.java
index 55aa86c554..d7626025e7 100644
---
a/start/src/main/java/org/apache/accumulo/start/classloader/vfs/AccumuloVFSClassLoader.java
+++
b/start/src/main/java/org/apache/accumulo/start/classloader/vfs/AccumuloVFSClassLoader.java
@@ -424,7 +424,7 @@ public class AccumuloVFSClassLoader {
}
}
- private static synchronized ContextManager getContextManager() throws
IOException {
+ public static synchronized ContextManager getContextManager() throws
IOException {
if (contextManager == null) {
getClassLoader();
contextManager = new ContextManager(generateVfs(),
AccumuloVFSClassLoader::getClassLoader);
diff --git
a/start/src/main/java/org/apache/accumulo/start/classloader/vfs/ContextManager.java
b/start/src/main/java/org/apache/accumulo/start/classloader/vfs/ContextManager.java
index c57c38c4c0..d571dba6c6 100644
---
a/start/src/main/java/org/apache/accumulo/start/classloader/vfs/ContextManager.java
+++
b/start/src/main/java/org/apache/accumulo/start/classloader/vfs/ContextManager.java
@@ -31,7 +31,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Deprecated
-class ContextManager {
+public class ContextManager {
private static final Logger log =
LoggerFactory.getLogger(ContextManager.class);
@@ -158,6 +158,14 @@ class ContextManager {
this.config = config;
}
+ public boolean isKnownContext(String contextName) {
+ ContextConfig cconfig = config.getContextConfig(contextName);
+ if (cconfig == null) {
+ return false;
+ }
+ return true;
+ }
+
public ClassLoader getClassLoader(String contextName) throws
FileSystemException {
ContextConfig cconfig = config.getContextConfig(contextName);
diff --git
a/test/src/main/java/org/apache/accumulo/test/shell/ShellServerIT.java
b/test/src/main/java/org/apache/accumulo/test/shell/ShellServerIT.java
index fae2e6531f..1122b13338 100644
--- a/test/src/main/java/org/apache/accumulo/test/shell/ShellServerIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/shell/ShellServerIT.java
@@ -50,6 +50,7 @@ import java.util.regex.Pattern;
import org.apache.accumulo.core.Constants;
import org.apache.accumulo.core.client.Accumulo;
import org.apache.accumulo.core.client.AccumuloClient;
+import org.apache.accumulo.core.client.AccumuloException;
import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.client.Scanner;
import org.apache.accumulo.core.client.sample.RowColumnSampler;
@@ -2257,4 +2258,28 @@ public class ShellServerIT extends SharedMiniClusterBase
{
System.setProperty("accumulo.properties", orgProps);
}
}
+
+ @Test
+ public void failOnInvalidClassloaderContestTest() throws Exception {
+
+ final String[] names = getUniqueNames(3);
+ final String table1 = names[0];
+ final String namespace1 = names[1];
+ final String table2 = namespace1 + "." + names[2];
+
+ ts.exec("createtable " + table1, true);
+ ts.exec("createnamespace " + namespace1, true);
+ ts.exec("createtable " + table2, true);
+
+ ts.exec("config -s table.class.loader.context=invalid", false,
+ AccumuloException.class.getName(), true);
+ ts.exec("config -s table.class.loader.context=invalid -ns " + namespace1,
false,
+ AccumuloException.class.getName(), true);
+ ts.exec("config -s table.class.loader.context=invalid -t " + table1, false,
+ AccumuloException.class.getName(), true);
+ ts.exec("config -s table.class.loader.context=invalid -t " + table2, false,
+ AccumuloException.class.getName(), true);
+
+ }
+
}