diff --git a/SOURCES/0008-Issue-3657-Add-options-to-dsctl-for-dsrc-file.patch b/SOURCES/0008-Issue-3657-Add-options-to-dsctl-for-dsrc-file.patch
new file mode 100644
index 0000000..9bca531
--- /dev/null
+++ b/SOURCES/0008-Issue-3657-Add-options-to-dsctl-for-dsrc-file.patch
@@ -0,0 +1,502 @@
+From 4faec52810e12070ef72da347bb590c57d8761e4 Mon Sep 17 00:00:00 2001
+From: Mark Reynolds <mreynolds@redhat.com>
+Date: Fri, 20 Nov 2020 17:47:18 -0500
+Subject: [PATCH 1/2] Issue 3657 - Add options to dsctl for dsrc file
+
+Description:  Add options to create, modify, delete, and display
+              the .dsrc CLI tool shortcut file.
+
+Relates: https://github.com/389ds/389-ds-base/issues/3657
+
+Reviewed by: firstyear(Thanks!)
+---
+ dirsrvtests/tests/suites/clu/dsrc_test.py | 136 ++++++++++
+ src/lib389/cli/dsctl                      |   2 +
+ src/lib389/lib389/cli_ctl/dsrc.py         | 312 ++++++++++++++++++++++
+ 3 files changed, 450 insertions(+)
+ create mode 100644 dirsrvtests/tests/suites/clu/dsrc_test.py
+ create mode 100644 src/lib389/lib389/cli_ctl/dsrc.py
+
+diff --git a/dirsrvtests/tests/suites/clu/dsrc_test.py b/dirsrvtests/tests/suites/clu/dsrc_test.py
+new file mode 100644
+index 000000000..1b27700ec
+--- /dev/null
++++ b/dirsrvtests/tests/suites/clu/dsrc_test.py
+@@ -0,0 +1,136 @@
++import logging
++import pytest
++import os
++from os.path import expanduser
++from lib389.cli_base import FakeArgs
++from lib389.cli_ctl.dsrc import create_dsrc, modify_dsrc, delete_dsrc, display_dsrc
++from lib389._constants import DEFAULT_SUFFIX, DN_DM
++from lib389.topologies import topology_st as topo
++
++log = logging.getLogger(__name__)
++
++
++@pytest.fixture(scope="function")
++def setup(topo, request):
++    """Preserve any existing .dsrc file"""
++
++    dsrc_file = f'{expanduser("~")}/.dsrc'
++    backup_file = dsrc_file + ".original"
++    if os.path.exists(dsrc_file):
++        os.rename(dsrc_file, backup_file)
++
++    def fin():
++        if os.path.exists(backup_file):
++            os.rename(backup_file, dsrc_file)
++
++    request.addfinalizer(fin)
++
++
++def test_dsrc(topo, setup):
++    """Test "dsctl dsrc" command
++
++    :id: 0610de6c-e167-4761-bdab-3e677b2d44bb
++    :setup: Standalone Instance
++    :steps:
++        1. Test creation works
++        2. Test creating duplicate section
++        3. Test adding an additional inst config works
++        4. Test removing an instance works
++        5. Test modify works
++        6. Test delete works
++        7. Test display fails when no file is present
++
++    :expectedresults:
++        1. Success
++        2. Success
++        3. Success
++        4. Success
++        5. Success
++        6. Success
++        7. Success
++    """
++
++    inst = topo.standalone
++    serverid = inst.serverid
++    second_inst_name = "Second"
++    second_inst_basedn = "o=second"
++    different_suffix = "o=different"
++
++    # Setup our args
++    args = FakeArgs()
++    args.basedn = DEFAULT_SUFFIX
++    args.binddn = DN_DM
++    args.json = None
++    args.uri = None
++    args.saslmech = None
++    args.tls_cacertdir = None
++    args.tls_cert = None
++    args.tls_key = None
++    args.tls_reqcert = None
++    args.starttls = None
++    args.cancel_starttls = None
++    args.pwdfile = None
++    args.do_it = True
++
++    # Create a dsrc configuration entry
++    create_dsrc(inst, log, args)
++    display_dsrc(inst, topo.logcap.log, args)
++    assert topo.logcap.contains("basedn = " + args.basedn)
++    assert topo.logcap.contains("binddn = " + args.binddn)
++    assert topo.logcap.contains("[" + serverid + "]")
++    topo.logcap.flush()
++
++    # Attempt to add duplicate instance section
++    with pytest.raises(ValueError):
++        create_dsrc(inst, log, args)
++
++    # Test adding a second instance works correctly
++    inst.serverid = second_inst_name
++    args.basedn = second_inst_basedn
++    create_dsrc(inst, log, args)
++    display_dsrc(inst, topo.logcap.log, args)
++    assert topo.logcap.contains("basedn = " + args.basedn)
++    assert topo.logcap.contains("[" + second_inst_name + "]")
++    topo.logcap.flush()
++
++    # Delete second instance
++    delete_dsrc(inst, log, args)
++    inst.serverid = serverid  # Restore original instance name
++    display_dsrc(inst, topo.logcap.log, args)
++    assert not topo.logcap.contains("[" + second_inst_name + "]")
++    assert not topo.logcap.contains("basedn = " + args.basedn)
++    # Make sure first instance config is still present
++    assert topo.logcap.contains("[" + serverid + "]")
++    assert topo.logcap.contains("binddn = " + args.binddn)
++    topo.logcap.flush()
++
++    # Modify the config
++    args.basedn = different_suffix
++    modify_dsrc(inst, log, args)
++    display_dsrc(inst, topo.logcap.log, args)
++    assert topo.logcap.contains(different_suffix)
++    topo.logcap.flush()
++
++    # Remove an arg from the config
++    args.basedn = ""
++    modify_dsrc(inst, log, args)
++    display_dsrc(inst, topo.logcap.log, args)
++    assert not topo.logcap.contains(different_suffix)
++    topo.logcap.flush()
++
++    # Remove the last entry, which should delete the file
++    delete_dsrc(inst, log, args)
++    dsrc_file = f'{expanduser("~")}/.dsrc'
++    assert not os.path.exists(dsrc_file)
++
++    # Make sure display fails
++    with pytest.raises(ValueError):
++        display_dsrc(inst, log, args)
++
++
++if __name__ == '__main__':
++    # Run isolated
++    # -s for DEBUG mode
++    CURRENT_FILE = os.path.realpath(__file__)
++    pytest.main(["-s", CURRENT_FILE])
++
+diff --git a/src/lib389/cli/dsctl b/src/lib389/cli/dsctl
+index fe9bc10e9..69f069297 100755
+--- a/src/lib389/cli/dsctl
++++ b/src/lib389/cli/dsctl
+@@ -23,6 +23,7 @@ from lib389.cli_ctl import tls as cli_tls
+ from lib389.cli_ctl import health as cli_health
+ from lib389.cli_ctl import nsstate as cli_nsstate
+ from lib389.cli_ctl import dbgen as cli_dbgen
++from lib389.cli_ctl import dsrc as cli_dsrc
+ from lib389.cli_ctl.instance import instance_remove_all
+ from lib389.cli_base import (
+     disconnect_instance,
+@@ -61,6 +62,7 @@ cli_tls.create_parser(subparsers)
+ cli_health.create_parser(subparsers)
+ cli_nsstate.create_parser(subparsers)
+ cli_dbgen.create_parser(subparsers)
++cli_dsrc.create_parser(subparsers)
+ 
+ argcomplete.autocomplete(parser)
+ 
+diff --git a/src/lib389/lib389/cli_ctl/dsrc.py b/src/lib389/lib389/cli_ctl/dsrc.py
+new file mode 100644
+index 000000000..e49c7f819
+--- /dev/null
++++ b/src/lib389/lib389/cli_ctl/dsrc.py
+@@ -0,0 +1,312 @@
++# --- BEGIN COPYRIGHT BLOCK ---
++# Copyright (C) 2020 Red Hat, Inc.
++# All rights reserved.
++#
++# License: GPL (version 3 or any later version).
++# See LICENSE for details.
++# --- END COPYRIGHT BLOCK ---
++
++import json
++from os.path import expanduser
++from os import path, remove
++from ldapurl import isLDAPUrl
++from ldap.dn import is_dn
++import configparser
++
++
++def create_dsrc(inst, log, args):
++    """Create the .dsrc file
++
++    [instance]
++    uri = ldaps://hostname:port
++    basedn = dc=example,dc=com
++    binddn = uid=user,....
++    saslmech = [EXTERNAL|PLAIN]
++    tls_cacertdir = /path/to/cacertdir
++    tls_cert = /path/to/user.crt
++    tls_key = /path/to/user.key
++    tls_reqcert = [never, hard, allow]
++    starttls = [true, false]
++    pwdfile = /path/to/file
++    """
++
++    dsrc_file = f'{expanduser("~")}/.dsrc'
++    config = configparser.ConfigParser()
++    config.read(dsrc_file)
++
++    # Verify this section does not already exist
++    instances = config.sections()
++    if inst.serverid in instances:
++        raise ValueError("There is already a configuration section for this instance!")
++
++    # Process and validate the args
++    config[inst.serverid] = {}
++
++    if args.uri is not None:
++        if not isLDAPUrl(args.uri):
++            raise ValueError("The uri is not a valid LDAP URL!")
++        if args.uri.startswith("ldapi"):
++            # We must use EXTERNAL saslmech for LDAPI
++            args.saslmech = "EXTERNAL"
++        config[inst.serverid]['uri'] = args.uri
++    if args.basedn is not None:
++        if not is_dn(args.basedn):
++            raise ValueError("The basedn is not a valid DN!")
++        config[inst.serverid]['basedn'] = args.basedn
++    if args.binddn is not None:
++        if not is_dn(args.binddn):
++            raise ValueError("The binddn is not a valid DN!")
++        config[inst.serverid]['binddn'] = args.binddn
++    if args.saslmech is not None:
++        if args.saslmech not in ['EXTERNAL', 'PLAIN']:
++            raise ValueError("The saslmech must be EXTERNAL or PLAIN!")
++        config[inst.serverid]['saslmech'] = args.saslmech
++    if args.tls_cacertdir is not None:
++        if not path.exists(args.tls_cacertdir):
++            raise ValueError('--tls-cacertdir directory does not exist!')
++        config[inst.serverid]['tls_cacertdir'] = args.tls_cacertdir
++    if args.tls_cert is not None:
++        if not path.exists(args.tls_cert):
++            raise ValueError('--tls-cert does not point to an existing file!')
++        config[inst.serverid]['tls_cert'] = args.tls_cert
++    if args.tls_key is not None:
++        if not path.exists(args.tls_key):
++            raise ValueError('--tls-key does not point to an existing file!')
++        config[inst.serverid]['tls_key'] = args.tls_key
++    if args.tls_reqcert is not None:
++        if args.tls_reqcert not in ['never', 'hard', 'allow']:
++            raise ValueError('--tls-reqcert value is invalid (must be either "never", "allow", or "hard")!')
++        config[inst.serverid]['tls_reqcert'] = args.tls_reqcert
++    if args.starttls:
++         config[inst.serverid]['starttls'] = 'true'
++    if args.pwdfile is not None:
++        if not path.exists(args.pwdfile):
++            raise ValueError('--pwdfile does not exist!')
++        config[inst.serverid]['pwdfile'] = args.pwdfile
++
++    if len(config[inst.serverid]) == 0:
++        # No args set
++        raise ValueError("You must set at least one argument for the new dsrc file!")
++
++    # Print a preview of the config
++    log.info(f'Updating "{dsrc_file}" with:\n')
++    log.info(f'    [{inst.serverid}]')
++    for k, v in config[inst.serverid].items():
++        log.info(f'    {k} = {v}')
++
++    # Perform confirmation?
++    if not args.do_it:
++        while 1:
++            val = input(f'\nUpdate "{dsrc_file}" ? [yes]: ').rstrip().lower()
++            if val == '' or val == 'y' or val == 'yes':
++                break
++            if val == 'n' or val == 'no':
++                return
++
++    # Now write the file
++    with open(dsrc_file, 'w') as configfile:
++        config.write(configfile)
++
++    log.info(f'Successfully updated: {dsrc_file}')
++
++
++def modify_dsrc(inst, log, args):
++    """Modify the instance config
++    """
++    dsrc_file = f'{expanduser("~")}/.dsrc'
++
++    if path.exists(dsrc_file):
++        config = configparser.ConfigParser()
++        config.read(dsrc_file)
++
++        # Verify we have a section to modify
++        instances = config.sections()
++        if inst.serverid not in instances:
++            raise ValueError("There is no configuration section for this instance to modify!")
++
++        # Process and validate the args
++        if args.uri is not None:
++            if not isLDAPUrl(args.uri):
++                raise ValueError("The uri is not a valid LDAP URL!")
++            if args.uri.startswith("ldapi"):
++                # We must use EXTERNAL saslmech for LDAPI
++                args.saslmech = "EXTERNAL"
++            if args.uri == '':
++                del config[inst.serverid]['uri']
++            else:
++                config[inst.serverid]['uri'] = args.uri
++        if args.basedn is not None:
++            if not is_dn(args.basedn):
++                raise ValueError("The basedn is not a valid DN!")
++            if args.basedn == '':
++                del config[inst.serverid]['basedn']
++            else:
++                config[inst.serverid]['basedn'] = args.basedn
++        if args.binddn is not None:
++            if not is_dn(args.binddn):
++                raise ValueError("The binddn is not a valid DN!")
++            if args.binddn == '':
++                del config[inst.serverid]['binddn']
++            else:
++                config[inst.serverid]['binddn'] = args.binddn
++        if args.saslmech is not None:
++            if args.saslmech not in ['EXTERNAL', 'PLAIN']:
++                raise ValueError("The saslmech must be EXTERNAL or PLAIN!")
++            if args.saslmech == '':
++                del config[inst.serverid]['saslmech']
++            else:
++                config[inst.serverid]['saslmech'] = args.saslmech
++        if args.tls_cacertdir is not None:
++            if not path.exists(args.tls_cacertdir):
++                raise ValueError('--tls-cacertdir directory does not exist!')
++            if args.tls_cacertdir == '':
++                del config[inst.serverid]['tls_cacertdir']
++            else:
++                config[inst.serverid]['tls_cacertdir'] = args.tls_cacertdir
++        if args.tls_cert is not None:
++            if not path.exists(args.tls_cert):
++                raise ValueError('--tls-cert does not point to an existing file!')
++            if args.tls_cert == '':
++                del config[inst.serverid]['tls_cert']
++            else:
++                config[inst.serverid]['tls_cert'] = args.tls_cert
++        if args.tls_key is not None:
++            if not path.exists(args.tls_key):
++                raise ValueError('--tls-key does not point to an existing file!')
++            if args.tls_key == '':
++                del config[inst.serverid]['tls_key']
++            else:
++                config[inst.serverid]['tls_key'] = args.tls_key
++        if args.tls_reqcert is not None:
++            if args.tls_reqcert not in ['never', 'hard', 'allow']:
++                raise ValueError('--tls-reqcert value is invalid (must be either "never", "allow", or "hard")!')
++            if args.tls_reqcert == '':
++                del config[inst.serverid]['tls_reqcert']
++            else:
++                config[inst.serverid]['tls_reqcert'] = args.tls_reqcert
++        if args.starttls:
++             config[inst.serverid]['starttls'] = 'true'
++        if args.cancel_starttls:
++            config[inst.serverid]['starttls'] = 'false'
++        if args.pwdfile is not None:
++            if not path.exists(args.pwdfile):
++                raise ValueError('--pwdfile does not exist!')
++            if args.pwdfile == '':
++                del config[inst.serverid]['pwdfile']
++            else:
++                config[inst.serverid]['pwdfile'] = args.pwdfile
++
++        # Okay now rewrite the file
++        with open(dsrc_file, 'w') as configfile:
++            config.write(configfile)
++
++        log.info(f'Successfully updated: {dsrc_file}')
++    else:
++        raise ValueError(f'There is no .dsrc file "{dsrc_file}" to update!')
++
++
++def delete_dsrc(inst, log, args):
++    """Delete the .dsrc file
++    """
++    dsrc_file = f'{expanduser("~")}/.dsrc'
++    if path.exists(dsrc_file):
++        if not args.do_it:
++            # Get confirmation
++            while 1:
++                val = input(f'\nAre you sure you want to remove this instances configuration ? [no]: ').rstrip().lower()
++                if val == 'y' or val == 'yes':
++                    break
++                if val == '' or val == 'n' or val == 'no':
++                    return
++
++        config = configparser.ConfigParser()
++        config.read(dsrc_file)
++        instances = config.sections()
++        if inst.serverid not in instances:
++            raise ValueError("The is no configuration for this instance")
++
++        # Update the config object
++        del config[inst.serverid]
++
++        if len(config.sections()) == 0:
++            # The file would be empty so just delete it
++            try:
++                remove(dsrc_file)
++                log.info(f'Successfully removed: {dsrc_file}')
++                return
++            except OSError as e:
++                raise ValueError(f'Failed to delete "{dsrc_file}",  error: {str(e)}')
++        else:
++            # write the updated config
++            with open(dsrc_file, 'w') as configfile:
++                config.write(configfile)
++    else:
++        raise ValueError(f'There is no .dsrc file "{dsrc_file}" to update!')
++
++    log.info(f'Successfully updated: {dsrc_file}')
++
++def display_dsrc(inst, log, args):
++    """Display the contents of the ~/.dsrc file
++    """
++    dsrc_file = f'{expanduser("~")}/.dsrc'
++
++    if not path.exists(dsrc_file):
++        raise ValueError(f'There is no dsrc file "{dsrc_file}" to display!')
++
++    config = configparser.ConfigParser()
++    config.read(dsrc_file)
++    instances = config.sections()
++
++    for inst_section in instances:
++        if args.json:
++            log.info(json.dumps({inst_section: dict(config[inst_section])}, indent=4))
++        else:
++            log.info(f'[{inst_section}]')
++            for k, v in config[inst_section].items():
++                log.info(f'{k} = {v}')
++            log.info("")
++
++
++def create_parser(subparsers):
++    dsrc_parser = subparsers.add_parser('dsrc', help="Manage the .dsrc file")
++    subcommands = dsrc_parser.add_subparsers(help="action")
++
++    # Create .dsrc file
++    dsrc_create_parser = subcommands.add_parser('create', help='Generate the .dsrc file')
++    dsrc_create_parser.set_defaults(func=create_dsrc)
++    dsrc_create_parser.add_argument('--uri', help="The URI (LDAP URL) for the Directory Server instance.")
++    dsrc_create_parser.add_argument('--basedn', help="The default database suffix.")
++    dsrc_create_parser.add_argument('--binddn', help="The default Bind DN used or authentication.")
++    dsrc_create_parser.add_argument('--saslmech', help="The SASL mechanism to use: PLAIN or EXTERNAL.")
++    dsrc_create_parser.add_argument('--tls-cacertdir', help="The directory containing the Trusted Certificate Authority certificate.")
++    dsrc_create_parser.add_argument('--tls-cert', help="The absolute file name to the server certificate.")
++    dsrc_create_parser.add_argument('--tls-key', help="The absolute file name to the server certificate key.")
++    dsrc_create_parser.add_argument('--tls-reqcert', help="Request certificate strength: 'never', 'allow', 'hard'")
++    dsrc_create_parser.add_argument('--starttls', action='store_true', help="Use startTLS for connection to the server.")
++    dsrc_create_parser.add_argument('--pwdfile', help="The absolute path to a file containing the Bind DN's password.")
++    dsrc_create_parser.add_argument('--do-it', action='store_true', help="Create the file without any confirmation.")
++
++    dsrc_modify_parser = subcommands.add_parser('modify', help='Modify the .dsrc file')
++    dsrc_modify_parser.set_defaults(func=modify_dsrc)
++    dsrc_modify_parser.add_argument('--uri', nargs='?', const='', help="The URI (LDAP URL) for the Directory Server instance.")
++    dsrc_modify_parser.add_argument('--basedn', nargs='?', const='', help="The default database suffix.")
++    dsrc_modify_parser.add_argument('--binddn', nargs='?', const='', help="The default Bind DN used or authentication.")
++    dsrc_modify_parser.add_argument('--saslmech', nargs='?', const='', help="The SASL mechanism to use: PLAIN or EXTERNAL.")
++    dsrc_modify_parser.add_argument('--tls-cacertdir', nargs='?', const='', help="The directory containing the Trusted Certificate Authority certificate.")
++    dsrc_modify_parser.add_argument('--tls-cert', nargs='?', const='', help="The absolute file name to the server certificate.")
++    dsrc_modify_parser.add_argument('--tls-key', nargs='?', const='', help="The absolute file name to the server certificate key.")
++    dsrc_modify_parser.add_argument('--tls-reqcert', nargs='?', const='', help="Request certificate strength: 'never', 'allow', 'hard'")
++    dsrc_modify_parser.add_argument('--starttls', action='store_true', help="Use startTLS for connection to the server.")
++    dsrc_modify_parser.add_argument('--cancel-starttls', action='store_true', help="Do not use startTLS for connection to the server.")
++    dsrc_modify_parser.add_argument('--pwdfile', nargs='?', const='', help="The absolute path to a file containing the Bind DN's password.")
++    dsrc_modify_parser.add_argument('--do-it', action='store_true', help="Update the file without any confirmation.")
++
++    # Delete the instance from the .dsrc file
++    dsrc_delete_parser = subcommands.add_parser('delete', help='Delete instance configuration from the .dsrc file.')
++    dsrc_delete_parser.set_defaults(func=delete_dsrc)
++    dsrc_delete_parser.add_argument('--do-it', action='store_true',
++                                    help="Delete this instance's configuration from the .dsrc file.")
++
++    # Display .dsrc file
++    dsrc_display_parser = subcommands.add_parser('display', help='Display the contents of the .dsrc file.')
++    dsrc_display_parser.set_defaults(func=display_dsrc)
+-- 
+2.26.2
+
diff --git a/SOURCES/0009-Issue-4440-BUG-ldifgen-with-start-idx-option-fails-w.patch b/SOURCES/0009-Issue-4440-BUG-ldifgen-with-start-idx-option-fails-w.patch
new file mode 100644
index 0000000..1a0df22
--- /dev/null
+++ b/SOURCES/0009-Issue-4440-BUG-ldifgen-with-start-idx-option-fails-w.patch
@@ -0,0 +1,902 @@
+From 201cb1147c0a34bddbd3e5c03aecd804c47a9905 Mon Sep 17 00:00:00 2001
+From: progier389 <72748589+progier389@users.noreply.github.com>
+Date: Thu, 19 Nov 2020 10:21:10 +0100
+Subject: [PATCH 2/2] Issue 4440 - BUG - ldifgen with --start-idx option fails
+ with unsupported operand (#4444)
+
+Bug description:
+Got TypeError exception when usign:
+  dsctl -v slapd-localhost ldifgen users --suffix
+     dc=example,dc=com --parent ou=people,dc=example,dc=com
+     --number 100000 --generic --start-idx=50
+The reason is that by default python parser provides
+ value for numeric options:
+  as an integer if specified by "--option value" or
+  as a string if specified by "--option=value"
+
+Fix description:
+convert the numeric parameters to integer when using it.
+ options impacted are:
+  - in users subcommand:   --number ,  --start-idx
+  - in mod-load subcommand:   --num-users, --add-users,
+               --del-users, --modrdn-users, --mod-users
+
+FYI: An alternative solution would have been to indicate the
+parser that these values are an integer. But two reasons
+ leaded me to implement the first solution:
+ - first solution fix the problem for all users while the
+   second one fixes only dsctl command.
+ - first solution is easier to test:
+    I just added a new test file generated by a script
+      that duplicated existing ldifgen test, renamed the
+       test cases and replaced the numeric arguments by
+       strings.
+   Second solution would need to redesign the test framework
+    to be able to test the parser.
+
+relates: https://github.com/389ds/389-ds-base/issues/4440
+
+Reviewed by:
+
+Platforms tested: F32
+
+(cherry picked from commit 3c3e1f30cdb046a1aabb93aacebcf261a76a0892)
+---
+ .../tests/suites/clu/dbgen_test_usan.py       | 806 ++++++++++++++++++
+ src/lib389/lib389/cli_ctl/dbgen.py            |  10 +-
+ src/lib389/lib389/dbgen.py                    |   3 +
+ 3 files changed, 814 insertions(+), 5 deletions(-)
+ create mode 100644 dirsrvtests/tests/suites/clu/dbgen_test_usan.py
+
+diff --git a/dirsrvtests/tests/suites/clu/dbgen_test_usan.py b/dirsrvtests/tests/suites/clu/dbgen_test_usan.py
+new file mode 100644
+index 000000000..80ff63417
+--- /dev/null
++++ b/dirsrvtests/tests/suites/clu/dbgen_test_usan.py
+@@ -0,0 +1,806 @@
++# --- BEGIN COPYRIGHT BLOCK ---
++# Copyright (C) 2020 Red Hat, Inc.
++# All rights reserved.
++#
++# License: GPL (version 3 or any later version).
++# See LICENSE for details.
++# --- END COPYRIGHT BLOCK ---
++#
++import time
++
++"""
++ This file contains tests similar to dbgen_test.py
++ except that paramaters that are number are expressed as string
++ (to mimic the parameters parser default behavior which returns an
++   int when parsing "option value" and a string when parsing "option=value"
++ This file has been generated by usign:
++sed '
++9r z1
++s/ test_/ test_usan/
++/args.*= [0-9]/s,[0-9]*$,"&",
++/:id:/s/.$/1/
++' dbgen_test.py > dbgen_test_usan.py
++ ( with z1 file containing this comment )
++"""
++
++ 
++
++import subprocess
++import pytest
++
++from lib389.cli_ctl.dbgen import *
++from lib389.cos import CosClassicDefinitions, CosPointerDefinitions, CosIndirectDefinitions, CosTemplates
++from lib389.idm.account import Accounts
++from lib389.idm.group import Groups
++from lib389.idm.role import ManagedRoles, FilteredRoles, NestedRoles
++from lib389.tasks import *
++from lib389.utils import *
++from lib389.topologies import topology_st
++from lib389.cli_base import FakeArgs
++
++pytestmark = pytest.mark.tier0
++
++LOG_FILE = '/tmp/dbgen.log'
++logging.getLogger(__name__).setLevel(logging.DEBUG)
++log = logging.getLogger(__name__)
++
++
++@pytest.fixture(scope="function")
++def set_log_file_and_ldif(topology_st, request):
++    global ldif_file
++    ldif_file = get_ldif_dir(topology_st.standalone) + '/created.ldif'
++
++    fh = logging.FileHandler(LOG_FILE)
++    fh.setLevel(logging.DEBUG)
++    log.addHandler(fh)
++
++    def fin():
++        log.info('Delete files')
++        os.remove(LOG_FILE)
++        os.remove(ldif_file)
++
++    request.addfinalizer(fin)
++
++
++def run_offline_import(instance, ldif_file):
++    log.info('Stopping the server and running offline import...')
++    instance.stop()
++    assert instance.ldif2db(bename=DEFAULT_BENAME, suffixes=[DEFAULT_SUFFIX], encrypt=None, excludeSuffixes=None,
++                              import_file=ldif_file)
++    instance.start()
++
++
++def run_ldapmodify_from_file(instance, ldif_file, output_to_check=None):
++    LDAP_MOD = '/usr/bin/ldapmodify'
++    log.info('Add entries from ldif file with ldapmodify')
++    result = subprocess.check_output([LDAP_MOD, '-cx', '-D', DN_DM, '-w', PASSWORD,
++                                      '-h', instance.host, '-p', str(instance.port), '-af', ldif_file])
++    if output_to_check is not None:
++        assert output_to_check in ensure_str(result)
++
++
++def check_value_in_log_and_reset(content_list):
++    with open(LOG_FILE, 'r+') as f:
++        file_content = f.read()
++        log.info('Check if content is present in output')
++        for item in content_list:
++            assert item in file_content
++
++        log.info('Reset log file for next test')
++        f.truncate(0)
++
++
++@pytest.mark.ds50545
++@pytest.mark.bz1798394
++@pytest.mark.skipif(ds_is_older("1.4.3"), reason="Not implemented")
++def test_usandsconf_dbgen_users(topology_st, set_log_file_and_ldif):
++    """Test ldifgen (formerly dbgen) tool to create ldif with users
++
++    :id: 426b5b94-9923-454d-a736-7e71ca985e91
++    :setup: Standalone instance
++    :steps:
++         1. Create DS instance
++         2. Run ldifgen to generate ldif with users
++         3. Import generated ldif to database
++         4. Check it was properly imported
++    :expectedresults:
++         1. Success
++         2. Success
++         3. Success
++         4. Success
++    """
++
++    standalone = topology_st.standalone
++
++    args = FakeArgs()
++    args.suffix = DEFAULT_SUFFIX
++    args.parent = 'ou=people,dc=example,dc=com'
++    args.number = "1000"
++    args.rdn_cn = False
++    args.generic = True
++    args.start_idx = "50"
++    args.localize = False
++    args.ldif_file = ldif_file
++
++    content_list = ['Generating LDIF with the following options:',
++                    'suffix={}'.format(args.suffix),
++                    'parent={}'.format(args.parent),
++                    'number={}'.format(args.number),
++                    'rdn-cn={}'.format(args.rdn_cn),
++                    'generic={}'.format(args.generic),
++                    'start-idx={}'.format(args.start_idx),
++                    'localize={}'.format(args.localize),
++                    'ldif-file={}'.format(args.ldif_file),
++                    'Writing LDIF',
++                    'Successfully created LDIF file: {}'.format(args.ldif_file)]
++
++    log.info('Run ldifgen to create users ldif')
++    dbgen_create_users(standalone, log, args)
++
++    log.info('Check if file exists')
++    assert os.path.exists(ldif_file)
++
++    check_value_in_log_and_reset(content_list)
++
++    log.info('Get number of accounts before import')
++    accounts = Accounts(standalone, DEFAULT_SUFFIX)
++    count_account = len(accounts.filter('(uid=*)'))
++
++    run_offline_import(standalone, ldif_file)
++
++    log.info('Check that accounts are imported')
++    assert len(accounts.filter('(uid=*)')) > count_account
++
++
++@pytest.mark.ds50545
++@pytest.mark.bz1798394
++@pytest.mark.skipif(ds_is_older("1.4.3"), reason="Not implemented")
++def test_usandsconf_dbgen_groups(topology_st, set_log_file_and_ldif):
++    """Test ldifgen (formerly dbgen) tool to create ldif with group
++
++            :id: 97207413-9a93-4065-a5ec-63aa93801a31
++            :setup: Standalone instance
++            :steps:
++                 1. Create DS instance
++                 2. Run ldifgen to generate ldif with group
++                 3. Import generated ldif to database
++                 4. Check it was properly imported
++            :expectedresults:
++                 1. Success
++                 2. Success
++                 3. Success
++                 4. Success
++            """
++    LDAP_RESULT = 'adding new entry "cn=myGroup-1,ou=groups,dc=example,dc=com"'
++
++    standalone = topology_st.standalone
++
++    args = FakeArgs()
++    args.NAME = 'myGroup'
++    args.parent = 'ou=groups,dc=example,dc=com'
++    args.suffix = DEFAULT_SUFFIX
++    args.number = "1"
++    args.num_members = "1000"
++    args.create_members = True
++    args.member_attr = 'uniquemember'
++    args.member_parent = 'ou=people,dc=example,dc=com'
++    args.ldif_file = ldif_file
++
++    content_list = ['Generating LDIF with the following options:',
++                    'NAME={}'.format(args.NAME),
++                    'number={}'.format(args.number),
++                    'suffix={}'.format(args.suffix),
++                    'num-members={}'.format(args.num_members),
++                    'create-members={}'.format(args.create_members),
++                    'member-parent={}'.format(args.member_parent),
++                    'member-attr={}'.format(args.member_attr),
++                    'ldif-file={}'.format(args.ldif_file),
++                    'Writing LDIF',
++                    'Successfully created LDIF file: {}'.format(args.ldif_file)]
++
++    log.info('Run ldifgen to create group ldif')
++    dbgen_create_groups(standalone, log, args)
++
++    log.info('Check if file exists')
++    assert os.path.exists(ldif_file)
++
++    check_value_in_log_and_reset(content_list)
++
++    log.info('Get number of accounts before import')
++    accounts = Accounts(standalone, DEFAULT_SUFFIX)
++    count_account = len(accounts.filter('(uid=*)'))
++
++    # Groups, COS, Roles and modification ldifs are designed to be used by ldapmodify, not ldif2db
++    # ldapmodify will complain about already existing parent which causes subprocess to return exit code != 0
++    with pytest.raises(subprocess.CalledProcessError):
++        run_ldapmodify_from_file(standalone, ldif_file, LDAP_RESULT)
++
++    log.info('Check that accounts are imported')
++    assert len(accounts.filter('(uid=*)')) > count_account
++
++    log.info('Check that group is imported')
++    groups = Groups(standalone, DEFAULT_SUFFIX)
++    assert groups.exists(args.NAME + '-1')
++    new_group = groups.get(args.NAME + '-1')
++    new_group.present('uniquemember', 'uid=group_entry1-0152,ou=people,dc=example,dc=com')
++
++
++@pytest.mark.ds50545
++@pytest.mark.bz1798394
++@pytest.mark.skipif(ds_is_older("1.4.3"), reason="Not implemented")
++def test_usandsconf_dbgen_cos_classic(topology_st, set_log_file_and_ldif):
++    """Test ldifgen (formerly dbgen) tool to create a COS definition
++
++        :id: 8557f994-8a91-4f8a-86f6-9cb826a0b8f1
++        :setup: Standalone instance
++        :steps:
++             1. Create DS instance
++             2. Run ldifgen to generate ldif with classic COS definition
++             3. Import generated ldif to database
++             4. Check it was properly imported
++        :expectedresults:
++             1. Success
++             2. Success
++             3. Success
++             4. Success
++        """
++
++    LDAP_RESULT = 'adding new entry "cn=My_Postal_Def,ou=cos definitions,dc=example,dc=com"'
++
++    standalone = topology_st.standalone
++
++    args = FakeArgs()
++    args.type = 'classic'
++    args.NAME = 'My_Postal_Def'
++    args.parent = 'ou=cos definitions,dc=example,dc=com'
++    args.create_parent = True
++    args.cos_specifier = 'businessCategory'
++    args.cos_attr = ['postalcode', 'telephonenumber']
++    args.cos_template = 'cn=sales,cn=classicCoS,dc=example,dc=com'
++    args.ldif_file = ldif_file
++
++    content_list = ['Generating LDIF with the following options:',
++                    'NAME={}'.format(args.NAME),
++                    'type={}'.format(args.type),
++                    'parent={}'.format(args.parent),
++                    'create-parent={}'.format(args.create_parent),
++                    'cos-specifier={}'.format(args.cos_specifier),
++                    'cos-template={}'.format(args.cos_template),
++                    'cos-attr={}'.format(args.cos_attr),
++                    'ldif-file={}'.format(args.ldif_file),
++                    'Writing LDIF',
++                    'Successfully created LDIF file: {}'.format(args.ldif_file)]
++
++    log.info('Run ldifgen to create COS definition ldif')
++    dbgen_create_cos_def(standalone, log, args)
++
++    log.info('Check if file exists')
++    assert os.path.exists(ldif_file)
++
++    check_value_in_log_and_reset(content_list)
++
++    # Groups, COS, Roles and modification ldifs are designed to be used by ldapmodify, not ldif2db
++    run_ldapmodify_from_file(standalone, ldif_file, LDAP_RESULT)
++
++    log.info('Check that COS definition is imported')
++    cos_def = CosClassicDefinitions(standalone, args.parent)
++    assert cos_def.exists(args.NAME)
++    new_cos = cos_def.get(args.NAME)
++    assert new_cos.present('cosTemplateDN', args.cos_template)
++    assert new_cos.present('cosSpecifier', args.cos_specifier)
++    assert new_cos.present('cosAttribute', args.cos_attr[0])
++    assert new_cos.present('cosAttribute', args.cos_attr[1])
++
++
++@pytest.mark.ds50545
++@pytest.mark.bz1798394
++@pytest.mark.skipif(ds_is_older("1.4.3"), reason="Not implemented")
++def test_usandsconf_dbgen_cos_pointer(topology_st, set_log_file_and_ldif):
++    """Test ldifgen (formerly dbgen) tool to create a COS definition
++
++        :id: 6b26ca6d-226a-4f93-925e-faf95cc20211
++        :setup: Standalone instance
++        :steps:
++             1. Create DS instance
++             2. Run ldifgen to generate ldif with pointer COS definition
++             3. Import generated ldif to database
++             4. Check it was properly imported
++        :expectedresults:
++             1. Success
++             2. Success
++             3. Success
++             4. Success
++        """
++
++    LDAP_RESULT = 'adding new entry "cn=My_Postal_Def_pointer,ou=cos pointer definitions,dc=example,dc=com"'
++
++    standalone = topology_st.standalone
++
++    args = FakeArgs()
++    args.type = 'pointer'
++    args.NAME = 'My_Postal_Def_pointer'
++    args.parent = 'ou=cos pointer definitions,dc=example,dc=com'
++    args.create_parent = True
++    args.cos_specifier = None
++    args.cos_attr = ['postalcode', 'telephonenumber']
++    args.cos_template = 'cn=sales,cn=pointerCoS,dc=example,dc=com'
++    args.ldif_file = ldif_file
++
++    content_list = ['Generating LDIF with the following options:',
++                    'NAME={}'.format(args.NAME),
++                    'type={}'.format(args.type),
++                    'parent={}'.format(args.parent),
++                    'create-parent={}'.format(args.create_parent),
++                    'cos-template={}'.format(args.cos_template),
++                    'cos-attr={}'.format(args.cos_attr),
++                    'ldif-file={}'.format(args.ldif_file),
++                    'Writing LDIF',
++                    'Successfully created LDIF file: {}'.format(args.ldif_file)]
++
++    log.info('Run ldifgen to create COS definition ldif')
++    dbgen_create_cos_def(standalone, log, args)
++
++    log.info('Check if file exists')
++    assert os.path.exists(ldif_file)
++
++    check_value_in_log_and_reset(content_list)
++
++    # Groups, COS, Roles and modification ldifs are designed to be used by ldapmodify, not ldif2db
++    run_ldapmodify_from_file(standalone, ldif_file, LDAP_RESULT)
++
++    log.info('Check that COS definition is imported')
++    cos_def = CosPointerDefinitions(standalone, args.parent)
++    assert cos_def.exists(args.NAME)
++    new_cos = cos_def.get(args.NAME)
++    assert new_cos.present('cosTemplateDN', args.cos_template)
++    assert new_cos.present('cosAttribute', args.cos_attr[0])
++    assert new_cos.present('cosAttribute', args.cos_attr[1])
++
++
++@pytest.mark.ds50545
++@pytest.mark.bz1798394
++@pytest.mark.skipif(ds_is_older("1.4.3"), reason="Not implemented")
++def test_usandsconf_dbgen_cos_indirect(topology_st, set_log_file_and_ldif):
++    """Test ldifgen (formerly dbgen) tool to create a COS definition
++
++        :id: ab4b799e-e801-432a-a61d-badad2628201
++        :setup: Standalone instance
++        :steps:
++             1. Create DS instance
++             2. Run ldifgen to generate ldif with indirect COS definition
++             3. Import generated ldif to database
++             4. Check it was properly imported
++        :expectedresults:
++             1. Success
++             2. Success
++             3. Success
++             4. Success
++        """
++
++    LDAP_RESULT = 'adding new entry "cn=My_Postal_Def_indirect,ou=cos indirect definitions,dc=example,dc=com"'
++
++    standalone = topology_st.standalone
++
++    args = FakeArgs()
++    args.type = 'indirect'
++    args.NAME = 'My_Postal_Def_indirect'
++    args.parent = 'ou=cos indirect definitions,dc=example,dc=com'
++    args.create_parent = True
++    args.cos_specifier = 'businessCategory'
++    args.cos_attr = ['postalcode', 'telephonenumber']
++    args.cos_template = None
++    args.ldif_file = ldif_file
++
++    content_list = ['Generating LDIF with the following options:',
++                    'NAME={}'.format(args.NAME),
++                    'type={}'.format(args.type),
++                    'parent={}'.format(args.parent),
++                    'create-parent={}'.format(args.create_parent),
++                    'cos-specifier={}'.format(args.cos_specifier),
++                    'cos-attr={}'.format(args.cos_attr),
++                    'ldif-file={}'.format(args.ldif_file),
++                    'Writing LDIF',
++                    'Successfully created LDIF file: {}'.format(args.ldif_file)]
++
++    log.info('Run ldifgen to create COS definition ldif')
++    dbgen_create_cos_def(standalone, log, args)
++
++    log.info('Check if file exists')
++    assert os.path.exists(ldif_file)
++
++    check_value_in_log_and_reset(content_list)
++
++    # Groups, COS, Roles and modification ldifs are designed to be used by ldapmodify, not ldif2db
++    run_ldapmodify_from_file(standalone, ldif_file, LDAP_RESULT)
++
++    log.info('Check that COS definition is imported')
++    cos_def = CosIndirectDefinitions(standalone, args.parent)
++    assert cos_def.exists(args.NAME)
++    new_cos = cos_def.get(args.NAME)
++    assert new_cos.present('cosIndirectSpecifier', args.cos_specifier)
++    assert new_cos.present('cosAttribute', args.cos_attr[0])
++    assert new_cos.present('cosAttribute', args.cos_attr[1])
++
++
++@pytest.mark.ds50545
++@pytest.mark.bz1798394
++@pytest.mark.skipif(ds_is_older("1.4.3"), reason="Not implemented")
++def test_usandsconf_dbgen_cos_template(topology_st, set_log_file_and_ldif):
++    """Test ldifgen (formerly dbgen) tool to create a COS template
++
++        :id: 544017c7-4a82-4e7d-a047-00b68a28e071
++        :setup: Standalone instance
++        :steps:
++             1. Create DS instance
++             2. Run ldifgen to generate ldif with COS template
++             3. Import generated ldif to database
++             4. Check it was properly imported
++        :expectedresults:
++             1. Success
++             2. Success
++             3. Success
++             4. Success
++        """
++
++    LDAP_RESULT = 'adding new entry "cn=My_Template,ou=cos templates,dc=example,dc=com"'
++
++    standalone = topology_st.standalone
++
++    args = FakeArgs()
++    args.NAME = 'My_Template'
++    args.parent = 'ou=cos templates,dc=example,dc=com'
++    args.create_parent = True
++    args.cos_priority = "1"
++    args.cos_attr_val = 'postalcode:12345'
++    args.ldif_file = ldif_file
++
++    content_list = ['Generating LDIF with the following options:',
++                    'NAME={}'.format(args.NAME),
++                    'parent={}'.format(args.parent),
++                    'create-parent={}'.format(args.create_parent),
++                    'cos-priority={}'.format(args.cos_priority),
++                    'cos-attr-val={}'.format(args.cos_attr_val),
++                    'ldif-file={}'.format(args.ldif_file),
++                    'Writing LDIF',
++                    'Successfully created LDIF file: {}'.format(args.ldif_file)]
++
++    log.info('Run ldifgen to create COS template ldif')
++    dbgen_create_cos_tmp(standalone, log, args)
++
++    log.info('Check if file exists')
++    assert os.path.exists(ldif_file)
++
++    check_value_in_log_and_reset(content_list)
++
++    # Groups, COS, Roles and modification ldifs are designed to be used by ldapmodify, not ldif2db
++    run_ldapmodify_from_file(standalone, ldif_file, LDAP_RESULT)
++
++    log.info('Check that COS template is imported')
++    cos_temp = CosTemplates(standalone, args.parent)
++    assert cos_temp.exists(args.NAME)
++    new_cos = cos_temp.get(args.NAME)
++    assert new_cos.present('cosPriority', str(args.cos_priority))
++    assert new_cos.present('postalcode', '12345')
++
++
++@pytest.mark.ds50545
++@pytest.mark.bz1798394
++@pytest.mark.skipif(ds_is_older("1.4.3"), reason="Not implemented")
++def test_usandsconf_dbgen_managed_role(topology_st, set_log_file_and_ldif):
++    """Test ldifgen (formerly dbgen) tool to create a managed role
++
++        :id: 10e77b41-0bc1-4ad5-a144-2c5107455b91
++        :setup: Standalone instance
++        :steps:
++             1. Create DS instance
++             2. Run ldifgen to generate ldif with managed role
++             3. Import generated ldif to database
++             4. Check it was properly imported
++        :expectedresults:
++             1. Success
++             2. Success
++             3. Success
++             4. Success
++        """
++
++    LDAP_RESULT = 'adding new entry "cn=My_Managed_Role,ou=managed roles,dc=example,dc=com"'
++
++    standalone = topology_st.standalone
++
++    args = FakeArgs()
++
++    args.NAME = 'My_Managed_Role'
++    args.parent = 'ou=managed roles,dc=example,dc=com'
++    args.create_parent = True
++    args.type = 'managed'
++    args.filter = None
++    args.role_dn = None
++    args.ldif_file = ldif_file
++
++    content_list = ['Generating LDIF with the following options:',
++                    'NAME={}'.format(args.NAME),
++                    'parent={}'.format(args.parent),
++                    'create-parent={}'.format(args.create_parent),
++                    'type={}'.format(args.type),
++                    'ldif-file={}'.format(args.ldif_file),
++                    'Writing LDIF',
++                    'Successfully created LDIF file: {}'.format(args.ldif_file)]
++
++    log.info('Run ldifgen to create managed role ldif')
++    dbgen_create_role(standalone, log, args)
++
++    log.info('Check if file exists')
++    assert os.path.exists(ldif_file)
++
++    check_value_in_log_and_reset(content_list)
++
++    # Groups, COS, Roles and modification ldifs are designed to be used by ldapmodify, not ldif2db
++    run_ldapmodify_from_file(standalone, ldif_file, LDAP_RESULT)
++
++    log.info('Check that managed role is imported')
++    roles = ManagedRoles(standalone, DEFAULT_SUFFIX)
++    assert roles.exists(args.NAME)
++
++
++@pytest.mark.ds50545
++@pytest.mark.bz1798394
++@pytest.mark.skipif(ds_is_older("1.4.3"), reason="Not implemented")
++def test_usandsconf_dbgen_filtered_role(topology_st, set_log_file_and_ldif):
++    """Test ldifgen (formerly dbgen) tool to create a filtered role
++
++        :id: cb3c8ea8-4234-40e2-8810-fb6a25973921
++        :setup: Standalone instance
++        :steps:
++             1. Create DS instance
++             2. Run ldifgen to generate ldif with filtered role
++             3. Import generated ldif to database
++             4. Check it was properly imported
++        :expectedresults:
++             1. Success
++             2. Success
++             3. Success
++             4. Success
++        """
++
++    LDAP_RESULT = 'adding new entry "cn=My_Filtered_Role,ou=filtered roles,dc=example,dc=com"'
++
++    standalone = topology_st.standalone
++
++    args = FakeArgs()
++
++    args.NAME = 'My_Filtered_Role'
++    args.parent = 'ou=filtered roles,dc=example,dc=com'
++    args.create_parent = True
++    args.type = 'filtered'
++    args.filter = '"objectclass=posixAccount"'
++    args.role_dn = None
++    args.ldif_file = ldif_file
++
++    content_list = ['Generating LDIF with the following options:',
++                    'NAME={}'.format(args.NAME),
++                    'parent={}'.format(args.parent),
++                    'create-parent={}'.format(args.create_parent),
++                    'type={}'.format(args.type),
++                    'filter={}'.format(args.filter),
++                    'ldif-file={}'.format(args.ldif_file),
++                    'Writing LDIF',
++                    'Successfully created LDIF file: {}'.format(args.ldif_file)]
++
++    log.info('Run ldifgen to create filtered role ldif')
++    dbgen_create_role(standalone, log, args)
++
++    log.info('Check if file exists')
++    assert os.path.exists(ldif_file)
++
++    check_value_in_log_and_reset(content_list)
++
++    # Groups, COS, Roles and modification ldifs are designed to be used by ldapmodify, not ldif2db
++    run_ldapmodify_from_file(standalone, ldif_file, LDAP_RESULT)
++
++    log.info('Check that filtered role is imported')
++    roles = FilteredRoles(standalone, DEFAULT_SUFFIX)
++    assert roles.exists(args.NAME)
++    new_role = roles.get(args.NAME)
++    assert new_role.present('nsRoleFilter', args.filter)
++
++
++@pytest.mark.ds50545
++@pytest.mark.bz1798394
++@pytest.mark.skipif(ds_is_older("1.4.3"), reason="Not implemented")
++def test_usandsconf_dbgen_nested_role(topology_st, set_log_file_and_ldif):
++    """Test ldifgen (formerly dbgen) tool to create a nested role
++
++        :id: 97fff0a8-3103-4adb-be04-2799ff58d8f1
++        :setup: Standalone instance
++        :steps:
++             1. Create DS instance
++             2. Run ldifgen to generate ldif with nested role
++             3. Import generated ldif to database
++             4. Check it was properly imported
++        :expectedresults:
++             1. Success
++             2. Success
++             3. Success
++             4. Success
++        """
++
++    LDAP_RESULT = 'adding new entry "cn=My_Nested_Role,ou=nested roles,dc=example,dc=com"'
++
++    standalone = topology_st.standalone
++
++    args = FakeArgs()
++    args.NAME = 'My_Nested_Role'
++    args.parent = 'ou=nested roles,dc=example,dc=com'
++    args.create_parent = True
++    args.type = 'nested'
++    args.filter = None
++    args.role_dn = ['cn=some_role,ou=roles,dc=example,dc=com']
++    args.ldif_file = ldif_file
++
++    content_list = ['Generating LDIF with the following options:',
++                    'NAME={}'.format(args.NAME),
++                    'parent={}'.format(args.parent),
++                    'create-parent={}'.format(args.create_parent),
++                    'type={}'.format(args.type),
++                    'role-dn={}'.format(args.role_dn),
++                    'ldif-file={}'.format(args.ldif_file),
++                    'Writing LDIF',
++                    'Successfully created LDIF file: {}'.format(args.ldif_file)]
++
++    log.info('Run ldifgen to create nested role ldif')
++    dbgen_create_role(standalone, log, args)
++
++    log.info('Check if file exists')
++    assert os.path.exists(ldif_file)
++
++    check_value_in_log_and_reset(content_list)
++
++    # Groups, COS, Roles and modification ldifs are designed to be used by ldapmodify, not ldif2db
++    run_ldapmodify_from_file(standalone, ldif_file, LDAP_RESULT)
++
++    log.info('Check that nested role is imported')
++    roles = NestedRoles(standalone, DEFAULT_SUFFIX)
++    assert roles.exists(args.NAME)
++    new_role = roles.get(args.NAME)
++    assert new_role.present('nsRoleDN', args.role_dn[0])
++
++
++@pytest.mark.ds50545
++@pytest.mark.bz1798394
++@pytest.mark.skipif(ds_is_older("1.4.3"), reason="Not implemented")
++def test_usandsconf_dbgen_mod_ldif_mixed(topology_st, set_log_file_and_ldif):
++    """Test ldifgen (formerly dbgen) tool to create mixed modification ldif
++
++        :id: 4a2e0901-2b48-452e-a4a0-507735132c81
++        :setup: Standalone instance
++        :steps:
++             1. Create DS instance
++             2. Run ldifgen to generate modification ldif
++             3. Import generated ldif to database
++             4. Check it was properly imported
++        :expectedresults:
++             1. Success
++             2. Success
++             3. Success
++             4. Success
++        """
++
++    standalone = topology_st.standalone
++
++    args = FakeArgs()
++    args.parent = DEFAULT_SUFFIX
++    args.create_users = True
++    args.delete_users = True
++    args.create_parent = False
++    args.num_users = "1000"
++    args.add_users = "100"
++    args.del_users = "999"
++    args.modrdn_users = "100"
++    args.mod_users = "10"
++    args.mod_attrs = ['cn', 'uid', 'sn']
++    args.randomize = False
++    args.ldif_file = ldif_file
++
++    content_list = ['Generating LDIF with the following options:',
++                    'create-users={}'.format(args.create_users),
++                    'parent={}'.format(args.parent),
++                    'create-parent={}'.format(args.create_parent),
++                    'delete-users={}'.format(args.delete_users),
++                    'num-users={}'.format(args.num_users),
++                    'add-users={}'.format(args.add_users),
++                    'del-users={}'.format(args.del_users),
++                    'modrdn-users={}'.format(args.modrdn_users),
++                    'mod-users={}'.format(args.mod_users),
++                    'mod-attrs={}'.format(args.mod_attrs),
++                    'randomize={}'.format(args.randomize),
++                    'ldif-file={}'.format(args.ldif_file),
++                    'Writing LDIF',
++                    'Successfully created LDIF file: {}'.format(args.ldif_file)]
++
++    log.info('Run ldifgen to create modification ldif')
++    dbgen_create_mods(standalone, log, args)
++
++    log.info('Check if file exists')
++    assert os.path.exists(ldif_file)
++
++    check_value_in_log_and_reset(content_list)
++
++    log.info('Get number of accounts before import')
++    accounts = Accounts(standalone, DEFAULT_SUFFIX)
++    count_account = len(accounts.filter('(uid=*)'))
++
++    # Groups, COS, Roles and modification ldifs are designed to be used by ldapmodify, not ldif2db
++    # ldapmodify will complain about a lot of changes done which causes subprocess to return exit code != 0
++    with pytest.raises(subprocess.CalledProcessError):
++        run_ldapmodify_from_file(standalone, ldif_file)
++
++    log.info('Check that some accounts are imported')
++    assert len(accounts.filter('(uid=*)')) > count_account
++
++
++@pytest.mark.ds50545
++@pytest.mark.bz1798394
++@pytest.mark.skipif(ds_is_older("1.4.3"), reason="Not implemented")
++def test_usandsconf_dbgen_nested_ldif(topology_st, set_log_file_and_ldif):
++    """Test ldifgen (formerly dbgen) tool to create nested ldif
++
++        :id: 9c281c28-4169-45e0-8c07-c5502d9a7581
++        :setup: Standalone instance
++        :steps:
++             1. Create DS instance
++             2. Run ldifgen to generate nested ldif
++             3. Import generated ldif to database
++             4. Check it was properly imported
++        :expectedresults:
++             1. Success
++             2. Success
++             3. Success
++             4. Success
++        """
++
++    standalone = topology_st.standalone
++
++    args = FakeArgs()
++    args.suffix = DEFAULT_SUFFIX
++    args.node_limit = "100"
++    args.num_users = "600"
++    args.ldif_file = ldif_file
++
++    content_list = ['Generating LDIF with the following options:',
++                    'suffix={}'.format(args.suffix),
++                    'node-limit={}'.format(args.node_limit),
++                    'num-users={}'.format(args.num_users),
++                    'ldif-file={}'.format(args.ldif_file),
++                    'Writing LDIF',
++                    'Successfully created nested LDIF file ({}) containing 6 nodes/subtrees'.format(args.ldif_file)]
++
++    log.info('Run ldifgen to create nested ldif')
++    dbgen_create_nested(standalone, log, args)
++
++    log.info('Check if file exists')
++    assert os.path.exists(ldif_file)
++
++    check_value_in_log_and_reset(content_list)
++
++    log.info('Get number of accounts before import')
++    accounts = Accounts(standalone, DEFAULT_SUFFIX)
++    count_account = len(accounts.filter('(uid=*)'))
++    count_ou = len(accounts.filter('(ou=*)'))
++
++    # Groups, COS, Roles and modification ldifs are designed to be used by ldapmodify, not ldif2db
++    # ldapmodify will complain about already existing suffix which causes subprocess to return exit code != 0
++    with pytest.raises(subprocess.CalledProcessError):
++        run_ldapmodify_from_file(standalone, ldif_file)
++
++    standalone.restart()
++
++    log.info('Check that accounts are imported')
++    assert len(accounts.filter('(uid=*)')) > count_account
++    assert len(accounts.filter('(ou=*)')) > count_ou
++
++
++if __name__ == '__main__':
++    # Run isolated
++    # -s for DEBUG mode
++    CURRENT_FILE = os.path.realpath(__file__)
++    pytest.main("-s %s" % CURRENT_FILE)
+diff --git a/src/lib389/lib389/cli_ctl/dbgen.py b/src/lib389/lib389/cli_ctl/dbgen.py
+index 7bc3892ba..058342fb1 100644
+--- a/src/lib389/lib389/cli_ctl/dbgen.py
++++ b/src/lib389/lib389/cli_ctl/dbgen.py
+@@ -451,13 +451,13 @@ def dbgen_create_mods(inst, log, args):
+     props = {
+         "createUsers": args.create_users,
+         "deleteUsers": args.delete_users,
+-        "numUsers": args.num_users,
++        "numUsers": int(args.num_users),
+         "parent": args.parent,
+         "createParent": args.create_parent,
+-        "addUsers": args.add_users,
+-        "delUsers": args.del_users,
+-        "modrdnUsers": args.modrdn_users,
+-        "modUsers": args.mod_users,
++        "addUsers": int(args.add_users),
++        "delUsers": int(args.del_users),
++        "modrdnUsers": int(args.modrdn_users),
++        "modUsers": int(args.mod_users),
+         "random": args.randomize,
+         "modAttrs": args.mod_attrs
+     }
+diff --git a/src/lib389/lib389/dbgen.py b/src/lib389/lib389/dbgen.py
+index 6273781a2..10fb200f7 100644
+--- a/src/lib389/lib389/dbgen.py
++++ b/src/lib389/lib389/dbgen.py
+@@ -220,6 +220,9 @@ def dbgen_users(instance, number, ldif_file, suffix, generic=False, entry_name="
+     """
+     Generate an LDIF of randomly named entries
+     """
++    # Lets insure that integer parameters are not string
++    number=int(number)
++    startIdx=int(startIdx)
+     familyname_file = os.path.join(instance.ds_paths.data_dir, 'dirsrv/data/dbgen-FamilyNames')
+     givename_file = os.path.join(instance.ds_paths.data_dir, 'dirsrv/data/dbgen-GivenNames')
+     familynames = []
+-- 
+2.26.2
+
diff --git a/SPECS/389-ds-base.spec b/SPECS/389-ds-base.spec
index 1e798bc..54de896 100644
--- a/SPECS/389-ds-base.spec
+++ b/SPECS/389-ds-base.spec
@@ -45,7 +45,7 @@ ExcludeArch: i686
 Summary:          389 Directory Server (base)
 Name:             389-ds-base
 Version:          1.4.3.16
-Release:          %{?relprefix}3%{?prerel}%{?dist}
+Release:          %{?relprefix}4%{?prerel}%{?dist}
 License:          GPLv3+
 URL:              https://www.port389.org
 Group:            System Environment/Daemons
@@ -181,6 +181,9 @@ Patch04:          0004-Ticket-50933-Update-2307compat.ldif.patch
 Patch05:          0005-Issue-50933-Fix-OID-change-between-10rfc2307-and-10r.patch
 Patch06:          0006-Ticket-51131-improve-mutex-alloc-in-conntable.patch
 Patch07:          0007-Issue-4297-2nd-fix-for-on-ADD-replication-URP-issue-.patch
+Patch08:          0008-Issue-3657-Add-options-to-dsctl-for-dsrc-file.patch
+Patch09:          0009-Issue-4440-BUG-ldifgen-with-start-idx-option-fails-w.patch
+
 
 %description
 389 Directory Server is an LDAPv3 compliant server.  The base package includes
@@ -798,6 +801,12 @@ exit 0
 %doc README.md
 
 %changelog
+* Thu Dec 3 2020 Mark Reynolds <mreynolds@redhat.com> - 1.4.3.16-4
+- Bump version to 1.4.3.16-4
+- Resolves: Bug 1843517 - Using ldifgen with --start-idx option fails with unsupported operand
+- Resolves: Bug 1801086 - [RFE] Generate dsrc file using dsconf
+- Resolves: Bug 1843838 - heap-use-after-free in slapi_be_getsuffix
+
 * Wed Nov 25 2020 Mark Reynolds <mreynolds@redhat.com> - 1.4.3.16-3
 - Bump version to 1.4.3.16-3
 - Resolves: Bug 1859219 - rfc2307 and rfc2307bis compat schema