This is an automated email from the ASF dual-hosted git repository.
smolnar82 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new d4eecad43 KNOX-3327: Support periodic refresh of gateway configuration
(#1235)
d4eecad43 is described below
commit d4eecad43e74d6c31ce7d56bf6421a4d1bf745ae
Author: Sandor Molnar <[email protected]>
AuthorDate: Wed May 20 14:51:40 2026 +0200
KNOX-3327: Support periodic refresh of gateway configuration (#1235)
---
.../org/apache/knox/gateway/GatewayMessages.java | 6 ++
.../org/apache/knox/gateway/GatewayServer.java | 41 ++++++++-
.../gateway/config/impl/GatewayConfigImpl.java | 12 ++-
.../org/apache/knox/gateway/GatewayServerTest.java | 98 ++++++++++++++++++++++
.../gateway/config/impl/GatewayConfigImplTest.java | 39 +++++++++
.../org/apache/knox/gateway/GatewayTestConfig.java | 5 ++
.../apache/knox/gateway/config/GatewayConfig.java | 6 ++
7 files changed, 205 insertions(+), 2 deletions(-)
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
index 7e8240879..21e0d7032 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
@@ -687,6 +687,12 @@ public interface GatewayMessages {
@Message(level = MessageLevel.INFO, text = "Stopping service: {0}")
void stoppingService(String serviceTypeName);
+ @Message(level = MessageLevel.INFO, text = "Refreshed gateway config")
+ void refreshedGatewayConfig();
+
+ @Message(level = MessageLevel.WARN, text = "Unable to refresh gateway
config")
+ void unableToReloadGatewayConfig(@StackTrace(level = MessageLevel.DEBUG)
Exception e);
+
@Message(level = MessageLevel.INFO, text = "Redeploying topology {0} due to
service definition change {1} / {2} / {3}")
void redeployingTopologyOnServiceDefinitionChange(String topologyName,
String serviceName, String role, String version);
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
index 1106d0d9c..cf1e8f998 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
@@ -109,6 +109,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.nio.file.attribute.FileTime;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
@@ -126,9 +127,14 @@ import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
+import static
org.apache.knox.gateway.config.impl.GatewayConfigImpl.RELOADABLE_CONFIG_FILENAME;
+
public class GatewayServer {
private static final GatewayResources res =
ResourcesFactory.get(GatewayResources.class);
private static final GatewayMessages log =
MessagesFactory.get(GatewayMessages.class);
@@ -144,6 +150,9 @@ public class GatewayServer {
private static Properties buildProperties;
+ private static final ScheduledExecutorService exec =
Executors.newSingleThreadScheduledExecutor();
+ private static FileTime lastReloadTime;
+
private Server jetty;
private GatewayConfig config;
private ContextHandlerCollection contexts;
@@ -171,12 +180,13 @@ public class GatewayServer {
if (services == null) {
log.failedToInstantiateGatewayServices();
}
- final GatewayConfig config = new GatewayConfigImpl();
+ final GatewayConfigImpl config = new GatewayConfigImpl();
validateConfigurableGatewayDirectories(config);
if (config.isHadoopKerberosSecured()) {
validateKerberosConfig(config);
configureKerberosSecurity( config );
}
+ setupGatewayConfigRefresh(config);
Map<String,String> options = new HashMap<>();
options.put(GatewayCommandLine.PERSIST_LONG,
Boolean.toString(cmd.hasOption(GatewayCommandLine.PERSIST_LONG)));
services.init(config, options);
@@ -229,6 +239,35 @@ public class GatewayServer {
return services;
}
+ private static void setupGatewayConfigRefresh(GatewayConfigImpl config) {
+ Path resourcePath = Paths.get(config.getGatewayConfDir(),
RELOADABLE_CONFIG_FILENAME);
+ int refreshInterval = config.getConfigRefreshInterval();
+ if(refreshInterval > 0) {
+ exec.scheduleAtFixedRate(() -> refreshGatewayConfig(config,
resourcePath),
+ 0, refreshInterval, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ /**
+ * Check if the resourcePath has been modified since the last reload time
+ * If so then reload the configuration to pick up the new configs
+ * @param resourcePath resource path to check and reload
+ */
+ private static synchronized void refreshGatewayConfig(GatewayConfigImpl
config, Path resourcePath) {
+ try {
+ if(Files.exists(resourcePath) && Files.isReadable(resourcePath)) {
+ FileTime lastModifiedTime = Files.getLastModifiedTime(resourcePath);
+ if (lastReloadTime == null ||
lastReloadTime.compareTo(lastModifiedTime) < 0) {
+ lastReloadTime = lastModifiedTime;
+ config.reloadConfiguration();
+ log.refreshedGatewayConfig();
+ }
+ }
+ } catch (IOException e) {
+ log.unableToReloadGatewayConfig(e);
+ }
+ }
+
private static void logSysProp( String name ) {
log.logSysProp( name, System.getProperty( name ) );
}
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
index 11e58527b..79c67f5a2 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
@@ -104,7 +104,9 @@ public class GatewayConfigImpl extends Configuration
implements GatewayConfig {
private static final String DEFAULT_APPLICATIONS_DIR = "applications";
- private static final String[] GATEWAY_CONFIG_FILENAMES =
{GATEWAY_CONFIG_FILE_PREFIX + "-default.xml", GATEWAY_CONFIG_FILE_PREFIX +
"-site.xml"};
+ public static final String RELOADABLE_CONFIG_FILENAME =
GATEWAY_CONFIG_FILE_PREFIX + "-reloadable.xml";
+
+ private static final String[] GATEWAY_CONFIG_FILENAMES =
{GATEWAY_CONFIG_FILE_PREFIX + "-default.xml", GATEWAY_CONFIG_FILE_PREFIX +
"-site.xml", RELOADABLE_CONFIG_FILENAME};
private static final String GATEWAY_SERVICE_PREFIX =
GATEWAY_CONFIG_FILE_PREFIX + ".service.";
public static final String HTTP_HOST = GATEWAY_CONFIG_FILE_PREFIX + ".host";
@@ -277,6 +279,9 @@ public class GatewayConfigImpl extends Configuration
implements GatewayConfig {
static final String REMOTE_ALIAS_SERVICE_CONFIG_PREFIX =
GATEWAY_CONFIG_FILE_PREFIX + ".remote.alias.service.config.prefix";
static final String REMOTE_ALIAS_SERVICE_CONFIG_PREFIX_DEFAULT =
GATEWAY_CONFIG_FILE_PREFIX + ".remote.alias.service.config.";
+ static final String CONFIG_REFRESH_INTERVAL = GATEWAY_CONFIG_FILE_PREFIX +
".config.refresh.interval";
+ static final int CONFIG_REFRESH_INTERVAL_DEFAULT = 10000;
+
private static final List<String> DEFAULT_GLOBAL_RULES_SERVICES =
Arrays.asList(
"NAMENODE", "JOBTRACKER", "WEBHDFS", "WEBHCAT",
"OOZIE", "WEBHBASE", "HIVE", "RESOURCEMANAGER",
@@ -1332,6 +1337,11 @@ public class GatewayConfigImpl extends Configuration
implements GatewayConfig {
return set;
}
+ @Override
+ public int getConfigRefreshInterval() {
+ return getInt(CONFIG_REFRESH_INTERVAL, CONFIG_REFRESH_INTERVAL_DEFAULT);
+ }
+
@Override
public long getClouderaManagerDescriptorsMonitoringInterval() {
return getLong(CLOUDERA_MANAGER_DESCRIPTORS_MONITOR_INTERVAL, -1L);
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/GatewayServerTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/GatewayServerTest.java
new file mode 100644
index 000000000..33d348190
--- /dev/null
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/GatewayServerTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+package org.apache.knox.gateway;
+
+import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
+import org.easymock.EasyMock;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+
+import static org.junit.Assert.assertNotNull;
+
+public class GatewayServerTest {
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ @Test
+ public void testRefreshGatewayConfig() throws Exception {
+ GatewayConfigImpl config =
EasyMock.createNiceMock(GatewayConfigImpl.class);
+
+ File configFile = folder.newFile("gateway-reloadable.xml");
+ try (PrintWriter writer = new PrintWriter(configFile,
StandardCharsets.UTF_8)) {
+ writer.println("<configuration>");
+ writer.println(" <property>");
+ writer.println(" <name>test.prop</name>");
+ writer.println(" <value>test.val</value>");
+ writer.println(" </property>");
+ writer.println("</configuration>");
+ }
+
+ Method refreshMethod =
GatewayServer.class.getDeclaredMethod("refreshGatewayConfig",
GatewayConfigImpl.class, Path.class);
+ refreshMethod.setAccessible(true);
+
+ // Initial load
+ config.reloadConfiguration();
+ EasyMock.expectLastCall().once();
+ EasyMock.replay(config);
+
+ refreshMethod.invoke(null, config, configFile.toPath());
+
+ EasyMock.verify(config);
+
+ // Check lastReloadTime is set
+ Field lastReloadTimeField =
GatewayServer.class.getDeclaredField("lastReloadTime");
+ lastReloadTimeField.setAccessible(true);
+ FileTime lastReloadTime = (FileTime) lastReloadTimeField.get(null);
+ assertNotNull(lastReloadTime);
+
+ // Second call without change should not trigger reload
+ EasyMock.reset(config);
+ EasyMock.replay(config);
+ refreshMethod.invoke(null, config, configFile.toPath());
+ EasyMock.verify(config);
+
+ // Update file to trigger reload
+ Thread.sleep(1000); // Ensure timestamp changes
+ try (PrintWriter writer = new PrintWriter(configFile,
StandardCharsets.UTF_8)) {
+ writer.println("<configuration>");
+ writer.println(" <property>");
+ writer.println(" <name>test.prop</name>");
+ writer.println(" <value>test.val2</value>");
+ writer.println(" </property>");
+ writer.println("</configuration>");
+ }
+
+ EasyMock.reset(config);
+ config.reloadConfiguration();
+ EasyMock.expectLastCall().once();
+ EasyMock.replay(config);
+
+ refreshMethod.invoke(null, config, configFile.toPath());
+ EasyMock.verify(config);
+ }
+}
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/config/impl/GatewayConfigImplTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/config/impl/GatewayConfigImplTest.java
index 6791fe996..88b52a148 100644
---
a/gateway-server/src/test/java/org/apache/knox/gateway/config/impl/GatewayConfigImplTest.java
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/config/impl/GatewayConfigImplTest.java
@@ -31,6 +31,9 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import java.io.File;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.util.ArrayList;
@@ -45,10 +48,15 @@ import org.apache.knox.gateway.config.GatewayConfig;
import
org.apache.knox.gateway.services.security.impl.ZookeeperRemoteAliasService;
import org.apache.knox.test.TestUtils;
import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
public class GatewayConfigImplTest {
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
@Test( timeout = TestUtils.SHORT_TIMEOUT )
public void testHttpServerSettings() {
GatewayConfigImpl config = new GatewayConfigImpl();
@@ -729,4 +737,35 @@ public class GatewayConfigImplTest {
final List<String> result = new
GatewayConfigImpl().getClouderaManagerClientSSLCiphers();
assertNull(result);
}
+
+ @Test
+ public void testGetConfigRefreshInterval() {
+ GatewayConfigImpl config = new GatewayConfigImpl();
+ assertThat(config.getConfigRefreshInterval(), is(10000));
+
+ config.setInt("gateway.config.refresh.interval", 5000);
+ assertThat(config.getConfigRefreshInterval(), is(5000));
+ }
+
+ @Test
+ public void testReloadableConfigLoading() throws Exception {
+ File dir = folder.newFolder("conf");
+ File reloadableFile = new File(dir, "gateway-reloadable.xml");
+ try (PrintWriter writer = new PrintWriter(reloadableFile,
StandardCharsets.UTF_8)) {
+ writer.println("<configuration>");
+ writer.println(" <property>");
+ writer.println(" <name>test.reloadable.prop</name>");
+ writer.println(" <value>test.reloadable.val</value>");
+ writer.println(" </property>");
+ writer.println("</configuration>");
+ }
+
+ try {
+ System.setProperty("KNOX_GATEWAY_CONF_DIR", dir.getAbsolutePath());
+ GatewayConfigImpl config = new GatewayConfigImpl();
+ assertThat(config.get("test.reloadable.prop"),
is("test.reloadable.val"));
+ } finally {
+ System.clearProperty("KNOX_GATEWAY_CONF_DIR");
+ }
+ }
}
diff --git
a/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
b/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
index 7fbaa9a2a..8a55d3736 100644
---
a/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
+++
b/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
@@ -862,6 +862,11 @@ public class GatewayTestConfig extends Configuration
implements GatewayConfig {
return null;
}
+ @Override
+ public int getConfigRefreshInterval() {
+ return 0;
+ }
+
@Override
public long getClouderaManagerDescriptorsMonitoringInterval() {
return 0;
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
index 75bc362a5..a3c62e011 100644
---
a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
@@ -746,6 +746,12 @@ public interface GatewayConfig {
*/
Set<String> getServicesToIgnoreDoAs();
+ /**
+ * @return refresh interval in ms
+ * @since 3.0.0
+ */
+ int getConfigRefreshInterval();
+
/**
* @return the monitoring interval (in milliseconds) of Cloudera Manager
descriptors
*/