andykimpe / rpms / 389-ds-base

Forked from rpms/389-ds-base 5 months ago
Clone

Blame SOURCES/0003-do-not-add-referrals-for-masters-with-different-data.patch

47c2e9
From e202c62c3b4c92163d2de9f3da9a9f3efc81e4b8 Mon Sep 17 00:00:00 2001
47c2e9
From: progier389 <72748589+progier389@users.noreply.github.com>
47c2e9
Date: Thu, 12 Nov 2020 18:50:04 +0100
47c2e9
Subject: [PATCH 3/3] do not add referrals for masters with different data
47c2e9
 generation #2054 (#4427)
47c2e9
47c2e9
Bug description:
47c2e9
The problem is that some operation mandatory in the usual cases are
47c2e9
also performed when replication cannot take place because the
47c2e9
database set are differents (i.e: RUV generation ids are different)
47c2e9
47c2e9
One of the issue is that the csn generator state is updated when
47c2e9
starting a replication session (it is a problem when trying to
47c2e9
reset the time skew, as freshly reinstalled replicas get infected
47c2e9
by the old ones)
47c2e9
47c2e9
A second issue is that the RUV got updated when ending a replication session
47c2e9
(which may add replica that does not share the same data set,
47c2e9
then update operations on consumer retun referrals towards wrong masters
47c2e9
47c2e9
Fix description:
47c2e9
The fix checks the RUVs generation id before updating the csn generator
47c2e9
and before updating the RUV.
47c2e9
47c2e9
Reviewed by: mreynolds
47c2e9
             firstyear
47c2e9
             vashirov
47c2e9
47c2e9
Platforms tested: F32
47c2e9
---
47c2e9
 .../suites/replication/regression_test.py     | 290 ++++++++++++++++++
47c2e9
 ldap/servers/plugins/replication/repl5.h      |   1 +
47c2e9
 .../plugins/replication/repl5_inc_protocol.c  |  20 +-
47c2e9
 .../plugins/replication/repl5_replica.c       |  39 ++-
47c2e9
 src/lib389/lib389/dseldif.py                  |  37 +++
47c2e9
 5 files changed, 368 insertions(+), 19 deletions(-)
47c2e9
47c2e9
diff --git a/dirsrvtests/tests/suites/replication/regression_test.py b/dirsrvtests/tests/suites/replication/regression_test.py
47c2e9
index 14b9d6a44..a72af6b30 100644
47c2e9
--- a/dirsrvtests/tests/suites/replication/regression_test.py
47c2e9
+++ b/dirsrvtests/tests/suites/replication/regression_test.py
47c2e9
@@ -13,6 +13,7 @@ from lib389.idm.user import TEST_USER_PROPERTIES, UserAccounts
47c2e9
 from lib389.pwpolicy import PwPolicyManager
47c2e9
 from lib389.utils import *
47c2e9
 from lib389.topologies import topology_m2 as topo_m2, TopologyMain, topology_m3 as topo_m3, create_topology, _remove_ssca_db, topology_i2 as topo_i2
47c2e9
+from lib389.topologies import topology_m2c2 as topo_m2c2
47c2e9
 from lib389._constants import *
47c2e9
 from lib389.idm.organizationalunit import OrganizationalUnits
47c2e9
 from lib389.idm.user import UserAccount
47c2e9
@@ -22,6 +23,7 @@ from lib389.idm.directorymanager import DirectoryManager
47c2e9
 from lib389.replica import Replicas, ReplicationManager, Changelog5, BootstrapReplicationManager
47c2e9
 from lib389.agreement import Agreements
47c2e9
 from lib389 import pid_from_file
47c2e9
+from lib389.dseldif import *
47c2e9
 
47c2e9
 
47c2e9
 pytestmark = pytest.mark.tier1
47c2e9
@@ -1027,6 +1029,294 @@ def test_online_init_should_create_keepalive_entries(topo_m2):
47c2e9
     verify_keepalive_entries(topo_m2, True);
47c2e9
 
47c2e9
 
47c2e9
+def get_agreement(agmts, consumer):
47c2e9
+    # Get agreement towards consumer among the agremment list
47c2e9
+    for agmt in agmts.list():
47c2e9
+        if (agmt.get_attr_val_utf8('nsDS5ReplicaPort') == str(consumer.port) and
47c2e9
+              agmt.get_attr_val_utf8('nsDS5ReplicaHost') == consumer.host):
47c2e9
+            return agmt
47c2e9
+    return None;
47c2e9
+
47c2e9
+
47c2e9
+def test_ruv_url_not_added_if_different_uuid(topo_m2c2):
47c2e9
+    """Check that RUV url is not updated if RUV generation uuid are different
47c2e9
+
47c2e9
+    :id: 7cc30a4e-0ffd-4758-8f00-e500279af344
47c2e9
+    :setup: Two masters + two consumers replication setup
47c2e9
+    :steps:
47c2e9
+        1. Generate ldif without replication data
47c2e9
+        2. Init both masters from that ldif
47c2e9
+             (to clear the ruvs and generates different generation uuid)
47c2e9
+        3. Perform on line init from master1 to consumer1
47c2e9
+               and from master2 to consumer2
47c2e9
+        4. Perform update on both masters
47c2e9
+        5. Check that c1 RUV does not contains URL towards m2
47c2e9
+        6. Check that c2 RUV does contains URL towards m2
47c2e9
+        7. Perform on line init from master1 to master2
47c2e9
+        8. Perform update on master2
47c2e9
+        9. Check that c1 RUV does contains URL towards m2
47c2e9
+    :expectedresults:
47c2e9
+        1. No error while generating ldif
47c2e9
+        2. No error while importing the ldif file
47c2e9
+        3. No error and Initialization done.
47c2e9
+        4. No error
47c2e9
+        5. master2 replicaid should not be in the consumer1 RUV
47c2e9
+        6. master2 replicaid should be in the consumer2 RUV
47c2e9
+        7. No error and Initialization done.
47c2e9
+        8. No error
47c2e9
+        9. master2 replicaid should be in the consumer1 RUV
47c2e9
+
47c2e9
+    """
47c2e9
+
47c2e9
+    # Variables initialization
47c2e9
+    repl = ReplicationManager(DEFAULT_SUFFIX)
47c2e9
+
47c2e9
+    m1 = topo_m2c2.ms["master1"]
47c2e9
+    m2 = topo_m2c2.ms["master2"]
47c2e9
+    c1 = topo_m2c2.cs["consumer1"]
47c2e9
+    c2 = topo_m2c2.cs["consumer2"]
47c2e9
+
47c2e9
+    replica_m1 = Replicas(m1).get(DEFAULT_SUFFIX)
47c2e9
+    replica_m2 = Replicas(m2).get(DEFAULT_SUFFIX)
47c2e9
+    replica_c1 = Replicas(c1).get(DEFAULT_SUFFIX)
47c2e9
+    replica_c2 = Replicas(c2).get(DEFAULT_SUFFIX)
47c2e9
+
47c2e9
+    replicid_m2 = replica_m2.get_rid()
47c2e9
+
47c2e9
+    agmts_m1 = Agreements(m1, replica_m1.dn)
47c2e9
+    agmts_m2 = Agreements(m2, replica_m2.dn)
47c2e9
+
47c2e9
+    m1_m2 = get_agreement(agmts_m1, m2)
47c2e9
+    m1_c1 = get_agreement(agmts_m1, c1)
47c2e9
+    m1_c2 = get_agreement(agmts_m1, c2)
47c2e9
+    m2_m1 = get_agreement(agmts_m2, m1)
47c2e9
+    m2_c1 = get_agreement(agmts_m2, c1)
47c2e9
+    m2_c2 = get_agreement(agmts_m2, c2)
47c2e9
+
47c2e9
+    # Step 1: Generate ldif without replication data
47c2e9
+    m1.stop()
47c2e9
+    m2.stop()
47c2e9
+    ldif_file = '%s/norepl.ldif' % m1.get_ldif_dir()
47c2e9
+    m1.db2ldif(bename=DEFAULT_BENAME, suffixes=[DEFAULT_SUFFIX],
47c2e9
+               excludeSuffixes=None, repl_data=False,
47c2e9
+               outputfile=ldif_file, encrypt=False)
47c2e9
+    # Remove replication metadata that are still in the ldif
47c2e9
+    # _remove_replication_data(ldif_file)
47c2e9
+
47c2e9
+    # Step 2: Init both masters from that ldif
47c2e9
+    m1.ldif2db(DEFAULT_BENAME, None, None, None, ldif_file)
47c2e9
+    m2.ldif2db(DEFAULT_BENAME, None, None, None, ldif_file)
47c2e9
+    m1.start()
47c2e9
+    m2.start()
47c2e9
+
47c2e9
+    # Step 3: Perform on line init from master1 to consumer1
47c2e9
+    #          and from master2 to consumer2
47c2e9
+    m1_c1.begin_reinit()
47c2e9
+    m2_c2.begin_reinit()
47c2e9
+    (done, error) = m1_c1.wait_reinit()
47c2e9
+    assert done is True
47c2e9
+    assert error is False
47c2e9
+    (done, error) = m2_c2.wait_reinit()
47c2e9
+    assert done is True
47c2e9
+    assert error is False
47c2e9
+
47c2e9
+    # Step 4: Perform update on both masters
47c2e9
+    repl.test_replication(m1, c1)
47c2e9
+    repl.test_replication(m2, c2)
47c2e9
+
47c2e9
+    # Step 5: Check that c1 RUV does not contains URL towards m2
47c2e9
+    ruv = replica_c1.get_ruv()
47c2e9
+    log.debug(f"c1 RUV: {ruv}")
47c2e9
+    url=ruv._rid_url.get(replica_m2.get_rid())
47c2e9
+    if (url == None):
47c2e9
+        log.debug(f"No URL for RID {replica_m2.get_rid()} in RUV");
47c2e9
+    else:
47c2e9
+        log.debug(f"URL for RID {replica_m2.get_rid()} in RUV is {url}");
47c2e9
+        log.error(f"URL for RID {replica_m2.get_rid()} found in RUV")
47c2e9
+        #Note: this assertion fails if issue 2054 is not fixed.
47c2e9
+        assert False
47c2e9
+
47c2e9
+    # Step 6: Check that c2 RUV does contains URL towards m2
47c2e9
+    ruv = replica_c2.get_ruv()
47c2e9
+    log.debug(f"c1 RUV: {ruv} {ruv._rids} ")
47c2e9
+    url=ruv._rid_url.get(replica_m2.get_rid())
47c2e9
+    if (url == None):
47c2e9
+        log.error(f"No URL for RID {replica_m2.get_rid()} in RUV");
47c2e9
+        assert False
47c2e9
+    else:
47c2e9
+        log.debug(f"URL for RID {replica_m2.get_rid()} in RUV is {url}");
47c2e9
+
47c2e9
+
47c2e9
+    # Step 7: Perform on line init from master1 to master2
47c2e9
+    m1_m2.begin_reinit()
47c2e9
+    (done, error) = m1_m2.wait_reinit()
47c2e9
+    assert done is True
47c2e9
+    assert error is False
47c2e9
+
47c2e9
+    # Step 8: Perform update on master2
47c2e9
+    repl.test_replication(m2, c1)
47c2e9
+
47c2e9
+    # Step 9: Check that c1 RUV does contains URL towards m2
47c2e9
+    ruv = replica_c1.get_ruv()
47c2e9
+    log.debug(f"c1 RUV: {ruv} {ruv._rids} ")
47c2e9
+    url=ruv._rid_url.get(replica_m2.get_rid())
47c2e9
+    if (url == None):
47c2e9
+        log.error(f"No URL for RID {replica_m2.get_rid()} in RUV");
47c2e9
+        assert False
47c2e9
+    else:
47c2e9
+        log.debug(f"URL for RID {replica_m2.get_rid()} in RUV is {url}");
47c2e9
+
47c2e9
+
47c2e9
+def test_csngen_state_not_updated_if_different_uuid(topo_m2c2):
47c2e9
+    """Check that csngen remote offset is not updated if RUV generation uuid are different
47c2e9
+
47c2e9
+    :id: 77694b8e-22ae-11eb-89b2-482ae39447e5
47c2e9
+    :setup: Two masters + two consumers replication setup
47c2e9
+    :steps:
47c2e9
+        1. Disable m1<->m2 agreement to avoid propagate timeSkew
47c2e9
+        2. Generate ldif without replication data
47c2e9
+        3. Increase time skew on master2
47c2e9
+        4. Init both masters from that ldif
47c2e9
+             (to clear the ruvs and generates different generation uuid)
47c2e9
+        5. Perform on line init from master1 to consumer1 and master2 to consumer2
47c2e9
+        6. Perform update on both masters
47c2e9
+        7: Check that c1 has no time skew
47c2e9
+        8: Check that c2 has time skew
47c2e9
+        9. Init master2 from master1
47c2e9
+        10. Perform update on master2
47c2e9
+        11. Check that c1 has time skew
47c2e9
+    :expectedresults:
47c2e9
+        1. No error
47c2e9
+        2. No error while generating ldif
47c2e9
+        3. No error
47c2e9
+        4. No error while importing the ldif file
47c2e9
+        5. No error and Initialization done.
47c2e9
+        6. No error
47c2e9
+        7. c1 time skew should be lesser than threshold
47c2e9
+        8. c2 time skew should be higher than threshold
47c2e9
+        9. No error and Initialization done.
47c2e9
+        10. No error
47c2e9
+        11. c1 time skew should be higher than threshold
47c2e9
+
47c2e9
+    """
47c2e9
+
47c2e9
+    # Variables initialization
47c2e9
+    repl = ReplicationManager(DEFAULT_SUFFIX)
47c2e9
+
47c2e9
+    m1 = topo_m2c2.ms["master1"]
47c2e9
+    m2 = topo_m2c2.ms["master2"]
47c2e9
+    c1 = topo_m2c2.cs["consumer1"]
47c2e9
+    c2 = topo_m2c2.cs["consumer2"]
47c2e9
+
47c2e9
+    replica_m1 = Replicas(m1).get(DEFAULT_SUFFIX)
47c2e9
+    replica_m2 = Replicas(m2).get(DEFAULT_SUFFIX)
47c2e9
+    replica_c1 = Replicas(c1).get(DEFAULT_SUFFIX)
47c2e9
+    replica_c2 = Replicas(c2).get(DEFAULT_SUFFIX)
47c2e9
+
47c2e9
+    replicid_m2 = replica_m2.get_rid()
47c2e9
+
47c2e9
+    agmts_m1 = Agreements(m1, replica_m1.dn)
47c2e9
+    agmts_m2 = Agreements(m2, replica_m2.dn)
47c2e9
+
47c2e9
+    m1_m2 = get_agreement(agmts_m1, m2)
47c2e9
+    m1_c1 = get_agreement(agmts_m1, c1)
47c2e9
+    m1_c2 = get_agreement(agmts_m1, c2)
47c2e9
+    m2_m1 = get_agreement(agmts_m2, m1)
47c2e9
+    m2_c1 = get_agreement(agmts_m2, c1)
47c2e9
+    m2_c2 = get_agreement(agmts_m2, c2)
47c2e9
+
47c2e9
+    # Step 1: Disable m1<->m2 agreement to avoid propagate timeSkew
47c2e9
+    m1_m2.pause()
47c2e9
+    m2_m1.pause()
47c2e9
+
47c2e9
+    # Step 2: Generate ldif without replication data
47c2e9
+    m1.stop()
47c2e9
+    m2.stop()
47c2e9
+    ldif_file = '%s/norepl.ldif' % m1.get_ldif_dir()
47c2e9
+    m1.db2ldif(bename=DEFAULT_BENAME, suffixes=[DEFAULT_SUFFIX],
47c2e9
+               excludeSuffixes=None, repl_data=False,
47c2e9
+               outputfile=ldif_file, encrypt=False)
47c2e9
+    # Remove replication metadata that are still in the ldif
47c2e9
+    # _remove_replication_data(ldif_file)
47c2e9
+
47c2e9
+    # Step 3: Increase time skew on master2
47c2e9
+    timeSkew=6*3600
47c2e9
+    # We can modify master2 time skew
47c2e9
+    # But the time skew on the consumer may be smaller
47c2e9
+    # depending on when the cnsgen generation time is updated
47c2e9
+    # and when first csn get replicated.
47c2e9
+    # Since we use timeSkew has threshold value to detect
47c2e9
+    # whether there are time skew or not,
47c2e9
+    # lets add a significative margin (longer than the test duration)
47c2e9
+    # to avoid any risk of erroneous failure
47c2e9
+    timeSkewMargin = 300
47c2e9
+    DSEldif(m2)._increaseTimeSkew(DEFAULT_SUFFIX, timeSkew+timeSkewMargin)
47c2e9
+
47c2e9
+    # Step 4: Init both masters from that ldif
47c2e9
+    m1.ldif2db(DEFAULT_BENAME, None, None, None, ldif_file)
47c2e9
+    m2.ldif2db(DEFAULT_BENAME, None, None, None, ldif_file)
47c2e9
+    m1.start()
47c2e9
+    m2.start()
47c2e9
+
47c2e9
+    # Step 5: Perform on line init from master1 to consumer1
47c2e9
+    #          and from master2 to consumer2
47c2e9
+    m1_c1.begin_reinit()
47c2e9
+    m2_c2.begin_reinit()
47c2e9
+    (done, error) = m1_c1.wait_reinit()
47c2e9
+    assert done is True
47c2e9
+    assert error is False
47c2e9
+    (done, error) = m2_c2.wait_reinit()
47c2e9
+    assert done is True
47c2e9
+    assert error is False
47c2e9
+
47c2e9
+    # Step 6: Perform update on both masters
47c2e9
+    repl.test_replication(m1, c1)
47c2e9
+    repl.test_replication(m2, c2)
47c2e9
+
47c2e9
+    # Step 7: Check that c1 has no time skew
47c2e9
+    # Stop server to insure that dse.ldif is uptodate
47c2e9
+    c1.stop()
47c2e9
+    c1_nsState = DSEldif(c1).readNsState(DEFAULT_SUFFIX)[0]
47c2e9
+    c1_timeSkew = int(c1_nsState['time_skew'])
47c2e9
+    log.debug(f"c1 time skew: {c1_timeSkew}")
47c2e9
+    if (c1_timeSkew >= timeSkew):
47c2e9
+        log.error(f"c1 csngen state has unexpectedly been synchronized with m2: time skew {c1_timeSkew}")
47c2e9
+        assert False
47c2e9
+    c1.start()
47c2e9
+
47c2e9
+    # Step 8: Check that c2 has time skew
47c2e9
+    # Stop server to insure that dse.ldif is uptodate
47c2e9
+    c2.stop()
47c2e9
+    c2_nsState = DSEldif(c2).readNsState(DEFAULT_SUFFIX)[0]
47c2e9
+    c2_timeSkew = int(c2_nsState['time_skew'])
47c2e9
+    log.debug(f"c2 time skew: {c2_timeSkew}")
47c2e9
+    if (c2_timeSkew < timeSkew):
47c2e9
+        log.error(f"c2 csngen state has not been synchronized with m2: time skew {c2_timeSkew}")
47c2e9
+        assert False
47c2e9
+    c2.start()
47c2e9
+
47c2e9
+    # Step 9: Perform on line init from master1 to master2
47c2e9
+    m1_c1.pause()
47c2e9
+    m1_m2.resume()
47c2e9
+    m1_m2.begin_reinit()
47c2e9
+    (done, error) = m1_m2.wait_reinit()
47c2e9
+    assert done is True
47c2e9
+    assert error is False
47c2e9
+
47c2e9
+    # Step 10: Perform update on master2
47c2e9
+    repl.test_replication(m2, c1)
47c2e9
+
47c2e9
+    # Step 11: Check that c1 has time skew
47c2e9
+    # Stop server to insure that dse.ldif is uptodate
47c2e9
+    c1.stop()
47c2e9
+    c1_nsState = DSEldif(c1).readNsState(DEFAULT_SUFFIX)[0]
47c2e9
+    c1_timeSkew = int(c1_nsState['time_skew'])
47c2e9
+    log.debug(f"c1 time skew: {c1_timeSkew}")
47c2e9
+    if (c1_timeSkew < timeSkew):
47c2e9
+        log.error(f"c1 csngen state has not been synchronized with m2: time skew {c1_timeSkew}")
47c2e9
+        assert False
47c2e9
+
47c2e9
+
47c2e9
 if __name__ == '__main__':
47c2e9
     # Run isolated
47c2e9
     # -s for DEBUG mode
47c2e9
diff --git a/ldap/servers/plugins/replication/repl5.h b/ldap/servers/plugins/replication/repl5.h
47c2e9
index b35f724c2..f1c596a3f 100644
47c2e9
--- a/ldap/servers/plugins/replication/repl5.h
47c2e9
+++ b/ldap/servers/plugins/replication/repl5.h
47c2e9
@@ -708,6 +708,7 @@ void replica_dump(Replica *r);
47c2e9
 void replica_set_enabled(Replica *r, PRBool enable);
47c2e9
 Replica *replica_get_replica_from_dn(const Slapi_DN *dn);
47c2e9
 Replica *replica_get_replica_from_root(const char *repl_root);
47c2e9
+int replica_check_generation(Replica *r, const RUV *remote_ruv);
47c2e9
 int replica_update_ruv(Replica *replica, const CSN *csn, const char *replica_purl);
47c2e9
 Replica *replica_get_replica_for_op(Slapi_PBlock *pb);
47c2e9
 /* the functions below manipulate replica hash */
47c2e9
diff --git a/ldap/servers/plugins/replication/repl5_inc_protocol.c b/ldap/servers/plugins/replication/repl5_inc_protocol.c
47c2e9
index 29b1fb073..af5e5897c 100644
47c2e9
--- a/ldap/servers/plugins/replication/repl5_inc_protocol.c
47c2e9
+++ b/ldap/servers/plugins/replication/repl5_inc_protocol.c
47c2e9
@@ -2161,26 +2161,12 @@ examine_update_vector(Private_Repl_Protocol *prp, RUV *remote_ruv)
47c2e9
     } else if (NULL == remote_ruv) {
47c2e9
         return_value = EXAMINE_RUV_PRISTINE_REPLICA;
47c2e9
     } else {
47c2e9
-        char *local_gen = NULL;
47c2e9
-        char *remote_gen = ruv_get_replica_generation(remote_ruv);
47c2e9
-        Object *local_ruv_obj;
47c2e9
-        RUV *local_ruv;
47c2e9
-
47c2e9
         PR_ASSERT(NULL != prp->replica);
47c2e9
-        local_ruv_obj = replica_get_ruv(prp->replica);
47c2e9
-        if (NULL != local_ruv_obj) {
47c2e9
-            local_ruv = (RUV *)object_get_data(local_ruv_obj);
47c2e9
-            PR_ASSERT(local_ruv);
47c2e9
-            local_gen = ruv_get_replica_generation(local_ruv);
47c2e9
-            object_release(local_ruv_obj);
47c2e9
-        }
47c2e9
-        if (NULL == remote_gen || NULL == local_gen || strcmp(remote_gen, local_gen) != 0) {
47c2e9
-            return_value = EXAMINE_RUV_GENERATION_MISMATCH;
47c2e9
-        } else {
47c2e9
+        if (replica_check_generation(prp->replica, remote_ruv)) {
47c2e9
             return_value = EXAMINE_RUV_OK;
47c2e9
+        } else {
47c2e9
+            return_value = EXAMINE_RUV_GENERATION_MISMATCH;
47c2e9
         }
47c2e9
-        slapi_ch_free((void **)&remote_gen);
47c2e9
-        slapi_ch_free((void **)&local_gen);
47c2e9
     }
47c2e9
     return return_value;
47c2e9
 }
