summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source4/torture/drs/python/drs_base.py46
-rw-r--r--source4/torture/drs/python/getncchanges.py150
2 files changed, 157 insertions, 39 deletions
diff --git a/source4/torture/drs/python/drs_base.py b/source4/torture/drs/python/drs_base.py
index b2df1812b8e..b19c51ac27f 100644
--- a/source4/torture/drs/python/drs_base.py
+++ b/source4/torture/drs/python/drs_base.py
@@ -197,6 +197,27 @@ class DrsBaseTestCase(SambaToolCmdTest):
id.dn = str(res[0].dn)
return id
+ def _get_ctr6_links(self, ctr6):
+ """
+ Unpacks the linked attributes from a DsGetNCChanges response
+ and returns them as a list.
+ """
+ ctr6_links = []
+ for lidx in range(0, ctr6.linked_attributes_count):
+ l = ctr6.linked_attributes[lidx]
+ try:
+ target = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
+ l.value.blob)
+ except:
+ target = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3Binary,
+ l.value.blob)
+ al = AbstractLink(l.attid, l.flags,
+ l.identifier.guid,
+ target.guid, target.dn)
+ ctr6_links.append(al)
+
+ return ctr6_links
+
def _ctr6_debug(self, ctr6):
"""
Displays basic info contained in a DsGetNCChanges response.
@@ -214,6 +235,11 @@ class DrsBaseTestCase(SambaToolCmdTest):
next_object = next_object.next_object
print("Linked Attributes: %d" % ctr6.linked_attributes_count)
+ ctr6_links = self._get_ctr6_links(ctr6)
+ for link in ctr6_links:
+ print("Link Tgt %s... <-- Src %s"
+ %(link.targetDN[:22], link.identifier))
+
print("HWM: %d" %(ctr6.new_highwatermark.highest_usn))
print("Tmp HWM: %d" %(ctr6.new_highwatermark.tmp_highest_usn))
print("More data: %d" %(ctr6.more_data))
@@ -343,21 +369,9 @@ class DrsBaseTestCase(SambaToolCmdTest):
else:
self.assertTrue(dn in ctr6_dns, "Couldn't find DN '%s' anywhere in ctr6 response." % dn)
- ctr6_links = []
+ # Extract the links from the response
+ ctr6_links = self._get_ctr6_links(ctr6)
expected_links.sort()
- lidx = 0
- for lidx in range(0, ctr6.linked_attributes_count):
- l = ctr6.linked_attributes[lidx]
- try:
- target = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3,
- l.value.blob)
- except:
- target = ndr_unpack(drsuapi.DsReplicaObjectIdentifier3Binary,
- l.value.blob)
- al = AbstractLink(l.attid, l.flags,
- l.identifier.guid,
- target.guid)
- ctr6_links.append(al)
lidx = 0
for el in expected_links:
@@ -438,13 +452,15 @@ class DrsBaseTestCase(SambaToolCmdTest):
class AbstractLink:
- def __init__(self, attid, flags, identifier, targetGUID):
+ def __init__(self, attid, flags, identifier, targetGUID,
+ targetDN=""):
self.attid = attid
self.flags = flags
self.identifier = str(identifier)
self.selfGUID_blob = ndr_pack(identifier)
self.targetGUID = str(targetGUID)
self.targetGUID_blob = ndr_pack(targetGUID)
+ self.targetDN = targetDN
def __repr__(self):
return "AbstractLink(0x%08x, 0x%08x, %s, %s)" % (
diff --git a/source4/torture/drs/python/getncchanges.py b/source4/torture/drs/python/getncchanges.py
index 2f914d8dd40..7d4813329d9 100644
--- a/source4/torture/drs/python/getncchanges.py
+++ b/source4/torture/drs/python/getncchanges.py
@@ -45,6 +45,9 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
(self.drs, self.drs_handle) = self._ds_bind(self.dnsname_dc1)
(self.default_hwm, self.default_utdv) = self._get_highest_hwm_utdv(self.ldb_dc1)
+ self.rxd_dn_list = []
+ self.rxd_links = []
+
# 100 is the minimum max_objects that Microsoft seems to honour
# (the max honoured is 400ish), so we use that in these tests
self.max_objects = 100
@@ -114,11 +117,12 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
return dn_list
- def assert_expected_data(self, received_list, expected_list):
+ def assert_expected_data(self, expected_list):
"""
Asserts that we received all the DNs that we expected and
none are missing.
"""
+ received_list = self.rxd_dn_list
# Note that with GET_ANC Windows can end up sending the same parent
# object multiple times, so this might be noteworthy but doesn't
@@ -150,9 +154,7 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
# We ask for the first page of 100 objects.
# For this test, we don't care what order we receive the objects in,
# so long as by the end we've received everything
- rxd_dn_list = []
- ctr6 = self.repl_get_next(rxd_dn_list)
- rxd_dn_list = self._get_ctr6_dn_list(ctr6)
+ self.repl_get_next()
# Modify some of the second page of objects. This should bump the highwatermark
for x in range(100, 200):
@@ -163,11 +165,10 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
# Get the remaining blocks of data
while not self.replication_complete():
- ctr6 = self.repl_get_next(rxd_dn_list)
- rxd_dn_list += self._get_ctr6_dn_list(ctr6)
+ self.repl_get_next()
# Check we still receive all the objects we're expecting
- self.assert_expected_data(rxd_dn_list, expected_dn_list)
+ self.assert_expected_data(expected_dn_list)
def is_parent_known(self, dn, known_dn_list):
"""
@@ -189,12 +190,8 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
# test object), or its parent has been seen previously
return parent_dn == self.ou or parent_dn in known_dn_list
- def repl_get_next(self, initial_objects, get_anc=False):
- """
- Requests the next block of replication data. This tries to simulate
- client behaviour - if we receive a replicated object that we don't know
- the parent of, then re-request the block with the GET_ANC flag set.
- """
+ def _repl_send_request(self, get_anc=False):
+ """Sends a GetNCChanges request for the next block of replication data."""
# we're just trying to mimic regular client behaviour here, so just
# use the highwatermark in the last response we received
@@ -202,13 +199,10 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
highwatermark = self.last_ctr.new_highwatermark
uptodateness_vector = self.last_ctr.uptodateness_vector
else:
- # this is the initial replication, so we're starting from the start
+ # this is the first replication chunk
highwatermark = None
uptodateness_vector = None
- # we'll add new objects as we discover them, so take a copy to modify
- known_objects = initial_objects[:]
-
# Ask for the next block of replication data
replica_flags = drsuapi.DRSUAPI_DRS_WRIT_REP
@@ -216,14 +210,30 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
replica_flags = drsuapi.DRSUAPI_DRS_WRIT_REP | drsuapi.DRSUAPI_DRS_GET_ANC
self.used_get_anc = True
- ctr6 = self._get_replication(replica_flags,
+ # return the response from the DC
+ return self._get_replication(replica_flags,
max_objects=self.max_objects,
highwatermark=highwatermark,
uptodateness_vector=uptodateness_vector)
+ def repl_get_next(self, get_anc=False):
+ """
+ Requests the next block of replication data. This tries to simulate
+ client behaviour - if we receive a replicated object that we don't know
+ the parent of, then re-request the block with the GET_ANC flag set.
+ """
+
+ # send a request to the DC and get the response
+ ctr6 = self._repl_send_request(get_anc=get_anc)
+
# check that we know the parent for every object received
rxd_dn_list = self._get_ctr6_dn_list(ctr6)
+ # we'll add new objects as we discover them, so take a copy of the
+ # ones we already know about, so we can modify the list safely
+ known_objects = self.rxd_dn_list[:]
+
+ # check that we know the parent for every object received
for i in range(0, len(rxd_dn_list)):
dn = rxd_dn_list[i]
@@ -246,6 +256,10 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
# store the last successful result so we know what HWM to request next
self.last_ctr = ctr6
+ # store the objects and links we received
+ self.rxd_dn_list += self._get_ctr6_dn_list(ctr6)
+ self.rxd_links += self._get_ctr6_links(ctr6)
+
return ctr6
def replication_complete(self):
@@ -300,9 +314,7 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
# GET_ANC set because we won't know about the first child's parent.
# On samba GET_ANC essentially starts the sync from scratch again, so
# we get this over with early before we learn too many parents
- rxd_dn_list = []
- ctr6 = self.repl_get_next(rxd_dn_list)
- rxd_dn_list = self._get_ctr6_dn_list(ctr6)
+ self.repl_get_next()
# modify the last chunk of parents. They should now have a USN higher
# than the highwater-mark for the replication cycle
@@ -312,8 +324,7 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
# Get the remaining blocks of data - this will resend the request with
# GET_ANC if it encounters an object it doesn't have the parent for.
while not self.replication_complete():
- ctr6 = self.repl_get_next(rxd_dn_list)
- rxd_dn_list += self._get_ctr6_dn_list(ctr6)
+ self.repl_get_next()
# The way the test objects have been created should force
# self.repl_get_next() to use the GET_ANC flag. If this doesn't
@@ -322,5 +333,96 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
"Test didn't use the GET_ANC flag as expected")
# Check we get all the objects we're expecting
- self.assert_expected_data(rxd_dn_list, expected_dn_list)
+ self.assert_expected_data(expected_dn_list)
+
+ def assert_expected_links(self, objects_with_links, link_attr="managedBy"):
+ """
+ Asserts that a GetNCChanges response contains any expected links
+ for the objects it contains.
+ """
+ received_links = self.rxd_links
+
+ num_expected = len(objects_with_links)
+
+ self.assertTrue(len(received_links) == num_expected,
+ "Received %d links but expected %d"
+ %(len(received_links), num_expected))
+
+ for dn in objects_with_links:
+ self.assert_object_has_link(dn, link_attr, received_links)
+
+ def assert_object_has_link(self, dn, link_attr, received_links):
+ """
+ Queries the object in the DB and asserts there is a link in the
+ GetNCChanges response that matches.
+ """
+
+ # Look up the link attribute in the DB
+ # The extended_dn option will dump the GUID info for the link
+ # attribute (as a hex blob)
+ res = self.ldb_dc1.search(ldb.Dn(self.ldb_dc1, dn), attrs=[link_attr],
+ controls=['extended_dn:1:0'], scope=ldb.SCOPE_BASE)
+
+ # We didn't find the expected link attribute in the DB for the object.
+ # Something has gone wrong somewhere...
+ self.assertTrue(link_attr in res[0], "%s in DB doesn't have attribute %s"
+ %(dn, link_attr))
+
+ # find the received link in the list and assert that the target and
+ # source GUIDs match what's in the DB
+ for val in res[0][link_attr]:
+ # Work out the expected source and target GUIDs for the DB link
+ target_dn = ldb.Dn(self.ldb_dc1, val)
+ targetGUID_blob = target_dn.get_extended_component("GUID")
+ sourceGUID_blob = res[0].dn.get_extended_component("GUID")
+
+ found = False
+
+ for link in received_links:
+ if link.selfGUID_blob == sourceGUID_blob and \
+ link.targetGUID_blob == targetGUID_blob:
+
+ found = True
+
+ if self._debug:
+ print("Link %s --> %s" %(dn[:25], link.targetDN[:25]))
+ break
+
+ self.assertTrue(found, "Did not receive expected link for DN %s" % dn)
+
+ def test_repl_get_anc_link_attr(self):
+ """
+ A basic GET_ANC test where the parents have linked attributes
+ """
+
+ # Create a block of 100 parents and 100 children
+ parent_dn_list = []
+ expected_dn_list = self.create_object_range(0, 100, prefix="parent",
+ children=("A"),
+ parent_list=parent_dn_list)
+
+ # Add links from the parents to the children
+ for x in range(0, 100):
+ self.modify_object(parent_dn_list[x], "managedBy", expected_dn_list[x + 100])
+
+ # add some filler objects at the end. This allows us to easily see
+ # which chunk the links get sent in
+ expected_dn_list += self.create_object_range(0, 100, prefix="filler")
+
+ # We've now got objects in the following order:
+ # [100 x children][100 x parents][100 x filler]
+
+ # Get the replication data - because the block of children come first,
+ # this should retry the request with GET_ANC
+ while not self.replication_complete():
+ self.repl_get_next()
+
+ self.assertTrue(self.used_get_anc,
+ "Test didn't use the GET_ANC flag as expected")
+
+ # Check we get all the objects we're expecting
+ self.assert_expected_data(expected_dn_list)
+
+ # Check we received links for all the parents
+ self.assert_expected_links(parent_dn_list)