[SCM] Samba Shared Repository - branch master updated

Andrew Bartlett abartlet at samba.org
Tue Sep 26 07:37:02 UTC 2017


The branch, master has been updated
       via  0d4c3e5 replmd: RMD_VERSION incorrectly incremented for link conflicts
       via  5f93ac1 selftest: Add test for a re-animated object conflict
       via  d278f5e selftest: Windows resolves object conflicts differently to Samba
       via  1b395f4 selftest: replica_sync did not fully cleanup if test failed
      from  3982b77 tfork: set waiter process title

https://git.samba.org/?p=samba.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit 0d4c3e5e3fd379b35645d5d10f9705116eba4197
Author: Tim Beale <timbeale at catalyst.net.nz>
Date:   Thu Sep 14 16:34:59 2017 +1200

    replmd: RMD_VERSION incorrectly incremented for link conflicts
    
    This problem was noticed when 2 DCs added the same linked attribute at
    roughly the same time. One DC would have a later timestamp than the
    other, so it would re-apply the same link information. However, when it
    did this, replmd_update_la_val() would incorrectly increment the
    RMD_VERSION for the attribute. We then end up with one DC having a
    higher RMD_VERSION than the others (and it doesn't replicate the new
    RMD_VERSION out).
    
    During replication RMD_VERSION is used to determine whether a linked
    attribute is old (and should be ignored), or whether the information is
    new and should be applied to the DB. This RMD_VERSION discrepancy could
    potentially cause a subsequent linked attribute update to be ignored.
    
    Normally when a local DB operation is performed, we just pass in a
    version of zero and get replmd_update_la_val() to increment what's
    already in the DB. However, we *never* want this to happen during
    replication - we should always use the version we receive from the peer
    DC.
    
    This patch fixes the problem by separating the API into two:
    - replmd_update_la_val(): we're updating a linked attribute in the DB,
      and so as part of this operation we always want to increment the
      version number (the version no longer need to be passed in because
      we can work it out from the existing DB entry).
    - replmd_set_la_val(): we want to set a linked attribute to use the
      exact values we're telling it, including the version. This is what
      replication needs to use.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13038
    Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    
    Autobuild-User(master): Andrew Bartlett <abartlet at samba.org>
    Autobuild-Date(master): Tue Sep 26 09:36:48 CEST 2017 on sn-devel-144

commit 5f93ac1f6f74f6960708dd7f58b9b95073df517d
Author: Tim Beale <timbeale at catalyst.net.nz>
Date:   Wed Sep 20 17:29:46 2017 +1200

    selftest: Add test for a re-animated object conflict
    
    Added a test to simulate a user accidentally being deleted and 2
    different admins trying to resolve the problem simultaneously - one by
    re-animating the object and one by just creating a new object with
    the same name.
    
    Currently this test fails on Samba because it chooses the higher
    version
    number as the winner instead of the latest change.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13039
    
    Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>

commit d278f5ea9959b6ec6892f4b0c13a7dd3933ff364
Author: Tim Beale <timbeale at catalyst.net.nz>
Date:   Tue Sep 26 13:11:47 2017 +1300

    selftest: Windows resolves object conflicts differently to Samba
    
    While testing link conflicts I noticed that Windows resolves conflicts
    differently to Samba. Samba considers the version number first when
    resolving the conflict, whereas Windows always takes the latest change.
    
    The existing object conflict test cases didn't detect this problem
    because they were both modifying the object the same number of times (so
    they had the same version number).
    
    I've added new tests that highlight the problem. They are basically the
    same as the existing rename tests, except that only one DC does the
    rename. Samba will always pick the renamed object as the winner, whereas
    Windows picks the most recent change.
    
    I've marked this test as a known fail for now.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=13039
    
    Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>

commit 1b395f488a94f2e2811622b61a8c750374d640ed
Author: Tim Beale <timbeale at catalyst.net.nz>
Date:   Mon Sep 18 12:39:21 2017 +1200

    selftest: replica_sync did not fully cleanup if test failed
    
    Normally the replica_sync tests do the cleanup at the end of the test
    case, rather than in the tearDown(). However, if the tests don't run to
    completion (because they fail), then the objects may not get cleaned up
    properly, which causes the tests to fail on the 2nd test-env.
    
    The problem is the object deletion only occurs on DC2 and it relies on
    replication to propagate the deletion to DC1. Presumably this
    propagation could be missed because the tests are repeatedly turning off
    inbound replication on both DCs.
    
    This patch changes the tearDown() so it tries to delete the objects off
    both DCs, which appears to fix the problem.
    
    Signed-off-by: Tim Beale <timbeale at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>

