You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by mo...@apache.org on 2026/10/22 20:34:53 UTC
(knox) branch master updated: KNOX-3526: Redeploy topologies with reloadableListener on gateway-rel… (#1466)
This is an automated email from the ASF dual-hosted git repository.
moresandeep 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 e19e63e4f KNOX-3526: Redeploy topologies with reloadableListener on gateway-rel… (#1466)
e19e63e4f is described below
commit e19e63e4faade107cc3f944e41327e3b94e764ed
Author: hanicz <ha...@users.noreply.github.com>
AuthorDate: Thu Oct 22 22:34:48 2026 +0200
KNOX-3526: Redeploy topologies with reloadableListener on gateway-rel… (#1466)
* KNOX-3526: Redeploy topologies with reloadableListener on gateway-reloadable.xml changes
* KNOX-3526: Keep notifying listeners in case of exceptions. Admin API now saves the reloadableListener flag.
---
.../org/apache/knox/gateway/GatewayMessages.java | 6 ++
.../org/apache/knox/gateway/GatewayServer.java | 12 ++-
.../topology/impl/DefaultTopologyService.java | 15 +++-
.../builder/BeanPropertyTopologyBuilder.java | 11 +++
.../topology/xml/KnoxFormatXmlTopologyRules.java | 2 +
.../src/main/resources/conf/topology-v1.xsd | 1 +
.../org/apache/knox/gateway/GatewayServerTest.java | 34 ++++++++
.../topology/DefaultTopologyServiceTest.java | 90 ++++++++++++++++++++++
.../simple/SimpleDescriptorFactoryTest.java | 18 +++++
.../simple/SimpleDescriptorHandlerTest.java | 40 ++++++++++
.../topology/validation/TopologyValidatorTest.java | 4 +
.../topology-valid-reloadable-listener.xml | 30 ++++++++
.../gateway/service/admin/beans/BeanConverter.java | 2 +
.../knox/gateway/service/admin/beans/Topology.java | 11 +++
.../org/apache/knox/gateway/topology/Topology.java | 17 ++++
.../knox/gateway/topology/topology_binding-xml.xml | 5 +-
.../hadoop/xml/HadoopXmlResourceParser.java | 4 +
.../hadoop/xml/HadoopXmlResourceParserTest.java | 2 +
.../src/test/resources/testDescriptor.xml | 1 +
.../gateway/topology/simple/SimpleDescriptor.java | 2 +
.../topology/simple/SimpleDescriptorHandler.java | 4 +
.../topology/simple/SimpleDescriptorImpl.java | 12 +++
22 files changed, 320 insertions(+), 3 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 d4639b011..817e72b33 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
@@ -702,9 +702,15 @@ public interface GatewayMessages {
@Message(level = MessageLevel.WARN, text = "Unable to refresh gateway config")
void unableToReloadGatewayConfig(@StackTrace(level = MessageLevel.DEBUG) Exception e);
+ @Message(level = MessageLevel.ERROR, text = "Gateway config change listener {0} failed: {1}")
+ void gatewayConfigChangeListenerFailed(String listener, @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);
+ @Message(level = MessageLevel.INFO, text = "Redeploying topology {0} due to gateway reloadable configuration change")
+ void redeployingTopologyOnGatewayConfigChange(String topologyName);
+
@Message(level = MessageLevel.INFO, text = "Saved service definition {0} / {1} / {2}")
void savedServiceDefinitionChange(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 405604f63..9438ac364 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
@@ -277,7 +277,11 @@ public class GatewayServer {
config.reloadConfiguration();
log.refreshedGatewayConfig();
for (GatewayConfigChangeListener listener : configChangeListeners) {
- listener.onGatewayConfigChanged(config);
+ try {
+ listener.onGatewayConfigChanged(config);
+ } catch (RuntimeException e) {
+ log.gatewayConfigChangeListenerFailed(listener.getClass().getName(), e);
+ }
}
}
}
@@ -725,6 +729,9 @@ public class GatewayServer {
final ServiceDefinitionRegistry serviceDefinitionRegistry = services.getService(ServiceType.SERVICE_DEFINITION_REGISTRY);
serviceDefinitionRegistry.addServiceDefinitionChangeListener(monitor);
+ if (monitor instanceof GatewayConfigChangeListener) {
+ registerConfigChangeListener((GatewayConfigChangeListener) monitor);
+ }
final Map<String, Integer> topologyPortMap = config.getGatewayPortMappings();
@@ -820,6 +827,9 @@ public class GatewayServer {
log.stoppingGateway();
services.stop();
monitor.stopMonitor();
+ if (monitor instanceof GatewayConfigChangeListener) {
+ unregisterConfigChangeListener((GatewayConfigChangeListener) monitor);
+ }
jetty.stop();
jetty.join();
log.stoppedGateway();
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java
index 0468e4aee..3437d678d 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java
@@ -33,6 +33,7 @@ import org.apache.knox.gateway.audit.api.Auditor;
import org.apache.knox.gateway.audit.api.ResourceType;
import org.apache.knox.gateway.audit.log4j.audit.AuditConstants;
import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.config.GatewayConfigChangeListener;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.service.definition.ServiceDefinition;
import org.apache.knox.gateway.service.definition.ServiceDefinitionChangeListener;
@@ -86,7 +87,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class DefaultTopologyService extends FileAlterationListenerAdaptor implements TopologyService, TopologyMonitor,
- TopologyProvider, FileFilter, FileAlterationListener, ServiceDefinitionChangeListener {
+ TopologyProvider, FileFilter, FileAlterationListener, ServiceDefinitionChangeListener, GatewayConfigChangeListener {
private static final JAXBContext jaxbContext = getJAXBContext();
private static final String TOPOLOGY_CLOSING_XML_ELEMENT = "</topology>";
@@ -536,6 +537,18 @@ public class DefaultTopologyService extends FileAlterationListenerAdaptor implem
});
}
+ @Override
+ public void onGatewayConfigChanged(GatewayConfig config) {
+ getTopologies().stream().filter(Topology::isReloadableListener).forEach(topology -> {
+ try {
+ log.redeployingTopologyOnGatewayConfigChange(topology.getName());
+ redeployTopology(topology);
+ } catch (RuntimeException e) {
+ log.failedToRedeployTopology(topology.getName(), e);
+ }
+ });
+ }
+
private boolean isRelevantService(Service service, String role, String name, String version) {
return service.getRole().equalsIgnoreCase(role)
&& (service.getName() == null || service.getName().equalsIgnoreCase(name) && (service.getVersion() == null || service.getVersion().equals(new Version(version))));
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/topology/builder/BeanPropertyTopologyBuilder.java b/gateway-server/src/main/java/org/apache/knox/gateway/topology/builder/BeanPropertyTopologyBuilder.java
index 0e08f06e4..7c5902217 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/topology/builder/BeanPropertyTopologyBuilder.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/topology/builder/BeanPropertyTopologyBuilder.java
@@ -30,6 +30,7 @@ public class BeanPropertyTopologyBuilder implements TopologyBuilder {
private String defaultService;
private boolean isGenerated;
private long redeployTime;
+ private boolean reloadableListener;
private List<Provider> providers;
private List<Service> services;
private List<Application> applications;
@@ -67,6 +68,15 @@ public class BeanPropertyTopologyBuilder implements TopologyBuilder {
return redeployTime;
}
+ public BeanPropertyTopologyBuilder reloadableListener(String reloadableListener) {
+ this.reloadableListener = Boolean.parseBoolean(reloadableListener);
+ return this;
+ }
+
+ public boolean isReloadableListener() {
+ return reloadableListener;
+ }
+
public BeanPropertyTopologyBuilder defaultService(String defaultService) {
this.defaultService = defaultService;
return this;
@@ -110,6 +120,7 @@ public class BeanPropertyTopologyBuilder implements TopologyBuilder {
topology.setDefaultServicePath(defaultService);
topology.setGenerated(isGenerated);
topology.setRedeployTime(redeployTime);
+ topology.setReloadableListener(reloadableListener);
for (Provider provider : providers) {
topology.addProvider(provider);
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/topology/xml/KnoxFormatXmlTopologyRules.java b/gateway-server/src/main/java/org/apache/knox/gateway/topology/xml/KnoxFormatXmlTopologyRules.java
index becdd3dfb..865197c18 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/topology/xml/KnoxFormatXmlTopologyRules.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/topology/xml/KnoxFormatXmlTopologyRules.java
@@ -37,6 +37,7 @@ public class KnoxFormatXmlTopologyRules extends AbstractRulesModule {
private static final String DEFAULT_SERVICE_TAG = "path";
private static final String GENERATED_TAG = "generated";
private static final String REDEPLOY_TIME_TAG = "redeployTime";
+ private static final String RELOADABLE_LISTENER_TAG = "reloadableListener";
private static final String APPLICATION_TAG = "application";
private static final String SERVICE_TAG = "service";
private static final String ROLE_TAG = "role";
@@ -66,6 +67,7 @@ public class KnoxFormatXmlTopologyRules extends AbstractRulesModule {
forPattern( ROOT_TAG + "/" + DEFAULT_SERVICE_TAG ).callMethod("defaultService").usingElementBodyAsArgument();
forPattern( ROOT_TAG + "/" + GENERATED_TAG ).callMethod("generated").usingElementBodyAsArgument();
forPattern( ROOT_TAG + "/" + REDEPLOY_TIME_TAG ).callMethod("redeployTime").usingElementBodyAsArgument();
+ forPattern( ROOT_TAG + "/" + RELOADABLE_LISTENER_TAG ).callMethod("reloadableListener").usingElementBodyAsArgument();
forPattern( ROOT_TAG + "/" + APPLICATION_TAG ).createObject().ofType( Application.class ).then().setNext( "addApplication" );
forPattern( ROOT_TAG + "/" + APPLICATION_TAG + "/" + ROLE_TAG ).setBeanProperty();
diff --git a/gateway-server/src/main/resources/conf/topology-v1.xsd b/gateway-server/src/main/resources/conf/topology-v1.xsd
index a828e72db..382467233 100644
--- a/gateway-server/src/main/resources/conf/topology-v1.xsd
+++ b/gateway-server/src/main/resources/conf/topology-v1.xsd
@@ -23,6 +23,7 @@ limitations under the License.
<h:element name="name" minOccurs="0" maxOccurs="1"/>
<h:element name="path" minOccurs="0" maxOccurs="1"/>
<h:element name="generated" type="h:boolean" minOccurs="0"/>
+ <h:element name="reloadableListener" type="h:boolean" minOccurs="0"/>
<h:element name="gateway" minOccurs="0" maxOccurs="1">
<h:complexType>
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
index e8ad52e9c..a142c19e4 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/GatewayServerTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/GatewayServerTest.java
@@ -138,4 +138,38 @@ public class GatewayServerTest {
GatewayServer.unregisterConfigChangeListener(listener);
}
}
+
+ @Test
+ public void testFailingConfigChangeListenerDoesNotBlockOthers() throws Exception {
+ GatewayConfigImpl config = EasyMock.createNiceMock(GatewayConfigImpl.class);
+ File configFile = folder.newFile("gateway-reloadable-failing-listener.xml");
+ try (PrintWriter writer = new PrintWriter(configFile, StandardCharsets.UTF_8)) {
+ writer.println("<configuration></configuration>");
+ }
+
+ final boolean[] notified = {false};
+ GatewayConfigChangeListener failing = (c) -> {
+ throw new IllegalStateException("listener failure");
+ };
+ GatewayConfigChangeListener listener = (c) -> notified[0] = true;
+
+ GatewayServer.registerConfigChangeListener(failing);
+ GatewayServer.registerConfigChangeListener(listener);
+
+ try {
+ Method refreshMethod = GatewayServer.class.getDeclaredMethod("refreshGatewayConfig", GatewayConfigImpl.class, Path.class);
+ refreshMethod.setAccessible(true);
+
+ Field lastReloadTimeField = GatewayServer.class.getDeclaredField("lastReloadTime");
+ lastReloadTimeField.setAccessible(true);
+ lastReloadTimeField.set(null, null);
+
+ refreshMethod.invoke(null, config, configFile.toPath());
+
+ assertTrue("Listener after the failing one should have been notified", notified[0]);
+ } finally {
+ GatewayServer.unregisterConfigChangeListener(failing);
+ GatewayServer.unregisterConfigChangeListener(listener);
+ }
+ }
}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/topology/DefaultTopologyServiceTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/topology/DefaultTopologyServiceTest.java
index e428d1e84..bf61974e2 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/topology/DefaultTopologyServiceTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/topology/DefaultTopologyServiceTest.java
@@ -39,6 +39,7 @@ import org.apache.knox.gateway.topology.Provider;
import org.apache.knox.gateway.topology.Topology;
import org.apache.knox.gateway.topology.TopologyEvent;
import org.apache.knox.gateway.topology.TopologyListener;
+import org.apache.knox.gateway.topology.validation.TopologyValidator;
import org.easymock.EasyMock;
import org.junit.Test;
@@ -830,6 +831,95 @@ public class DefaultTopologyServiceTest {
}
}
+ @Test
+ public void testOnlyReloadableListenerTopologiesRedeployedOnGatewayConfigChange() throws Exception {
+ final File dir = createDir();
+ try {
+ final File topologyDir = new File(dir, "topologies");
+ final long time = topologyDir.lastModified();
+ final String listening = "<topology>\n <reloadableListener>true</reloadableListener>\n"
+ + " <service>\n <role>WEBHDFS</role>\n <url>http://host-one:80/webhdfs</url>\n </service>\n</topology>\n";
+ final String notListening = "<topology>\n"
+ + " <service>\n <role>WEBHDFS</role>\n <url>http://host-two:80/webhdfs</url>\n </service>\n</topology>\n";
+ final File listeningFile = createFile(topologyDir, "listening.xml",
+ new ByteArrayInputStream(listening.getBytes(StandardCharsets.UTF_8)), time);
+ final File notListeningFile = createFile(topologyDir, "notlistening.xml",
+ new ByteArrayInputStream(notListening.getBytes(StandardCharsets.UTF_8)), time);
+
+ final GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+ EasyMock.expect(config.getGatewayTopologyDir()).andReturn(topologyDir.getAbsolutePath()).anyTimes();
+ EasyMock.expect(config.isTopologyValidationEnabled()).andReturn(true).anyTimes();
+ EasyMock.replay(config);
+
+ final DefaultTopologyService topologyService = new DefaultTopologyService();
+ topologyService.init(config, new HashMap<>());
+ topologyService.reloadTopologies();
+
+ final Map<String, Boolean> flags = new HashMap<>();
+ topologyService.getTopologies().forEach(t -> flags.put(t.getName(), t.isReloadableListener()));
+ assertEquals(Boolean.TRUE, flags.get("listening"));
+ assertEquals(Boolean.FALSE, flags.get("notlistening"));
+
+ topologyService.onGatewayConfigChanged(config);
+
+ assertTrue(FileUtils.readFileToString(listeningFile, StandardCharsets.UTF_8).contains("<redeployTime>"));
+ assertEquals(notListening, FileUtils.readFileToString(notListeningFile, StandardCharsets.UTF_8));
+ } finally {
+ FileUtils.deleteQuietly(dir);
+ }
+ }
+
+ @Test
+ public void testDeployTopologyPersistsReloadableListener() throws Exception {
+ final File dir = createDir();
+ try {
+ final File topologyDir = new File(dir, "topologies");
+ topologyDir.mkdirs();
+
+ final GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+ EasyMock.expect(config.getGatewayTopologyDir()).andReturn(topologyDir.getAbsolutePath()).anyTimes();
+ EasyMock.replay(config);
+
+ final DefaultTopologyService topologyService = new DefaultTopologyService();
+ topologyService.init(config, new HashMap<>());
+
+ topologyService.deployTopology(createTopologyForDeployment("listening", true));
+ topologyService.deployTopology(createTopologyForDeployment("notlistening", false));
+ topologyService.reloadTopologies();
+
+ final Map<String, Boolean> flags = new HashMap<>();
+ topologyService.getTopologies().forEach(t -> flags.put(t.getName(), t.isReloadableListener()));
+ assertEquals(Boolean.TRUE, flags.get("listening"));
+ assertEquals(Boolean.FALSE, flags.get("notlistening"));
+
+ final File listeningFile = new File(topologyDir, "listening.xml");
+ final File notListeningFile = new File(topologyDir, "notlistening.xml");
+ assertTrue(FileUtils.readFileToString(listeningFile, StandardCharsets.UTF_8).contains("<reloadableListener>true</reloadableListener>"));
+ assertFalse(FileUtils.readFileToString(notListeningFile, StandardCharsets.UTF_8).contains("reloadableListener"));
+
+ final TopologyValidator validator = new TopologyValidator(listeningFile.getAbsolutePath());
+ assertTrue(validator.getErrorString(), validator.validateTopology());
+ } finally {
+ FileUtils.deleteQuietly(dir);
+ }
+ }
+
+ private Topology createTopologyForDeployment(String name, boolean reloadableListener) {
+ final Topology topology = new Topology();
+ topology.setName(name);
+ topology.setReloadableListener(reloadableListener);
+ final Provider provider = new Provider();
+ provider.setRole("authentication");
+ provider.setName("Anonymous");
+ provider.setEnabled(true);
+ topology.addProvider(provider);
+ final org.apache.knox.gateway.topology.Service service = new org.apache.knox.gateway.topology.Service();
+ service.setRole("WEBHDFS");
+ service.addUrl("http://localhost:50070/webhdfs");
+ topology.addService(service);
+ return topology;
+ }
+
private class TestTopologyListener implements TopologyListener {
List<List<TopologyEvent>> events = new ArrayList<>();
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorFactoryTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorFactoryTest.java
index 985aa0006..8507d31c9 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorFactoryTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorFactoryTest.java
@@ -34,6 +34,7 @@ import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -103,6 +104,23 @@ public class SimpleDescriptorFactoryTest {
testParseSimpleDescriptorWithServicesAndApplications(FileType.YAML);
}
+ @Test
+ public void testParseReloadableListener() throws Exception {
+ assertTrue(parseDescriptorContent("json", "{\"provider-config-ref\":\"pc\",\"reloadable-listener\":true}").isReloadableListener());
+ assertFalse(parseDescriptorContent("json", "{\"provider-config-ref\":\"pc\"}").isReloadableListener());
+ assertTrue(parseDescriptorContent("yaml", "---\nprovider-config-ref: pc\nreloadable-listener: true\n").isReloadableListener());
+ assertFalse(parseDescriptorContent("yml", "---\nprovider-config-ref: pc\n").isReloadableListener());
+ }
+
+ private SimpleDescriptor parseDescriptorContent(String extension, String content) throws Exception {
+ final Path descriptor = Files.createTempFile("reloadable-listener-test", "." + extension);
+ try {
+ Files.write(descriptor, content.getBytes(StandardCharsets.UTF_8));
+ return SimpleDescriptorFactory.parse(descriptor.toString());
+ } finally {
+ Files.deleteIfExists(descriptor);
+ }
+ }
private void testParseSimpleDescriptor(FileType type, boolean provisionEncryptQueryStringCredential) throws Exception {
final String discoveryType = "AMBARI";
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandlerTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandlerTest.java
index 9bab863e7..c3d40c591 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandlerTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandlerTest.java
@@ -141,6 +141,46 @@ public class SimpleDescriptorHandlerTest {
doTestDiscoveryConfig(null, null, null, null, null);
}
+ @Test
+ public void testReloadableListenerWrittenToGeneratedTopology() throws Exception {
+ assertTrue(generateTopologyContent(true).contains("<reloadableListener>true</reloadableListener>"));
+ assertFalse(generateTopologyContent(false).contains("reloadableListener"));
+ }
+
+ private String generateTopologyContent(boolean reloadableListener) throws Exception {
+ GatewayConfig gc = EasyMock.createNiceMock(GatewayConfig.class);
+ EasyMock.expect(gc.getReadOnlyOverrideTopologyNames()).andReturn(Collections.emptyList()).anyTimes();
+ EasyMock.replay(gc);
+
+ File providerConfig = new File(System.getProperty("java.io.tmpdir"), "test-providers.xml");
+ FileUtils.write(providerConfig, TEST_PROVIDER_CONFIG, StandardCharsets.UTF_8);
+
+ SimpleDescriptor testDescriptor = EasyMock.createNiceMock(SimpleDescriptor.class);
+ EasyMock.expect(testDescriptor.getName()).andReturn("reloadablelistenerdescriptor").anyTimes();
+ EasyMock.expect(testDescriptor.getProviderConfig()).andReturn(providerConfig.getAbsolutePath()).anyTimes();
+ EasyMock.expect(testDescriptor.isReloadableListener()).andReturn(reloadableListener).anyTimes();
+ SimpleDescriptor.Service svc = EasyMock.createNiceMock(SimpleDescriptor.Service.class);
+ EasyMock.expect(svc.getName()).andReturn("KNOXTOKEN").anyTimes();
+ EasyMock.expect(svc.getURLs()).andReturn(Collections.emptyList()).anyTimes();
+ EasyMock.expect(svc.getParams()).andReturn(Collections.emptyMap()).anyTimes();
+ EasyMock.replay(svc);
+ EasyMock.expect(testDescriptor.getServices()).andReturn(Collections.singletonList(svc)).anyTimes();
+ EasyMock.replay(testDescriptor);
+
+ File destDir = new File(System.getProperty("java.io.tmpdir")).getCanonicalFile();
+ File topologyFile = null;
+ try {
+ Map<String, File> files = SimpleDescriptorHandler.handle(gc, testDescriptor, providerConfig.getParentFile(), destDir);
+ topologyFile = files.get("topology");
+ return FileUtils.readFileToString(topologyFile, StandardCharsets.UTF_8);
+ } finally {
+ providerConfig.delete();
+ if (topologyFile != null) {
+ topologyFile.delete();
+ }
+ }
+ }
+
private void doTestDiscoveryConfig(final String discoveryType,
final String address,
final String clusterName,
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/topology/validation/TopologyValidatorTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/topology/validation/TopologyValidatorTest.java
index 299027f27..bd22926d0 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/topology/validation/TopologyValidatorTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/topology/validation/TopologyValidatorTest.java
@@ -42,5 +42,9 @@ public class TopologyValidatorTest {
url = TestUtils.getResourceUrl( TopologyValidatorTest.class, "topology-valid-with-name.xml" );
validator = new TopologyValidator( url );
assertThat( validator.getErrorString(), validator.validateTopology(), is( true ) );
+
+ url = TestUtils.getResourceUrl( TopologyValidatorTest.class, "topology-valid-reloadable-listener.xml" );
+ validator = new TopologyValidator( url );
+ assertThat( validator.getErrorString(), validator.validateTopology(), is( true ) );
}
}
\ No newline at end of file
diff --git a/gateway-server/src/test/resources/org/apache/knox/gateway/topology/validation/TopologyValidatorTest/topology-valid-reloadable-listener.xml b/gateway-server/src/test/resources/org/apache/knox/gateway/topology/validation/TopologyValidatorTest/topology-valid-reloadable-listener.xml
new file mode 100644
index 000000000..081392b7b
--- /dev/null
+++ b/gateway-server/src/test/resources/org/apache/knox/gateway/topology/validation/TopologyValidatorTest/topology-valid-reloadable-listener.xml
@@ -0,0 +1,30 @@
+<!--
+ 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.
+-->
+<topology>
+ <generated>true</generated>
+ <reloadableListener>true</reloadableListener>
+ <gateway>
+ <provider>
+ <role>authentication</role>
+ <name>Anonymous</name>
+ <enabled>true</enabled>
+ </provider>
+ </gateway>
+ <service>
+ <role>KNOX</role>
+ </service>
+</topology>
diff --git a/gateway-service-admin/src/main/java/org/apache/knox/gateway/service/admin/beans/BeanConverter.java b/gateway-service-admin/src/main/java/org/apache/knox/gateway/service/admin/beans/BeanConverter.java
index a06ba7abf..9cb4b45d0 100644
--- a/gateway-service-admin/src/main/java/org/apache/knox/gateway/service/admin/beans/BeanConverter.java
+++ b/gateway-service-admin/src/main/java/org/apache/knox/gateway/service/admin/beans/BeanConverter.java
@@ -32,6 +32,7 @@ public class BeanConverter {
topologyResource.setUri(topology.getUri());
topologyResource.setGenerated(topology.isGenerated());
topologyResource.setRedeployTime(topology.getRedeployTime());
+ topologyResource.setReloadableListener(topology.isReloadableListener());
for ( org.apache.knox.gateway.topology.Provider provider : topology.getProviders() ) {
topologyResource.getProviders().add( getProvider(provider) );
}
@@ -52,6 +53,7 @@ public class BeanConverter {
deploymentTopology.setUri(topology.getUri());
deploymentTopology.setGenerated(topology.isGenerated());
deploymentTopology.setRedeployTime(topology.getRedeployTime());
+ deploymentTopology.setReloadableListener(topology.isReloadableListener());
for ( Provider provider : topology.getProviders() ) {
deploymentTopology.addProvider( getProvider(provider) );
}
diff --git a/gateway-service-admin/src/main/java/org/apache/knox/gateway/service/admin/beans/Topology.java b/gateway-service-admin/src/main/java/org/apache/knox/gateway/service/admin/beans/Topology.java
index 00f9e7d1f..1cf4f25ae 100644
--- a/gateway-service-admin/src/main/java/org/apache/knox/gateway/service/admin/beans/Topology.java
+++ b/gateway-service-admin/src/main/java/org/apache/knox/gateway/service/admin/beans/Topology.java
@@ -45,6 +45,9 @@ public class Topology {
@XmlElement(name="redeployTime")
private long redeployTime;
+ @XmlElement(name="reloadableListener")
+ private boolean reloadableListener;
+
@XmlElement(name="provider")
@XmlElementWrapper(name="gateway")
public List<Provider> providers;
@@ -106,6 +109,14 @@ public class Topology {
this.redeployTime = redeployTime;
}
+ public boolean isReloadableListener() {
+ return reloadableListener;
+ }
+
+ public void setReloadableListener(boolean reloadableListener) {
+ this.reloadableListener = reloadableListener;
+ }
+
public List<Service> getServices() {
if (services == null) {
services = new ArrayList<>();
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/topology/Topology.java b/gateway-spi/src/main/java/org/apache/knox/gateway/topology/Topology.java
index 003dd3965..f8d9176b5 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/topology/Topology.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/topology/Topology.java
@@ -39,6 +39,7 @@ public class Topology {
private long timestamp;
private boolean isGenerated;
private long redeployTime = -1L;
+ private boolean reloadableListener;
public List<Provider> providerList = new ArrayList<>();
private Map<String,Map<String,Provider>> providerMap = new HashMap<>();
public List<Service> services = new ArrayList<>();
@@ -102,6 +103,22 @@ public class Topology {
this.redeployTime = redeployTime;
}
+ public boolean isReloadableListener() {
+ return reloadableListener;
+ }
+
+ public void setReloadableListener(boolean reloadableListener) {
+ this.reloadableListener = reloadableListener;
+ }
+
+ public Boolean getReloadableListenerElement() {
+ return reloadableListener ? Boolean.TRUE : null;
+ }
+
+ public void setReloadableListenerElement(Boolean reloadableListener) {
+ this.reloadableListener = Boolean.TRUE.equals(reloadableListener);
+ }
+
public Collection<Service> getServices() {
return services;
}
diff --git a/gateway-spi/src/main/resources/org/apache/knox/gateway/topology/topology_binding-xml.xml b/gateway-spi/src/main/resources/org/apache/knox/gateway/topology/topology_binding-xml.xml
index 956387e29..52d115ad6 100644
--- a/gateway-spi/src/main/resources/org/apache/knox/gateway/topology/topology_binding-xml.xml
+++ b/gateway-spi/src/main/resources/org/apache/knox/gateway/topology/topology_binding-xml.xml
@@ -24,11 +24,14 @@ or more contributor license agreements. See the NOTICE file
element-form-default="QUALIFIED"/>
<java-types>
<java-type name="Topology" xml-accessor-type="NONE">
- <xml-type prop-order="name defaultServicePath providers services applications"/>
+ <xml-type prop-order="name defaultServicePath reloadableListenerElement providers services applications"/>
<xml-root-element/>
<java-attributes>
<xml-element java-attribute="name" name="name"/>
<xml-element java-attribute="defaultServicePath" name="path"/>
+ <xml-element java-attribute="reloadableListenerElement" name="reloadableListener">
+ <xml-access-methods get-method="getReloadableListenerElement" set-method="setReloadableListenerElement"/>
+ </xml-element>
<xml-elements java-attribute="providers">
<xml-element name="provider"/>
<xml-element-wrapper name="gateway"/>
diff --git a/gateway-topology-hadoop-xml/src/main/java/org/apache/knox/gateway/topology/hadoop/xml/HadoopXmlResourceParser.java b/gateway-topology-hadoop-xml/src/main/java/org/apache/knox/gateway/topology/hadoop/xml/HadoopXmlResourceParser.java
index 75f00fe62..c9869be30 100644
--- a/gateway-topology-hadoop-xml/src/main/java/org/apache/knox/gateway/topology/hadoop/xml/HadoopXmlResourceParser.java
+++ b/gateway-topology-hadoop-xml/src/main/java/org/apache/knox/gateway/topology/hadoop/xml/HadoopXmlResourceParser.java
@@ -64,6 +64,7 @@ public class HadoopXmlResourceParser {
private static final String CONFIG_NAME_DISCOVERY_CLUSTER = "cluster";
private static final String CONFIG_NAME_PROVIDER_CONFIG_REFERENCE = "providerConfigRef";
private static final String CONFIG_NAME_PROVISION_ENCRYPT_QUERY_STRING_CREDENTIAL = "provisionEncryptQueryStringCredential";
+ private static final String CONFIG_NAME_RELOADABLE_LISTENER = "reloadableListener";
private static final String CONFIG_NAME_APPLICATION_PREFIX = "app";
private static final String CONFIG_NAME_SERVICE_URL = "url";
private static final String CONFIG_NAME_SERVICE_VERSION = "version";
@@ -265,6 +266,9 @@ public class HadoopXmlResourceParser {
case CONFIG_NAME_PROVISION_ENCRYPT_QUERY_STRING_CREDENTIAL:
descriptor.setProvisionEncryptQueryStringCredential(Boolean.valueOf(parameterPairParts[1].trim()));
break;
+ case CONFIG_NAME_RELOADABLE_LISTENER:
+ descriptor.setReloadableListener(Boolean.parseBoolean(parameterPairParts[1].trim()));
+ break;
case CONFIG_NAME_SERVICES:
Arrays.asList(parameterPairParts[1].trim().split(",", -1)).forEach(serviceName -> descriptor.addService(emptyService(serviceName)));
break;
diff --git a/gateway-topology-hadoop-xml/src/test/java/org/apache/knox/gateway/topology/hadoop/xml/HadoopXmlResourceParserTest.java b/gateway-topology-hadoop-xml/src/test/java/org/apache/knox/gateway/topology/hadoop/xml/HadoopXmlResourceParserTest.java
index 587b3ceee..43ef191a2 100644
--- a/gateway-topology-hadoop-xml/src/test/java/org/apache/knox/gateway/topology/hadoop/xml/HadoopXmlResourceParserTest.java
+++ b/gateway-topology-hadoop-xml/src/test/java/org/apache/knox/gateway/topology/hadoop/xml/HadoopXmlResourceParserTest.java
@@ -218,6 +218,7 @@ public class HadoopXmlResourceParserTest {
assertEquals("Cluster 1", descriptor.getCluster());
assertEquals("topology1-provider", descriptor.getProviderConfig());
assertTrue(descriptor.isProvisionEncryptQueryStringCredential());
+ assertFalse(descriptor.isReloadableListener());
assertEquals(2, descriptor.getApplications().size());
assertApplication(descriptor, "knoxauth", Collections.singletonMap("param1.name", "param1.value"));
@@ -244,6 +245,7 @@ public class HadoopXmlResourceParserTest {
assertEquals("Cluster 2", descriptor.getCluster());
assertEquals("topology2-provider", descriptor.getProviderConfig());
assertFalse(descriptor.isProvisionEncryptQueryStringCredential());
+ assertTrue(descriptor.isReloadableListener());
assertTrue(descriptor.getApplications().isEmpty());
final Map<String, String> expectedServiceParameters = Stream.of(new String[][] { { "httpclient.connectionTimeout", "5m" }, { "httpclient.socketTimeout", "100m" }, })
diff --git a/gateway-topology-hadoop-xml/src/test/resources/testDescriptor.xml b/gateway-topology-hadoop-xml/src/test/resources/testDescriptor.xml
index bf35934fc..7530aab19 100644
--- a/gateway-topology-hadoop-xml/src/test/resources/testDescriptor.xml
+++ b/gateway-topology-hadoop-xml/src/test/resources/testDescriptor.xml
@@ -40,6 +40,7 @@ limitations under the License.
cluster=Cluster 2#
providerConfigRef=topology2-provider#
provisionEncryptQueryStringCredential=false#
+ reloadableListener=true#
ATLAS-API:url=http://localhost:456#
ATLAS-API:httpclient.connectionTimeout=5m#
ATLAS-API:httpclient.socketTimeout=100m#
diff --git a/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptor.java b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptor.java
index 2aa75b3c0..a37b806f1 100644
--- a/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptor.java
+++ b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptor.java
@@ -44,6 +44,8 @@ public interface SimpleDescriptor {
boolean isProvisionEncryptQueryStringCredential();
+ boolean isReloadableListener();
+
List<Service> getServices();
Service getService(String serviceName);
diff --git a/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandler.java b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandler.java
index b6bbc3270..9581bc306 100644
--- a/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandler.java
+++ b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandler.java
@@ -468,6 +468,10 @@ public class SimpleDescriptorHandler {
// KNOX-1105 Indicate that this topology was auto-generated
sw.write(" <generated>true</generated>\n");
+ if (desc.isReloadableListener()) {
+ sw.write(" <reloadableListener>true</reloadableListener>\n");
+ }
+
// Incorporate the externalized provider configuration content into the topology descriptor
sw.write(" <gateway>\n");
for (ProviderConfiguration.Provider provider : providerConfiguration.getProviders()) {
diff --git a/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorImpl.java b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorImpl.java
index 1feec15b9..68d5d1cfb 100644
--- a/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorImpl.java
+++ b/gateway-topology-simple/src/main/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorImpl.java
@@ -51,6 +51,9 @@ public class SimpleDescriptorImpl implements SimpleDescriptor {
@JsonProperty("provision-encrypt-query-string-credential")
private boolean provisionEncryptQueryStringCredential = true;
+ @JsonProperty("reloadable-listener")
+ private boolean reloadableListener;
+
@JsonProperty("services")
private List<Service> services;
@@ -140,6 +143,15 @@ public class SimpleDescriptorImpl implements SimpleDescriptor {
this.provisionEncryptQueryStringCredential = provisionEncryptQueryStringCredential;
}
+ @Override
+ public boolean isReloadableListener() {
+ return reloadableListener;
+ }
+
+ public void setReloadableListener(boolean reloadableListener) {
+ this.reloadableListener = reloadableListener;
+ }
+
public void addService(Service service) {
if (services == null) {
services = new ArrayList<>();