2 # -*- coding: utf-8 -*-
3 from __future__ import print_function
7 sys.path.insert(0, 'bin/python')
11 import samba.getopt as options
18 from samba.netcmd.main import cmd_sambatool
20 # We try to use the test infrastructure of Samba 4.3+, but if it
21 # doesn't work, we are probably in a back-ported patch and trying to
22 # run on 4.1 or something.
24 # Don't copy this horror into ordinary tests -- it is special for
25 # performance tests that want to apply to old versions.
27 from samba.tests.subunitrun import SubunitOptions, TestProgram
31 samba.ensure_external_module("testtools", "testtools")
32 samba.ensure_external_module("subunit", "subunit/python")
33 from subunit.run import SubunitTestRunner
36 from samba.samdb import SamDB
37 from samba.auth import system_session
38 from ldb import Message, MessageElement, Dn, LdbError
39 from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE
40 from ldb import SCOPE_BASE, SCOPE_SUBTREE, SCOPE_ONELEVEL
41 from ldb import ERR_NO_SUCH_OBJECT
43 parser = optparse.OptionParser("ad_dc_performance.py [options] <host>")
44 sambaopts = options.SambaOptions(parser)
45 parser.add_option_group(sambaopts)
46 parser.add_option_group(options.VersionOptions(parser))
49 subunitopts = SubunitOptions(parser)
50 parser.add_option_group(subunitopts)
52 # use command line creds if available
53 credopts = options.CredentialsOptions(parser)
54 parser.add_option_group(credopts)
55 opts, args = parser.parse_args()
64 lp = sambaopts.get_loadparm()
65 creds = credopts.get_credentials(lp)
70 class PerfTestException(Exception):
75 LINK_BATCH_SIZE = 1000
76 DELETE_BATCH_SIZE = 50
80 class GlobalState(object):
84 next_relinked_user = 0
85 next_linked_user_3 = 0
86 next_removed_link_0 = 0
91 class UserTests(samba.tests.TestCase):
93 def add_if_possible(self, *args, **kwargs):
94 """In these tests sometimes things are left in the database
95 deliberately, so we don't worry if we fail to add them a second
98 self.ldb.add(*args, **kwargs)
103 super(UserTests, self).setUp()
104 self.state = GlobalState # the class itself, not an instance
106 self.ldb = SamDB(host, credentials=creds,
107 session_info=system_session(lp), lp=lp)
108 self.base_dn = self.ldb.domain_dn()
109 self.ou = "OU=pid%s,%s" % (os.getpid(), self.base_dn)
110 self.ou_users = "OU=users,%s" % self.ou
111 self.ou_groups = "OU=groups,%s" % self.ou
112 self.ou_computers = "OU=computers,%s" % self.ou
114 self.state.test_number += 1
115 random.seed(self.state.test_number)
118 super(UserTests, self).tearDown()
120 def test_00_00_do_nothing(self):
121 # this gives us an idea of the overhead
124 def test_00_01_do_nothing_relevant(self):
125 # takes around 1 second on i7-4770
127 for i in range(30000000):
130 def test_00_02_do_nothing_sleepily(self):
133 def test_00_03_add_ous_and_groups(self):
134 # initialise the database
141 "objectclass": "organizationalUnit"
144 for i in range(N_GROUPS):
146 "dn": "cn=g%d,%s" % (i, self.ou_groups),
147 "objectclass": "group"
150 self.state.n_groups = N_GROUPS
152 def _add_users(self, start, end):
153 for i in range(start, end):
155 "dn": "cn=u%d,%s" % (i, self.ou_users),
156 "objectclass": "user"
159 def _add_users_ldif(self, start, end):
161 for i in range(start, end):
162 lines.append("dn: cn=u%d,%s" % (i, self.ou_users))
163 lines.append("objectclass: user")
165 self.ldb.add_ldif('\n'.join(lines))
167 def _test_join(self):
168 tmpdir = tempfile.mkdtemp()
170 server = host.split('://', 1)[1]
173 cmd = cmd_sambatool.subcommands['domain'].subcommands['join']
174 result = cmd._run("samba-tool domain join",
176 "dc", "-U%s%%%s" % (creds.get_username(),
177 creds.get_password()),
178 '--targetdir=%s' % tmpdir,
179 '--server=%s' % server)
181 shutil.rmtree(tmpdir)
183 def _test_unindexed_search(self):
185 ('(&(objectclass=user)(description='
186 'Built-in account for adminstering the computer/domain))'),
187 '(description=Built-in account for adminstering the computer/domain)',
188 '(objectCategory=*)',
189 '(samaccountname=Administrator*)'
191 for expression in expressions:
194 self.ldb.search(self.ou,
195 expression=expression,
198 print('%d %s took %s' % (i, expression,
202 def _test_indexed_search(self):
203 expressions = ['(objectclass=group)',
204 '(samaccountname=Administrator)'
206 for expression in expressions:
208 for i in range(4000):
209 self.ldb.search(self.ou,
210 expression=expression,
213 print('%d runs %s took %s' % (i, expression,
217 def _test_base_search(self):
218 for dn in [self.base_dn, self.ou, self.ou_users,
219 self.ou_groups, self.ou_computers]:
220 for i in range(4000):
225 except LdbError as e:
227 if num != ERR_NO_SUCH_OBJECT:
230 def _test_base_search_failing(self):
231 pattern = 'missing%d' + self.ou
232 for i in range(4000):
234 self.ldb.search(pattern % i,
237 except LdbError as e:
239 if num != ERR_NO_SUCH_OBJECT:
242 def search_expression_list(self, expressions, rounds,
244 scope=SCOPE_SUBTREE):
245 for expression in expressions:
247 for i in range(rounds):
248 self.ldb.search(self.ou,
249 expression=expression,
252 print('%d runs %s took %s' % (i, expression,
256 def _test_complex_search(self, n=100):
257 classes = ['samaccountname', 'objectCategory', 'dn', 'member']
258 values = ['*', '*t*', 'g*', 'user']
259 comparators = ['=', '<=', '>='] # '~=' causes error
260 maybe_not = ['!(', '']
263 # The number of permuations is 18432, which is not huge but
264 # would take hours to search. So we take a sample.
265 all_permutations = list(itertools.product(joiners,
268 comparators, comparators,
269 maybe_not, maybe_not))
273 for (j, c1, c2, v1, v2,
274 o1, o2, n1, n2) in random.sample(all_permutations, n):
275 expression = ''.join(['(', j,
281 expressions.append(expression)
283 self.search_expression_list(expressions, 1)
285 def _test_member_search(self, rounds=10):
288 expressions.append('(member=cn=u%d,%s)' % (d + 500, self.ou_users))
289 expressions.append('(member=u%d*)' % (d + 700,))
291 self.search_expression_list(expressions, rounds)
293 def _test_memberof_search(self, rounds=200):
295 for i in range(min(self.state.n_groups, rounds)):
296 expressions.append('(memberOf=cn=g%d,%s)' % (i, self.ou_groups))
297 expressions.append('(memberOf=cn=g%d*)' % (i,))
298 expressions.append('(memberOf=cn=*%s*)' % self.ou_groups)
300 self.search_expression_list(expressions, 2)
302 def _test_add_many_users(self, n=BATCH_SIZE):
303 s = self.state.next_user_id
305 self._add_users(s, e)
306 self.state.next_user_id = e
308 def _test_add_many_users_ldif(self, n=BATCH_SIZE):
309 s = self.state.next_user_id
311 self._add_users_ldif(s, e)
312 self.state.next_user_id = e
314 def _link_user_and_group(self, u, g):
316 if link in self.state.active_links:
320 m.dn = Dn(self.ldb, "CN=g%d,%s" % (g, self.ou_groups))
321 m["member"] = MessageElement("cn=u%d,%s" % (u, self.ou_users),
322 FLAG_MOD_ADD, "member")
324 self.state.active_links.add(link)
327 def _unlink_user_and_group(self, u, g):
329 if link not in self.state.active_links:
332 user = "cn=u%d,%s" % (u, self.ou_users)
333 group = "CN=g%d,%s" % (g, self.ou_groups)
335 m.dn = Dn(self.ldb, group)
336 m["member"] = MessageElement(user, FLAG_MOD_DELETE, "member")
338 self.state.active_links.remove(link)
341 def _test_link_many_users(self, n=LINK_BATCH_SIZE):
342 # this links unevenly, putting more users in the first group
343 # and fewer in the last.
344 ng = self.state.n_groups
345 nu = self.state.next_user_id
347 u = random.randrange(nu)
348 g = random.randrange(random.randrange(ng) + 1)
349 if self._link_user_and_group(u, g):
352 def _test_link_many_users_batch(self, n=(LINK_BATCH_SIZE * 10)):
353 # this links unevenly, putting more users in the first group
354 # and fewer in the last.
355 ng = self.state.n_groups
356 nu = self.state.next_user_id
360 m.dn = Dn(self.ldb, "CN=g%d,%s" % (g, self.ou_groups))
364 u = random.randrange(nu)
365 g = random.randrange(random.randrange(ng) + 1)
367 if link in self.state.active_links:
370 m["member%s" % u] = MessageElement("cn=u%d,%s" %
372 FLAG_MOD_ADD, "member")
373 self.state.active_links.add(link)
379 except LdbError as e:
383 def _test_remove_some_links(self, n=(LINK_BATCH_SIZE // 2)):
384 victims = random.sample(list(self.state.active_links), n)
386 self._unlink_user_and_group(*x)
388 test_00_11_join_empty_dc = _test_join
390 test_00_12_adding_users_2000 = _test_add_many_users
392 test_00_20_join_unlinked_2k_users = _test_join
393 test_00_21_unindexed_search_2k_users = _test_unindexed_search
394 test_00_22_indexed_search_2k_users = _test_indexed_search
396 test_00_23_complex_search_2k_users = _test_complex_search
397 test_00_24_member_search_2k_users = _test_member_search
398 test_00_25_memberof_search_2k_users = _test_memberof_search
400 test_00_27_base_search_2k_users = _test_base_search
401 test_00_28_base_search_failing_2k_users = _test_base_search_failing
403 test_01_01_link_2k_users = _test_link_many_users
404 test_01_02_link_2k_users_batch = _test_link_many_users_batch
406 test_02_10_join_2k_linked_dc = _test_join
407 test_02_11_unindexed_search_2k_linked_dc = _test_unindexed_search
408 test_02_12_indexed_search_2k_linked_dc = _test_indexed_search
410 test_04_01_remove_some_links_2k = _test_remove_some_links
412 test_05_01_adding_users_after_links_4k_ldif = _test_add_many_users_ldif
414 test_06_04_link_users_4k = _test_link_many_users
415 test_06_05_link_users_4k_batch = _test_link_many_users_batch
417 test_07_01_adding_users_after_links_6k = _test_add_many_users
419 def _test_ldif_well_linked_group(self, link_chance=1.0):
420 g = self.state.n_groups
421 self.state.n_groups += 1
422 lines = ["dn: CN=g%d,%s" % (g, self.ou_groups),
423 "objectclass: group"]
425 for i in range(self.state.next_user_id):
426 if random.random() <= link_chance:
427 lines.append("member: cn=u%d,%s" % (i, self.ou_users))
428 self.state.active_links.add((i, g))
431 self.ldb.add_ldif('\n'.join(lines))
433 test_09_01_add_fully_linked_group = _test_ldif_well_linked_group
435 def test_09_02_add_exponentially_diminishing_linked_groups(self):
437 while linkage > 0.01:
438 self._test_ldif_well_linked_group(linkage)
441 test_09_04_link_users_6k = _test_link_many_users
443 test_10_01_unindexed_search_6k_users = _test_unindexed_search
444 test_10_02_indexed_search_6k_users = _test_indexed_search
446 test_10_27_base_search_6k_users = _test_base_search
447 test_10_28_base_search_failing_6k_users = _test_base_search_failing
449 def test_10_03_complex_search_6k_users(self):
450 self._test_complex_search(n=50)
452 def test_10_04_member_search_6k_users(self):
453 self._test_member_search(rounds=1)
455 def test_10_05_memberof_search_6k_users(self):
456 self._test_memberof_search(rounds=5)
458 test_11_02_join_full_dc = _test_join
460 test_12_01_remove_some_links_6k = _test_remove_some_links
462 def _test_delete_many_users(self, n=DELETE_BATCH_SIZE):
463 e = self.state.next_user_id
465 self.state.next_user_id = s
466 for i in range(s, e):
467 self.ldb.delete("cn=u%d,%s" % (i, self.ou_users))
469 for x in tuple(self.state.active_links):
471 self.state.active_links.remove(x)
473 test_20_01_delete_users_6k = _test_delete_many_users
475 def test_21_01_delete_10_groups(self):
476 for i in range(self.state.n_groups - 10, self.state.n_groups):
477 self.ldb.delete("cn=g%d,%s" % (i, self.ou_groups))
478 self.state.n_groups -= 10
479 for x in tuple(self.state.active_links):
480 if x[1] >= self.state.n_groups:
481 self.state.active_links.remove(x)
483 test_21_02_delete_users_5950 = _test_delete_many_users
485 def test_22_01_delete_all_groups(self):
486 for i in range(self.state.n_groups):
487 self.ldb.delete("cn=g%d,%s" % (i, self.ou_groups))
488 self.state.n_groups = 0
489 self.state.active_links = set()
491 # XXX assert the state is as we think, using searches
493 def test_23_01_delete_users_5900_after_groups(self):
494 # we do not delete everything because it takes too long
495 n = 4 * DELETE_BATCH_SIZE
496 self._test_delete_many_users(n=n)
498 test_24_02_join_after_partial_cleanup = _test_join
501 if "://" not in host:
502 if os.path.isfile(host):
503 host = "tdb://%s" % host
505 host = "ldap://%s" % host
509 runner = SubunitTestRunner()
510 if not runner.run(unittest.makeSuite(UserTests)).wasSuccessful():
514 TestProgram(module=__name__, opts=subunitopts)