-----------------------------------------------------------------------

Summary of changes:
 selftest/knownfail.d/link_conflicts             |   5 -
 selftest/knownfail.d/replica_sync               |  10 ++
 source4/dsdb/samdb/ldb_modules/repl_meta_data.c |  69 ++++++----
 source4/torture/drs/python/replica_sync.py      | 169 ++++++++++++++++++++++--
 4 files changed, 213 insertions(+), 40 deletions(-)
 create mode 100644 selftest/knownfail.d/replica_sync


Changeset truncated at 500 lines:

diff --git a/selftest/knownfail.d/link_conflicts b/selftest/knownfail.d/link_conflicts
index 1c41335..e0c1ded 100644
--- a/selftest/knownfail.d/link_conflicts
+++ b/selftest/knownfail.d/link_conflicts
@@ -1,9 +1,4 @@
 # Currently Samba can't resolve a conflict for a single-valued link attribute
 samba4.drs.link_conflicts.python\(vampire_dc\).link_conflicts.DrsReplicaLinkConflictTestCase.test_conflict_single_valued_link\(vampire_dc\)
 samba4.drs.link_conflicts.python\(promoted_dc\).link_conflicts.DrsReplicaLinkConflictTestCase.test_conflict_single_valued_link\(promoted_dc\)
-# There's a bug where Samba can incorrectly increment the attribute's version number
-samba4.drs.link_conflicts.python\(vampire_dc\).link_conflicts.DrsReplicaLinkConflictTestCase.test_link_deletion_conflict\(vampire_dc\)
-samba4.drs.link_conflicts.python\(promoted_dc\).link_conflicts.DrsReplicaLinkConflictTestCase.test_link_deletion_conflict\(promoted_dc\)
-samba4.drs.link_conflicts.python\(vampire_dc\).link_conflicts.DrsReplicaLinkConflictTestCase.test_full_sync_link_conflict\(vampire_dc\)
-samba4.drs.link_conflicts.python\(promoted_dc\).link_conflicts.DrsReplicaLinkConflictTestCase.test_full_sync_link_conflict\(promoted_dc\)
 
diff --git a/selftest/knownfail.d/replica_sync b/selftest/knownfail.d/replica_sync
new file mode 100644
index 0000000..067e07c
--- /dev/null
+++ b/selftest/knownfail.d/replica_sync
@@ -0,0 +1,10 @@
+# Samba currently picks a different winner of object conflicts compared to Windows.
+# Samba uses the version number whereas Windows always takes the most recent change
+samba4.drs.replica_sync.python\(vampire_dc\).replica_sync.DrsReplicaSyncTestCase.test_ReplConflictsRenamedVsNewRemoteWin\(vampire_dc:local\)
+samba4.drs.replica_sync.python\(promoted_dc\).replica_sync.DrsReplicaSyncTestCase.test_ReplConflictsRenamedVsNewRemoteWin\(promoted_dc:local\)
+samba4.drs.replica_sync.python\(vampire_dc\).replica_sync.DrsReplicaSyncTestCase.test_ReplConflictsRenamedVsNewLocalWin\(vampire_dc:local\)
+samba4.drs.replica_sync.python\(promoted_dc\).replica_sync.DrsReplicaSyncTestCase.test_ReplConflictsRenamedVsNewLocalWin\(promoted_dc:local\)
+samba4.drs.replica_sync.python\(vampire_dc\).replica_sync.DrsReplicaSyncTestCase.test_ReplReanimationConflict\(vampire_dc:local\)
+samba4.drs.replica_sync.python\(promoted_dc\).replica_sync.DrsReplicaSyncTestCase.test_ReplReanimationConflict\(promoted_dc:local\)
+
+
diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
index 909a1be..2e0f705 100644
--- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
+++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
@@ -2214,7 +2214,7 @@ static int replmd_build_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct ds
 static int replmd_update_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct dsdb_dn *dsdb_dn,
 				struct dsdb_dn *old_dsdb_dn, const struct GUID *invocation_id,
 				uint64_t seq_num, uint64_t local_usn, NTTIME nttime,
