Browse Source

Ticket 47828: DNA scope: allow to exlude some subtrees

Bug Description:
	DNA plugin generates attributes values to any added entry
	in the scope of the plugin (and filter).
	For ULC (http://www.freeipa.org/page/V3/User_Life-Cycle_Management),
	the provisioning container must be excluded.

Fix Description:
	Add the ability to define multivalued attribute: "dnaExcludeScope"
	in the config entry.
	DNA will not generate attributes values for entries added
	under one of the excluded DIT

http://fedorahosted.org/389/ticket/47828ticket URL>

Platforms tested: F17/F20

Reviewed by : Noriko (many thanks)

Flag Day: no

Doc impact: yes (adding a config attribute)
Thierry bordaz (tbordaz) 11 years ago
parent
commit
b43e899454

+ 721 - 0
dirsrvtests/tickets/ticket47828_test.py

@@ -0,0 +1,721 @@
+import os
+import sys
+import time
+import ldap
+import logging
+import socket
+import pytest
+import shutil
+from lib389 import DirSrv, Entry, tools
+from lib389.tools import DirSrvTools
+from lib389._constants import *
+from lib389.properties import *
+from constants import *
+
+log = logging.getLogger(__name__)
+
+installation_prefix = None
+
+ACCT_POLICY_CONFIG_DN = 'cn=config,cn=%s,cn=plugins,cn=config' % PLUGIN_ACCT_POLICY 
+ACCT_POLICY_DN = 'cn=Account Inactivation Pplicy,%s' % SUFFIX
+INACTIVITY_LIMIT = '9'
+SEARCHFILTER = '(objectclass=*)'
+
+DUMMY_CONTAINER = 'cn=dummy container,%s' % SUFFIX
+PROVISIONING = 'cn=provisioning,%s' % SUFFIX
+ACTIVE_USER1_CN = 'active user1'
+ACTIVE_USER1_DN = 'cn=%s,%s' % (ACTIVE_USER1_CN, SUFFIX)
+STAGED_USER1_CN = 'staged user1'
+STAGED_USER1_DN = 'cn=%s,%s' % (STAGED_USER1_CN, PROVISIONING)
+DUMMY_USER1_CN  = 'dummy user1'
+DUMMY_USER1_DN  = 'cn=%s,%s' % (DUMMY_USER1_CN, DUMMY_CONTAINER)
+
+ALLOCATED_ATTR = 'employeeNumber'
+
+class TopologyStandalone(object):
+    def __init__(self, standalone):
+        standalone.open()
+        self.standalone = standalone
+
+
[email protected](scope="module")
+def topology(request):
+    '''
+        This fixture is used to standalone topology for the 'module'.
+        At the beginning, It may exists a standalone instance.
+        It may also exists a backup for the standalone instance.
+
+        Principle:
+            If standalone instance exists:
+                restart it
+            If backup of standalone exists:
+                create/rebind to standalone
+
+                restore standalone instance from backup
+            else:
+                Cleanup everything
+                    remove instance
+                    remove backup
+                Create instance
+                Create backup
+    '''
+    global installation_prefix
+
+    if installation_prefix:
+        args_instance[SER_DEPLOYED_DIR] = installation_prefix
+
+    standalone = DirSrv(verbose=False)
+
+    # Args for the standalone instance
+    args_instance[SER_HOST] = HOST_STANDALONE
+    args_instance[SER_PORT] = PORT_STANDALONE
+    args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE
+    args_standalone = args_instance.copy()
+    standalone.allocate(args_standalone)
+
+    # Get the status of the backups
+    backup_standalone = standalone.checkBackupFS()
+
+    # Get the status of the instance and restart it if it exists
+    instance_standalone = standalone.exists()
+    if instance_standalone:
+        # assuming the instance is already stopped, just wait 5 sec max
+        standalone.stop(timeout=5)
+        try:
+            standalone.start(timeout=10)
+        except ldap.SERVER_DOWN:
+            pass
+
+    if backup_standalone:
+        # The backup exist, assuming it is correct
+        # we just re-init the instance with it
+        if not instance_standalone:
+            standalone.create()
+            # Used to retrieve configuration information (dbdir, confdir...)
+            standalone.open()
+
+        # restore standalone instance from backup
+        standalone.stop(timeout=10)
+        standalone.restoreFS(backup_standalone)
+        standalone.start(timeout=10)
+
+    else:
+        # We should be here only in two conditions
+        #      - This is the first time a test involve standalone instance
+        #      - Something weird happened (instance/backup destroyed)
+        #        so we discard everything and recreate all
+
+        # Remove the backup. So even if we have a specific backup file
+        # (e.g backup_standalone) we clear backup that an instance may have created
+        if backup_standalone:
+            standalone.clearBackupFS()
+
+        # Remove the instance
+        if instance_standalone:
+            standalone.delete()
+
+        # Create the instance
+        standalone.create()
+
+        # Used to retrieve configuration information (dbdir, confdir...)
+        standalone.open()
+
+        # Time to create the backups
+        standalone.stop(timeout=10)
+        standalone.backupfile = standalone.backupFS()
+        standalone.start(timeout=10)
+
+    #
+    # Here we have standalone instance up and running
+    # Either coming from a backup recovery
+    # or from a fresh (re)init
+    # Time to return the topology
+    return TopologyStandalone(standalone)
+
+def _header(topology, label):
+    topology.standalone.log.info("\n\n###############################################")
+    topology.standalone.log.info("#######")
+    topology.standalone.log.info("####### %s" % label)
+    topology.standalone.log.info("#######")
+    topology.standalone.log.info("###############################################")
+
+def test_ticket47828_init(topology):
+    """
+    Enable DNA
+    """
+    topology.standalone.plugins.enable(name=PLUGIN_DNA)
+
+    topology.standalone.add_s(Entry((PROVISIONING,{'objectclass': "top nscontainer".split(),
+                                                  'cn': 'provisioning'})))
+    topology.standalone.add_s(Entry((DUMMY_CONTAINER,{'objectclass': "top nscontainer".split(),
+                                                  'cn': 'dummy container'})))
+    
+    dn_config = "cn=excluded scope, cn=%s, %s" % (PLUGIN_DNA, DN_PLUGIN)
+    topology.standalone.add_s(Entry((dn_config, {'objectclass': "top extensibleObject".split(),
+                                                    'cn': 'excluded scope',
+                                                    'dnaType': ALLOCATED_ATTR,
+                                                    'dnaNextValue': str(1000),
+                                                    'dnaMaxValue': str(2000),
+                                                    'dnaMagicRegen': str(-1),
+                                                    'dnaFilter': '(&(objectClass=person)(objectClass=organizationalPerson)(objectClass=inetOrgPerson))',
+                                                    'dnaScope': SUFFIX})))
+    topology.standalone.restart(timeout=10)
+
+                                                    
+
+def test_ticket47828_run_0(topology):
+    """
+    NO exclude scope: Add an active entry and check its ALLOCATED_ATTR is set
+    """
+    _header(topology, 'NO exclude scope: Add an active entry and check its ALLOCATED_ATTR is set')
+
+    topology.standalone.add_s(Entry((ACTIVE_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': ACTIVE_USER1_CN,
+                                                'sn': ACTIVE_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(ACTIVE_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) != str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (ACTIVE_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(ACTIVE_USER1_DN)
+    
+def test_ticket47828_run_1(topology):
+    """
+    NO exclude scope: Add an active entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'NO exclude scope: Add an active entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((ACTIVE_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': ACTIVE_USER1_CN,
+                                                'sn': ACTIVE_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(ACTIVE_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (ACTIVE_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(ACTIVE_USER1_DN)
+    
+def test_ticket47828_run_2(topology):
+    """
+    NO exclude scope: Add a staged entry and check its ALLOCATED_ATTR is  set
+    """
+    _header(topology, 'NO exclude scope: Add a staged entry and check its ALLOCATED_ATTR is  set')
+
+    topology.standalone.add_s(Entry((STAGED_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': STAGED_USER1_CN,
+                                                'sn': STAGED_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(STAGED_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) != str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (STAGED_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(STAGED_USER1_DN)
+    
+def test_ticket47828_run_3(topology):
+    """
+    NO exclude scope: Add a staged entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'NO exclude scope: Add a staged entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((STAGED_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': STAGED_USER1_CN,
+                                                'sn': STAGED_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(STAGED_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (STAGED_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(STAGED_USER1_DN)
+    
+def test_ticket47828_run_4(topology):
+    '''
+    Exclude the provisioning container
+    '''
+    _header(topology, 'Exclude the provisioning container')
+    
+    dn_config = "cn=excluded scope, cn=%s, %s" % (PLUGIN_DNA, DN_PLUGIN)
+    mod = [(ldap.MOD_REPLACE, 'dnaExcludeScope', PROVISIONING)]
+    topology.standalone.modify_s(dn_config, mod)
+    
+def test_ticket47828_run_5(topology):
+    """
+    Provisioning excluded scope: Add an active entry and check its ALLOCATED_ATTR is set
+    """
+    _header(topology, 'Provisioning excluded scope: Add an active entry and check its ALLOCATED_ATTR is set')
+
+    topology.standalone.add_s(Entry((ACTIVE_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': ACTIVE_USER1_CN,
+                                                'sn': ACTIVE_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(ACTIVE_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) != str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (ACTIVE_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(ACTIVE_USER1_DN)
+    
+def test_ticket47828_run_6(topology):
+    """
+    Provisioning excluded scope: Add an active entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'Provisioning excluded scope: Add an active entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((ACTIVE_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': ACTIVE_USER1_CN,
+                                                'sn': ACTIVE_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(ACTIVE_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (ACTIVE_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(ACTIVE_USER1_DN)
+    
+def test_ticket47828_run_7(topology):
+    """
+    Provisioning excluded scope: Add a staged entry and check its ALLOCATED_ATTR is  not set
+    """
+    _header(topology, 'Provisioning excluded scope: Add a staged entry and check its ALLOCATED_ATTR is  not set')
+
+    topology.standalone.add_s(Entry((STAGED_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': STAGED_USER1_CN,
+                                                'sn': STAGED_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(STAGED_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (STAGED_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(STAGED_USER1_DN)
+    
+def test_ticket47828_run_8(topology):
+    """
+    Provisioning excluded scope: Add a staged entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'Provisioning excluded scope: Add a staged entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((STAGED_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': STAGED_USER1_CN,
+                                                'sn': STAGED_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(STAGED_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (STAGED_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(STAGED_USER1_DN)
+    
+def test_ticket47828_run_9(topology):
+    """
+    Provisioning excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is set
+    """
+    _header(topology, 'Provisioning excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is set')
+
+    topology.standalone.add_s(Entry((DUMMY_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': DUMMY_USER1_CN,
+                                                'sn': DUMMY_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(DUMMY_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) != str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (DUMMY_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(DUMMY_USER1_DN)
+    
+def test_ticket47828_run_10(topology):
+    """
+    Provisioning excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'Provisioning excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((DUMMY_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': DUMMY_USER1_CN,
+                                                'sn': DUMMY_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(DUMMY_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (DUMMY_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(DUMMY_USER1_DN)
+    
+def test_ticket47828_run_11(topology):
+    '''
+    Exclude (in addition) the dummy container
+    '''
+    _header(topology, 'Exclude (in addition) the dummy container')
+    
+    dn_config = "cn=excluded scope, cn=%s, %s" % (PLUGIN_DNA, DN_PLUGIN)
+    mod = [(ldap.MOD_ADD, 'dnaExcludeScope', DUMMY_CONTAINER)]
+    topology.standalone.modify_s(dn_config, mod)
+    
+def test_ticket47828_run_12(topology):
+    """
+    Provisioning/Dummy excluded scope: Add an active entry and check its ALLOCATED_ATTR is set
+    """
+    _header(topology, 'Provisioning/Dummy excluded scope: Add an active entry and check its ALLOCATED_ATTR is set')
+
+    topology.standalone.add_s(Entry((ACTIVE_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': ACTIVE_USER1_CN,
+                                                'sn': ACTIVE_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(ACTIVE_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) != str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (ACTIVE_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(ACTIVE_USER1_DN)
+    
+def test_ticket47828_run_13(topology):
+    """
+    Provisioning/Dummy excluded scope: Add an active entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'Provisioning/Dummy excluded scope: Add an active entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((ACTIVE_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': ACTIVE_USER1_CN,
+                                                'sn': ACTIVE_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(ACTIVE_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (ACTIVE_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(ACTIVE_USER1_DN)
+    
+def test_ticket47828_run_14(topology):
+    """
+    Provisioning/Dummy excluded scope: Add a staged entry and check its ALLOCATED_ATTR is not set
+    """
+    _header(topology, 'Provisioning/Dummy excluded scope: Add a staged entry and check its ALLOCATED_ATTR is not set')
+
+    topology.standalone.add_s(Entry((STAGED_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': STAGED_USER1_CN,
+                                                'sn': STAGED_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(STAGED_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (STAGED_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(STAGED_USER1_DN)
+    
+def test_ticket47828_run_15(topology):
+    """
+    Provisioning/Dummy excluded scope: Add a staged entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'Provisioning/Dummy excluded scope: Add a staged entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((STAGED_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': STAGED_USER1_CN,
+                                                'sn': STAGED_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(STAGED_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (STAGED_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(STAGED_USER1_DN)
+    
+def test_ticket47828_run_16(topology):
+    """
+    Provisioning/Dummy excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is not set
+    """
+    _header(topology, 'Provisioning/Dummy excluded scope: Add an dummy entry and check its ALLOCATED_ATTR not is set')
+
+    topology.standalone.add_s(Entry((DUMMY_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': DUMMY_USER1_CN,
+                                                'sn': DUMMY_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(DUMMY_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (DUMMY_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(DUMMY_USER1_DN)
+    
+def test_ticket47828_run_17(topology):
+    """
+    Provisioning/Dummy excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'Provisioning/Dummy excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((DUMMY_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': DUMMY_USER1_CN,
+                                                'sn': DUMMY_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(DUMMY_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (DUMMY_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(DUMMY_USER1_DN)
+    
+    
+def test_ticket47828_run_18(topology):
+    '''
+    Exclude PROVISIONING and a wrong container
+    '''
+    _header(topology, 'Exclude PROVISIONING and a wrong container')
+    
+    dn_config = "cn=excluded scope, cn=%s, %s" % (PLUGIN_DNA, DN_PLUGIN)
+    mod = [(ldap.MOD_REPLACE, 'dnaExcludeScope', PROVISIONING)]
+    topology.standalone.modify_s(dn_config, mod)
+    try:
+        mod = [(ldap.MOD_ADD, 'dnaExcludeScope', "invalidDN,%s" % SUFFIX)]
+        topology.standalone.modify_s(dn_config, mod)
+        raise ValueError("invalid dnaExcludeScope value (not a DN)")
+    except ldap.INVALID_SYNTAX:
+        pass
+    
+def test_ticket47828_run_19(topology):
+    """
+    Provisioning+wrong container excluded scope: Add an active entry and check its ALLOCATED_ATTR is set
+    """
+    _header(topology, 'Provisioning+wrong container excluded scope: Add an active entry and check its ALLOCATED_ATTR is set')
+
+    topology.standalone.add_s(Entry((ACTIVE_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': ACTIVE_USER1_CN,
+                                                'sn': ACTIVE_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(ACTIVE_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) != str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (ACTIVE_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(ACTIVE_USER1_DN)
+    
+def test_ticket47828_run_20(topology):
+    """
+    Provisioning+wrong container excluded scope: Add an active entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'Provisioning+wrong container excluded scope: Add an active entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((ACTIVE_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': ACTIVE_USER1_CN,
+                                                'sn': ACTIVE_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(ACTIVE_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (ACTIVE_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(ACTIVE_USER1_DN)
+    
+def test_ticket47828_run_21(topology):
+    """
+    Provisioning+wrong container excluded scope: Add a staged entry and check its ALLOCATED_ATTR is  not set
+    """
+    _header(topology, 'Provisioning+wrong container excluded scope: Add a staged entry and check its ALLOCATED_ATTR is  not set')
+
+    topology.standalone.add_s(Entry((STAGED_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': STAGED_USER1_CN,
+                                                'sn': STAGED_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(STAGED_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (STAGED_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(STAGED_USER1_DN)
+    
+def test_ticket47828_run_22(topology):
+    """
+    Provisioning+wrong container excluded scope: Add a staged entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'Provisioning+wrong container excluded scope: Add a staged entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((STAGED_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': STAGED_USER1_CN,
+                                                'sn': STAGED_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(STAGED_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (STAGED_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(STAGED_USER1_DN)
+    
+def test_ticket47828_run_23(topology):
+    """
+    Provisioning+wrong container excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is set
+    """
+    _header(topology, 'Provisioning+wrong container excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is set')
+
+    topology.standalone.add_s(Entry((DUMMY_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': DUMMY_USER1_CN,
+                                                'sn': DUMMY_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(DUMMY_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) != str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (DUMMY_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(DUMMY_USER1_DN)
+    
+def test_ticket47828_run_24(topology):
+    """
+    Provisioning+wrong container excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'Provisioning+wrong container excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((DUMMY_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': DUMMY_USER1_CN,
+                                                'sn': DUMMY_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(DUMMY_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (DUMMY_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(DUMMY_USER1_DN)
+    
+def test_ticket47828_run_25(topology):
+    '''
+    Exclude  a wrong container
+    '''
+    _header(topology, 'Exclude a wrong container')
+    
+    dn_config = "cn=excluded scope, cn=%s, %s" % (PLUGIN_DNA, DN_PLUGIN)
+    
+    try:
+        mod = [(ldap.MOD_REPLACE, 'dnaExcludeScope', "invalidDN,%s" % SUFFIX)]
+        topology.standalone.modify_s(dn_config, mod)
+        raise ValueError("invalid dnaExcludeScope value (not a DN)")
+    except ldap.INVALID_SYNTAX:
+        pass
+    
+def test_ticket47828_run_26(topology):
+    """
+    Wrong container excluded scope: Add an active entry and check its ALLOCATED_ATTR is set
+    """
+    _header(topology, 'Wrong container excluded scope: Add an active entry and check its ALLOCATED_ATTR is set')
+
+    topology.standalone.add_s(Entry((ACTIVE_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': ACTIVE_USER1_CN,
+                                                'sn': ACTIVE_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(ACTIVE_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) != str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (ACTIVE_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(ACTIVE_USER1_DN)
+    
+def test_ticket47828_run_27(topology):
+    """
+    Wrong container excluded scope: Add an active entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'Wrong container excluded scope: Add an active entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((ACTIVE_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': ACTIVE_USER1_CN,
+                                                'sn': ACTIVE_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(ACTIVE_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (ACTIVE_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(ACTIVE_USER1_DN)
+    
+def test_ticket47828_run_28(topology):
+    """
+    Wrong container excluded scope: Add a staged entry and check its ALLOCATED_ATTR is  not set
+    """
+    _header(topology, 'Wrong container excluded scope: Add a staged entry and check its ALLOCATED_ATTR is  not set')
+
+    topology.standalone.add_s(Entry((STAGED_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': STAGED_USER1_CN,
+                                                'sn': STAGED_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(STAGED_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (STAGED_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(STAGED_USER1_DN)
+    
+def test_ticket47828_run_29(topology):
+    """
+    Wrong container excluded scope: Add a staged entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'Wrong container excluded scope: Add a staged entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((STAGED_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': STAGED_USER1_CN,
+                                                'sn': STAGED_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(STAGED_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (STAGED_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(STAGED_USER1_DN)
+    
+def test_ticket47828_run_30(topology):
+    """
+    Wrong container excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is set
+    """
+    _header(topology, 'Wrong container excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is set')
+
+    topology.standalone.add_s(Entry((DUMMY_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': DUMMY_USER1_CN,
+                                                'sn': DUMMY_USER1_CN,
+                                                ALLOCATED_ATTR: str(-1)})))
+    ent = topology.standalone.getEntry(DUMMY_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) != str(-1)
+    topology.standalone.log.debug('%s.%s=%s' % (DUMMY_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(DUMMY_USER1_DN)
+    
+def test_ticket47828_run_31(topology):
+    """
+    Wrong container excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is unchanged (!= magic)
+    """
+    _header(topology, 'Wrong container excluded scope: Add an dummy entry and check its ALLOCATED_ATTR is unchanged (!= magic)')
+
+    topology.standalone.add_s(Entry((DUMMY_USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson".split(),
+                                                'cn': DUMMY_USER1_CN,
+                                                'sn': DUMMY_USER1_CN,
+                                                ALLOCATED_ATTR: str(20)})))
+    ent = topology.standalone.getEntry(DUMMY_USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+    assert ent.hasAttr(ALLOCATED_ATTR)
+    assert ent.getValue(ALLOCATED_ATTR) == str(20)
+    topology.standalone.log.debug('%s.%s=%s' % (DUMMY_USER1_CN, ALLOCATED_ATTR, ent.getValue(ALLOCATED_ATTR)))
+    topology.standalone.delete_s(DUMMY_USER1_DN)
+    
+def test_ticket47828_final(topology):
+    topology.standalone.plugins.disable(name=PLUGIN_DNA)
+    topology.standalone.stop(timeout=10)
+
+def run_isolated():
+    '''
+        run_isolated is used to run these test cases independently of a test scheduler (xunit, py.test..)
+        To run isolated without py.test, you need to
+            - edit this file and comment '@pytest.fixture' line before 'topology' function.
+            - set the installation prefix
+            - run this program
+    '''
+    global installation_prefix
+    installation_prefix = None
+
+    topo = topology(True)
+    test_ticket47828_init(topo)
+    
+    test_ticket47828_run_0(topo)
+    test_ticket47828_run_1(topo)
+    test_ticket47828_run_2(topo)
+    test_ticket47828_run_3(topo)
+    test_ticket47828_run_4(topo)
+    test_ticket47828_run_5(topo)
+    test_ticket47828_run_6(topo)
+    test_ticket47828_run_7(topo)
+    test_ticket47828_run_8(topo)
+    test_ticket47828_run_9(topo)
+    test_ticket47828_run_10(topo)
+    test_ticket47828_run_11(topo)
+    test_ticket47828_run_12(topo)
+    test_ticket47828_run_13(topo)
+    test_ticket47828_run_14(topo)
+    test_ticket47828_run_15(topo)
+    test_ticket47828_run_16(topo)
+    test_ticket47828_run_17(topo)
+    test_ticket47828_run_18(topo)
+    test_ticket47828_run_19(topo)
+    test_ticket47828_run_20(topo)
+    test_ticket47828_run_21(topo)
+    test_ticket47828_run_22(topo)
+    test_ticket47828_run_23(topo)
+    test_ticket47828_run_24(topo)
+    test_ticket47828_run_25(topo)
+    test_ticket47828_run_26(topo)
+    test_ticket47828_run_27(topo)
+    test_ticket47828_run_28(topo)
+    test_ticket47828_run_29(topo)
+    test_ticket47828_run_30(topo)
+    test_ticket47828_run_31(topo)
+    
+    test_ticket47828_final(topo)
+    
+
+if __name__ == '__main__':
+    run_isolated()

+ 8 - 0
ldap/schema/10dna-plugin.ldif

@@ -202,6 +202,13 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2160 NAME 'dnaRemoteBindMethod'
 #
 ################################################################################
 #
+attributeTypes: ( 2.16.840.1.113730.3.1.2312 NAME 'dnaExcludeScope'
+  DESC 'DN of a subtree excluded from DNA plugin scope'
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+  X-ORIGIN '389 Directory Server' )
+#
+################################################################################
+#
 objectClasses: ( 2.16.840.1.113730.3.2.324 NAME 'dnaPluginConfig'
   DESC 'DNA plugin configuration'
   SUP top
@@ -214,6 +221,7 @@ objectClasses: ( 2.16.840.1.113730.3.2.324 NAME 'dnaPluginConfig'
         dnaMagicRegen $
         dnaFilter $
         dnaScope $
+        dnaExcludeScope $
         dnaSharedCfgDN $
         dnaThreshold $
         dnaNextRange $

+ 72 - 1
ldap/servers/plugins/dna/dna.c

@@ -87,6 +87,7 @@
 #define DNA_GENERATE        "dnaMagicRegen"
 #define DNA_FILTER          "dnaFilter"
 #define DNA_SCOPE           "dnaScope"
+#define DNA_EXCLUDE_SCOPE   "dnaExcludeScope"
 #define DNA_REMOTE_BIND_DN  "dnaRemoteBindDN"
 #define DNA_REMOTE_BIND_PW  "dnaRemoteBindCred"
 
@@ -164,6 +165,7 @@ struct configEntry {
     Slapi_Filter *slapi_filter;
     char *generate;
     char *scope;
+    Slapi_DN **excludescope;
     PRUint64 interval;
     PRUint64 threshold;
     char *shared_cfg_base;
@@ -356,6 +358,17 @@ dna_config_copy()
             new_entry->slapi_filter = slapi_filter_dup(config_entry->slapi_filter);
             new_entry->generate = slapi_ch_strdup(config_entry->generate);
             new_entry->scope = slapi_ch_strdup(config_entry->scope);
+            if (config_entry->excludescope == NULL) {
+                new_entry->excludescope = NULL;
+            } else {
+                int i;
+                
+                for (i = 0; config_entry->excludescope[i]; i++);
+                new_entry->excludescope = (Slapi_DN **) slapi_ch_calloc(sizeof (Slapi_DN *), i + 1);
+                for (i = 0; new_entry->excludescope[i]; i++) {
+                    new_entry->excludescope[i] = slapi_sdn_dup(config_entry->excludescope[i]);
+                }
+            }
             new_entry->shared_cfg_base = slapi_ch_strdup(config_entry->shared_cfg_base);
             new_entry->shared_cfg_dn   = slapi_ch_strdup(config_entry->shared_cfg_dn);
             new_entry->remote_binddn   = slapi_ch_strdup(config_entry->remote_binddn);
@@ -906,6 +919,7 @@ dna_parse_config_entry(Slapi_PBlock *pb, Slapi_Entry * e, int apply)
     int entry_added = 0;
     int i = 0;
     int ret = DNA_SUCCESS;
+    char **plugin_attr_values;
 
     slapi_log_error(SLAPI_LOG_TRACE, DNA_PLUGIN_SUBSYSTEM,
                     "--> dna_parse_config_entry\n");
@@ -1056,6 +1070,31 @@ dna_parse_config_entry(Slapi_PBlock *pb, Slapi_Entry * e, int apply)
 
     slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
                     "----------> %s [%s]\n", DNA_SCOPE, entry->scope);
+    
+    plugin_attr_values = slapi_entry_attr_get_charray(e, DNA_EXCLUDE_SCOPE);
+    if (plugin_attr_values) {
+        int j = 0;
+        
+        /* Allocate the array of excluded scopes */
+        for (i = 0; plugin_attr_values[i]; i++);
+        entry->excludescope = (Slapi_DN **) slapi_ch_calloc(sizeof (Slapi_DN *), i + 1);
+        
+        /* Copy them in the config at the condition they are valid DN */
+        for (i = 0; plugin_attr_values[i]; i++) {
+            if (slapi_dn_syntax_check(NULL, plugin_attr_values[i], 1) == 1) {
+                slapi_log_error(SLAPI_LOG_FATAL, DNA_PLUGIN_SUBSYSTEM,
+                        "Error: Ignoring invalid DN used as excluded scope: [%s]\n",  plugin_attr_values[i]);
+                slapi_ch_free_string(&plugin_attr_values[i]);
+            } else {
+                entry->excludescope[j++] = slapi_sdn_new_dn_passin(plugin_attr_values[i]);
+            }
+        }
+        slapi_ch_free((void**) &plugin_attr_values);
+    }
+    for (i = 0; entry->excludescope && entry->excludescope[i]; i++) {
+        slapi_log_error(SLAPI_LOG_CONFIG, DNA_PLUGIN_SUBSYSTEM,
+                "----------> %s[%d] [%s]\n", DNA_EXCLUDE_SCOPE, i, slapi_sdn_get_dn(entry->excludescope[i]));
+    }
 
     /* optional, if not specified set -1 which is converted to the max unsigned
      * value */
@@ -1381,6 +1420,14 @@ dna_free_config_entry(struct configEntry ** entry)
     slapi_filter_free(e->slapi_filter, 1);
     slapi_ch_free_string(&e->generate);
     slapi_ch_free_string(&e->scope);
+    if (e->excludescope) {
+        int i;
+        
+        for (i = 0; e->excludescope[i]; i++) {
+            slapi_sdn_free(&e->excludescope[i]);
+        }
+        slapi_ch_free((void **)&e->excludescope);
+    }
     slapi_ch_free_string(&e->shared_cfg_base);
     slapi_ch_free_string(&e->shared_cfg_dn);
     slapi_ch_free_string(&e->remote_binddn);
@@ -3311,6 +3358,13 @@ _dna_pre_op_add(Slapi_PBlock *pb, Slapi_Entry *e, char **errstr)
                 goto next;
             }
 
+            /* is this entry in an excluded scope? */
+            for (i = 0; config_entry->excludescope && config_entry->excludescope[i]; i++) {
+                if (slapi_dn_issuffix(dn, slapi_sdn_get_dn(config_entry->excludescope[i]))) {
+                    goto next;
+                }
+            }
+            
             /* does the entry match the filter? */
             if (config_entry->slapi_filter) {
                 ret = slapi_vattr_filter_test(pb, e, config_entry->slapi_filter, 0);
@@ -3509,7 +3563,14 @@ _dna_pre_op_modify(Slapi_PBlock *pb, Slapi_Entry *e, Slapi_Mods *smods, char **e
                 !slapi_dn_issuffix(dn, config_entry->scope)) {
                 goto next;
             }
-
+            
+            /* is this entry in an excluded scope? */
+            for (i = 0; config_entry->excludescope && config_entry->excludescope[i]; i++) {
+                if (slapi_dn_issuffix(dn, slapi_sdn_get_dn(config_entry->excludescope[i]))) {
+                    goto next;
+                }
+            }
+            
             /* does the entry match the filter? */
             if (config_entry->slapi_filter) {
                 ret = slapi_vattr_filter_test(pb, e, 
@@ -3955,6 +4016,13 @@ static int dna_be_txn_pre_op(Slapi_PBlock *pb, int modtype)
                     goto next;
             }
 
+            /* is this entry in an excluded scope? */
+            for (i = 0; config_entry->excludescope && config_entry->excludescope[i]; i++) {
+                if (slapi_dn_issuffix(dn, slapi_sdn_get_dn(config_entry->excludescope[i]))) {
+                    goto next;
+                }
+            }
+            
             /* does the entry match the filter? */
             if (config_entry->slapi_filter) {
                 if(LDAP_SUCCESS != slapi_vattr_filter_test(pb,e,config_entry->slapi_filter, 0))
@@ -4562,6 +4630,9 @@ void dna_dump_config_entry(struct configEntry * entry)
     printf("<---- filter ---------> %s\n", entry->filter);
     printf("<---- prefix ---------> %s\n", entry->prefix);
     printf("<---- scope ----------> %s\n", entry->scope);
+    for (i = 0; entry->excludescope && entry->excludescope[i]; i++) {
+        printf("<---- excluded scope -> %s\n", slapi_sdn_get_dn(entry->excludescope[i]));
+    }
     printf("<---- next value -----> %" PRIu64 "\n", entry->nextval);
     printf("<---- max value ------> %" PRIu64 "\n", entry->maxval);
     printf("<---- interval -------> %" PRIu64 "\n", entry->interval);