[PATCH] Re: I think sn-devel is stuck... (samba.dsdb test flapping deadlock)

Andrew Bartlett abartlet at samba.org
Wed Dec 6 01:41:05 UTC 2017


On Tue, 2017-12-05 at 17:11 +1300, Andrew Bartlett via samba-technical
wrote:
> On Sat, 2017-12-02 at 16:57 +1300, Andrew Bartlett via samba-technical
> wrote:
> > 
> > It got stuck on:
> > 
> > [2024(12875)/2216 at 1h48m41s] samba.tests.dsdb(ad_dc_ntvfs:local)
> > 
> > Garming found something similar and send me details while I was on
> > leave.  Given this has happened more than once I'll look into it next
> > week.  Another race condition no doubt...
> 
> Samba is racing against the test, which is blocking on a pipe. 
> Production code should never block on anything while holding a lock,
> test code that does this on purpose should not operate on a database
> that other processes are using, so I'm re-factoring the test.
> 
> In the meantime, if this happens again just kill the test manually.

Attached is a patch to move this test off the 'live' database.

Please review and push if OK.

Thanks!

Andrew Bartlett
-- 
Andrew Bartlett
https://samba.org/~abartlet/
Authentication Developer, Samba Team         https://samba.org
Samba Development and Support, Catalyst IT   
https://catalyst.net.nz/services/samba



-------------- next part --------------
From c67c0d0dd40e160716528da069f033094bbff584 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Wed, 6 Dec 2017 14:31:54 +1300
Subject: [PATCH] selftest: Rework samba.dsdb locking tests into
 samba.dsdb_lock on a new provision

This avoids running the test while samba is modifying and locking the same database,
as this can lead to a deadlock.

The deadlock is not seen in production as the LDB read lock is not held while
waiting for another process, but this test needs to do this to demonstrate
the locking safety.

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/tests/dsdb.py      | 358 -------------------------------------
 python/samba/tests/dsdb_lock.py | 379 ++++++++++++++++++++++++++++++++++++++++
 python/samba/tests/samdb.py     |  83 +++------
 source4/selftest/tests.py       |   1 +
 4 files changed, 406 insertions(+), 415 deletions(-)
 create mode 100644 python/samba/tests/dsdb_lock.py

diff --git a/python/samba/tests/dsdb.py b/python/samba/tests/dsdb.py
index 493ea25db53..c740a419fed 100644
--- a/python/samba/tests/dsdb.py
+++ b/python/samba/tests/dsdb.py
@@ -26,10 +26,7 @@ from samba.ndr import ndr_unpack, ndr_pack
 from samba.dcerpc import drsblobs
 from samba import dsdb
 import ldb
-import os
 import samba
-import gc
-import time
 
 class DsdbTests(TestCase):
 
@@ -166,361 +163,6 @@ class DsdbTests(TestCase):
         self.samdb.set_attribute_replmetadata_version(dn, "description", version + 2)
         self.assertEqual(self.samdb.get_attribute_replmetadata_version(dn, "description"), version + 2)
 