-				uint32_t version, bool deleted);
+				bool deleted);
 
 /*
   check if any links need upgrading from w2k format
@@ -2268,7 +2268,7 @@ static int replmd_check_upgrade_links(struct ldb_context *ldb,
 		/* it's an old one that needs upgrading */
 		ret = replmd_update_la_val(el->values, dns[i].v,
 					   dns[i].dsdb_dn, dns[i].dsdb_dn,
-					   invocation_id, 1, 1, 0, 0, false);
+					   invocation_id, 1, 1, 0, false);
 		if (ret != LDB_SUCCESS) {
 			return ret;
 		}
@@ -2290,14 +2290,14 @@ static int replmd_check_upgrade_links(struct ldb_context *ldb,
 }
 
 /*
-  update an extended DN, including all meta data fields
+  Sets the value for a linked attribute, including all meta data fields
 
   see replmd_build_la_val for value names
  */
-static int replmd_update_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct dsdb_dn *dsdb_dn,
-				struct dsdb_dn *old_dsdb_dn, const struct GUID *invocation_id,
-				uint64_t usn, uint64_t local_usn, NTTIME nttime,
-				uint32_t version, bool deleted)
+static int replmd_set_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct dsdb_dn *dsdb_dn,
+			     struct dsdb_dn *old_dsdb_dn, const struct GUID *invocation_id,
+			     uint64_t usn, uint64_t local_usn, NTTIME nttime,
+			     uint32_t version, bool deleted)
 {
 	struct ldb_dn *dn = dsdb_dn->dn;
 	const char *tstring, *usn_string, *flags_string;
@@ -2306,7 +2306,6 @@ static int replmd_update_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct d
 	struct ldb_val usnv, local_usnv;
 	struct ldb_val vers, flagsv;
 	const struct ldb_val *old_addtime;
-	uint32_t old_version;
 	NTSTATUS status;
 	int ret;
 	const char *dnstring;
@@ -2371,11 +2370,6 @@ static int replmd_update_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct d
 	ret = ldb_dn_set_extended_component(dn, "RMD_LOCAL_USN", &local_usnv);
 	if (ret != LDB_SUCCESS) return ret;
 
-	/* increase the version by 1 */
-	status = dsdb_get_extended_dn_uint32(old_dsdb_dn->dn, &old_version, "RMD_VERSION");
-	if (NT_STATUS_IS_OK(status) && old_version >= version) {
-		version = old_version+1;
-	}
 	vstring = talloc_asprintf(dn, "%lu", (unsigned long)version);
 	vers = data_blob_string_const(vstring);
 	ret = ldb_dn_set_extended_component(dn, "RMD_VERSION", &vers);
@@ -2390,6 +2384,33 @@ static int replmd_update_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct d
 	return LDB_SUCCESS;
 }
 
+/**
+ * Updates the value for a linked attribute, including all meta data fields
+ */
+static int replmd_update_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct dsdb_dn *dsdb_dn,
+				struct dsdb_dn *old_dsdb_dn, const struct GUID *invocation_id,
+				uint64_t usn, uint64_t local_usn, NTTIME nttime,
+				bool deleted)
+{
+	uint32_t old_version;
+	uint32_t version = 0;
+	NTSTATUS status;
+
+	/*
+	 * We're updating the linked attribute locally, so increase the version
+	 * by 1 so that other DCs will see the change when it gets replicated out
+	 */
+	status = dsdb_get_extended_dn_uint32(old_dsdb_dn->dn, &old_version,
+					     "RMD_VERSION");
+
+	if (NT_STATUS_IS_OK(status)) {
+		version = old_version + 1;
+	}
+
+	return replmd_set_la_val(mem_ctx, v, dsdb_dn, old_dsdb_dn, invocation_id,
+				 usn, local_usn, nttime, version, deleted);
+}
+
 /*
   handle adding a linked attribute
  */
