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

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