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

adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new c935f17339 HDDS-8668. Allow reconfiguration of annotated config object 
(#4774)
c935f17339 is described below

commit c935f173390f52cb749d8fb421cf849de578b651
Author: Doroszlai, Attila <[email protected]>
AuthorDate: Tue Jun 6 08:42:45 2023 +0200

    HDDS-8668. Allow reconfiguration of annotated config object (#4774)
---
 hadoop-hdds/config/pom.xml                         |  14 ++
 .../java/org/apache/hadoop/hdds/conf/Config.java   |   5 +
 .../org/apache/hadoop/hdds/conf/ConfigType.java    |  83 ++++++++-
 .../hdds/conf/ConfigurationReflectionUtil.java     | 189 ++++++++++++---------
 .../hadoop/hdds/conf/ConfigurationSource.java      |  12 +-
 .../hadoop/hdds/conf/ReconfigurableConfig.java}    |  27 +--
 .../hadoop/hdds/conf/ConfigurationExample.java     |  18 ++
 .../hdds/conf/ConfigurationExampleGrandParent.java |   6 +-
 .../hdds/conf/TestConfigurationReflectionUtil.java | 145 +++++++---------
 .../hadoop/hdds/conf/TestConfigurationSource.java  |  49 ++++--
 ...ndParent.java => TestReconfigurableConfig.java} |  24 +--
 .../config/src/test/resources/log4j.properties     |  23 +++
 12 files changed, 389 insertions(+), 206 deletions(-)

diff --git a/hadoop-hdds/config/pom.xml b/hadoop-hdds/config/pom.xml
index 52837149f8..ac1fb8ba30 100644
--- a/hadoop-hdds/config/pom.xml
+++ b/hadoop-hdds/config/pom.xml
@@ -37,6 +37,16 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd";>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
     </dependency>
+    <dependency>
+      <groupId>ch.qos.reload4j</groupId>
+      <artifactId>reload4j</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-reload4j</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.apache.ozone</groupId>
       <artifactId>hdds-test-utils</artifactId>
@@ -49,6 +59,10 @@ https://maven.apache.org/xsd/maven-4.0.0.xsd";>
       <scope>test</scope>
     </dependency>
 
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-params</artifactId>
+    </dependency>
   </dependencies>
 
 </project>
diff --git 
a/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/Config.java 
b/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/Config.java
index 5d4b4774a5..19018e6629 100644
--- a/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/Config.java
+++ b/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/Config.java
@@ -61,4 +61,9 @@ public @interface Config {
   StorageUnit sizeUnit() default StorageUnit.BYTES;
 
   ConfigTag[] tags();
+
+  /**
+   * Indicates whether this property can be dynamically reconfigured at 
runtime.
+   */
+  boolean reconfigurable() default false;
 }
diff --git 
a/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ConfigType.java 
b/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ConfigType.java
index 7dd38ac08a..96c6f0ed84 100644
--- 
a/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ConfigType.java
+++ 
b/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ConfigType.java
@@ -17,6 +17,8 @@
  */
 package org.apache.hadoop.hdds.conf;
 
+import static 
org.apache.hadoop.hdds.conf.TimeDurationUtil.getTimeDurationHelper;
+
 /**
  * Possible type of injected configuration.
  * <p>
@@ -24,13 +26,76 @@ package org.apache.hadoop.hdds.conf;
  * the configuration field.
  */
 public enum ConfigType {
-  AUTO,
-  STRING,
-  BOOLEAN,
-  INT,
-  LONG,
-  TIME,
-  SIZE,
-  CLASS,
-  DOUBLE
+  AUTO {
+    @Override
+    Object parse(String value, Config config, Class<?> type, String key) {
+      throw new UnsupportedOperationException();
+    }
+
+  },
+  STRING {
+    @Override
+    String parse(String value, Config config, Class<?> type, String key) {
+      return value;
+    }
+
+  },
+  BOOLEAN {
+    @Override
+    Boolean parse(String value, Config config, Class<?> type, String key) {
+      return Boolean.parseBoolean(value);
+    }
+
+  },
+  INT {
+    @Override
+    Integer parse(String value, Config config, Class<?> type, String key) {
+      return Integer.parseInt(value);
+    }
+
+  },
+  LONG {
+    @Override
+    Long parse(String value, Config config, Class<?> type, String key) {
+      return Long.parseLong(value);
+    }
+
+  },
+  TIME {
+    @Override
+    Long parse(String value, Config config, Class<?> type, String key) {
+      return getTimeDurationHelper(key, value, config.timeUnit());
+    }
+
+  },
+  SIZE {
+    @Override
+    Object parse(String value, Config config, Class<?> type, String key) {
+      StorageSize measure = StorageSize.parse(value);
+      long val = Math.round(measure.getUnit().toBytes(measure.getValue()));
+      if (type == int.class) {
+        return (int) val;
+      }
+      return val;
+    }
+
+  },
+  CLASS {
+    @Override
+    Class<?> parse(String value, Config config, Class<?> type, String key)
+        throws ClassNotFoundException {
+      return Class.forName(value);
+    }
+
+  },
+  DOUBLE {
+    @Override
+    Double parse(String value, Config config, Class<?> type, String key) {
+      return Double.parseDouble(value);
+    }
+
+  };
+
+  abstract Object parse(String value, Config config, Class<?> type, String key)
+      throws Exception;
 }
