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 8fb441b7d6 HDDS-8760. Let reconfiguration handler update 
reconfigurable config objects (#4851)
8fb441b7d6 is described below

commit 8fb441b7d67a62feec6e45b6fc9d908074791889
Author: Doroszlai, Attila <[email protected]>
AuthorDate: Thu Jun 15 18:30:34 2023 +0200

    HDDS-8760. Let reconfiguration handler update reconfigurable config objects 
(#4851)
---
 .../hadoop/hdds/conf/SimpleConfiguration.java      | 54 +++++++++++++------
 .../hdds/conf/SimpleConfigurationParent.java       |  2 +-
 .../hdds/conf/ConfigurationReflectionUtil.java     | 62 +++++++++++++++++-----
 .../hadoop/hdds/conf/ConfigurationSource.java      |  3 +-
 .../hadoop/hdds/conf/ReconfigurationHandler.java   | 25 +++++++--
 .../hdds/conf/TestReconfigurationHandler.java      | 56 +++++++++++++++----
 .../ozone/reconfig/TestOmReconfiguration.java      |  5 +-
 .../ozone/reconfig/TestScmReconfiguration.java     |  3 +-
 8 files changed, 160 insertions(+), 50 deletions(-)

diff --git 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/conf/SimpleConfiguration.java
 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/conf/SimpleConfiguration.java
index 16fc4e6311..a48bbcdb3b 100644
--- 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/conf/SimpleConfiguration.java
+++ 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/conf/SimpleConfiguration.java
@@ -25,36 +25,52 @@ import java.util.concurrent.TimeUnit;
 @ConfigGroup(prefix = "test.scm.client")
 public class SimpleConfiguration extends SimpleConfigurationParent {
 
-  @Config(key = "address", defaultValue = "localhost", description = "Client "
-      + "address (To test string injection).", tags = ConfigTag.MANAGEMENT)
+  @Config(key = "address",
+      defaultValue = "localhost",
+      description = "Client address (To test string injection).",
+      tags = ConfigTag.MANAGEMENT)
   private String clientAddress;
 
-  @Config(key = "bind.host", defaultValue = "0.0.0.0", description = "Bind "
-      + "host(To test string injection).", tags = ConfigTag.MANAGEMENT)
+  @Config(key = "bind.host",
+      defaultValue = "0.0.0.0",
+      description = "Bind host(To test string injection).",
+      tags = ConfigTag.MANAGEMENT)
   private String bindHost;
 
-  @Config(key = "compression.enabled", defaultValue = "true", description =
-      "Compression enabled. (Just to test boolean flag)", tags =
-      ConfigTag.MANAGEMENT)
+  @Config(key = "compression.enabled",
+      defaultValue = "true",
+      reconfigurable = true,
+      description = "Compression enabled. (Just to test boolean flag)",
+      tags = ConfigTag.MANAGEMENT)
   private boolean compressionEnabled;
 
-  @Config(key = "port", defaultValue = "9878", description = "Port number "
-      + "config (To test in injection)", tags = ConfigTag.MANAGEMENT)
+  @Config(key = "port",
+      defaultValue = "9878",
+      description = "Port number config (To test int injection)",
+      tags = ConfigTag.MANAGEMENT)
   private int port;
 
-  @Config(key = "wait", type = ConfigType.TIME, timeUnit =
-      TimeUnit.SECONDS, defaultValue = "30m", description = "Wait time (To "
-      + "test TIME config type)", tags = ConfigTag.MANAGEMENT)
+  @Config(key = "wait",
+      type = ConfigType.TIME,
+      timeUnit = TimeUnit.SECONDS,
+      defaultValue = "30m",
+      reconfigurable = true,
+      description = "Wait time (To test TIME config type)",
+      tags = ConfigTag.MANAGEMENT)
   private long waitTime;
 
-  @Config(key = "class", type = ConfigType.CLASS,
-      defaultValue = "java.lang.Object", description = "",
+  @Config(key = "class",
+      type = ConfigType.CLASS,
+      defaultValue = "java.lang.Object",
+      description = "",
       tags = ConfigTag.OZONE)
   private Class<?> myClass = Object.class;
 
-  @Config(key = "threshold", type = ConfigType.DOUBLE,
-      defaultValue = "10", description = "Threshold (To test DOUBLE config" +
-      " type)", tags = ConfigTag.MANAGEMENT)
+  @Config(key = "threshold",
+      type = ConfigType.DOUBLE,
+      defaultValue = "10",
+      description = "Threshold (To test DOUBLE config type)",
+      tags = ConfigTag.MANAGEMENT)
   private double threshold;
 
   @PostConstruct
@@ -62,6 +78,10 @@ public class SimpleConfiguration extends 
SimpleConfigurationParent {
     if (port < 0) {
       throw new NumberFormatException("Please use a positive port number");
     }
+    if (waitTime < 42) {
+      throw new IllegalArgumentException("Wait time less than 42 seconds: " +
+          waitTime);
+    }
   }
 
   public void setClientAddress(String clientAddress) {
diff --git 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/conf/SimpleConfigurationParent.java
 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/conf/SimpleConfigurationParent.java
index 85ef7babd7..04f2d5b686 100644
--- 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/conf/SimpleConfigurationParent.java
+++ 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/conf/SimpleConfigurationParent.java
@@ -20,7 +20,7 @@ package org.apache.hadoop.hdds.conf;
 /**
  * Parent class for the example configuration.
  */
-public class SimpleConfigurationParent {
+public class SimpleConfigurationParent extends ReconfigurableConfig {
 
   @Config(key = "enabled", defaultValue = "true", description = "Example "
       + "boolean config.", tags = ConfigTag.MANAGEMENT)
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 96e5546da2..d5c9587a2b 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
@@ -107,14 +107,26 @@ public final class ConfigurationReflectionUtil {
 
   public static <T> void reconfigureProperty(T configuration, Field field,
       String key, String value) {
+    Class<?> klass = field.getDeclaringClass();
     if (!field.isAnnotationPresent(Config.class)) {
       throw new ConfigurationException("Not configurable field: "
-          + field.getDeclaringClass() + "." + field.getName());
+          + klass + "." + field.getName());
     }
     Config configAnnotation = field.getAnnotation(Config.class);
 
-    setField(field.getDeclaringClass(), configuration, field, configAnnotation,
-        key, value);
+    try {
+      final Object oldValue = forcedFieldGet(field, configuration);
+      setField(klass, configuration, field, configAnnotation, key, value);
+      try {
+        callPostConstruct(configuration);
+      } catch (Exception e) {
+        forcedFieldSet(field, configuration, oldValue);
+        throw e;
+      }
+    } catch (IllegalAccessException e) {
+      throw new ConfigurationException("Failed to inject configuration to "
+          + klass.getSimpleName() + "." + field.getName(), e);
+    }
   }
 
   private static <T> void setField(
@@ -135,19 +147,45 @@ public final class ConfigurationReflectionUtil {
   }
 
   /**
-   * Set the value of one field even if it's private.
+   * Set possibly private {@code field} to {@code value} in {@code object}.
    */
   private static <T> void forcedFieldSet(Field field, T object, Object value)
       throws IllegalAccessException {
-    boolean accessChanged = false;
+    final boolean accessChanged = setAccessible(field);
+    try {
+      field.set(object, value);
+    } finally {
+      if (accessChanged) {
+        field.setAccessible(false);
+      }
+    }
+  }
+
+  /**
+   * @return the value of possibly private {@code field} in {@code object}
+   */
+  private static <T> Object forcedFieldGet(Field field, T object)
+      throws IllegalAccessException {
+    final boolean accessChanged = setAccessible(field);
+    try {
+      return field.get(object);
+    } finally {
+      if (accessChanged) {
+        field.setAccessible(false);
+      }
+    }
+  }
+
+  /**
+   * Make {@code field} accessible.
+   * @return true if access changed
+   */
+  private static boolean setAccessible(Field field) {
     if (!field.isAccessible()) {
       field.setAccessible(true);
-      accessChanged = true;
-    }
-    field.set(object, value);
-    if (accessChanged) {
-      field.setAccessible(false);
+      return true;
     }
+    return false;
   }
 
   private static ConfigType detectConfigType(Field field) {
@@ -174,8 +212,8 @@ public final class ConfigurationReflectionUtil {
     return type;
   }
 
-  public static <T> void callPostConstruct(Class<T> configurationClass,
-      T configObject) {
+  static <T> void callPostConstruct(T configObject) {
+    Class<?> configurationClass = configObject.getClass();
     for (Method method : configurationClass.getMethods()) {
       if (method.isAnnotationPresent(PostConstruct.class)) {
         try {
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 63231cfa14..dae095a193 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
@@ -177,8 +177,7 @@ public interface ConfigurationSource {
         .injectConfiguration(this, configurationClass, configObject,
             prefix, false);
 
-    ConfigurationReflectionUtil
-        .callPostConstruct(configurationClass, configObject);
+    ConfigurationReflectionUtil.callPostConstruct(configObject);
 
     return configObject;
 
diff --git 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/conf/ReconfigurationHandler.java
 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/conf/ReconfigurationHandler.java
index 8eae49d083..7a08e47ffa 100644
--- 
a/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/conf/ReconfigurationHandler.java
+++ 
b/hadoop-hdds/framework/src/main/java/org/apache/hadoop/hdds/conf/ReconfigurationHandler.java
@@ -19,6 +19,7 @@ package org.apache.hadoop.hdds.conf;
 
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.conf.ReconfigurableBase;
+import org.apache.hadoop.conf.ReconfigurationException;
 import org.apache.hadoop.conf.ReconfigurationTaskStatus;
 import org.apache.hadoop.hdds.protocol.ReconfigureProtocol;
 import org.apache.ratis.util.function.CheckedConsumer;
@@ -49,9 +50,9 @@ public class ReconfigurationHandler extends ReconfigurableBase
 
   public ReconfigurationHandler(String name, OzoneConfiguration config,
       CheckedConsumer<String, IOException> requireAdminPrivilege) {
+    super(config);
     this.name = name;
     this.requireAdminPrivilege = requireAdminPrivilege;
-    setConf(config);
   }
 
   public ReconfigurationHandler register(
@@ -60,6 +61,16 @@ public class ReconfigurationHandler extends 
ReconfigurableBase
     return this;
   }
 
+  public ReconfigurationHandler register(ReconfigurableConfig config) {
+    config.reconfigurableProperties().forEach(
+        prop -> properties.put(prop, newValue -> {
+          config.reconfigureProperty(prop, newValue);
+          return newValue;
+        })
+    );
+    return this;
+  }
+
   @Override
   protected Configuration getNewConf() {
     return new OzoneConfiguration();
@@ -71,9 +82,15 @@ public class ReconfigurationHandler extends 
ReconfigurableBase
   }
 
   @Override
-  public String reconfigurePropertyImpl(String property, String newValue) {
-    return properties.getOrDefault(property, identity())
-        .apply(newValue);
+  public String reconfigurePropertyImpl(String property, String newValue)
+      throws ReconfigurationException {
+    final String oldValue = getConf().get(property);
+    try {
+      return properties.getOrDefault(property, identity())
+          .apply(newValue);
+    } catch (Exception e) {
+      throw new ReconfigurationException(property, newValue, oldValue, e);
+    }
   }
 
   @Override
diff --git 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/conf/TestReconfigurationHandler.java
 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/conf/TestReconfigurationHandler.java
index 1166eee33d..a7da5763a0 100644
--- 
a/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/conf/TestReconfigurationHandler.java
+++ 
b/hadoop-hdds/framework/src/test/java/org/apache/hadoop/hdds/conf/TestReconfigurationHandler.java
@@ -17,6 +17,7 @@
  */
 package org.apache.hadoop.hdds.conf;
 
+import org.apache.hadoop.conf.ReconfigurationException;
 import org.apache.ratis.util.function.CheckedConsumer;
 import org.junit.jupiter.api.Test;
 
@@ -37,42 +38,75 @@ class TestReconfigurationHandler {
 
   private static final String PROP_A = "some.test.property";
   private static final String PROP_B = "other.property";
+  private static final String COMPRESSION_ENABLED =
+      "test.scm.client.compression.enabled";
+  private static final String WAIT = "test.scm.client.wait";
+
   private static final CheckedConsumer<String, IOException> ACCEPT = any -> { 
};
   private static final CheckedConsumer<String, IOException> DENY = any -> {
     throw new IOException("access denied");
   };
 
+  private final OzoneConfiguration config = new OzoneConfiguration();
+
   private final AtomicReference<String> refA =
       new AtomicReference<>("oldA");
   private final AtomicReference<String> refB =
       new AtomicReference<>("oldB");
+  private final SimpleConfiguration object =
+      config.getObject(SimpleConfiguration.class);
   private final AtomicReference<CheckedConsumer<String, IOException>> 
adminCheck
       = new AtomicReference<>(ACCEPT);
 
-  private final ReconfigurationHandler subject = new ReconfigurationHandler(
-      "test", new OzoneConfiguration(), op -> adminCheck.get().accept(op))
-              .register(PROP_A, refA::getAndSet)
-              .register(PROP_B, refB::getAndSet);
+  private final ReconfigurationHandler subject =
+      new ReconfigurationHandler(
+              "test", config, op -> adminCheck.get().accept(op))
+          .register(PROP_A, refA::getAndSet)
+          .register(PROP_B, refB::getAndSet)
+          .register(object);
+
+  private static Stream<String> expectedReconfigurableProperties() {
+    return Stream.of(PROP_A, PROP_B, COMPRESSION_ENABLED, WAIT);
+  }
 
   @Test
   void getProperties() {
-    assertEquals(Stream.of(PROP_A, PROP_B).collect(toSet()),
+    assertEquals(expectedReconfigurableProperties().collect(toSet()),
         subject.getReconfigurableProperties());
   }
 
   @Test
   void listProperties() throws IOException {
-    assertEquals(Stream.of(PROP_A, PROP_B).sorted().collect(toList()),
+    assertEquals(expectedReconfigurableProperties().sorted().collect(toList()),
         subject.listReconfigureProperties());
   }
 
   @Test
-  void callsReconfigurationFunction() {
-    subject.reconfigurePropertyImpl(PROP_A, "newA");
-    assertEquals("newA", refA.get());
+  void callsReconfigurationFunction() throws ReconfigurationException {
+    final String newA = "newA";
+    subject.reconfigurePropertyImpl(PROP_A, newA);
+    assertEquals(newA, refA.get());
+
+    final String newB = "newB";
+    subject.reconfigurePropertyImpl(PROP_B, newB);
+    assertEquals(newB, refB.get());
 
-    subject.reconfigurePropertyImpl(PROP_B, "newB");
-    assertEquals("newB", refB.get());
+    final boolean newCompressionEnabled = !object.isCompressionEnabled();
+    subject.reconfigurePropertyImpl(COMPRESSION_ENABLED,
+        Boolean.toString(newCompressionEnabled));
+    assertEquals(newCompressionEnabled, object.isCompressionEnabled());
+
+    final long newWaitTime = object.getWaitTime() + 5;
+    subject.reconfigurePropertyImpl(WAIT, Long.toString(newWaitTime));
+    assertEquals(newWaitTime, object.getWaitTime());
+  }
+
+  @Test
+  void validatesNewConfiguration() {
+    final long oldWaitTime = object.getWaitTime();
+    assertThrows(ReconfigurationException.class,
+        () -> subject.reconfigurePropertyImpl(WAIT, "0"));
+    assertEquals(oldWaitTime, object.getWaitTime());
   }
 
   @Test
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/reconfig/TestOmReconfiguration.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/reconfig/TestOmReconfiguration.java
index daa9e65467..a42763f0ce 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/reconfig/TestOmReconfiguration.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/reconfig/TestOmReconfiguration.java
@@ -19,6 +19,7 @@
 package org.apache.hadoop.ozone.reconfig;
 
 import com.google.common.collect.ImmutableSet;
+import org.apache.hadoop.conf.ReconfigurationException;
 import org.apache.hadoop.hdds.conf.ReconfigurationHandler;
 import org.junit.jupiter.api.Test;
 
@@ -44,7 +45,7 @@ class TestOmReconfiguration extends ReconfigurationTestBase {
   }
 
   @Test
-  void adminUsernames() {
+  void adminUsernames() throws ReconfigurationException {
     final String newValue = randomAlphabetic(10);
 
     getSubject().reconfigurePropertyImpl(OZONE_ADMINISTRATORS, newValue);
@@ -55,7 +56,7 @@ class TestOmReconfiguration extends ReconfigurationTestBase {
   }
 
   @Test
-  void readOnlyAdmins() {
+  void readOnlyAdmins() throws ReconfigurationException {
     final String newValue = randomAlphabetic(10);
 
     getSubject().reconfigurePropertyImpl(OZONE_READONLY_ADMINISTRATORS,
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/reconfig/TestScmReconfiguration.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/reconfig/TestScmReconfiguration.java
index 84045325dc..a407899c22 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/reconfig/TestScmReconfiguration.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/reconfig/TestScmReconfiguration.java
@@ -19,6 +19,7 @@
 package org.apache.hadoop.ozone.reconfig;
 
 import com.google.common.collect.ImmutableSet;
+import org.apache.hadoop.conf.ReconfigurationException;
 import org.apache.hadoop.hdds.conf.ReconfigurationHandler;
 import org.junit.jupiter.api.Test;
 
@@ -43,7 +44,7 @@ class TestScmReconfiguration extends ReconfigurationTestBase {
   }
 
   @Test
-  void adminUsernames() {
+  void adminUsernames() throws ReconfigurationException {
     final String newValue = randomAlphabetic(10);
 
     getSubject().reconfigurePropertyImpl(OZONE_ADMINISTRATORS, newValue);


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

Reply via email to