-    def test_db_lock1(self):
-        basedn = self.samdb.get_default_basedn()
-        (r1, w1) = os.pipe()
-
-        pid = os.fork()
-        if pid == 0:
-            # In the child, close the main DB, re-open just one DB
-            del(self.samdb)
-            gc.collect()
-            self.samdb = SamDB(session_info=self.session,
-                               credentials=self.creds,
-                               lp=self.lp)
-
-            self.samdb.transaction_start()
-
-            dn = "cn=test_db_lock_user,cn=users," + str(basedn)
-            self.samdb.add({
-                 "dn": dn,
-                 "objectclass": "user",
-            })
-            self.samdb.delete(dn)
-
-            # Obtain a write lock
-            self.samdb.transaction_prepare_commit()
-            os.write(w1, b"prepared")
-            time.sleep(2)
-
-            # Drop the write lock
-            self.samdb.transaction_cancel()
-            os._exit(0)
-
-        self.assertEqual(os.read(r1, 8), b"prepared")
-
-        start = time.time()
-
-        # We need to hold this iterator open to hold the all-record lock.
-        res = self.samdb.search_iterator()
-
-        # This should take at least 2 seconds because the transaction
-        # has a write lock on one backend db open
-
-        # Release the locks
-        for l in res:
-            pass
-
-        end = time.time()
-        self.assertGreater(end - start, 1.9)
-
-        (got_pid, status) = os.waitpid(pid, 0)
-        self.assertEqual(got_pid, pid)
-        self.assertTrue(os.WIFEXITED(status))
-        self.assertEqual(os.WEXITSTATUS(status), 0)
-
-    def test_db_lock2(self):
-        basedn = self.samdb.get_default_basedn()
-        (r1, w1) = os.pipe()
-        (r2, w2) = os.pipe()
-
-        pid = os.fork()
-        if pid == 0:
-            # In the child, close the main DB, re-open
-            del(self.samdb)
-            gc.collect()
-            self.samdb = SamDB(session_info=self.session,
-                           credentials=self.creds,
-                           lp=self.lp)
-
-            # We need to hold this iterator open to hold the all-record lock.
-            res = self.samdb.search_iterator()
-
-            os.write(w2, b"start")
-            if (os.read(r1, 7) != b"started"):
-                os._exit(1)
-
-            os.write(w2, b"add")
-            if (os.read(r1, 5) != b"added"):
-                os._exit(2)
-
-            # Wait 2 seconds to block prepare_commit() in the child.
-            os.write(w2, b"prepare")
-            time.sleep(2)
-
-            # Release the locks
-            for l in res:
-                pass
-
-            if (os.read(r1, 8) != b"prepared"):
-                os._exit(3)
-
-            os._exit(0)
-
-        # We can start the transaction during the search
-        # because both just grab the all-record read lock.
-        self.assertEqual(os.read(r2, 5), b"start")
-        self.samdb.transaction_start()
-        os.write(w1, b"started")
-
-        self.assertEqual(os.read(r2, 3), b"add")
-        dn = "cn=test_db_lock_user,cn=users," + str(basedn)
-        self.samdb.add({
-             "dn": dn,
-             "objectclass": "user",
-        })
-        self.samdb.delete(dn)
-        os.write(w1, b"added")
-
-        # Obtain a write lock, this will block until
-        # the parent releases the read lock.
-        self.assertEqual(os.read(r2, 7), b"prepare")
-        start = time.time()
-        self.samdb.transaction_prepare_commit()
-        end = time.time()
-        try:
-            self.assertGreater(end - start, 1.9)
-        except:
-            raise
-        finally:
-            os.write(w1, b"prepared")
-
-            # Drop the write lock
-            self.samdb.transaction_cancel()
-
-            (got_pid, status) = os.waitpid(pid, 0)
-            self.assertEqual(got_pid, pid)
-            self.assertTrue(os.WIFEXITED(status))
-            self.assertEqual(os.WEXITSTATUS(status), 0)
-
-    def test_db_lock3(self):
-        basedn = self.samdb.get_default_basedn()
-        (r1, w1) = os.pipe()
-        (r2, w2) = os.pipe()
-
-        pid = os.fork()
-        if pid == 0:
-            # In the child, close the main DB, re-open
-            del(self.samdb)
-            gc.collect()
-            self.samdb = SamDB(session_info=self.session,
-                           credentials=self.creds,
-                           lp=self.lp)
-
-            # We need to hold this iterator open to hold the all-record lock.
-            res = self.samdb.search_iterator()
-
-            os.write(w2, b"start")
-            if (os.read(r1, 7) != b"started"):
-                os._exit(1)
-
-            os.write(w2, b"add")
-            if (os.read(r1, 5) != b"added"):
-                os._exit(2)
-
-            # Wait 2 seconds to block prepare_commit() in the child.
-            os.write(w2, b"prepare")
-            time.sleep(2)
-
-            # Release the locks
-            for l in res:
-                pass
-
-            if (os.read(r1, 8) != b"prepared"):
-                os._exit(3)
-
-            os._exit(0)
-
-        # We can start the transaction during the search
-        # because both just grab the all-record read lock.
-        self.assertEqual(os.read(r2, 5), b"start")
-        self.samdb.transaction_start()
-        os.write(w1, b"started")
-
-        self.assertEqual(os.read(r2, 3), b"add")
-
-        # This will end up in the top level db
-        dn = "@DSDB_LOCK_TEST"
-        self.samdb.add({
-             "dn": dn})
-        self.samdb.delete(dn)
-        os.write(w1, b"added")
-
-        # Obtain a write lock, this will block until
-        # the child releases the read lock.
-        self.assertEqual(os.read(r2, 7), b"prepare")
-        start = time.time()
-        self.samdb.transaction_prepare_commit()
-        end = time.time()
-        self.assertGreater(end - start, 1.9)
-        os.write(w1, b"prepared")
-
-        # Drop the write lock
-        self.samdb.transaction_cancel()
-
-        (got_pid, status) = os.waitpid(pid, 0)
-        self.assertTrue(os.WIFEXITED(status))
-        self.assertEqual(os.WEXITSTATUS(status), 0)
-        self.assertEqual(got_pid, pid)
-
-
-    def _test_full_db_lock1(self, backend_path):
-        (r1, w1) = os.pipe()
-
-        pid = os.fork()
-        if pid == 0:
-            # In the child, close the main DB, re-open just one DB
-            del(self.samdb)
-            gc.collect()
-
-            backenddb = ldb.Ldb(backend_path)
-
-
-            backenddb.transaction_start()
-
-            backenddb.add({"dn":"@DSDB_LOCK_TEST"})
-            backenddb.delete("@DSDB_LOCK_TEST")
-
-            # Obtain a write lock
-            backenddb.transaction_prepare_commit()
-            os.write(w1, b"prepared")
-            time.sleep(2)
-
-            # Drop the write lock
-            backenddb.transaction_cancel()
-            os._exit(0)
-
-        self.assertEqual(os.read(r1, 8), b"prepared")
-
-        start = time.time()
-
-        # We need to hold this iterator open to hold the all-record lock.
-        res = self.samdb.search_iterator()
-
-        # This should take at least 2 seconds because the transaction
-        # has a write lock on one backend db open
-
-        end = time.time()
-        self.assertGreater(end - start, 1.9)
-
-        # Release the locks
-        for l in res:
-            pass
-
-        (got_pid, status) = os.waitpid(pid, 0)
-        self.assertEqual(got_pid, pid)
-        self.assertTrue(os.WIFEXITED(status))
-        self.assertEqual(os.WEXITSTATUS(status), 0)
-
-    def test_full_db_lock1(self):
-        basedn = self.samdb.get_default_basedn()
-        backend_filename = "%s.ldb" % basedn.get_casefold()
-        backend_subpath = os.path.join("sam.ldb.d",
-                                       backend_filename)
-        backend_path = self.lp.private_path(backend_subpath)
-        self._test_full_db_lock1(backend_path)
-
-
-    def test_full_db_lock1_config(self):
-        basedn = self.samdb.get_config_basedn()
-        backend_filename = "%s.ldb" % basedn.get_casefold()
-        backend_subpath = os.path.join("sam.ldb.d",
-                                       backend_filename)
-        backend_path = self.lp.private_path(backend_subpath)
-        self._test_full_db_lock1(backend_path)
-
-
-    def _test_full_db_lock2(self, backend_path):
-        (r1, w1) = os.pipe()
-        (r2, w2) = os.pipe()
-
-        pid = os.fork()
-        if pid == 0:
-
-            # In the child, close the main DB, re-open
-            del(self.samdb)
-            gc.collect()
-            self.samdb = SamDB(session_info=self.session,
-                           credentials=self.creds,
-                           lp=self.lp)
-
-            # We need to hold this iterator open to hold the all-record lock.
-            res = self.samdb.search_iterator()
-
-            os.write(w2, b"start")
-            if (os.read(r1, 7) != b"started"):
-                os._exit(1)
-            os.write(w2, b"add")
-            if (os.read(r1, 5) != b"added"):
-                os._exit(2)
-
-            # Wait 2 seconds to block prepare_commit() in the child.
-            os.write(w2, b"prepare")
-            time.sleep(2)
-
-            # Release the locks
-            for l in res:
-                pass
-
-            if (os.read(r1, 8) != b"prepared"):
-                os._exit(3)
-
-            os._exit(0)
-
-        # In the parent, close the main DB, re-open just one DB
-        del(self.samdb)
-        gc.collect()
-        backenddb = ldb.Ldb(backend_path)
-
-        # We can start the transaction during the search
-        # because both just grab the all-record read lock.
-        self.assertEqual(os.read(r2, 5), b"start")
-        backenddb.transaction_start()
-        os.write(w1, b"started")
-
-        self.assertEqual(os.read(r2, 3), b"add")
-        backenddb.add({"dn":"@DSDB_LOCK_TEST"})
-        backenddb.delete("@DSDB_LOCK_TEST")
-        os.write(w1, b"added")
-
-        # Obtain a write lock, this will block until
-        # the child releases the read lock.
-        self.assertEqual(os.read(r2, 7), b"prepare")
-        start = time.time()
-        backenddb.transaction_prepare_commit()
-        end = time.time()
-
-        try:
-            self.assertGreater(end - start, 1.9)
-        except:
-            raise
-        finally:
-            os.write(w1, b"prepared")
-
-            # Drop the write lock
-            backenddb.transaction_cancel()
-
-            (got_pid, status) = os.waitpid(pid, 0)
-            self.assertEqual(got_pid, pid)
-            self.assertTrue(os.WIFEXITED(status))
-            self.assertEqual(os.WEXITSTATUS(status), 0)
-
-    def test_full_db_lock2(self):
-        basedn = self.samdb.get_default_basedn()
-        backend_filename = "%s.ldb" % basedn.get_casefold()
-        backend_subpath = os.path.join("sam.ldb.d",
-                                       backend_filename)
-        backend_path = self.lp.private_path(backend_subpath)
-        self._test_full_db_lock2(backend_path)
-
-    def test_full_db_lock2_config(self):
-        basedn = self.samdb.get_config_basedn()
-        backend_filename = "%s.ldb" % basedn.get_casefold()
-        backend_subpath = os.path.join("sam.ldb.d",
-                                       backend_filename)
-        backend_path = self.lp.private_path(backend_subpath)
-        self._test_full_db_lock2(backend_path)
-
     def test_no_error_on_invalid_control(self):
         try:
             res = self.samdb.search(scope=ldb.SCOPE_SUBTREE,
diff --git a/python/samba/tests/dsdb_lock.py b/python/samba/tests/dsdb_lock.py
new file mode 100644
index 00000000000..0b152254984
--- /dev/null
+++ b/python/samba/tests/dsdb_lock.py
@@ -0,0 +1,379 @@
+# Unix SMB/CIFS implementation. Tests for SamDB
+# Copyright (C) Jelmer Vernooij <jelmer at samba.org> 2008
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+"""Tests for samba's dsdb modules"""
+
+from samba.tests.samdb import SamDBTestCase
+from samba.samdb import SamDB
+import ldb
+import os
+import samba
+import gc
+import time
+
+class DsdbLockTestCase(SamDBTestCase):
+    def test_db_lock1(self):
+        basedn = self.samdb.get_default_basedn()
+        (r1, w1) = os.pipe()
+
+        pid = os.fork()
+        if pid == 0:
+            # In the child, close the main DB, re-open just one DB
+            del(self.samdb)
+            gc.collect()
+            self.samdb = SamDB(session_info=self.session,
+                               lp=self.lp)
+
+            self.samdb.transaction_start()
+
+            dn = "cn=test_db_lock_user,cn=users," + str(basedn)
+            self.samdb.add({
+                 "dn": dn,
+                 "objectclass": "user",
+            })
+            self.samdb.delete(dn)
+
+            # Obtain a write lock
+            self.samdb.transaction_prepare_commit()
+            os.write(w1, b"prepared")
+            time.sleep(2)
+
+            # Drop the write lock
+            self.samdb.transaction_cancel()
+            os._exit(0)
+
+        self.assertEqual(os.read(r1, 8), b"prepared")
+
+        start = time.time()
+
+        # We need to hold this iterator open to hold the all-record lock.
+        res = self.samdb.search_iterator()
+
+        # This should take at least 2 seconds because the transaction
+        # has a write lock on one backend db open
+
+        # Release the locks
+        for l in res:
+            pass
+
+        end = time.time()
+        self.assertGreater(end - start, 1.9)
+
+        (got_pid, status) = os.waitpid(pid, 0)
+        self.assertEqual(got_pid, pid)
+        self.assertTrue(os.WIFEXITED(status))
+        self.assertEqual(os.WEXITSTATUS(status), 0)
+
+    def test_db_lock2(self):
+        basedn = self.samdb.get_default_basedn()
+        (r1, w1) = os.pipe()
+        (r2, w2) = os.pipe()
+
+        pid = os.fork()
+        if pid == 0:
+            # In the child, close the main DB, re-open
+            del(self.samdb)
+            gc.collect()
+            self.samdb = SamDB(session_info=self.session,
+                               lp=self.lp)
+
+            # We need to hold this iterator open to hold the all-record lock.
+            res = self.samdb.search_iterator()
+
+            os.write(w2, b"start")
+            if (os.read(r1, 7) != b"started"):
+                os._exit(1)
+
+            os.write(w2, b"add")
+            if (os.read(r1, 5) != b"added"):
+                os._exit(2)
+
+            # Wait 2 seconds to block prepare_commit() in the child.
+            os.write(w2, b"prepare")
+            time.sleep(2)
+
+            # Release the locks
+            for l in res:
+                pass
+
+            if (os.read(r1, 8) != b"prepared"):
+                os._exit(3)
+
+            os._exit(0)
+
+        # We can start the transaction during the search
+        # because both just grab the all-record read lock.
+        self.assertEqual(os.read(r2, 5), b"start")
+        self.samdb.transaction_start()
+        os.write(w1, b"started")
+
+        self.assertEqual(os.read(r2, 3), b"add")
+        dn = "cn=test_db_lock_user,cn=users," + str(basedn)
+        self.samdb.add({
+             "dn": dn,
+             "objectclass": "user",
+        })
+        self.samdb.delete(dn)
+        os.write(w1, b"added")
+
+        # Obtain a write lock, this will block until
+        # the parent releases the read lock.
+        self.assertEqual(os.read(r2, 7), b"prepare")
+        start = time.time()
+        self.samdb.transaction_prepare_commit()
+        end = time.time()
+        try:
+            self.assertGreater(end - start, 1.9)
+        except:
+            raise
+        finally:
+            os.write(w1, b"prepared")
+
+            # Drop the write lock
+            self.samdb.transaction_cancel()
+
+            (got_pid, status) = os.waitpid(pid, 0)
+            self.assertEqual(got_pid, pid)
+            self.assertTrue(os.WIFEXITED(status))
+            self.assertEqual(os.WEXITSTATUS(status), 0)
+
+    def test_db_lock3(self):
+        basedn = self.samdb.get_default_basedn()
+        (r1, w1) = os.pipe()
+        (r2, w2) = os.pipe()
+
+        pid = os.fork()
+        if pid == 0:
+            # In the child, close the main DB, re-open
+            del(self.samdb)
+            gc.collect()
+            self.samdb = SamDB(session_info=self.session,
+                               lp=self.lp)
+
+            # We need to hold this iterator open to hold the all-record lock.
+            res = self.samdb.search_iterator()
+
+            os.write(w2, b"start")
+            if (os.read(r1, 7) != b"started"):
+                os._exit(1)
+
+            os.write(w2, b"add")
+            if (os.read(r1, 5) != b"added"):
+                os._exit(2)
+
+            # Wait 2 seconds to block prepare_commit() in the child.
+            os.write(w2, b"prepare")
+            time.sleep(2)
+
+            # Release the locks
+            for l in res:
+                pass
+
+            if (os.read(r1, 8) != b"prepared"):
+                os._exit(3)
+
+            os._exit(0)
+
+        # We can start the transaction during the search
+        # because both just grab the all-record read lock.
+        self.assertEqual(os.read(r2, 5), b"start")
+        self.samdb.transaction_start()
+        os.write(w1, b"started")
+
+        self.assertEqual(os.read(r2, 3), b"add")
+
+        # This will end up in the top level db
+        dn = "@DSDB_LOCK_TEST"
+        self.samdb.add({
+             "dn": dn})
+        self.samdb.delete(dn)
+        os.write(w1, b"added")
+
+        # Obtain a write lock, this will block until
+        # the child releases the read lock.
+        self.assertEqual(os.read(r2, 7), b"prepare")
+        start = time.time()
+        self.samdb.transaction_prepare_commit()
+        end = time.time()
+        self.assertGreater(end - start, 1.9)
+        os.write(w1, b"prepared")
+
+        # Drop the write lock
+        self.samdb.transaction_cancel()
+
+        (got_pid, status) = os.waitpid(pid, 0)
+        self.assertTrue(os.WIFEXITED(status))
+        self.assertEqual(os.WEXITSTATUS(status), 0)
+        self.assertEqual(got_pid, pid)
+
+
+    def _test_full_db_lock1(self, backend_path):
+        (r1, w1) = os.pipe()
+
+        pid = os.fork()
+        if pid == 0:
+            # In the child, close the main DB, re-open just one DB
+            del(self.samdb)
+            gc.collect()
+
+            backenddb = ldb.Ldb(backend_path)
+
+
+            backenddb.transaction_start()
+
+            backenddb.add({"dn":"@DSDB_LOCK_TEST"})
+            backenddb.delete("@DSDB_LOCK_TEST")
+
+            # Obtain a write lock
+            backenddb.transaction_prepare_commit()
+            os.write(w1, b"prepared")
+            time.sleep(2)
+
+            # Drop the write lock
+            backenddb.transaction_cancel()
+            os._exit(0)
+
+        self.assertEqual(os.read(r1, 8), b"prepared")
+
+        start = time.time()
+
+        # We need to hold this iterator open to hold the all-record lock.
+        res = self.samdb.search_iterator()
+
+        # This should take at least 2 seconds because the transaction
+        # has a write lock on one backend db open
+
+        end = time.time()
+        self.assertGreater(end - start, 1.9)
+
+        # Release the locks
+        for l in res:
+            pass
+
+        (got_pid, status) = os.waitpid(pid, 0)
+        self.assertEqual(got_pid, pid)
+        self.assertTrue(os.WIFEXITED(status))
+        self.assertEqual(os.WEXITSTATUS(status), 0)
+
+    def test_full_db_lock1(self):
+        basedn = self.samdb.get_default_basedn()
+        backend_filename = "%s.ldb" % basedn.get_casefold()
+        backend_subpath = os.path.join("sam.ldb.d",
+                                       backend_filename)
+        backend_path = self.lp.private_path(backend_subpath)
+        self._test_full_db_lock1(backend_path)
+
+
+    def test_full_db_lock1_config(self):
+        basedn = self.samdb.get_config_basedn()
+        backend_filename = "%s.ldb" % basedn.get_casefold()
+        backend_subpath = os.path.join("sam.ldb.d",
+                                       backend_filename)
+        backend_path = self.lp.private_path(backend_subpath)
+        self._test_full_db_lock1(backend_path)
+
+
+    def _test_full_db_lock2(self, backend_path):
+        (r1, w1) = os.pipe()
+        (r2, w2) = os.pipe()
+
+        pid = os.fork()
+        if pid == 0:
+
+            # In the child, close the main DB, re-open
+            del(self.samdb)
+            gc.collect()
+            self.samdb = SamDB(session_info=self.session,
+                               lp=self.lp)
+
+            # We need to hold this iterator open to hold the all-record lock.
+            res = self.samdb.search_iterator()
+
+            os.write(w2, b"start")
+            if (os.read(r1, 7) != b"started"):
+                os._exit(1)
+            os.write(w2, b"add")
+            if (os.read(r1, 5) != b"added"):
+                os._exit(2)
+
+            # Wait 2 seconds to block prepare_commit() in the child.
+            os.write(w2, b"prepare")
+            time.sleep(2)
+
+            # Release the locks
+            for l in res:
+                pass
+
+            if (os.read(r1, 8) != b"prepared"):
+                os._exit(3)
+
+            os._exit(0)
+
+        # In the parent, close the main DB, re-open just one DB
+        del(self.samdb)
+        gc.collect()
+        backenddb = ldb.Ldb(backend_path)
+
+        # We can start the transaction during the search
+        # because both just grab the all-record read lock.
+        self.assertEqual(os.read(r2, 5), b"start")
+        backenddb.transaction_start()
+        os.write(w1, b"started")
+
+        self.assertEqual(os.read(r2, 3), b"add")
+        backenddb.add({"dn":"@DSDB_LOCK_TEST"})
+        backenddb.delete("@DSDB_LOCK_TEST")
+        os.write(w1, b"added")
+
+        # Obtain a write lock, this will block until
+        # the child releases the read lock.
+        self.assertEqual(os.read(r2, 7), b"prepare")
+        start = time.time()
+        backenddb.transaction_prepare_commit()
+        end = time.time()
+
+        try:
+            self.assertGreater(end - start, 1.9)
+        except:
+            raise
+        finally:
+            os.write(w1, b"prepared")
+
+            # Drop the write lock
+            backenddb.transaction_cancel()
+
+            (got_pid, status) = os.waitpid(pid, 0)
+            self.assertEqual(got_pid, pid)
+            self.assertTrue(os.WIFEXITED(status))
+            self.assertEqual(os.WEXITSTATUS(status), 0)
+
+    def test_full_db_lock2(self):
+        basedn = self.samdb.get_default_basedn()
+        backend_filename = "%s.ldb" % basedn.get_casefold()
+        backend_subpath = os.path.join("sam.ldb.d",
+                                       backend_filename)
+        backend_path = self.lp.private_path(backend_subpath)
+        self._test_full_db_lock2(backend_path)
+
+    def test_full_db_lock2_config(self):
+        basedn = self.samdb.get_config_basedn()
+        backend_filename = "%s.ldb" % basedn.get_casefold()
+        backend_subpath = os.path.join("sam.ldb.d",
+                                       backend_filename)
+        backend_path = self.lp.private_path(backend_subpath)
+        self._test_full_db_lock2(backend_path)
+
diff --git a/python/samba/tests/samdb.py b/python/samba/tests/samdb.py
index 5c80391cbae..a19a0dc0892 100644
--- a/python/samba/tests/samdb.py
+++ b/python/samba/tests/samdb.py
@@ -20,16 +20,12 @@
 import logging
 import os
 import uuid
+import shutil
 
 from samba.auth import system_session
-from samba.provision import (setup_samdb, guess_names, make_smbconf,
-    provision_paths_from_lp)
-from samba.provision import DEFAULT_POLICY_GUID, DEFAULT_DC_POLICY_GUID
-from samba.provision.backend import ProvisionBackend
+from samba.provision import provision
 from samba.tests import TestCaseInTempDir
-from samba.dcerpc import security
-from samba.schema import Schema
-from samba import param
+from samba.dsdb import DS_DOMAIN_FUNCTION_2008_R2
 
 
 class SamDBTestCase(TestCaseInTempDir):
@@ -41,56 +37,29 @@ class SamDBTestCase(TestCaseInTempDir):
 
     def setUp(self):
         super(SamDBTestCase, self).setUp()
-        invocationid = str(uuid.uuid4())
-        domaindn = "DC=COM,DC=EXAMPLE"
-        self.domaindn = domaindn
-        configdn = "CN=Configuration," + domaindn
-        schemadn = "CN=Schema," + configdn
-        domainguid = str(uuid.uuid4())
-        policyguid = DEFAULT_POLICY_GUID
-        domainsid = security.random_sid()
-        path = os.path.join(self.tempdir, "samdb.ldb")
-        session_info = system_session()
-
-        hostname="foo"
-        domain="EXAMPLE"
-        dnsdomain="example.com"
-        serverrole="domain controller"
-        policyguid_dc = DEFAULT_DC_POLICY_GUID
-
-        smbconf = os.path.join(self.tempdir, "smb.conf")
-        make_smbconf(smbconf, hostname, domain, dnsdomain,
-                     self.tempdir, serverrole=serverrole)
-
-        self.lp = param.LoadParm()
-        self.lp.load(smbconf)
-
-        names = guess_names(lp=self.lp, hostname=hostname,
-                            domain=domain, dnsdomain=dnsdomain,
-                            serverrole=serverrole,
-                            domaindn=self.domaindn, configdn=configdn,
-                            schemadn=schemadn)
-
-        paths = provision_paths_from_lp(self.lp, names.dnsdomain)
-
-        logger = logging.getLogger("provision")
-
-        provision_backend = ProvisionBackend("ldb", paths=paths,
-                lp=self.lp, credentials=None,
-                names=names, logger=logger)
-
-        schema = Schema(domainsid, invocationid=invocationid,
-                schemadn=names.schemadn, serverdn=names.serverdn,
-                am_rodc=False)
-
-        self.samdb = setup_samdb(path, session_info,
-                provision_backend, self.lp, names, logger,
-                domainsid, domainguid, policyguid, policyguid_dc, False,
-                "secret", "secret", "secret", invocationid, "secret",
-                None, "domain controller", schema=schema)
-
+        self.session = system_session()
+        logger = logging.getLogger("selftest")
+        domain = "dsdb"
+        realm = "dsdb.samba.example.com"
+        host_name = "test"
+        server_role = "active directory domain controller"
+        dns_backend = "SAMBA_INTERNAL"
+        self.result = provision(logger,
+                                self.session, targetdir=self.tempdir,
+                                realm=realm, domain=domain,
+                                hostname=host_name,
+                                use_ntvfs=True,
+                                serverrole=server_role,
+                                dns_backend="SAMBA_INTERNAL",
+                                dom_for_fun_level=DS_DOMAIN_FUNCTION_2008_R2)
+        self.samdb = self.result.samdb
+        self.lp = self.result.lp
+        
     def tearDown(self):
-        for f in ['schema.ldb', 'configuration.ldb',
-                  'users.ldb', 'samdb.ldb', 'smb.conf']:
+        for f in ['names.tdb']:
             os.remove(os.path.join(self.tempdir, f))
+                      
+        for d in ['etc', 'msg.lock', 'private', 'state']:
+            shutil.rmtree(os.path.join(self.tempdir, d))
+            
         super(SamDBTestCase, self).tearDown()
diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index 8d3d5261266..c132ac90b54 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -580,6 +580,7 @@ planoldpythontestsuite("ad_dc_ntvfs:local", "samba.tests.gensec", extra_args=['-
 planoldpythontestsuite("none", "simple", extra_path=["%s/lib/tdb/python/tests" % srcdir()], name="tdb.python")
 planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.dcerpc.sam")
 planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.dsdb")
+planpythontestsuite("none", "samba.tests.dsdb_lock")
 planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.dcerpc.bare")
 planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.dcerpc.unix")
 planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.dcerpc.srvsvc")
-- 
2.11.0



More information about the samba-technical mailing list