Blame SOURCES/0081-Ticket-49671-Readonly-replicas-should-not-write-inte.patch

b663b9
From 279489884f56cfc97d1ad9afdf92da3ad3b05b70 Mon Sep 17 00:00:00 2001
b663b9
From: Mark Reynolds <mreynolds@redhat.com>
b663b9
Date: Fri, 11 May 2018 10:53:06 -0400
b663b9
Subject: [PATCH] Ticket 49671 - Readonly replicas should not write internal
b663b9
 ops to changelog
b663b9
b663b9
Bug Description:  When a hub receives an update that triggers the memberOf
b663b9
                  plugin, but that interal operation has no csn and that
b663b9
                  causes the update to the changelog to fail and break
b663b9
                  replication.
b663b9
b663b9
Fix Description:  Do not write internal updates with no csns to the changelog
b663b9
                  on read-only replicas.
b663b9
b663b9
https://pagure.io/389-ds-base/issue/49671
b663b9
b663b9
Reviewed by: simon, tbordaz, and lkrispen (Thanks!!!)
b663b9
b663b9
(cherry picked from commit afb755bd95f1643665ea34c5a5fa2bb26bfa21b9)
b663b9
---
b663b9
 .../tests/suites/replication/cascading_test.py     | 150 +++++++++++++++++++++
b663b9
 ldap/servers/plugins/replication/repl5_plugins.c   |  10 ++
b663b9
 2 files changed, 160 insertions(+)
b663b9
 create mode 100644 dirsrvtests/tests/suites/replication/cascading_test.py