diff --git 
a/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ConfigurationReflectionUtil.java
 
b/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ConfigurationReflectionUtil.java
index 189d97207c..96e5546da2 100644
--- 
a/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ConfigurationReflectionUtil.java
+++ 
b/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ConfigurationReflectionUtil.java
@@ -22,7 +22,9 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Deque;
+import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Stream;
 
@@ -34,99 +36,101 @@ public final class ConfigurationReflectionUtil {
   private ConfigurationReflectionUtil() {
   }
 
+  public static <T> Map<String, Field> mapReconfigurableProperties(
+      Class<T> configurationClass) {
+    Optional<String> prefix = getPrefix(configurationClass);
+    Map<String, Field> props =
+        mapReconfigurableProperties(configurationClass, prefix);
+    Class<? super T> superClass = configurationClass.getSuperclass();
+    while (superClass != null) {
+      props.putAll(mapReconfigurableProperties(superClass, prefix));
+      superClass = superClass.getSuperclass();
+    }
+    return props;
+  }
+
+  private static <T> Map<String, Field> mapReconfigurableProperties(
+      Class<T> configurationClass, Optional<String> prefix) {
+    Map<String, Field> props = new HashMap<>();
+    for (Field field : configurationClass.getDeclaredFields()) {
+      if (field.isAnnotationPresent(Config.class)) {
+        Config configAnnotation = field.getAnnotation(Config.class);
+
+        if (configAnnotation.reconfigurable()) {
+          checkNotFinal(configurationClass, field);
+          props.put(getFullKey(prefix, configAnnotation), field);
+        }
+      }
+    }
+    return props;
+  }
+
   public static <T> void injectConfiguration(
       ConfigurationSource configuration,
       Class<T> configurationClass,
-      T configObject, String prefix) {
+      T configObject, String prefix, boolean reconfiguration) {
     injectConfigurationToObject(configuration, configurationClass, 
configObject,
-        prefix);
+        prefix, reconfiguration);
     Class<? super T> superClass = configurationClass.getSuperclass();
     while (superClass != null) {
       injectConfigurationToObject(configuration, superClass, configObject,
-          prefix);
+          prefix, reconfiguration);
       superClass = superClass.getSuperclass();
     }
   }
 
