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 616a4e06e KNOX-2844 - Remote config monitor should not automatically 
delete local files if they're missing from the DB (#680)
616a4e06e is described below

commit 616a4e06e8c24c6ba65250d4f853851d6a7d81b9
Author: Attila Magyar <[email protected]>
AuthorDate: Tue Nov 29 10:16:19 2022 +0100

    KNOX-2844 - Remote config monitor should not automatically delete local 
files if they're missing from the DB (#680)
---
 .../org/apache/knox/gateway/GatewayMessages.java   |  8 +++
 .../gateway/config/impl/GatewayConfigImpl.java     | 10 +++-
 .../topology/impl/DefaultTopologyService.java      | 16 +++---
 .../RemoteConfigurationMonitorServiceFactory.java  | 15 +++++-
 .../ZkRemoteConfigurationMonitorService.java       |  2 +-
 .../db/DbRemoteConfigurationMonitorService.java    | 49 ++++++++++++-----
 .../gateway/topology/monitor/db/RemoteConfig.java  | 10 ++++
 .../topology/monitor/db/RemoteConfigDatabase.java  | 57 ++++++++++++++++----
 .../main/resources/createKnoxProvidersTable.sql    |  1 +
 .../resources/createKnoxTokenDescriptorsTable.sql  |  1 +
 .../DbRemoteConfigurationMonitorServiceTest.java   | 17 +++---
 .../monitor/db/RemoteConfigDatabaseTest.java       | 63 ++++++++++++++++++++--
 .../org/apache/knox/gateway/GatewayTestConfig.java |  5 ++
 .../apache/knox/gateway/config/GatewayConfig.java  |  2 +
 14 files changed, 213 insertions(+), 43 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 428f40f8e..cba99f933 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
