The branch, master has been updated via b4a6c054ec6 selftest: Use setUpClass() to reduce "make test TESTS=large_ldap" time via cad96f59a08 lib/ldb: Avoid allocation and memcpy() for every wildcard match candidate via 4fa0242b9d3 python:netcmd: Decode return value of find_netbios() from bytes into string via bfc33b47bb4 dsdb: Avoid ERROR(ldb): uncaught exception - Deleted target CN=NTDS Settings... in join via 2d41bcce83a selftest/drs: Demonstrate ERROR(ldb): uncaught exception - Deleted target CN=NTDS Settings... in join via 5a7a28cc458 tsocket: Increase tcp_user_timeout max_loops from 7ee725f2860 idmap_hash: remember new domain sids in idmap_hash_sid_to_id()
https://git.samba.org/?p=samba.git;a=shortlog;h=master - Log ----------------------------------------------------------------- commit b4a6c054ec6acefacd22cb7230a783d20cb07c05 Author: Andrew Bartlett <abart...@samba.org> Date: Mon Mar 13 17:20:00 2023 +1300 selftest: Use setUpClass() to reduce "make test TESTS=large_ldap" time This reduces the elapsed time to 6m from 20m on my laptop. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15332 Signed-off-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Joseph Sutton <josephsut...@catalyst.net.nz> Autobuild-User(master): Andrew Bartlett <abart...@samba.org> Autobuild-Date(master): Tue Mar 14 07:16:04 UTC 2023 on atb-devel-224 commit cad96f59a08192df927fb1df4e9787c7f70991a2 Author: Andrew Bartlett <abart...@samba.org> Date: Mon Mar 13 14:25:56 2023 +1300 lib/ldb: Avoid allocation and memcpy() for every wildcard match candidate The value can be quite large, the allocation will take much longer than the actual match and is repeated per candidate record. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15331 Signed-off-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Joseph Sutton <josephsut...@catalyst.net.nz> commit 4fa0242b9d34decd8dbd813be40655a593df3db9 Author: Andreas Schneider <a...@samba.org> Date: Fri Mar 10 09:08:48 2023 +0100 python:netcmd: Decode return value of find_netbios() from bytes into string ERROR(<class 'TypeError'>): uncaught exception - replace() argument 1 must be str, not bytes File "bin/python/samba/netcmd/__init__.py", line 230, in _run return self.run(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "bin/python/samba/netcmd/ldapcmp.py", line 966, in run if b1.diff(b2): ^^^^^^^^^^^ File "bin/python/samba/netcmd/ldapcmp.py", line 790, in diff if object1 == object2: ^^^^^^^^^^^^^^^^^^ File "bin/python/samba/netcmd/ldapcmp.py", line 557, in __eq__ return self.cmp_attrs(other) ^^^^^^^^^^^^^^^^^^^^^ File "bin/python/samba/netcmd/ldapcmp.py", line 656, in cmp_attrs p = [self.fix_domain_netbios(j) for j in m] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "bin/python/samba/netcmd/ldapcmp.py", line 656, in <listcomp> p = [self.fix_domain_netbios(j) for j in m] ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "bin/python/samba/netcmd/ldapcmp.py", line 542, in fix_domain_netbios res = res.replace(self.con.domain_netbios.lower(), self.con.domain_netbios.upper()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ BUGS: https://bugzilla.samba.org/show_bug.cgi?id=15330 Signed-off-by: Andreas Schneider <a...@samba.org> Reviewed-by: Andrew Bartlett <abart...@samba.org> commit bfc33b47bb428233e100f75e7a725ac52179f823 Author: Andrew Bartlett <abart...@samba.org> Date: Thu Mar 9 20:25:06 2023 +1300 dsdb: Avoid ERROR(ldb): uncaught exception - Deleted target CN=NTDS Settings... in join "samba-tool domain join" uses the replication API in a strange way, perhaps no longer required, except that we often still have folks upgrading from very old Samba versions. When deferring the writing out to the DB of link replication to the very end, there is a greater opportunity for the deletion of an object to have been sent with the other objects, and have the link applied later. This tells the repl_meta_data code to behave as if GET_TGT had been sent at the time the link was returned, allowing a link to a deleted object to be silently discarded. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15329 Signed-off-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Joseph Sutton <josephsut...@catalyst.net.nz> commit 2d41bcce83a976b85636c92d6fc38c63fdde5431 Author: Andrew Bartlett <abart...@samba.org> Date: Thu Mar 9 17:02:35 2023 +1300 selftest/drs: Demonstrate ERROR(ldb): uncaught exception - Deleted target CN=NTDS Settings... in join "samba-tool domain join" uses the replication API in a strange way, perhaps no longer required, except that we often still have folks upgrading from very old Samba versions. By deferring the writing out to the DB of link replication to the very end, we have a better chance that all the objects required are present, however the situation may have changed during the cycle, and a link could still be sent, pointing to a deleted object. We currently fail in this situation. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15329 Signed-off-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Joseph Sutton <josephsut...@catalyst.net.nz> commit 5a7a28cc45870949fc11d30586a06c309aa517dc Author: Andrew Bartlett <abart...@samba.org> Date: Thu Mar 9 10:06:26 2023 +1300 tsocket: Increase tcp_user_timeout max_loops Often, on rackspace GitLab CI runners, we get: UNEXPECTED(failure): samba.unittests.tsocket_tstream.test_tstream_more_tcp_user_timeout_spin(none) REASON: Exception: Exception: 0xf == 0xf ../../lib/tsocket/tests/test_tstream.c:405: error: Failure! This allows us more spins before we fail the test. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15328 Signed-off-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Joseph Sutton <josephsut...@catalyst.net.nz> ----------------------------------------------------------------------- Summary of changes: lib/ldb/common/ldb_match.c | 60 +++++++++-- lib/tsocket/tests/test_tstream.c | 2 +- python/samba/join.py | 19 ++++ python/samba/netcmd/ldapcmp.py | 2 +- source4/dsdb/pydsdb.c | 2 + source4/dsdb/samdb/ldb_modules/repl_meta_data.c | 13 ++- source4/dsdb/samdb/samdb.h | 2 + source4/dsdb/tests/python/large_ldap.py | 69 ++++++------ source4/torture/drs/python/ridalloc_exop.py | 135 ++++++++++++++++++++++++ 9 files changed, 258 insertions(+), 46 deletions(-) Changeset truncated at 500 lines: diff --git a/lib/ldb/common/ldb_match.c b/lib/ldb/common/ldb_match.c index 2f4d41f3441..51376871b4c 100644 --- a/lib/ldb/common/ldb_match.c +++ b/lib/ldb/common/ldb_match.c @@ -34,6 +34,7 @@ #include "ldb_private.h" #include "dlinklist.h" +#include "ldb_handlers.h" /* check if the scope matches in a search result @@ -259,20 +260,42 @@ static int ldb_wildcard_compare(struct ldb_context *ldb, return LDB_SUCCESS; } - if (a->syntax->canonicalise_fn(ldb, ldb, &value, &val) != 0) { - return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + /* No need to just copy this value for a binary match */ + if (a->syntax->canonicalise_fn != ldb_handler_copy) { + if (a->syntax->canonicalise_fn(ldb, ldb, &value, &val) != 0) { + return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; + } + + /* + * Only set save_p if we allocate (call + * a->syntax->canonicalise_fn()), as we + * talloc_free(save_p) below to clean up + */ + save_p = val.data; + } else { + val = value; } - save_p = val.data; cnk.data = NULL; if ( ! tree->u.substring.start_with_wildcard ) { + uint8_t *cnk_to_free = NULL; chunk = tree->u.substring.chunks[c]; - if (a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) goto mismatch; + /* No need to just copy this value for a binary match */ + if (a->syntax->canonicalise_fn != ldb_handler_copy) { + if (a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) { + goto mismatch; + } + + cnk_to_free = cnk.data; + } else { + cnk = *chunk; + } /* This deals with wildcard prefix searches on binary attributes (eg objectGUID) */ if (cnk.length > val.length) { + TALLOC_FREE(cnk_to_free); goto mismatch; } /* @@ -280,32 +303,47 @@ static int ldb_wildcard_compare(struct ldb_context *ldb, * we can cope with this. */ if (cnk.length == 0) { + TALLOC_FREE(cnk_to_free); + goto mismatch; + } + + if (memcmp((char *)val.data, (char *)cnk.data, cnk.length) != 0) { + TALLOC_FREE(cnk_to_free); goto mismatch; } - if (memcmp((char *)val.data, (char *)cnk.data, cnk.length) != 0) goto mismatch; val.length -= cnk.length; val.data += cnk.length; c++; - talloc_free(cnk.data); + TALLOC_FREE(cnk_to_free); cnk.data = NULL; } while (tree->u.substring.chunks[c]) { uint8_t *p; + uint8_t *cnk_to_free = NULL; chunk = tree->u.substring.chunks[c]; - if(a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) { - goto mismatch; + /* No need to just copy this value for a binary match */ + if (a->syntax->canonicalise_fn != ldb_handler_copy) { + if (a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) { + goto mismatch; + } + + cnk_to_free = cnk.data; + } else { + cnk = *chunk; } /* * Empty strings are returned as length 0. Ensure * we can cope with this. */ if (cnk.length == 0) { + TALLOC_FREE(cnk_to_free); goto mismatch; } if (cnk.length > val.length) { + TALLOC_FREE(cnk_to_free); goto mismatch; } @@ -320,6 +358,8 @@ static int ldb_wildcard_compare(struct ldb_context *ldb, cmp = memcmp(p, cnk.data, cnk.length); + TALLOC_FREE(cnk_to_free); + if (cmp != 0) { goto mismatch; } @@ -331,15 +371,16 @@ static int ldb_wildcard_compare(struct ldb_context *ldb, p = memmem((const void *)val.data, val.length, (const void *)cnk.data, cnk.length); if (p == NULL) { + TALLOC_FREE(cnk_to_free); goto mismatch; } /* move val to the end of the match */ p += cnk.length; val.length -= (p - val.data); val.data = p; + TALLOC_FREE(cnk_to_free); } c++; - TALLOC_FREE(cnk.data); } talloc_free(save_p); @@ -349,7 +390,6 @@ static int ldb_wildcard_compare(struct ldb_context *ldb, mismatch: *matched = false; talloc_free(save_p); - talloc_free(cnk.data); return LDB_SUCCESS; } diff --git a/lib/tsocket/tests/test_tstream.c b/lib/tsocket/tests/test_tstream.c index a920e671cda..47008bb8bf8 100644 --- a/lib/tsocket/tests/test_tstream.c +++ b/lib/tsocket/tests/test_tstream.c @@ -322,7 +322,7 @@ static void test_tstream_server_spin_client_tcp_user_timeout(struct socket_pair rc = write(sp->socket_client, TEST_STRING, sizeof(TEST_STRING)); assert_return_code(rc, errno); sp->expected_errno = ETIMEDOUT; - sp->max_loops = 15; + sp->max_loops = 30; } static void test_tstream_server_spin_client_both_timer(struct tevent_context *ev, diff --git a/python/samba/join.py b/python/samba/join.py index 70b3c9729b0..6c1ab3be7b4 100644 --- a/python/samba/join.py +++ b/python/samba/join.py @@ -50,6 +50,7 @@ import tempfile from collections import OrderedDict from samba.common import get_string from samba.netcmd import CommandError +from samba import dsdb class DCJoinException(Exception): @@ -937,6 +938,10 @@ class DCJoinContext(object): """Replicate the SAM.""" ctx.logger.info("Starting replication") + + # A global transaction is started so that linked attributes + # are applied at the very end, once all partitions are + # replicated. This helps get all cross-partition links. ctx.local_samdb.transaction_start() try: source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id()) @@ -1057,7 +1062,21 @@ class DCJoinContext(object): ctx.local_samdb.transaction_cancel() raise else: + + # This is a special case, we have completed a full + # replication so if a link comes to us that points to a + # deleted object, and we asked for all objects already, we + # just have to ignore it, the chance to re-try the + # replication with GET_TGT has long gone. This can happen + # if the object is deleted and sent to us after the link + # was sent, as we are processing all links in the + # transaction_commit(). + if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY: + ctx.local_samdb.set_opaque_integer(dsdb.DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME, + 1) ctx.local_samdb.transaction_commit() + ctx.local_samdb.set_opaque_integer(dsdb.DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME, + 0) ctx.logger.info("Committed SAM database") # A large replication may have caused our LDB connection to the diff --git a/python/samba/netcmd/ldapcmp.py b/python/samba/netcmd/ldapcmp.py index fb49cbe1ae5..ff26e96332d 100644 --- a/python/samba/netcmd/ldapcmp.py +++ b/python/samba/netcmd/ldapcmp.py @@ -121,7 +121,7 @@ class LDAPBase(object): for x in res: if "nETBIOSName" in x: - return x["nETBIOSName"][0] + return x["nETBIOSName"][0].decode() def object_exists(self, object_dn): res = None diff --git a/source4/dsdb/pydsdb.c b/source4/dsdb/pydsdb.c index 804007e9e86..fd7cda21a43 100644 --- a/source4/dsdb/pydsdb.c +++ b/source4/dsdb/pydsdb.c @@ -1768,5 +1768,7 @@ MODULE_INIT_FUNC(dsdb) ADD_DSDB_STRING(DS_GUID_SCHEMA_CLASS_MANAGED_SERVICE_ACCOUNT); ADD_DSDB_STRING(DS_GUID_SCHEMA_CLASS_USER); + ADD_DSDB_STRING(DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME); + return m; } diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index c1ea5ad90f8..175a02d3ba7 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -7533,6 +7533,16 @@ static int replmd_allow_missing_target(struct ldb_module *module, source_dn, target_dn); if (is_in_same_nc) { + /* + * We allow the join.py code to point out that all + * replication is completed, so failing now would just + * trigger errors, rather than trigger a GET_TGT + */ + int *finished_full_join_ptr = + talloc_get_type(ldb_get_opaque(ldb, + DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME), + int); + bool finished_full_join = finished_full_join_ptr && *finished_full_join_ptr; /* * if the target is already be up-to-date there's no point in @@ -7540,7 +7550,8 @@ static int replmd_allow_missing_target(struct ldb_module *module, * on a one-way link was deleted. We ignore the link rather * than failing the replication cycle completely */ - if (dsdb_repl_flags & DSDB_REPL_FLAG_TARGETS_UPTODATE) { + if (finished_full_join + || dsdb_repl_flags & DSDB_REPL_FLAG_TARGETS_UPTODATE) { *ignore_link = true; DBG_WARNING("%s is %s " "but up to date. Ignoring link from %s\n", diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h index d76cbeba841..cbbb766ea22 100644 --- a/source4/dsdb/samdb/samdb.h +++ b/source4/dsdb/samdb/samdb.h @@ -371,6 +371,8 @@ struct dsdb_extended_dn_store_format { #define DSDB_OPAQUE_PARTITION_MODULE_MSG_OPAQUE_NAME "DSDB_OPAQUE_PARTITION_MODULE_MSG" +#define DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME "DSDB_FULL_JOIN_REPLICATION_COMPLETED" + #define DSDB_ACL_CHECKS_DIRSYNC_FLAG 0x1 #define DSDB_SAMDB_MINIMUM_ALLOWED_RID 1000 diff --git a/source4/dsdb/tests/python/large_ldap.py b/source4/dsdb/tests/python/large_ldap.py index 0805119a700..13729052e65 100644 --- a/source4/dsdb/tests/python/large_ldap.py +++ b/source4/dsdb/tests/python/large_ldap.py @@ -66,30 +66,32 @@ creds = credopts.get_credentials(lp) class ManyLDAPTest(samba.tests.TestCase): - def setUp(self): - super(ManyLDAPTest, self).setUp() - self.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp) - self.base_dn = self.ldb.domain_dn() - self.OU_NAME_MANY="many_ou" + format(random.randint(0, 99999), "05") - self.ou_dn = ldb.Dn(self.ldb, "ou=" + self.OU_NAME_MANY + "," + str(self.base_dn)) - - samba.tests.delete_force(self.ldb, self.ou_dn, + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp) + cls.base_dn = self.ldb.domain_dn() + cls.OU_NAME_MANY="many_ou" + format(random.randint(0, 99999), "05") + cls.ou_dn = ldb.Dn(self.ldb, "ou=" + self.OU_NAME_MANY + "," + str(self.base_dn)) + + samba.tests.delete_force(cls.ldb, cls.ou_dn, controls=['tree_delete:1']) - self.ldb.add({ - "dn": self.ou_dn, + cls.ldb.add({ + "dn": cls.ou_dn, "objectclass": "organizationalUnit", - "ou": self.OU_NAME_MANY}) + "ou": cls.OU_NAME_MANY}) for x in range(2000): - ou_name = self.OU_NAME_MANY + str(x) - self.ldb.add({ - "dn": "ou=" + ou_name + "," + str(self.ou_dn), + ou_name = cls.OU_NAME_MANY + str(x) + cls.ldb.add({ + "dn": "ou=" + ou_name + "," + str(cls.ou_dn), "objectclass": "organizationalUnit", "ou": ou_name}) - def tearDown(self): - samba.tests.delete_force(self.ldb, self.ou_dn, + @classmethod + def tearDownClass(cls): + samba.tests.delete_force(cls.ldb, self.ou_dn, controls=['tree_delete:1']) def test_unindexed_iterator_search(self): @@ -117,34 +119,35 @@ class ManyLDAPTest(samba.tests.TestCase): class LargeLDAPTest(samba.tests.TestCase): - def setUp(self): - super(LargeLDAPTest, self).setUp() - self.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp) - self.base_dn = self.ldb.domain_dn() - self.USER_NAME = "large_user" + format(random.randint(0, 99999), "05") + "-" - self.OU_NAME="large_user_ou" + format(random.randint(0, 99999), "05") - self.ou_dn = ldb.Dn(self.ldb, "ou=" + self.OU_NAME + "," + str(self.base_dn)) + @classmethod + def setUpClass(cls): + cls.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp) + cls.base_dn = cls.ldb.domain_dn() + cls.USER_NAME = "large_user" + format(random.randint(0, 99999), "05") + "-" + cls.OU_NAME="large_user_ou" + format(random.randint(0, 99999), "05") + cls.ou_dn = ldb.Dn(cls.ldb, "ou=" + cls.OU_NAME + "," + str(cls.base_dn)) - samba.tests.delete_force(self.ldb, self.ou_dn, + samba.tests.delete_force(cls.ldb, cls.ou_dn, controls=['tree_delete:1']) - self.ldb.add({ - "dn": self.ou_dn, + cls.ldb.add({ + "dn": cls.ou_dn, "objectclass": "organizationalUnit", - "ou": self.OU_NAME}) + "ou": cls.OU_NAME}) for x in range(200): - user_name = self.USER_NAME + format(x, "03") - self.ldb.add({ - "dn": "cn=" + user_name + "," + str(self.ou_dn), + user_name = cls.USER_NAME + format(x, "03") + cls.ldb.add({ + "dn": "cn=" + user_name + "," + str(cls.ou_dn), "objectclass": "user", "sAMAccountName": user_name, "jpegPhoto": b'a' * (2 * 1024 * 1024)}) - def tearDown(self): + @classmethod + def tearDownClass(cls): # Remake the connection for tear-down (old Samba drops the socket) - self.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp) - samba.tests.delete_force(self.ldb, self.ou_dn, + cls.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp) + samba.tests.delete_force(cls.ldb, cls.ou_dn, controls=['tree_delete:1']) def test_unindexed_iterator_search(self): diff --git a/source4/torture/drs/python/ridalloc_exop.py b/source4/torture/drs/python/ridalloc_exop.py index 76b36854adf..d6d0082eccd 100644 --- a/source4/torture/drs/python/ridalloc_exop.py +++ b/source4/torture/drs/python/ridalloc_exop.py @@ -43,6 +43,7 @@ from samba.auth import system_session, admin_session from samba.dbchecker import dbcheck from samba.ndr import ndr_pack from samba.dcerpc import security +from samba import drs_utils, dsdb class DrsReplicaSyncTestCase(drs_base.DrsBaseTestCase): @@ -665,3 +666,137 @@ class DrsReplicaSyncTestCase(drs_base.DrsBaseTestCase): finally: self._test_force_demote(fsmo_owner['dns_name'], "RIDALLOCTEST7") shutil.rmtree(targetdir, ignore_errors=True) + + def test_replicate_against_deleted_objects_transaction(self): + """Not related to RID allocation, but uses the infrastructure here. + Do a join, create a link between two objects remotely, but + remove the target locally. Show that we need to set a magic + opaque if there is an outer transaction. + + """ + fsmo_dn = ldb.Dn(self.ldb_dc1, "CN=RID Manager$,CN=System," + self.ldb_dc1.domain_dn()) + (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn) + + test_user4 = "ridalloctestuser4" + test_group = "ridalloctestgroup1" + + self.ldb_dc1.newuser(test_user4, "P@ssword!") + + self.addCleanup(self.ldb_dc1.deleteuser, test_user4) + + self.ldb_dc1.newgroup(test_group) + self.addCleanup(self.ldb_dc1.deletegroup, test_group) + + targetdir = self._test_join(self.dnsname_dc1, "RIDALLOCTEST8") + try: + # Connect to the database + ldb_url = "tdb://%s" % os.path.join(targetdir, "private/sam.ldb") + lp = self.get_loadparm() + + new_ldb = SamDB(ldb_url, + session_info=system_session(lp), lp=lp) + + destination_dsa_guid = misc.GUID(new_ldb.get_ntds_GUID()) + + repl = drs_utils.drs_Replicate(f'ncacn_ip_tcp:{self.dnsname_dc1}[seal]', + lp, + self.get_credentials(), + new_ldb, + destination_dsa_guid) + + source_dsa_invocation_id = misc.GUID(self.ldb_dc1.invocation_id) + + # Add the link on the remote DC + self.ldb_dc1.add_remove_group_members(test_group, [test_user4]) + + # Starting a transaction overrides, currently the logic + # inside repl.replicatate to retry with GET_TGT which in + # turn tells the repl_meta_data module that the most up to + # date info is already available + new_ldb.transaction_start() + repl.replicate(self.ldb_dc1.domain_dn(), + source_dsa_invocation_id, + destination_dsa_guid) + + # Delete the user locally, before applying the links. + # This simulates getting the delete in the replciation + # stream. + new_ldb.deleteuser(test_user4) + + # This fails as the user has been deleted locally but a remote link is sent + self.assertRaises(ldb.LdbError, new_ldb.transaction_commit) + + new_ldb.transaction_start() + repl.replicate(self.ldb_dc1.domain_dn(), + source_dsa_invocation_id, + destination_dsa_guid) + + # Delete the user locally (the previous transaction + # doesn't apply), before applying the links. This + # simulates getting the delete in the replciation stream. + new_ldb.deleteuser(test_user4) + + new_ldb.set_opaque_integer(dsdb.DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME, + 1) + + # This should now work + try: + new_ldb.transaction_commit() + except ldb.LdbError as e: + self.fail(f"Failed to replicate despite setting opaque with {e.args[1]}") + + finally: + self._test_force_demote(self.dnsname_dc1, "RIDALLOCTEST8") + shutil.rmtree(targetdir, ignore_errors=True) + + def test_replicate_against_deleted_objects_normal(self): + """Not related to RID allocation, but uses the infrastructure here. + Do a join, create a link between two objects remotely, but + remove the target locally. . + + """ + fsmo_dn = ldb.Dn(self.ldb_dc1, "CN=RID Manager$,CN=System," + self.ldb_dc1.domain_dn()) + (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn) + + test_user5 = "ridalloctestuser5" + test_group2 = "ridalloctestgroup2" + + self.ldb_dc1.newuser(test_user5, "P@ssword!") + self.addCleanup(self.ldb_dc1.deleteuser, test_user5) + + self.ldb_dc1.newgroup(test_group2) + self.addCleanup(self.ldb_dc1.deletegroup, test_group2) + + targetdir = self._test_join(self.dnsname_dc1, "RIDALLOCTEST9") + try: + # Connect to the database + ldb_url = "tdb://%s" % os.path.join(targetdir, "private/sam.ldb") -- Samba Shared Repository