173 lines
7.4 KiB
Diff
173 lines
7.4 KiB
Diff
From 37a56f75afac2805e1ba958eebd496e77b7079e7 Mon Sep 17 00:00:00 2001
|
|
From: Simon Pichugin <spichugi@redhat.com>
|
|
Date: Mon, 28 Jul 2025 15:35:50 -0700
|
|
Subject: [PATCH] Issue 6594 - Add test for numSubordinates replication
|
|
consistency with tombstones (#6862)
|
|
|
|
Description: Add a comprehensive test to verify that numSubordinates and
|
|
tombstoneNumSubordinates attributes are correctly replicated between
|
|
instances when tombstone entries are present.
|
|
|
|
Fixes: https://github.com/389ds/389-ds-base/issues/6594
|
|
|
|
Reviewed by: @progier389 (Thanks!)
|
|
---
|
|
.../numsubordinates_replication_test.py | 144 ++++++++++++++++++
|
|
1 file changed, 144 insertions(+)
|
|
create mode 100644 dirsrvtests/tests/suites/replication/numsubordinates_replication_test.py
|
|
|
|
diff --git a/dirsrvtests/tests/suites/replication/numsubordinates_replication_test.py b/dirsrvtests/tests/suites/replication/numsubordinates_replication_test.py
|
|
new file mode 100644
|
|
index 000000000..9ba10657d
|
|
--- /dev/null
|
|
+++ b/dirsrvtests/tests/suites/replication/numsubordinates_replication_test.py
|
|
@@ -0,0 +1,144 @@
|
|
+# --- BEGIN COPYRIGHT BLOCK ---
|
|
+# Copyright (C) 2025 Red Hat, Inc.
|
|
+# All rights reserved.
|
|
+#
|
|
+# License: GPL (version 3 or any later version).
|
|
+# See LICENSE for details.
|
|
+# --- END COPYRIGHT BLOCK ---
|
|
+
|
|
+import os
|
|
+import logging
|
|
+import pytest
|
|
+from lib389._constants import DEFAULT_SUFFIX
|
|
+from lib389.replica import ReplicationManager
|
|
+from lib389.idm.organizationalunit import OrganizationalUnits
|
|
+from lib389.idm.user import UserAccounts
|
|
+from lib389.topologies import topology_i2 as topo_i2
|
|
+
|
|
+
|
|
+pytestmark = pytest.mark.tier1
|
|
+
|
|
+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__)
|
|
+
|
|
+
|
|
+def test_numsubordinates_tombstone_replication_mismatch(topo_i2):
|
|
+ """Test that numSubordinates values match between replicas after tombstone creation
|
|
+
|
|
+ :id: c43ecc7a-d706-42e8-9179-1ff7d0e7163a
|
|
+ :setup: Two standalone instances
|
|
+ :steps:
|
|
+ 1. Create a container (organizational unit) on the first instance
|
|
+ 2. Create a user object in that container
|
|
+ 3. Delete the user object (this creates a tombstone)
|
|
+ 4. Set up replication between the two instances
|
|
+ 5. Wait for replication to complete
|
|
+ 6. Check numSubordinates on both instances
|
|
+ 7. Check tombstoneNumSubordinates on both instances
|
|
+ 8. Verify that numSubordinates values match on both instances
|
|
+ :expectedresults:
|
|
+ 1. Container should be created successfully
|
|
+ 2. User object should be created successfully
|
|
+ 3. User object should be deleted successfully
|
|
+ 4. Replication should be set up successfully
|
|
+ 5. Replication should complete successfully
|
|
+ 6. numSubordinates should be accessible on both instances
|
|
+ 7. tombstoneNumSubordinates should be accessible on both instances
|
|
+ 8. numSubordinates values should match on both instances
|
|
+ """
|
|
+
|
|
+ instance1 = topo_i2.ins["standalone1"]
|
|
+ instance2 = topo_i2.ins["standalone2"]
|
|
+
|
|
+ log.info("Create a container (organizational unit) on the first instance")
|
|
+ ous1 = OrganizationalUnits(instance1, DEFAULT_SUFFIX)
|
|
+ container = ous1.create(properties={
|
|
+ 'ou': 'test_container',
|
|
+ 'description': 'Test container for numSubordinates replication test'
|
|
+ })
|
|
+ container_rdn = container.rdn
|
|
+ log.info(f"Created container: {container_rdn}")
|
|
+
|
|
+ log.info("Create a user object in that container")
|
|
+ users1 = UserAccounts(instance1, DEFAULT_SUFFIX, rdn=f"ou={container_rdn}")
|
|
+ test_user = users1.create_test_user(uid=1001)
|
|
+ log.info(f"Created user: {test_user.dn}")
|
|
+
|
|
+ log.info("Checking initial numSubordinates on container")
|
|
+ container_obj1 = OrganizationalUnits(instance1, DEFAULT_SUFFIX).get(container_rdn)
|
|
+ initial_numsubordinates = container_obj1.get_attr_val_int('numSubordinates')
|
|
+ log.info(f"Initial numSubordinates: {initial_numsubordinates}")
|
|
+ assert initial_numsubordinates == 1
|
|
+
|
|
+ log.info("Delete the user object (this creates a tombstone)")
|
|
+ test_user.delete()
|
|
+
|
|
+ log.info("Checking numSubordinates after deletion")
|
|
+ after_delete_numsubordinates = container_obj1.get_attr_val_int('numSubordinates')
|
|
+ log.info(f"numSubordinates after deletion: {after_delete_numsubordinates}")
|
|
+
|
|
+ log.info("Checking tombstoneNumSubordinates after deletion")
|
|
+ try:
|
|
+ tombstone_numsubordinates = container_obj1.get_attr_val_int('tombstoneNumSubordinates')
|
|
+ log.info(f"tombstoneNumSubordinates: {tombstone_numsubordinates}")
|
|
+ except Exception as e:
|
|
+ log.info(f"tombstoneNumSubordinates not found or error: {e}")
|
|
+ tombstone_numsubordinates = 0
|
|
+
|
|
+ log.info("Set up replication between the two instances")
|
|
+ repl = ReplicationManager(DEFAULT_SUFFIX)
|
|
+ repl.create_first_supplier(instance1)
|
|
+ repl.join_supplier(instance1, instance2)
|
|
+
|
|
+ log.info("Wait for replication to complete")
|
|
+ repl.wait_for_replication(instance1, instance2)
|
|
+
|
|
+ log.info("Check numSubordinates on both instances")
|
|
+ container_obj1 = OrganizationalUnits(instance1, DEFAULT_SUFFIX).get(container_rdn)
|
|
+ numsubordinates_instance1 = container_obj1.get_attr_val_int('numSubordinates')
|
|
+ log.info(f"numSubordinates on instance1: {numsubordinates_instance1}")
|
|
+
|
|
+ container_obj2 = OrganizationalUnits(instance2, DEFAULT_SUFFIX).get(container_rdn)
|
|
+ numsubordinates_instance2 = container_obj2.get_attr_val_int('numSubordinates')
|
|
+ log.info(f"numSubordinates on instance2: {numsubordinates_instance2}")
|
|
+
|
|
+ log.info("Check tombstoneNumSubordinates on both instances")
|
|
+ try:
|
|
+ tombstone_numsubordinates_instance1 = container_obj1.get_attr_val_int('tombstoneNumSubordinates')
|
|
+ log.info(f"tombstoneNumSubordinates on instance1: {tombstone_numsubordinates_instance1}")
|
|
+ except Exception as e:
|
|
+ log.info(f"tombstoneNumSubordinates not found on instance1: {e}")
|
|
+ tombstone_numsubordinates_instance1 = 0
|
|
+
|
|
+ try:
|
|
+ tombstone_numsubordinates_instance2 = container_obj2.get_attr_val_int('tombstoneNumSubordinates')
|
|
+ log.info(f"tombstoneNumSubordinates on instance2: {tombstone_numsubordinates_instance2}")
|
|
+ except Exception as e:
|
|
+ log.info(f"tombstoneNumSubordinates not found on instance2: {e}")
|
|
+ tombstone_numsubordinates_instance2 = 0
|
|
+
|
|
+ log.info("Verify that numSubordinates values match on both instances")
|
|
+ log.info(f"Comparison: instance1 numSubordinates={numsubordinates_instance1}, "
|
|
+ f"instance2 numSubordinates={numsubordinates_instance2}")
|
|
+ log.info(f"Comparison: instance1 tombstoneNumSubordinates={tombstone_numsubordinates_instance1}, "
|
|
+ f"instance2 tombstoneNumSubordinates={tombstone_numsubordinates_instance2}")
|
|
+
|
|
+ assert numsubordinates_instance1 == numsubordinates_instance2, (
|
|
+ f"numSubordinates mismatch: instance1 has {numsubordinates_instance1}, "
|
|
+ f"instance2 has {numsubordinates_instance2}. "
|
|
+ )
|
|
+ assert tombstone_numsubordinates_instance1 == tombstone_numsubordinates_instance2, (
|
|
+ f"tombstoneNumSubordinates mismatch: instance1 has {tombstone_numsubordinates_instance1}, "
|
|
+ f"instance2 has {tombstone_numsubordinates_instance2}. "
|
|
+ )
|
|
+
|
|
+
|
|
+if __name__ == '__main__':
|
|
+ # Run isolated
|
|
+ # -s for DEBUG mode
|
|
+ CURRENT_FILE = os.path.realpath(__file__)
|
|
+ pytest.main("-s %s" % CURRENT_FILE)
|
|
\ No newline at end of file
|
|
--
|
|
2.49.0
|
|
|