summaryrefslogtreecommitdiff
path: root/source4/torture/drs
diff options
context:
space:
mode:
Diffstat (limited to 'source4/torture/drs')
-rw-r--r--source4/torture/drs/python/drs_base.py26
-rw-r--r--source4/torture/drs/python/getncchanges.py215
2 files changed, 212 insertions, 29 deletions
diff --git a/source4/torture/drs/python/drs_base.py b/source4/torture/drs/python/drs_base.py
index 57bb0545ea0..79d40a98fb5 100644
--- a/source4/torture/drs/python/drs_base.py
+++ b/source4/torture/drs/python/drs_base.py
@@ -59,6 +59,7 @@ class DrsBaseTestCase(SambaToolCmdTest):
url_dc = samba.tests.env_get_var_value("DC2")
(self.ldb_dc2, self.info_dc2) = samba.tests.connect_samdb_ex(url_dc,
ldap_only=True)
+ self.test_ldb_dc = self.ldb_dc1
# cache some of RootDSE props
self.schema_dn = self.info_dc1["schemaNamingContext"][0]
@@ -76,8 +77,12 @@ class DrsBaseTestCase(SambaToolCmdTest):
def tearDown(self):
super(DrsBaseTestCase, self).tearDown()
+ def set_test_ldb_dc(self, ldb_dc):
+ """Sets which DC's LDB we perform operations on during the test"""
+ self.test_ldb_dc = ldb_dc
+
def _GUID_string(self, guid):
- return self.ldb_dc1.schema_format_value("objectGUID", guid)
+ return self.test_ldb_dc.schema_format_value("objectGUID", guid)
def _ldap_schemaUpdateNow(self, sam_db):
rec = {"dn": "",
@@ -220,6 +225,17 @@ class DrsBaseTestCase(SambaToolCmdTest):
return ctr6_links
+ def _get_ctr6_object_guids(self, ctr6):
+ """Returns all the object GUIDs in a GetNCChanges response"""
+ guid_list = []
+
+ obj = ctr6.first_object
+ for i in range(0, ctr6.object_count):
+ guid_list.append(str(obj.object.identifier.guid))
+ obj = obj.next_object
+
+ return guid_list
+
def _ctr6_debug(self, ctr6):
"""
Displays basic info contained in a DsGetNCChanges response.
@@ -257,15 +273,15 @@ class DrsBaseTestCase(SambaToolCmdTest):
and returns the response received from the DC.
"""
if source_dsa is None:
- source_dsa = self.ldb_dc1.get_ntds_GUID()
+ source_dsa = self.test_ldb_dc.get_ntds_GUID()
if invocation_id is None:
- invocation_id = self.ldb_dc1.get_invocation_id()
+ invocation_id = self.test_ldb_dc.get_invocation_id()
if nc_dn_str is None:
- nc_dn_str = self.ldb_dc1.domain_dn()
+ nc_dn_str = self.test_ldb_dc.domain_dn()
if highwatermark is None:
if self.default_hwm is None:
- (highwatermark, _) = self._get_highest_hwm_utdv(self.ldb_dc1)
+ (highwatermark, _) = self._get_highest_hwm_utdv(self.test_ldb_dc)
else:
highwatermark = self.default_hwm
diff --git a/source4/torture/drs/python/getncchanges.py b/source4/torture/drs/python/getncchanges.py
index 7d4813329d9..c87523dcea5 100644
--- a/source4/torture/drs/python/getncchanges.py
+++ b/source4/torture/drs/python/getncchanges.py
@@ -31,52 +31,64 @@ import drs_base
import samba.tests
import ldb
from ldb import SCOPE_BASE
+import random
from samba.dcerpc import drsuapi
class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
def setUp(self):
super(DrsReplicaSyncIntegrityTestCase, self).setUp()
- self.base_dn = self.ldb_dc1.get_default_basedn()
- self.ou = "OU=uptodateness_test,%s" % self.base_dn
- self.ldb_dc1.add({
+
+ # Note that DC2 is the DC with the testenv-specific quirks (e.g. it's
+ # the vampire_dc), so we point this test directly at that DC
+ self.set_test_ldb_dc(self.ldb_dc2)
+ (self.drs, self.drs_handle) = self._ds_bind(self.dnsname_dc2)
+
+ # add some randomness to the test OU. (Deletion of the last test's
+ # objects can be slow to replicate out. So the OU created by a previous
+ # testenv may still exist at this point).
+ rand = random.randint(1, 10000000)
+ self.base_dn = self.test_ldb_dc.get_default_basedn()
+ self.ou = "OU=getncchanges%d_test,%s" %(rand, self.base_dn)
+ self.test_ldb_dc.add({
"dn": self.ou,
"objectclass": "organizationalUnit"})
- (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.default_hwm, self.default_utdv) = self._get_highest_hwm_utdv(self.test_ldb_dc)
self.rxd_dn_list = []
self.rxd_links = []
+ self.rxd_guids = []
# 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
self.last_ctr = None
- # store whether we used GET_ANC flags in the requests
+ # store whether we used GET_TGT/GET_ANC flags in the requests
+ self.used_get_tgt = False
self.used_get_anc = False
def tearDown(self):
super(DrsReplicaSyncIntegrityTestCase, self).tearDown()
# tidyup groups and users
try:
- self.ldb_dc1.delete(self.ou, ["tree_delete:1"])
+ self.ldb_dc2.delete(self.ou, ["tree_delete:1"])
except ldb.LdbError as (enum, string):
if enum == ldb.ERR_NO_SUCH_OBJECT:
pass
def add_object(self, dn):
"""Adds an OU object"""
- self.ldb_dc1.add({"dn": dn, "objectclass": "organizationalunit"})
- res = self.ldb_dc1.search(base=dn, scope=SCOPE_BASE)
+ self.test_ldb_dc.add({"dn": dn, "objectclass": "organizationalunit"})
+ res = self.test_ldb_dc.search(base=dn, scope=SCOPE_BASE)
self.assertEquals(len(res), 1)
def modify_object(self, dn, attr, value):
"""Modifies an object's USN by adding an attribute value to it"""
m = ldb.Message()
- m.dn = ldb.Dn(self.ldb_dc1, dn)
+ m.dn = ldb.Dn(self.test_ldb_dc, dn)
m[attr] = ldb.MessageElement(value, ldb.FLAG_MOD_ADD, attr)
- self.ldb_dc1.modify(m)
+ self.test_ldb_dc.modify(m)
def create_object_range(self, start, end, prefix="",
children=None, parent_list=None):
@@ -149,7 +161,7 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
# Create a range of objects to replicate.
expected_dn_list = self.create_object_range(0, 400)
- (orig_hwm, unused) = self._get_highest_hwm_utdv(self.ldb_dc1)
+ (orig_hwm, unused) = self._get_highest_hwm_utdv(self.test_ldb_dc)
# We ask for the first page of 100 objects.
# For this test, we don't care what order we receive the objects in,
@@ -160,7 +172,7 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
for x in range(100, 200):
self.modify_object(expected_dn_list[x], "displayName", "OU%d" % x)
- (post_modify_hwm, unused) = self._get_highest_hwm_utdv(self.ldb_dc1)
+ (post_modify_hwm, unused) = self._get_highest_hwm_utdv(self.test_ldb_dc)
self.assertTrue(post_modify_hwm.highest_usn > orig_hwm.highest_usn)
# Get the remaining blocks of data
@@ -190,7 +202,7 @@ 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_send_request(self, get_anc=False):
+ def _repl_send_request(self, get_anc=False, get_tgt=False):
"""Sends a GetNCChanges request for the next block of replication data."""
# we're just trying to mimic regular client behaviour here, so just
@@ -205,44 +217,56 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
# Ask for the next block of replication data
replica_flags = drsuapi.DRSUAPI_DRS_WRIT_REP
+ more_flags = 0
if get_anc:
replica_flags = drsuapi.DRSUAPI_DRS_WRIT_REP | drsuapi.DRSUAPI_DRS_GET_ANC
self.used_get_anc = True
+ if get_tgt:
+ more_flags = drsuapi.DRSUAPI_DRS_GET_TGT
+ self.used_get_tgt = True
+
# return the response from the DC
return self._get_replication(replica_flags,
max_objects=self.max_objects,
highwatermark=highwatermark,
- uptodateness_vector=uptodateness_vector)
+ uptodateness_vector=uptodateness_vector,
+ more_flags=more_flags)
- def repl_get_next(self, get_anc=False):
+ def repl_get_next(self, get_anc=False, get_tgt=False, assert_links=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.
+ If we don't know the target object for a linked attribute, then
+ re-request with GET_TGT.
"""
# send a request to the DC and get the response
- ctr6 = self._repl_send_request(get_anc=get_anc)
+ ctr6 = self._repl_send_request(get_anc=get_anc, get_tgt=get_tgt)
- # check that we know the parent for every object received
+ # extract the object DNs and their GUIDs from the response
rxd_dn_list = self._get_ctr6_dn_list(ctr6)
+ rxd_guid_list = self._get_ctr6_object_guids(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
+ # ones we already know about, so we can modify these lists safely
known_objects = self.rxd_dn_list[:]
+ known_guids = self.rxd_guids[:]
# check that we know the parent for every object received
for i in range(0, len(rxd_dn_list)):
dn = rxd_dn_list[i]
+ guid = rxd_guid_list[i]
if self.is_parent_known(dn, known_objects):
# the new DN is now known so add it to the list.
# It may be the parent of another child in this block
known_objects.append(dn)
+ known_guids.append(guid)
else:
# If we've already set the GET_ANC flag then it should mean
# we receive the parents before the child
@@ -251,14 +275,57 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
print("Unknown parent for %s - try GET_ANC" % dn)
# try the same thing again with the GET_ANC flag set this time
- return self.repl_get_next(get_anc=True)
+ return self.repl_get_next(get_anc=True, get_tgt=get_tgt,
+ assert_links=assert_links)
+
+ # check we know about references to any objects in the linked attritbutes
+ received_links = self._get_ctr6_links(ctr6)
+
+ # This is so that older versions of Samba fail - we want the links to be
+ # sent roughly with the objects, rather than getting all links at the end
+ if assert_links:
+ self.assertTrue(len(received_links) > 0,
+ "Links were expected in the GetNCChanges response")
+
+ for link in received_links:
+
+ # check the source object is known (Windows can actually send links
+ # where we don't know the source object yet). Samba shouldn't ever
+ # hit this case because it gets the links based on the source
+ if link.identifier not in known_guids:
+
+ # If we've already set the GET_ANC flag then it should mean
+ # this case doesn't happen
+ self.assertFalse(get_anc, "Unknown source object for GUID %s"
+ % link.identifier)
+
+ print("Unknown source GUID %s - try GET_ANC" % link.identifier)
+
+ # try the same thing again with the GET_ANC flag set this time
+ return self.repl_get_next(get_anc=True, get_tgt=get_tgt,
+ assert_links=assert_links)
+
+ # check we know the target object
+ if link.targetGUID not in known_guids:
+
+ # If we've already set the GET_TGT flag then we should have
+ # already received any objects we need to know about
+ self.assertFalse(get_tgt, "Unknown linked target for object %s"
+ % link.targetDN)
+
+ print("Unknown target for %s - try GET_TGT" % link.targetDN)
+
+ # try the same thing again with the GET_TGT flag set this time
+ return self.repl_get_next(get_anc=get_anc, get_tgt=True,
+ assert_links=assert_links)
# store the last successful result so we know what HWM to request next
self.last_ctr = ctr6
- # store the objects and links we received
+ # store the objects, GUIDs, and links we received
self.rxd_dn_list += self._get_ctr6_dn_list(ctr6)
self.rxd_links += self._get_ctr6_links(ctr6)
+ self.rxd_guids += self._get_ctr6_object_guids(ctr6)
return ctr6
@@ -360,8 +427,8 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
# 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)
+ res = self.test_ldb_dc.search(ldb.Dn(self.test_ldb_dc, 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...
@@ -372,7 +439,7 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
# 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)
+ target_dn = ldb.Dn(self.test_ldb_dc, val)
targetGUID_blob = target_dn.get_extended_component("GUID")
sourceGUID_blob = res[0].dn.get_extended_component("GUID")
@@ -390,6 +457,106 @@ class DrsReplicaSyncIntegrityTestCase(drs_base.DrsBaseTestCase):
self.assertTrue(found, "Did not receive expected link for DN %s" % dn)
+ def test_repl_get_tgt(self):
+ """
+ Creates a scenario where we should receive the linked attribute before
+ we know about the target object, and therefore need to use GET_TGT.
+ Note: Samba currently avoids this problem by sending all its links last
+ """
+
+ # create the test objects
+ reportees = self.create_object_range(0, 100, prefix="reportee")
+ managers = self.create_object_range(0, 100, prefix="manager")
+ all_objects = managers + reportees
+ expected_links = reportees
+
+ # add a link attribute to each reportee object that points to the
+ # corresponding manager object as the target
+ for i in range(0, 100):
+ self.modify_object(reportees[i], "managedBy", managers[i])
+
+ # touch the managers (the link-target objects) again to make sure the
+ # reportees (link source objects) get returned first by the replication
+ for i in range(0, 100):
+ self.modify_object(managers[i], "displayName", "OU%d" % i)
+
+ links_expected = True
+
+ # Get all the replication data - this code should resend the requests
+ # with GET_TGT
+ while not self.replication_complete():
+
+ # get the next block of replication data (this sets GET_TGT if needed)
+ self.repl_get_next(assert_links=links_expected)
+ links_expected = len(self.rxd_links) < len(expected_links)
+
+ # The way the test objects have been created should force
+ # self.repl_get_next() to use the GET_TGT flag. If this doesn't
+ # actually happen, then the test isn't doing its job properly
+ self.assertTrue(self.used_get_tgt,
+ "Test didn't use the GET_TGT flag as expected")
+
+ # Check we get all the objects we're expecting
+ self.assert_expected_data(all_objects)
+
+ # Check we received links for all the reportees
+ self.assert_expected_links(expected_links)
+
+ def test_repl_get_tgt_chain(self):
+ """
+ Tests the behaviour of GET_TGT with a more complicated scenario.
+ Here we create a chain of objects linked together, so if we follow
+ the link target, then we'd traverse ~200 objects each time.
+ """
+
+ # create the test objects
+ objectsA = self.create_object_range(0, 100, prefix="AAA")
+ objectsB = self.create_object_range(0, 100, prefix="BBB")
+ objectsC = self.create_object_range(0, 100, prefix="CCC")
+
+ # create a complex set of object links:
+ # A0-->B0-->C1-->B2-->C3-->B4-->and so on...
+ # Basically each object-A should link to a circular chain of 200 B/C
+ # objects. We create the links in separate chunks here, as it makes it
+ # clearer what happens with the USN (links on Windows have their own
+ # USN, so this approach means the A->B/B->C links aren't interleaved)
+ for i in range(0, 100):
+ self.modify_object(objectsA[i], "managedBy", objectsB[i])
+
+ for i in range(0, 100):
+ self.modify_object(objectsB[i], "managedBy", objectsC[(i + 1) % 100])
+
+ for i in range(0, 100):
+ self.modify_object(objectsC[i], "managedBy", objectsB[(i + 1) % 100])
+
+ all_objects = objectsA + objectsB + objectsC
+ expected_links = all_objects
+
+ # the default order the objects now get returned in should be:
+ # [A0-A99][B0-B99][C0-C99]
+
+ links_expected = True
+
+ # Get all the replication data - this code should resend the requests
+ # with GET_TGT
+ while not self.replication_complete():
+
+ # get the next block of replication data (this sets GET_TGT if needed)
+ self.repl_get_next(assert_links=links_expected)
+ links_expected = len(self.rxd_links) < len(expected_links)
+
+ # The way the test objects have been created should force
+ # self.repl_get_next() to use the GET_TGT flag. If this doesn't
+ # actually happen, then the test isn't doing its job properly
+ self.assertTrue(self.used_get_tgt,
+ "Test didn't use the GET_TGT flag as expected")
+
+ # Check we get all the objects we're expecting
+ self.assert_expected_data(all_objects)
+
+ # Check we received links for all the reportees
+ self.assert_expected_links(expected_links)
+
def test_repl_get_anc_link_attr(self):
"""
A basic GET_ANC test where the parents have linked attributes