47c2e9
diff --git a/ldap/servers/plugins/replication/repl5_replica.c b/ldap/servers/plugins/replication/repl5_replica.c
47c2e9
index f0ea0f8ef..7e56d6557 100644
47c2e9
--- a/ldap/servers/plugins/replication/repl5_replica.c
47c2e9
+++ b/ldap/servers/plugins/replication/repl5_replica.c
47c2e9
@@ -812,6 +812,36 @@ replica_set_ruv(Replica *r, RUV *ruv)
47c2e9
     replica_unlock(r->repl_lock);
47c2e9
 }
47c2e9
 
47c2e9
+/*
47c2e9
+ * Check if replica generation is the same than the remote ruv one
47c2e9
+ */
47c2e9
+int
47c2e9
+replica_check_generation(Replica *r, const RUV *remote_ruv)
47c2e9
+{
47c2e9
+    int return_value;
47c2e9
+    char *local_gen = NULL;
47c2e9
+    char *remote_gen = ruv_get_replica_generation(remote_ruv);
47c2e9
+    Object *local_ruv_obj;
47c2e9
+    RUV *local_ruv;
47c2e9
+
47c2e9
+    PR_ASSERT(NULL != r);
47c2e9
+    local_ruv_obj = replica_get_ruv(r);
47c2e9
+    if (NULL != local_ruv_obj) {
47c2e9
+        local_ruv = (RUV *)object_get_data(local_ruv_obj);
47c2e9
+        PR_ASSERT(local_ruv);
47c2e9
+        local_gen = ruv_get_replica_generation(local_ruv);
47c2e9
+        object_release(local_ruv_obj);
47c2e9
+    }
47c2e9
+    if (NULL == remote_gen || NULL == local_gen || strcmp(remote_gen, local_gen) != 0) {
47c2e9
+        return_value = PR_FALSE;
47c2e9
+    } else {
47c2e9
+        return_value = PR_TRUE;
47c2e9
+    }
47c2e9
+    slapi_ch_free_string(&remote_gen);
47c2e9
+    slapi_ch_free_string(&local_gen);
47c2e9
+    return return_value;
47c2e9
+}
47c2e9
+
47c2e9
 /*
47c2e9
  * Update one particular CSN in an RUV. This is meant to be called
47c2e9
  * whenever (a) the server has processed a client operation and
47c2e9
@@ -1298,6 +1328,11 @@ replica_update_csngen_state_ext(Replica *r, const RUV *ruv, const CSN *extracsn)
47c2e9
 
47c2e9
     PR_ASSERT(r && ruv);
47c2e9
 
47c2e9
+    if (!replica_check_generation(r, ruv)) /* ruv has wrong generation - we are done */
47c2e9
+    {
47c2e9
+        return 0;
47c2e9
+    }
47c2e9
+
47c2e9
     rc = ruv_get_max_csn(ruv, &csn;;
47c2e9
     if (rc != RUV_SUCCESS) {
47c2e9
         return -1;
47c2e9
@@ -3713,8 +3748,8 @@ replica_update_ruv_consumer(Replica *r, RUV *supplier_ruv)
47c2e9
         replica_lock(r->repl_lock);
47c2e9
 
47c2e9
         local_ruv = (RUV *)object_get_data(r->repl_ruv);
47c2e9
-
47c2e9
-        if (is_cleaned_rid(supplier_id) || local_ruv == NULL) {
47c2e9
+        if (is_cleaned_rid(supplier_id) || local_ruv == NULL ||
47c2e9
+                !replica_check_generation(r, supplier_ruv)) {
47c2e9
             replica_unlock(r->repl_lock);
47c2e9
             return;
47c2e9
         }
47c2e9
diff --git a/src/lib389/lib389/dseldif.py b/src/lib389/lib389/dseldif.py
47c2e9
index 10baba4d7..6850c9a8a 100644
47c2e9
--- a/src/lib389/lib389/dseldif.py
47c2e9
+++ b/src/lib389/lib389/dseldif.py
47c2e9
@@ -317,6 +317,43 @@ class DSEldif(DSLint):
47c2e9
 
47c2e9
         return states
47c2e9
 
47c2e9
+    def _increaseTimeSkew(self, suffix, timeSkew):
47c2e9
+        # Increase csngen state local_offset by timeSkew
47c2e9
+        # Warning: instance must be stopped before calling this function
47c2e9
+        assert (timeSkew >= 0)
47c2e9
+        nsState = self.readNsState(suffix)[0]
47c2e9
+        self._instance.log.debug(f'_increaseTimeSkew nsState is {nsState}')
47c2e9
+        oldNsState = self.get(nsState['dn'], 'nsState', True)
47c2e9
+        self._instance.log.debug(f'oldNsState is {oldNsState}')
47c2e9
+
47c2e9
+        # Lets reencode the new nsState
47c2e9
+        from lib389.utils import print_nice_time
47c2e9
+        if pack('
47c2e9
+            end = '<'
47c2e9
+        elif pack('>h', 1) == pack('=h',1):
47c2e9
+            end = '>'
47c2e9
+        else:
47c2e9
+            raise ValueError("Unknown endian, unable to proceed")
47c2e9
+
47c2e9
+        thelen = len(oldNsState)
47c2e9
+        if thelen <= 20:
47c2e9
+            pad = 2 # padding for short H values
47c2e9
+            timefmt = 'I' # timevals are unsigned 32-bit int
47c2e9
+        else:
47c2e9
+            pad = 6 # padding for short H values
47c2e9
+            timefmt = 'Q' # timevals are unsigned 64-bit int
47c2e9
+        fmtstr = "%sH%dx3%sH%dx" % (end, pad, timefmt, pad)
47c2e9
+        newNsState = base64.b64encode(pack(fmtstr, int(nsState['rid']),
47c2e9
+           int(nsState['gen_time']), int(nsState['local_offset'])+timeSkew,
47c2e9
+           int(nsState['remote_offset']), int(nsState['seq_num'])))
47c2e9
+        newNsState = newNsState.decode('utf-8')
47c2e9
+        self._instance.log.debug(f'newNsState is {newNsState}')
47c2e9
+        # Lets replace the value.
47c2e9
+        (entry_dn_i, attr_data) = self._find_attr(nsState['dn'], 'nsState')
47c2e9
+        attr_i = next(iter(attr_data))
47c2e9
+        self._contents[entry_dn_i + attr_i] = f"nsState:: {newNsState}"
47c2e9
+        self._update()
47c2e9
+
47c2e9
 
47c2e9
 class FSChecks(DSLint):
47c2e9
     """This is for the healthcheck feature, check commonly used system config files the
47c2e9
-- 
47c2e9
2.26.2
47c2e9