-  public static <T> void injectConfigurationToObject(ConfigurationSource from,
+  private static <T> void injectConfigurationToObject(ConfigurationSource from,
       Class<T> configurationClass,
       T configuration,
-      String prefix) {
+      String prefix,
+      boolean reconfiguration
+  ) {
     for (Field field : configurationClass.getDeclaredFields()) {
       if (field.isAnnotationPresent(Config.class)) {
-        if ((field.getModifiers() & Modifier.FINAL) != 0) {
-          throw new ConfigurationException(String.format(
-              "Trying to set final field %s#%s, probably indicates misplaced " 
+
-                  "@Config annotation",
-              configurationClass.getSimpleName(), field.getName()));
-        }
-
-        String fieldLocation =
-            configurationClass + "." + field.getName();
+        checkNotFinal(configurationClass, field);
 
         Config configAnnotation = field.getAnnotation(Config.class);
 
-        String key = prefix + "." + configAnnotation.key();
+        if (reconfiguration && !configAnnotation.reconfigurable()) {
+          continue;
+        }
 
+        String key = prefix + "." + configAnnotation.key();
         String defaultValue = configAnnotation.defaultValue();
+        String value = from.get(key, defaultValue);
 
-        ConfigType type = configAnnotation.type();
+        setField(configurationClass, configuration, field, configAnnotation,
+            key, value);
+      }
+    }
+  }
 
-        if (type == ConfigType.AUTO) {
-          type = detectConfigType(field.getType(), fieldLocation);
-        }
+  public static <T> void reconfigureProperty(T configuration, Field field,
+      String key, String value) {
+    if (!field.isAnnotationPresent(Config.class)) {
+      throw new ConfigurationException("Not configurable field: "
+          + field.getDeclaringClass() + "." + field.getName());
+    }
+    Config configAnnotation = field.getAnnotation(Config.class);
 
-        try {
-          switch (type) {
-          case STRING:
-            forcedFieldSet(field, configuration, from.get(key, defaultValue));
-            break;
-          case INT:
-            forcedFieldSet(field, configuration,
-                from.getInt(key, Integer.parseInt(defaultValue)));
-            break;
-          case BOOLEAN:
-            forcedFieldSet(field, configuration,
-                from.getBoolean(key, Boolean.parseBoolean(defaultValue)));
-            break;
-          case LONG:
-            forcedFieldSet(field, configuration,
-                from.getLong(key, Long.parseLong(defaultValue)));
-            break;
-          case DOUBLE:
-            forcedFieldSet(field, configuration,
-                from.getDouble(key, Double.parseDouble(defaultValue)));
-            break;
-          case TIME:
-            forcedFieldSet(field, configuration,
-                from.getTimeDuration(key, defaultValue,
-                    configAnnotation.timeUnit()));
-            break;
-          case SIZE:
-            final long value =
-                Math.round(from.getStorageSize(key,
-                    defaultValue, StorageUnit.BYTES));
-            if (field.getType() == int.class) {
-              forcedFieldSet(field, configuration, (int) value);
-            } else {
-              forcedFieldSet(field, configuration, value);
+    setField(field.getDeclaringClass(), configuration, field, configAnnotation,
+        key, value);
+  }
 
-            }
-            break;
-          case CLASS:
-            forcedFieldSet(field, configuration,
-                from.getClass(key, Class.forName(defaultValue)));
-            break;
-          default:
-            throw new ConfigurationException(
-                "Unsupported ConfigType " + type + " on " + fieldLocation);
-          }
-        } catch (IllegalAccessException | ClassNotFoundException e) {
-          throw new ConfigurationException(
-              "Can't inject configuration to " + fieldLocation, e);
-        }
+  private static <T> void setField(
+      Class<?> configurationClass, T configuration, Field field,
+      Config configAnnotation, String key, String value) {
+    ConfigType type = configAnnotation.type();
+    if (type == ConfigType.AUTO) {
+      type = detectConfigType(field);
+    }
 
-      }
+    try {
+      Object parsed = type.parse(value, configAnnotation, field.getType(), 
key);
+      forcedFieldSet(field, configuration, parsed);
+    } catch (Exception e) {
+      throw new ConfigurationException("Failed to inject configuration to "
+          + configurationClass.getSimpleName() + "." + field.getName(), e);
     }
   }
 
@@ -146,9 +150,9 @@ public final class ConfigurationReflectionUtil {
     }
   }
 
