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

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