Blame SOURCES/0011-Issue-4778-RFE-Allow-setting-TOD-for-db-compaction-a.patch

e4a41f
From c79630de8012a893ed3d1c46b41bc7871a07a3e2 Mon Sep 17 00:00:00 2001
e4a41f
From: Mark Reynolds <mreynolds@redhat.com>
e4a41f
Date: Wed, 26 May 2021 13:32:13 -0400
e4a41f
Subject: [PATCH 11/12] Issue 4778 - RFE - Allow setting TOD for db compaction
e4a41f
 and add task
e4a41f
e4a41f
Description:  Since database compaction can be costly it should be allowed
e4a41f
              to set a time to execute it during offpeak hours.  Once the
e4a41f
              compaction interval has been met, it will wait for the configured
e4a41f
              time of day to do the compaction.  The default is just before
e4a41f
              midnight: 23:59
e4a41f
e4a41f
              A task was also created that can run compaction on demand,
e4a41f
              and can also just target the replication changelog.  This could
e4a41f
              be used in conjunction with a cronjob for more complex
e4a41f
              execution patterns.
e4a41f
e4a41f
ASAN tested and approved.
e4a41f
e4a41f
relates: https://github.com/389ds/389-ds-base/issues/4778
e4a41f
e4a41f
Reviewed by: spichugi(Thanks!)
e4a41f
---
e4a41f
 .../tests/suites/config/compact_test.py       |  81 ++++++
e4a41f
 ldap/schema/01core389.ldif                    |   3 +-
e4a41f
 ldap/servers/plugins/replication/cl5.h        |   1 +
e4a41f
 ldap/servers/plugins/replication/cl5_api.c    |  70 ++++-
e4a41f
 ldap/servers/plugins/replication/cl5_api.h    |   2 +-
e4a41f
 .../servers/plugins/replication/cl5_clcache.c |   3 -
e4a41f
 ldap/servers/plugins/replication/cl5_config.c | 102 ++++++-
e4a41f
 ldap/servers/plugins/replication/cl5_init.c   |   2 +-
e4a41f
 .../servers/plugins/replication/repl_shared.h |   2 +
e4a41f
 ldap/servers/plugins/retrocl/retrocl.c        |   1 -
e4a41f
 .../slapd/back-ldbm/db-bdb/bdb_config.c       |  79 ++++++
e4a41f
 .../slapd/back-ldbm/db-bdb/bdb_layer.c        | 258 ++++++++++++------
e4a41f
 .../slapd/back-ldbm/db-bdb/bdb_layer.h        |   4 +-
e4a41f
 ldap/servers/slapd/back-ldbm/init.c           |   2 +
e4a41f
 ldap/servers/slapd/back-ldbm/ldbm_config.h    |   1 +
e4a41f
 .../servers/slapd/back-ldbm/proto-back-ldbm.h |   1 +
e4a41f
 ldap/servers/slapd/filtercmp.c                |   5 +-
e4a41f
 ldap/servers/slapd/pblock.c                   |  17 +-
e4a41f
 ldap/servers/slapd/slap.h                     |   2 +
e4a41f
 ldap/servers/slapd/slapi-private.h            |   1 +
e4a41f
 ldap/servers/slapd/task.c                     | 102 ++++++-
e4a41f
 src/cockpit/389-console/src/database.jsx      |   1 +
e4a41f
 .../src/lib/database/databaseConfig.jsx       |  16 +-
e4a41f
 src/lib389/lib389/_constants.py               |   1 +
e4a41f
 src/lib389/lib389/backend.py                  |   1 +
e4a41f
 src/lib389/lib389/cli_conf/backend.py         |  24 +-
e4a41f
 src/lib389/lib389/cli_conf/replication.py     |   3 +
e4a41f
 src/lib389/lib389/tasks.py                    |  14 +-
e4a41f
 28 files changed, 689 insertions(+), 110 deletions(-)
e4a41f
 create mode 100644 dirsrvtests/tests/suites/config/compact_test.py
