selftest: Rework samba.dsdb locking test to samba.dsdb_lock
[Samba.git] / python / samba / tests / dsdb_lock.py
blob9cc93aa7bb8651f050644836fe22bbe261e104d7
1 # Unix SMB/CIFS implementation. Tests for SamDB
2 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008
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
22 import ldb
23 import os
24 import samba
25 import gc
26 import time
28 class DsdbLockTestCase(SamDBTestCase):
29 def test_db_lock1(self):
30 basedn = self.samdb.get_default_basedn()
31 (r1, w1) = os.pipe()
33 pid = os.fork()
34 if pid == 0:
35 # In the child, close the main DB, re-open just one DB
36 del(self.samdb)
37 gc.collect()
38 self.samdb = SamDB(session_info=self.session,
39 lp=self.lp)
41 self.samdb.transaction_start()
43 dn = "cn=test_db_lock_user,cn=users," + str(basedn)
44 self.samdb.add({
45 "dn": dn,
46 "objectclass": "user",
48 self.samdb.delete(dn)
50 # Obtain a write lock
51 self.samdb.transaction_prepare_commit()
52 os.write(w1, b"prepared")
53 time.sleep(2)
55 # Drop the write lock
56 self.samdb.transaction_cancel()
57 os._exit(0)
59 self.assertEqual(os.read(r1, 8), b"prepared")
61 start = time.time()
63 # We need to hold this iterator open to hold the all-record lock.
64 res = self.samdb.search_iterator()
66 # This should take at least 2 seconds because the transaction
67 # has a write lock on one backend db open
69 # Release the locks
70 for l in res:
71 pass
73 end = time.time()
74 self.assertGreater(end - start, 1.9)
76 (got_pid, status) = os.waitpid(pid, 0)
77 self.assertEqual(got_pid, pid)
78 self.assertTrue(os.WIFEXITED(status))
79 self.assertEqual(os.WEXITSTATUS(status), 0)
81 def test_db_lock2(self):
82 basedn = self.samdb.get_default_basedn()
83 (r1, w1) = os.pipe()
84 (r2, w2) = os.pipe()
86 pid = os.fork()
87 if pid == 0:
88 # In the child, close the main DB, re-open
89 del(self.samdb)
90 gc.collect()
91 self.samdb = SamDB(session_info=self.session,
92 lp=self.lp)
94 # We need to hold this iterator open to hold the all-record lock.
95 res = self.samdb.search_iterator()
97 os.write(w2, b"start")
98 if (os.read(r1, 7) != b"started"):
99 os._exit(1)
101 os.write(w2, b"add")
102 if (os.read(r1, 5) != b"added"):
103 os._exit(2)
105 # Wait 2 seconds to block prepare_commit() in the child.
106 os.write(w2, b"prepare")
107 time.sleep(2)
109 # Release the locks
110 for l in res:
111 pass
113 if (os.read(r1, 8) != b"prepared"):
114 os._exit(3)
116 os._exit(0)
118 # We can start the transaction during the search
119 # because both just grab the all-record read lock.
120 self.assertEqual(os.read(r2, 5), b"start")
121 self.samdb.transaction_start()
122 os.write(w1, b"started")
124 self.assertEqual(os.read(r2, 3), b"add")
125 dn = "cn=test_db_lock_user,cn=users," + str(basedn)
126 self.samdb.add({
127 "dn": dn,
128 "objectclass": "user",
130 self.samdb.delete(dn)
131 os.write(w1, b"added")
133 # Obtain a write lock, this will block until
134 # the parent releases the read lock.
135 self.assertEqual(os.read(r2, 7), b"prepare")
136 start = time.time()
137 self.samdb.transaction_prepare_commit()
138 end = time.time()
139 try:
140 self.assertGreater(end - start, 1.9)
141 except:
142 raise
143 finally:
144 os.write(w1, b"prepared")
146 # Drop the write lock
147 self.samdb.transaction_cancel()
149 (got_pid, status) = os.waitpid(pid, 0)
150 self.assertEqual(got_pid, pid)
151 self.assertTrue(os.WIFEXITED(status))
152 self.assertEqual(os.WEXITSTATUS(status), 0)
154 def test_db_lock3(self):
155 basedn = self.samdb.get_default_basedn()
156 (r1, w1) = os.pipe()
157 (r2, w2) = os.pipe()
159 pid = os.fork()
160 if pid == 0:
161 # In the child, close the main DB, re-open
162 del(self.samdb)
163 gc.collect()
164 self.samdb = SamDB(session_info=self.session,
165 lp=self.lp)
167 # We need to hold this iterator open to hold the all-record lock.
168 res = self.samdb.search_iterator()
170 os.write(w2, b"start")
171 if (os.read(r1, 7) != b"started"):
172 os._exit(1)
174 os.write(w2, b"add")
175 if (os.read(r1, 5) != b"added"):
176 os._exit(2)
178 # Wait 2 seconds to block prepare_commit() in the child.
179 os.write(w2, b"prepare")
180 time.sleep(2)
182 # Release the locks
183 for l in res:
184 pass
186 if (os.read(r1, 8) != b"prepared"):
187 os._exit(3)
189 os._exit(0)
191 # We can start the transaction during the search
192 # because both just grab the all-record read lock.
193 self.assertEqual(os.read(r2, 5), b"start")
194 self.samdb.transaction_start()
195 os.write(w1, b"started")
197 self.assertEqual(os.read(r2, 3), b"add")
199 # This will end up in the top level db
200 dn = "@DSDB_LOCK_TEST"
201 self.samdb.add({
202 "dn": dn})
203 self.samdb.delete(dn)
204 os.write(w1, b"added")
206 # Obtain a write lock, this will block until
207 # the child releases the read lock.
208 self.assertEqual(os.read(r2, 7), b"prepare")
209 start = time.time()
210 self.samdb.transaction_prepare_commit()
211 end = time.time()
212 self.assertGreater(end - start, 1.9)
213 os.write(w1, b"prepared")
215 # Drop the write lock
216 self.samdb.transaction_cancel()
218 (got_pid, status) = os.waitpid(pid, 0)
219 self.assertTrue(os.WIFEXITED(status))
220 self.assertEqual(os.WEXITSTATUS(status), 0)
221 self.assertEqual(got_pid, pid)
224 def _test_full_db_lock1(self, backend_path):
225 (r1, w1) = os.pipe()
227 pid = os.fork()
228 if pid == 0:
229 # In the child, close the main DB, re-open just one DB
230 del(self.samdb)
231 gc.collect()
233 backenddb = ldb.Ldb(backend_path)
236 backenddb.transaction_start()
238 backenddb.add({"dn":"@DSDB_LOCK_TEST"})
239 backenddb.delete("@DSDB_LOCK_TEST")
241 # Obtain a write lock
242 backenddb.transaction_prepare_commit()
243 os.write(w1, b"prepared")
244 time.sleep(2)
246 # Drop the write lock
247 backenddb.transaction_cancel()
248 os._exit(0)
250 self.assertEqual(os.read(r1, 8), b"prepared")
252 start = time.time()
254 # We need to hold this iterator open to hold the all-record lock.
255 res = self.samdb.search_iterator()
257 # This should take at least 2 seconds because the transaction
258 # has a write lock on one backend db open
260 end = time.time()
261 self.assertGreater(end - start, 1.9)
263 # Release the locks
264 for l in res:
265 pass
267 (got_pid, status) = os.waitpid(pid, 0)
268 self.assertEqual(got_pid, pid)
269 self.assertTrue(os.WIFEXITED(status))
270 self.assertEqual(os.WEXITSTATUS(status), 0)
272 def test_full_db_lock1(self):
273 basedn = self.samdb.get_default_basedn()
274 backend_filename = "%s.ldb" % basedn.get_casefold()
275 backend_subpath = os.path.join("sam.ldb.d",
276 backend_filename)
277 backend_path = self.lp.private_path(backend_subpath)
278 self._test_full_db_lock1(backend_path)
281 def test_full_db_lock1_config(self):
282 basedn = self.samdb.get_config_basedn()
283 backend_filename = "%s.ldb" % basedn.get_casefold()
284 backend_subpath = os.path.join("sam.ldb.d",
285 backend_filename)
286 backend_path = self.lp.private_path(backend_subpath)
287 self._test_full_db_lock1(backend_path)
290 def _test_full_db_lock2(self, backend_path):
291 (r1, w1) = os.pipe()
292 (r2, w2) = os.pipe()
294 pid = os.fork()
295 if pid == 0:
297 # In the child, close the main DB, re-open
298 del(self.samdb)
299 gc.collect()
300 self.samdb = SamDB(session_info=self.session,
301 lp=self.lp)
303 # We need to hold this iterator open to hold the all-record lock.
304 res = self.samdb.search_iterator()
306 os.write(w2, b"start")
307 if (os.read(r1, 7) != b"started"):
308 os._exit(1)
309 os.write(w2, b"add")
310 if (os.read(r1, 5) != b"added"):
311 os._exit(2)
313 # Wait 2 seconds to block prepare_commit() in the child.
314 os.write(w2, b"prepare")
315 time.sleep(2)
317 # Release the locks
318 for l in res:
319 pass
321 if (os.read(r1, 8) != b"prepared"):
322 os._exit(3)
324 os._exit(0)
326 # In the parent, close the main DB, re-open just one DB
327 del(self.samdb)
328 gc.collect()
329 backenddb = ldb.Ldb(backend_path)
331 # We can start the transaction during the search
332 # because both just grab the all-record read lock.
333 self.assertEqual(os.read(r2, 5), b"start")
334 backenddb.transaction_start()
335 os.write(w1, b"started")
337 self.assertEqual(os.read(r2, 3), b"add")
338 backenddb.add({"dn":"@DSDB_LOCK_TEST"})
339 backenddb.delete("@DSDB_LOCK_TEST")
340 os.write(w1, b"added")
342 # Obtain a write lock, this will block until
343 # the child releases the read lock.
344 self.assertEqual(os.read(r2, 7), b"prepare")
345 start = time.time()
346 backenddb.transaction_prepare_commit()
347 end = time.time()
349 try:
350 self.assertGreater(end - start, 1.9)
351 except:
352 raise
353 finally:
354 os.write(w1, b"prepared")
356 # Drop the write lock
357 backenddb.transaction_cancel()
359 (got_pid, status) = os.waitpid(pid, 0)
360 self.assertEqual(got_pid, pid)
361 self.assertTrue(os.WIFEXITED(status))
362 self.assertEqual(os.WEXITSTATUS(status), 0)
364 def test_full_db_lock2(self):
365 basedn = self.samdb.get_default_basedn()
366 backend_filename = "%s.ldb" % basedn.get_casefold()
367 backend_subpath = os.path.join("sam.ldb.d",
368 backend_filename)
369 backend_path = self.lp.private_path(backend_subpath)
370 self._test_full_db_lock2(backend_path)
372 def test_full_db_lock2_config(self):
373 basedn = self.samdb.get_config_basedn()
374 backend_filename = "%s.ldb" % basedn.get_casefold()
375 backend_subpath = os.path.join("sam.ldb.d",
376 backend_filename)
377 backend_path = self.lp.private_path(backend_subpath)
378 self._test_full_db_lock2(backend_path)