From 7d9e69b808bd9092cd779c1c04237b8844b0bfa6 Mon Sep 17 00:00:00 2001
From: Sankar Ramalingam <sramling@redhat.com>
Date: Wed, 7 Jun 2017 22:54:54 +0530
Subject: [PATCH] Ticket #48944 -  on a read only replica invalid state info can accumulate

Description: Added test cases to check if read only replica accumulates
invalid state info in DS error logs.

https://pagure.io/389-ds-base/issue/48944

Reviewed by: ?
---
 dirsrvtests/tests/tickets/ticket48944_test.py | 204 ++++++++++++++++++++++++++
 1 file changed, 204 insertions(+)
 create mode 100644 dirsrvtests/tests/tickets/ticket48944_test.py

diff --git a/dirsrvtests/tests/tickets/ticket48944_test.py b/dirsrvtests/tests/tickets/ticket48944_test.py
new file mode 100644
index 0000000..51ab104
--- /dev/null
+++ b/dirsrvtests/tests/tickets/ticket48944_test.py
@@ -0,0 +1,204 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2017 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+#
+import pytest
+from lib389.tasks import *
+from lib389.utils import *
+from lib389.topologies import topology_m2c2 as topo_m2c2
+
+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__)
+
+ACCPOL_DN = "cn={},{}".format(PLUGIN_ACCT_POLICY, DN_PLUGIN)
+ACCP_CONF = "{},{}".format(DN_CONFIG, ACCPOL_DN)
+USER_PW = 'Secret123'
+
+
+def test_ticket48944(topo_m2c2):
+    """On a read only replica invalid state info can accumulate
+
+    :ID: 833be131-f3bf-493e-97c6-3121438a07b1
+    :feature: Account Policy Plugin
+    :setup: Two master and two consumer setup
+    :steps: 1. Configure Account policy plugin with alwaysrecordlogin set to yes
+            2. Check if entries are synced across masters and consumers
+            3. Stop all masters and consumers
+            4. Start master1 and bind as user1 to create lastLoginTime attribute
+            5. Start master2 and wait for the sync of lastLoginTime attribute
+            6. Stop master1 and bind as user1 from master2
+            7. Check if lastLoginTime attribute is updated and greater than master1
+            8. Stop master2, start consumer1, consumer2 and then master2
+            9. Check if lastLoginTime attribute is updated on both consumers
+            10. Bind as user1 to both consumers and check the value is updated
+            11. Check if lastLoginTime attribute is not updated from consumers
+            12. Start master1 and make sure the lastLoginTime attribute is not updated on consumers
+            13. Bind as user1 from master1 and check if all masters and consumers have the same value
+            14. Check error logs of consumers for "deletedattribute;deleted" message
+    :expectedresults: No accumulation of replica invalid state info on consumers
+    """
+
+    log.info("Ticket 48944 - On a read only replica invalid state info can accumulate")
+    user_name = 'newbzusr'
+    inst_list = ['master1', 'master2']
+    log.info('Configure Account policy plugin for master1 and master2')
+    for inst in inst_list:
+        try:
+            topo_m2c2.ms[inst].plugins.enable(name=PLUGIN_ACCT_POLICY)
+            topo_m2c2.ms[inst].modify_s(ACCPOL_DN, [(ldap.MOD_REPLACE, 'nsslapd-pluginarg0', ACCP_CONF)])
+            topo_m2c2.ms[inst].modify_s(ACCP_CONF, [(ldap.MOD_REPLACE, 'alwaysrecordlogin', 'yes')])
+            topo_m2c2.ms[inst].modify_s(ACCP_CONF, [(ldap.MOD_REPLACE, 'stateattrname', 'lastLoginTime')])
+            topo_m2c2.ms[inst].modify_s(ACCP_CONF, [(ldap.MOD_REPLACE, 'altstateattrname', 'createTimestamp')])
+            topo_m2c2.ms[inst].modify_s(ACCP_CONF, [(ldap.MOD_REPLACE, 'specattrname', 'acctPolicySubentry')])
+            topo_m2c2.ms[inst].modify_s(ACCP_CONF, [(ldap.MOD_REPLACE, 'limitattrname', 'accountInactivityLimit')])
+            topo_m2c2.ms[inst].modify_s(ACCP_CONF, [(ldap.MOD_REPLACE, 'accountInactivityLimit', '3600')])
+        except ldap.LDAPError as e:
+            log.error('Failed to configure {} plugin for inst-{}'.format(PLUGIN_ACCT_POLICY, inst))
+        topo_m2c2.ms[inst].restart(timeout=10)
+
+    inst_list = ['consumer1', 'consumer2']
+    log.info('Configure Account policy plugin for consumer1 and consumer2')
+    for inst in inst_list:
+        try:
+            topo_m2c2.cs[inst].plugins.enable(name=PLUGIN_ACCT_POLICY)
+            topo_m2c2.cs[inst].modify_s(ACCPOL_DN, [(ldap.MOD_REPLACE, 'nsslapd-pluginarg0', ACCP_CONF)])
+            topo_m2c2.cs[inst].modify_s(ACCP_CONF, [(ldap.MOD_REPLACE, 'alwaysrecordlogin', 'yes')])
+            topo_m2c2.cs[inst].modify_s(ACCP_CONF, [(ldap.MOD_REPLACE, 'stateattrname', 'lastLoginTime')])
+            topo_m2c2.cs[inst].modify_s(ACCP_CONF, [(ldap.MOD_REPLACE, 'altstateattrname', 'createTimestamp')])
+            topo_m2c2.cs[inst].modify_s(ACCP_CONF, [(ldap.MOD_REPLACE, 'specattrname', 'acctPolicySubentry')])
+            topo_m2c2.cs[inst].modify_s(ACCP_CONF, [(ldap.MOD_REPLACE, 'limitattrname', 'accountInactivityLimit')])
+            topo_m2c2.cs[inst].modify_s(ACCP_CONF, [(ldap.MOD_REPLACE, 'accountInactivityLimit', '3600')])
+        except ldap.LDAPError as e:
+            log.error('Failed to configure {} plugin for inst-{}'.format(PLUGIN_ACCT_POLICY, inst))
+        topo_m2c2.cs[inst].restart(timeout=10)
+
+    # Sleep for 10secs for the server to come up
+    log.info('Sleep for 10secs for the server to come up')
+    time.sleep(10)
+    userdn = 'uid={},ou=people,{}'.format(user_name, SUFFIX)
+    try:
+        topo_m2c2.ms['master1'].add_s(Entry((userdn, {
+            'objectclass': 'top person'.split(),
+            'objectclass': 'inetorgperson',
+            'cn': user_name,
+            'sn': user_name,
+            'userpassword': USER_PW,
+            'mail': '{}@redhat.com'.format(user_name)})))
+    except ldap.LDAPError as e:
+        log.error('Failed to add {} user: error {}'.format(userdn, e.message['desc']))
+        raise e
+
+    # Checking if entries are synced across masters and cosumers
+    log.info('Checking if entries are synced across masters and cosumers')
+    entries_m1 = topo_m2c2.ms['master1'].search_s(SUFFIX, ldap.SCOPE_SUBTREE, 'uid={}*'.format(user_name), ['uid=*'])
+    exp_entries = str(entries_m1).count('dn: uid={}*'.format(user_name))
+    entries_m2 = topo_m2c2.ms['master2'].search_s(SUFFIX, ldap.SCOPE_SUBTREE, 'uid={}*'.format(user_name), ['uid=*'])
+    act_entries = str(entries_m2).count('dn: uid={}*'.format(user_name))
+    assert act_entries == exp_entries
+    inst_list = ['consumer1', 'consumer2']
+    for inst in inst_list:
+        entries_other = topo_m2c2.cs[inst].search_s(SUFFIX, ldap.SCOPE_SUBTREE, 'uid={}*'.format(user_name), ['uid=*'])
+        act_entries = str(entries_other).count('dn: uid={}*'.format(user_name))
+        assert act_entries == exp_entries
+
+    topo_m2c2.ms['master2'].stop(timeout=10)
+    topo_m2c2.ms['master1'].stop(timeout=10)
+    topo_m2c2.cs['consumer1'].stop(timeout=10)
+    topo_m2c2.cs['consumer2'].stop(timeout=10)
+
+    # Create lastLoginTime attribute for users 
+    topo_m2c2.ms['master1'].start(timeout=10)
+    topo_m2c2.ms['master1'].simple_bind_s(userdn, USER_PW)
+    topo_m2c2.ms['master1'].simple_bind_s(DN_DM, PASSWORD)
+    m1entry = topo_m2c2.ms['master1'].search_s(userdn, ldap.SCOPE_BASE, 'objectClass=*', ['lastLoginTime'])
+    m1lastLogin = m1entry[0].lastLoginTime
+
+    # Start master2 to sync lastLoginTime attribute from master1
+    topo_m2c2.ms['master2'].start(timeout=10)
+    time.sleep(10)
+    topo_m2c2.ms['master1'].stop(timeout=10)
+    # Bind as user1 to master2 and check if lastLoginTime attribute is greater than master1
+    topo_m2c2.ms['master2'].simple_bind_s(userdn, USER_PW)
+    topo_m2c2.ms['master2'].simple_bind_s(DN_DM, PASSWORD)
+    m2entry = topo_m2c2.ms['master2'].search_s(userdn, ldap.SCOPE_BASE, 'objectClass=*', ['lastLoginTime'])
+    m2lastLogin = m2entry[0].lastLoginTime
+    assert m2lastLogin > m1lastLogin
+
+    # Start all servers except master1 
+    log.info('Start all servers except master1')
+    topo_m2c2.ms['master2'].stop(timeout=10)
+    topo_m2c2.cs['consumer1'].start(timeout=10)
+    topo_m2c2.cs['consumer2'].start(timeout=10)
+    topo_m2c2.ms['master2'].start(timeout=10)
+    time.sleep(10)
+    # Check if consumers are updated with lastLoginTime attribute value from master2
+    log.info('Check if consumers are updated with lastLoginTime attribute value from master2')
+    topo_m2c2.cs['consumer1'].simple_bind_s(DN_DM, PASSWORD)
+    n1c1entry = topo_m2c2.cs['consumer1'].search_s(userdn, ldap.SCOPE_BASE, 'objectClass=*', ['lastLoginTime'])
+    c1n1lastLogin = n1c1entry[0].lastLoginTime
+    assert c1n1lastLogin == m2lastLogin
+
+    n1c2entry = topo_m2c2.cs['consumer2'].search_s(userdn, ldap.SCOPE_BASE, 'objectClass=*', ['lastLoginTime'])
+    c2n1lastLogin = n1c2entry[0].lastLoginTime
+    assert c2n1lastLogin == m2lastLogin
+
+    # Check lastLoginTime update in consumers not synced to master2
+    log.info('Check if lastLoginTime update in consumers not synced to master2')
+    topo_m2c2.cs['consumer1'].simple_bind_s(userdn, USER_PW)
+    topo_m2c2.cs['consumer1'].simple_bind_s(DN_DM, PASSWORD)
+    n2c1entry = topo_m2c2.cs['consumer1'].search_s(userdn, ldap.SCOPE_BASE, 'objectClass=*', ['lastLoginTime'])
+    c1n2lastLogin = n2c1entry[0].lastLoginTime
+    assert c1n2lastLogin > m2lastLogin
+
+    topo_m2c2.cs['consumer2'].simple_bind_s(userdn, USER_PW)
+    topo_m2c2.cs['consumer2'].simple_bind_s(DN_DM, PASSWORD)
+    n2c2entry = topo_m2c2.cs['consumer2'].search_s(userdn, ldap.SCOPE_BASE, 'objectClass=*', ['lastLoginTime'])
+    c2n2lastLogin = n2c2entry[0].lastLoginTime
+    assert c2n2lastLogin > c1n2lastLogin
+
+    # Start master1 and check if its not updating its older lastLoginTime attribute to consumers
+    topo_m2c2.ms['master1'].start(timeout=10)
+    time.sleep(10)
+    n3c1entry = topo_m2c2.cs['consumer1'].search_s(userdn, ldap.SCOPE_BASE, 'objectClass=*', ['lastLoginTime'])
+    c1n3lastLogin = n3c1entry[0].lastLoginTime
+    assert c1n3lastLogin == c1n2lastLogin
+
+    n3c2entry = topo_m2c2.cs['consumer2'].search_s(userdn, ldap.SCOPE_BASE, 'objectClass=*', ['lastLoginTime'])
+    c2n3lastLogin = n3c2entry[0].lastLoginTime
+    assert c2n3lastLogin == c2n2lastLogin
+
+    # Check if lastLoginTime update from master2 is synced to all masters and consumers
+    log.info('Check if lastLoginTime update from master2 is synced to all masters and consumers')
+    topo_m2c2.ms['master2'].simple_bind_s(userdn, USER_PW)
+    topo_m2c2.ms['master2'].simple_bind_s(DN_DM, PASSWORD)
+    n1m2entry = topo_m2c2.ms['master1'].search_s(userdn, ldap.SCOPE_BASE, 'objectClass=*', ['lastLoginTime'])
+    m2n1lastLogin = n1m2entry[0].lastLoginTime
+    time.sleep(5)
+
+    n1m1entry = topo_m2c2.ms['master1'].search_s(userdn, ldap.SCOPE_BASE, 'objectClass=*', ['lastLoginTime'])
+    m1n1lastLogin = n1m1entry[0].lastLoginTime
+    n4c2entry = topo_m2c2.cs['consumer2'].search_s(userdn, ldap.SCOPE_BASE, 'objectClass=*', ['lastLoginTime'])
+    c2n4lastLogin = n4c2entry[0].lastLoginTime
+    n4c1entry = topo_m2c2.cs['consumer1'].search_s(userdn, ldap.SCOPE_BASE, 'objectClass=*', ['lastLoginTime'])
+    c1n4lastLogin = n4c1entry[0].lastLoginTime
+    assert m1n1lastLogin == m2n1lastLogin == c2n4lastLogin == c1n4lastLogin
+
+    # Check if no errors accumulate for replica invalid state
+    log.info('Checking error logs of consumer for replica invalid state info')
+    assert not topo_m2c2.cs['consumer2'].ds_error_log.match('.*deletedattribute;deleted.*')
+    assert not topo_m2c2.cs['consumer1'].ds_error_log.match('.*deletedattribute;deleted.*')
+
+
+if __name__ == '__main__':
+    # Run isolated
+    # -s for DEBUG mode
+    CURRENT_FILE = os.path.realpath(__file__)
+    pytest.main("-s %s" % CURRENT_FILE)
-- 
2.7.4