@@ -2520,7 +2541,7 @@ static int replmd_modify_la_add(struct ldb_module *module,
 						   dns[i].dsdb_dn,
 						   exact->dsdb_dn,
 						   invocation_id, seq_num,
-						   seq_num, now, 0, false);
+						   seq_num, now, false);
 			if (ret != LDB_SUCCESS) {
 				talloc_free(tmp_ctx);
 				return ret;
@@ -2727,7 +2748,7 @@ static int replmd_modify_la_delete(struct ldb_module *module,
 			ret = replmd_update_la_val(old_el->values, p->v,
 						   p->dsdb_dn, p->dsdb_dn,
 						   invocation_id, seq_num,
-						   seq_num, now, 0, true);
+						   seq_num, now, true);
 			if (ret != LDB_SUCCESS) {
 				talloc_free(tmp_ctx);
 				return ret;
@@ -2824,7 +2845,7 @@ static int replmd_modify_la_delete(struct ldb_module *module,
 		ret = replmd_update_la_val(old_el->values, exact->v,
 					   exact->dsdb_dn, exact->dsdb_dn,
 					   invocation_id, seq_num, seq_num,
-					   now, 0, true);
+					   now, true);
 		if (ret != LDB_SUCCESS) {
 			talloc_free(tmp_ctx);
 			return ret;
@@ -2993,7 +3014,7 @@ static int replmd_modify_la_replace(struct ldb_module *module,
 							   old_p->dsdb_dn,
 							   invocation_id,
 							   seq_num, seq_num,
-							   now, 0, true);
+							   now, true);
 				if (ret != LDB_SUCCESS) {
 					talloc_free(tmp_ctx);
 					return ret;
@@ -3027,7 +3048,7 @@ static int replmd_modify_la_replace(struct ldb_module *module,
 						   old_p->dsdb_dn,
 						   invocation_id,
 						   seq_num, seq_num,
-						   now, 0, false);
+						   now, false);
 			if (ret != LDB_SUCCESS) {
 				talloc_free(tmp_ctx);
 				return ret;
@@ -7270,12 +7291,12 @@ static int replmd_process_linked_attribute(struct ldb_module *module,
 			}
 		}
 
-		ret = replmd_update_la_val(tmp_ctx, pdn->v, dsdb_dn, pdn->dsdb_dn,
-					   &la->meta_data.originating_invocation_id,
-					   la->meta_data.originating_usn, seq_num,
-					   la->meta_data.originating_change_time,
-					   la->meta_data.version,
-					   !active);
+		ret = replmd_set_la_val(tmp_ctx, pdn->v, dsdb_dn, pdn->dsdb_dn,
+					&la->meta_data.originating_invocation_id,
+					la->meta_data.originating_usn, seq_num,
+					la->meta_data.originating_change_time,
+					la->meta_data.version,
+					!active);
 		if (ret != LDB_SUCCESS) {
 			talloc_free(tmp_ctx);
 			return ret;
diff --git a/source4/torture/drs/python/replica_sync.py b/source4/torture/drs/python/replica_sync.py
index dd9f276..93407df 100644
--- a/source4/torture/drs/python/replica_sync.py
+++ b/source4/torture/drs/python/replica_sync.py
@@ -30,6 +30,7 @@
 import drs_base
 import samba.tests
 import time
+import ldb
 
 from ldb import (
     SCOPE_BASE, LdbError, ERR_NO_SUCH_OBJECT)
@@ -45,23 +46,27 @@ class DrsReplicaSyncTestCase(drs_base.DrsBaseTestCase):
         self.ou2 = None
 
     def tearDown(self):
+        self._cleanup_object(self.ou1)
+        self._cleanup_object(self.ou2)
+
         # re-enable replication
         self._enable_inbound_repl(self.dnsname_dc1)
         self._enable_inbound_repl(self.dnsname_dc2)
-        if self.ldb_dc2 is not None:
-            if self.ou1 is not None:
-                try:
-                    self.ldb_dc2.delete('<GUID=%s>' % self.ou1, ["tree_delete:1"])
-                except LdbError, (num, _):
-                    self.assertEquals(num, ERR_NO_SUCH_OBJECT)
-            if self.ou2 is not None:
-                try:
-                    self.ldb_dc2.delete('<GUID=%s>' % self.ou2, ["tree_delete:1"])
-                except LdbError, (num, _):
-                    self.assertEquals(num, ERR_NO_SUCH_OBJECT)
 
         super(DrsReplicaSyncTestCase, self).tearDown()
 
+    def _cleanup_object(self, guid):
+        """Cleans up a test object, if it still exists"""
+        if guid is not None:
+            try:
+                self.ldb_dc2.delete('<GUID=%s>' % guid, ["tree_delete:1"])
+            except LdbError, (num, _):
+                self.assertEquals(num, ERR_NO_SUCH_OBJECT)
+            try:
+                self.ldb_dc1.delete('<GUID=%s>' % guid, ["tree_delete:1"])
+            except LdbError, (num, _):
+                self.assertEquals(num, ERR_NO_SUCH_OBJECT)
+
     def test_ReplEnabled(self):
         """Tests we can replicate when replication is enabled"""
         self._enable_inbound_repl(self.dnsname_dc1)
@@ -294,6 +299,86 @@ objectClass: organizationalUnit
         self._check_deleted(self.ldb_dc2, ou1_child)
         self._check_deleted(self.ldb_dc2, ou2_child)
 
+    def test_ReplConflictsRenamedVsNewRemoteWin(self):
+        """Tests resolving a DN conflict between a renamed object and a new object"""
+        self._disable_inbound_repl(self.dnsname_dc1)
+        self._disable_inbound_repl(self.dnsname_dc2)
+
+        # Create an OU and rename it on DC1
+        self.ou1 = self._create_ou(self.ldb_dc1, "OU=Test Remote Rename Conflict orig")
+        self.ldb_dc1.rename("<GUID=%s>" % self.ou1, "OU=Test Remote Rename Conflict,%s" % self.domain_dn)
+
+        # We have to sleep to ensure that the two objects have different timestamps
+        time.sleep(1)
+
+        # create a conflicting object with the same DN on DC2
+        self.ou2 = self._create_ou(self.ldb_dc2, "OU=Test Remote Rename Conflict")
+
+        self._net_drs_replicate(DC=self.dnsname_dc1, fromDC=self.dnsname_dc2, forced=True, full_sync=False)
+
+        # Check that DC2 got the DC1 object, and SELF.OU1 was made into conflict
+        res1 = self.ldb_dc1.search(base="<GUID=%s>" % self.ou1,
+                                  scope=SCOPE_BASE, attrs=["name"])
+        res2 = self.ldb_dc1.search(base="<GUID=%s>" % self.ou2,
+                                  scope=SCOPE_BASE, attrs=["name"])
+        print res1[0]["name"][0]
+        print res2[0]["name"][0]
+        self.assertTrue('CNF:%s' % self.ou1 in str(res1[0]["name"][0]))
+        self.assertTrue(self._lost_and_found_dn(self.ldb_dc1, self.domain_dn) not in str(res1[0].dn))
+        self.assertTrue(self._lost_and_found_dn(self.ldb_dc1, self.domain_dn) not in str(res2[0].dn))
+        self.assertEqual(str(res1[0]["name"][0]), res1[0].dn.get_rdn_value())
+        self.assertEqual(str(res2[0]["name"][0]), res2[0].dn.get_rdn_value())
+
+        # Delete both objects by GUID on DC1
+        self.ldb_dc1.delete('<GUID=%s>' % self.ou1)
+        self.ldb_dc1.delete('<GUID=%s>' % self.ou2)
+
+        self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1, forced=True, full_sync=False)
+
+        self._check_deleted(self.ldb_dc1, self.ou1)
+        self._check_deleted(self.ldb_dc1, self.ou2)
+        # Check deleted on DC2
+        self._check_deleted(self.ldb_dc2, self.ou1)
+        self._check_deleted(self.ldb_dc2, self.ou2)
+
+    def test_ReplConflictsRenamedVsNewLocalWin(self):
+        """Tests resolving a DN conflict between a renamed object and a new object"""
+        self._disable_inbound_repl(self.dnsname_dc1)
+        self._disable_inbound_repl(self.dnsname_dc2)
+
+        # Create conflicting objects on DC1 and DC2, where the DC2 object has been renamed
+        self.ou2 = self._create_ou(self.ldb_dc2, "OU=Test Rename Local Conflict orig")
+        self.ldb_dc2.rename("<GUID=%s>" % self.ou2, "OU=Test Rename Local Conflict,%s" % self.domain_dn)
+        # We have to sleep to ensure that the two objects have different timestamps
+        time.sleep(1)
+        self.ou1 = self._create_ou(self.ldb_dc1, "OU=Test Rename Local Conflict")
+
+        self._net_drs_replicate(DC=self.dnsname_dc1, fromDC=self.dnsname_dc2, forced=True, full_sync=False)
+
+        # Check that DC2 got the DC1 object, and OU2 was made into conflict
+        res1 = self.ldb_dc1.search(base="<GUID=%s>" % self.ou1,
+                                  scope=SCOPE_BASE, attrs=["name"])
+        res2 = self.ldb_dc1.search(base="<GUID=%s>" % self.ou2,
+                                  scope=SCOPE_BASE, attrs=["name"])
+        print res1[0]["name"][0]
+        print res2[0]["name"][0]
+        self.assertTrue('CNF:%s' % self.ou2 in str(res2[0]["name"][0]))
+        self.assertTrue(self._lost_and_found_dn(self.ldb_dc1, self.domain_dn) not in str(res1[0].dn))
+        self.assertTrue(self._lost_and_found_dn(self.ldb_dc1, self.domain_dn) not in str(res2[0].dn))
+        self.assertEqual(str(res1[0]["name"][0]), res1[0].dn.get_rdn_value())
+        self.assertEqual(str(res2[0]["name"][0]), res2[0].dn.get_rdn_value())
+
+        # Delete both objects by GUID on DC1
+        self.ldb_dc1.delete('<GUID=%s>' % self.ou1)
+        self.ldb_dc1.delete('<GUID=%s>' % self.ou2)
+
+        self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1, forced=True, full_sync=False)
+
+        self._check_deleted(self.ldb_dc1, self.ou1)
+        self._check_deleted(self.ldb_dc1, self.ou2)
+        # Check deleted on DC2
+        self._check_deleted(self.ldb_dc2, self.ou1)
+        self._check_deleted(self.ldb_dc2, self.ou2)
 
     def test_ReplConflictsRenameRemoteWin(self):
         """Tests that objects created in conflict become conflict DNs"""
@@ -589,3 +674,65 @@ objectClass: organizationalUnit
         self._check_deleted(self.ldb_dc2, ou1_child)
         self._check_deleted(self.ldb_dc2, ou2_child)
         self._check_deleted(self.ldb_dc2, ou3_child)
+
+    def reanimate_object(self, samdb, guid, new_dn):
+        """Re-animates a deleted object"""
+        res = samdb.search(base="<GUID=%s>" % guid, attrs=["isDeleted"],
+                           controls=['show_deleted:1'], scope=SCOPE_BASE)
+        if len(res) != 1:
+            return
+
+        msg = ldb.Message()
+        msg.dn = res[0].dn
+        msg["isDeleted"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "isDeleted")
+        msg["distinguishedName"] = ldb.MessageElement([new_dn], ldb.FLAG_MOD_REPLACE, "distinguishedName")
+        samdb.modify(msg, ["show_deleted:1"])
+
+    def test_ReplReanimationConflict(self):
+        """
+        Checks that if a reanimated object conflicts with a new object, then
+        the conflict is resolved correctly.
+        """
+
+        self._disable_inbound_repl(self.dnsname_dc1)
+        self._disable_inbound_repl(self.dnsname_dc2)
+
+        # create an object, "accidentally" delete it, and replicate the changes to both DCs
+        self.ou1 = self._create_ou(self.ldb_dc2, "OU=Conflict object")
+        self.ldb_dc2.delete('<GUID=%s>' % self.ou1)
+        self._net_drs_replicate(DC=self.dnsname_dc1, fromDC=self.dnsname_dc2, forced=True, full_sync=False)
+
+        # Now pretend that the admin for one DC resolves the problem by
+        # re-animating the object...
+        self.reanimate_object(self.ldb_dc1, self.ou1, "OU=Conflict object,%s" % self.domain_dn)
+
+        # ...whereas another admin just creates a user with the same name
+        # again on a different DC
+        time.sleep(1)
+        self.ou2 = self._create_ou(self.ldb_dc2, "OU=Conflict object")
+
+        # Now sync the DCs to resolve the conflict
+        self._net_drs_replicate(DC=self.dnsname_dc1, fromDC=self.dnsname_dc2, forced=True, full_sync=False)
+
+        # Check the latest change won and SELF.OU1 was made into a conflict
+        res1 = self.ldb_dc1.search(base="<GUID=%s>" % self.ou1,
+                                  scope=SCOPE_BASE, attrs=["name"])
+        res2 = self.ldb_dc1.search(base="<GUID=%s>" % self.ou2,
+                                  scope=SCOPE_BASE, attrs=["name"])
+        print res1[0]["name"][0]
+        print res2[0]["name"][0]
+        self.assertTrue('CNF:%s' % self.ou1 in str(res1[0]["name"][0]))
+        self.assertFalse('CNF:%s' % self.ou2 in str(res2[0]["name"][0]))
+
+        # Delete both objects by GUID on DC1
+        self.ldb_dc1.delete('<GUID=%s>' % self.ou1)
+        self.ldb_dc1.delete('<GUID=%s>' % self.ou2)
+
+        self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1, forced=True, full_sync=False)
+
+        self._check_deleted(self.ldb_dc1, self.ou1)
+        self._check_deleted(self.ldb_dc1, self.ou2)
+        # Check deleted on DC2
+        self._check_deleted(self.ldb_dc2, self.ou1)
+        self._check_deleted(self.ldb_dc2, self.ou2)
+


-- 
Samba Shared Repository



More information about the samba-cvs mailing list