e4a41f
e4a41f
diff --git a/dirsrvtests/tests/suites/config/compact_test.py b/dirsrvtests/tests/suites/config/compact_test.py
e4a41f
new file mode 100644
e4a41f
index 000000000..1f1c097e4
e4a41f
--- /dev/null
e4a41f
+++ b/dirsrvtests/tests/suites/config/compact_test.py
e4a41f
@@ -0,0 +1,81 @@
e4a41f
+import logging
e4a41f
+import pytest
e4a41f
+import os
e4a41f
+import time
e4a41f
+from lib389.tasks import DBCompactTask
e4a41f
+from lib389.backend import DatabaseConfig
e4a41f
+from lib389.replica import Changelog5
e4a41f
+from lib389.topologies import topology_m1 as topo
e4a41f
+
e4a41f
+log = logging.getLogger(__name__)
e4a41f
+
e4a41f
+
e4a41f
+def test_compact_db_task(topo):
e4a41f
+    """Specify a test case purpose or name here
e4a41f
+
e4a41f
+    :id: 1b3222ef-a336-4259-be21-6a52f76e1859
e4a41f
+    :setup: Standalone Instance
e4a41f
+    :steps:
e4a41f
+        1. Create task
e4a41f
+        2. Check task was successful
e4a41f
+        3. Check errors log to show task was run
e4a41f
+        3. Create task just for replication
e4a41f
+    :expectedresults:
e4a41f
+        1. Success
e4a41f
+        2. Success
e4a41f
+        3. Success
e4a41f
+        4. Success
e4a41f
+    """
e4a41f
+    inst = topo.ms["supplier1"]
e4a41f
+
e4a41f
+    task = DBCompactTask(inst)
e4a41f
+    task.create()
e4a41f
+    task.wait()
e4a41f
+    assert task.get_exit_code() == 0
e4a41f
+
e4a41f
+    # Check errors log to make sure task actually compacted db
e4a41f
+    assert inst.searchErrorsLog("Compacting databases")
e4a41f
+    inst.deleteErrorLogs(restart=False)
e4a41f
+
e4a41f
+
e4a41f
+def test_compaction_interval_and_time(topo):
e4a41f
+    """Specify a test case purpose or name here
e4a41f
+
e4a41f
+    :id: f361bee9-d7e7-4569-9255-d7b60dd9d92e
e4a41f
+    :setup: Supplier Instance
e4a41f
+    :steps:
e4a41f
+        1. Configure compact interval and time for database and changelog
e4a41f
+        2. Check compaction occurs as expected
e4a41f
+    :expectedresults:
e4a41f
+        1. Success
e4a41f
+        2. Success
e4a41f
+    """
e4a41f
+
e4a41f
+    inst = topo.ms["supplier1"]
e4a41f
+
e4a41f
+    # Configure DB compaction
e4a41f
+    config = DatabaseConfig(inst)
e4a41f
+    config.set([('nsslapd-db-compactdb-interval', '2'), ('nsslapd-db-compactdb-time', '00:01')])
e4a41f
+
e4a41f
+    # Configure changelog compaction
e4a41f
+    cl5 = Changelog5(inst)
e4a41f
+    cl5.replace_many(
e4a41f
+        ('nsslapd-changelogcompactdb-interval', '2'),
e4a41f
+        ('nsslapd-changelogcompactdb-time', '00:01'),
e4a41f
+        ('nsslapd-changelogtrim-interval',  '2')
e4a41f
+    )
e4a41f
+    inst.deleteErrorLogs()
e4a41f
+
e4a41f
+    # Check is compaction occurred
e4a41f
+    time.sleep(6)
e4a41f
+    assert inst.searchErrorsLog("Compacting databases")
e4a41f
+    assert inst.searchErrorsLog("compacting replication changelogs")
e4a41f
+    inst.deleteErrorLogs(restart=False)
e4a41f
+
e4a41f
+
e4a41f
+if __name__ == '__main__':
e4a41f
+    # Run isolated
e4a41f
+    # -s for DEBUG mode
e4a41f
+    CURRENT_FILE = os.path.realpath(__file__)
e4a41f
+    pytest.main(["-s", CURRENT_FILE])
e4a41f
+
e4a41f
diff --git a/ldap/schema/01core389.ldif b/ldap/schema/01core389.ldif
e4a41f
index 9e9a26c21..0c73e5114 100644
e4a41f
--- a/ldap/schema/01core389.ldif
e4a41f
+++ b/ldap/schema/01core389.ldif
e4a41f
@@ -285,6 +285,7 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2310 NAME 'nsds5ReplicaFlowControlWindow
e4a41f
 attributeTypes: ( 2.16.840.1.113730.3.1.2311 NAME 'nsds5ReplicaFlowControlPause' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
e4a41f
 attributeTypes: ( 2.16.840.1.113730.3.1.2313 NAME 'nsslapd-changelogtrim-interval' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
e4a41f
 attributeTypes: ( 2.16.840.1.113730.3.1.2314 NAME 'nsslapd-changelogcompactdb-interval' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
e4a41f
+attributeTypes: ( 2.16.840.1.113730.3.1.2385 NAME 'nsslapd-changelogcompactdb-time' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
e4a41f
 attributeTypes: ( 2.16.840.1.113730.3.1.2315 NAME 'nsDS5ReplicaWaitForAsyncResults' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
e4a41f
 attributeTypes: ( 2.16.840.1.113730.3.1.2316 NAME 'nsslapd-auditfaillog-maxlogsize' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
e4a41f
 attributeTypes: ( 2.16.840.1.113730.3.1.2317 NAME 'nsslapd-auditfaillog-logrotationsync-enabled' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
e4a41f
@@ -345,5 +346,5 @@ objectClasses: ( nsEncryptionConfig-oid NAME 'nsEncryptionConfig' DESC 'Netscape
e4a41f
 objectClasses: ( nsEncryptionModule-oid NAME 'nsEncryptionModule' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( nsSSLToken $ nsSSLPersonalityssl $ nsSSLActivation $ ServerKeyExtractFile $ ServerCertExtractFile ) X-ORIGIN 'Netscape' )
e4a41f
 objectClasses: ( 2.16.840.1.113730.3.2.327 NAME 'rootDNPluginConfig' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( rootdn-open-time $ rootdn-close-time $ rootdn-days-allowed $ rootdn-allow-host $ rootdn-deny-host $ rootdn-allow-ip $ rootdn-deny-ip ) X-ORIGIN 'Netscape' )
e4a41f
 objectClasses: ( 2.16.840.1.113730.3.2.328 NAME 'nsSchemaPolicy' DESC 'Netscape defined objectclass' SUP top  MAY ( cn $ schemaUpdateObjectclassAccept $ schemaUpdateObjectclassReject $ schemaUpdateAttributeAccept $ schemaUpdateAttributeReject) X-ORIGIN 'Netscape Directory Server' )
e4a41f
-objectClasses: ( 2.16.840.1.113730.3.2.332 NAME 'nsChangelogConfig' DESC 'Configuration of the changelog5 object' SUP top MUST ( cn $ nsslapd-changelogdir ) MAY ( nsslapd-changelogmaxage $ nsslapd-changelogtrim-interval $ nsslapd-changelogmaxentries $ nsslapd-changelogsuffix $ nsslapd-changelogcompactdb-interval $ nsslapd-encryptionalgorithm $ nsSymmetricKey ) X-ORIGIN '389 Directory Server' )
e4a41f
+objectClasses: ( 2.16.840.1.113730.3.2.332 NAME 'nsChangelogConfig' DESC 'Configuration of the changelog5 object' SUP top MUST ( cn $ nsslapd-changelogdir ) MAY ( nsslapd-changelogmaxage $ nsslapd-changelogtrim-interval $ nsslapd-changelogmaxentries $ nsslapd-changelogsuffix $ nsslapd-changelogcompactdb-interval $ nsslapd-changelogcompactdb-time $ nsslapd-encryptionalgorithm $ nsSymmetricKey ) X-ORIGIN '389 Directory Server' )
e4a41f
 objectClasses: ( 2.16.840.1.113730.3.2.337 NAME 'rewriterEntry' DESC '' SUP top MUST ( nsslapd-libPath ) MAY ( cn $ nsslapd-filterrewriter $ nsslapd-returnedAttrRewriter ) X-ORIGIN '389 Directory Server' )
e4a41f
diff --git a/ldap/servers/plugins/replication/cl5.h b/ldap/servers/plugins/replication/cl5.h
e4a41f
index 2af57e369..99ea1c6a2 100644
e4a41f
--- a/ldap/servers/plugins/replication/cl5.h
e4a41f
+++ b/ldap/servers/plugins/replication/cl5.h
e4a41f
@@ -29,6 +29,7 @@ typedef struct changelog5Config
e4a41f
     char *symmetricKey;
e4a41f
     long compactInterval;
e4a41f
     long trimInterval;
e4a41f
+    char *compactTime;
e4a41f
 } changelog5Config;
e4a41f
 
e4a41f
 /* initializes changelog*/
e4a41f
diff --git a/ldap/servers/plugins/replication/cl5_api.c b/ldap/servers/plugins/replication/cl5_api.c
e4a41f
index 403a6a666..75a2f46f5 100644
e4a41f
--- a/ldap/servers/plugins/replication/cl5_api.c
e4a41f
+++ b/ldap/servers/plugins/replication/cl5_api.c
e4a41f
@@ -158,6 +158,7 @@ typedef struct cl5trim
e4a41f
     time_t maxAge;       /* maximum entry age in seconds                            */
e4a41f
     int maxEntries;      /* maximum number of entries across all changelog files    */
e4a41f
     int compactInterval; /* interval to compact changelog db */
e4a41f
+    char *compactTime;   /* time to compact changelog db */
e4a41f
     int trimInterval;    /* trimming interval */
e4a41f
     PRLock *lock;        /* controls access to trimming configuration            */
e4a41f
 } CL5Trim;
e4a41f
@@ -184,6 +185,7 @@ typedef struct cl5desc
e4a41f
     PRLock *clLock;         /* Lock associated to clVar, used to notify threads on close */
e4a41f
     PRCondVar *clCvar;      /* Condition Variable used to notify threads on close */
e4a41f
     void *clcrypt_handle;   /* for cl encryption */
e4a41f
+    char *compact_time;     /* Time to execute changelog compaction */
e4a41f
 } CL5Desc;
e4a41f
 
e4a41f
 typedef void (*VFP)(void *);
e4a41f
@@ -1025,7 +1027,7 @@ cl5GetState()
e4a41f
                 CL5_BAD_STATE if changelog is not open
e4a41f
  */
e4a41f
 int
e4a41f
-cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, int trimInterval)
e4a41f
+cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, char *compactTime, int trimInterval)
e4a41f
 {
e4a41f
     if (s_cl5Desc.dbState == CL5_STATE_NONE) {
e4a41f
         slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl,
e4a41f
@@ -1061,6 +1063,10 @@ cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, int t
e4a41f
         s_cl5Desc.dbTrim.compactInterval = compactInterval;
e4a41f
     }
e4a41f
 
e4a41f
+    if (strcmp(compactTime, CL5_STR_IGNORE) != 0) {
e4a41f
+        s_cl5Desc.dbTrim.compactTime = slapi_ch_strdup(compactTime);
e4a41f
+    }
e4a41f
+
e4a41f
     if (trimInterval != CL5_NUM_IGNORE) {
e4a41f
         s_cl5Desc.dbTrim.trimInterval = trimInterval;
e4a41f
     }
e4a41f
@@ -3077,16 +3083,48 @@ _cl5TrimCleanup(void)
e4a41f
 {
e4a41f
     if (s_cl5Desc.dbTrim.lock)
e4a41f
         PR_DestroyLock(s_cl5Desc.dbTrim.lock);
e4a41f
+    slapi_ch_free_string(&s_cl5Desc.dbTrim.compactTime);
e4a41f
 
e4a41f
     memset(&s_cl5Desc.dbTrim, 0, sizeof(s_cl5Desc.dbTrim));
e4a41f
 }
e4a41f
 
e4a41f
+static time_t
e4a41f
+_cl5_get_tod_expiration(char *expire_time)
e4a41f
+{
e4a41f
+    time_t start_time, todays_elapsed_time, now = time(NULL);
e4a41f
+    struct tm *tm_struct = localtime(&now;;
e4a41f
+    char hour_str[3] = {0};
e4a41f
+    char min_str[3] = {0};
e4a41f
+    char *s = expire_time;
e4a41f
+    char *endp = NULL;
e4a41f
+    int32_t hour, min, expiring_time;
e4a41f
+
e4a41f
+    /* Get today's start time */
e4a41f
+    todays_elapsed_time = (tm_struct->tm_hour * 3600) + (tm_struct->tm_min * 60) + (tm_struct->tm_sec);
e4a41f
+    start_time = slapi_current_utc_time() - todays_elapsed_time;
e4a41f
+
e4a41f
+    /* Get the hour and minute and calculate the expiring time.  The time was
e4a41f
+     * already validated in bdb_config.c:  HH:MM */
e4a41f
+    hour_str[0] = *s++;
e4a41f
+    hour_str[1] = *s++;
e4a41f
+    s++;  /* skip colon */
e4a41f
+    min_str[0] = *s++;
e4a41f
+    min_str[1] = *s++;
e4a41f
+    hour = strtoll(hour_str, &endp, 10);
e4a41f
+    min = strtoll(min_str, &endp, 10);
e4a41f
+    expiring_time = (hour * 60 * 60) + (min * 60);
e4a41f
+
e4a41f
+    return start_time + expiring_time;
e4a41f
+}
e4a41f
+
e4a41f
 static int
e4a41f
 _cl5TrimMain(void *param __attribute__((unused)))
e4a41f
 {
e4a41f
     time_t timePrev = slapi_current_utc_time();
e4a41f
     time_t timeCompactPrev = slapi_current_utc_time();
e4a41f
     time_t timeNow;
e4a41f
+    PRBool compacting = PR_FALSE;
e4a41f
+    int32_t compactdb_time = 0;
e4a41f
 
e4a41f
     PR_AtomicIncrement(&s_cl5Desc.threadCount);
e4a41f
 
e4a41f
@@ -3097,11 +3135,26 @@ _cl5TrimMain(void *param __attribute__((unused)))
e4a41f
             timePrev = timeNow;
e4a41f
             _cl5DoTrimming();
e4a41f
         }
e4a41f
+
e4a41f
+        if (!compacting) {
e4a41f
+            /* Once we know we want to compact we need to stop refreshing the
e4a41f
+             * TOD expiration. Otherwise if the compact time is close to
e4a41f
+             * midnight we could roll over past midnight during the checkpoint
e4a41f
+             * sleep interval, and we'd never actually compact the databases.
e4a41f
+             * We also need to get this value before the sleep.
e4a41f
+            */
e4a41f
+            compactdb_time = _cl5_get_tod_expiration(s_cl5Desc.dbTrim.compactTime);
e4a41f
+        }
e4a41f
         if ((s_cl5Desc.dbTrim.compactInterval > 0) &&
e4a41f
-            (timeNow - timeCompactPrev >= s_cl5Desc.dbTrim.compactInterval)) {
e4a41f
-            /* time to trim */
e4a41f
-            timeCompactPrev = timeNow;
e4a41f
-            _cl5CompactDBs();
e4a41f
+            (timeNow - timeCompactPrev >= s_cl5Desc.dbTrim.compactInterval))
e4a41f
+        {
e4a41f
+            compacting = PR_TRUE;
e4a41f
+            if (slapi_current_utc_time() > compactdb_time) {
e4a41f
+				/* time to trim */
e4a41f
+				timeCompactPrev = timeNow;
e4a41f
+				_cl5CompactDBs();
e4a41f
+				compacting = PR_FALSE;
e4a41f
+            }
e4a41f
         }
e4a41f
         if (NULL == s_cl5Desc.clLock) {
e4a41f
             /* most likely, emergency */
e4a41f
@@ -3215,6 +3268,10 @@ _cl5CompactDBs(void)
e4a41f
                       rc, db_strerror(rc));
e4a41f
         goto bail;
e4a41f
     }
e4a41f
+
e4a41f
+
e4a41f
+    slapi_log_err(SLAPI_LOG_NOTICE, repl_plugin_name_cl,
e4a41f
+                  "_cl5CompactDBs - compacting replication changelogs...\n");
e4a41f
     for (fileObj = objset_first_obj(s_cl5Desc.dbFiles);
e4a41f
          fileObj;
e4a41f
          fileObj = objset_next_obj(s_cl5Desc.dbFiles, fileObj)) {
e4a41f
@@ -3235,6 +3292,9 @@ _cl5CompactDBs(void)
e4a41f
                       "_cl5CompactDBs - %s - %d pages freed\n",
e4a41f
                       dbFile->replName, c_data.compact_pages_free);
e4a41f
     }
e4a41f
+
e4a41f
+    slapi_log_err(SLAPI_LOG_NOTICE, repl_plugin_name_cl,
e4a41f
+                  "_cl5CompactDBs - compacting replication changelogs finished.\n");
e4a41f
 bail:
e4a41f
     if (fileObj) {
e4a41f
         object_release(fileObj);
e4a41f
diff --git a/ldap/servers/plugins/replication/cl5_api.h b/ldap/servers/plugins/replication/cl5_api.h
e4a41f
index 302af97a0..4b0949fb3 100644
e4a41f
--- a/ldap/servers/plugins/replication/cl5_api.h
e4a41f
+++ b/ldap/servers/plugins/replication/cl5_api.h
e4a41f
@@ -236,7 +236,7 @@ int cl5GetState(void);
e4a41f
    Return:        CL5_SUCCESS if successful;
e4a41f
                 CL5_BAD_STATE if changelog has not been open
e4a41f
  */
e4a41f
-int cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, int trimInterval);
e4a41f
+int cl5ConfigTrimming(int maxEntries, const char *maxAge, int compactInterval, char *compactTime, int trimInterval);
e4a41f
 
e4a41f
 void cl5DestroyIterator(void *iterator);
e4a41f
 
e4a41f
diff --git a/ldap/servers/plugins/replication/cl5_clcache.c b/ldap/servers/plugins/replication/cl5_clcache.c
e4a41f
index 90dec4d54..e5a39c9c1 100644
e4a41f
--- a/ldap/servers/plugins/replication/cl5_clcache.c
e4a41f
+++ b/ldap/servers/plugins/replication/cl5_clcache.c
e4a41f
@@ -452,9 +452,6 @@ static int
e4a41f
 clcache_cursor_set(DBC *cursor, CLC_Buffer *buf)
e4a41f
 {
e4a41f
     int rc;
e4a41f
-    uint32_t ulen;
e4a41f
-    uint32_t dlen;
e4a41f
-    uint32_t size;
e4a41f
 
e4a41f
     rc = cursor->c_get(cursor, &buf->buf_key, &buf->buf_data, DB_SET);
e4a41f
     if (rc == DB_BUFFER_SMALL) {
e4a41f
diff --git a/ldap/servers/plugins/replication/cl5_config.c b/ldap/servers/plugins/replication/cl5_config.c
e4a41f
index e0530bed2..b32686788 100644
e4a41f
--- a/ldap/servers/plugins/replication/cl5_config.c
e4a41f
+++ b/ldap/servers/plugins/replication/cl5_config.c
e4a41f
@@ -131,6 +131,7 @@ changelog5_config_done(changelog5Config *config)
e4a41f
         /* slapi_ch_free_string accepts NULL pointer */
e4a41f
         slapi_ch_free_string(&config->maxAge);
e4a41f
         slapi_ch_free_string(&config->dir);
e4a41f
+        slapi_ch_free_string(&config->compactTime);
e4a41f
         slapi_ch_free_string(&config->symmetricKey);
e4a41f
         slapi_ch_free_string(&config->dbconfig.encryptionAlgorithm);
e4a41f
         slapi_ch_free_string(&config->dbconfig.symmetricKey);
e4a41f
@@ -211,7 +212,7 @@ changelog5_config_add(Slapi_PBlock *pb __attribute__((unused)),
e4a41f
     }
e4a41f
 
e4a41f
     /* set trimming parameters */
e4a41f
-    rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.trimInterval);
e4a41f
+    rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.compactTime, config.trimInterval);
e4a41f
     if (rc != CL5_SUCCESS) {
e4a41f
         *returncode = 1;
e4a41f
         if (returntext) {
e4a41f
@@ -302,6 +303,7 @@ changelog5_config_modify(Slapi_PBlock *pb,
e4a41f
     config.compactInterval = CL5_NUM_IGNORE;
e4a41f
     slapi_ch_free_string(&config.maxAge);
e4a41f
     config.maxAge = slapi_ch_strdup(CL5_STR_IGNORE);
e4a41f
+    config.compactTime = slapi_ch_strdup(CHANGELOGDB_COMPACT_TIME);
e4a41f
     config.trimInterval = CL5_NUM_IGNORE;
e4a41f
 
e4a41f
     slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods;;
e4a41f
@@ -375,6 +377,55 @@ changelog5_config_modify(Slapi_PBlock *pb,
e4a41f
                         *returncode = LDAP_UNWILLING_TO_PERFORM;
e4a41f
                         goto done;
e4a41f
                     }
e4a41f
+                } else if (strcasecmp(config_attr, CONFIG_CHANGELOG_COMPACTTIME_ATTRIBUTE) == 0) {
e4a41f
+                	if (config_attr_value && config_attr_value[0] != '\0') {
e4a41f
+                	    char *val = slapi_ch_strdup(config_attr_value);
e4a41f
+                        char *endp = NULL;
e4a41f
+                        char *hour_str = NULL;
e4a41f
+                        char *min_str = NULL;
e4a41f
+                        int32_t hour, min;
e4a41f
+                        errno = 0;
e4a41f
+
e4a41f
+                        slapi_ch_free_string(&config.compactTime);
e4a41f
+
e4a41f
+                      	if (strstr(val, ":")) {
e4a41f
+                            /* Get the hour and minute */
e4a41f
+                            hour_str = ldap_utf8strtok_r(val, ":", &min_str);
e4a41f
+                  	        /* Validate hour */
e4a41f
+                   	        hour = strtoll(hour_str, &endp, 10);
e4a41f
+                 	        if (*endp != '\0' || errno == ERANGE || hour < 0 || hour > 23 || strlen(hour_str) != 2) {
e4a41f
+          	       	            slapi_create_errormsg(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
e4a41f
+                   	                    "Invalid hour set (%s), must be a two digit number between 00 and 23",
e4a41f
+                   	                    hour_str);
e4a41f
+                   	            slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config",
e4a41f
+                                       "Invalid minute set (%s), must be a two digit number between 00 and 59.  "
e4a41f
+                	       	                    "Using default of 23:59\n", hour_str);
e4a41f
+                                *returncode = LDAP_UNWILLING_TO_PERFORM;
e4a41f
+                   	            goto done;
e4a41f
+           	       	        }
e4a41f
+       	        	        /* Validate minute */
e4a41f
+           	       	        min = strtoll(min_str, &endp, 10);
e4a41f
+           	      	        if (*endp != '\0' || errno == ERANGE || min < 0 || min > 59 || strlen(min_str) != 2) {
e4a41f
+                   	            slapi_create_errormsg(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
e4a41f
+                  	                    "Invalid minute set (%s), must be a two digit number between 00 and 59",
e4a41f
+                   	                    hour_str);
e4a41f
+                   	            slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config",
e4a41f
+                   	                    "Invalid minute set (%s), must be a two digit number between 00 and 59.  "
e4a41f
+                   	                    "Using default of 23:59\n", min_str);
e4a41f
+                                *returncode = LDAP_UNWILLING_TO_PERFORM;
e4a41f
+                   	            goto done;
e4a41f
+                   	        }
e4a41f
+                   	    } else {
e4a41f
+                   	        /* Wrong format */
e4a41f
+                   	        slapi_create_errormsg(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
e4a41f
+                   	                "Invalid setting (%s), must have a time format of HH:MM", val);
e4a41f
+                   	        slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config",
e4a41f
+                   	                "Invalid setting (%s), must have a time format of HH:MM\n", val);
e4a41f
+                            *returncode = LDAP_UNWILLING_TO_PERFORM;
e4a41f
+                   	        goto done;
e4a41f
+                   	    }
e4a41f
+                        config.compactTime = slapi_ch_strdup(config_attr_value);
e4a41f
+                    }
e4a41f
                 } else if (strcasecmp(config_attr, CONFIG_CHANGELOG_TRIM_ATTRIBUTE) == 0) {
e4a41f
                     if (slapi_is_duration_valid(config_attr_value)) {
e4a41f
                         config.trimInterval = (long)slapi_parse_duration(config_attr_value);
e4a41f
@@ -419,6 +470,11 @@ changelog5_config_modify(Slapi_PBlock *pb,
e4a41f
         if (originalConfig->maxAge)
e4a41f
             config.maxAge = slapi_ch_strdup(originalConfig->maxAge);
e4a41f
     }
e4a41f
+    if (strcmp(config.compactTime, CL5_STR_IGNORE) == 0) {
e4a41f
+        slapi_ch_free_string(&config.compactTime);
e4a41f
+        if (originalConfig->compactTime)
e4a41f
+            config.compactTime = slapi_ch_strdup(originalConfig->compactTime);
e4a41f
+    }
e4a41f
 
e4a41f
     /* attempt to change chagelog dir */
e4a41f
     if (config.dir) {
e4a41f
@@ -519,7 +575,7 @@ changelog5_config_modify(Slapi_PBlock *pb,
e4a41f
     if (config.maxEntries != CL5_NUM_IGNORE ||
e4a41f
         config.trimInterval != CL5_NUM_IGNORE ||
e4a41f
         strcmp(config.maxAge, CL5_STR_IGNORE) != 0) {
e4a41f
-        rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.trimInterval);
e4a41f
+        rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.compactTime, config.trimInterval);
e4a41f
         if (rc != CL5_SUCCESS) {
e4a41f
             *returncode = 1;
e4a41f
             if (returntext) {
e4a41f
@@ -689,6 +745,7 @@ changelog5_extract_config(Slapi_Entry *entry, changelog5Config *config)
e4a41f
 {
e4a41f
     const char *arg;
e4a41f
     char *max_age = NULL;
e4a41f
+    char *val = NULL;
e4a41f
 
e4a41f
     memset(config, 0, sizeof(*config));
e4a41f
     config->dir = slapi_entry_attr_get_charptr(entry, CONFIG_CHANGELOG_DIR_ATTRIBUTE);
e4a41f
@@ -711,6 +768,47 @@ changelog5_extract_config(Slapi_Entry *entry, changelog5Config *config)
e4a41f
         config->compactInterval = CHANGELOGDB_COMPACT_INTERVAL;
e4a41f
     }
e4a41f
 
e4a41f
+    arg = slapi_entry_attr_get_ref(entry, CONFIG_CHANGELOG_COMPACTTIME_ATTRIBUTE);
e4a41f
+    if (arg) {
e4a41f
+        char *endp = NULL;
e4a41f
+        char *hour_str = NULL;
e4a41f
+        char *min_str = NULL;
e4a41f
+        int32_t hour, min;
e4a41f
+        errno = 0;
e4a41f
+
e4a41f
+        val = slapi_ch_strdup((char *)arg);
e4a41f
+    	if (strstr(val, ":")) {
e4a41f
+            /* Get the hour and minute */
e4a41f
+            hour_str = ldap_utf8strtok_r(val, ":", &min_str);
e4a41f
+    	        /* Validate hour */
e4a41f
+   	        hour = strtoll(hour_str, &endp, 10);
e4a41f
+   	        if (*endp != '\0' || errno == ERANGE || hour < 0 || hour > 23 || strlen(hour_str) != 2) {
e4a41f
+   	            slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config",
e4a41f
+   	                    "Invalid minute set (%s), must be a two digit number between 00 and 59.  "
e4a41f
+   	                    "Using default of 23:59\n", hour_str);
e4a41f
+   	            goto set_default;
e4a41f
+   	        }
e4a41f
+    	        /* Validate minute */
e4a41f
+   	        min = strtoll(min_str, &endp, 10);
e4a41f
+  	        if (*endp != '\0' || errno == ERANGE || min < 0 || min > 59 || strlen(min_str) != 2) {
e4a41f
+   	            slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config",
e4a41f
+   	                    "Invalid minute set (%s), must be a two digit number between 00 and 59.  "
e4a41f
+   	                    "Using default of 23:59\n", min_str);
e4a41f
+   	            goto set_default;
e4a41f
+   	        }
e4a41f
+   	    } else {
e4a41f
+   	        /* Wrong format */
e4a41f
+   	        slapi_log_err(SLAPI_LOG_ERR, "changelog5_extract_config",
e4a41f
+   	                "Invalid setting (%s), must have a time format of HH:MM\n", val);
e4a41f
+   	        goto set_default;
e4a41f
+   	    }
e4a41f
+        config->compactTime = slapi_ch_strdup(arg);
e4a41f
+    } else {
e4a41f
+    	set_default:
e4a41f
+        config->compactTime = slapi_ch_strdup(CHANGELOGDB_COMPACT_TIME);
e4a41f
+    }
e4a41f
+    slapi_ch_free_string(&val;;
e4a41f
+
e4a41f
     arg = slapi_entry_attr_get_ref(entry, CONFIG_CHANGELOG_TRIM_ATTRIBUTE);
e4a41f
     if (arg) {
e4a41f
         if (slapi_is_duration_valid(arg)) {
e4a41f
diff --git a/ldap/servers/plugins/replication/cl5_init.c b/ldap/servers/plugins/replication/cl5_init.c
e4a41f
index 112c4ece4..251859714 100644
e4a41f
--- a/ldap/servers/plugins/replication/cl5_init.c
e4a41f
+++ b/ldap/servers/plugins/replication/cl5_init.c
e4a41f
@@ -57,7 +57,7 @@ changelog5_init()
e4a41f
     }
e4a41f
 
e4a41f
     /* set trimming parameters */
e4a41f
-    rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.trimInterval);
e4a41f
+    rc = cl5ConfigTrimming(config.maxEntries, config.maxAge, config.compactInterval, config.compactTime, config.trimInterval);
e4a41f
     if (rc != CL5_SUCCESS) {
e4a41f
         slapi_log_err(SLAPI_LOG_ERR, repl_plugin_name_cl,
e4a41f
                       "changelog5_init: failed to configure changelog trimming\n");
e4a41f
diff --git a/ldap/servers/plugins/replication/repl_shared.h b/ldap/servers/plugins/replication/repl_shared.h
e4a41f
index b1ed86934..6708e12f7 100644
e4a41f
--- a/ldap/servers/plugins/replication/repl_shared.h
e4a41f
+++ b/ldap/servers/plugins/replication/repl_shared.h
e4a41f
@@ -26,11 +26,13 @@
e4a41f
 
e4a41f
 #define CHANGELOGDB_TRIM_INTERVAL 300        /* 5 minutes */
e4a41f
 #define CHANGELOGDB_COMPACT_INTERVAL 2592000 /* 30 days */
e4a41f
+#define CHANGELOGDB_COMPACT_TIME "23:55" /* 30 days */
e4a41f
 
e4a41f
 #define CONFIG_CHANGELOG_DIR_ATTRIBUTE "nsslapd-changelogdir"
e4a41f
 #define CONFIG_CHANGELOG_MAXENTRIES_ATTRIBUTE "nsslapd-changelogmaxentries"
e4a41f
 #define CONFIG_CHANGELOG_MAXAGE_ATTRIBUTE "nsslapd-changelogmaxage"
e4a41f
 #define CONFIG_CHANGELOG_COMPACTDB_ATTRIBUTE "nsslapd-changelogcompactdb-interval"
e4a41f
+#define CONFIG_CHANGELOG_COMPACTTIME_ATTRIBUTE "nsslapd-changelogcompactdb-time"
e4a41f
 #define CONFIG_CHANGELOG_TRIM_ATTRIBUTE "nsslapd-changelogtrim-interval"
e4a41f
 /* Changelog Internal Configuration Parameters -> Changelog Cache related */
e4a41f
 #define CONFIG_CHANGELOG_ENCRYPTION_ALGORITHM "nsslapd-encryptionalgorithm"
e4a41f
diff --git a/ldap/servers/plugins/retrocl/retrocl.c b/ldap/servers/plugins/retrocl/retrocl.c
e4a41f
index 2a620301c..f73c81528 100644
e4a41f
--- a/ldap/servers/plugins/retrocl/retrocl.c
e4a41f
+++ b/ldap/servers/plugins/retrocl/retrocl.c
e4a41f
@@ -400,7 +400,6 @@ retrocl_start(Slapi_PBlock *pb)
e4a41f
 
e4a41f
         for (size_t i = 0; i < num_vals; i++) {
e4a41f
             char *value = values[i];
e4a41f
-            size_t length = strlen(value);
e4a41f
 
e4a41f
             char *pos = strchr(value, ':');
e4a41f
             if (pos == NULL) {
e4a41f
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
e4a41f
index 167644943..4261c6ce2 100644
e4a41f
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
e4a41f
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_config.c
e4a41f
@@ -678,6 +678,84 @@ bdb_config_db_compactdb_interval_set(void *arg,
e4a41f
     return retval;
e4a41f
 }
e4a41f
 
e4a41f
+static void *
e4a41f
+bdb_config_db_compactdb_time_get(void *arg)
e4a41f
+{
e4a41f
+    struct ldbminfo *li = (struct ldbminfo *)arg;
e4a41f
+    return (void *)slapi_ch_strdup(BDB_CONFIG(li)->bdb_compactdb_time);
e4a41f
+}
e4a41f
+
e4a41f
+static int
e4a41f
+bdb_config_db_compactdb_time_set(void *arg,
e4a41f
+                                 void *value,
e4a41f
+                                 char *errorbuf __attribute__((unused)),
e4a41f
+                                 int phase __attribute__((unused)),
e4a41f
+                                 int apply)
e4a41f
+{
e4a41f
+    struct ldbminfo *li = (struct ldbminfo *)arg;
e4a41f
+    char *val = slapi_ch_strdup((char *)value);
e4a41f
+    char *endp = NULL;
e4a41f
+    char *hour_str = NULL;
e4a41f
+    char *min_str = NULL;
e4a41f
+    char *default_time = "23:59";
e4a41f
+    int32_t hour, min;
e4a41f
+    int retval = LDAP_SUCCESS;
e4a41f
+    errno = 0;
e4a41f
+
e4a41f
+    if (strstr(val, ":")) {
e4a41f
+        /* Get the hour and minute */
e4a41f
+        hour_str = ldap_utf8strtok_r(val, ":", &min_str);
e4a41f
+
e4a41f
+        /* Validate hour */
e4a41f
+        hour = strtoll(hour_str, &endp, 10);
e4a41f
+        if (*endp != '\0' || errno == ERANGE || hour < 0 || hour > 23 || strlen(hour_str) != 2) {
e4a41f
+            slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
e4a41f
+                    "Invalid hour set (%s), must be a two digit number between 00 and 23",
e4a41f
+                    hour_str);
e4a41f
+            slapi_log_err(SLAPI_LOG_ERR, "bdb_config_db_compactdb_interval_set",
e4a41f
+                    "Invalid minute set (%s), must be a two digit number between 00 and 59.  "
e4a41f
+                    "Using default of 23:59\n", hour_str);
e4a41f
+            retval = LDAP_OPERATIONS_ERROR;
e4a41f
+            goto done;
e4a41f
+        }
e4a41f
+
e4a41f
+        /* Validate minute */
e4a41f
+        min = strtoll(min_str, &endp, 10);
e4a41f
+        if (*endp != '\0' || errno == ERANGE || min < 0 || min > 59 || strlen(min_str) != 2) {
e4a41f
+            slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
e4a41f
+                    "Invalid minute set (%s), must be a two digit number between 00 and 59",
e4a41f
+                    hour_str);
e4a41f
+            slapi_log_err(SLAPI_LOG_ERR, "bdb_config_db_compactdb_interval_set",
e4a41f
+                    "Invalid minute set (%s), must be a two digit number between 00 and 59.  "
e4a41f
+                    "Using default of 23:59\n", min_str);
e4a41f
+            retval = LDAP_OPERATIONS_ERROR;
e4a41f
+            goto done;
e4a41f
+        }
e4a41f
+    } else {
e4a41f
+        /* Wrong format */
e4a41f
+        slapi_create_errormsg(errorbuf, SLAPI_DSE_RETURNTEXT_SIZE,
e4a41f
+                "Invalid setting (%s), must have a time format of HH:MM", val);
e4a41f
+        slapi_log_err(SLAPI_LOG_ERR, "bdb_config_db_compactdb_interval_set",
e4a41f
+                "Invalid setting (%s), must have a time format of HH:MM\n", val);
e4a41f
+        retval = LDAP_OPERATIONS_ERROR;
e4a41f
+        goto done;
e4a41f
+    }
e4a41f
+
e4a41f
+done:
e4a41f
+    if (apply) {
e4a41f
+        slapi_ch_free((void **)&(BDB_CONFIG(li)->bdb_compactdb_time));
e4a41f
+        if (retval) {
e4a41f
+            /* Something went wrong, use the default */
e4a41f
+            BDB_CONFIG(li)->bdb_compactdb_time = slapi_ch_strdup(default_time);
e4a41f
+        } else {
e4a41f
+            BDB_CONFIG(li)->bdb_compactdb_time = slapi_ch_strdup((char *)value);
e4a41f
+        }
e4a41f
+    }
e4a41f
+    slapi_ch_free_string(&val;;
e4a41f
+
e4a41f
+    return retval;
e4a41f
+}
e4a41f
+
e4a41f
 static void *
e4a41f
 bdb_config_db_page_size_get(void *arg)
e4a41f
 {
e4a41f
@@ -1473,6 +1551,7 @@ static config_info bdb_config_param[] = {
e4a41f
     {CONFIG_DB_TRANSACTION_WAIT, CONFIG_TYPE_ONOFF, "off", &bdb_config_db_transaction_wait_get, &bdb_config_db_transaction_wait_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
e4a41f
     {CONFIG_DB_CHECKPOINT_INTERVAL, CONFIG_TYPE_INT, "60", &bdb_config_db_checkpoint_interval_get, &bdb_config_db_checkpoint_interval_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
e4a41f
     {CONFIG_DB_COMPACTDB_INTERVAL, CONFIG_TYPE_INT, "2592000" /*30days*/, &bdb_config_db_compactdb_interval_get, &bdb_config_db_compactdb_interval_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
e4a41f
+    {CONFIG_DB_COMPACTDB_TIME, CONFIG_TYPE_STRING, "23:59", &bdb_config_db_compactdb_time_get, &bdb_config_db_compactdb_time_set, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
e4a41f
     {CONFIG_DB_TRANSACTION_BATCH, CONFIG_TYPE_INT, "0", &bdb_get_batch_transactions, &bdb_set_batch_transactions, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
e4a41f
     {CONFIG_DB_TRANSACTION_BATCH_MIN_SLEEP, CONFIG_TYPE_INT, "50", &bdb_get_batch_txn_min_sleep, &bdb_set_batch_txn_min_sleep, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
e4a41f
     {CONFIG_DB_TRANSACTION_BATCH_MAX_SLEEP, CONFIG_TYPE_INT, "50", &bdb_get_batch_txn_max_sleep, &bdb_set_batch_txn_max_sleep, CONFIG_FLAG_ALWAYS_SHOW | CONFIG_FLAG_ALLOW_RUNNING_CHANGE},
e4a41f
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
e4a41f
index 2f25f67a2..ec1976d38 100644
e4a41f
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
e4a41f
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.c
e4a41f
@@ -2126,6 +2126,7 @@ bdb_post_close(struct ldbminfo *li, int dbmode)
e4a41f
          */
e4a41f
         slapi_ch_free_string(&conf->bdb_dbhome_directory);
e4a41f
         slapi_ch_free_string(&conf->bdb_home_directory);
e4a41f
+        slapi_ch_free_string(&conf->bdb_compactdb_time);
e4a41f
     }
e4a41f
 
e4a41f
     return return_value;
e4a41f
@@ -3644,6 +3645,39 @@ log_flush_threadmain(void *param)
e4a41f
     return 0;
e4a41f
 }
e4a41f
 
e4a41f
+/*
e4a41f
+ * This refreshes the TOD expiration.  So live changes to the configuration
e4a41f
+ * will take effect immediately.
e4a41f
+ */
e4a41f
+static time_t
e4a41f
+bdb_get_tod_expiration(char *expire_time)
e4a41f
+{
e4a41f
+    time_t start_time, todays_elapsed_time, now = time(NULL);
e4a41f
+    struct tm *tm_struct = localtime(&now;;
e4a41f
+    char hour_str[3] = {0};
e4a41f
+    char min_str[3] = {0};
e4a41f
+    char *s = expire_time;
e4a41f
+    char *endp = NULL;
e4a41f
+    int32_t hour, min, expiring_time;
e4a41f
+
e4a41f
+    /* Get today's start time */
e4a41f
+    todays_elapsed_time = (tm_struct->tm_hour * 3600) + (tm_struct->tm_min * 60) + (tm_struct->tm_sec);
e4a41f
+    start_time = slapi_current_utc_time() - todays_elapsed_time;
e4a41f
+
e4a41f
+    /* Get the hour and minute and calculate the expiring time.  The time was
e4a41f
+     * already validated in bdb_config.c:  HH:MM */
e4a41f
+    hour_str[0] = *s++;
e4a41f
+    hour_str[1] = *s++;
e4a41f
+    s++;  /* skip colon */
e4a41f
+    min_str[0] = *s++;
e4a41f
+    min_str[1] = *s++;
e4a41f
+    hour = strtoll(hour_str, &endp, 10);
e4a41f
+    min = strtoll(min_str, &endp, 10);
e4a41f
+    expiring_time = (hour * 60 * 60) + (min * 60);
e4a41f
+
e4a41f
+    return start_time + expiring_time;
e4a41f
+}
e4a41f
+
e4a41f
 /*
e4a41f
  * create a thread for checkpoint_threadmain
e4a41f
  */
e4a41f
@@ -3685,7 +3719,9 @@ checkpoint_threadmain(void *param)
e4a41f
     time_t checkpoint_interval_update = 0;
e4a41f
     time_t compactdb_interval = 0;
e4a41f
     time_t checkpoint_interval = 0;
e4a41f
-    back_txn txn;
e4a41f
+    int32_t compactdb_time = 0;
e4a41f
+    PRBool compacting = PR_FALSE;
e4a41f
+
e4a41f
 
e4a41f
     PR_ASSERT(NULL != param);
e4a41f
     li = (struct ldbminfo *)param;
e4a41f
@@ -3724,22 +3760,35 @@ checkpoint_threadmain(void *param)
e4a41f
     slapi_timespec_expire_at(checkpoint_interval, &checkpoint_expire);
e4a41f
 
e4a41f
     while (!BDB_CONFIG(li)->bdb_stop_threads) {
e4a41f
-        /* sleep for a while */
e4a41f
-        /* why aren't we sleeping exactly the right amount of time ? */
e4a41f
-        /* answer---because the interval might be changed after the server
e4a41f
-         * starts up */
e4a41f
+        PR_Lock(li->li_config_mutex);
e4a41f
+        checkpoint_interval_update = (time_t)BDB_CONFIG(li)->bdb_checkpoint_interval;
e4a41f
+        compactdb_interval_update = (time_t)BDB_CONFIG(li)->bdb_compactdb_interval;
e4a41f
+        if (!compacting) {
e4a41f
+            /* Once we know we want to compact we need to stop refreshing the
e4a41f
+             * TOD expiration. Otherwise if the compact time is close to
e4a41f
+             * midnight we could roll over past midnight during the checkpoint
e4a41f
+             * sleep interval, and we'd never actually compact the databases.
e4a41f
+             * We also need to get this value before the sleep.
e4a41f
+             */
e4a41f
+            compactdb_time = bdb_get_tod_expiration((char *)BDB_CONFIG(li)->bdb_compactdb_time);
e4a41f
+        }
e4a41f
+        PR_Unlock(li->li_config_mutex);
e4a41f
+
e4a41f
+        if (compactdb_interval_update != compactdb_interval) {
e4a41f
+            /* Compact interval was changed, so reset the timer */
e4a41f
+            slapi_timespec_expire_at(compactdb_interval_update, &compactdb_expire);
e4a41f
+        }
e4a41f
 
e4a41f
+        /* Sleep for a while ...
e4a41f
+         * Why aren't we sleeping exactly the right amount of time ?
e4a41f
+         * Answer---because the interval might be changed after the server
e4a41f
+         * starts up */
e4a41f
         DS_Sleep(interval);
e4a41f
 
e4a41f
         if (0 == BDB_CONFIG(li)->bdb_enable_transactions) {
e4a41f
             continue;
e4a41f
         }
e4a41f
 
e4a41f
-        PR_Lock(li->li_config_mutex);
e4a41f
-        checkpoint_interval_update = (time_t)BDB_CONFIG(li)->bdb_checkpoint_interval;
e4a41f
-        compactdb_interval_update = (time_t)BDB_CONFIG(li)->bdb_compactdb_interval;
e4a41f
-        PR_Unlock(li->li_config_mutex);
e4a41f
-
e4a41f
         /* If the checkpoint has been updated OR we have expired */
e4a41f
         if (checkpoint_interval != checkpoint_interval_update ||
e4a41f
             slapi_timespec_expire_check(&checkpoint_expire) == TIMER_EXPIRED) {
e4a41f
@@ -3807,94 +3856,37 @@ checkpoint_threadmain(void *param)
e4a41f
 
e4a41f
         /*
e4a41f
          * Remember that if compactdb_interval is 0, timer_expired can
e4a41f
-         * never occur unless the value in compctdb_interval changes.
e4a41f
+         * never occur unless the value in compactdb_interval changes.
e4a41f
          *
e4a41f
-         * this could have been a bug infact, where compactdb_interval
e4a41f
+         * this could have been a bug in fact, where compactdb_interval
e4a41f
          * was 0, if you change while running it would never take effect ....
e4a41f
          */
e4a41f
-        if (compactdb_interval_update != compactdb_interval ||
e4a41f
-            slapi_timespec_expire_check(&compactdb_expire) == TIMER_EXPIRED) {
e4a41f
-            int rc = 0;
e4a41f
-            Object *inst_obj;
e4a41f
-            ldbm_instance *inst;
e4a41f
-            DB *db = NULL;
e4a41f
-            DB_COMPACT c_data = {0};
e4a41f
-
e4a41f
-            for (inst_obj = objset_first_obj(li->li_instance_set);
e4a41f
-                 inst_obj;
e4a41f
-                 inst_obj = objset_next_obj(li->li_instance_set, inst_obj)) {
e4a41f
-                inst = (ldbm_instance *)object_get_data(inst_obj);
e4a41f
-                rc = dblayer_get_id2entry(inst->inst_be, &db);
e4a41f
-                if (!db || rc) {
e4a41f
-                    continue;
e4a41f
-                }
e4a41f
-                slapi_log_err(SLAPI_LOG_NOTICE, "checkpoint_threadmain", "Compacting DB start: %s\n",
e4a41f
-                              inst->inst_name);
e4a41f
-
e4a41f
-                /*
e4a41f
-                 * It's possible for this to heap us after free because when we access db
e4a41f
-                 * *just* as the server shut's down, we don't know it. So we should probably
e4a41f
-                 * do something like wrapping access to the db var in a rwlock, and have "read"
e4a41f
-                 * to access, and take writes to change the state. This would prevent the issue.
e4a41f
-                 */
e4a41f
-                DBTYPE type;
e4a41f
-                rc = db->get_type(db, &type);
e4a41f
-                if (rc) {
e4a41f
-                    slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain",
e4a41f
-                                  "compactdb: failed to determine db type for %s: db error - %d %s\n",
e4a41f
-                                  inst->inst_name, rc, db_strerror(rc));
e4a41f
-                    continue;
e4a41f
-                }
e4a41f
+        if (slapi_timespec_expire_check(&compactdb_expire) == TIMER_EXPIRED) {
e4a41f
+            compacting = PR_TRUE;
e4a41f
+            if (slapi_current_utc_time() < compactdb_time) {
e4a41f
+                /* We have passed the interval, but we need to wait for a
e4a41f
+                 * particular TOD to pass before compacting */
e4a41f
+                continue;
e4a41f
+            }
e4a41f
 
e4a41f
-                rc = dblayer_txn_begin(inst->inst_be, NULL, &txn);
e4a41f
-                if (rc) {
e4a41f
-                    slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain", "compactdb: transaction begin failed: %d\n", rc);
e4a41f
-                    break;
e4a41f
-                }
e4a41f
-                /*
e4a41f
-                 * https://docs.oracle.com/cd/E17275_01/html/api_reference/C/BDB-C_APIReference.pdf
e4a41f
-                 * "DB_FREELIST_ONLY
e4a41f
-                 * Do no page compaction, only returning pages to the filesystem that are already free and at the end
e4a41f
-                 * of the file. This flag must be set if the database is a Hash access method database."
e4a41f
-                 *
e4a41f
-                 */
e4a41f
+            /* Time to compact the DB's */
e4a41f
+            dblayer_force_checkpoint(li);
e4a41f
+            bdb_compact(li);
e4a41f
+            dblayer_force_checkpoint(li);
e4a41f
 
e4a41f
-                uint32_t compact_flags = DB_FREE_SPACE;
e4a41f
-                if (type == DB_HASH) {
e4a41f
-                    compact_flags |= DB_FREELIST_ONLY;
e4a41f
-                }
e4a41f
-                rc = db->compact(db, txn.back_txn_txn, NULL /*start*/, NULL /*stop*/,
e4a41f
-                                 &c_data, compact_flags, NULL /*end*/);
e4a41f
-                if (rc) {
e4a41f
-                    slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain",
e4a41f
-                                  "compactdb: failed to compact %s; db error - %d %s\n",
e4a41f
-                                  inst->inst_name, rc, db_strerror(rc));
e4a41f
-                    if ((rc = dblayer_txn_abort(inst->inst_be, &txn))) {
e4a41f
-                        slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain", "compactdb: failed to abort txn (%s) db error - %d %s\n",
e4a41f
-                                      inst->inst_name, rc, db_strerror(rc));
e4a41f
-                        break;
e4a41f
-                    }
e4a41f
-                } else {
e4a41f
-                    slapi_log_err(SLAPI_LOG_NOTICE, "checkpoint_threadmain",
e4a41f
-                                  "compactdb: compact %s - %d pages freed\n",
e4a41f
-                                  inst->inst_name, c_data.compact_pages_free);
e4a41f
-                    if ((rc = dblayer_txn_commit(inst->inst_be, &txn))) {
e4a41f
-                        slapi_log_err(SLAPI_LOG_ERR, "checkpoint_threadmain", "compactdb: failed to commit txn (%s) db error - %d %s\n",
e4a41f
-                                      inst->inst_name, rc, db_strerror(rc));
e4a41f
-                        break;
e4a41f
-                    }
e4a41f
-                }
e4a41f
-            }
e4a41f
+            /* Now reset the timer and compacting flag */
e4a41f
             compactdb_interval = compactdb_interval_update;
e4a41f
             slapi_timespec_expire_at(compactdb_interval, &compactdb_expire);
e4a41f
+            compacting = PR_FALSE;
e4a41f
         }
e4a41f
     }
e4a41f
-    slapi_log_err(SLAPI_LOG_TRACE, "checkpoint_threadmain", "Check point before leaving\n");
e4a41f
+    slapi_log_err(SLAPI_LOG_HOUSE, "checkpoint_threadmain", "Check point before leaving\n");
e4a41f
     rval = dblayer_force_checkpoint(li);
e4a41f
+
e4a41f
 error_return:
e4a41f
 
e4a41f
     DECR_THREAD_COUNT(pEnv);
e4a41f
-    slapi_log_err(SLAPI_LOG_TRACE, "checkpoint_threadmain", "Leaving checkpoint_threadmain\n");
e4a41f
+    slapi_log_err(SLAPI_LOG_HOUSE, "checkpoint_threadmain", "Leaving checkpoint_threadmain\n");
e4a41f
     return rval;
e4a41f
 }
e4a41f
 
e4a41f
@@ -6209,3 +6201,99 @@ bdb_back_ctrl(Slapi_Backend *be, int cmd, void *info)
e4a41f
 
e4a41f
     return rc;
e4a41f
 }
e4a41f
+
e4a41f
+int32_t
e4a41f
+ldbm_back_compact(Slapi_Backend *be)
e4a41f
+{
e4a41f
+    struct ldbminfo *li = NULL;
e4a41f
+    int32_t rc = -1;
e4a41f
+
e4a41f
+    li = (struct ldbminfo *)be->be_database->plg_private;
e4a41f
+    dblayer_force_checkpoint(li);
e4a41f
+    rc = bdb_compact(li);
e4a41f
+    dblayer_force_checkpoint(li);
e4a41f
+    return rc;
e4a41f
+}
e4a41f
+
e4a41f
+
e4a41f
+int32_t
e4a41f
+bdb_compact(struct ldbminfo *li)
e4a41f
+{
e4a41f
+    Object *inst_obj;
e4a41f
+    ldbm_instance *inst;
e4a41f
+    DB *db = NULL;
e4a41f
+    back_txn txn = {0};
e4a41f
+    int rc = 0;
e4a41f
+    DB_COMPACT c_data = {0};
e4a41f
+
e4a41f
+    slapi_log_err(SLAPI_LOG_NOTICE, "bdb_compact",
e4a41f
+                  "Compacting databases ...\n");
e4a41f
+    for (inst_obj = objset_first_obj(li->li_instance_set);
e4a41f
+        inst_obj;
e4a41f
+        inst_obj = objset_next_obj(li->li_instance_set, inst_obj))
e4a41f
+    {
e4a41f
+        inst = (ldbm_instance *)object_get_data(inst_obj);
e4a41f
+        rc = dblayer_get_id2entry(inst->inst_be, &db);
e4a41f
+        if (!db || rc) {
e4a41f
+            continue;
e4a41f
+        }
e4a41f
+        slapi_log_err(SLAPI_LOG_NOTICE, "bdb_compact", "Compacting DB start: %s\n",
e4a41f
+                      inst->inst_name);
e4a41f
+
e4a41f
+        /*
e4a41f
+         * It's possible for this to heap us after free because when we access db
e4a41f
+         * *just* as the server shut's down, we don't know it. So we should probably
e4a41f
+         * do something like wrapping access to the db var in a rwlock, and have "read"
e4a41f
+         * to access, and take writes to change the state. This would prevent the issue.
e4a41f
+         */
e4a41f
+        DBTYPE type;
e4a41f
+        rc = db->get_type(db, &type);
e4a41f
+        if (rc) {
e4a41f
+            slapi_log_err(SLAPI_LOG_ERR, "bdb_compact",
e4a41f
+                          "compactdb: failed to determine db type for %s: db error - %d %s\n",
e4a41f
+                          inst->inst_name, rc, db_strerror(rc));
e4a41f
+            continue;
e4a41f
+        }
e4a41f
+
e4a41f
+        rc = dblayer_txn_begin(inst->inst_be, NULL, &txn);
e4a41f
+        if (rc) {
e4a41f
+            slapi_log_err(SLAPI_LOG_ERR, "bdb_compact", "compactdb: transaction begin failed: %d\n", rc);
e4a41f
+            break;
e4a41f
+        }
e4a41f
+        /*
e4a41f
+         * https://docs.oracle.com/cd/E17275_01/html/api_reference/C/BDB-C_APIReference.pdf
e4a41f
+         * "DB_FREELIST_ONLY
e4a41f
+         * Do no page compaction, only returning pages to the filesystem that are already free and at the end
e4a41f
+         * of the file. This flag must be set if the database is a Hash access method database."
e4a41f
+         *
e4a41f
+         */
e4a41f
+        uint32_t compact_flags = DB_FREE_SPACE;
e4a41f
+        if (type == DB_HASH) {
e4a41f
+            compact_flags |= DB_FREELIST_ONLY;
e4a41f
+        }
e4a41f
+        rc = db->compact(db, txn.back_txn_txn, NULL /*start*/, NULL /*stop*/,
e4a41f
+                         &c_data, compact_flags, NULL /*end*/);
e4a41f
+        if (rc) {
e4a41f
+            slapi_log_err(SLAPI_LOG_ERR, "bdb_compact",
e4a41f
+                    "compactdb: failed to compact %s; db error - %d %s\n",
e4a41f
+                    inst->inst_name, rc, db_strerror(rc));
e4a41f
+            if ((rc = dblayer_txn_abort(inst->inst_be, &txn))) {
e4a41f
+                slapi_log_err(SLAPI_LOG_ERR, "bdb_compact", "compactdb: failed to abort txn (%s) db error - %d %s\n",
e4a41f
+                              inst->inst_name, rc, db_strerror(rc));
e4a41f
+                break;
e4a41f
+            }
e4a41f
+        } else {
e4a41f
+            slapi_log_err(SLAPI_LOG_NOTICE, "bdb_compact",
e4a41f
+                          "compactdb: compact %s - %d pages freed\n",
e4a41f
+                          inst->inst_name, c_data.compact_pages_free);
e4a41f
+            if ((rc = dblayer_txn_commit(inst->inst_be, &txn))) {
e4a41f
+                slapi_log_err(SLAPI_LOG_ERR, "bdb_compact", "compactdb: failed to commit txn (%s) db error - %d %s\n",
e4a41f
+                              inst->inst_name, rc, db_strerror(rc));
e4a41f
+                break;
e4a41f
+            }
e4a41f
+        }
e4a41f
+    }
e4a41f
+    slapi_log_err(SLAPI_LOG_NOTICE, "bdb_compact", "Compacting databases finished.\n");
e4a41f
+
e4a41f
+    return rc;
e4a41f
+}
e4a41f
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h
e4a41f
index 6bb04d21a..e3a49dbac 100644
e4a41f
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h
e4a41f
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_layer.h
e4a41f
@@ -79,7 +79,8 @@ typedef struct bdb_config
e4a41f
     int bdb_previous_lock_config;  /* Max lock count when we last shut down--
e4a41f
                                       * used to determine if we delete the mpool */
e4a41f
     u_int32_t bdb_deadlock_policy; /* i.e. the atype to DB_ENV->lock_detect in deadlock_threadmain */
e4a41f
-    int bdb_compactdb_interval;    /* interval to execute compact id2entry dbs */
e4a41f
+    int32_t bdb_compactdb_interval; /* interval to execute compact id2entry dbs */
e4a41f
+    char *bdb_compactdb_time;       /* time of day to execute compact id2entry dbs */
e4a41f
 } bdb_config;
e4a41f
 
e4a41f
 int bdb_init(struct ldbminfo *li, config_info *config_array);
e4a41f
@@ -96,6 +97,7 @@ int bdb_db_size(Slapi_PBlock *pb);
e4a41f
 int bdb_upgradedb(Slapi_PBlock *pb);
e4a41f
 int bdb_upgradednformat(Slapi_PBlock *pb);
e4a41f
 int bdb_upgradeddformat(Slapi_PBlock *pb);
e4a41f
+int32_t bdb_compact(struct ldbminfo *li);
e4a41f
 int bdb_restore(struct ldbminfo *li, char *src_dir, Slapi_Task *task);
e4a41f
 int bdb_cleanup(struct ldbminfo *li);
e4a41f
 int bdb_txn_begin(struct ldbminfo *li, back_txnid parent_txn, back_txn *txn, PRBool use_lock);
e4a41f
diff --git a/ldap/servers/slapd/back-ldbm/init.c b/ldap/servers/slapd/back-ldbm/init.c
e4a41f
index 4165c8fad..42c9bd00a 100644
e4a41f
--- a/ldap/servers/slapd/back-ldbm/init.c
e4a41f
+++ b/ldap/servers/slapd/back-ldbm/init.c
e4a41f
@@ -180,6 +180,8 @@ ldbm_back_init(Slapi_PBlock *pb)
e4a41f
                            (void *)ldbm_back_set_info);
e4a41f
     rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DB_CTRL_INFO_FN,
e4a41f
                            (void *)ldbm_back_ctrl_info);
e4a41f
+    rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DB_COMPACT_FN,
e4a41f
+                           (void *)ldbm_back_compact);
e4a41f
 
e4a41f
     if (rc != 0) {
e4a41f
         slapi_log_err(SLAPI_LOG_CRIT, "ldbm_back_init", "Failed %d\n", rc);
e4a41f
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.h b/ldap/servers/slapd/back-ldbm/ldbm_config.h
e4a41f
index 6fa8292eb..48446193e 100644
e4a41f
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.h
e4a41f
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.h
e4a41f
@@ -84,6 +84,7 @@ struct config_info
e4a41f
 #define CONFIG_DB_TRANSACTION_WAIT "nsslapd-db-transaction-wait"
e4a41f
 #define CONFIG_DB_CHECKPOINT_INTERVAL "nsslapd-db-checkpoint-interval"
e4a41f
 #define CONFIG_DB_COMPACTDB_INTERVAL "nsslapd-db-compactdb-interval"
e4a41f
+#define CONFIG_DB_COMPACTDB_TIME "nsslapd-db-compactdb-time"
e4a41f
 #define CONFIG_DB_TRANSACTION_BATCH "nsslapd-db-transaction-batch-val"
e4a41f
 #define CONFIG_DB_TRANSACTION_BATCH_MIN_SLEEP "nsslapd-db-transaction-batch-min-wait"
e4a41f
 #define CONFIG_DB_TRANSACTION_BATCH_MAX_SLEEP "nsslapd-db-transaction-batch-max-wait"
e4a41f
diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
e4a41f
index 5d618a89c..30c9003bf 100644
e4a41f
--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
e4a41f
+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
e4a41f
@@ -478,6 +478,7 @@ void ldbm_back_search_results_release(void **search_results);
e4a41f
 int ldbm_back_init(Slapi_PBlock *pb);
e4a41f
 void ldbm_back_prev_search_results(Slapi_PBlock *pb);
e4a41f
 int ldbm_back_isinitialized(void);
e4a41f
+int32_t ldbm_back_compact(Slapi_Backend *be);
e4a41f
 
e4a41f
 /*
e4a41f
  * vlv.c
e4a41f
diff --git a/ldap/servers/slapd/filtercmp.c b/ldap/servers/slapd/filtercmp.c
e4a41f
index f7e3ed4d5..c886267bd 100644
e4a41f
--- a/ldap/servers/slapd/filtercmp.c
e4a41f
+++ b/ldap/servers/slapd/filtercmp.c
e4a41f
@@ -344,7 +344,6 @@ slapi_filter_compare(struct slapi_filter *f1, struct slapi_filter *f2)
e4a41f
     struct berval *inval1[2], *inval2[2], **outval1, **outval2;
e4a41f
     int ret;
e4a41f
     Slapi_Attr sattr;
e4a41f
-    int cmplen;
e4a41f
 
e4a41f
     slapi_log_err(SLAPI_LOG_TRACE, "slapi_filter_compare", "=>\n");
e4a41f
 
e4a41f
@@ -379,11 +378,11 @@ slapi_filter_compare(struct slapi_filter *f1, struct slapi_filter *f2)
e4a41f
         if (key1 && key2) {
e4a41f
             struct berval bvkey1 = {
e4a41f
                 slapi_value_get_length(key1[0]),
e4a41f
-                slapi_value_get_string(key1[0])
e4a41f
+				(char *)slapi_value_get_string(key1[0])
e4a41f
             };
e4a41f
             struct berval bvkey2 = {
e4a41f
                 slapi_value_get_length(key2[0]),
e4a41f
-                slapi_value_get_string(key2[0])
e4a41f
+				(char *)slapi_value_get_string(key2[0])
e4a41f
             };
e4a41f
             ret = slapi_berval_cmp(&bvkey1, &bvkey2);
e4a41f
         }
e4a41f
diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c
e4a41f
index f7d1f8885..fcac53839 100644
e4a41f
--- a/ldap/servers/slapd/pblock.c
e4a41f
+++ b/ldap/servers/slapd/pblock.c
e4a41f
@@ -925,6 +925,12 @@ slapi_pblock_get(Slapi_PBlock *pblock, int arg, void *value)
e4a41f
         }
e4a41f
         (*(IFP *)value) = pblock->pb_plugin->plg_db2ldif;
e4a41f
         break;
e4a41f
+    case SLAPI_PLUGIN_DB_COMPACT_FN:
e4a41f
+        if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) {
e4a41f
+            return (-1);
e4a41f
+        }
e4a41f
+        (*(IFP *)value) = pblock->pb_plugin->plg_dbcompact;
e4a41f
+        break;
e4a41f
     case SLAPI_PLUGIN_DB_DB2INDEX_FN:
e4a41f
         if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) {
e4a41f
             return (-1);
e4a41f
@@ -2925,7 +2931,12 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value)
e4a41f
         }
e4a41f
         pblock->pb_backend->be_noacl = *((int *)value);
e4a41f
         break;
e4a41f
-
e4a41f
+    case SLAPI_PLUGIN_DB_COMPACT_FN:
e4a41f
+        if (pblock->pb_plugin->plg_type != SLAPI_PLUGIN_DATABASE) {
e4a41f
+            return (-1);
e4a41f
+        }
e4a41f
+        pblock->pb_plugin->plg_dbcompact = (IFP)value;
e4a41f
+        break;
e4a41f
 
e4a41f
     /* extendedop plugin functions */
e4a41f
     case SLAPI_PLUGIN_EXT_OP_FN:
e4a41f
@@ -4137,8 +4148,8 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value)
e4a41f
         break;
e4a41f
 
e4a41f
     case SLAPI_URP_TOMBSTONE_CONFLICT_DN:
e4a41f
-	pblock->pb_intop->pb_urp_tombstone_conflict_dn = (char *)value;
e4a41f
-	break;
e4a41f
+        pblock->pb_intop->pb_urp_tombstone_conflict_dn = (char *)value;
e4a41f
+        break;
e4a41f
 
e4a41f
     case SLAPI_URP_TOMBSTONE_UNIQUEID:
e4a41f
         _pblock_assert_pb_intop(pblock);
e4a41f
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
e4a41f
index 3126a65f3..c48516157 100644
e4a41f
--- a/ldap/servers/slapd/slap.h
e4a41f
+++ b/ldap/servers/slapd/slap.h
e4a41f
@@ -1041,6 +1041,7 @@ struct slapdplugin
e4a41f
             IFP plg_un_db_ldif2db;              /* ldif 2 database */
e4a41f
             IFP plg_un_db_db2ldif;              /* database 2 ldif */
e4a41f
             IFP plg_un_db_db2index;             /* database 2 index */
e4a41f
+            IFP plg_un_db_dbcompact;            /* compact database */
e4a41f
             IFP plg_un_db_archive2db;           /* ldif 2 database */
e4a41f
             IFP plg_un_db_db2archive;           /* database 2 ldif */
e4a41f
             IFP plg_un_db_upgradedb;            /* convert old idl to new */
e4a41f
@@ -1082,6 +1083,7 @@ struct slapdplugin
e4a41f
 #define plg_result plg_un.plg_un_db.plg_un_db_result
e4a41f
 #define plg_ldif2db plg_un.plg_un_db.plg_un_db_ldif2db
e4a41f
 #define plg_db2ldif plg_un.plg_un_db.plg_un_db_db2ldif
e4a41f
+#define plg_dbcompact plg_un.plg_un_db.plg_un_db_dbcompact
e4a41f
 #define plg_db2index plg_un.plg_un_db.plg_un_db_db2index
e4a41f
 #define plg_archive2db plg_un.plg_un_db.plg_un_db_archive2db
e4a41f
 #define plg_db2archive plg_un.plg_un_db.plg_un_db_db2archive
e4a41f
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
e4a41f
index b956ebe63..570765e47 100644
e4a41f
--- a/ldap/servers/slapd/slapi-private.h
e4a41f
+++ b/ldap/servers/slapd/slapi-private.h
e4a41f
@@ -928,6 +928,7 @@ int proxyauth_get_dn(Slapi_PBlock *pb, char **proxydnp, char **errtextp);
e4a41f
 #define SLAPI_PLUGIN_DB_GET_INFO_FN               290
e4a41f
 #define SLAPI_PLUGIN_DB_SET_INFO_FN               291
e4a41f
 #define SLAPI_PLUGIN_DB_CTRL_INFO_FN              292
e4a41f
+#define SLAPI_PLUGIN_DB_COMPACT_FN                294
e4a41f
 
e4a41f
 /**** End of database plugin interface. **************************************/
e4a41f
 
e4a41f
diff --git a/ldap/servers/slapd/task.c b/ldap/servers/slapd/task.c
e4a41f
index 93d31b806..4c7262ab3 100644
e4a41f
--- a/ldap/servers/slapd/task.c
e4a41f
+++ b/ldap/servers/slapd/task.c
e4a41f
@@ -1,6 +1,6 @@
e4a41f
 /** BEGIN COPYRIGHT BLOCK
e4a41f
  * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
e4a41f
- * Copyright (C) 2005 Red Hat, Inc.
e4a41f
+ * Copyright (C) 2021 Red Hat, Inc.
e4a41f
  * All rights reserved.
e4a41f
  *
e4a41f
  * License: GPL (version 3 or any later version).
e4a41f
@@ -2928,6 +2928,105 @@ des2aes_task_destructor(Slapi_Task *task)
e4a41f
                   "des2aes_task_destructor <--\n");
e4a41f
 }
e4a41f
 
e4a41f
+struct task_compact_data
e4a41f
+{
e4a41f
+    char *suffix;
e4a41f
+    Slapi_Task *task;
e4a41f
+};
e4a41f
+
e4a41f
+static void
e4a41f
+compact_db_task_destructor(Slapi_Task *task)
e4a41f
+{
e4a41f
+    slapi_log_err(SLAPI_LOG_PLUGIN, "compact db task",
e4a41f
+                  "compact_db_task_destructor -->\n");
e4a41f
+    if (task) {
e4a41f
+        struct task_compact_data *mydata = (struct task_compact_data *)slapi_task_get_data(task);
e4a41f
+        while (slapi_task_get_refcount(task) > 0) {
e4a41f
+            /* Yield to wait for the task to finish */
e4a41f
+            DS_Sleep(PR_MillisecondsToInterval(100));
e4a41f
+        }
e4a41f
+        if (mydata) {
e4a41f
+            slapi_ch_free((void **)&mydata);
e4a41f
+        }
e4a41f
+    }
e4a41f
+    slapi_log_err(SLAPI_LOG_PLUGIN, "compact db task",
e4a41f
+                  "compact_db_task_destructor <--\n");
e4a41f
+}
e4a41f
+
e4a41f
+static void
e4a41f
+task_compact_thread(void *arg)
e4a41f
+{
e4a41f
+    struct task_compact_data *task_data = arg;
e4a41f
+    Slapi_Task *task = task_data->task;
e4a41f
+    Slapi_Backend *be = NULL;
e4a41f
+    char *cookie = NULL;
e4a41f
+    int32_t rc = -1;
e4a41f
+
e4a41f
+    slapi_task_inc_refcount(task);
e4a41f
+    slapi_task_begin(task, 1);
e4a41f
+
e4a41f
+    be = slapi_get_first_backend(&cookie);
e4a41f
+    while (be) {
e4a41f
+        if (be->be_private == 0) {
e4a41f
+            /* Found a non-private backend, start compacting */
e4a41f
+            rc = (be->be_database->plg_dbcompact)(be);
e4a41f
+            break;
e4a41f
+        }
e4a41f
+        be = (backend *)slapi_get_next_backend(cookie);
e4a41f
+    }
e4a41f
+    slapi_ch_free_string(&cookie);
e4a41f
+
e4a41f
+    slapi_task_finish(task, rc);
e4a41f
+    slapi_task_dec_refcount(task);
e4a41f
+}
e4a41f
+
e4a41f
+/*
e4a41f
+ * compact the BDB database
e4a41f
+ *
e4a41f
+ *  dn: cn=compact_it,cn=compact db,cn=tasks,cn=config
e4a41f
+ *  objectclass: top
e4a41f
+ *  objectclass: extensibleObject
e4a41f
+ *  cn: compact_it
e4a41f
+ */
e4a41f
+static int
e4a41f
+task_compact_db_add(Slapi_PBlock *pb,
e4a41f
+                    Slapi_Entry *e,
e4a41f
+                    Slapi_Entry *eAfter __attribute__((unused)),
e4a41f
+                    int *returncode,
e4a41f
+                    char *returntext,
e4a41f
+                    void *arg __attribute__((unused)))
e4a41f
+{
e4a41f
+    Slapi_Task *task = slapi_new_task(slapi_entry_get_ndn(e));
e4a41f
+    struct task_compact_data *task_data = NULL;
e4a41f
+    PRThread *thread = NULL;
e4a41f
+
e4a41f
+    slapi_task_log_notice(task, "Beginning database compaction task...\n");
e4a41f
+
e4a41f
+    /* Register our destructor for cleaning up our private data */
e4a41f
+    slapi_task_set_destructor_fn(task, compact_db_task_destructor);
e4a41f
+
e4a41f
+    task_data = (struct task_compact_data *)slapi_ch_calloc(1, sizeof(struct task_compact_data));
e4a41f
+    task_data->task = task;
e4a41f
+    slapi_task_set_data(task, task_data);
e4a41f
+
e4a41f
+    /* Start the compaction as a separate thread */
e4a41f
+    thread = PR_CreateThread(PR_USER_THREAD, task_compact_thread,
e4a41f
+             (void *)task_data, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
e4a41f
+             PR_UNJOINABLE_THREAD, SLAPD_DEFAULT_THREAD_STACKSIZE);
e4a41f
+    if (thread == NULL) {
e4a41f
+        slapi_log_err(SLAPI_LOG_ERR, "task_compact_db_add", "Unable to create db compact thread!\n");
e4a41f
+        *returncode = LDAP_OPERATIONS_ERROR;
e4a41f
+        slapi_ch_free((void **)&task_data);
e4a41f
+    }
e4a41f
+
e4a41f
+    if (*returncode != LDAP_SUCCESS) {
e4a41f
+        slapi_task_finish(task, *returncode);
e4a41f
+        return SLAPI_DSE_CALLBACK_ERROR;
e4a41f
+    }
e4a41f
+
e4a41f
+    return SLAPI_DSE_CALLBACK_OK;
e4a41f
+}
e4a41f
+
e4a41f
 /* cleanup old tasks that may still be in the DSE from a previous session
e4a41f
  * (this can happen if the server crashes [no matter how unlikely we like
e4a41f
  * to think that is].)
e4a41f
@@ -3010,6 +3109,7 @@ task_init(void)
e4a41f
     slapi_task_register_handler("sysconfig reload", task_sysconfig_reload_add);
e4a41f
     slapi_task_register_handler("fixup tombstones", task_fixup_tombstones_add);
e4a41f
     slapi_task_register_handler("des2aes", task_des2aes);
e4a41f
+    slapi_task_register_handler("compact db", task_compact_db_add);
e4a41f
 }
e4a41f
 
e4a41f
 /* called when the server is shutting down -- abort all existing tasks */
e4a41f
diff --git a/src/cockpit/389-console/src/database.jsx b/src/cockpit/389-console/src/database.jsx
e4a41f
index 11cae972c..b73dc8460 100644
e4a41f
--- a/src/cockpit/389-console/src/database.jsx
e4a41f
+++ b/src/cockpit/389-console/src/database.jsx
e4a41f
@@ -196,6 +196,7 @@ export class Database extends React.Component {
e4a41f
                                     dblocksMonitoringPause: attrs['nsslapd-db-locks-monitoring-pause'],
e4a41f
                                     chxpoint: attrs['nsslapd-db-checkpoint-interval'],
e4a41f
                                     compactinterval: attrs['nsslapd-db-compactdb-interval'],
e4a41f
+                                    compacttime: attrs['nsslapd-db-compactdb-time'],
e4a41f
                                     importcacheauto: attrs['nsslapd-import-cache-autosize'],
e4a41f
                                     importcachesize: attrs['nsslapd-import-cachesize'],
e4a41f
                                 },
e4a41f
diff --git a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
e4a41f
index 6a71c138d..1fa9f2cc2 100644
e4a41f
--- a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
e4a41f
+++ b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx
e4a41f
@@ -36,6 +36,7 @@ export class GlobalDatabaseConfig extends React.Component {
e4a41f
             dblocksMonitoringPause: this.props.data.dblocksMonitoringPause,
e4a41f
             chxpoint: this.props.data.chxpoint,
e4a41f
             compactinterval: this.props.data.compactinterval,
e4a41f
+            compacttime: this.props.data.compacttime,
e4a41f
             importcachesize: this.props.data.importcachesize,
e4a41f
             importcacheauto: this.props.data.importcacheauto,
e4a41f
             // These variables store the original value (used for saving config)
e4a41f
@@ -55,6 +56,7 @@ export class GlobalDatabaseConfig extends React.Component {
e4a41f
             _dblocksMonitoringPause: this.props.data.dblocksMonitoringPause,
e4a41f
             _chxpoint: this.props.data.chxpoint,
e4a41f
             _compactinterval: this.props.data.compactinterval,
e4a41f
+            _compacttime: this.props.data.compacttime,
e4a41f
             _importcachesize: this.props.data.importcachesize,
e4a41f
             _importcacheauto: this.props.data.importcacheauto,
e4a41f
             _db_cache_auto: this.props.data.db_cache_auto,
e4a41f
@@ -186,6 +188,10 @@ export class GlobalDatabaseConfig extends React.Component {
e4a41f
             cmd.push("--compactdb-interval=" + this.state.compactinterval);
e4a41f
             requireRestart = true;
e4a41f
         }
e4a41f
+        if (this.state._compacttime != this.state.compacttime) {
e4a41f
+            cmd.push("--compactdb-time=" + this.state.compacttime);
e4a41f
+            requireRestart = true;
e4a41f
+        }
e4a41f
         if (this.state.import_cache_auto) {
e4a41f
             // Auto cache is selected
e4a41f
             if (this.state._import_cache_auto != this.state.import_cache_auto) {
e4a41f
@@ -485,7 +491,15 @@ export class GlobalDatabaseConfig extends React.Component {
e4a41f
                                             Database Compact Interval
e4a41f
                                         
e4a41f
                                         
e4a41f
-                                            <input id="compactinterval" value={this.state.compactinterval} onChange={this.handleChange} className="ds-input-auto" type="text" />
e4a41f
+                                            <input id="compactinterval" value={this.state.compactinterval} onChange={this.handleChange} className="ds-input-auto" type="number" />
e4a41f
+                                        
e4a41f
+                                    </Row>
e4a41f
+                                    <Row className="ds-margin-top" title="The Time Of Day to perform the database compaction after the compact interval has been met.  Uses the format: 'HH:MM' and defaults to '23:59'. (nsslapd-db-compactdb-time)">
e4a41f
+                                        
e4a41f
+                                            Database Compact Time
e4a41f
+                                        
e4a41f
+                                        
e4a41f
+                                            <input id="compacttime" value={this.state.compacttime} onChange={this.handleChange} className="ds-input-auto" type="number" />
e4a41f
                                         
e4a41f
                                     </Row>
e4a41f
                                     <Row className="ds-margin-top" title="The number of database locks (nsslapd-db-locks).">
e4a41f
diff --git a/src/lib389/lib389/_constants.py b/src/lib389/lib389/_constants.py
e4a41f
index c184c8d4f..d6161cebb 100644
e4a41f
--- a/src/lib389/lib389/_constants.py
e4a41f
+++ b/src/lib389/lib389/_constants.py
e4a41f
@@ -154,6 +154,7 @@ DN_EUUID_TASK = "cn=entryuuid task,%s" % DN_TASKS
e4a41f
 DN_TOMB_FIXUP_TASK = "cn=fixup tombstones,%s" % DN_TASKS
e4a41f
 DN_FIXUP_LINKED_ATTIBUTES = "cn=fixup linked attributes,%s" % DN_TASKS
e4a41f
 DN_AUTOMEMBER_REBUILD_TASK = "cn=automember rebuild membership,%s" % DN_TASKS
e4a41f
+DN_COMPACTDB_TASK = "cn=compact db,%s" % DN_TASKS
e4a41f
 
e4a41f
 # Script Constants
e4a41f
 LDIF2DB = 'ldif2db'
e4a41f
diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py
e4a41f
index 13bb27842..ad78a6ffe 100644
e4a41f
--- a/src/lib389/lib389/backend.py
e4a41f
+++ b/src/lib389/lib389/backend.py
e4a41f
@@ -1005,6 +1005,7 @@ class DatabaseConfig(DSLdapObject):
e4a41f
                     'nsslapd-db-transaction-wait',
e4a41f
                     'nsslapd-db-checkpoint-interval',
e4a41f
                     'nsslapd-db-compactdb-interval',
e4a41f
+                    'nsslapd-db-compactdb-time',
e4a41f
                     'nsslapd-db-page-size',
e4a41f
                     'nsslapd-db-transaction-batch-val',
e4a41f
                     'nsslapd-db-transaction-batch-min-wait',
e4a41f
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
e4a41f
index 722764d10..7b2f32c23 100644
e4a41f
--- a/src/lib389/lib389/cli_conf/backend.py
e4a41f
+++ b/src/lib389/lib389/cli_conf/backend.py
e4a41f
@@ -1,5 +1,5 @@
e4a41f
 # --- BEGIN COPYRIGHT BLOCK ---
e4a41f
-# Copyright (C) 2020 Red Hat, Inc.
e4a41f
+# Copyright (C) 2021 Red Hat, Inc.
e4a41f
 # Copyright (C) 2019 William Brown <william@blackhats.net.au>
e4a41f
 # All rights reserved.
e4a41f
 #
e4a41f
@@ -19,6 +19,7 @@ from lib389.chaining import (ChainingLinks)
e4a41f
 from lib389.monitor import MonitorLDBM
e4a41f
 from lib389.replica import Replicas
e4a41f
 from lib389.utils import ensure_str, is_a_dn, is_dn_parent
e4a41f
+from lib389.tasks import DBCompactTask
e4a41f
 from lib389._constants import *
e4a41f
 from lib389.cli_base import (
e4a41f
     _format_status,
e4a41f
@@ -41,6 +42,7 @@ arg_to_attr = {
e4a41f
         'txn_wait': 'nsslapd-db-transaction-wait',
e4a41f
         'checkpoint_interval': 'nsslapd-db-checkpoint-interval',
e4a41f
         'compactdb_interval': 'nsslapd-db-compactdb-interval',
e4a41f
+        'compactdb_time': 'nsslapd-db-compactdb-time',
e4a41f
         'txn_batch_val': 'nsslapd-db-transaction-batch-val',
e4a41f
         'txn_batch_min': 'nsslapd-db-transaction-batch-min-wait',
e4a41f
         'txn_batch_max': 'nsslapd-db-transaction-batch-max-wait',
e4a41f
@@ -789,6 +791,18 @@ def backend_reindex_vlv(inst, basedn, log, args):
e4a41f
     log.info("Successfully reindexed VLV indexes")
e4a41f
 
e4a41f
 
e4a41f
+def backend_compact(inst, basedn, log, args):
e4a41f
+    task = DBCompactTask(inst)
e4a41f
+    task_properties = {}
e4a41f
+    if args.only_changelog:
e4a41f
+        task_properties = {'justChangelog': 'yes'}
e4a41f
+    task.create(properties=task_properties)
e4a41f
+    task.wait()
e4a41f
+    if task.get_exit_code() != 0:
e4a41f
+        raise ValueError("Failed to create Database Compaction Task")
e4a41f
+    log.info("Successfully started Database Compaction Task")
e4a41f
+
e4a41f
+
e4a41f
 def create_parser(subparsers):
e4a41f
     backend_parser = subparsers.add_parser('backend', help="Manage database suffixes and backends")
e4a41f
     subcommands = backend_parser.add_subparsers(help="action")
e4a41f
@@ -994,6 +1008,7 @@ def create_parser(subparsers):
e4a41f
     set_db_config_parser.add_argument('--checkpoint-interval', help='Sets the amount of time in seconds after which the Directory Server sends a '
e4a41f
                                                                     'checkpoint entry to the database transaction log')
e4a41f
     set_db_config_parser.add_argument('--compactdb-interval', help='Sets the interval in seconds when the database is compacted')
e4a41f
+    set_db_config_parser.add_argument('--compactdb-time', help='Sets the Time Of Day to compact the database after the "compactdb interval" has been reached:  Use this format to set the hour and minute: HH:MM')
e4a41f
     set_db_config_parser.add_argument('--txn-batch-val', help='Specifies how many transactions will be batched before being committed')
e4a41f
     set_db_config_parser.add_argument('--txn-batch-min', help='Controls when transactions should be flushed earliest, independently of '
e4a41f
                                                               'the batch count (only works when txn-batch-val is set)')
e4a41f
@@ -1121,3 +1136,10 @@ def create_parser(subparsers):
e4a41f
     #######################################################
e4a41f
     get_tree_parser = subcommands.add_parser('get-tree', help='Get a representation of the suffix tree')
e4a41f
     get_tree_parser.set_defaults(func=backend_get_tree)
e4a41f
+
e4a41f
+    #######################################################
e4a41f
+    # Run the db compaction task
e4a41f
+    #######################################################
e4a41f
+    compact_parser = subcommands.add_parser('compact-db', help='Compact the database and the replication changelog')
e4a41f
+    compact_parser.set_defaults(func=backend_compact)
e4a41f
+    compact_parser.add_argument('--only-changelog', action='store_true', help='Only compact the Replication Change Log')
e4a41f
diff --git a/src/lib389/lib389/cli_conf/replication.py b/src/lib389/lib389/cli_conf/replication.py
e4a41f
index 04886f632..3478a0a1f 100644
e4a41f
--- a/src/lib389/lib389/cli_conf/replication.py
e4a41f
+++ b/src/lib389/lib389/cli_conf/replication.py
e4a41f
@@ -37,6 +37,7 @@ arg_to_attr = {
e4a41f
         'max_entries': 'nsslapd-changelogmaxentries',
e4a41f
         'max_age': 'nsslapd-changelogmaxage',
e4a41f
         'compact_interval': 'nsslapd-changelogcompactdb-interval',
e4a41f
+        'compact_time': 'nsslapd-changelogcompactdb-time',
e4a41f
         'trim_interval': 'nsslapd-changelogtrim-interval',
e4a41f
         'encrypt_algo': 'nsslapd-encryptionalgorithm',
e4a41f
         'encrypt_key': 'nssymmetrickey',
e4a41f
@@ -1216,6 +1217,8 @@ def create_parser(subparsers):
e4a41f
     repl_set_cl.add_argument('--max-entries', help="The maximum number of entries to get in the replication changelog")
e4a41f
     repl_set_cl.add_argument('--max-age', help="The maximum age of a replication changelog entry")
e4a41f
     repl_set_cl.add_argument('--compact-interval', help="The replication changelog compaction interval")
e4a41f
+    repl_set_cl.add_argument('--compact-time', help='Sets the Time Of Day to compact the database after the changelog "compact interval" '
e4a41f
+                                                    'has been reached:  Use this format to set the hour and minute: HH:MM')
e4a41f
     repl_set_cl.add_argument('--trim-interval', help="The interval to check if the replication changelog can be trimmed")
e4a41f
 
e4a41f
     repl_get_cl = repl_subcommands.add_parser('get-changelog', help='Display replication changelog attributes.')
e4a41f
diff --git a/src/lib389/lib389/tasks.py b/src/lib389/lib389/tasks.py
e4a41f
index 590c6ee79..b64bc6ce5 100644
e4a41f
--- a/src/lib389/lib389/tasks.py
e4a41f
+++ b/src/lib389/lib389/tasks.py
e4a41f
@@ -217,6 +217,19 @@ class EntryUUIDFixupTask(Task):
e4a41f
         self._must_attributes.extend(['basedn'])
e4a41f
 
e4a41f
 
e4a41f
+class DBCompactTask(Task):
e4a41f
+    """A single instance of compactdb task entry
e4a41f
+
e4a41f
+    :param instance: An instance
e4a41f
+    :type instance: lib389.DirSrv
e4a41f
+    """
e4a41f
+
e4a41f
+    def __init__(self, instance, dn=None):
e4a41f
+        self.cn = 'compact_db_' + Task._get_task_date()
e4a41f
+        dn = "cn=" + self.cn + "," + DN_COMPACTDB_TASK
e4a41f
+        super(DBCompactTask, self).__init__(instance, dn)
e4a41f
+
e4a41f
+
e4a41f
 class SchemaReloadTask(Task):
e4a41f
     """A single instance of schema reload task entry
e4a41f
 
e4a41f
@@ -227,7 +240,6 @@ class SchemaReloadTask(Task):
e4a41f
     def __init__(self, instance, dn=None):
e4a41f
         self.cn = 'schema_reload_' + Task._get_task_date()
e4a41f
         dn = "cn=" + self.cn + ",cn=schema reload task," + DN_TASKS
e4a41f
-
e4a41f
         super(SchemaReloadTask, self).__init__(instance, dn)
e4a41f
 
e4a41f
 
e4a41f
-- 
e4a41f
2.26.3
e4a41f