-  private static ConfigType detectConfigType(Class<?> parameterType,
-      String methodLocation) {
+  private static ConfigType detectConfigType(Field field) {
     ConfigType type;
+    Class<?> parameterType = field.getType();
     if (parameterType == String.class) {
       type = ConfigType.STRING;
     } else if (parameterType == Integer.class || parameterType == int.class) {
@@ -163,9 +167,9 @@ public final class ConfigurationReflectionUtil {
     } else if (parameterType == Class.class) {
       type = ConfigType.CLASS;
     } else {
-      throw new ConfigurationException(
-          "Unsupported configuration type " + parameterType + " in "
-              + methodLocation);
+      throw new ConfigurationException("Unsupported configuration type "
+          + parameterType + " in "
+          + field.getDeclaringClass() + "." + field.getName());
     }
     return type;
   }
@@ -223,7 +227,7 @@ public final class ConfigurationReflectionUtil {
         ConfigType type = configAnnotation.type();
 
         if (type == ConfigType.AUTO) {
-          type = detectConfigType(field.getType(), fieldLocation);
+          type = detectConfigType(field);
         }
 
         //Note: default value is handled by ozone-default.xml. Here we can
@@ -291,13 +295,10 @@ public final class ConfigurationReflectionUtil {
 
   public static Optional<String> getKey(Class<?> configClass,
       String fieldName) {
-    ConfigGroup configGroup =
-        configClass.getAnnotation(ConfigGroup.class);
+    Optional<String> prefix = getPrefix(configClass);
 
-    return findFieldConfigAnnotationByName(configClass,
-        fieldName).map(
-            config -> configGroup == null ? config.key()
-                : configGroup.prefix() + "." + config.key());
+    return findFieldConfigAnnotationByName(configClass, fieldName)
+        .map(config -> getFullKey(prefix, config));
   }
 
   public static Optional<ConfigType> getType(Class<?> configClass,
@@ -326,4 +327,32 @@ public final class ConfigurationReflectionUtil {
     }
     return Optional.empty();
   }
+
+  private static <T> void checkNotFinal(
+      Class<T> configurationClass, Field field) {
+
+    if ((field.getModifiers() & Modifier.FINAL) != 0) {
+      throw new ConfigurationException(String.format(
+          "Trying to set final field %s#%s, probably indicates misplaced " +
+              "@Config annotation",
+          configurationClass.getSimpleName(), field.getName()));
+    }
+  }
+
+  private static <T> Optional<String> getPrefix(Class<T> configurationClass) {
+    ConfigGroup configGroup =
+        configurationClass.getAnnotation(ConfigGroup.class);
+    return configGroup != null
+        ? Optional.of(configGroup.prefix())
+        : Optional.empty();
+  }
+
+  private static String getFullKey(
+      Optional<String> optionalPrefix, Config configAnnotation) {
+    String key = configAnnotation.key();
+    return optionalPrefix
+        .map(prefix -> prefix + "." + key)
+        .orElse(key);
+  }
+
 }
diff --git 
a/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ConfigurationSource.java
 
b/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ConfigurationSource.java
index 8c4ee7cd2f..63231cfa14 100644
--- 
a/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ConfigurationSource.java
+++ 
b/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ConfigurationSource.java
@@ -175,7 +175,7 @@ public interface ConfigurationSource {
 
     ConfigurationReflectionUtil
         .injectConfiguration(this, configurationClass, configObject,
-            prefix);
+            prefix, false);
 
     ConfigurationReflectionUtil
         .callPostConstruct(configurationClass, configObject);
@@ -184,6 +184,16 @@ public interface ConfigurationSource {
 
   }
 
+  /**
+   * Update {@code object}'s reconfigurable properties from this configuration.
+   */
+  default <T> void reconfigure(Class<T> configClass, T object) {
+    ConfigGroup configGroup = configClass.getAnnotation(ConfigGroup.class);
+    String prefix = configGroup.prefix();
+    ConfigurationReflectionUtil.injectConfiguration(
+        this, configClass, object, prefix, true);
+  }
+
   /**
    * Get the value of the <code>name</code> property as a <code>Class</code>
    * implementing the interface specified by <code>xface</code>.
diff --git 
a/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExampleGrandParent.java
 
b/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ReconfigurableConfig.java
similarity index 56%
copy from 
hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExampleGrandParent.java
copy to 
hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ReconfigurableConfig.java
index f6007c2221..1427364000 100644
--- 
a/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExampleGrandParent.java
+++ 
b/hadoop-hdds/config/src/main/java/org/apache/hadoop/hdds/conf/ReconfigurableConfig.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -17,20 +17,27 @@
  */
 package org.apache.hadoop.hdds.conf;
 
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.Set;
+
 /**
- * Example configuration to test inherited configuration injection.
+ * Base class for config with reconfigurable properties.
  */
-public class ConfigurationExampleGrandParent {
+public abstract class ReconfigurableConfig {
 
-  @Config(key = "number", defaultValue = "2", description = "Example numeric "
-      + "configuration", tags = ConfigTag.MANAGEMENT)
-  private int number = 1;
+  private final Map<String, Field> reconfigurableProperties =
+      ConfigurationReflectionUtil.mapReconfigurableProperties(getClass());
 
-  public int getNumber() {
-    return number;
+  public void reconfigureProperty(String property, String newValue) {
+    Field field = reconfigurableProperties.get(property);
+    if (field != null) {
+      ConfigurationReflectionUtil.reconfigureProperty(this,
+          field, property, newValue);
+    }
   }
 
-  public void setNumber(int number) {
-    this.number = number;
+  public Set<String> reconfigurableProperties() {
+    return reconfigurableProperties.keySet();
   }
 }
diff --git 
a/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExample.java
 
b/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExample.java
index 9e039d1279..1a2e76b59f 100644
--- 
a/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExample.java
+++ 
b/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExample.java
@@ -47,11 +47,25 @@ public class ConfigurationExample extends 
ConfigurationExampleParent {
       + "test TIME config type)", tags = ConfigTag.MANAGEMENT)
   private long waitTime = 1;
 
+  @Config(key = "size.small", type = ConfigType.SIZE, defaultValue = "42MB",
+      tags = {},
+      description = "Testing SIZE with int field")
+  private int smallSize;
+
+  @Config(key = "size.large", type = ConfigType.SIZE,
+      defaultValue = "5GB", tags = {},
+      description = "Testing SIZE with long field")
+  private long largeSize;
+
   @Config(key = "threshold", type = ConfigType.DOUBLE, defaultValue = "10",
       description = "Threshold (To test DOUBLE config type)",
       tags = ConfigTag.MANAGEMENT)
   private double threshold = 10;
 
+  @Config(key = "dynamic", reconfigurable = true, defaultValue = "original",
+      description = "Test dynamic property", tags = {})
+  private String dynamic;
+
   public void setClientAddress(String clientAddress) {
     this.clientAddress = clientAddress;
   }
@@ -99,4 +113,8 @@ public class ConfigurationExample extends 
ConfigurationExampleParent {
   public double getThreshold() {
     return threshold;
   }
+
+  public String getDynamic() {
+    return dynamic;
+  }
 }
diff --git 
a/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExampleGrandParent.java
 
b/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExampleGrandParent.java
index f6007c2221..e302638e5c 100644
--- 
a/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExampleGrandParent.java
+++ 
b/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExampleGrandParent.java
@@ -20,12 +20,16 @@ package org.apache.hadoop.hdds.conf;
 /**
  * Example configuration to test inherited configuration injection.
  */
-public class ConfigurationExampleGrandParent {
+public class ConfigurationExampleGrandParent extends ReconfigurableConfig {
 
   @Config(key = "number", defaultValue = "2", description = "Example numeric "
       + "configuration", tags = ConfigTag.MANAGEMENT)
   private int number = 1;
 
+  @Config(key = "grandpa.dyna", reconfigurable = true, defaultValue = "x",
+      description = "Test inherited dynamic property", tags = {})
+  private String grandpaDynamic;
+
   public int getNumber() {
     return number;
   }
diff --git 
a/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/TestConfigurationReflectionUtil.java
 
b/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/TestConfigurationReflectionUtil.java
index d9f3d43198..5732f6c561 100644
--- 
a/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/TestConfigurationReflectionUtil.java
+++ 
b/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/TestConfigurationReflectionUtil.java
@@ -17,99 +17,82 @@
  */
 package org.apache.hadoop.hdds.conf;
 
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
+import com.google.common.collect.ImmutableSet;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
 
 /**
  * Test the configuration reflection utility class.
  */
-@RunWith(Parameterized.class)
-public class TestConfigurationReflectionUtil {
-
-  private final ConfigType type;
-  private final String key;
-  private final String defaultValue;
-  private final Class testClass;
-  private final String fieldName;
-  private final boolean typePresent;
-  private final boolean keyPresent;
-  private final boolean defaultValuePresent;
+class TestConfigurationReflectionUtil {
 
-  @Parameterized.Parameters
-  public static Collection<Object[]> data() {
-    return Arrays.asList(new Object[][]{
-        {ConfigurationExample.class, "waitTime",
-            ConfigType.TIME, true,
-            "ozone.scm.client.wait", true,
-            "30m", true},
-        {ConfigurationExampleGrandParent.class, "number",
-            ConfigType.AUTO, true,
-            "number", true,
-            "2", true},
-        {ConfigurationExample.class, "secure",
-            ConfigType.AUTO, true,
-            "ozone.scm.client.secure", true,
-            "true", true},
-        {ConfigurationExample.class, "no-such-field",
-            null, false,
-            "", false,
-            "", false},
-        {ConfigFileAppender.class, "document",
-            null, false,
-            "", false,
-            "", false},
-        {ConfigurationExample.class, "threshold",
-            ConfigType.DOUBLE, true,
-            "ozone.scm.client.threshold", true,
-            "10", true},
-    });
+  static Stream<Arguments> data() {
+    return Stream.of(
+        arguments(ConfigurationExample.class, "waitTime",
+            Optional.of(ConfigType.TIME),
+            Optional.of("ozone.scm.client.wait"),
+            Optional.of("30m")),
+        arguments(ConfigurationExampleGrandParent.class, "number",
+            Optional.of(ConfigType.AUTO),
+            Optional.of("number"),
+            Optional.of("2")),
+        arguments(ConfigurationExample.class, "secure",
+            Optional.of(ConfigType.AUTO),
+            Optional.of("ozone.scm.client.secure"),
+            Optional.of("true")),
+        arguments(ConfigurationExample.class, "no-such-field",
+            Optional.empty(),
+            Optional.empty(),
+            Optional.empty()),
+        arguments(ConfigFileAppender.class, "document",
+            Optional.empty(),
+            Optional.empty(),
+            Optional.empty()),
+        arguments(ConfigurationExample.class, "threshold",
+            Optional.of(ConfigType.DOUBLE),
+            Optional.of("ozone.scm.client.threshold"),
+            Optional.of("10"))
+    );
   }
 
-  @SuppressWarnings("checkstyle:ParameterNumber")
-  public TestConfigurationReflectionUtil(
-      Class testClass, String fieldName,
-      ConfigType type, boolean typePresent,
-      String key, boolean keyPresent,
-      String defaultValue, boolean defaultValuePresent) {
-    this.testClass = testClass;
-    this.fieldName = fieldName;
-    this.typePresent = typePresent;
-    this.type = type;
-    this.key = key;
-    this.keyPresent = keyPresent;
-    this.defaultValue = defaultValue;
-    this.defaultValuePresent = defaultValuePresent;
+  @ParameterizedTest
+  @MethodSource("data")
+  void testForGivenClasses(Class<?> testClass, String fieldName,
+      Optional<ConfigType> expectedType,
+      Optional<String> expectedKey,
+      Optional<String> expectedDefault) {
+    Optional<ConfigType> type = ConfigurationReflectionUtil.getType(
+        testClass, fieldName);
+    assertEquals(expectedType, type);
+
+    Optional<String> key = ConfigurationReflectionUtil.getKey(
+        testClass, fieldName);
+    assertEquals(expectedKey, key);
+
+    Optional<String> defaultValue = 
ConfigurationReflectionUtil.getDefaultValue(
+        testClass, fieldName);
+    assertEquals(expectedDefault, defaultValue);
   }
 
   @Test
-  public void testForGivenClasses() {
-    Optional<ConfigType> actualType =
-        ConfigurationReflectionUtil.getType(
-            testClass, fieldName);
-    Assert.assertEquals(typePresent, actualType.isPresent());
-    if (typePresent) {
-      Assert.assertEquals(type, actualType.get());
-    }
+  void listReconfigurableProperties() {
+    Set<String> props =
+        ConfigurationReflectionUtil.mapReconfigurableProperties(
+            ConfigurationExample.class).keySet();
 
-    Optional<String> actualKey =
-        ConfigurationReflectionUtil.getKey(
-            testClass, fieldName);
-    Assert.assertEquals(keyPresent, actualKey.isPresent());
-    if (keyPresent) {
-      Assert.assertEquals(key, actualKey.get());
-    }
-    Optional<String> actualDefaultValue =
-        ConfigurationReflectionUtil.getDefaultValue(
-            testClass, fieldName);
-    Assert.assertEquals(defaultValuePresent, actualDefaultValue.isPresent());
-    if (defaultValuePresent) {
-      Assert.assertEquals(defaultValue, actualDefaultValue.get());
-    }
+    String prefix = "ozone.scm.client";
+    assertEquals(ImmutableSet.of(
+        prefix + ".dynamic",
+        prefix + ".grandpa.dyna"
+    ), props);
   }
 }
diff --git 
a/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/TestConfigurationSource.java
 
b/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/TestConfigurationSource.java
index ac92276a73..e135fb8c36 100644
--- 
a/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/TestConfigurationSource.java
+++ 
b/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/TestConfigurationSource.java
@@ -18,10 +18,11 @@
 
 package org.apache.hadoop.hdds.conf;
 
-import org.junit.Assert;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import org.junit.jupiter.api.Test;
 
-import java.util.Map;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 class TestConfigurationSource {
 
@@ -29,22 +30,44 @@ class TestConfigurationSource {
   void getPropsMatchPrefixAndTrimPrefix() {
     MutableConfigurationSource c = new InMemoryConfiguration();
     c.set("somePrefix.key", "value");
-    ConfigurationSource config = c;
-    Map<String, String> entry =
-        config.getPropsMatchPrefixAndTrimPrefix("somePrefix.");
-    Assert.assertEquals("key", entry.keySet().toArray()[0]);
-    Assert.assertEquals("value", entry.values().toArray()[0]);
+
+    assertEquals(ImmutableMap.of("key", "value"),
+        c.getPropsMatchPrefixAndTrimPrefix("somePrefix."));
   }
 
   @Test
   void getPropsMatchPrefix() {
     MutableConfigurationSource c = new InMemoryConfiguration();
     c.set("somePrefix.key", "value");
-    ConfigurationSource config = c;
-    Map<String, String> entry =
-        config.getPropsMatchPrefix("somePrefix.");
-    Assert.assertEquals("somePrefix.key",
-        entry.keySet().toArray()[0]);
-    Assert.assertEquals("value", entry.values().toArray()[0]);
+
+    assertEquals(ImmutableMap.of("somePrefix.key", "value"),
+        c.getPropsMatchPrefix("somePrefix."));
+  }
+  @Test
+  void reconfigurableProperties() {
+    String prefix = "ozone.scm.client";
+    ImmutableSet<String> expected = ImmutableSet.of(
+        prefix + ".dynamic",
+        prefix + ".grandpa.dyna"
+    );
+
+    ConfigurationExample obj = new InMemoryConfiguration().getObject(
+        ConfigurationExample.class);
+
+    assertEquals(expected, obj.reconfigurableProperties());
+  }
+
+  @Test
+  void reconfiguration() {
+    MutableConfigurationSource subject = new InMemoryConfiguration();
+    ConfigurationExample orig = subject.getObject(ConfigurationExample.class);
+    ConfigurationExample obj = subject.getObject(ConfigurationExample.class);
+
+    subject.set("ozone.scm.client.dynamic", "updated");
+    subject.setLong("ozone.scm.client.wait", orig.getWaitTime() + 42);
+    subject.reconfigure(ConfigurationExample.class, obj);
+
+    assertEquals("updated", obj.getDynamic());
+    assertEquals(orig.getWaitTime(), obj.getWaitTime());
   }
 }
diff --git 
a/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExampleGrandParent.java
 
b/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/TestReconfigurableConfig.java
similarity index 65%
copy from 
hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExampleGrandParent.java
copy to 
hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/TestReconfigurableConfig.java
index f6007c2221..ed1123aeb7 100644
--- 
a/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/ConfigurationExampleGrandParent.java
+++ 
b/hadoop-hdds/config/src/test/java/org/apache/hadoop/hdds/conf/TestReconfigurableConfig.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -17,20 +17,22 @@
  */
 package org.apache.hadoop.hdds.conf;
 
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
 /**
- * Example configuration to test inherited configuration injection.
+ * Test for {@link ReconfigurableConfig}.
  */
-public class ConfigurationExampleGrandParent {
+class TestReconfigurableConfig {
 
-  @Config(key = "number", defaultValue = "2", description = "Example numeric "
-      + "configuration", tags = ConfigTag.MANAGEMENT)
-  private int number = 1;
+  @Test
+  void testReconfigureProperty() {
+    ConfigurationExample subject = new InMemoryConfiguration()
+        .getObject(ConfigurationExample.class);
 
-  public int getNumber() {
-    return number;
-  }
+    subject.reconfigureProperty("ozone.scm.client.dynamic", "updated");
 
-  public void setNumber(int number) {
-    this.number = number;
+    assertEquals("updated", subject.getDynamic());
   }
 }
diff --git a/hadoop-hdds/config/src/test/resources/log4j.properties 
b/hadoop-hdds/config/src/test/resources/log4j.properties
new file mode 100644
index 0000000000..398786689a
--- /dev/null
+++ b/hadoop-hdds/config/src/test/resources/log4j.properties
@@ -0,0 +1,23 @@
+#
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+# log4j configuration used during build and unit tests
+
+log4j.rootLogger=INFO,stdout
+log4j.threshold=ALL
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c{2} 
(%F:%M(%L)) - %m%n


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to