b663b9
b663b9
diff --git a/dirsrvtests/tests/suites/replication/cascading_test.py b/dirsrvtests/tests/suites/replication/cascading_test.py
b663b9
new file mode 100644
b663b9
index 000000000..7331f20e9
b663b9
--- /dev/null
b663b9
+++ b/dirsrvtests/tests/suites/replication/cascading_test.py
b663b9
@@ -0,0 +1,150 @@
b663b9
+# --- BEGIN COPYRIGHT BLOCK ---
b663b9
+# Copyright (C) 2018 Red Hat, Inc.
b663b9
+# All rights reserved.
b663b9
+#
b663b9
+# License: GPL (version 3 or any later version).
b663b9
+# See LICENSE for details.
b663b9
+# --- END COPYRIGHT BLOCK ---
b663b9
+#
b663b9
+import logging
b663b9
+import pytest
b663b9
+import os
b663b9
+import ldap
b663b9
+from lib389._constants import *
b663b9
+from lib389.replica import ReplicationManager
b663b9
+from lib389.plugins import MemberOfPlugin
b663b9
+from lib389.agreement import Agreements
b663b9
+from lib389.idm.user import UserAccount, TEST_USER_PROPERTIES
b663b9
+from lib389.idm.group import Groups
b663b9
+from lib389.topologies import topology_m1h1c1 as topo
b663b9
+
b663b9
+DEBUGGING = os.getenv("DEBUGGING", default=False)
b663b9
+if DEBUGGING:
b663b9
+    logging.getLogger(__name__).setLevel(logging.DEBUG)
b663b9
+else:
b663b9
+    logging.getLogger(__name__).setLevel(logging.INFO)
b663b9
+log = logging.getLogger(__name__)
b663b9
+
b663b9
+BIND_DN = 'uid=tuser1,ou=People,dc=example,dc=com'
b663b9
+BIND_RDN = 'tuser1'
b663b9
+
b663b9
+
b663b9
+def config_memberof(server):
b663b9
+    """Configure memberOf plugin and configure fractional
b663b9
+    to prevent total init to send memberof
b663b9
+    """
b663b9
+
b663b9
+    memberof = MemberOfPlugin(server)
b663b9
+    memberof.enable()
b663b9
+    memberof.set_autoaddoc('nsMemberOf')
b663b9
+    server.restart()
b663b9
+    agmts = Agreements(server)
b663b9
+    for agmt in agmts.list():
b663b9
+        log.info('update %s to add nsDS5ReplicatedAttributeListTotal' % agmt.dn)
b663b9
+        agmt.replace_many(('nsDS5ReplicatedAttributeListTotal', '(objectclass=*) $ EXCLUDE '),
b663b9
+                          ('nsDS5ReplicatedAttributeList', '(objectclass=*) $ EXCLUDE memberOf'))
b663b9
+
b663b9
+
b663b9
+def test_basic_with_hub(topo):
b663b9
+    """Check that basic operations work in cascading replication, this includes
b663b9
+    testing plugins that perform internal operatons, and replicated password
b663b9
+    policy state attributes.
b663b9
+
b663b9
+    :id: 4ac85552-45bc-477b-89a4-226dfff8c6cc
b663b9
+    :setup: 1 master, 1 hub, 1 consumer
b663b9
+    :steps:
b663b9
+        1. Enable memberOf plugin and set password account lockout settings
b663b9
+        2. Restart the instance
b663b9
+        3. Add a user
b663b9
+        4. Add a group
b663b9
+        5. Test that the replication works
b663b9
+        6. Add the user as a member to the group
b663b9
+        7. Test that the replication works
b663b9
+        8. Issue bad binds to update passwordRetryCount
b663b9
+        9. Test that replicaton works
b663b9
+        10. Check that passwordRetyCount was replicated
b663b9
+    :expectedresults:
b663b9
+        1. Should be a success
b663b9
+        2. Should be a success
b663b9
+        3. Should be a success
b663b9
+        4. Should be a success
b663b9
+        5. Should be a success
b663b9
+        6. Should be a success
b663b9
+        7. Should be a success
b663b9
+        8. Should be a success
b663b9
+        9. Should be a success
b663b9
+        10. Should be a success
b663b9
+    """
b663b9
+
b663b9
+    repl_manager = ReplicationManager(DEFAULT_SUFFIX)
b663b9
+    master = topo.ms["master1"]
b663b9
+    consumer = topo.cs["consumer1"]
b663b9
+    hub = topo.hs["hub1"]
b663b9
+
b663b9
+    for inst in topo:
b663b9
+        config_memberof(inst)
b663b9
+        inst.config.set('passwordlockout', 'on')
b663b9
+        inst.config.set('passwordlockoutduration', '60')
b663b9
+        inst.config.set('passwordmaxfailure', '3')
b663b9
+        inst.config.set('passwordIsGlobalPolicy', 'on')
b663b9
+
b663b9
+    # Create user
b663b9
+    user1 = UserAccount(master, BIND_DN)
b663b9
+    user_props = TEST_USER_PROPERTIES.copy()
b663b9
+    user_props.update({'sn': BIND_RDN,
b663b9
+                       'cn': BIND_RDN,
b663b9
+                       'uid': BIND_RDN,
b663b9
+                       'inetUserStatus': '1',
b663b9
+                       'objectclass': 'extensibleObject',
b663b9
+                       'userpassword': PASSWORD})
b663b9
+    user1.create(properties=user_props, basedn=SUFFIX)
b663b9
+
b663b9
+    # Create group
b663b9
+    groups = Groups(master, DEFAULT_SUFFIX)
b663b9
+    group = groups.create(properties={'cn': 'group'})
b663b9
+
b663b9
+    # Test replication
b663b9
+    repl_manager.test_replication(master, consumer)
b663b9
+
b663b9
+    # Trigger memberOf plugin by adding user to group
b663b9
+    group.replace('member', user1.dn)
b663b9
+
b663b9
+    # Test replication once more
b663b9
+    repl_manager.test_replication(master, consumer)
b663b9
+
b663b9
+    # Issue bad password to update passwordRetryCount
b663b9
+    try:
b663b9
+        master.simple_bind_s(user1.dn, "badpassword")
b663b9
+    except:
b663b9
+        pass
b663b9
+
b663b9
+    # Test replication one last time
b663b9
+    master.simple_bind_s(DN_DM, PASSWORD)
b663b9
+    repl_manager.test_replication(master, consumer)
b663b9
+
b663b9
+    # Finally check if passwordRetyCount was replicated to the hub and consumer
b663b9
+    user1 = UserAccount(hub, BIND_DN)
b663b9
+    count = user1.get_attr_val_int('passwordRetryCount')
b663b9
+    if count is None:
b663b9
+        log.fatal('PasswordRetyCount was not replicated to hub')
b663b9
+        assert False
b663b9
+    if int(count) != 1:
b663b9
+        log.fatal('PasswordRetyCount has unexpected value: {}'.format(count))
b663b9
+        assert False
b663b9
+
b663b9
+    user1 = UserAccount(consumer, BIND_DN)
b663b9
+    count = user1.get_attr_val_int('passwordRetryCount')
b663b9
+    if count is None:
b663b9
+        log.fatal('PasswordRetyCount was not replicated to consumer')
b663b9
+        assert False
b663b9
+    if int(count) != 1:
b663b9
+        log.fatal('PasswordRetyCount has unexpected value: {}'.format(count))
b663b9
+        assert False
b663b9
+
b663b9
+
b663b9
+if __name__ == '__main__':
b663b9
+    # Run isolated
b663b9
+    # -s for DEBUG mode
b663b9
+    CURRENT_FILE = os.path.realpath(__file__)
b663b9
+    pytest.main(["-s", CURRENT_FILE])
b663b9
+
b663b9
diff --git a/ldap/servers/plugins/replication/repl5_plugins.c b/ldap/servers/plugins/replication/repl5_plugins.c
b663b9
index 0aee8829a..324e38263 100644
b663b9
--- a/ldap/servers/plugins/replication/repl5_plugins.c
b663b9
+++ b/ldap/servers/plugins/replication/repl5_plugins.c
b663b9
@@ -1059,6 +1059,16 @@ write_changelog_and_ruv(Slapi_PBlock *pb)
b663b9
             goto common_return;
b663b9
         }
b663b9
 
b663b9
+        /* Skip internal operations with no op csn if this is a read-only replica */
b663b9
+        if (op_params->csn == NULL &&
b663b9
+            operation_is_flag_set(op, OP_FLAG_INTERNAL) &&
b663b9
+            replica_get_type(r) == REPLICA_TYPE_READONLY)
b663b9
+        {
b663b9
+            slapi_log_err(SLAPI_LOG_REPL, "write_changelog_and_ruv",
b663b9
+                          "Skipping internal operation on read-only replica\n");
b663b9
+            goto common_return;
b663b9
+        }
b663b9
+
b663b9
         /* we might have stripped all the mods - in that case we do not
b663b9
            log the operation */
b663b9
         if (op_params->operation_type != SLAPI_OPERATION_MODIFY ||
b663b9
-- 
b663b9
2.13.6
b663b9