This is an automated email from the ASF dual-hosted git repository.
amagyar 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 6d3f960b5 KNOX-2835 - SQL DB based topology monitor
6d3f960b5 is described below
commit 6d3f960b54fee520b2311f614a073cc8c2592403
Author: Attila Magyar <[email protected]>
AuthorDate: Tue Nov 15 11:24:01 2022 +0100
KNOX-2835 - SQL DB based topology monitor
---
.../org/apache/knox/gateway/GatewayMessages.java | 40 ++++
.../gateway/config/impl/GatewayConfigImpl.java | 8 +
.../services/token/impl/TokenStateDatabase.java | 30 +--
.../topology/impl/DefaultTopologyService.java | 65 ++-----
.../monitor/RemoteConfigurationMonitorFactory.java | 73 -------
.../RemoteConfigurationMonitorServiceFactory.java | 75 ++++++++
...va => ZkRemoteConfigurationMonitorService.java} | 90 +++++++--
.../db/DbRemoteConfigurationMonitorService.java | 149 ++++++++++++++
.../topology/monitor/db/LocalDirectory.java | 85 ++++++++
.../RemoteConfig.java} | 30 ++-
.../topology/monitor/db/RemoteConfigDatabase.java | 150 +++++++++++++++
.../org/apache/knox/gateway/util/JDBCUtils.java | 28 +++
...logy.monitor.RemoteConfigurationMonitorProvider | 19 --
.../main/resources/createKnoxProvidersTable.sql | 21 ++
.../resources/createKnoxTokenDescriptorsTable.sql | 21 ++
.../services/AbstractGatewayServicesTest.java | 3 +-
.../token/impl/JDBCTokenStateServiceTest.java | 13 +-
...> ZkRemoteConfigurationMonitorServiceTest.java} | 4 +-
.../monitor/ZooKeeperConfigurationMonitorTest.java | 2 +-
.../DbRemoteConfigurationMonitorServiceTest.java | 213 +++++++++++++++++++++
.../topology/monitor/db/LocalDirectoryTest.java | 106 ++++++++++
.../monitor/db/RemoteConfigDatabaseTest.java | 141 ++++++++++++++
.../org/apache/knox/gateway/GatewayTestConfig.java | 5 +
.../apache/knox/gateway/config/GatewayConfig.java | 2 +
.../apache/knox/gateway/services/ServiceType.java | 3 +-
.../monitor/RemoteConfigurationMonitor.java | 15 +-
.../RemoteConfigurationMonitorProvider.java | 34 ----
.../monitor/RemoteConfigurationMonitorTest.java | 21 +-
28 files changed, 1183 insertions(+), 263 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 aa2130fbc..428f40f8e 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
@@ -19,6 +19,7 @@ package org.apache.knox.gateway;
import java.io.File;
import java.net.URI;
+import java.time.Instant;
import java.util.Date;
import java.util.Map;
import java.util.Set;
@@ -29,6 +30,9 @@ import org.apache.knox.gateway.i18n.messages.MessageLevel;
import org.apache.knox.gateway.i18n.messages.Messages;
import org.apache.knox.gateway.i18n.messages.StackTrace;
import org.apache.knox.gateway.services.security.KeystoreServiceException;
+import org.apache.knox.gateway.topology.monitor.db.LocalDirectory;
+
+import java.io.IOException;
@Messages(logger="org.apache.knox.gateway")
public interface GatewayMessages {
@@ -727,4 +731,40 @@ public interface GatewayMessages {
@Message(level = MessageLevel.WARN, text =
"InMemoryConcurrentSessionVerifier is used and privileged user group is not
configured! Non-privileged limit applies to all users (except the unlimited
group).")
void privilegedUserGroupIsNotConfigured();
+
+ @Message(level = MessageLevel.WARN,
+ text = "Could not create local provider/descriptor config at {0},
cause: {1}")
+ void errorSynchronizingLocalProviderDescriptor(LocalDirectory dir, Exception
cause);
+
+ @Message(level = MessageLevel.INFO,
+ text = "Downloading provider/descriptor {0} to {1} from database")
+ void downloadingProviderDescriptor(String name, LocalDirectory localDir);
+
+ @Message(level = MessageLevel.INFO,
+ text = "Deleting provider/descriptor {0} from directory: {1}")
+ void deletingProviderDescriptor(String name, LocalDirectory localDir);
+
+ @Message(level = MessageLevel.WARN,
+ text = "Cannot create local directory: {0} cause: {1}")
+ void cannotCreateLocalDirectory(File base, IOException e);
+
+ @Message(level = MessageLevel.INFO,
+ text = "DB remote configuration monitor. Interval = {0} seconds")
+ void startingDbRemoteConfigurationMonitor(long intervalSeconds);
+
+ @Message(level = MessageLevel.WARN,
+ text = "Can not sync local file system from DB, cause = {0}")
+ void errorWhileSyncingLocalFileSystem(Exception e);
+
+ @Message(level = MessageLevel.DEBUG,
+ text = "Remote configuration sync completed at: {0}")
+ void remoteConfigurationSyncCompleted(Instant lastSyncTime);
+
+ @Message(level = MessageLevel.DEBUG,
+ text = "Deleting local {0} with name {1}")
+ void deletingLocalDescriptorProvider(String type, String name);
+
+ @Message(level = MessageLevel.DEBUG,
+ text = "Creating local {0} with name {1}")
+ void creatingLocalDescriptorProvider(String type, String 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 43c92351c..71e839dc3 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
@@ -242,6 +242,9 @@ public class GatewayConfigImpl extends Configuration
implements GatewayConfig {
static final String REMOTE_CONFIG_MONITOR_CLIENT_ALLOW_READ_ACCESS =
REMOTE_CONFIG_MONITOR_CLIENT_NAME + ".allowUnauthenticatedReadAccess";
+ private static final String
REMOTE_CONFIG_MONITOR_DB_POLLING_INTERVAL_SECONDS = GATEWAY_CONFIG_FILE_PREFIX
+ ".remote.config.monitor.db.poll.interval.seconds";
+ private static final long
REMOTE_CONFIG_MONITOR_DB_POLLING_INTERVAL_SECONDS_DEFAULT = 15;
+
/* @since 1.1.0 Default discovery configuration */
static final String DEFAULT_DISCOVERY_ADDRESS = GATEWAY_CONFIG_FILE_PREFIX +
".discovery.default.address";
static final String DEFAULT_DISCOVERY_CLUSTER = GATEWAY_CONFIG_FILE_PREFIX +
".discovery.default.cluster";
@@ -1426,6 +1429,11 @@ public class GatewayConfigImpl extends Configuration
implements GatewayConfig {
return nonPrivilegedUsers == null ? Collections.emptySet() : new
HashSet<>(nonPrivilegedUsers);
}
+ @Override
+ public long getDbRemoteConfigMonitorPollingInterval() {
+ return getLong(REMOTE_CONFIG_MONITOR_DB_POLLING_INTERVAL_SECONDS,
REMOTE_CONFIG_MONITOR_DB_POLLING_INTERVAL_SECONDS_DEFAULT);
+ }
+
@Override
public long getConcurrentSessionVerifierExpiredTokensCleaningPeriod() {
return
getLong(GATEWAY_SESSION_VERIFICATION_EXPIRED_TOKENS_CLEANING_PERIOD,
GATEWAY_SESSION_VERIFICATION_EXPIRED_TOKENS_CLEANING_PERIOD_DEFAULT);
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
index 0b705141c..b5f5f0abd 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
@@ -19,26 +19,22 @@ package org.apache.knox.gateway.services.token.impl;
import static java.nio.charset.StandardCharsets.UTF_8;
-import java.io.InputStream;
import java.sql.Connection;
-import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
-import java.sql.Statement;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.sql.DataSource;
import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.io.IOUtils;
import org.apache.knox.gateway.services.security.token.KnoxToken;
import org.apache.knox.gateway.services.security.token.TokenMetadata;
+import org.apache.knox.gateway.util.JDBCUtils;
public class TokenStateDatabase {
private static final String TOKENS_TABLE_CREATE_SQL_FILE_NAME =
"createKnoxTokenDatabaseTable.sql";
@@ -72,28 +68,8 @@ public class TokenStateDatabase {
}
private void createTableIfNotExists(String tableName, String
createSqlFileName) throws Exception {
- if (!isTableExists(tableName)) {
- createTable(createSqlFileName);
- }
- }
-
- private boolean isTableExists(String tableName) throws SQLException {
- boolean exists = false;
- try (Connection connection = dataSource.getConnection()) {
- final DatabaseMetaData dbMetadata = connection.getMetaData();
- final String tableNameToCheck = dbMetadata.storesUpperCaseIdentifiers()
? tableName : tableName.toLowerCase(Locale.ROOT);
- try (ResultSet tables = dbMetadata.getTables(connection.getCatalog(),
null, tableNameToCheck, null)) {
- exists = tables.next();
- }
- }
- return exists;
- }
-
- private void createTable(String createSqlFileName) throws Exception {
- final InputStream is =
TokenStateDatabase.class.getClassLoader().getResourceAsStream(createSqlFileName);
- final String createTableSql = IOUtils.toString(is, UTF_8);
- try (Connection connection = dataSource.getConnection(); Statement
createTableStatment = connection.createStatement();) {
- createTableStatment.execute(createTableSql);
+ if (!JDBCUtils.isTableExists(tableName, dataSource)) {
+ JDBCUtils.createTable(createSqlFileName, dataSource,
TokenStateDatabase.class.getClassLoader());
}
}
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 23598170a..223fdd4d4 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
@@ -39,7 +39,6 @@ import
org.apache.knox.gateway.service.definition.ServiceDefinitionChangeListene
import org.apache.knox.gateway.services.GatewayServices;
import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.ServiceType;
-import
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClient;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.topology.TopologyService;
import org.apache.knox.gateway.services.topology.monitor.DescriptorsMonitor;
@@ -54,8 +53,8 @@ import org.apache.knox.gateway.topology.TopologyProvider;
import org.apache.knox.gateway.topology.Version;
import org.apache.knox.gateway.topology.discovery.ClusterConfigurationMonitor;
import org.apache.knox.gateway.topology.discovery.ServiceDiscovery;
+import
org.apache.knox.gateway.topology.monitor.RemoteConfigurationMonitorServiceFactory;
import org.apache.knox.gateway.topology.monitor.RemoteConfigurationMonitor;
-import
org.apache.knox.gateway.topology.monitor.RemoteConfigurationMonitorFactory;
import org.apache.knox.gateway.topology.simple.SimpleDescriptor;
import org.apache.knox.gateway.topology.simple.SimpleDescriptorFactory;
import org.apache.knox.gateway.topology.validation.TopologyValidator;
@@ -419,14 +418,8 @@ public class DefaultTopologyService extends
FileAlterationListenerAdaptor implem
// If the remote configuration registry is being employed, persist it
there also
if (remoteMonitor != null) {
- RemoteConfigurationRegistryClient client = remoteMonitor.getClient();
- if (client != null) {
- String entryPath = "/knox/config/shared-providers/" + name;
- client.createEntry(entryPath, content);
- result = (client.getEntryData(entryPath) != null);
- }
+ remoteMonitor.createProvider(name, content);
}
-
return result;
}
@@ -465,7 +458,10 @@ public class DefaultTopologyService extends
FileAlterationListenerAdaptor implem
// If the remote config monitor is configured, attempt to delete the
provider configuration from the remote
// registry, even if it does not exist locally.
- deleteRemoteEntry("/knox/config/shared-providers", name);
+ if (remoteMonitor != null) {
+ // If the remote config monitor is configured, delete the descriptor
from the remote registry
+ remoteMonitor.deleteProvider(providerConfig.getName()); // use file
name with extension
+ }
// Whether the remote configuration registry is being employed or not,
delete the local file if it exists
result = providerConfig == null || !providerConfig.exists() ||
providerConfig.delete();
@@ -486,12 +482,7 @@ public class DefaultTopologyService extends
FileAlterationListenerAdaptor implem
// If the remote configuration registry is being employed, persist it
there also
if (remoteMonitor != null) {
- RemoteConfigurationRegistryClient client = remoteMonitor.getClient();
- if (client != null) {
- String entryPath = "/knox/config/descriptors/" + name;
- client.createEntry(entryPath, content);
- result = (client.getEntryData(entryPath) != null);
- }
+ return remoteMonitor.createDescriptor(name, content);
}
return result;
@@ -512,8 +503,10 @@ public class DefaultTopologyService extends
FileAlterationListenerAdaptor implem
public boolean deleteDescriptor(String name) {
boolean result;
- // If the remote config monitor is configured, delete the descriptor from
the remote registry
- deleteRemoteEntry("/knox/config/descriptors", name);
+ if (remoteMonitor != null) {
+ // If the remote config monitor is configured, delete the descriptor
from the remote registry
+ remoteMonitor.deleteDescriptor(name);
+ }
// Whether the remote configuration registry is being employed or not,
delete the local file
File descriptor = getExistingFile(descriptorsDirectory, name);
@@ -661,44 +654,14 @@ public class DefaultTopologyService extends
FileAlterationListenerAdaptor implem
log.configuredMonitoringProviderConfigChangesInDirectory(sharedProvidersDirectory.getAbsolutePath());
// Initialize the remote configuration monitor, if it has been configured
- remoteMonitor = RemoteConfigurationMonitorFactory.get(config);
+ RemoteConfigurationMonitorServiceFactory provider = new
RemoteConfigurationMonitorServiceFactory();
+ remoteMonitor = (RemoteConfigurationMonitor) provider.create(gwServices,
ServiceType.REMOTE_CONFIGURATION_MONITOR, config, Collections.emptyMap());
+
} catch (Exception e) {
throw new ServiceLifecycleException(e.getMessage(), e);
}
}
- /**
- * Delete the entry in the remote configuration registry, which matches the
specified resource name.
- *
- * @param entryParent The remote registry path in which the entry exists.
- * @param name The name of the entry (typically without any file
extension).
- *
- * @return true, if the entry is deleted, or did not exist; otherwise, false.
- */
- private boolean deleteRemoteEntry(String entryParent, String name) {
- boolean result = true;
-
- if (remoteMonitor != null) {
- RemoteConfigurationRegistryClient client = remoteMonitor.getClient();
- if (client != null) {
- List<String> existingProviderConfigs =
client.listChildEntries(entryParent);
- for (String entryName : existingProviderConfigs) {
- if (FilenameUtils.getBaseName(entryName).equals(name)) {
- String entryPath = entryParent + "/" + entryName;
- client.deleteEntry(entryPath);
- result = !client.entryExists(entryPath);
- if (!result) {
- log.failedToDeletedRemoteConfigFile("descriptor", name);
- }
- break;
- }
- }
- }
- }
-
- return result;
- }
-
/**
* Utility method for listing the files in the specified directory.
* This method is "nicer" than the File#listFiles() because it will not
return null.
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitorFactory.java
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitorFactory.java
deleted file mode 100644
index 15ab3f202..000000000
---
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitorFactory.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
- * 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.topology.monitor;
-
-import org.apache.knox.gateway.GatewayMessages;
-import org.apache.knox.gateway.GatewayServer;
-import org.apache.knox.gateway.config.GatewayConfig;
-import org.apache.knox.gateway.i18n.messages.MessagesFactory;
-import org.apache.knox.gateway.services.ServiceType;
-import org.apache.knox.gateway.services.GatewayServices;
-import
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClientService;
-
-import java.util.ServiceLoader;
-
-public class RemoteConfigurationMonitorFactory {
- private static final GatewayMessages log =
MessagesFactory.get(GatewayMessages.class);
-
- private static RemoteConfigurationRegistryClientService
remoteConfigRegistryClientService;
-
- static synchronized void
setClientService(RemoteConfigurationRegistryClientService clientService) {
- remoteConfigRegistryClientService = clientService;
- }
-
- private static synchronized RemoteConfigurationRegistryClientService
getClientService() {
- if (remoteConfigRegistryClientService == null) {
- GatewayServices services = GatewayServer.getGatewayServices();
- if (services != null) {
- remoteConfigRegistryClientService =
services.getService(ServiceType.REMOTE_REGISTRY_CLIENT_SERVICE);
- }
- }
-
- return remoteConfigRegistryClientService;
- }
-
- /**
- *
- * @param config The GatewayConfig
- *
- * @return The first RemoteConfigurationMonitor extension that is found.
- */
- public static RemoteConfigurationMonitor get(GatewayConfig config) {
- RemoteConfigurationMonitor rcm = null;
-
- ServiceLoader<RemoteConfigurationMonitorProvider> providers =
-
ServiceLoader.load(RemoteConfigurationMonitorProvider.class);
- for (RemoteConfigurationMonitorProvider provider : providers) {
- try {
- rcm = provider.newInstance(config, getClientService());
- if (rcm != null) {
- break;
- }
- } catch (Exception e) {
-
log.remoteConfigurationMonitorInitFailure(e.getLocalizedMessage(), e);
- }
- }
-
- return rcm;
- }
-}
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitorServiceFactory.java
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitorServiceFactory.java
new file mode 100644
index 000000000..80bb5f9ca
--- /dev/null
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitorServiceFactory.java
@@ -0,0 +1,75 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.topology.monitor;
+
+import java.io.File;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.ServiceType;
+import org.apache.knox.gateway.services.factory.AbstractServiceFactory;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.AliasServiceException;
+import
org.apache.knox.gateway.topology.monitor.db.DbRemoteConfigurationMonitorService;
+import org.apache.knox.gateway.topology.monitor.db.LocalDirectory;
+import org.apache.knox.gateway.topology.monitor.db.RemoteConfigDatabase;
+import org.apache.knox.gateway.util.JDBCUtils;
+
+
+public class RemoteConfigurationMonitorServiceFactory extends
AbstractServiceFactory {
+ @Override
+ protected RemoteConfigurationMonitor createService(GatewayServices
gatewayServices,
+ ServiceType serviceType,
+ GatewayConfig gatewayConfig,
+ Map<String, String> options,
+ String implementation) throws
ServiceLifecycleException {
+ RemoteConfigurationMonitor service = null;
+ if (matchesImplementation(implementation,
ZkRemoteConfigurationMonitorService.class)) {
+ service = new ZkRemoteConfigurationMonitorService(gatewayConfig,
gatewayServices.getService(ServiceType.REMOTE_REGISTRY_CLIENT_SERVICE));
+ } else if (matchesImplementation(implementation,
DbRemoteConfigurationMonitorService.class)) {
+ service = createDbBasedMonitor(gatewayConfig,
getAliasService(gatewayServices));
+ }
+ return service;
+ }
+
+ private DbRemoteConfigurationMonitorService
createDbBasedMonitor(GatewayConfig config, AliasService aliasService) throws
ServiceLifecycleException {
+ try {
+ RemoteConfigDatabase db = new
RemoteConfigDatabase(JDBCUtils.getDataSource(config, aliasService));
+ LocalDirectory descriptorDir = new LocalDirectory(new
File(config.getGatewayDescriptorsDir()));
+ LocalDirectory providerDir = new LocalDirectory(new
File(config.getGatewayProvidersConfigDir()));
+ return new DbRemoteConfigurationMonitorService(
+ db, providerDir, descriptorDir,
config.getDbRemoteConfigMonitorPollingInterval());
+ } catch (SQLException | AliasServiceException e) {
+ throw new ServiceLifecycleException("Cannot create
DbRemoteConfigurationMonitor", e);
+ }
+ }
+
+ @Override
+ protected ServiceType getServiceType() {
+ return ServiceType.REMOTE_CONFIGURATION_MONITOR;
+ }
+
+ @Override
+ protected Collection<String> getKnownImplementations() {
+ return
Arrays.asList(ZkRemoteConfigurationMonitorService.class.getName(),
DbRemoteConfigurationMonitorService.class.getName());
+ }
+}
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/DefaultRemoteConfigurationMonitor.java
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/ZkRemoteConfigurationMonitorService.java
similarity index 74%
rename from
gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/DefaultRemoteConfigurationMonitor.java
rename to
gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/ZkRemoteConfigurationMonitorService.java
index 90663fc0a..29a0b6c44 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/DefaultRemoteConfigurationMonitor.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/ZkRemoteConfigurationMonitorService.java
@@ -17,9 +17,11 @@
package org.apache.knox.gateway.topology.monitor;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
import org.apache.knox.gateway.GatewayMessages;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
import
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClient;
import
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClient.ChildEntryListener;
import
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClient.EntryListener;
@@ -32,8 +34,9 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
-class DefaultRemoteConfigurationMonitor implements RemoteConfigurationMonitor {
+class ZkRemoteConfigurationMonitorService implements
RemoteConfigurationMonitor {
private static final String NODE_KNOX = "/knox";
private static final String NODE_KNOX_CONFIG = NODE_KNOX + "/config";
@@ -114,8 +117,8 @@ class DefaultRemoteConfigurationMonitor implements
RemoteConfigurationMonitor {
* @param config The gateway configuration
* @param registryClientService The service from which the remote registry
client should be acquired.
*/
- DefaultRemoteConfigurationMonitor(GatewayConfig
config,
- RemoteConfigurationRegistryClientService
registryClientService) {
+ ZkRemoteConfigurationMonitorService(GatewayConfig
config,
+
RemoteConfigurationRegistryClientService registryClientService) {
this.providersDir = new File(config.getGatewayProvidersConfigDir());
this.descriptorsDir = new File(config.getGatewayDescriptorsDir());
@@ -138,12 +141,10 @@ class DefaultRemoteConfigurationMonitor implements
RemoteConfigurationMonitor {
}
@Override
- public RemoteConfigurationRegistryClient getClient() {
- return client;
- }
+ public void init(GatewayConfig config, Map<String, String> options) throws
ServiceLifecycleException {}
@Override
- public void start() throws Exception {
+ public void start() throws ServiceLifecycleException {
if (client == null) {
throw new IllegalStateException("Failed to acquire a remote
configuration registry client.");
}
@@ -166,10 +167,14 @@ class DefaultRemoteConfigurationMonitor implements
RemoteConfigurationMonitor {
for (String providerConfig : providerConfigs) {
File localFile = new File(providersDir, providerConfig);
- byte[] remoteContent = client.getEntryData(NODE_KNOX_PROVIDERS
+ "/" + providerConfig).getBytes(StandardCharsets.UTF_8);
- if (!localFile.exists() || !Arrays.equals(remoteContent,
FileUtils.readFileToByteArray(localFile))) {
- FileUtils.writeByteArrayToFile(localFile, remoteContent);
- log.downloadedRemoteConfigFile(providersDir.getName(),
providerConfig);
+ try {
+ byte[] remoteContent =
client.getEntryData(NODE_KNOX_PROVIDERS + "/" +
providerConfig).getBytes(StandardCharsets.UTF_8);
+ if (!localFile.exists() || !Arrays.equals(remoteContent,
FileUtils.readFileToByteArray(localFile))) {
+ FileUtils.writeByteArrayToFile(localFile,
remoteContent);
+ log.downloadedRemoteConfigFile(providersDir.getName(),
providerConfig);
+ }
+ } catch (IOException e) {
+ throw new ServiceLifecycleException("Exception while
downloading remote configs from zookeeper", e);
}
}
}
@@ -181,20 +186,69 @@ class DefaultRemoteConfigurationMonitor implements
RemoteConfigurationMonitor {
throw new IllegalStateException("Unable to access remote path: " +
NODE_KNOX_DESCRIPTORS);
}
- // Register a listener for provider config znode additions/removals
- client.addChildEntryListener(NODE_KNOX_PROVIDERS, new
ConfigDirChildEntryListener(providersDir));
+ try {
+ // Register a listener for provider config znode additions/removals
+ client.addChildEntryListener(NODE_KNOX_PROVIDERS, new
ConfigDirChildEntryListener(providersDir));
- // Register a listener for descriptor znode additions/removals
- client.addChildEntryListener(NODE_KNOX_DESCRIPTORS, new
ConfigDirChildEntryListener(descriptorsDir));
+ // Register a listener for descriptor znode additions/removals
+ client.addChildEntryListener(NODE_KNOX_DESCRIPTORS, new
ConfigDirChildEntryListener(descriptorsDir));
+ } catch (Exception e) {
+ throw new ServiceLifecycleException("Exception while registering
provider/descriptor znode listeners", e);
+ }
log.monitoringRemoteConfigurationSource(monitorSource);
}
@Override
- public void stop() throws Exception {
- client.removeEntryListener(NODE_KNOX_PROVIDERS);
- client.removeEntryListener(NODE_KNOX_DESCRIPTORS);
+ public void stop() throws ServiceLifecycleException {
+ try {
+ client.removeEntryListener(NODE_KNOX_PROVIDERS);
+ client.removeEntryListener(NODE_KNOX_DESCRIPTORS);
+ } catch (Exception e) {
+ throw new ServiceLifecycleException("Exception while stopping: " +
getClass().getName(), e);
+ }
+ }
+
+ @Override
+ public boolean createProvider(String name, String content) {
+ String entryPath = "/knox/config/shared-providers/" + name;
+ client.createEntry(entryPath, content);
+ return (client.getEntryData(entryPath) != null);
+ }
+
+ @Override
+ public boolean createDescriptor(String name, String content) {
+ String entryPath = "/knox/config/descriptors/" + name;
+ client.createEntry(entryPath, content);
+ return (client.getEntryData(entryPath) != null);
+ }
+
+ @Override
+ public boolean deleteProvider(String name) {
+ return deleteEntry("/knox/config/descriptors", name);
+ }
+
+ @Override
+ public boolean deleteDescriptor(String name) {
+ return deleteEntry("/knox/config/shared-providers", name);
+ }
+
+ private boolean deleteEntry(String entryParent, String name) {
+ boolean result = false;
+ List<String> existingProviderConfigs =
client.listChildEntries(entryParent);
+ for (String entryName : existingProviderConfigs) {
+ if (FilenameUtils.getBaseName(entryName).equals(name)) {
+ String entryPath = entryParent + "/" + entryName;
+ client.deleteEntry(entryPath);
+ result = !client.entryExists(entryPath);
+ if (!result) {
+ log.failedToDeletedRemoteConfigFile("descriptor", name);
+ }
+ break;
+ }
+ }
+ return result;
}
private void ensureEntries() {
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/DbRemoteConfigurationMonitorService.java
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/DbRemoteConfigurationMonitorService.java
new file mode 100644
index 000000000..d48ded957
--- /dev/null
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/DbRemoteConfigurationMonitorService.java
@@ -0,0 +1,149 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.topology.monitor.db;
+
+import static java.util.stream.Collectors.toSet;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.knox.gateway.GatewayMessages;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.topology.monitor.RemoteConfigurationMonitor;
+
+public class DbRemoteConfigurationMonitorService implements
RemoteConfigurationMonitor {
+ private static final GatewayMessages LOG =
MessagesFactory.get(GatewayMessages.class);
+ public static final int OFFSET_SECONDS = 5;
+ private final RemoteConfigDatabase db;
+ private final LocalDirectory providersDir;
+ private final LocalDirectory descriptorsDir;
+ private long intervalSeconds;
+ private final ScheduledExecutorService executor;
+ private Instant lastSyncTime;
+
+ public DbRemoteConfigurationMonitorService(RemoteConfigDatabase db,
LocalDirectory providersDir, LocalDirectory descriptorsDir, long
intervalSeconds) {
+ this.db = db;
+ this.providersDir = providersDir;
+ this.descriptorsDir = descriptorsDir;
+ this.executor = Executors.newSingleThreadScheduledExecutor();
+ this.intervalSeconds = intervalSeconds;
+ }
+
+ @Override
+ public void init(GatewayConfig config, Map<String, String> options) throws
ServiceLifecycleException {}
+
+ @Override
+ public void start() throws ServiceLifecycleException {
+ LOG.startingDbRemoteConfigurationMonitor(intervalSeconds);
+ executor.scheduleWithFixedDelay(this::sync, intervalSeconds,
intervalSeconds, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void stop() throws ServiceLifecycleException {
+ executor.shutdown();
+ }
+
+ @Override
+ public boolean createProvider(String name, String content) {
+ LOG.creatingLocalDescriptorProvider("provider", name);
+ return db.putProvider(name, content);
+ }
+
+ @Override
+ public boolean createDescriptor(String name, String content) {
+ LOG.creatingLocalDescriptorProvider("descriptor", name);
+ return db.putDescriptor(name, content);
+ }
+
+ @Override
+ public boolean deleteProvider(String name) {
+ LOG.deletingLocalDescriptorProvider("provider", name);
+ return db.deleteProvider(name);
+ }
+
+ @Override
+ public boolean deleteDescriptor(String name) {
+ LOG.deletingLocalDescriptorProvider("descriptor", name);
+ return db.deleteDescriptor(name);
+ }
+
+ public void sync() {
+ try {
+ syncLocalWithRemote(db.selectProviders(), providersDir);
+ syncLocalWithRemote(db.selectDescriptors(), descriptorsDir);
+ lastSyncTime = Instant.now();
+ LOG.remoteConfigurationSyncCompleted(lastSyncTime);
+ } catch (Exception e) {
+ LOG.errorWhileSyncingLocalFileSystem(e);
+ }
+ }
+
+ private void syncLocalWithRemote(List<RemoteConfig> remoteConfigs,
LocalDirectory localDir) {
+ createOrUpdateLocalFiles(remoteConfigs, localDir);
+ deleteLocalFiles(remoteConfigs, localDir);
+ }
+
+ private void createOrUpdateLocalFiles(List<RemoteConfig> remoteConfigs,
LocalDirectory localDir) {
+ Set<String> localFiles = localDir.list();
+ for (RemoteConfig remoteConfig : remoteConfigs) {
+ try {
+ String remoteContent = remoteConfig.getContent();
+ if (!localFiles.contains(remoteConfig.getName())) {
+ // if file does not exist locally, create it
+ LOG.downloadingProviderDescriptor(remoteConfig.getName(), localDir);
+ localDir.writeFile(remoteConfig.getName(), remoteContent);
+ } else if (shouldUpdateContent(remoteConfig, localDir)) {
+ // exists locally, overwrite content only if necessary
+ LOG.downloadingProviderDescriptor(remoteConfig.getName(),
localDir);
+ localDir.writeFile(remoteConfig.getName(), remoteContent);
+ }
+ } catch (IOException e) {
+ LOG.errorSynchronizingLocalProviderDescriptor(localDir, e);
+ }
+ }
+ }
+
+ private boolean shouldUpdateContent(RemoteConfig remoteConfig,
LocalDirectory localDir) throws IOException {
+ if (lastSyncTime == null || remoteConfig.getLastModified() // remote
changed since last sync?
+ .isAfter(lastSyncTime.minusSeconds(OFFSET_SECONDS))) {
+ // Change in remote config can happen during a sync.
+ // We apply an offset on lastSyncTime to make sure the changes are
picked up at the next sync.
+ // If a remote change happened after (lastSync-offset) we'll still sync.
If it happened before (lastSync-offset) we won't.
+ return
!remoteConfig.getContent().equals(localDir.fileContent(remoteConfig.getName()));
+ } else {
+ return false;
+ }
+ }
+
+ private void deleteLocalFiles(List<RemoteConfig> remoteConfigs,
LocalDirectory localDir) {
+ Set<String> remoteConfigNames =
remoteConfigs.stream().map(RemoteConfig::getName).collect(toSet());
+ for (String localFileName : localDir.list()) {
+ if (!remoteConfigNames.contains(localFileName)) {
+ LOG.deletingProviderDescriptor(localFileName, localDir);
+ localDir.deleteFile(localFileName);
+ }
+ }
+ }
+}
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/LocalDirectory.java
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/LocalDirectory.java
new file mode 100644
index 000000000..c5a2c903e
--- /dev/null
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/LocalDirectory.java
@@ -0,0 +1,85 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.topology.monitor.db;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.knox.gateway.GatewayMessages;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+
+public class LocalDirectory {
+ private static final GatewayMessages LOG =
MessagesFactory.get(GatewayMessages.class);
+ public static final Charset CHARSET = StandardCharsets.UTF_8;
+ private final File base;
+
+ public LocalDirectory(File base) {
+ this.base = base;
+ ensureExists(base);
+ }
+
+ private void ensureExists(File base) {
+ try {
+ if (!base.exists()) {
+ Files.createDirectories(Paths.get(base.getAbsolutePath()));
+ }
+ } catch (IOException e) {
+ LOG.cannotCreateLocalDirectory(base, e);
+ }
+ }
+
+ public void writeFile(String name, String content) throws IOException {
+ FileUtils.writeStringToFile(file(name), content, CHARSET);
+ }
+
+ public boolean deleteFile(String name) {
+ return FileUtils.deleteQuietly(file(name));
+ }
+
+ public String fileContent(String name) throws IOException {
+ try {
+ return FileUtils.readFileToString(file(name), CHARSET);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+
+ public Set<String> list() {
+ return FileUtils.listFiles(base, null, false).stream()
+ .map(File::getName)
+ .collect(Collectors.toSet());
+ }
+
+ private File file(String name) {
+ return new File(base, name);
+ }
+
+ @Override
+ public String toString() {
+ return "LocalDirectory{" +
+ "base='" + base + '\'' +
+ '}';
+ }
+}
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/DefaultConfigurationMonitorProvider.java
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/RemoteConfig.java
similarity index 56%
rename from
gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/DefaultConfigurationMonitorProvider.java
rename to
gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/RemoteConfig.java
index 723c9f60f..87c9f87d5 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/DefaultConfigurationMonitorProvider.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/RemoteConfig.java
@@ -14,18 +14,30 @@
* License for the specific language governing permissions and limitations
under
* the License.
*/
-package org.apache.knox.gateway.topology.monitor;
+package org.apache.knox.gateway.topology.monitor.db;
-import org.apache.knox.gateway.config.GatewayConfig;
-import
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+import java.time.Instant;
+public class RemoteConfig {
+ private final String name;
+ private final String content;
+ private final Instant lastModified;
-public class DefaultConfigurationMonitorProvider implements
RemoteConfigurationMonitorProvider {
+ public RemoteConfig(String name, String content, Instant lastModified) {
+ this.name = name;
+ this.content = content;
+ this.lastModified = lastModified;
+ }
- @Override
- public RemoteConfigurationMonitor newInstance(final GatewayConfig
config,
- final
RemoteConfigurationRegistryClientService clientService) {
- return new DefaultRemoteConfigurationMonitor(config, clientService);
- }
+ public String getName() {
+ return name;
+ }
+ public String getContent() {
+ return content;
+ }
+
+ public Instant getLastModified() {
+ return lastModified;
+ }
}
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/RemoteConfigDatabase.java
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/RemoteConfigDatabase.java
new file mode 100644
index 000000000..891c526b0
--- /dev/null
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/RemoteConfigDatabase.java
@@ -0,0 +1,150 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.topology.monitor.db;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import javax.sql.DataSource;
+
+import org.apache.knox.gateway.util.JDBCUtils;
+
+public class RemoteConfigDatabase {
+ private static final String KNOX_PROVIDERS_TABLE_CREATE_SQL_FILE_NAME =
"createKnoxProvidersTable.sql";
+ private static final String KNOX_DESCRIPTORS_TABLE_CREATE_SQL_FILE_NAME =
"createKnoxTokenDescriptorsTable.sql";
+ private static final String KNOX_PROVIDERS_TABLE_NAME = "KNOX_PROVIDERS";
+ private static final String KNOX_DESCRIPTORS_TABLE_NAME = "KNOX_DESCRIPTORS";
+ private final DataSource dataSource;
+
+ public RemoteConfigDatabase(DataSource dataSource) {
+ this.dataSource = dataSource;
+ ensureTablesExist();
+ }
+
+ private void ensureTablesExist() {
+ try {
+ createTableIfNotExists(KNOX_PROVIDERS_TABLE_NAME,
KNOX_PROVIDERS_TABLE_CREATE_SQL_FILE_NAME);
+ createTableIfNotExists(KNOX_DESCRIPTORS_TABLE_NAME,
KNOX_DESCRIPTORS_TABLE_CREATE_SQL_FILE_NAME);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void createTableIfNotExists(String tableName, String
createSqlFileName) throws Exception {
+ if (!JDBCUtils.isTableExists(tableName, dataSource)) {
+ JDBCUtils.createTable(createSqlFileName, dataSource,
this.getClass().getClassLoader());
+ }
+ }
+
+ public List<RemoteConfig> selectProviders() {
+ return selectFrom(KNOX_PROVIDERS_TABLE_NAME);
+ }
+
+ public List<RemoteConfig> selectDescriptors() {
+ return selectFrom(KNOX_DESCRIPTORS_TABLE_NAME);
+ }
+
+ private List<RemoteConfig> selectFrom(String tableName) {
+ List<RemoteConfig> result = new ArrayList<>();
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement statement = connection.prepareStatement("SELECT
name, content, last_modified_time FROM " + tableName)) {
+ try (ResultSet rs = statement.executeQuery()) {
+ while(rs.next()) {
+ result.add(new RemoteConfig(rs.getString(1), rs.getString(2),
rs.getTimestamp(3).toInstant()));
+ }
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ return result;
+ }
+
+ /**
+ * Save provider config to DB, overwrite if exists
+ */
+ public boolean putProvider(String name, String content) {
+ return insert(name, content, KNOX_PROVIDERS_TABLE_NAME);
+ }
+
+ /**
+ * Save descriptor config to DB, overwrite if exists
+ */
+ public boolean putDescriptor(String name, String content) {
+ return insert(name, content, KNOX_DESCRIPTORS_TABLE_NAME);
+ }
+
+ private boolean insert(String name, String content, String tableName) {
+ try {
+ if (exists(name, tableName)) {
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement statement = connection.prepareStatement(
+ "UPDATE " + tableName + " SET content = ?,
last_modified_time = ? WHERE name = ?")) {
+ statement.setString(1, content);
+ statement.setTimestamp(2, Timestamp.from(Instant.now()));
+ statement.setString(3, name);
+ return statement.executeUpdate() == 1;
+ }
+ } else {
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement statement = connection.prepareStatement(
+ "INSERT INTO " + tableName + " (name, content,
last_modified_time) VALUES(?,?,?)")) {
+ statement.setString(1, name);
+ statement.setString(2, content);
+ statement.setTimestamp(3, Timestamp.from(Instant.now()));
+ return statement.executeUpdate() == 1;
+ }
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private boolean exists(String name, String tableName) throws SQLException {
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement statement = connection.prepareStatement(
+ "SELECT name FROM " + tableName + " WHERE name = ?")) {
+ statement.setString(1, name);
+ try (ResultSet resultSet = statement.executeQuery()) {
+ return resultSet.next();
+ }
+ }
+ }
+
+ public boolean deleteProvider(String name) {
+ return delete(name, KNOX_PROVIDERS_TABLE_NAME);
+ }
+
+ public boolean deleteDescriptor(String name) {
+ return delete(name, KNOX_DESCRIPTORS_TABLE_NAME);
+ }
+
+ private boolean delete(String name, String tableName) {
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement statement = connection.prepareStatement(
+ "DELETE FROM " + tableName + " WHERE name = ?")) {
+ statement.setString(1, name);
+ return statement.executeUpdate() == 1;
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/util/JDBCUtils.java
b/gateway-server/src/main/java/org/apache/knox/gateway/util/JDBCUtils.java
index d01fc2032..7933a6740 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/util/JDBCUtils.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/util/JDBCUtils.java
@@ -17,8 +17,11 @@
*/
package org.apache.knox.gateway.util;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.mysql.cj.conf.PropertyDefinitions;
import com.mysql.cj.jdbc.MysqlDataSource;
+import org.apache.commons.io.IOUtils;
import org.apache.derby.jdbc.ClientDataSource;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.services.security.AliasService;
@@ -29,7 +32,13 @@ import org.postgresql.jdbc.SslMode;
import org.postgresql.ssl.NonValidatingFactory;
import javax.sql.DataSource;
+import java.io.InputStream;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Locale;
public class JDBCUtils {
public static final String POSTGRESQL_DB_TYPE = "postgresql";
@@ -143,4 +152,23 @@ public class JDBCUtils {
return value == null ? null : new String(value);
}
+ public static boolean isTableExists(String tableName, DataSource dataSource)
throws SQLException {
+ boolean exists = false;
+ try (Connection connection = dataSource.getConnection()) {
+ final DatabaseMetaData dbMetadata = connection.getMetaData();
+ final String tableNameToCheck = dbMetadata.storesUpperCaseIdentifiers()
? tableName : tableName.toLowerCase(Locale.ROOT);
+ try (ResultSet tables = dbMetadata.getTables(connection.getCatalog(),
null, tableNameToCheck, null)) {
+ exists = tables.next();
+ }
+ }
+ return exists;
+ }
+
+ public static void createTable(String createSqlFileName, DataSource
dataSource, ClassLoader classLoader) throws Exception {
+ final InputStream is = classLoader.getResourceAsStream(createSqlFileName);
+ String createTableSql = IOUtils.toString(is, UTF_8);
+ try (Connection connection = dataSource.getConnection(); Statement
createTableStatment = connection.createStatement();) {
+ createTableStatment.execute(createTableSql);
+ }
+ }
}
diff --git
a/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.topology.monitor.RemoteConfigurationMonitorProvider
b/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.topology.monitor.RemoteConfigurationMonitorProvider
deleted file mode 100644
index 63f438acc..000000000
---
a/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.topology.monitor.RemoteConfigurationMonitorProvider
+++ /dev/null
@@ -1,19 +0,0 @@
-##########################################################################
-# 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.
-##########################################################################
-
-org.apache.knox.gateway.topology.monitor.DefaultConfigurationMonitorProvider
diff --git a/gateway-server/src/main/resources/createKnoxProvidersTable.sql
b/gateway-server/src/main/resources/createKnoxProvidersTable.sql
new file mode 100644
index 000000000..da4577a5c
--- /dev/null
+++ b/gateway-server/src/main/resources/createKnoxProvidersTable.sql
@@ -0,0 +1,21 @@
+-- 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.
+
+CREATE TABLE IF NOT EXISTS KNOX_PROVIDERS ( -- IF NOT EXISTS syntax is not
supported by Derby
+ name varchar(256) NOT NULL,
+ content TEXT NOT NULL,
+ last_modified_time TIMESTAMP NOT NULL,
+ PRIMARY KEY (name)
+)
\ No newline at end of file
diff --git
a/gateway-server/src/main/resources/createKnoxTokenDescriptorsTable.sql
b/gateway-server/src/main/resources/createKnoxTokenDescriptorsTable.sql
new file mode 100644
index 000000000..1887d1695
--- /dev/null
+++ b/gateway-server/src/main/resources/createKnoxTokenDescriptorsTable.sql
@@ -0,0 +1,21 @@
+-- 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.
+
+CREATE TABLE IF NOT EXISTS KNOX_DESCRIPTORS ( -- IF NOT EXISTS syntax is not
supported by Derby
+ name varchar(256) NOT NULL,
+ content TEXT NOT NULL,
+ last_modified_time TIMESTAMP NOT NULL,
+ PRIMARY KEY (name)
+)
\ No newline at end of file
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java
index 8ecb72684..fb1ce553d 100644
---
a/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java
@@ -63,7 +63,8 @@ public class AbstractGatewayServicesTest {
ServiceType.HOST_MAPPING_SERVICE,
ServiceType.SERVICE_DEFINITION_REGISTRY,
ServiceType.SERVICE_REGISTRY_SERVICE,
- ServiceType.CONCURRENT_SESSION_VERIFIER
+ ServiceType.CONCURRENT_SESSION_VERIFIER,
+ ServiceType.REMOTE_CONFIGURATION_MONITOR,
};
assertNotEquals(ServiceType.values(), orderedServiceTypes);
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateServiceTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateServiceTest.java
index d3f33df79..024025282 100644
---
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateServiceTest.java
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateServiceTest.java
@@ -28,6 +28,7 @@ import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
+import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
@@ -48,6 +49,7 @@ import
org.apache.knox.gateway.services.security.token.UnknownTokenException;
import org.apache.knox.gateway.services.security.token.impl.TokenMAC;
import org.apache.knox.gateway.util.JDBCUtils;
import org.easymock.EasyMock;
+import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
@@ -85,6 +87,15 @@ public class JDBCTokenStateServiceTest {
tokenMAC = new TokenMAC(HmacAlgorithms.HMAC_SHA_256.getName(),
"sPj8FCgQhCEi6G18kBfpswxYSki33plbelGLs0hMSbk".toCharArray());
}
+ @SuppressWarnings("PMD.JUnit4TestShouldUseAfterAnnotation")
+ @AfterClass
+ public static void tearDown() throws Exception {
+ try (Connection connection = getConnection();
+ Statement statement = connection.createStatement()) {
+ statement.execute("SHUTDOWN");
+ }
+ }
+
@Test
public void testAddToken() throws Exception {
final String tokenId = UUID.randomUUID().toString();
@@ -259,7 +270,7 @@ public class JDBCTokenStateServiceTest {
}
}
- private Connection getConnection() throws SQLException {
+ private static Connection getConnection() throws SQLException {
return DriverManager.getConnection(CONNECTION_URL, USERNAME, PASSWORD);
}
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/DefaultRemoteConfigurationMonitorTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/ZkRemoteConfigurationMonitorServiceTest.java
similarity index 91%
rename from
gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/DefaultRemoteConfigurationMonitorTest.java
rename to
gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/ZkRemoteConfigurationMonitorServiceTest.java
index 0d48828ac..da7fb442f 100644
---
a/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/DefaultRemoteConfigurationMonitorTest.java
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/ZkRemoteConfigurationMonitorServiceTest.java
@@ -21,7 +21,7 @@ import
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistr
import org.easymock.EasyMock;
import org.junit.Test;
-public class DefaultRemoteConfigurationMonitorTest {
+public class ZkRemoteConfigurationMonitorServiceTest {
@Test(expected=IllegalStateException.class)
public void testInitWithoutRequiredConfig() {
GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class);
@@ -32,6 +32,6 @@ public class DefaultRemoteConfigurationMonitorTest {
RemoteConfigurationRegistryClientService
remoteConfigurationRegistryClientService =
EasyMock.createNiceMock(RemoteConfigurationRegistryClientService.class);
- new DefaultRemoteConfigurationMonitor(gatewayConfig,
remoteConfigurationRegistryClientService);
+ new ZkRemoteConfigurationMonitorService(gatewayConfig,
remoteConfigurationRegistryClientService);
}
}
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/ZooKeeperConfigurationMonitorTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/ZooKeeperConfigurationMonitorTest.java
index a72c1f007..8a5842859 100644
---
a/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/ZooKeeperConfigurationMonitorTest.java
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/ZooKeeperConfigurationMonitorTest.java
@@ -170,7 +170,7 @@ public class ZooKeeperConfigurationMonitorTest {
clientService.init(gc, Collections.emptyMap());
clientService.start();
- DefaultRemoteConfigurationMonitor cm = new
DefaultRemoteConfigurationMonitor(gc, clientService);
+ ZkRemoteConfigurationMonitorService cm = new
ZkRemoteConfigurationMonitorService(gc, clientService);
// Create a provider configuration in the test ZK, prior to starting
the monitor, to make sure that the monitor
// will download existing entries upon starting.
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/db/DbRemoteConfigurationMonitorServiceTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/db/DbRemoteConfigurationMonitorServiceTest.java
new file mode 100644
index 000000000..466595661
--- /dev/null
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/db/DbRemoteConfigurationMonitorServiceTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.topology.monitor.db;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptySet;
+
+import java.time.Instant;
+import java.util.HashSet;
+
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DbRemoteConfigurationMonitorServiceTest {
+ private DbRemoteConfigurationMonitorService monitor;
+ private RemoteConfigDatabase db;
+ private LocalDirectory providersDir;
+ private LocalDirectory descriptorsDir;
+ private Instant NOW = Instant.now();
+
+ @Before
+ public void setUp() throws Exception {
+ db = EasyMock.createMock(RemoteConfigDatabase.class);
+ providersDir = EasyMock.createMock(LocalDirectory.class);
+ descriptorsDir = EasyMock.createMock(LocalDirectory.class);
+ monitor = new DbRemoteConfigurationMonitorService(db, providersDir,
descriptorsDir, 60);
+ monitor.start();
+ }
+
+ @Test
+ public void testNothingToSynchronizeWhenDbIsEmpty() throws Exception {
+ EasyMock.expect(db.selectDescriptors()).andReturn(emptyList()).anyTimes();
+ EasyMock.expect(db.selectProviders()).andReturn(emptyList()).anyTimes();
+
+ EasyMock.expect(providersDir.list()).andReturn(emptySet()).anyTimes();
+ EasyMock.expect(descriptorsDir.list()).andReturn(emptySet()).anyTimes();
+
+ EasyMock.replay(providersDir, descriptorsDir, db);
+ monitor.sync();
+ EasyMock.verify(providersDir, descriptorsDir, db);
+ }
+
+ @Test
+ public void testCreatesNewProvider() throws Exception {
+ EasyMock.expect(providersDir.list()).andReturn(emptySet()).anyTimes();
+ EasyMock.expect(descriptorsDir.list()).andReturn(emptySet()).anyTimes();
+
+ EasyMock.expect(db.selectDescriptors()).andReturn(emptyList()).anyTimes();
+ EasyMock.expect(db.selectProviders()).andReturn(asList(
+ new RemoteConfig("test-prov", "test-prov-content",
NOW))).anyTimes();
+
+ providersDir.writeFile("test-prov", "test-prov-content");
+ EasyMock.expectLastCall().once();
+ EasyMock.replay(providersDir, descriptorsDir, db);
+ monitor.sync();
+ EasyMock.verify(providersDir, descriptorsDir, db);
+ }
+
+ @Test
+ public void testDeletesProviderFromFileSystem() throws Exception {
+ EasyMock.expect(providersDir.list()).andReturn(new
HashSet<>(asList("local"))).anyTimes();
+ EasyMock.expect(descriptorsDir.list()).andReturn(emptySet()).anyTimes();
+
+ EasyMock.expect(db.selectDescriptors()).andReturn(emptyList()).anyTimes();
+ EasyMock.expect(db.selectProviders()).andReturn(emptyList()).anyTimes();
+
+ EasyMock.expect(providersDir.deleteFile("local")).andReturn(true).once();
+
+ EasyMock.replay(providersDir, descriptorsDir, db);
+ monitor.sync();
+ EasyMock.verify(providersDir, descriptorsDir, db);
+ }
+
+ @Test
+ public void testCreatesNewDescriptor() throws Exception {
+ EasyMock.expect(providersDir.list()).andReturn(emptySet()).anyTimes();
+ EasyMock.expect(descriptorsDir.list()).andReturn(emptySet()).anyTimes();
+
+ EasyMock.expect(db.selectProviders()).andReturn(emptyList()).anyTimes();
+ EasyMock.expect(db.selectDescriptors()).andReturn(asList(
+ new RemoteConfig("test-desc", "test-desc-content",
NOW))).anyTimes();
+
+ descriptorsDir.writeFile("test-desc", "test-desc-content");
+ EasyMock.expectLastCall().once();
+ EasyMock.replay(providersDir, descriptorsDir, db);
+ monitor.sync();
+ EasyMock.verify(providersDir, descriptorsDir, db);
+ }
+
+ @Test
+ public void testDeletesDescriptorFromFileSystem() throws Exception {
+ EasyMock.expect(providersDir.list()).andReturn(emptySet()).anyTimes();
+ EasyMock.expect(descriptorsDir.list()).andReturn(new
HashSet<>(asList("local"))).anyTimes();
+
+ EasyMock.expect(db.selectDescriptors()).andReturn(emptyList()).anyTimes();
+ EasyMock.expect(db.selectProviders()).andReturn(emptyList()).anyTimes();
+
+ EasyMock.expect(descriptorsDir.deleteFile("local")).andReturn(true).once();
+
+ EasyMock.replay(providersDir, descriptorsDir, db);
+ monitor.sync();
+ EasyMock.verify(providersDir, descriptorsDir, db);
+ }
+
+ @Test
+ public void testMixedDeleteUpdateAtTheSameTime() throws Exception {
+ // Local FS
+ EasyMock.expect(providersDir.list()).andReturn(new
HashSet<>(asList("prov1"))).anyTimes();
+ EasyMock.expect(descriptorsDir.list()).andReturn(new
HashSet<>(asList("desc1", "desc2", "desc3-to-be-deleted"))).anyTimes();
+ // Local Contents
+
EasyMock.expect(descriptorsDir.fileContent("desc1")).andReturn("desc1-same-content").anyTimes();
+
EasyMock.expect(descriptorsDir.fileContent("desc2")).andReturn("desc2-local-content").anyTimes();
+
EasyMock.expect(providersDir.fileContent("prov1")).andReturn(null).anyTimes();
+
+ // Remote DB
+ EasyMock.expect(db.selectProviders()).andReturn(asList(
+ new RemoteConfig("prov1", "prov1-new-file", NOW)
+ )).anyTimes();
+
+ EasyMock.expect(db.selectDescriptors()).andReturn(asList(
+ new RemoteConfig("desc1", "desc1-same-content", NOW),
+ new RemoteConfig("desc2", "desc2-remote-content", NOW)
+ )).anyTimes();
+
+ // Expectations
+ descriptorsDir.writeFile("desc2", "desc2-remote-content");
+ EasyMock.expectLastCall().once();
+
EasyMock.expect(descriptorsDir.deleteFile("desc3-to-be-deleted")).andReturn(true).once();
+
+ providersDir.writeFile("prov1", "prov1-new-file");
+ EasyMock.expectLastCall().once();
+
+ EasyMock.replay(providersDir, descriptorsDir, db);
+ monitor.sync();
+ EasyMock.verify(providersDir, descriptorsDir, db);
+ }
+
+ @Test
+ public void testTwoSyncOnlyOneUpdate() throws Exception {
+ // Local FS
+ EasyMock.expect(providersDir.list()).andReturn(emptySet()).anyTimes();
+ EasyMock.expect(descriptorsDir.list()).andReturn(new
HashSet<>(asList("desc1"))).anyTimes();
+ // Local Contents
+
EasyMock.expect(descriptorsDir.fileContent("desc1")).andReturn("local-content").anyTimes();
+
+ // Remote DB
+ EasyMock.expect(db.selectProviders()).andReturn(emptyList()).anyTimes();
+ EasyMock.expect(db.selectDescriptors()).andReturn(asList(
+ new RemoteConfig("desc1", "remote-content", NOW.minusSeconds(15))
+ )).anyTimes();
+
+ // Expectations
+ descriptorsDir.writeFile("desc1", "remote-content");
+ EasyMock.expectLastCall().once();
+
+ EasyMock.replay(providersDir, descriptorsDir, db);
+ monitor.sync();
+ monitor.sync();
+ EasyMock.verify(providersDir, descriptorsDir, db);
+ }
+
+ @Test
+ public void testTwoSyncTwoUpdate() throws Exception {
+ // Local FS
+ EasyMock.expect(providersDir.list()).andReturn(emptySet()).anyTimes();
+ EasyMock.expect(descriptorsDir.list()).andReturn(new
HashSet<>(asList("desc1"))).anyTimes();
+ // Local Contents
+
EasyMock.expect(descriptorsDir.fileContent("desc1")).andReturn("local-content").anyTimes();
+
+ // Remote DB
+ EasyMock.expect(db.selectProviders()).andReturn(emptyList()).anyTimes();
+ EasyMock.expect(db.selectDescriptors()).andReturn(asList(
+ new RemoteConfig("desc1", "remote-content", NOW.plusSeconds(10))
+ )).once();
+ EasyMock.expect(db.selectDescriptors()).andReturn(asList(
+ new RemoteConfig("desc1", "remote-content", NOW.plusSeconds(20))
+ )).once();
+
+ // Expectations
+ descriptorsDir.writeFile("desc1", "remote-content");
+ EasyMock.expectLastCall().times(2);
+
+ EasyMock.replay(providersDir, descriptorsDir, db);
+ monitor.sync();
+ monitor.sync();
+ EasyMock.verify(providersDir, descriptorsDir, db);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (monitor != null) {
+ monitor.stop();
+ }
+ }
+
+}
\ No newline at end of file
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/db/LocalDirectoryTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/db/LocalDirectoryTest.java
new file mode 100644
index 000000000..bd052f3f2
--- /dev/null
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/db/LocalDirectoryTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.topology.monitor.db;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class LocalDirectoryTest {
+ @Rule
+ public final TemporaryFolder testFolder = new TemporaryFolder();
+ private LocalDirectory localDirectory;
+
+ @Before
+ public void setUp() throws Exception {
+ localDirectory = new LocalDirectory(testFolder.getRoot());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (localDirectory != null) {
+ localDirectory.list().forEach(localDirectory::deleteFile);
+ }
+ }
+
+ @Test
+ public void testEmpty() {
+ assertEquals(0, localDirectory.list().size());
+ }
+
+ @Test
+ public void testDeleteNonExisting() {
+ assertFalse(localDirectory.deleteFile("non_existing.txt"));
+ }
+
+ @Test
+ public void testDeleteExisting() throws Exception {
+ localDirectory.writeFile("new.txt", "content");
+ assertTrue(localDirectory.deleteFile("new.txt"));
+ }
+
+ @Test
+ public void testCreateNewWithContent() throws Exception {
+ localDirectory.writeFile("new.txt", "content");
+ assertEquals("content", localDirectory.fileContent("new.txt"));
+ }
+
+ @Test
+ public void testOverwriteExisting() throws Exception {
+ localDirectory.writeFile("existing.txt", "old content");
+ localDirectory.writeFile("existing.txt", "new content");
+ assertEquals("new content", localDirectory.fileContent("existing.txt"));
+ }
+
+ @Test
+ public void testContentOfNonExistingIsNull() throws Exception {
+ assertEquals(null, localDirectory.fileContent("non_existing"));
+ }
+
+ @Test
+ public void testList() throws Exception {
+ localDirectory.writeFile("1.txt", "any");
+ localDirectory.writeFile("2.txt", "any");
+ assertEquals(2, localDirectory.list().size());
+ assertTrue(localDirectory.list().contains("1.txt"));
+ assertTrue(localDirectory.list().contains("2.txt"));
+ }
+
+ @Test
+ public void testTwoWithDifferentContent() throws Exception {
+ localDirectory.writeFile("1.txt", "content 1");
+ localDirectory.writeFile("2.txt", "content 2");
+ assertEquals("content 1", localDirectory.fileContent("1.txt"));
+ assertEquals("content 2", localDirectory.fileContent("2.txt"));
+ }
+
+ @Test
+ public void testCreatesDirectoryIfDoesNotExist() throws Exception {
+ LocalDirectory subDir = new LocalDirectory(new File(testFolder.getRoot(),
"sub1/sub2"));
+ assertTrue(subDir.list().isEmpty());
+ subDir.writeFile("s.txt", "any");
+ assertTrue(subDir.list().contains("s.txt"));
+ }
+}
\ No newline at end of file
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/db/RemoteConfigDatabaseTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/db/RemoteConfigDatabaseTest.java
new file mode 100644
index 000000000..baa9aecce
--- /dev/null
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/topology/monitor/db/RemoteConfigDatabaseTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.topology.monitor.db;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Connection;
+import java.sql.Statement;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+
+import org.hsqldb.jdbc.JDBCDataSource;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class RemoteConfigDatabaseTest {
+ public static final String DB_NAME = "remote_config_test";
+ public static final String USER = "sa";
+ public static final String PASSWORD = "";
+ private static JDBCDataSource dataSource;
+ private RemoteConfigDatabase db;
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ dataSource = new JDBCDataSource();
+ dataSource.setDatabaseName(DB_NAME);
+ dataSource.setUser(USER);
+ dataSource.setPassword(PASSWORD);
+ dataSource.setUrl("jdbc:hsqldb:mem:knox;sql.syntax_pgs=true"); //
sql.syntax_pgs => use postgres syntax
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ db = new RemoteConfigDatabase(dataSource);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ try (Connection connection = dataSource.getConnection(USER, PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.execute("DROP TABLE KNOX_PROVIDERS");
+ statement.execute("DROP TABLE KNOX_DESCRIPTORS");
+ }
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ try (Connection connection = dataSource.getConnection(USER, PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.execute("SHUTDOWN");
+ }
+ }
+
+ @Test
+ public void testEmpty() {
+ assertTrue(db.selectDescriptors().isEmpty());
+ assertTrue(db.selectDescriptors().isEmpty());
+ }
+
+ @Test
+ public void testList() {
+ db.putProvider("provider_1", "test provider1 content");
+ db.putProvider("provider_2", "test provider2 content");
+ db.putDescriptor("descriptor_1", "test descriptor content");
+
+ List<RemoteConfig> providers = db.selectProviders();
+ List<RemoteConfig> descriptors = db.selectDescriptors();
+
+ assertEquals(2, providers.size());
+ assertEquals(1, descriptors.size());
+
+ RemoteConfig descriptor = descriptors.get(0);
+ assertEquals("descriptor_1", descriptor.getName());
+ assertEquals("test descriptor content", descriptor.getContent());
+ assertTrue(Duration.between(Instant.now(),
descriptor.getLastModified()).toMillis() < 1000);
+
+ RemoteConfig provider1 = providers.stream().filter(each ->
each.getName().equals("provider_1")).findFirst().get();
+ assertEquals("test provider1 content", provider1.getContent());
+ assertTrue(Duration.between(Instant.now(),
provider1.getLastModified()).toMillis() < 1000);
+
+ RemoteConfig provider2 = providers.stream().filter(each ->
each.getName().equals("provider_2")).findFirst().get();
+ assertEquals("test provider2 content", provider2.getContent());
+ assertTrue(Duration.between(Instant.now(),
provider2.getLastModified()).toMillis() < 1000);
+ }
+
+ @Test
+ public void testInsertOverwrite() {
+ db.putProvider("provider_1", "test provider1 content v1");
+ db.putProvider("provider_1", "test provider1 content v2");
+ List<RemoteConfig> providers = db.selectProviders();
+ assertEquals(1, providers.size());
+ assertEquals("test provider1 content v2",
db.selectProviders().get(0).getContent());
+
+ db.putDescriptor("descriptor_1", "test descriptor1 content v1");
+ db.putDescriptor("descriptor_1", "test descriptor1 content v2");
+ List<RemoteConfig> descriptors = db.selectDescriptors();
+ assertEquals(1, descriptors.size());
+ assertEquals("test descriptor1 content v2",
db.selectDescriptors().get(0).getContent());
+ }
+
+ @Test
+ public void testDelete() {
+ db.putProvider("provider_1", "test provider1 content");
+ db.putProvider("provider_2", "test provider2 content");
+ db.putDescriptor("descriptor_1", "test descriptor content");
+
+ assertEquals(2, db.selectProviders().size());
+ assertEquals(1, db.selectDescriptors().size());
+
+ db.deleteProvider("provider_2");
+
+ assertEquals(1, db.selectProviders().size());
+ assertEquals(1, db.selectDescriptors().size());
+
+ db.deleteDescriptor("descriptor_1");
+
+ assertEquals(1, db.selectProviders().size());
+ assertEquals(0, db.selectDescriptors().size());
+
+ assertEquals("provider_1", db.selectProviders().get(0).getName());
+ }
+}
\ No newline at end of file
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 59d47a0cc..f9b87296b 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
@@ -1010,6 +1010,11 @@ public class GatewayTestConfig extends Configuration
implements GatewayConfig {
return null;
}
+ @Override
+ public long getDbRemoteConfigMonitorPollingInterval() {
+ return 30;
+ }
+
@Override
public long getConcurrentSessionVerifierExpiredTokensCleaningPeriod() {
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 a3bc6ac57..423b2e6a6 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
@@ -860,6 +860,8 @@ public interface GatewayConfig {
Set<String> getSessionVerificationUnlimitedUsers();
+ long getDbRemoteConfigMonitorPollingInterval();
+
long getConcurrentSessionVerifierExpiredTokensCleaningPeriod();
/**
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/services/ServiceType.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/services/ServiceType.java
index a41f92429..f0123043c 100644
---
a/gateway-spi/src/main/java/org/apache/knox/gateway/services/ServiceType.java
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/services/ServiceType.java
@@ -36,7 +36,8 @@ public enum ServiceType {
TOKEN_SERVICE("TokenService"),
TOKEN_STATE_SERVICE("TokenStateService"),
TOPOLOGY_SERVICE("TopologyService"),
- CONCURRENT_SESSION_VERIFIER("ConcurrentSessionVerifier");
+ CONCURRENT_SESSION_VERIFIER("ConcurrentSessionVerifier"),
+ REMOTE_CONFIGURATION_MONITOR("RemoteConfigurationMonitor");
private final String serviceTypeName;
private final String shortName;
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitor.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitor.java
index 5159d5e43..a3ecb7fe5 100644
---
a/gateway-spi/src/main/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitor.java
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitor.java
@@ -16,14 +16,11 @@
*/
package org.apache.knox.gateway.topology.monitor;
-import
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClient;
-
-public interface RemoteConfigurationMonitor {
-
- void start() throws Exception;
-
- void stop() throws Exception;
-
- RemoteConfigurationRegistryClient getClient();
+import org.apache.knox.gateway.services.Service;
+public interface RemoteConfigurationMonitor extends Service {
+ boolean createProvider(String name, String content);
+ boolean createDescriptor(String name, String content);
+ boolean deleteProvider(String name);
+ boolean deleteDescriptor(String name);
}
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitorProvider.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitorProvider.java
deleted file mode 100644
index ea9003a54..000000000
---
a/gateway-spi/src/main/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitorProvider.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
- * 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.topology.monitor;
-
-
-import org.apache.knox.gateway.config.GatewayConfig;
-import
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClientService;
-
-public interface RemoteConfigurationMonitorProvider {
-
- /**
- *
- * @param config The gateway configuration.
- * @param clientService The RemoteConfigurationRegistryClientService for
accessing the remote configuration.
- *
- * @return A RemoteConfigurationMonitor for keeping the local config in
sync with the remote config
- */
- RemoteConfigurationMonitor newInstance(GatewayConfig config,
RemoteConfigurationRegistryClientService clientService);
-
-}
diff --git
a/gateway-test/src/test/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitorTest.java
b/gateway-test/src/test/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitorTest.java
index d8529c0c4..6419df216 100644
---
a/gateway-test/src/test/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitorTest.java
+++
b/gateway-test/src/test/java/org/apache/knox/gateway/topology/monitor/RemoteConfigurationMonitorTest.java
@@ -248,10 +248,7 @@ public class RemoteConfigurationMonitorTest {
clientService.init(gc, Collections.emptyMap());
clientService.start();
- RemoteConfigurationMonitorFactory.setClientService(clientService);
-
- RemoteConfigurationMonitor cm =
RemoteConfigurationMonitorFactory.get(gc);
- assertNotNull("Failed to load RemoteConfigurationMonitor", cm);
+ RemoteConfigurationMonitor cm = new
ZkRemoteConfigurationMonitorService(gc, clientService);
final ACL ANY_AUTHENTICATED_USER_ALL = new ACL(ZooDefs.Perms.ALL, new
Id("auth", ""));
List<ACL> acls = Arrays.asList(ANY_AUTHENTICATED_USER_ALL, new
ACL(ZooDefs.Perms.WRITE, ZooDefs.Ids.ANYONE_ID_UNSAFE));
@@ -285,7 +282,6 @@ public class RemoteConfigurationMonitorTest {
}
}
-
/*
* KNOX-1135
*/
@@ -324,10 +320,7 @@ public class RemoteConfigurationMonitorTest {
clientService.init(gc, Collections.emptyMap());
clientService.start();
- RemoteConfigurationMonitorFactory.setClientService(clientService);
-
- RemoteConfigurationMonitor cm =
RemoteConfigurationMonitorFactory.get(gc);
- assertNotNull("Failed to load RemoteConfigurationMonitor", cm);
+ RemoteConfigurationMonitor cm = new
ZkRemoteConfigurationMonitorService(gc, clientService);
final ACL ANY_AUTHENTICATED_USER_ALL = new ACL(ZooDefs.Perms.ALL, new
Id("auth", ""));
List<ACL> acls = Arrays.asList(ANY_AUTHENTICATED_USER_ALL, new
ACL(ZooDefs.Perms.WRITE, ZooDefs.Ids.ANYONE_ID_UNSAFE));
@@ -398,10 +391,7 @@ public class RemoteConfigurationMonitorTest {
clientService.init(gc, Collections.emptyMap());
clientService.start();
- RemoteConfigurationMonitorFactory.setClientService(clientService);
-
- RemoteConfigurationMonitor cm =
RemoteConfigurationMonitorFactory.get(gc);
- assertNotNull("Failed to load RemoteConfigurationMonitor", cm);
+ RemoteConfigurationMonitor cm = new
ZkRemoteConfigurationMonitorService(gc, clientService);
List<ACL> acls = Collections.singletonList(ANY_AUTHENTICATED_USER_ALL);
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).withACL(acls).forPath(PATH_KNOX);
@@ -472,10 +462,7 @@ public class RemoteConfigurationMonitorTest {
clientService.init(gc, Collections.emptyMap());
clientService.start();
- RemoteConfigurationMonitorFactory.setClientService(clientService);
-
- RemoteConfigurationMonitor cm =
RemoteConfigurationMonitorFactory.get(gc);
- assertNotNull("Failed to load RemoteConfigurationMonitor", cm);
+ RemoteConfigurationMonitor cm = new
ZkRemoteConfigurationMonitorService(gc, clientService);
// Check that the config nodes really don't yet exist (the monitor
will create them if they're not present)
assertNull(client.checkExists().forPath(PATH_KNOX));