s3:utils: Fix 'Usage:' for 'net ads enctypes'
[samba4-gss.git] / python / samba / tests / dsdb_lock.py
blob628be9c9726842d18ee4aa0dcb7546b7332ab3f0
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
22 import ldb
23 import os
24 import gc
25 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)
223 def _test_full_db_lock1(self, backend_path):
224 (r1, w1) = os.pipe()
226 pid = os.fork()
227 if pid == 0:
228 # In the child, close the main DB, re-open just one DB
229 del(self.samdb)
230 gc.collect()
232 backenddb = ldb.Ldb(backend_path)
234 backenddb.transaction_start()
236 backenddb.add({"dn": "@DSDB_LOCK_TEST"})
237 backenddb.delete("@DSDB_LOCK_TEST")
239 # Obtain a write lock
240 backenddb.transaction_prepare_commit()
241 os.write(w1, b"prepared")
242 time.sleep(2)
244 # Drop the write lock
245 backenddb.transaction_cancel()
246 os._exit(0)
248 self.assertEqual(os.read(r1, 8), b"prepared")
250 start = time.time()
252 # We need to hold this iterator open to hold the all-record lock.
253 res = self.samdb.search_iterator()
255 # This should take at least 2 seconds because the transaction
256 # has a write lock on one backend db open
258 end = time.time()
259 self.assertGreater(end - start, 1.9)
261 # Release the locks
262 for l in res:
263 pass
265 (got_pid, status) = os.waitpid(pid, 0)
266 self.assertEqual(got_pid, pid)
267 self.assertTrue(os.WIFEXITED(status))
268 self.assertEqual(os.WEXITSTATUS(status), 0)
270 def test_full_db_lock1(self):
271 basedn = self.samdb.get_default_basedn()
272 backend_filename = "%s.ldb" % basedn.get_casefold()
273 backend_subpath = os.path.join("sam.ldb.d",
274 backend_filename)
275 backend_path = self.lp.private_path(backend_subpath)
276 self._test_full_db_lock1(backend_path)
278 def test_full_db_lock1_config(self):
279 basedn = self.samdb.get_config_basedn()
280 backend_filename = "%s.ldb" % basedn.get_casefold()
281 backend_subpath = os.path.join("sam.ldb.d",
282 backend_filename)
283 backend_path = self.lp.private_path(backend_subpath)
284 self._test_full_db_lock1(backend_path)
286 def _test_full_db_lock2(self, backend_path):
287 (r1, w1) = os.pipe()
288 (r2, w2) = os.pipe()
290 pid = os.fork()
291 if pid == 0:
293 # In the child, close the main DB, re-open
294 del(self.samdb)
295 gc.collect()
296 self.samdb = SamDB(session_info=self.session,
297 lp=self.lp)
299 # We need to hold this iterator open to hold the all-record lock.
300 res = self.samdb.search_iterator()
302 os.write(w2, b"start")
303 if (os.read(r1, 7) != b"started"):
304 os._exit(1)
305 os.write(w2, b"add")
306 if (os.read(r1, 5) != b"added"):
307 os._exit(2)
309 # Wait 2 seconds to block prepare_commit() in the child.
310 os.write(w2, b"prepare")
311 time.sleep(2)
313 # Release the locks
314 for l in res:
315 pass
317 if (os.read(r1, 8) != b"prepared"):
318 os._exit(3)
320 os._exit(0)
322 # In the parent, close the main DB, re-open just one DB
323 del(self.samdb)
324 gc.collect()
325 backenddb = ldb.Ldb(backend_path)
327 # We can start the transaction during the search
328 # because both just grab the all-record read lock.
329 self.assertEqual(os.read(r2, 5), b"start")
330 backenddb.transaction_start()
331 os.write(w1, b"started")
333 self.assertEqual(os.read(r2, 3), b"add")
334 backenddb.add({"dn": "@DSDB_LOCK_TEST"})
335 backenddb.delete("@DSDB_LOCK_TEST")
336 os.write(w1, b"added")
338 # Obtain a write lock, this will block until
339 # the child releases the read lock.
340 self.assertEqual(os.read(r2, 7), b"prepare")
341 start = time.time()
342 backenddb.transaction_prepare_commit()
343 end = time.time()
345 try:
346 self.assertGreater(end - start, 1.9)
347 except:
348 raise
349 finally:
350 os.write(w1, b"prepared")
352 # Drop the write lock
353 backenddb.transaction_cancel()
355 (got_pid, status) = os.waitpid(pid, 0)
356 self.assertEqual(got_pid, pid)
357 self.assertTrue(os.WIFEXITED(status))
358 self.assertEqual(os.WEXITSTATUS(status), 0)
360 def test_full_db_lock2(self):
361 basedn = self.samdb.get_default_basedn()
362 backend_filename = "%s.ldb" % basedn.get_casefold()
363 backend_subpath = os.path.join("sam.ldb.d",
364 backend_filename)
365 backend_path = self.lp.private_path(backend_subpath)
366 self._test_full_db_lock2(backend_path)
368 def test_full_db_lock2_config(self):
369 basedn = self.samdb.get_config_basedn()
370 backend_filename = "%s.ldb" % basedn.get_casefold()
371 backend_subpath = os.path.join("sam.ldb.d",
372 backend_filename)
373 backend_path = self.lp.private_path(backend_subpath)
374 self._test_full_db_lock2(backend_path)