2 # -*- coding: utf-8 -*-
4 # Tests various schema replication scenarios
6 # Copyright (C) Catalyst.Net Ltd. 2017
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # export DC1=dc1_dns_name
25 # export DC2=dc2_dns_name
26 # export SUBUNITRUN=$samba4srcdir/scripting/bin/subunitrun
27 # PYTHONPATH="$PYTHONPATH:$samba4srcdir/torture/drs/python" $SUBUNITRUN getncchanges -U"$DOMAIN/$DC_USERNAME"%"$DC_PASSWORD"
33 from ldb import SCOPE_BASE
36 from samba.dcerpc import drsuapi
38 class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
40 super(DrsReplicaSyncIntegrityTestCase, self).setUp()
42 # Note that DC2 is the DC with the testenv-specific quirks (e.g. it's
43 # the vampire_dc), so we point this test directly at that DC
44 self.set_test_ldb_dc(self.ldb_dc2)
45 (self.drs, self.drs_handle) = self._ds_bind(self.dnsname_dc2)
47 # add some randomness to the test OU. (Deletion of the last test's
48 # objects can be slow to replicate out. So the OU created by a previous
49 # testenv may still exist at this point).
50 rand = random.randint(1, 10000000)
51 self.base_dn = self.test_ldb_dc.get_default_basedn()
52 self.ou = "OU=getncchanges%d_test,%s" %(rand, self.base_dn)
53 self.test_ldb_dc.add({
55 "objectclass": "organizationalUnit"})
56 (self.default_hwm, self.default_utdv) = self._get_highest_hwm_utdv(self.test_ldb_dc)
62 # 100 is the minimum max_objects that Microsoft seems to honour
63 # (the max honoured is 400ish), so we use that in these tests
64 self.max_objects = 100
67 # store whether we used GET_TGT/GET_ANC flags in the requests
68 self.used_get_tgt = False
69 self.used_get_anc = False
72 super(DrsReplicaSyncIntegrityTestCase, self).tearDown()
73 # tidyup groups and users
75 self.ldb_dc2.delete(self.ou, ["tree_delete:1"])
76 except ldb.LdbError as (enum, string):
77 if enum == ldb.ERR_NO_SUCH_OBJECT:
80 def add_object(self, dn):
81 """Adds an OU object"""
82 self.test_ldb_dc.add({"dn": dn, "objectclass": "organizationalunit"})
83 res = self.test_ldb_dc.search(base=dn, scope=SCOPE_BASE)
84 self.assertEquals(len(res), 1)
86 def modify_object(self, dn, attr, value):
87 """Modifies an object's USN by adding an attribute value to it"""
89 m.dn = ldb.Dn(self.test_ldb_dc, dn)
90 m[attr] = ldb.MessageElement(value, ldb.FLAG_MOD_ADD, attr)
91 self.test_ldb_dc.modify(m)
93 def delete_attribute(self, dn, attr, value):
94 """Deletes an attribute from an object"""
96 m.dn = ldb.Dn(self.ldb_dc2, dn)
97 m[attr] = ldb.MessageElement(value, ldb.FLAG_MOD_DELETE, attr)
98 self.ldb_dc2.modify(m)
100 def start_new_repl_cycle(self):
101 """Resets enough state info to start a new replication cycle"""
102 # reset rxd_links, but leave rxd_guids and rxd_dn_list alone so we know
103 # whether a parent/target is unknown and needs GET_ANC/GET_TGT to resolve
106 self.used_get_tgt = False
107 self.used_get_anc = False
108 # mostly preserve self.last_ctr, so that we use the last HWM
109 if self.last_ctr is not None:
110 self.last_ctr.more_data = True
112 def create_object_range(self, start, end, prefix="",
113 children=None, parent_list=None):
115 Creates a block of objects. Object names are numbered sequentially,
116 using the optional prefix supplied. If the children parameter is
117 supplied it will create a parent-child hierarchy and return the
118 top-level parents separately.
122 # Use dummy/empty lists if we're not creating a parent/child hierarchy
126 if parent_list is None:
129 # Create the parents first, then the children.
130 # This makes it easier to see in debug when GET_ANC takes effect
131 # because the parent/children become interleaved (by default,
132 # this approach means the objects are organized into blocks of
133 # parents and blocks of children together)
134 for x in range(start, end):
135 ou = "OU=test_ou_%s%d,%s" % (prefix, x, self.ou)
139 # keep track of the top-level parents (if needed)
140 parent_list.append(ou)
142 # create the block of children (if needed)
143 for x in range(start, end):
144 for child in children:
145 ou = "OU=test_ou_child%s%d,%s" % (child, x, parent_list[x])
151 def assert_expected_data(self, expected_list):
153 Asserts that we received all the DNs that we expected and
156 received_list = self.rxd_dn_list
158 # Note that with GET_ANC Windows can end up sending the same parent
159 # object multiple times, so this might be noteworthy but doesn't
160 # warrant failing the test
161 if (len(received_list) != len(expected_list)):
162 print("Note: received %d objects but expected %d" %(len(received_list),
165 # Check that we received every object that we were expecting
166 for dn in expected_list:
167 self.assertTrue(dn in received_list, "DN '%s' missing from replication." % dn)
169 def test_repl_integrity(self):
171 Modify the objects being replicated while the replication is still
172 in progress and check that no object loss occurs.
175 # The server behaviour differs between samba and Windows. Samba returns
176 # the objects in the original order (up to the pre-modify HWM). Windows
177 # incorporates the modified objects and returns them in the new order
178 # (i.e. modified objects last), up to the post-modify HWM. The Microsoft
179 # docs state the Windows behaviour is optional.
181 # Create a range of objects to replicate.
182 expected_dn_list = self.create_object_range(0, 400)
183 (orig_hwm, unused) = self._get_highest_hwm_utdv(self.test_ldb_dc)
185 # We ask for the first page of 100 objects.
186 # For this test, we don't care what order we receive the objects in,
187 # so long as by the end we've received everything
190 # Modify some of the second page of objects. This should bump the highwatermark
191 for x in range(100, 200):
192 self.modify_object(expected_dn_list[x], "displayName", "OU%d" % x)
194 (post_modify_hwm, unused) = self._get_highest_hwm_utdv(self.test_ldb_dc)
195 self.assertTrue(post_modify_hwm.highest_usn > orig_hwm.highest_usn)
197 # Get the remaining blocks of data
198 while not self.replication_complete():
201 # Check we still receive all the objects we're expecting
202 self.assert_expected_data(expected_dn_list)
204 def is_parent_known(self, dn, known_dn_list):
206 Returns True if the parent of the dn specified is in known_dn_list
209 # we can sometimes get system objects like the RID Manager returned.
210 # Ignore anything that is not under the test OU we created
211 if self.ou not in dn:
214 # Remove the child portion from the name to get the parent's DN
215 name_substrings = dn.split(",")
216 del name_substrings[0]
218 parent_dn = ",".join(name_substrings)
220 # check either this object is a parent (it's parent is the top-level
221 # test object), or its parent has been seen previously
222 return parent_dn == self.ou or parent_dn in known_dn_list
224 def _repl_send_request(self, get_anc=False, get_tgt=False):
225 """Sends a GetNCChanges request for the next block of replication data."""
227 # we're just trying to mimic regular client behaviour here, so just
228 # use the highwatermark in the last response we received
230 highwatermark = self.last_ctr.new_highwatermark
231 uptodateness_vector = self.last_ctr.uptodateness_vector
233 # this is the first replication chunk
235 uptodateness_vector = None
237 # Ask for the next block of replication data
238 replica_flags = drsuapi.DRSUAPI_DRS_WRIT_REP
242 replica_flags = drsuapi.DRSUAPI_DRS_WRIT_REP | drsuapi.DRSUAPI_DRS_GET_ANC
243 self.used_get_anc = True
246 more_flags = drsuapi.DRSUAPI_DRS_GET_TGT
247 self.used_get_tgt = True
249 # return the response from the DC
250 return self._get_replication(replica_flags,
251 max_objects=self.max_objects,
252 highwatermark=highwatermark,
253 uptodateness_vector=uptodateness_vector,
254 more_flags=more_flags)
256 def repl_get_next(self, get_anc=False, get_tgt=False, assert_links=False):
258 Requests the next block of replication data. This tries to simulate
259 client behaviour - if we receive a replicated object that we don't know
260 the parent of, then re-request the block with the GET_ANC flag set.
261 If we don't know the target object for a linked attribute, then
262 re-request with GET_TGT.
265 # send a request to the DC and get the response
266 ctr6 = self._repl_send_request(get_anc=get_anc, get_tgt=get_tgt)
268 # extract the object DNs and their GUIDs from the response
269 rxd_dn_list = self._get_ctr6_dn_list(ctr6)
270 rxd_guid_list = self._get_ctr6_object_guids(ctr6)
272 # we'll add new objects as we discover them, so take a copy of the
273 # ones we already know about, so we can modify these lists safely
274 known_objects = self.rxd_dn_list[:]
275 known_guids = self.rxd_guids[:]
277 # check that we know the parent for every object received
278 for i in range(0, len(rxd_dn_list)):
281 guid = rxd_guid_list[i]
283 if self.is_parent_known(dn, known_objects):
285 # the new DN is now known so add it to the list.
286 # It may be the parent of another child in this block
287 known_objects.append(dn)
288 known_guids.append(guid)
290 # If we've already set the GET_ANC flag then it should mean
291 # we receive the parents before the child
292 self.assertFalse(get_anc, "Unknown parent for object %s" % dn)
294 print("Unknown parent for %s - try GET_ANC" % dn)
296 # try the same thing again with the GET_ANC flag set this time
297 return self.repl_get_next(get_anc=True, get_tgt=get_tgt,
298 assert_links=assert_links)
300 # check we know about references to any objects in the linked attritbutes
301 received_links = self._get_ctr6_links(ctr6)
303 # This is so that older versions of Samba fail - we want the links to be
304 # sent roughly with the objects, rather than getting all links at the end
306 self.assertTrue(len(received_links) > 0,
307 "Links were expected in the GetNCChanges response")
309 for link in received_links:
311 # check the source object is known (Windows can actually send links
312 # where we don't know the source object yet). Samba shouldn't ever
313 # hit this case because it gets the links based on the source
314 if link.identifier not in known_guids:
316 # If we've already set the GET_ANC flag then it should mean
317 # this case doesn't happen
318 self.assertFalse(get_anc, "Unknown source object for GUID %s"
321 print("Unknown source GUID %s - try GET_ANC" % link.identifier)
323 # try the same thing again with the GET_ANC flag set this time
324 return self.repl_get_next(get_anc=True, get_tgt=get_tgt,
325 assert_links=assert_links)
327 # check we know the target object
328 if link.targetGUID not in known_guids:
330 # If we've already set the GET_TGT flag then we should have
331 # already received any objects we need to know about
332 self.assertFalse(get_tgt, "Unknown linked target for object %s"
335 print("Unknown target for %s - try GET_TGT" % link.targetDN)
337 # try the same thing again with the GET_TGT flag set this time
338 return self.repl_get_next(get_anc=get_anc, get_tgt=True,
339 assert_links=assert_links)
341 # store the last successful result so we know what HWM to request next
344 # store the objects, GUIDs, and links we received
345 self.rxd_dn_list += self._get_ctr6_dn_list(ctr6)
346 self.rxd_links += self._get_ctr6_links(ctr6)
347 self.rxd_guids += self._get_ctr6_object_guids(ctr6)
351 def replication_complete(self):
352 """Returns True if the current/last replication cycle is complete"""
354 if self.last_ctr is None or self.last_ctr.more_data:
359 def test_repl_integrity_get_anc(self):
361 Modify the parent objects being replicated while the replication is still
362 in progress (using GET_ANC) and check that no object loss occurs.
365 # Note that GET_ANC behaviour varies between Windows and Samba.
366 # On Samba GET_ANC results in the replication restarting from the very
367 # beginning. After that, Samba remembers GET_ANC and also sends the
368 # parents in subsequent requests (regardless of whether GET_ANC is
369 # specified in the later request).
370 # Windows only sends the parents if GET_ANC was specified in the last
371 # request. It will also resend a parent, even if it's already sent the
372 # parent in a previous response (whereas Samba doesn't).
374 # Create a small block of 50 parents, each with 2 children (A and B)
375 # This is so that we receive some children in the first block, so we
376 # can resend with GET_ANC before we learn too many parents
378 expected_dn_list = self.create_object_range(0, 50, prefix="parent",
380 parent_list=parent_dn_list)
382 # create the remaining parents and children
383 expected_dn_list += self.create_object_range(50, 150, prefix="parent",
385 parent_list=parent_dn_list)
387 # We've now got objects in the following order:
388 # [50 parents][100 children][100 parents][200 children]
390 # Modify the first parent so that it's now ordered last by USN
391 # This means we set the GET_ANC flag pretty much straight away
392 # because we receive the first child before the first parent
393 self.modify_object(parent_dn_list[0], "displayName", "OU0")
395 # modify a later block of parents so they also get reordered
396 for x in range(50, 100):
397 self.modify_object(parent_dn_list[x], "displayName", "OU%d" % x)
399 # Get the first block of objects - this should resend the request with
400 # GET_ANC set because we won't know about the first child's parent.
401 # On samba GET_ANC essentially starts the sync from scratch again, so
402 # we get this over with early before we learn too many parents
405 # modify the last chunk of parents. They should now have a USN higher
406 # than the highwater-mark for the replication cycle
407 for x in range(100, 150):
408 self.modify_object(parent_dn_list[x], "displayName", "OU%d" % x)
410 # Get the remaining blocks of data - this will resend the request with
411 # GET_ANC if it encounters an object it doesn't have the parent for.
412 while not self.replication_complete():
415 # The way the test objects have been created should force
416 # self.repl_get_next() to use the GET_ANC flag. If this doesn't
417 # actually happen, then the test isn't doing its job properly
418 self.assertTrue(self.used_get_anc,
419 "Test didn't use the GET_ANC flag as expected")
421 # Check we get all the objects we're expecting
422 self.assert_expected_data(expected_dn_list)
424 def assert_expected_links(self, objects_with_links, link_attr="managedBy",
427 Asserts that a GetNCChanges response contains any expected links
428 for the objects it contains.
430 received_links = self.rxd_links
432 if num_expected is None:
433 num_expected = len(objects_with_links)
435 self.assertTrue(len(received_links) == num_expected,
436 "Received %d links but expected %d"
437 %(len(received_links), num_expected))
439 for dn in objects_with_links:
440 self.assert_object_has_link(dn, link_attr, received_links)
442 def assert_object_has_link(self, dn, link_attr, received_links):
444 Queries the object in the DB and asserts there is a link in the
445 GetNCChanges response that matches.
448 # Look up the link attribute in the DB
449 # The extended_dn option will dump the GUID info for the link
450 # attribute (as a hex blob)
451 res = self.test_ldb_dc.search(ldb.Dn(self.test_ldb_dc, dn), attrs=[link_attr],
452 controls=['extended_dn:1:0'], scope=ldb.SCOPE_BASE)
454 # We didn't find the expected link attribute in the DB for the object.
455 # Something has gone wrong somewhere...
456 self.assertTrue(link_attr in res[0], "%s in DB doesn't have attribute %s"
459 # find the received link in the list and assert that the target and
460 # source GUIDs match what's in the DB
461 for val in res[0][link_attr]:
462 # Work out the expected source and target GUIDs for the DB link
463 target_dn = ldb.Dn(self.test_ldb_dc, val)
464 targetGUID_blob = target_dn.get_extended_component("GUID")
465 sourceGUID_blob = res[0].dn.get_extended_component("GUID")
469 for link in received_links:
470 if link.selfGUID_blob == sourceGUID_blob and \
471 link.targetGUID_blob == targetGUID_blob:
476 print("Link %s --> %s" %(dn[:25], link.targetDN[:25]))
479 self.assertTrue(found, "Did not receive expected link for DN %s" % dn)
481 def test_repl_get_tgt(self):
483 Creates a scenario where we should receive the linked attribute before
484 we know about the target object, and therefore need to use GET_TGT.
485 Note: Samba currently avoids this problem by sending all its links last
488 # create the test objects
489 reportees = self.create_object_range(0, 100, prefix="reportee")
490 managers = self.create_object_range(0, 100, prefix="manager")
491 all_objects = managers + reportees
492 expected_links = reportees
494 # add a link attribute to each reportee object that points to the
495 # corresponding manager object as the target
496 for i in range(0, 100):
497 self.modify_object(reportees[i], "managedBy", managers[i])
499 # touch the managers (the link-target objects) again to make sure the
500 # reportees (link source objects) get returned first by the replication
501 for i in range(0, 100):
502 self.modify_object(managers[i], "displayName", "OU%d" % i)
504 links_expected = True
506 # Get all the replication data - this code should resend the requests
508 while not self.replication_complete():
510 # get the next block of replication data (this sets GET_TGT if needed)
511 self.repl_get_next(assert_links=links_expected)
512 links_expected = len(self.rxd_links) < len(expected_links)
514 # The way the test objects have been created should force
515 # self.repl_get_next() to use the GET_TGT flag. If this doesn't
516 # actually happen, then the test isn't doing its job properly
517 self.assertTrue(self.used_get_tgt,
518 "Test didn't use the GET_TGT flag as expected")
520 # Check we get all the objects we're expecting
521 self.assert_expected_data(all_objects)
523 # Check we received links for all the reportees
524 self.assert_expected_links(expected_links)
526 def test_repl_get_tgt_chain(self):
528 Tests the behaviour of GET_TGT with a more complicated scenario.
529 Here we create a chain of objects linked together, so if we follow
530 the link target, then we'd traverse ~200 objects each time.
533 # create the test objects
534 objectsA = self.create_object_range(0, 100, prefix="AAA")
535 objectsB = self.create_object_range(0, 100, prefix="BBB")
536 objectsC = self.create_object_range(0, 100, prefix="CCC")
538 # create a complex set of object links:
539 # A0-->B0-->C1-->B2-->C3-->B4-->and so on...
540 # Basically each object-A should link to a circular chain of 200 B/C
541 # objects. We create the links in separate chunks here, as it makes it
542 # clearer what happens with the USN (links on Windows have their own
543 # USN, so this approach means the A->B/B->C links aren't interleaved)
544 for i in range(0, 100):
545 self.modify_object(objectsA[i], "managedBy", objectsB[i])
547 for i in range(0, 100):
548 self.modify_object(objectsB[i], "managedBy", objectsC[(i + 1) % 100])
550 for i in range(0, 100):
551 self.modify_object(objectsC[i], "managedBy", objectsB[(i + 1) % 100])
553 all_objects = objectsA + objectsB + objectsC
554 expected_links = all_objects
556 # the default order the objects now get returned in should be:
557 # [A0-A99][B0-B99][C0-C99]
559 links_expected = True
561 # Get all the replication data - this code should resend the requests
563 while not self.replication_complete():
565 # get the next block of replication data (this sets GET_TGT if needed)
566 self.repl_get_next(assert_links=links_expected)
567 links_expected = len(self.rxd_links) < len(expected_links)
569 # The way the test objects have been created should force
570 # self.repl_get_next() to use the GET_TGT flag. If this doesn't
571 # actually happen, then the test isn't doing its job properly
572 self.assertTrue(self.used_get_tgt,
573 "Test didn't use the GET_TGT flag as expected")
575 # Check we get all the objects we're expecting
576 self.assert_expected_data(all_objects)
578 # Check we received links for all the reportees
579 self.assert_expected_links(expected_links)
581 def test_repl_integrity_link_attr(self):
583 Tests adding links to new objects while a replication is in progress.
586 # create some source objects for the linked attributes, sandwiched
587 # between 2 blocks of filler objects
588 filler = self.create_object_range(0, 100, prefix="filler")
589 reportees = self.create_object_range(0, 100, prefix="reportee")
590 filler += self.create_object_range(100, 200, prefix="filler")
592 # Start the replication and get the first block of filler objects
593 # (We're being mean here and setting the GET_TGT flag right from the
594 # start. On earlier Samba versions, if the client encountered an
595 # unknown target object and retried with GET_TGT, it would restart the
596 # replication cycle from scratch, which avoids the problem).
597 self.repl_get_next(get_tgt=True)
599 # create the target objects and add the links. These objects should be
600 # outside the scope of the Samba replication cycle, but the links should
601 # still get sent with the source object
602 managers = self.create_object_range(0, 100, prefix="manager")
604 for i in range(0, 100):
605 self.modify_object(reportees[i], "managedBy", managers[i])
607 expected_objects = managers + reportees + filler
608 expected_links = reportees
610 # complete the replication
611 while not self.replication_complete():
612 self.repl_get_next(get_tgt=True)
614 # If we didn't receive the most recently created objects in the last
615 # replication cycle, then kick off another replication to get them
616 if len(self.rxd_dn_list) < len(expected_objects):
619 while not self.replication_complete():
622 # Check we get all the objects we're expecting
623 self.assert_expected_data(expected_objects)
625 # Check we received links for all the parents
626 self.assert_expected_links(expected_links)
628 def test_repl_get_anc_link_attr(self):
630 A basic GET_ANC test where the parents have linked attributes
633 # Create a block of 100 parents and 100 children
635 expected_dn_list = self.create_object_range(0, 100, prefix="parent",
637 parent_list=parent_dn_list)
639 # Add links from the parents to the children
640 for x in range(0, 100):
641 self.modify_object(parent_dn_list[x], "managedBy", expected_dn_list[x + 100])
643 # add some filler objects at the end. This allows us to easily see
644 # which chunk the links get sent in
645 expected_dn_list += self.create_object_range(0, 100, prefix="filler")
647 # We've now got objects in the following order:
648 # [100 x children][100 x parents][100 x filler]
650 # Get the replication data - because the block of children come first,
651 # this should retry the request with GET_ANC
652 while not self.replication_complete():
655 self.assertTrue(self.used_get_anc,
656 "Test didn't use the GET_ANC flag as expected")
658 # Check we get all the objects we're expecting
659 self.assert_expected_data(expected_dn_list)
661 # Check we received links for all the parents
662 self.assert_expected_links(parent_dn_list)
664 def test_repl_get_tgt_and_anc(self):
666 Check we can resolve an unknown ancestor when fetching the link target,
667 i.e. tests using GET_TGT and GET_ANC in combination
670 # Create some parent/child objects (the child will be the link target)
672 all_objects = self.create_object_range(0, 100, prefix="parent",
676 children = [item for item in all_objects if item not in parents]
678 # create the link source objects and link them to the child/target
679 la_sources = self.create_object_range(0, 100, prefix="la_src")
680 all_objects += la_sources
682 for i in range(0, 100):
683 self.modify_object(la_sources[i], "managedBy", children[i])
685 expected_links = la_sources
687 # modify the children/targets so they come after the link source
688 for x in range(0, 100):
689 self.modify_object(children[x], "displayName", "OU%d" % x)
691 # modify the parents, so they now come last in the replication
692 for x in range(0, 100):
693 self.modify_object(parents[x], "displayName", "OU%d" % x)
695 # We've now got objects in the following order:
696 # [100 la_source][100 la_target][100 parents (of la_target)]
698 links_expected = True
700 # Get all the replication data - this code should resend the requests
701 # with GET_TGT and GET_ANC
702 while not self.replication_complete():
704 # get the next block of replication data (this sets GET_TGT/GET_ANC)
705 self.repl_get_next(assert_links=links_expected)
706 links_expected = len(self.rxd_links) < len(expected_links)
708 # The way the test objects have been created should force
709 # self.repl_get_next() to use the GET_TGT/GET_ANC flags. If this
710 # doesn't actually happen, then the test isn't doing its job properly
711 self.assertTrue(self.used_get_tgt,
712 "Test didn't use the GET_TGT flag as expected")
713 self.assertTrue(self.used_get_anc,
714 "Test didn't use the GET_ANC flag as expected")
716 # Check we get all the objects we're expecting
717 self.assert_expected_data(all_objects)
719 # Check we received links for all the link sources
720 self.assert_expected_links(expected_links)
722 # Second part of test. Add some extra objects and kick off another
723 # replication. The test code will use the HWM from the last replication
724 # so we'll only receive the objects we modify below
725 self.start_new_repl_cycle()
727 # add an extra level of grandchildren that hang off a child
728 # that got created last time
729 new_parent = "OU=test_new_parent,%s" % children[0]
730 self.add_object(new_parent)
733 for x in range(0, 50):
734 dn = "OU=test_new_la_tgt%d,%s" % (x, new_parent)
736 new_children.append(dn)
738 # replace half of the links to point to the new children
739 for x in range(0, 50):
740 self.delete_attribute(la_sources[x], "managedBy", children[x])
741 self.modify_object(la_sources[x], "managedBy", new_children[x])
743 # add some filler objects to fill up the 1st chunk
744 filler = self.create_object_range(0, 100, prefix="filler")
746 # modify the new children/targets so they come after the link source
747 for x in range(0, 50):
748 self.modify_object(new_children[x], "displayName", "OU-%d" % x)
750 # modify the parent, so it now comes last in the replication
751 self.modify_object(new_parent, "displayName", "OU%d" % x)
753 # We should now get the modified objects in the following order:
754 # [50 links (x 2)][100 filler][50 new children][new parent]
755 # Note that the link sources aren't actually sent (their new linked
756 # attributes are sent, but apart from that, nothing has changed)
757 all_objects = filler + new_children + [new_parent]
758 expected_links = la_sources[:50]
760 links_expected = True
762 while not self.replication_complete():
763 self.repl_get_next(assert_links=links_expected)
764 links_expected = len(self.rxd_links) < len(expected_links)
766 self.assertTrue(self.used_get_tgt,
767 "Test didn't use the GET_TGT flag as expected")
768 self.assertTrue(self.used_get_anc,
769 "Test didn't use the GET_ANC flag as expected")
771 # Check we get all the objects we're expecting
772 self.assert_expected_data(all_objects)
774 # Check we received links (50 deleted links and 50 new)
775 self.assert_expected_links(expected_links, num_expected=100)
777 def _repl_integrity_obj_deletion(self, delete_link_source=True):
779 Tests deleting link objects while a replication is in progress.
782 # create some objects and link them together, with some filler
783 # object in between the link sources
784 la_sources = self.create_object_range(0, 100, prefix="la_source")
785 la_targets = self.create_object_range(0, 100, prefix="la_targets")
787 for i in range(0, 50):
788 self.modify_object(la_sources[i], "managedBy", la_targets[i])
790 filler = self.create_object_range(0, 100, prefix="filler")
792 for i in range(50, 100):
793 self.modify_object(la_sources[i], "managedBy", la_targets[i])
795 # touch the targets so that the sources get replicated first
796 for i in range(0, 100):
797 self.modify_object(la_targets[i], "displayName", "OU%d" % i)
799 # objects should now be in the following USN order:
800 # [50 la_source][100 filler][50 la_source][100 la_target]
802 # Get the first block containing 50 link sources
805 # delete either the link targets or link source objects
806 if delete_link_source:
807 objects_to_delete = la_sources
808 # in GET_TGT testenvs we only receive the first 50 source objects
809 expected_objects = la_sources[:50] + la_targets + filler
811 objects_to_delete = la_targets
812 expected_objects = la_sources + filler
814 for obj in objects_to_delete:
815 self.ldb_dc2.delete(obj)
817 # complete the replication
818 while not self.replication_complete():
821 # Check we get all the objects we're expecting
822 self.assert_expected_data(expected_objects)
824 # we can't use assert_expected_links() here because it tries to check
825 # against the deleted objects on the DC. (Although we receive some
826 # links from the first block processed, the Samba client should end up
827 # deleting these, as the source/target object involved is deleted)
828 self.assertTrue(len(self.rxd_links) == 50,
829 "Expected 50 links, not %d" % len(self.rxd_links))
831 def test_repl_integrity_src_obj_deletion(self):
832 self._repl_integrity_obj_deletion(delete_link_source=True)
834 def test_repl_integrity_tgt_obj_deletion(self):
835 self._repl_integrity_obj_deletion(delete_link_source=False)