andykimpe / rpms / 389-ds-base

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

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

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