libsmb: Use clistr_smb2_extract_snapshot_token() in cli_smb2_create_fnum_send()
[Samba.git] / python / samba / tests / dsdb_lock.py
blob3f7cc9b5ae58f77d8dd7b41cc881e8ea5372c17e
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 samba
25 import gc
26 import time
29 class DsdbLockTestCase(SamDBTestCase):
30 def test_db_lock1(self):
31 basedn = self.samdb.get_default_basedn()
32 (r1, w1) = os.pipe()
34 pid = os.fork()
35 if pid == 0:
36 # In the child, close the main DB, re-open just one DB
37 del(self.samdb)
38 gc.collect()
39 self.samdb = SamDB(session_info=self.session,
40 lp=self.lp)
42 self.samdb.transaction_start()
44 dn = "cn=test_db_lock_user,cn=users," + str(basedn)
45 self.samdb.add({
46 "dn": dn,
47 "objectclass": "user",
49 self.samdb.delete(dn)
51 # Obtain a write lock
52 self.samdb.transaction_prepare_commit()
53 os.write(w1, b"prepared")
54 time.sleep(2)
56 # Drop the write lock
57 self.samdb.transaction_cancel()
58 os._exit(0)
60 self.assertEqual(os.read(r1, 8), b"prepared")
62 start = time.time()
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
70 # Release the locks
71 for l in res:
72 pass
74 end = time.time()
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()
84 (r1, w1) = os.pipe()
85 (r2, w2) = os.pipe()
87 pid = os.fork()
88 if pid == 0:
89 # In the child, close the main DB, re-open
90 del(self.samdb)
91 gc.collect()
92 self.samdb = SamDB(session_info=self.session,
93 lp=self.lp)
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"):
100 os._exit(1)
102 os.write(w2, b"add")
103 if (os.read(r1, 5) != b"added"):
104 os._exit(2)
106 # Wait 2 seconds to block prepare_commit() in the child.
107 os.write(w2, b"prepare")
108 time.sleep(2)
110 # Release the locks
111 for l in res:
112 pass
114 if (os.read(r1, 8) != b"prepared"):
115 os._exit(3)
117 os._exit(0)
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)
127 self.samdb.add({
128 "dn": dn,
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")
137 start = time.time()
138 self.samdb.transaction_prepare_commit()
139 end = time.time()
140 try:
141 self.assertGreater(end - start, 1.9)
142 except:
143 raise
144 finally:
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()
157 (r1, w1) = os.pipe()
158 (r2, w2) = os.pipe()
160 pid = os.fork()
161 if pid == 0:
162 # In the child, close the main DB, re-open
163 del(self.samdb)
164 gc.collect()
165 self.samdb = SamDB(session_info=self.session,
166 lp=self.lp)
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"):
173 os._exit(1)
175 os.write(w2, b"add")
176 if (os.read(r1, 5) != b"added"):
177 os._exit(2)
179 # Wait 2 seconds to block prepare_commit() in the child.
180 os.write(w2, b"prepare")
181 time.sleep(2)
183 # Release the locks
184 for l in res:
185 pass
187 if (os.read(r1, 8) != b"prepared"):
188 os._exit(3)
190 os._exit(0)
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"
202 self.samdb.add({
203 "dn": dn})
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")
210 start = time.time()
211 self.samdb.transaction_prepare_commit()
212 end = time.time()
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):
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)
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")
243 time.sleep(2)
245 # Drop the write lock
246 backenddb.transaction_cancel()
247 os._exit(0)
249 self.assertEqual(os.read(r1, 8), b"prepared")
251 start = time.time()
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
259 end = time.time()
260 self.assertGreater(end - start, 1.9)
262 # Release the locks
263 for l in res:
264 pass
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",
275 backend_filename)
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",
283 backend_filename)
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):
288 (r1, w1) = os.pipe()
289 (r2, w2) = os.pipe()
291 pid = os.fork()
292 if pid == 0:
294 # In the child, close the main DB, re-open
295 del(self.samdb)
296 gc.collect()
297 self.samdb = SamDB(session_info=self.session,
298 lp=self.lp)
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"):
305 os._exit(1)
306 os.write(w2, b"add")
307 if (os.read(r1, 5) != b"added"):
308 os._exit(2)
310 # Wait 2 seconds to block prepare_commit() in the child.
311 os.write(w2, b"prepare")
312 time.sleep(2)
314 # Release the locks
315 for l in res:
316 pass
318 if (os.read(r1, 8) != b"prepared"):
319 os._exit(3)
321 os._exit(0)
323 # In the parent, close the main DB, re-open just one DB
324 del(self.samdb)
325 gc.collect()
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")
342 start = time.time()
343 backenddb.transaction_prepare_commit()
344 end = time.time()
346 try:
347 self.assertGreater(end - start, 1.9)
348 except:
349 raise
350 finally:
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",
365 backend_filename)
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",
373 backend_filename)
374 backend_path = self.lp.private_path(backend_subpath)
375 self._test_full_db_lock2(backend_path)