|
|
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 |
|