@@ -767,4 +767,12 @@ public interface GatewayMessages {
   @Message(level = MessageLevel.DEBUG,
           text = "Creating local {0} with name {1}")
   void creatingLocalDescriptorProvider(String type, String name);
+
+  @Message(level = MessageLevel.DEBUG,
+          text = "Cleaning remote configuration db. Deleting logically deleted 
rows which are older than {0} seconds.")
+  void cleaningRemoteConfigTables(int cleanUpPeriodSeconds);
+
+  @Message(level = MessageLevel.INFO,
+          text = "Initializing remote configuration db. Sync interval={0} 
seconds. Clean up interval={1} seconds.")
+  void initDbRemoteConfigMonitor(long syncIntervalSeconds, int 
cleanUpPeriodSeconds);
 }
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 71e839dc3..33bbf7066 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
@@ -243,7 +243,10 @@ public class GatewayConfigImpl extends Configuration 
implements GatewayConfig {
                                                   
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;
+  private static final long 
REMOTE_CONFIG_MONITOR_DB_POLLING_INTERVAL_SECONDS_DEFAULT = 30;
+
+  private static final String 
REMOTE_CONFIG_MONITOR_DB_POLLING_CLEANUP_INTERVAL_SECONDS = 
GATEWAY_CONFIG_FILE_PREFIX + 
".remote.config.monitor.db.cleanup.interval.seconds";
+  private static final int 
REMOTE_CONFIG_MONITOR_DB_POLLING_CLEANUP_INTERVAL_DEFAULT = 3 * 60 * 60;
 
   /* @since 1.1.0 Default discovery configuration */
   static final String DEFAULT_DISCOVERY_ADDRESS = GATEWAY_CONFIG_FILE_PREFIX + 
".discovery.default.address";
@@ -1434,6 +1437,11 @@ public class GatewayConfigImpl extends Configuration 
implements GatewayConfig {
     return getLong(REMOTE_CONFIG_MONITOR_DB_POLLING_INTERVAL_SECONDS, 
REMOTE_CONFIG_MONITOR_DB_POLLING_INTERVAL_SECONDS_DEFAULT);
   }
 
+  @Override
+  public int getDbRemoteConfigMonitorCleanUpInterval() {
+    return getInt(REMOTE_CONFIG_MONITOR_DB_POLLING_CLEANUP_INTERVAL_SECONDS, 
REMOTE_CONFIG_MONITOR_DB_POLLING_CLEANUP_INTERVAL_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/topology/impl/DefaultTopologyService.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/topology/impl/DefaultTopologyService.java
index 223fdd4d4..0ab8a7095 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
@@ -503,15 +503,15 @@ public class DefaultTopologyService extends 
FileAlterationListenerAdaptor implem
   public boolean deleteDescriptor(String name) {
     boolean result;
 
-    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);
     result = (descriptor == null) || descriptor.delete();
 
+    if (remoteMonitor != null && descriptor != null) {
+      // If the remote config monitor is configured, delete the descriptor 
from the remote registry
+      remoteMonitor.deleteDescriptor(descriptor.getName());
+    }
+
     return result;
   }
 
@@ -654,8 +654,10 @@ public class DefaultTopologyService extends 
FileAlterationListenerAdaptor implem
       
log.configuredMonitoringProviderConfigChangesInDirectory(sharedProvidersDirectory.getAbsolutePath());
 
       // Initialize the remote configuration monitor, if it has been configured
-      RemoteConfigurationMonitorServiceFactory provider = new 
RemoteConfigurationMonitorServiceFactory();
-      remoteMonitor = (RemoteConfigurationMonitor) provider.create(gwServices, 
ServiceType.REMOTE_CONFIGURATION_MONITOR, config, Collections.emptyMap());
+      if (gwServices != null) { // if it was called from knoxcli, we don't 
have gwServices
+        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);
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
index 80bb5f9ca..88992095f 100644
--- 
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
@@ -22,6 +22,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Map;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.services.GatewayServices;
 import org.apache.knox.gateway.services.ServiceLifecycleException;
@@ -36,6 +37,8 @@ import org.apache.knox.gateway.util.JDBCUtils;
 
 
 public class RemoteConfigurationMonitorServiceFactory extends 
AbstractServiceFactory {
+    private static final String DEFAULT_IMPLEMENTATION = 
ZkRemoteConfigurationMonitorService.class.getName();
+
     @Override
     protected RemoteConfigurationMonitor createService(GatewayServices 
gatewayServices,
                                     ServiceType serviceType,
@@ -43,6 +46,9 @@ public class RemoteConfigurationMonitorServiceFactory extends 
AbstractServiceFac
                                     Map<String, String> options,
                                     String implementation) throws 
ServiceLifecycleException {
         RemoteConfigurationMonitor service = null;
+        if (StringUtils.isBlank(implementation) && 
gatewayConfig.getRemoteConfigurationMonitorClientName() != null) {
+            implementation = DEFAULT_IMPLEMENTATION; // for backward 
compatibility
+        }
         if (matchesImplementation(implementation, 
ZkRemoteConfigurationMonitorService.class)) {
             service = new ZkRemoteConfigurationMonitorService(gatewayConfig, 
gatewayServices.getService(ServiceType.REMOTE_REGISTRY_CLIENT_SERVICE));
         } else if (matchesImplementation(implementation, 
DbRemoteConfigurationMonitorService.class)) {
@@ -57,7 +63,12 @@ public class RemoteConfigurationMonitorServiceFactory 
extends AbstractServiceFac
             LocalDirectory descriptorDir = new LocalDirectory(new 
File(config.getGatewayDescriptorsDir()));
             LocalDirectory providerDir = new LocalDirectory(new 
File(config.getGatewayProvidersConfigDir()));
             return new DbRemoteConfigurationMonitorService(
-                    db, providerDir, descriptorDir, 
config.getDbRemoteConfigMonitorPollingInterval());
+                    db,
+                    providerDir,
+                    descriptorDir,
+                    config.getDbRemoteConfigMonitorPollingInterval(),
+                    config.getDbRemoteConfigMonitorCleanUpInterval()
+            );
         } catch (SQLException | AliasServiceException e) {
             throw new ServiceLifecycleException("Cannot create 
DbRemoteConfigurationMonitor", e);
         }
@@ -70,6 +81,6 @@ public class RemoteConfigurationMonitorServiceFactory extends 
AbstractServiceFac
 
     @Override
     protected Collection<String> getKnownImplementations() {
-        return 
Arrays.asList(ZkRemoteConfigurationMonitorService.class.getName(), 
DbRemoteConfigurationMonitorService.class.getName());
+        return Arrays.asList(DEFAULT_IMPLEMENTATION, 
DbRemoteConfigurationMonitorService.class.getName());
     }
 }
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/ZkRemoteConfigurationMonitorService.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/ZkRemoteConfigurationMonitorService.java
index 29a0b6c44..0d8e34c57 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/ZkRemoteConfigurationMonitorService.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/ZkRemoteConfigurationMonitorService.java
@@ -238,7 +238,7 @@ class ZkRemoteConfigurationMonitorService implements 
RemoteConfigurationMonitor
         boolean result = false;
         List<String> existingProviderConfigs = 
client.listChildEntries(entryParent);
         for (String entryName : existingProviderConfigs) {
-            if (FilenameUtils.getBaseName(entryName).equals(name)) {
+            if (FilenameUtils.getName(entryName).equals(name)) {
                 String entryPath = entryParent + "/" + entryName;
                 client.deleteEntry(entryPath);
                 result = !client.entryExists(entryPath);
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
index d48ded957..8718ed138 100644
--- 
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
@@ -16,7 +16,7 @@
  */
 package org.apache.knox.gateway.topology.monitor.db;
 
-import static java.util.stream.Collectors.toSet;
+import static java.util.stream.Collectors.toList;
 
 import java.io.IOException;
 import java.time.Instant;
@@ -26,6 +26,7 @@ import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 import org.apache.knox.gateway.GatewayMessages;
 import org.apache.knox.gateway.config.GatewayConfig;
@@ -39,25 +40,30 @@ public class DbRemoteConfigurationMonitorService implements 
RemoteConfigurationM
   private final RemoteConfigDatabase db;
   private final LocalDirectory providersDir;
   private final LocalDirectory descriptorsDir;
-  private long intervalSeconds;
+  private final long syncIntervalSeconds;
   private final ScheduledExecutorService executor;
+  private final int cleanUpPeriodSeconds;
   private Instant lastSyncTime;
 
-  public DbRemoteConfigurationMonitorService(RemoteConfigDatabase db, 
LocalDirectory providersDir, LocalDirectory descriptorsDir, long 
intervalSeconds) {
+  public DbRemoteConfigurationMonitorService(RemoteConfigDatabase db, 
LocalDirectory providersDir, LocalDirectory descriptorsDir, long 
syncIntervalSeconds, int cleanUpPeriodSeconds) {
     this.db = db;
     this.providersDir = providersDir;
     this.descriptorsDir = descriptorsDir;
     this.executor = Executors.newSingleThreadScheduledExecutor();
-    this.intervalSeconds = intervalSeconds;
+    this.syncIntervalSeconds = syncIntervalSeconds;
+    this.cleanUpPeriodSeconds = cleanUpPeriodSeconds;
   }
 
   @Override
-  public void init(GatewayConfig config, Map<String, String> options) throws 
ServiceLifecycleException {}
+  public void init(GatewayConfig config, Map<String, String> options) throws 
ServiceLifecycleException {
+    LOG.initDbRemoteConfigMonitor(syncIntervalSeconds, cleanUpPeriodSeconds);
+  }
 
   @Override
   public void start() throws ServiceLifecycleException {
-    LOG.startingDbRemoteConfigurationMonitor(intervalSeconds);
-    executor.scheduleWithFixedDelay(this::sync, intervalSeconds, 
intervalSeconds, TimeUnit.SECONDS);
+    LOG.startingDbRemoteConfigurationMonitor(syncIntervalSeconds);
+    executor.scheduleWithFixedDelay(this::sync, syncIntervalSeconds, 
syncIntervalSeconds, TimeUnit.SECONDS);
+    executor.scheduleWithFixedDelay(this::cleanUp, cleanUpPeriodSeconds, 
cleanUpPeriodSeconds, TimeUnit.SECONDS);
   }
 
   @Override
@@ -101,8 +107,15 @@ public class DbRemoteConfigurationMonitorService 
implements RemoteConfigurationM
   }
 
   private void syncLocalWithRemote(List<RemoteConfig> remoteConfigs, 
LocalDirectory localDir) {
-    createOrUpdateLocalFiles(remoteConfigs, localDir);
-    deleteLocalFiles(remoteConfigs, localDir);
+    List<RemoteConfig> existingConfigs = remoteConfigs.stream()
+            .filter(each -> !each.isDeleted())
+            .collect(toList());
+    createOrUpdateLocalFiles(existingConfigs, localDir);
+    Set<String> deletedConfigs = remoteConfigs.stream()
+            .filter(RemoteConfig::isDeleted)
+            .map(RemoteConfig::getName)
+            .collect(Collectors.toSet());
+    deleteLocalFiles(deletedConfigs, localDir);
   }
 
   private void createOrUpdateLocalFiles(List<RemoteConfig> remoteConfigs, 
LocalDirectory localDir) {
@@ -137,13 +150,21 @@ public class DbRemoteConfigurationMonitorService 
implements RemoteConfigurationM
     }
   }
 
-  private void deleteLocalFiles(List<RemoteConfig> remoteConfigs, 
LocalDirectory localDir) {
-    Set<String> remoteConfigNames = 
remoteConfigs.stream().map(RemoteConfig::getName).collect(toSet());
+  private void deleteLocalFiles(Set<String> deletedRemoteConfigNames, 
LocalDirectory localDir) {
     for (String localFileName : localDir.list()) {
-      if (!remoteConfigNames.contains(localFileName)) {
-        LOG.deletingProviderDescriptor(localFileName, localDir);
-        localDir.deleteFile(localFileName);
+      if (deletedRemoteConfigNames.contains(localFileName)) {
+        if (localDir.deleteFile(localFileName)) {
+          LOG.deletingProviderDescriptor(localFileName, localDir);
+        }
       }
     }
   }
+
+  /**
+   * Remove entries which were logically deleted, and they're older than the 
given cleanUpPeriodHours
+   */
+  private void cleanUp() {
+    LOG.cleaningRemoteConfigTables(cleanUpPeriodSeconds);
+    db.cleanTables(cleanUpPeriodSeconds);
+  }
 }
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/RemoteConfig.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/RemoteConfig.java
index 87c9f87d5..6ee353931 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/RemoteConfig.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/db/RemoteConfig.java
@@ -22,11 +22,17 @@ public class RemoteConfig {
   private final String name;
   private final String content;
   private final Instant lastModified;
+  private final boolean deleted;
 
   public RemoteConfig(String name, String content, Instant lastModified) {
+    this(name, content, lastModified, false);
+  }
+
+  public RemoteConfig(String name, String content, Instant lastModified, 
boolean deleted) {
     this.name = name;
     this.content = content;
     this.lastModified = lastModified;
+    this.deleted = deleted;
   }
 
   public String getName() {
@@ -40,4 +46,8 @@ public class RemoteConfig {
   public Instant getLastModified() {
     return lastModified;
   }
+
+  public boolean isDeleted() {
+    return deleted;
+  }
 }
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
index 891c526b0..e298d6a6a 100644
--- 
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
@@ -55,10 +55,16 @@ public class RemoteConfigDatabase {
     }
   }
 
+  /**
+   * @return all remote providers, including the deleted ones
+   */
   public List<RemoteConfig> selectProviders() {
     return selectFrom(KNOX_PROVIDERS_TABLE_NAME);
   }
 
+  /**
+   * @return all remote descriptors, including the deleted one
+   */
   public List<RemoteConfig> selectDescriptors() {
     return selectFrom(KNOX_DESCRIPTORS_TABLE_NAME);
   }
@@ -66,10 +72,16 @@ public class RemoteConfigDatabase {
   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)) {
+         PreparedStatement statement = connection.prepareStatement(
+                 "SELECT name, content, last_modified_time, deleted FROM " + 
tableName)) {
       try (ResultSet rs = statement.executeQuery()) {
         while(rs.next()) {
-          result.add(new RemoteConfig(rs.getString(1), rs.getString(2), 
rs.getTimestamp(3).toInstant()));
+          result.add(new RemoteConfig(
+                  rs.getString(1),
+                  rs.getString(2),
+                  rs.getTimestamp(3).toInstant(),
+                  rs.getBoolean(4)
+          ));
         }
       }
     } catch (SQLException e) {
@@ -97,10 +109,11 @@ public class RemoteConfigDatabase {
       if (exists(name, tableName)) {
         try (Connection connection = dataSource.getConnection();
              PreparedStatement statement = connection.prepareStatement(
-                     "UPDATE " + tableName + " SET content = ?, 
last_modified_time = ? WHERE name = ?")) {
+                     "UPDATE " + tableName + " SET content = ?, 
last_modified_time = ?, deleted = ? WHERE name = ?")) {
           statement.setString(1, content);
           statement.setTimestamp(2, Timestamp.from(Instant.now()));
-          statement.setString(3, name);
+          statement.setBoolean(3, false);
+          statement.setString(4, name);
           return statement.executeUpdate() == 1;
         }
       } else {
@@ -129,22 +142,48 @@ public class RemoteConfigDatabase {
     }
   }
 
+  /**
+   * Logically delete a provider
+   */
   public boolean deleteProvider(String name) {
-    return delete(name, KNOX_PROVIDERS_TABLE_NAME);
+    return deleteLogical(name, KNOX_PROVIDERS_TABLE_NAME);
   }
 
+  /**
+   * Logically delete a descriptor
+   */
   public boolean deleteDescriptor(String name) {
-    return delete(name, KNOX_DESCRIPTORS_TABLE_NAME);
+    return deleteLogical(name, KNOX_DESCRIPTORS_TABLE_NAME);
   }
 
-  private boolean delete(String name, String tableName) {
+  private boolean deleteLogical(String name, String tableName) {
     try (Connection connection = dataSource.getConnection();
          PreparedStatement statement = connection.prepareStatement(
-                 "DELETE FROM " + tableName + " WHERE name = ?")) {
-      statement.setString(1, name);
+                 "UPDATE " + tableName + " SET deleted = ?, last_modified_time 
= ? WHERE name = ?")) {
+      statement.setBoolean(1, true);
+      statement.setTimestamp(2, Timestamp.from(Instant.now()));
+      statement.setString(3, name);
       return statement.executeUpdate() == 1;
     } catch (SQLException e) {
       throw new RuntimeException(e);
     }
   }
+
+  public int cleanTables(int olderThanSeconds) {
+    Instant instant = Instant.now().minusSeconds(olderThanSeconds);
+    return cleanTable(KNOX_PROVIDERS_TABLE_NAME, instant)
+            + cleanTable(KNOX_DESCRIPTORS_TABLE_NAME, instant);
+  }
+
+  private int cleanTable(String tableName, Instant instant) {
+    try (Connection connection = dataSource.getConnection();
+         PreparedStatement statement = connection.prepareStatement(
+                 "DELETE FROM " + tableName + " WHERE deleted = ? AND 
last_modified_time <= ?")) {
+      statement.setBoolean(1, true);
+      statement.setTimestamp(2, Timestamp.from(instant));
+      return statement.executeUpdate();
+    } catch (SQLException e) {
+      throw new RuntimeException(e);
+    }
+  }
 }
diff --git a/gateway-server/src/main/resources/createKnoxProvidersTable.sql 
b/gateway-server/src/main/resources/createKnoxProvidersTable.sql
index da4577a5c..a4eda9dd9 100644
--- a/gateway-server/src/main/resources/createKnoxProvidersTable.sql
+++ b/gateway-server/src/main/resources/createKnoxProvidersTable.sql
@@ -17,5 +17,6 @@ CREATE TABLE IF NOT EXISTS KNOX_PROVIDERS ( -- IF NOT EXISTS 
syntax is not suppo
    name varchar(256) NOT NULL,
    content TEXT NOT NULL,
    last_modified_time TIMESTAMP NOT NULL,
+   deleted boolean DEFAULT false 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
index 1887d1695..614c7a139 100644
--- a/gateway-server/src/main/resources/createKnoxTokenDescriptorsTable.sql
+++ b/gateway-server/src/main/resources/createKnoxTokenDescriptorsTable.sql
@@ -17,5 +17,6 @@ CREATE TABLE IF NOT EXISTS KNOX_DESCRIPTORS ( -- IF NOT 
EXISTS syntax is not sup
    name varchar(256) NOT NULL,
    content TEXT NOT NULL,
    last_modified_time TIMESTAMP NOT NULL,
+   deleted boolean DEFAULT false NOT NULL,
    PRIMARY KEY (name)
 )
\ No newline at end of file
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
index 466595661..35e78c282 100644
--- 
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
@@ -21,6 +21,7 @@ import static java.util.Collections.emptyList;
 import static java.util.Collections.emptySet;
 
 import java.time.Instant;
+import java.util.Arrays;
 import java.util.HashSet;
 
 import org.easymock.EasyMock;
@@ -40,7 +41,7 @@ public class DbRemoteConfigurationMonitorServiceTest {
     db = EasyMock.createMock(RemoteConfigDatabase.class);
     providersDir = EasyMock.createMock(LocalDirectory.class);
     descriptorsDir = EasyMock.createMock(LocalDirectory.class);
-    monitor = new DbRemoteConfigurationMonitorService(db, providersDir, 
descriptorsDir, 60);
+    monitor = new DbRemoteConfigurationMonitorService(db, providersDir, 
descriptorsDir, 60, 3600);
     monitor.start();
   }
 
@@ -75,13 +76,15 @@ public class DbRemoteConfigurationMonitorServiceTest {
 
   @Test
   public void testDeletesProviderFromFileSystem() throws Exception {
-    EasyMock.expect(providersDir.list()).andReturn(new 
HashSet<>(asList("local"))).anyTimes();
+    EasyMock.expect(providersDir.list()).andReturn(new 
HashSet<>(asList("to-be-deleted"))).anyTimes();
     EasyMock.expect(descriptorsDir.list()).andReturn(emptySet()).anyTimes();
 
+    EasyMock.expect(db.selectProviders()).andReturn(Arrays.asList(
+            new RemoteConfig("to-be-deleted", "any", NOW, true)
+    )).anyTimes();
     EasyMock.expect(db.selectDescriptors()).andReturn(emptyList()).anyTimes();
-    EasyMock.expect(db.selectProviders()).andReturn(emptyList()).anyTimes();
 
-    EasyMock.expect(providersDir.deleteFile("local")).andReturn(true).once();
+    
EasyMock.expect(providersDir.deleteFile("to-be-deleted")).andReturn(true).once();
 
     EasyMock.replay(providersDir, descriptorsDir, db);
     monitor.sync();
@@ -109,7 +112,8 @@ public class DbRemoteConfigurationMonitorServiceTest {
     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.selectDescriptors()).andReturn(Arrays.asList(
+            new RemoteConfig("local", "any", NOW, true))).anyTimes();
     EasyMock.expect(db.selectProviders()).andReturn(emptyList()).anyTimes();
 
     EasyMock.expect(descriptorsDir.deleteFile("local")).andReturn(true).once();
@@ -136,7 +140,8 @@ public class DbRemoteConfigurationMonitorServiceTest {
 
     EasyMock.expect(db.selectDescriptors()).andReturn(asList(
             new RemoteConfig("desc1", "desc1-same-content", NOW),
-            new RemoteConfig("desc2", "desc2-remote-content", NOW)
+            new RemoteConfig("desc2", "desc2-remote-content", NOW),
+            new RemoteConfig("desc3-to-be-deleted", "any", NOW, true)
     )).anyTimes();
 
     // Expectations
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
index baa9aecce..34c558482 100644
--- 
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
@@ -17,7 +17,9 @@
 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 static org.junit.Assert.fail;
 
 import java.sql.Connection;
 import java.sql.Statement;
@@ -128,14 +130,69 @@ public class RemoteConfigDatabaseTest {
 
     db.deleteProvider("provider_2");
 
-    assertEquals(1, db.selectProviders().size());
+    assertEquals(2, db.selectProviders().size());
+    for (RemoteConfig provider : db.selectProviders()) {
+      if (provider.getName().equals("provider_1")) {
+        assertFalse(provider.isDeleted());
+      } else if (provider.getName().equals("provider_2")) {
+        assertTrue(provider.isDeleted());
+      } else {
+        fail("unexpected name: " + provider.getName());
+      }
+    }
+
+    db.deleteDescriptor("descriptor_1");
+
+    assertEquals(2, db.selectProviders().size());
     assertEquals(1, db.selectDescriptors().size());
 
+    assertTrue(db.selectDescriptors().get(0).isDeleted());
+  }
+
+  @Test
+  public void testPutBackDeletedOne() {
+    db.putProvider("provider_1", "test provider1 content");
+    db.putDescriptor("descriptor_1", "test descriptor content");
+
+    db.deleteProvider("provider_1");
     db.deleteDescriptor("descriptor_1");
 
     assertEquals(1, db.selectProviders().size());
-    assertEquals(0, db.selectDescriptors().size());
+    assertTrue(db.selectProviders().get(0).isDeleted());
+    assertEquals(1, db.selectDescriptors().size());
+    assertTrue(db.selectDescriptors().get(0).isDeleted());
+
+    db.putProvider("provider_1", "test provider1 new content");
+    db.putDescriptor("descriptor_1", "test descriptor new content");
+
+    assertEquals(1, db.selectProviders().size());
+    assertEquals(1, db.selectDescriptors().size());
+
+    assertEquals(1, db.selectProviders().size());
+    assertFalse(db.selectProviders().get(0).isDeleted());
+    assertEquals(1, db.selectDescriptors().size());
+    assertFalse(db.selectDescriptors().get(0).isDeleted());
+
+    assertEquals("test provider1 new content", 
db.selectProviders().get(0).getContent());
+    assertEquals("test descriptor new content", 
db.selectDescriptors().get(0).getContent());
+  }
+
+  @Test
+  public void testPhysicalDelete() {
+    db.putProvider("provider_1", "any");
+    db.putDescriptor("descriptor_1", "any");
 
-    assertEquals("provider_1", db.selectProviders().get(0).getName());
+    assertEquals(0, db.cleanTables(0));
+
+    assertEquals(1, db.selectProviders().size());
+    assertEquals(1, db.selectDescriptors().size());
+
+    db.deleteDescriptor("descriptor_1");
+    db.deleteProvider("provider_1");
+
+    assertEquals(2, db.cleanTables(0));
+
+    assertEquals(0, db.selectProviders().size());
+    assertEquals(0, db.selectDescriptors().size());
   }
 }
\ 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 f9b87296b..fb70ded11 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
@@ -1015,6 +1015,11 @@ public class GatewayTestConfig extends Configuration 
implements GatewayConfig {
     return 30;
   }
 
+  @Override
+  public int getDbRemoteConfigMonitorCleanUpInterval() {
+    return 1;
+  }
+
   @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 423b2e6a6..ced53f013 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
@@ -862,6 +862,8 @@ public interface GatewayConfig {
 
   long getDbRemoteConfigMonitorPollingInterval();
 
+  int getDbRemoteConfigMonitorCleanUpInterval();
+
   long getConcurrentSessionVerifierExpiredTokensCleaningPeriod();
 
   /**

Reply via email to