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 +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 - 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 - 1.4.3.16-3 - Bump version to 1.4.3.16-3 - Resolves: Bug 1859219 - rfc2307 and rfc2307bis compat schema