1 # Unix SMB/CIFS implementation. Tests for DSDB locking
2 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 """Tests for samba's dsdb modules"""
20 from samba.tests.samdb import SamDBTestCase
21 from samba.samdb import SamDB
29 class DsdbLockTestCase(SamDBTestCase):
30 def test_db_lock1(self):
31 basedn = self.samdb.get_default_basedn()
36 # In the child, close the main DB, re-open just one DB
39 self.samdb = SamDB(session_info=self.session,
42 self.samdb.transaction_start()
44 dn = "cn=test_db_lock_user,cn=users," + str(basedn)
47 "objectclass": "user",
52 self.samdb.transaction_prepare_commit()
53 os.write(w1, b"prepared")
57 self.samdb.transaction_cancel()
60 self.assertEqual(os.read(r1, 8), b"prepared")
64 # We need to hold this iterator open to hold the all-record lock.
65 res = self.samdb.search_iterator()
67 # This should take at least 2 seconds because the transaction
68 # has a write lock on one backend db open
75 self.assertGreater(end - start, 1.9)
77 (got_pid, status) = os.waitpid(pid, 0)
78 self.assertEqual(got_pid, pid)
79 self.assertTrue(os.WIFEXITED(status))
80 self.assertEqual(os.WEXITSTATUS(status), 0)
82 def test_db_lock2(self):
83 basedn = self.samdb.get_default_basedn()
89 # In the child, close the main DB, re-open
92 self.samdb = SamDB(session_info=self.session,
95 # We need to hold this iterator open to hold the all-record lock.
96 res = self.samdb.search_iterator()
98 os.write(w2, b"start")
99 if (os.read(r1, 7) != b"started"):
103 if (os.read(r1, 5) != b"added"):
106 # Wait 2 seconds to block prepare_commit() in the child.
107 os.write(w2, b"prepare")
114 if (os.read(r1, 8) != b"prepared"):
119 # We can start the transaction during the search
120 # because both just grab the all-record read lock.
121 self.assertEqual(os.read(r2, 5), b"start")
122 self.samdb.transaction_start()
123 os.write(w1, b"started")
125 self.assertEqual(os.read(r2, 3), b"add")
126 dn = "cn=test_db_lock_user,cn=users," + str(basedn)
129 "objectclass": "user",
131 self.samdb.delete(dn)
132 os.write(w1, b"added")
134 # Obtain a write lock, this will block until
135 # the parent releases the read lock.
136 self.assertEqual(os.read(r2, 7), b"prepare")
138 self.samdb.transaction_prepare_commit()
141 self.assertGreater(end - start, 1.9)
145 os.write(w1, b"prepared")
147 # Drop the write lock
148 self.samdb.transaction_cancel()
150 (got_pid, status) = os.waitpid(pid, 0)
151 self.assertEqual(got_pid, pid)
152 self.assertTrue(os.WIFEXITED(status))
153 self.assertEqual(os.WEXITSTATUS(status), 0)
155 def test_db_lock3(self):
156 basedn = self.samdb.get_default_basedn()
162 # In the child, close the main DB, re-open
165 self.samdb = SamDB(session_info=self.session,
168 # We need to hold this iterator open to hold the all-record lock.
169 res = self.samdb.search_iterator()
171 os.write(w2, b"start")
172 if (os.read(r1, 7) != b"started"):
176 if (os.read(r1, 5) != b"added"):
179 # Wait 2 seconds to block prepare_commit() in the child.
180 os.write(w2, b"prepare")
187 if (os.read(r1, 8) != b"prepared"):
192 # We can start the transaction during the search
193 # because both just grab the all-record read lock.
194 self.assertEqual(os.read(r2, 5), b"start")
195 self.samdb.transaction_start()
196 os.write(w1, b"started")
198 self.assertEqual(os.read(r2, 3), b"add")
200 # This will end up in the top level db
201 dn = "@DSDB_LOCK_TEST"
204 self.samdb.delete(dn)
205 os.write(w1, b"added")
207 # Obtain a write lock, this will block until
208 # the child releases the read lock.
209 self.assertEqual(os.read(r2, 7), b"prepare")
211 self.samdb.transaction_prepare_commit()
213 self.assertGreater(end - start, 1.9)
214 os.write(w1, b"prepared")
216 # Drop the write lock
217 self.samdb.transaction_cancel()
219 (got_pid, status) = os.waitpid(pid, 0)
220 self.assertTrue(os.WIFEXITED(status))
221 self.assertEqual(os.WEXITSTATUS(status), 0)
222 self.assertEqual(got_pid, pid)
224 def _test_full_db_lock1(self, backend_path):
229 # In the child, close the main DB, re-open just one DB
233 backenddb = ldb.Ldb(backend_path)
235 backenddb.transaction_start()
237 backenddb.add({"dn": "@DSDB_LOCK_TEST"})
238 backenddb.delete("@DSDB_LOCK_TEST")
240 # Obtain a write lock
241 backenddb.transaction_prepare_commit()
242 os.write(w1, b"prepared")
245 # Drop the write lock
246 backenddb.transaction_cancel()
249 self.assertEqual(os.read(r1, 8), b"prepared")
253 # We need to hold this iterator open to hold the all-record lock.
254 res = self.samdb.search_iterator()
256 # This should take at least 2 seconds because the transaction
257 # has a write lock on one backend db open
260 self.assertGreater(end - start, 1.9)
266 (got_pid, status) = os.waitpid(pid, 0)
267 self.assertEqual(got_pid, pid)
268 self.assertTrue(os.WIFEXITED(status))
269 self.assertEqual(os.WEXITSTATUS(status), 0)
271 def test_full_db_lock1(self):
272 basedn = self.samdb.get_default_basedn()
273 backend_filename = "%s.ldb" % basedn.get_casefold()
274 backend_subpath = os.path.join("sam.ldb.d",
276 backend_path = self.lp.private_path(backend_subpath)
277 self._test_full_db_lock1(backend_path)
279 def test_full_db_lock1_config(self):
280 basedn = self.samdb.get_config_basedn()
281 backend_filename = "%s.ldb" % basedn.get_casefold()
282 backend_subpath = os.path.join("sam.ldb.d",
284 backend_path = self.lp.private_path(backend_subpath)
285 self._test_full_db_lock1(backend_path)
287 def _test_full_db_lock2(self, backend_path):
294 # In the child, close the main DB, re-open
297 self.samdb = SamDB(session_info=self.session,
300 # We need to hold this iterator open to hold the all-record lock.
301 res = self.samdb.search_iterator()
303 os.write(w2, b"start")
304 if (os.read(r1, 7) != b"started"):
307 if (os.read(r1, 5) != b"added"):
310 # Wait 2 seconds to block prepare_commit() in the child.
311 os.write(w2, b"prepare")
318 if (os.read(r1, 8) != b"prepared"):
323 # In the parent, close the main DB, re-open just one DB
326 backenddb = ldb.Ldb(backend_path)
328 # We can start the transaction during the search
329 # because both just grab the all-record read lock.
330 self.assertEqual(os.read(r2, 5), b"start")
331 backenddb.transaction_start()
332 os.write(w1, b"started")
334 self.assertEqual(os.read(r2, 3), b"add")
335 backenddb.add({"dn": "@DSDB_LOCK_TEST"})
336 backenddb.delete("@DSDB_LOCK_TEST")
337 os.write(w1, b"added")
339 # Obtain a write lock, this will block until
340 # the child releases the read lock.
341 self.assertEqual(os.read(r2, 7), b"prepare")
343 backenddb.transaction_prepare_commit()
347 self.assertGreater(end - start, 1.9)
351 os.write(w1, b"prepared")
353 # Drop the write lock
354 backenddb.transaction_cancel()
356 (got_pid, status) = os.waitpid(pid, 0)
357 self.assertEqual(got_pid, pid)
358 self.assertTrue(os.WIFEXITED(status))
359 self.assertEqual(os.WEXITSTATUS(status), 0)
361 def test_full_db_lock2(self):
362 basedn = self.samdb.get_default_basedn()
363 backend_filename = "%s.ldb" % basedn.get_casefold()
364 backend_subpath = os.path.join("sam.ldb.d",
366 backend_path = self.lp.private_path(backend_subpath)
367 self._test_full_db_lock2(backend_path)
369 def test_full_db_lock2_config(self):
370 basedn = self.samdb.get_config_basedn()
371 backend_filename = "%s.ldb" % basedn.get_casefold()
372 backend_subpath = os.path.join("sam.ldb.d",
374 backend_path = self.lp.private_path(backend_subpath)
375 self._test_full_db_lock2(backend_path)