traffic: new version of model with packet_rate, version number
[samba.git] / python / samba / tests / dsdb_lock.py
1 # Unix SMB/CIFS implementation. Tests for DSDB locking
2 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
3 #
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.
8 #
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.
13 #
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/>.
16 #
17
18 """Tests for samba's dsdb modules"""
19
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
27
28
29 class DsdbLockTestCase(SamDBTestCase):
30     def test_db_lock1(self):
31         basedn = self.samdb.get_default_basedn()
32         (r1, w1) = os.pipe()
33
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)
41
42             self.samdb.transaction_start()
43
44             dn = "cn=test_db_lock_user,cn=users," + str(basedn)
45             self.samdb.add({
46                  "dn": dn,
47                  "objectclass": "user",
48             })
49             self.samdb.delete(dn)
50
51             # Obtain a write lock
52             self.samdb.transaction_prepare_commit()
53             os.write(w1, b"prepared")
54             time.sleep(2)
55
56             # Drop the write lock
57             self.samdb.transaction_cancel()
58             os._exit(0)
59
60         self.assertEqual(os.read(r1, 8), b"prepared")
61
62         start = time.time()
63
64         # We need to hold this iterator open to hold the all-record lock.
65         res = self.samdb.search_iterator()
66
67         # This should take at least 2 seconds because the transaction
68         # has a write lock on one backend db open
69
70         # Release the locks
71         for l in res:
72             pass
73
74         end = time.time()
75         self.assertGreater(end - start, 1.9)
76
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)
81
82     def test_db_lock2(self):
83         basedn = self.samdb.get_default_basedn()
84         (r1, w1) = os.pipe()
85         (r2, w2) = os.pipe()
86
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)
94
95             # We need to hold this iterator open to hold the all-record lock.
96             res = self.samdb.search_iterator()
97
98             os.write(w2, b"start")
99             if (os.read(r1, 7) != b"started"):
100                 os._exit(1)
101
102             os.write(w2, b"add")
103             if (os.read(r1, 5) != b"added"):
104                 os._exit(2)
105
106             # Wait 2 seconds to block prepare_commit() in the child.
107             os.write(w2, b"prepare")
108             time.sleep(2)
109
110             # Release the locks
111             for l in res:
112                 pass
113
114             if (os.read(r1, 8) != b"prepared"):
115                 os._exit(3)
116
117             os._exit(0)
118
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")
124
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",
130         })
131         self.samdb.delete(dn)
132         os.write(w1, b"added")
133
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")
146
147             # Drop the write lock
148             self.samdb.transaction_cancel()
149
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)
154
155     def test_db_lock3(self):
156         basedn = self.samdb.get_default_basedn()
157         (r1, w1) = os.pipe()
158         (r2, w2) = os.pipe()
159
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)
167
168             # We need to hold this iterator open to hold the all-record lock.
169             res = self.samdb.search_iterator()
170
171             os.write(w2, b"start")
172             if (os.read(r1, 7) != b"started"):
173                 os._exit(1)
174
175             os.write(w2, b"add")
176             if (os.read(r1, 5) != b"added"):
177                 os._exit(2)
178
179             # Wait 2 seconds to block prepare_commit() in the child.
180             os.write(w2, b"prepare")
181             time.sleep(2)
182
183             # Release the locks
184             for l in res:
185                 pass
186
187             if (os.read(r1, 8) != b"prepared"):
188                 os._exit(3)
189
190             os._exit(0)
191
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")
197
198         self.assertEqual(os.read(r2, 3), b"add")
199
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")
206
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")
215
216         # Drop the write lock
217         self.samdb.transaction_cancel()
218
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)
223
224     def _test_full_db_lock1(self, backend_path):
225         (r1, w1) = os.pipe()
226
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()
232
233             backenddb = ldb.Ldb(backend_path)
234
235             backenddb.transaction_start()
236
237             backenddb.add({"dn": "@DSDB_LOCK_TEST"})
238             backenddb.delete("@DSDB_LOCK_TEST")
239
240             # Obtain a write lock
241             backenddb.transaction_prepare_commit()
242             os.write(w1, b"prepared")
243             time.sleep(2)
244
245             # Drop the write lock
246             backenddb.transaction_cancel()
247             os._exit(0)
248
249         self.assertEqual(os.read(r1, 8), b"prepared")
250
251         start = time.time()
252
253         # We need to hold this iterator open to hold the all-record lock.
254         res = self.samdb.search_iterator()
255
256         # This should take at least 2 seconds because the transaction
257         # has a write lock on one backend db open
258
259         end = time.time()
260         self.assertGreater(end - start, 1.9)
261
262         # Release the locks
263         for l in res:
264             pass
265
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)
270
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)
278
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)
286
287     def _test_full_db_lock2(self, backend_path):
288         (r1, w1) = os.pipe()
289         (r2, w2) = os.pipe()
290
291         pid = os.fork()
292         if pid == 0:
293
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)
299
300             # We need to hold this iterator open to hold the all-record lock.
301             res = self.samdb.search_iterator()
302
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)
309
310             # Wait 2 seconds to block prepare_commit() in the child.
311             os.write(w2, b"prepare")
312             time.sleep(2)
313
314             # Release the locks
315             for l in res:
316                 pass
317
318             if (os.read(r1, 8) != b"prepared"):
319                 os._exit(3)
320
321             os._exit(0)
322
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)
327
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")
333
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")
338
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()
345
346         try:
347             self.assertGreater(end - start, 1.9)
348         except:
349             raise
350         finally:
351             os.write(w1, b"prepared")
352
353             # Drop the write lock
354             backenddb.transaction_cancel()
355
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)
360
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)
368
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)