From 3827bc9bd26469f5080ad04cf0d223fd3c4f213a Mon Sep 17 00:00:00 2001
From: Thierry Bordaz <tbordaz@redhat.com>
Date: Wed, 10 Jan 2018 15:42:37 +0100
Subject: [PATCH] Ticket 49463: testcase

---
 dirsrvtests/tests/tickets/ticket49463_test.py | 286 ++++++++++++++++++++++++++
 1 file changed, 286 insertions(+)
 create mode 100644 dirsrvtests/tests/tickets/ticket49463_test.py

diff --git a/dirsrvtests/tests/tickets/ticket49463_test.py b/dirsrvtests/tests/tickets/ticket49463_test.py
new file mode 100644
index 000000000..d6242dfac
--- /dev/null
+++ b/dirsrvtests/tests/tickets/ticket49463_test.py
@@ -0,0 +1,286 @@
+import time
+import ldap
+import logging
+import pytest
+import os
+import re
+from lib389._constants import *
+from lib389.config import Config
+from lib389 import DirSrv, Entry
+from lib389.topologies import topology_m4 as topo
+from lib389.replica import Replicas
+
+DEBUGGING = os.getenv("DEBUGGING", default=False)
+if DEBUGGING:
+    logging.getLogger(__name__).setLevel(logging.DEBUG)
+else:
+    logging.getLogger(__name__).setLevel(logging.INFO)
+log = logging.getLogger(__name__)
+
+USER_CN="user"
+
+def add_user(server, no, desc='dummy', sleep=True):
+    cn = '%s%d' % (USER_CN, no)
+    dn = 'cn=%s,ou=people,%s' % (cn, SUFFIX)
+    log.fatal('Adding user (%s): ' % dn)
+    server.add_s(Entry((dn, {'objectclass': ['top', 'person', 'inetuser', 'userSecurityInformation'],
+                             'sn': ['_%s' % cn],
+                             'description': [desc]})))
+    time.sleep(1)
+
+def check_user(server, no, timeout=10):
+    
+    cn = '%s%d' % (USER_CN, no)
+    dn = 'cn=%s,ou=people,%s' % (cn, SUFFIX)
+    found = False
+    cpt = 0
+    while cpt < timeout:
+        try:
+            server.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)")
+            found = True
+            break
+        except ldap.NO_SUCH_OBJECT:
+            time.sleep(1)
+            cpt += 1
+    return found
+
+def pattern_errorlog(server, log_pattern):
+    file_obj = open(server.errlog, "r")
+
+    found = None
+    # Use a while true iteration because 'for line in file: hit a
+    while True:
+        line = file_obj.readline()
+        found = log_pattern.search(line)
+        if ((line == '') or (found)):
+            break
+
+    return found
+
+def fractional_server_to_replica(server, replica):
+    server_to_replica = server.agreement.list(suffix=SUFFIX, consumer_host=replica.host, consumer_port=replica.port)
+    server.modify_s(server_to_replica[0].dn,
+                              [(ldap.MOD_REPLACE,
+                                'nsDS5ReplicatedAttributeListTotal',
+                                '(objectclass=*) $ EXCLUDE telephoneNumber'),
+                               (ldap.MOD_REPLACE,
+                                'nsDS5ReplicatedAttributeList',
+                                '(objectclass=*) $ EXCLUDE telephoneNumber'),
+                                (ldap.MOD_REPLACE, 'nsds5ReplicaStripAttrs',
+                                'modifiersname modifytimestamp')])
+
+
+def do_skipped_update(server, no):
+    cn = '%s%d' % (USER_CN, no)
+    dn = 'cn=%s,ou=people,%s' % (cn, SUFFIX)
+    for i in range(110):
+        server.modify_s(dn, [(ldap.MOD_REPLACE, 'telephoneNumber', str(i))])
+        
+def count_pattern_accesslog(server, log_pattern):
+    file_obj = open(server.accesslog, "r")
+
+    count = 0
+    # Use a while true iteration because 'for line in file: hit a
+    while True:
+        line = file_obj.readline()
+        if (log_pattern.search(line)):
+            count = count + 1
+        if ((line == '')):
+            break
+    file_obj.close()
+
+    return count
+
+def test_ticket_49463(topo):
+    """Specify a test case purpose or name here
+
+    :id: d1aa2e8b-e6ab-4fc6-9c63-c6f622544f2d
+    :setup: Fill in set up configuration here
+    :steps:
+        1. Enable fractional replication
+        2. Enable replication logging
+        3. Check that replication is working fine
+        4. Generate skipped updates to create keep alive entries
+        5. Remove M3 from the topology
+        6. issue cleanAllRuv FORCE that will run on M1 then propagated M2 and M4
+        7. Check that Number DEL keep alive '3' is <= 1
+        8. Check M1 is the originator of cleanAllRuv and M2/M4 the propagated ones
+        9. Check replication M1,M2 and M4 can recover
+        10. Remove M4 from the topology
+        11. Issue cleanAllRuv not force  while M2 is stopped (that hangs the cleanAllRuv)
+        12. Check that nsds5ReplicaCleanRUV is correctly encoded on M1 (last value: 1)
+        13. Check that nsds5ReplicaCleanRUV encoding survives M1 restart
+        14. Check that nsds5ReplicaCleanRUV encoding is valid on M2 (last value: 0)
+        15. Check that (for M4 cleanAllRUV) M1 is Originator and M2 propagation
+    :expectedresults:
+        1. No report of failure when the RUV is updated
+    """
+    
+    # Step 1 - Configure fractional (skip telephonenumber) replication
+    M1 = topo.ms["master1"]
+    M2 = topo.ms["master2"]
+    M3 = topo.ms["master3"]
+    M4 = topo.ms["master4"]
+    fractional_server_to_replica(M1, M2)
+    fractional_server_to_replica(M1, M3)
+    fractional_server_to_replica(M1, M4)
+    
+    fractional_server_to_replica(M2, M1)
+    fractional_server_to_replica(M2, M3)
+    fractional_server_to_replica(M2, M4)
+    
+    fractional_server_to_replica(M3, M1)
+    fractional_server_to_replica(M3, M2)
+    fractional_server_to_replica(M3, M4)
+    
+    fractional_server_to_replica(M4, M1)
+    fractional_server_to_replica(M4, M2)
+    fractional_server_to_replica(M4, M3)
+    for i in (M1, M2, M3, M4):
+        i.restart()
+    
+
+    # Step 2 - enable internal op logging and replication debug
+    for i in (M1, M2, M3, M4):
+        i.config.loglevel(vals=[256 + 4], service='access')
+        i.config.loglevel(vals=[LOG_REPLICA, LOG_DEFAULT], service='error')
+    
+    # Step 3 - Check that replication is working fine
+    add_user(M1, 11, desc="add to M1")
+    add_user(M2, 21, desc="add to M2")
+    add_user(M3, 31, desc="add to M3")
+    add_user(M4, 41, desc="add to M4")
+    
+    for i in (M1, M2, M3, M4):
+            assert check_user(i, 11)
+            assert check_user(i, 21)
+            assert check_user(i, 31)
+            assert check_user(i, 41)
+    
+        
+    # Step 4 - Generate skipped updates to create keep alive entries
+    for i in (M1, M2, M3, M4):
+        do_skipped_update(i, 11)
+        
+    time.sleep(10)
+    
+    # Step 5 - Remove M3 from the topology
+    M3.stop()
+    M1.agreement.delete(suffix=SUFFIX, consumer_host=M3.host, consumer_port=M3.port)
+    M2.agreement.delete(suffix=SUFFIX, consumer_host=M3.host, consumer_port=M3.port)
+    M4.agreement.delete(suffix=SUFFIX, consumer_host=M3.host, consumer_port=M3.port)
+    
+    time.sleep(10)
+    
+    # Step 6 - Then issue cleanAllRuv FORCE that will run on M1, M2 and M4
+    M1.tasks.cleanAllRUV(suffix=SUFFIX, replicaid='3',
+                        force=True, args={TASK_WAIT: True})
+
+    # Step 7 - Count the number of received DEL of the keep alive 3
+    time.sleep(20)
+    for i in (M1, M2, M4):
+        i.restart()
+    time.sleep(20)
+    regex = re.compile(".*DEL dn=.cn=repl keep alive 3.*")
+    for i in (M1, M2, M4):
+        count = count_pattern_accesslog(M1, regex)
+        log.debug("count on %s = %d" % (i, count))
+    
+        # check that DEL is replicated once (If DEL is kept in the fix)
+        # check that DEL is is not replicated (If DEL is finally no long done in the fix)
+        assert ((count == 1) or (count == 0))
+    
+    # Step 8 - Check that M1 is Originator of cleanAllRuv and M2, M4 propagation
+    regex = re.compile(".*Original task deletes Keep alive entry .3.*")
+    assert pattern_errorlog(M1, regex)
+    
+    regex = re.compile(".*Propagated task does not delete Keep alive entry .3.*")
+    assert pattern_errorlog(M2, regex)
+    assert pattern_errorlog(M4, regex)
+    
+    # Step 9 - Check replication M1,M2 and M4 can recover
+    add_user(M1, 12, desc="add to M1")
+    add_user(M2, 22, desc="add to M2")
+    for i in (M1, M2, M4):
+            assert check_user(i, 12)
+            assert check_user(i, 22)
+    
+    # Step 10 - Remove M4 from the topology
+    M4.stop()
+    M1.agreement.delete(suffix=SUFFIX, consumer_host=M4.host, consumer_port=M4.port)
+    M2.agreement.delete(suffix=SUFFIX, consumer_host=M4.host, consumer_port=M4.port)
+    
+    # Step 11 - Issue cleanAllRuv not force  while M2 is stopped (that hangs the cleanAllRuv)
+    M2.stop()
+    time.sleep(5)
+    M1.tasks.cleanAllRUV(suffix=SUFFIX, replicaid='4',
+                        force=False, args={TASK_WAIT: False})
+    time.sleep(5)
+    
+    # Step 12
+    # CleanAllRuv is hanging waiting for M2 to restart
+    # Check that nsds5ReplicaCleanRUV is correctly encoded on M1
+    replica = Replicas(M1)
+    entry = M1.getEntry(replica.get_dn(SUFFIX), ldap.SCOPE_BASE, "(objectclass=*)", ['nsds5ReplicaCleanRUV'])
+    assert entry.hasAttr('nsds5ReplicaCleanRUV')
+
+    log.info("M1: nsds5ReplicaCleanRUV=%s" % entry.getValue('nsds5ReplicaCleanRUV'))
+    regex = re.compile("^4:.*:no:1$")
+    assert regex.match(entry.getValue('nsds5ReplicaCleanRUV'))
+    
+    # Step 13
+    # Check that it encoding survives restart
+    M1.restart()
+    entry = M1.getEntry(replica.get_dn(SUFFIX), ldap.SCOPE_BASE, "(objectclass=*)", ['nsds5ReplicaCleanRUV'])
+    assert entry.hasAttr('nsds5ReplicaCleanRUV')
+    assert regex.match(entry.getValue('nsds5ReplicaCleanRUV'))
+    
+    # Step 14 - Check that nsds5ReplicaCleanRUV encoding is valid on M2
+    M1.stop()
+    replica = Replicas(M2)
+    M2.start()
+    M1.start()
+    attempt = 0
+    while attempt <= 20:
+        entry = M2.getEntry(replica.get_dn(SUFFIX), ldap.SCOPE_BASE, "(objectclass=*)", ['nsds5ReplicaCleanRUV'])
+        if entry.hasAttr('nsds5ReplicaCleanRUV'):
+            break;
+        attempt = attempt + 1
+        time.sleep(1)
+        
+    if entry.hasAttr('nsds5ReplicaCleanRUV'):
+        log.info("M2: nsds5ReplicaCleanRUV=%s" % entry.getValue('nsds5ReplicaCleanRUV'))
+        regex = re.compile("^4:.*:no:0$")
+        assert regex.match(entry.getValue('nsds5ReplicaCleanRUV'))
+    
+    
+    # time to run cleanAllRuv
+    time.sleep(30)
+    
+    # Step 15 - Check that M1 is Originator of cleanAllRuv and M2 propagation
+    regex = re.compile(".*Original task deletes Keep alive entry .4.*")
+    assert pattern_errorlog(M1, regex)
+    
+    regex = re.compile(".*Propagated task does not delete Keep alive entry .4.*")
+    assert pattern_errorlog(M2, regex)
+    
+
+    
+    # If you need any test suite initialization,
+    # please, write additional fixture for that (including finalizer).
+    # Topology for suites are predefined in lib389/topologies.py.
+
+    # If you need host, port or any other data about instance,
+    # Please, use the instance object attributes for that (for example, topo.ms["master1"].serverid)
+
+    if DEBUGGING:
+        # Add debugging steps(if any)...
+        pass
+
+
+if __name__ == '__main__':
+    # Run isolated
+    # -s for DEBUG mode
+    CURRENT_FILE = os.path.realpath(__file__)
+    pytest.main("-s %s" % CURRENT_FILE)
+
-- 
2.13.6

