#51022 Issue 50545 - Port dbgen.pl to dsctl
Closed 3 years ago by spichugi. Opened 3 years ago by mreynolds.
mreynolds/389-ds-base issue50545  into  master

@@ -1,5 +1,6 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

  # Copyright (C) 2019 William Brown <william@blackhats.net.au>

+ # Copyright (C) 2020 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -8,16 +9,15 @@ 

  #

  

  from lib389.topologies import topology_st

- from lib389.dbgen import dbgen

+ from lib389.dbgen import dbgen_users

  from lib389.ldclt import Ldclt

  from lib389.tasks import ImportTask

- 

  from lib389._constants import DEFAULT_SUFFIX

  

  

  def test_stress_search_simple(topology_st):

      """Test a simple stress test of searches on the directory server.

-     

+ 

      :id: 3786d01c-ea03-4655-a4f9-450693c75863

      :setup: Standalone Instance

      :steps:
@@ -31,7 +31,6 @@ 

      """

  

      inst = topology_st.standalone

- 

      inst.config.set("nsslapd-verify-filter-schema", "off")

      # Bump idllimit to test OR worst cases.

      from lib389.config import LDBMConfig
@@ -39,10 +38,9 @@ 

      # lconfig.set("nsslapd-idlistscanlimit", '20000')

      # lconfig.set("nsslapd-lookthroughlimit", '20000')

  

- 

      ldif_dir = inst.get_ldif_dir()

      import_ldif = ldif_dir + '/basic_import.ldif'

-     dbgen(inst, 10000, import_ldif, DEFAULT_SUFFIX)

+     dbgen_users(inst, 10000, import_ldif, DEFAULT_SUFFIX)

  

      r = ImportTask(inst)

      r.import_suffix_from_ldif(ldiffile=import_ldif, suffix=DEFAULT_SUFFIX)
@@ -50,10 +48,8 @@ 

  

      # Run a small to warm up the server's caches ...

      l = Ldclt(inst)

- 

      l.search_loadtest(DEFAULT_SUFFIX, "(mail=XXXX@example.com)", rounds=1)

  

      # Now do it for realsies!

      # l.search_loadtest(DEFAULT_SUFFIX, "(|(mail=XXXX@example.com)(nonexist=foo))", rounds=10)

      l.search_loadtest(DEFAULT_SUFFIX, "(mail=XXXX@example.com)", rounds=10)

- 

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2016 Red Hat, Inc.

+ # Copyright (C) 2020 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -18,10 +18,9 @@ 

  from lib389.tasks import *

  from lib389.utils import *

  from lib389.topologies import topology_st

- from lib389.dbgen import dbgen

+ from lib389.dbgen import dbgen_users

  from lib389.idm.organizationalunit import OrganizationalUnits

  from lib389._constants import DN_DM, PASSWORD, PW_DM

- from lib389.topologies import topology_st

  from lib389.paths import Paths

  from lib389.idm.directorymanager import DirectoryManager

  from lib389.config import LDBMConfig
@@ -266,7 +265,7 @@ 

      log.info("Generating LDIF...")

      ldif_dir = topology_st.standalone.get_ldif_dir()

      import_ldif = ldif_dir + '/basic_import.ldif'

-     dbgen(topology_st.standalone, 50000, import_ldif, DEFAULT_SUFFIX)

+     dbgen_users(topology_st.standalone, 50000, import_ldif, DEFAULT_SUFFIX)

  

      # Online

      log.info("Importing LDIF online...")

@@ -1,22 +1,23 @@ 

- # Copyright (C) 2017 Red Hat, Inc.

+ # 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 ldap

+ import logging

+ import os

  import pytest

+ import threading

+ import time

  from lib389.backend import Backends

  from lib389.properties import TASK_WAIT

- from lib389.utils import time, ldap, os, logging

  from lib389.topologies import topology_st as topo

- from lib389.dbgen import dbgen

+ from lib389.dbgen import dbgen_users

  from lib389._constants import DEFAULT_SUFFIX

  from lib389.tasks import *

  from lib389.idm.user import UserAccounts

- import threading

- import time

- 

  from lib389.idm.directorymanager import DirectoryManager

  

  pytestmark = pytest.mark.tier1
@@ -145,7 +146,7 @@ 

      log.info('Create LDIF file and import it...')

      ldif_dir = topo.standalone.get_ldif_dir()

      ldif_file = os.path.join(ldif_dir, 'default.ldif')

-     dbgen(topo.standalone, 5, ldif_file, TEST_DEFAULT_SUFFIX)

+     dbgen_users(topo.standalone, 5, ldif_file, TEST_DEFAULT_SUFFIX)

  

      log.info('Stopping the server and running offline import...')

      topo.standalone.stop()
@@ -185,7 +186,7 @@ 

      ldif_dir = topo.standalone.get_ldif_dir()

      ldif_file = os.path.join(ldif_dir, 'suffix_del1.ldif')

  

-     dbgen(topo.standalone, 10, ldif_file, TEST_SUFFIX1)

+     dbgen_users(topo.standalone, 10, ldif_file, TEST_SUFFIX1)

  

      log.info('Stopping the server and running offline import')

      topo.standalone.stop()
@@ -223,7 +224,7 @@ 

      ldif_dir = topo.standalone.get_ldif_dir()

      ldif_file = os.path.join(ldif_dir, 'suffix_del2.ldif')

  

-     dbgen(topo.standalone, 10, ldif_file, TEST_SUFFIX2)

+     dbgen_users(topo.standalone, 10, ldif_file, TEST_SUFFIX2)

  

      topo.standalone.tasks.importLDIF(suffix=TEST_SUFFIX2, input_file=ldif_file, args={TASK_WAIT: True})

  

@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2017 Red Hat, Inc.

+ # Copyright (C) 2020 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -9,12 +9,10 @@ 

  import ldap

  import pytest

  from lib389.topologies import topology_m2

- from lib389._constants import (DEFAULT_SUFFIX, HOST_MASTER_2, PORT_MASTER_2, TASK_WAIT)

+ from lib389._constants import (DEFAULT_SUFFIX)

  from lib389.agreement import Agreements

- 

  from lib389.idm.user import (TEST_USER_PROPERTIES, UserAccounts)

- 

- from lib389.dbgen import dbgen

+ from lib389.dbgen import dbgen_users

  from lib389.utils import ds_is_older

  

  pytestmark = pytest.mark.tier1
@@ -26,17 +24,15 @@ 

      master2 = topology_m2.ms["master2"]

  

      users = UserAccounts(master2, DEFAULT_SUFFIX)

- 

      u = users.create(properties=TEST_USER_PROPERTIES)

      u.set('userPassword', 'password')

- 

      binddn = u.dn

      bindpw = 'password'

  

      # Create a bunch of entries on master1

      ldif_dir = master1.get_ldif_dir()

      import_ldif = ldif_dir + '/ref_during_tot_import.ldif'

-     dbgen(master1, 10000, import_ldif, DEFAULT_SUFFIX)

+     dbgen_users(master1, 10000, import_ldif, DEFAULT_SUFFIX)

  

      master1.stop()

      master1.ldif2db(bename=None, excludeSuffixes=None, encrypt=False, suffixes=[DEFAULT_SUFFIX], import_file=import_ldif)
@@ -61,9 +57,7 @@ 

          except ldap.REFERRAL:

              referred = True

              break

-     # Means we never go a referral, should not happen! 

+     # Means we never go a referral, should not happen!

      assert referred

  

      # Done.

- 

- 

@@ -1,8 +1,17 @@ 

+ # --- 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 logging

  import pytest

  import os

  import ldap

- from lib389.dbgen import dbgen

+ from lib389.dbgen import dbgen_users

  from lib389._constants import *

  from lib389.topologies import topology_st as topo

  from lib389._controls import SSSRequestControl
@@ -33,7 +42,7 @@ 

      log.info("Creating LDIF...")

      ldif_dir = topo.standalone.get_ldif_dir()

      ldif_file = os.path.join(ldif_dir, 'mr-crash.ldif')

-     dbgen(topo.standalone, 5, ldif_file, DEFAULT_SUFFIX)

+     dbgen_users(topo.standalone, 5, ldif_file, DEFAULT_SUFFIX)

  

      log.info("Importing LDIF...")

      topo.standalone.stop()

file modified
+2 -2
@@ -12,7 +12,6 @@ 

  

  import json

  import argparse, argcomplete

- import logging

  import sys

  import signal

  import os
@@ -23,9 +22,9 @@ 

  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.instance import instance_remove_all

  from lib389.cli_base import (

-     _get_arg,

      disconnect_instance,

      setup_script_logger,

      format_error_to_dict)
@@ -61,6 +60,7 @@ 

  cli_tls.create_parser(subparsers)

  cli_health.create_parser(subparsers)

  cli_nsstate.create_parser(subparsers)

+ cli_dbgen.create_parser(subparsers)

  

  argcomplete.autocomplete(parser)

  

@@ -8,7 +8,7 @@ 

  

  import json

  from lib389.chaining import (

-     ChainingLink, ChainingLinks, ChainingConfig, ChainingDefault)

+     ChainingLinks, ChainingConfig, ChainingDefault)

  from lib389.cli_base import (

      _generic_list,

      _generic_get,

@@ -0,0 +1,587 @@ 

+ # --- 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 ---

+ 

+ from lib389.dbgen import (

+     dbgen_users,

+     dbgen_groups,

+     dbgen_cos_def,

+     dbgen_cos_template,

+     dbgen_role,

+     dbgen_mod_load,

+     dbgen_nested_ldif,

+ )

+ from lib389.utils import is_a_dn

+ 

+ DEFAULT_LDIF = "/tmp/ldifgen.ldif"

+ USERS_LDIF_NAME = "/users.ldif"

+ ignore_args = [

+     "ldif_file",

+     "func",

+     "verbose",

+     "list",

+     "instance",

+     "json",

+     "remove_all",

+ ]

+ 

+ def get_ldif_dir(instance):

+     """

+     Get the server's LDIF directory.  This is only used for user & nested LDIFs

+     """

+     server_dir = instance.get_ldif_dir()

+     if server_dir is not None:

+         return server_dir

+     return DEFAULT_LDIF

+ 

+ 

+ def adjust_ldif_name(instance, ldif_name):

+     """

+     If just a name is provided append it to the server's LDIF directory

+     """

+     if ldif_name[0] == '.' or ldif_name[0] == '/':

+         # Name appears to already be an absolute path

+         return ldif_name

+     else:

+         # Its just a name, add the server's ldif directory

+         return instance.get_ldif_dir() + "/" + ldif_name

+ 

+ 

+ def display_args(log, args):

+     # Display all the options that are being used to generate the ldif file

+     log.info(f"\nGenerating LDIF with the following options:")

+     for k, v in vars(args).items():

+         if k in ignore_args or v is None:

+             continue

+         k = k.replace("_", "-")  # Restore arg's original name

+         log.info(f" - {k}={v}")

+     log.info(f" - ldif-file={args.ldif_file}")

+     log.info("\nWriting LDIF ...")

+ 

+ 

+ def validate_ldif_file(ldif_file, log=None):

+     """

+     Check if the LDIF file exists.  If interactive then return some error

+     text to the caller, otherwise raise an error.

+     """

+     try:

+         f = open(ldif_file, 'w')

+         f.close()

+         return True

+     except PermissionError:

+         # File might already exist

+         msg = f"The LDIF file ({ldif_file}) exists and can not be overwritten.  Please choose a different file name."

+         if log is not None:

+             log.info(msg)

+         else:

+             raise ValueError(msg)

+     except FileNotFoundError:

+         msg = f"The LDIF file ({ldif_file}) location does not exist.  Please choose a different location."

+         if log is not None:

+             log.info(msg)

+         else:

+             raise ValueError(msg)

+     except Exception as e:

+         msg = f"The LDIF file ({ldif_file}) can not be written: {str(e)}"

+         if log is not None:

+             log.info(msg)

+         else:

+             raise ValueError(msg)

+ 

+     return False

+ 

+ 

+ def get_ldif_file_input(log, default_name=DEFAULT_LDIF):

+     valid = False

+     while not valid:

+         file_name = get_input(log, "Enter the new LDIF file name", default_name)

+         valid = validate_ldif_file(file_name, log=log)

+     return file_name

+ 

+ 

+ def get_input(log, msg, default, type="", options=None):

+     # Interactive prompt

+     display_default = default

+     if isinstance(default, bool):

+         if default:

+             display_default = "yes"

+         else:

+             display_default = "no"

+     while 1:

+         if display_default != "":

+             val = input(f'\n{msg} [{display_default}]: ')

+         else:

+             val = input(f'\n{msg}: ')

+         if val != '':

+             if type == "dn":

+                 if is_a_dn(val, allow_anon=False):

+                     return val

+                 else:

+                     log.info(f"\n --->  The value you entered \"{val}\" is not a valid DN")

+                     continue

+             elif type == "int":

+                 if val.isdigit():

+                     if int(val) < 1:

+                         log.info("\n --->  You must enter number greater than 0")

+                         continue

+                     return int(val)

+                 else:

+                     log.info(f"\n --->  The number you entered \"{val}\" is not a number")

+                     continue

+             elif type == "bool":

+                 if val.lower() == "y" or val.lower() == "yes":

+                     return True

+                 elif val.lower() == "n" or val.lower() == "no":

+                     return False

+                 else:

+                     log.info(f"\n --->  Invalid value ({val}), please enter \"yes\" or \"no\".")

+                     continue

+             else:

+                 # Just a string, nothing to validate

+                 if options is not None:

+                     if val.lower() not in options:

+                         opt_str = ', '.join(options)

+                         log.info(f"\n --->  Invalid value ({val}), please enter one of the following types: {opt_str}")

+                         continue

+                 return val

+         else:

+             # User selected the default value

+             return default

+ 

+ 

+ def dbgen_create_users(inst, log, args):

+     """

+     Create a LDIF of user entries

+     """

+     if args.number is None or args.suffix is None:

+         """

+         Interactively get all the info ...

+         """

+         log.info("Missing required parameters, switching to Interactive mode ...")

+ 

+         # Get the suffix

+         args.suffix = get_input(log, "Enter the suffix", "dc=example,dc=com", "dn")

+ 

+         # Get the parent

+         args.parent = get_input(log, "Enter the parent entry for the users", "ou=people,dc=example,dc=com", "dn")

+ 

+         # Get the number of users to create

+         args.number = get_input(log, "Enter the number of users to create", 100000, "int")

+ 

+         # Confirm the RDN attribute

+         args.rdn_cn = get_input(log, "Do you want to use \"cn\" instead of \"uid\" for the entry RDN attribute (yes/no)", False, "bool")

+ 

+         # Create generic entries

+         args.generic = get_input(log, "Create generic entries that can be used with \"ldclt\" (yes/no)", False, "bool")

+ 

+         # get offset size

+         if args.generic:

+             args.start_idx = get_input(log, "Choose the starting index for the generic user entries", 0, "int")

+ 

+         # localize the data

+         args.localize = get_input(log, "Do you want to localize the LDIF data (yes/no)", False, "bool")

+ 

+         # Get the output LDIF file name

+         args.ldif_file = get_ldif_file_input(log, default_name=get_ldif_dir(inst) + USERS_LDIF_NAME)

+     else:

+         args.ldif_file = adjust_ldif_name(inst, args.ldif_file)

+         validate_ldif_file(args.ldif_file)

+ 

+     display_args(log, args)

+     dbgen_users(inst, args.number, args.ldif_file, args.suffix, generic=args.generic, parent=args.parent, startIdx=args.start_idx, rdnCN=False, pseudol10n=args.localize)

+     log.info(f"Successfully created LDIF file: {args.ldif_file}")

+ 

+ 

+ def dbgen_create_groups(inst, log, args):

+     """

+     Create static groups and their members

+     """

+ 

+     if args.number is None or args.suffix is None:

+         """

+         Interactively get all the info ...

+         """

+         log.info("Missing required parameters, switching to Interactive mode ...")

+ 

+         # Get the number of users to create

+         args.number = get_input(log, "Enter the number of groups to create", 1, "int")

+ 

+         # Get the suffix

+         args.suffix = get_input(log, "Enter the suffix", "dc=example,dc=com", "dn")

+ 

+         # Get the parent

+         args.parent = get_input(log, "Enter the parent entry to add the groups under", args.suffix, "dn")

+ 

+         # Get the membership attr

+         args.member_attr = get_input(log, "Enter the attribute to use for the group membership", "uniquemember")

+ 

+         # Number of members

+         args.num_members = get_input(log, "Enter the number of members to add to the group", 10000, "int")

+ 

+         # Create member entries

+         args.create_members = get_input(log, "Do you want to create the member entries (yes/no)", True, "bool")

+ 

+         # member entries parent

+         if args.create_members:

+             args.member_parent = get_input(log, "Enter the parent entry to add the users under", args.suffix, "dn")

+ 

+         # Get the output LDIF file name

+         args.ldif_file = get_ldif_file_input(log)

+     else:

+         validate_ldif_file(args.ldif_file)

+ 

+     props = {

+         "name": args.NAME,

+         "parent": args.parent,

+         "suffix": args.suffix,

+         "number": args.number,

+         "numMembers": args.num_members,

+         "createMembers": args.create_members,

+         "memberParent": args.member_parent,

+         "membershipAttr": args.member_attr,

+     }

+ 

+     display_args(log, args)

+     dbgen_groups(inst, args.ldif_file, props)

+     log.info(f"Successfully created LDIF file: {args.ldif_file}")

+ 

+ 

+ def dbgen_create_cos_def(inst, log, args):

+     """

+     Create a COS definition

+     """

+     if args.type is None or args.parent is None or len(args.cos_attr) == 0 or \

+         ((args.type == "classic" or args.type == "indirect") and args.cos_specifier is None) \

+         or ((args.type == "classic" or args.type == "pointer") and args.cos_template is None):

+         log.info("Missing required parameters, switching to Interactive mode ...")

+ 

+         # Get the number of users to create

+         args.type = get_input(log, "Type of COS definition: \"classic\", \"pointer\", or \"indirect\"",

+                               "classic",  options=["classic", "pointer", "indirect"])

+ 

+         # Get the parent

+         args.parent = get_input(log, "Enter the parent entry to add the COS definition under", "dc=example,dc=com", "dn")

+ 

+         # Create parent

+         args.create_members = get_input(log, "Do you want to create the parent entry (yes/no)", True, "bool")

+ 

+         # COS specifier

+         if args.type == "classic" or args.type == "indirect":

+             args.cos_specifier = get_input(log, "Enter the COS specifier attribute", "description")

+ 

+         # COS template DN

+         if args.type == "classic" or args.type == "pointer":

+             args.cos_template = get_input(log, "Enter the COS Template DN", "cn=COS Template Entry,dc=example,dc=com", "dn")

+ 

+         # Gather the COS attributes

+         while True:

+             val = get_input(log, "Enter COS attributes, press Enter when finished", "")

+             if val == "" and len(args.cos_attr) > 0:

+                 break

+             args.cos_attr.append(val)

+ 

+         # Get the output LDIF file name

+         args.ldif_file = get_ldif_file_input(log)

+     else:

+         validate_ldif_file(args.ldif_file)

+ 

+     props = {

+         "cosType": args.type,

+         "defName": args.NAME,

+         "defParent": args.parent,

+         "defCreateParent": args.create_parent,

+         "cosSpecifier": args.cos_specifier,

+         "cosAttrs": args.cos_attr,

+         "tmpName": args.cos_template

+     }

+ 

+     display_args(log, args)

+     dbgen_cos_def(inst, args.ldif_file, props)

+     log.info(f"Successfully created LDIF file: {args.ldif_file}")

+ 

+ 

+ def dbgen_create_cos_tmp(inst, log, args):

+     """

+     Create a COS template entry

+     """

+     if args.parent is None or args.cos_priority is None or args.cos_attr_val is None:

+         log.info("Missing required parameters, switching to Interactive mode ...")

+ 

+         # Get the parent

+         args.parent = get_input(log, "Enter the parent entry to add the COS template under", "dc=example,dc=com", "dn")

+ 

+         # Create parent

+         args.create_parent = get_input(log, "Do you want to create the parent entry (yes/no)", True, "bool")

+ 

+         # Get the COS priority

+         args.cos_priority = get_input(log, "Enter the COS priority for this template", "0", "int")

+ 

+         # Get the attribute value pair

+         args.cos_attr_val = get_input(log, "Enter the attribute and value pair.  Use this format: \"ATTRIBUTE:VALUE\"", "postalcode:19605")

+ 

+         # Get the output LDIF file name

+         args.ldif_file = get_ldif_file_input(log)

+     else:

+         validate_ldif_file(args.ldif_file)

+ 

+     props = {

+         "tmpName": args.NAME,

+         "tmpParent": args.parent,

+         "tmpCreateParent": args.create_parent,

+         "cosPriority": args.cos_priority,

+         "cosTmpAttrVal": args.cos_attr_val,

+     }

+ 

+     display_args(log, args)

+     dbgen_cos_template(inst,  args.ldif_file, props)

+     log.info(f"Successfully created LDIF file: {args.ldif_file}")

+ 

+ 

+ def dbgen_create_role(inst, log, args):

+     """

+     Create a Role

+     """

+     if args.type is None or args.parent is None or \

+         (args.type == "filtered" and args.filter is None) or \

+         (args.type == "nested" and len(args.role_dn) == 0):

+         log.info("Missing required parameters, switching to Interactive mode ...")

+ 

+         # Get the number of users to create

+         args.type = get_input(log, "Type of Role: \"managed\", \"filtered\", or \"nested\"",

+                               "managed",  options=["managed", "filtered", "nested"])

+ 

+         # Get the parent

+         args.parent = get_input(log, "Enter the parent entry to add the Role under", "dc=example,dc=com", "dn")

+ 

+         # Create parent

+         args.create_parent = get_input(log, "Do you want to create the parent entry (yes/no)", True, "bool")

+ 

+         # Role filter

+         if args.type == "filtered":

+             args.filter = get_input(log, "Enter the Role filter", "cn=some_value")

+ 

+         # Role DN (nested only)

+         if args.type == "nested":

+             while True:

+                 val = get_input(log, "Enter the Role DN", "cn=some other role,dc=example,dc=com", "dn")

+                 if val == "" and len(args.role_dn) > 0:

+                     break

+                 args.role_dn.append(val)

+ 

+         # Get the output LDIF file name

+         args.ldif_file = get_ldif_file_input(log)

+     else:

+         validate_ldif_file(args.ldif_file)

+ 

+     props = {

+         "role_type": args.type,

+         "role_name": args.NAME,

+         "parent": args.parent,

+         "createParent": args.create_parent,

+         "filter": args.filter,

+         "role_list": args.role_dn

+     }

+ 

+     display_args(log, args)

+     dbgen_role(inst, args.ldif_file, props)

+     log.info(f"Successfully created LDIF file: {args.ldif_file}")

+ 

+ 

+ def dbgen_create_mods(inst, log, args):

+     """

+     Create a LDIF file of update operations that can be consumed by ldapmodify

+ 

+     There are a lot of options here for creating different types of modification

+     LDIFs.  One technique/option is that you can work with existing users.  Use

+     dbgen to generate a large generic user ldif, import it, then you can create

+     a modification LDIF that will work on that database.  It's a lot faster than

+     using ldapmodify to add 1 million first then modify those entries.

+ 

+     The other nice option is that you can randomize the operations, but this does

+     introduce a potential for some of the operations to fail.  You could delete

+     an entry before you modify it, etc...

+     """

+ 

+     if args.num_users is None or args.parent is None:

+         log.info("Missing required parameters, switching to Interactive mode ...")

+ 

+         # Create users

+         args.create_users = get_input(log, "Do you want to create the user entries (yes/no)", True, "bool")

+ 

+         # Delete users

+         args.delete_users = get_input(log, "Do you want to delete all the user entries at the end (yes/no)", True, "bool")

+ 

+         # Get the number of entries

+         args.num_users = get_input(log, "Enter the number of user entries that can be modified", "100000", "int")

+ 

+         # Get the parent entry

+         args.parent = get_input(log, "The DN of the parent entry where the user entries are located", "ou=people,dc=example,dc=com")

+ 

+         # Create parent

+         args.create_parent = get_input(log, "Create the parent entry (yes/no)", True, "bool")

+ 

+         # Add users

+         args.add_users = get_input(log, "The number of users to add during the load", 0, "int")

+ 

+         # Delete users

+         args.del_users = get_input(log, "The number of users to delete during the load", 0, "int")

+ 

+         # modrdn users

+         args.modrdn_users = get_input(log, "The number of users to modrdn during the load", 0, "int")

+ 

+         # modify users

+         args.mod_users = get_input(log, "The number of users to modify during the load", 0, "int")

+ 

+         # Modification attributes

+         args.mod_attrs = get_input(log, "List of attributes that will be randomly chosen from when modifying an entry", "description cn")

+         args.mod_attrs = args.mod_attrs.split(' ')

+ 

+         # Randomize the load

+         args.randomize = get_input(log, "Randomly perform the specified add, mod, delete, and modrdn operations (yes/no)", True, "bool")

+ 

+         # Get the output LDIF file name

+         args.ldif_file = get_ldif_file_input(log)

+     else:

+         validate_ldif_file(args.ldif_file)

+ 

+     props = {

+         "createUsers": args.create_users,

+         "deleteUsers": args.delete_users,

+         "numUsers": 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,

+         "random": args.randomize,

+         "modAttrs": args.mod_attrs

+     }

+ 

+     display_args(log, args)

+     dbgen_mod_load(args.ldif_file, props)

+     log.info(f"Successfully created LDIF file: {args.ldif_file}")

+ 

+ 

+ def dbgen_create_nested(inst, log, args):

+     """

+     Create a cascading/fractal tree.  Every node splits in half and keeps

+     branching out in that fashion until all the entries are used up.

+     """

+ 

+     if  args.num_users is None or args.node_limit is None or args.suffix is None:

+         # Num users

+         args.num_users = get_input(log, "The number of users to add during the load", 1000000, "int")

+ 

+         # Node limit

+         args.node_limit = get_input(log, "The total number of user entries to create under each node/subtree", 500, "int")

+ 

+         # Suffix

+         args.suffix = get_input(log, "Enter the suffix", "dc=example,dc=com", "dn")

+ 

+         # Get the output LDIF file name

+         args.ldif_file = get_ldif_file_input(log, default_name=get_ldif_dir(inst) + USERS_LDIF_NAME)

+     else:

+         args.ldif_file = adjust_ldif_name(inst, args.ldif_file)

+         validate_ldif_file(args.ldif_file)

+ 

+     props = {

+         "numUsers": int(args.num_users),

+         "nodeLimit": int(args.node_limit),

+         "suffix": args.suffix,

+     }

+ 

+     display_args(log, args)

+     node_count = dbgen_nested_ldif(inst, args.ldif_file, props)

+     log.info(f"Successfully created nested LDIF file ({args.ldif_file}) containing {node_count} nodes/subtrees")

+ 

+ 

+ def create_parser(subparsers):

+     db_gen_parser = subparsers.add_parser('ldifgen', help="LDIF generator to make sample LDIF files for testing")

+     subcommands = db_gen_parser.add_subparsers(help="action")

+ 

+     # Create just users

+     dbgen_users_parser = subcommands.add_parser('users', help='Generate a LDIF containing user entries')

+     dbgen_users_parser.set_defaults(func=dbgen_create_users)

+     dbgen_users_parser.add_argument('--number', help="The number of users to create.")

+     dbgen_users_parser.add_argument('--suffix', help="The database suffix where the entries will be created.")

+     dbgen_users_parser.add_argument('--parent', help="The parent entry that the user entries should be created under.  If not specified, the entries are stored under random Organizational Units.")

+     dbgen_users_parser.add_argument('--generic', action='store_true', help="Create generic entries in the format of \"uid=user####\".  These entries are also compatible with ldclt.")

+     dbgen_users_parser.add_argument('--start-idx', default=0, help="For generic LDIF's you can choose the starting index for the user entries.  The default is \"0\".")

+     dbgen_users_parser.add_argument('--rdn-cn', action='store_true', help="Use the attribute \"cn\" as the RDN attribute in the DN instead of \"uid\"")

+     dbgen_users_parser.add_argument('--localize', action='store_true', help="Localize the LDIF data")

+     dbgen_users_parser.add_argument('--ldif-file', default="users.ldif", help=f"The LDIF file name.  Default location is the server's LDIF directory using the name 'users.ldif'")

+ 

+     # Create static groups

+     dbgen_groups_parser = subcommands.add_parser('groups', help='Generate a LDIF containing groups and members')

+     dbgen_groups_parser.set_defaults(func=dbgen_create_groups)

+     dbgen_groups_parser.add_argument('NAME', help="The group name.")

+     dbgen_groups_parser.add_argument('--number', default=1, help="The number of groups to create.")

+     dbgen_groups_parser.add_argument('--suffix', help="The database suffix where the groups will be created.")

+     dbgen_groups_parser.add_argument('--parent', help="The parent entry that the group entries should be created under.  If not specified the groups are stored under the suffix.")

+     dbgen_groups_parser.add_argument('--num-members', default="10000", help="The number of members in the group.  Default is 10000")

+     dbgen_groups_parser.add_argument('--create-members', action='store_true', help="Create the member user entries.")

+     dbgen_groups_parser.add_argument('--member-parent', help="The entry DN that the members should be created under.  The default is the suffix entry.")

+     dbgen_groups_parser.add_argument('--member-attr', default="uniquemember", help="The membership attribute to use in the group.  Default is \"uniquemember\".")

+     dbgen_groups_parser.add_argument('--ldif-file', default=DEFAULT_LDIF, help=f"The LDIF file name.  Default is \"{DEFAULT_LDIF}\"")

+ 

+     # Create a COS definition

+     dbgen_cos_def_parser = subcommands.add_parser('cos-def', help='Generate a LDIF containing a COS definition (classic, pointer, or indirect)')

+     dbgen_cos_def_parser.set_defaults(func=dbgen_create_cos_def)

+     dbgen_cos_def_parser.add_argument('NAME', help="The COS definition name.")

+     dbgen_cos_def_parser.add_argument('--type', help="The COS definition type: \"classic\", \"pointer\", or \"indirect\".")

+     dbgen_cos_def_parser.add_argument('--parent', help="The parent entry that the COS definition should be created under.")

+     dbgen_cos_def_parser.add_argument('--create-parent', action='store_true', help="Create the parent entry")

+     dbgen_cos_def_parser.add_argument('--cos-specifier', help="Used in a classic COS definition, this attribute located in the user entry is used to select which COS template to use.")

+     dbgen_cos_def_parser.add_argument('--cos-template', help="The DN of the COS template entry, only used for \"classic\" and \"pointer\" COS definitions.")

+     dbgen_cos_def_parser.add_argument('--cos-attr', nargs='*', default=[], help="A list of attributes which defines which attribute the COS generates values for.")

+     dbgen_cos_def_parser.add_argument('--ldif-file', default=DEFAULT_LDIF, help=f"The LDIF file name.  Default is \"{DEFAULT_LDIF}\"")

+ 

+     # Create a COS Template

+     dbgen_cos_tmp_parser = subcommands.add_parser('cos-template', help='Generate a LDIF containing a COS template')

+     dbgen_cos_tmp_parser.set_defaults(func=dbgen_create_cos_tmp)

+     dbgen_cos_tmp_parser.add_argument('NAME', help="The COS template name.")

+     dbgen_cos_tmp_parser.add_argument('--parent', help="The DN of the entry to store the COS template entry under.")

+     dbgen_cos_tmp_parser.add_argument('--create-parent', action='store_true', help="Create the parent entry")

+     dbgen_cos_tmp_parser.add_argument('--cos-priority', type=int, help="Sets the priority of this conflicting/competing COS templates.")

+     dbgen_cos_tmp_parser.add_argument('--cos-attr-val', help="defines the attribute and value that the template provides.")

+     dbgen_cos_tmp_parser.add_argument('--ldif-file', default=DEFAULT_LDIF, help=f"The LDIF file name.  Default is \"{DEFAULT_LDIF}\"")

+ 

+     # Create Role entries

+     dbgen_roles_parser = subcommands.add_parser('roles', help='Generate a LDIF containing a role entry (managed, filtered, or indirect)')

+     dbgen_roles_parser.set_defaults(func=dbgen_create_role)

+     dbgen_roles_parser.add_argument('NAME', help="The Role name.")

+     dbgen_roles_parser.add_argument('--type', help="The Role type: \"managed\", \"filtered\", or \"nested\".")

+     dbgen_roles_parser.add_argument('--parent', help="The DN of the entry to store the Role entry under")

+     dbgen_roles_parser.add_argument('--create-parent', action='store_true', help="Create the parent entry")

+     dbgen_roles_parser.add_argument('--filter', help="A search filter for gathering Role members.  Required for a \"filtered\" role.")

+     dbgen_roles_parser.add_argument('--role-dn', nargs='*', default=[], help="A DN of a role entry that should be included in this role.  Used for \"nested\" roles only.")

+     dbgen_roles_parser.add_argument('--ldif-file', default=DEFAULT_LDIF, help=f"The LDIF file name.  Default is \"{DEFAULT_LDIF}\"")

+ 

+     # Create a modification LDIF

+     dbgen_mod_load_parser = subcommands.add_parser('mod-load', help='Generate a LDIF containing modify operations.  This is intended to be consumed by ldapmodify.')

+     dbgen_mod_load_parser.set_defaults(func=dbgen_create_mods)

+     dbgen_mod_load_parser.add_argument('--create-users', action='store_true', help="Create the entries that will be modified or deleted.  By default the script assumes the user entries already exist.")

+     dbgen_mod_load_parser.add_argument('--delete-users', action='store_true', help="Delete all the user entries at the end of the LDIF.")

+     dbgen_mod_load_parser.add_argument('--num-users', type=int, help="The number of user entries that will be modified or deleted")

+     dbgen_mod_load_parser.add_argument('--parent', help="The DN of the parent entry where the user entries are located.")

+     dbgen_mod_load_parser.add_argument('--create-parent', action='store_true', help="Create the parent entry")

+     dbgen_mod_load_parser.add_argument('--add-users', default=100, help="The number of additional entries to add during the load.")

+     dbgen_mod_load_parser.add_argument('--del-users', default=100, help="The number of entries to delete during the load.")

+     dbgen_mod_load_parser.add_argument('--modrdn-users', default=100, help="The number of entries to perform a modrdn operation on.")

+     dbgen_mod_load_parser.add_argument('--mod-users', default=100, help="The number of entries to modify.")

+     dbgen_mod_load_parser.add_argument('--mod-attrs', nargs="*", default=['description'], help="List of attributes the script will randomly choose from when modifying an entry.  The default is \"description\".")

+     dbgen_mod_load_parser.add_argument('--randomize', action='store_true', help="Randomly perform the specified add, mod, delete, and modrdn operations")

+     dbgen_mod_load_parser.add_argument('--ldif-file', default=DEFAULT_LDIF, help=f"The LDIF file name.  Default is \"{DEFAULT_LDIF}\"")

+ 

+     # Create a heavily nested LDIF

+     dbgen_nested_parser = subcommands.add_parser('nested', help='Generate a heavily nested database LDIF in a cascading/fractal tree design')

+     dbgen_nested_parser.set_defaults(func=dbgen_create_nested)

+     dbgen_nested_parser.add_argument('--num-users', help="The total number of user entries to create in the entire LDIF (does not include the container entries).")

+     dbgen_nested_parser.add_argument('--node-limit', help="The total number of user entries to create under each node/subtree")

+     dbgen_nested_parser.add_argument('--suffix', help="The suffix DN for the LDIF")

+     dbgen_nested_parser.add_argument('--ldif-file', default="nested-users.ldif",  help=f"The LDIF file name.  Default location is the server's LDIF directory using the name 'users.ldif'")

@@ -7,10 +7,9 @@ 

  # --- END COPYRIGHT BLOCK ---

  

  import json

- from getpass import getpass

- from lib389.cli_base import connect_instance, disconnect_instance, format_error_to_dict

+ from lib389.cli_base import connect_instance, disconnect_instance

  from lib389.cli_base.dsrc import dsrc_to_ldap, dsrc_arg_concat

- from lib389.backend import Backend, Backends

+ from lib389.backend import Backends

  from lib389.config import Encryption, Config

  from lib389.monitor import MonitorDiskSpace

  from lib389.replica import Replica, Changelog5

@@ -42,7 +42,7 @@ 

      repl_get_nsstate = subparsers.add_parser('get-nsstate', help="""Get the replication nsState in a human readable format

  

  Replica DN:           The DN of the replication configuration entry

- Replica SUffix:       The replicated suffix

+ Replica Suffix:       The replicated suffix

  Replica ID:           The Replica identifier

  Gen Time              The time the CSN generator was created

  Gen Time String:      The time string of generator
@@ -61,4 +61,4 @@ 

  """)

      repl_get_nsstate.add_argument('--suffix', default=False, help='The DN of the replication suffix to read the state from')

      repl_get_nsstate.add_argument('--flip', default=False, help='Flip between Little/Big Endian, this might be required for certain architectures')

-     repl_get_nsstate.set_defaults(func=get_nsstate)  

+     repl_get_nsstate.set_defaults(func=get_nsstate)

file modified
+583 -38
@@ -1,5 +1,5 @@ 

  # --- BEGIN COPYRIGHT BLOCK ---

- # Copyright (C) 2017 Red Hat, Inc.

+ # Copyright (C) 2020 Red Hat, Inc.

  # All rights reserved.

  #

  # License: GPL (version 3 or any later version).
@@ -8,12 +8,14 @@ 

  

  # Replacement of the dbgen.pl utility

  

- from lib389.utils import pseudolocalize

+ from lib389.utils import (ensure_str, pseudolocalize)

  import random

  import os

  import pwd

  import grp

  

+ global node_count

+ 

  DBGEN_POSITIONS = [

  "Accountant",

  "Admin",
@@ -66,21 +68,22 @@ 

  ]

  

  DBGEN_OUS = [

- "Accounting",

- "Product Development",

- "Product Testing",

- "Human Resources",

- "Payroll",

- "People",

- "Groups",

+ "accounting",

+ "product development",

+ "product testing",

+ "human resources",

+ "payroll",

+ "people",

+ "groups",

  ]

  

- DBGEN_TEMPLATE = """dn: {DN}

+ DBGEN_TEMPLATE = """dn: {DN}{CHANGETYPE}

  objectClass: top

  objectClass: person

  objectClass: organizationalPerson

  objectClass: inetOrgPerson

- cn: {FIRST} {LAST}

+ objectclass: inetUser

+ cn: {CN}

  sn: {LAST}

  uid: {UID}

  givenName: {FIRST}
@@ -100,7 +103,7 @@ 

  ou: {OU}

  mail: {UID}@example.com

  mail: {UIDNUMBER}@example.com

- postalAddress: 518,  Dept #851, Room#{OU}

+ postalAddress: 518, Dept #851, Room#{OU}

  title: {TITLE}

  usercertificate;binary:: MIIBvjCCASegAwIBAgIBAjANBgkqhkiG9w0BAQQFADAnMQ8wDQYD

   VQQDEwZjb25maWcxFDASBgNVBAMTC01NUiBDQSBDZXJ0MB4XDTAxMDQwNTE1NTEwNloXDTExMDcw
@@ -114,25 +117,109 @@ 

  

  """

  

- DBGEN_HEADER = """dn: {SUFFIX}

+ DBGEN_OU_TEMPLATE = """dn: ou={OU},{SUFFIX}

  objectClass: top

- objectClass: domain

- dc: {RDN}

- aci: (target=ldap:///{SUFFIX})(targetattr=*)(version 3.0; acl "acl1"; allow(write) userdn = "ldap:///self";)

- aci: (target=ldap:///{SUFFIX})(targetattr=*)(version 3.0; acl "acl2"; allow(write) groupdn = "ldap:///cn=Directory Administrators, {SUFFIX}";)

- aci: (target=ldap:///{SUFFIX})(targetattr=*)(version 3.0; acl "acl3"; allow(read, search, compare) userdn = "ldap:///anyone";)

+ objectClass: organizationalUnit

+ ou: {OU}

  

  """

  

- DBGEN_OU_TEMPLATE = """dn: ou={OU},{SUFFIX}

+ RANDOM_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqurstuvwxyz0123456789_#@%&()?~$^`~*-=+{}|"\'.,<>'

+ 

+ 

+ def finalize_ldif_file(instance, ldif_file):

+     # Make the file owned by dirsrv

+     os.chmod(ldif_file, 0o644)

+     if os.getuid() == 0:

+         # root user - chown the ldif to the server user

+         userid = ensure_str(instance.userid)

+         uid = pwd.getpwnam(userid).pw_uid

+         gid = grp.getgrnam(userid).gr_gid

+         os.chown(ldif_file, uid, gid)

+ 

+ 

+ def get_index(idx, numUsers):

+     # Get ldclt style entry "0" padded index number

+     zeroLen = len(str(numUsers)) - len(str(idx))

+     index = '0' * zeroLen

+     index = index + str(idx)

+     return index

+ 

+ 

+ def get_node(suffix):

+     # Build a node/container entry based on the suffix DN

+     rdn_attr = suffix.split('=')[0].lower()

+     rdn_attr_val = suffix.split('=')[1].split(',')[0]

+     if rdn_attr == 'c':

+         oc = 'country'

+     elif rdn_attr == 'cn':

+         oc = 'nscontainer'

+     elif rdn_attr == 'dc':

+         oc = 'domain'

+     elif rdn_attr == 'o':

+         oc = 'organization'

+     elif rdn_attr == 'ou':

+         oc = 'organizationalunit'

+     else:

+         # Unsupported rdn

+         raise ValueError("Suffix RDN '{}' in '{}' is not supported.  Supported RDN's are: 'c', 'cn', 'dc', 'o', and 'ou'".format(rdn_attr, suffix))

+ 

+     return f"""dn: {suffix}

  objectClass: top

- objectClass: organizationalUnit

- ou: {OU}

+ objectClass: {oc}

+ {rdn_attr}: {rdn_attr_val}

+ aci: (target=ldap:///{suffix})(targetattr=*)(version 3.0; acl "Self Write"; allow(write) userdn = "ldap:///self";)

+ aci: (target=ldap:///{suffix})(targetattr=*)(version 3.0; acl "Directory Admin Group"; allow(write) groupdn = "ldap:///cn=Directory Administrators,ou=Groups,{suffix}";)

+ aci: (target=ldap:///{suffix})(targetattr=*)(version 3.0; acl "Anonymous Access"; allow(read, search, compare) userdn = "ldap:///anyone";)

  

  """

  

  

- def dbgen(instance, number, ldif_file, suffix, pseudol10n=False):

+ def randomPick(values):

+     # Return a randomly selected value from the provided list of values

+     val_count = len(values)

+     val_count -= 1

+     idx = random.randint(0, val_count)

+     return values[idx].lstrip()

+ 

+ 

+ def write_generic_user(LDIF, index, number, parent, name="user", changetype="", pseudol10n=False):

+     uid_val = name + get_index(index, number)

+     ou = random.choice(DBGEN_OUS)

+     first = uid_val

+     last = uid_val[::-1]  # reverse name

+     cn = f"{first} {last}"

+     initials = "%s. %s" % (first[0], last[0])

+     l = random.choice(DBGEN_LOCATIONS)

+     title = "%s %s" % (random.choice(DBGEN_TITLE_LEVELS), random.choice(DBGEN_POSITIONS))

+     if pseudol10n:

+         ou = pseudolocalize(ou)

+         first = pseudolocalize(first)

+         last = pseudolocalize(last)

+         initials = pseudolocalize(initials)

+         l = pseudolocalize(l)

+         title = pseudolocalize(title)

+     dn = f"uid={uid_val},{parent}"

+ 

+     LDIF.write(DBGEN_TEMPLATE.format(

+         DN=dn,

+         CHANGETYPE=changetype,

+         UID=uid_val,

+         UIDNUMBER=index,

+         FIRST=first,

+         LAST=last,

+         CN=cn,

+         INITIALS=initials,

+         OU=ou,

+         LOCATION=l,

+         TITLE=title,

+     ))

+     return dn

+ 

+ def dbgen_users(instance, number, ldif_file, suffix, generic=False, entry_name="user", parent=None, startIdx=0, rdnCN=False, pseudol10n=False):

+     """

+     Generate an LDIF of randomly named entries

+     """

      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 = []
@@ -142,20 +229,34 @@ 

      with open(givename_file, 'r') as f:

          givennames = [n.strip() for n in f]

  

-     with open(ldif_file, 'w') as output:

-         rdn = suffix.split(",", 1)[0].split("=", 1)[1]

-         output.write(DBGEN_HEADER.format(SUFFIX=suffix, RDN=rdn))

+     with open(ldif_file, 'w') as LDIF:

+         LDIF.write(get_node(suffix))

          for ou in DBGEN_OUS:

              ou = pseudolocalize(ou) if pseudol10n else ou

-             output.write(DBGEN_OU_TEMPLATE.format(SUFFIX=suffix, OU=ou))

-         for i in range(0, number):

+             LDIF.write(DBGEN_OU_TEMPLATE.format(SUFFIX=suffix, OU=ou))

+ 

+         if parent is not None:

+             parent_rdn = parent.split(',')[0].split('=')[1]

+             if parent_rdn.lower() not in DBGEN_OUS:

+                 LDIF.write(get_node(parent))

+ 

+         for i in range(1, int(number) + 1):

              # Pick a random ou

              ou = random.choice(DBGEN_OUS)

              first = random.choice(givennames)

              last = random.choice(familynames)

+             if generic:

+                 i += startIdx

+                 name = entry_name + get_index(i, number)

+                 uid = name

+                 cn = name

+             else:

+                 first = random.choice(givennames)

+                 last = random.choice(familynames)

+                 uid = "%s%s%s" % (first[0], last, i)

+                 cn = f"{first} {last}"

              # How do we subscript from a generator?

              initials = "%s. %s" % (first[0], last[0])

-             uid = "%s%s%s" % (first[0], last, i)

              l = random.choice(DBGEN_LOCATIONS)

              title = "%s %s" % (random.choice(DBGEN_TITLE_LEVELS), random.choice(DBGEN_POSITIONS))

              if pseudol10n:
@@ -165,24 +266,468 @@ 

                  initials = pseudolocalize(initials)

                  l = pseudolocalize(l)

                  title = pseudolocalize(title)

-             dn = "uid=%s,ou=%s,%s" % (uid, ou, suffix)

-             output.write(DBGEN_TEMPLATE.format(

+ 

+             if parent is None:

+                 parent = f"ou={ou},{suffix}"

+ 

+             if rdnCN:

+                 # Not using "uid" so use "cn" instead

+                 dn = f"cn={cn},{parent}"

+             else:

+                 dn = f"uid={uid},{parent}"

+ 

+             LDIF.write(DBGEN_TEMPLATE.format(

                  DN=dn,

+                 CHANGETYPE="",

                  UID=uid,

                  UIDNUMBER=i,

                  FIRST=first,

                  LAST=last,

+                 CN=cn,

                  INITIALS=initials,

                  OU=ou,

                  LOCATION=l,

                  TITLE=title,

-                 SUFFIX=suffix

              ))

  

-     # Make the file owned by dirsrv

-     os.chmod(ldif_file, 0o644)

-     if os.getuid() == 0:

-         # root user - chown the ldif to the server user

-         uid = pwd.getpwnam(instance.userid).pw_uid

-         gid = grp.getgrnam(instance.userid).gr_gid

-         os.chown(ldif_file, uid, gid)

+     finalize_ldif_file(instance, ldif_file)

+ 

+ 

+ def dbgen_groups(instance, ldif_file, props):

+     """

+     Create static group(s) and the member entries

+ 

+         props = {

+             "name": STRING,

+             "parent": DN,

+             "suffix": DN,

+             "number": ###,  --> number of groups to create

+             "numMembers": ###,

+             "createMembers": True/False  --> Create the member entries (default is True)

+             "memberParent": DN

+             "membershipAttr": ATTR

+         }

+     """

+     with open(ldif_file, 'w') as LDIF:

+         # Create the top node

+         LDIF.write(get_node(props['suffix']))

+         if props['parent'] is not None:

+             if props['parent'] != props['suffix']:

+                 # Create the group container

+                 LDIF.write(get_node(props['parent']))

+         else:

+             props['parent'] = props['suffix']

+ 

+         if props['memberParent'] is not None:

+             if props['memberParent'] != props['suffix'] and props['memberParent'] != props['parent']:

+                 # Create the member/user container

+                 LDIF.write(get_node(props['memberParent']))

+         else:

+             props['memberParent'] = props['suffix']

+ 

+         for idx in range(1, int(props['number']) + 1):

+             # Build the member list and create the member entries

+             group_member_list = []

+             if props['createMembers']:

+                 for user_idx in range(1, int(props['numMembers']) + 1):

+                     dn = write_generic_user(LDIF, user_idx, props['numMembers'], props['memberParent'], name=f"group_entry{idx}-")

+                     group_member_list.append(dn)

+             else:

+                 # Not creating the member entries, just build the DN list of members

+                 for user_idx in range(1, int(props['numMembers']) + 1):

+                     name = "user" + get_index(user_idx, props['numMembers'])

+                     dn = f"uid={name},{props['memberParent']}"

+                     group_member_list.append(dn)

+ 

+             if props['number'] == 0:

+                 # Only creating one group, do not add the idx to DN

+                 group_dn = f"dn: cn={props['name']},{props['parent']}\n"

+                 cn = f"cn={props['name']},{props['parent']}"

+             else:

+                 group_dn = f"dn: cn={props['name']}-{idx},{props['parent']}\n"

+                 cn = f"cn={props['name']}-{idx},{props['parent']}"

+ 

+             LDIF.write(group_dn)

+             LDIF.write('objectclass: top\n')

+             LDIF.write('objectclass: groupOfUniqueNames\n')

+             LDIF.write('objectclass: groupOfNames\n')

+             LDIF.write('objectclass: inetAdmin\n')

+             LDIF.write(f'cn: {cn}\n')

+             for dn in group_member_list:

+                 LDIF.write(f"{props['membershipAttr']}: {dn}\n")

+             LDIF.write('\n')

+ 

+     finalize_ldif_file(instance, ldif_file)

+ 

+ 

+ def dbgen_cos_def(instance, ldif_file, props):

+     """

+     Create a COS definition

+ 

+         props = {

+             "cosType": "classic", "pointer", "indirect",

+             "defName": VAL,

+             "defParent": VAL,

+             "defCreateParent": True/False,

+             "cosSpecifier": can be used for cosIndirectSpecifier

+             "cosAttrs": [],

+             "tmpName": DN (need to classic and pointer COS defs)

+         }

+     """

+ 

+     if props['cosType'] == 'pointer':

+         objectclass = 'objectclass: cosPointerDefinition\n'

+     if props['cosType'] == 'indirect':

+         objectclass = 'objectclass: cosIndirectDefinition\n'

+     if props['cosType'] == 'classic':

+         objectclass = 'objectclass: cosClassicDefinition\n'

+ 

+     with open(ldif_file, 'w') as LDIF:

+         # Create parent

+         if props['defCreateParent']:

+             LDIF.write(get_node(props['defParent']))

+ 

+         #

+         # Create definition

+         #

+         dn = (f"dn: cn={props['defName']},{props['defParent']}\n")

+         LDIF.write(dn)

+         LDIF.write('objectclass: top\n')

+         LDIF.write('objectclass: cosSuperDefinition\n')

+         LDIF.write(objectclass)

+         LDIF.write('cn: ' + props['defName'] + "\n")

+ 

+         if props['cosType'] == 'pointer' or props['cosType'] == 'classic':

+             LDIF.write(f"cosTemplateDN: {props['tmpName']}\n")

+         if props['cosType'] == 'indirect':

+             LDIF.write(f"cosIndirectSpecifier: {props['cosSpecifier']}\n")

+         elif props['cosType'] == "classic":

+             LDIF.write(f"cosSpecifier: {props['cosSpecifier']}\n")

+         for attr in props['cosAttrs']:

+             # There can be multiple COS attributes

+             LDIF.write(f"cosAttribute: {attr}\n")

+ 

+     finalize_ldif_file(instance, ldif_file)

+ 

+ 

+ def dbgen_cos_template(instance, ldif_file, props):

+     """

+     Create a COS Template

+ 

+         props = {

+             "tmpName": VAL,

+             "tmpParent": VAL,

+             "tmpCreateParent": True/False,

+             "cosPriority": ####

+             "cosTmpAttrVal": Attr/val

+         }

+     """

+ 

+     with open(ldif_file, 'w') as LDIF:

+         # Create parent

+         if props['tmpCreateParent']:

+             LDIF.write(get_node(props['tmpParent']))

+ 

+         # Create template

+         dn = f"dn: cn={props['tmpName']},{props['tmpParent']}\n"

+         LDIF.write(dn)

+         LDIF.write('objectclass: top\n')

+         LDIF.write('objectclass: extensibleObject\n')

+         LDIF.write('objectclass: cosTemplate\n')

+         LDIF.write(f"cn: {props['tmpName']}\n")

+         if props['cosPriority'] is not None:

+             LDIF.write(f"cosPriority: {props['cosPriority']}\n")

+         pair = props['cosTmpAttrVal'].split(':')

+         LDIF.write(f"{pair[0]}: {pair[1]}\n")

+ 

+     finalize_ldif_file(instance, ldif_file)

+ 

+ 

+ def dbgen_role(instance, ldif_file, props):

+     """

+     Create a Role

+ 

+         props = {

+             role_type: "managed", "filter", pr "nested",

+             role_name: NAME,

+             parent: DN of parent,

+             createParent: True/False,

+             filter: FILTER,

+             role_list: [DN, DN, ...]  # For nested role only

+         }

+     """

+ 

+     if props['role_type'].lower() == 'managed':

+         objectclasses = ('objectclass: nsSimpleRoleDefinition\n' +

+                          'objectclass: nsManagedRoleDefinition\n')

+     elif props['role_type'].lower() == 'filtered':

+         objectclasses = ('objectclass: nsComplexRoleDefinition\n' +

+                          'objectclass: nsFilteredRoleDefinition\n')

+     elif props['role_type'].lower() == 'nested':

+         objectclasses = ('objectclass: nsComplexRoleDefinition\n' +

+                          'objectclass: nsNestedRoleDefinition\n')

+ 

+     with open(ldif_file, 'w') as LDIF:

+         # Create parent entry

+         if props['createParent']:

+             LDIF.write(get_node(props['parent']))

+ 

+         dn = f"dn: cn={props['role_name']},{props['parent']}\n"

+         LDIF.write(dn)

+         LDIF.write('objectclass: top\n')

+         LDIF.write('objectclass: LdapSubEntry\n')

+         LDIF.write('objectclass: nsRoleDefinition\n')

+         LDIF.write(objectclasses)

+         LDIF.write(f"cn: {props['role_name']}\n")

+         if props['role_type'] == 'nested':

+             # Write out each role DN

+             for value in props['role_list']:

+                 LDIF.write(f'nsRoleDN: {value}\n')

+         elif props['role_type'] == 'filtered':

+             LDIF.write(f"nsRoleFilter: {props['filter']}\n")

+ 

+     finalize_ldif_file(instance, ldif_file)

+ 

+ 

+ def dbgen_mod_load(ldif_file, props):

+     """

+     Generate a "load" LDIF file that can be consumed by ldapmodify

+ 

+         props = {

+             "createUsers": True/False,

+             "deleteUsers": True/False,

+             "numUsers": ###,

+             "parent": DN,  --> ou=people,dc=example,dc=com

+             "createParent": True/False,

+             "addUsers": ###,

+             "delUsers": ###,

+             'modrdnUsers": ###,

+             "modUsers": ###,  --> number of entries to modify

+             "random": True/False,

+             "modAttrs": [ATTR, ATTR, ...]

+         }

+     """

+ 

+     entry_dn_list = []  # List used to delete entries at the end of the LDIF

+     if props['modAttrs'] is None:

+         props['modAttrs'] = ['description', 'title']

+ 

+     with open(ldif_file, 'w') as LDIF:

+         if props['createParent']:

+             # Create the container entry that the users will be add to

+             LDIF.write(get_node(props['parent']))

+ 

+         # Create entries

+         for user_idx in range(1, props['numUsers'] + 1):

+             if props['createUsers']:

+                 dn = write_generic_user(

+                     LDIF, user_idx, props['numUsers'], props['parent'],

+                     changetype="\nchangetype: add")

+                 entry_dn_list.append(dn)

+             else:

+                 dn = f"uid=user{get_index(user_idx, props['numUsers'])},{props['parent']}"

+                 entry_dn_list.append(dn)

+ 

+         # Set the types of operations and how many of them to perform

+         addc = int(props['addUsers'])

+         delc = int(props['delUsers'])

+         modc = int(props['modUsers'])

+         mrdnc = int(props['modrdnUsers'])

+         total_ops = addc + delc + modc + mrdnc

+ 

+         if props['random']:

+             # Mix up the selected operations

+             operations = ['add', 'mod', 'modrdn', 'delete']

+             while total_ops != 0:

+                 op = randomPick(operations)

+                 if op == 'add':

+                     if addc == 0:

+                         # no more adds to do

+                         operations.remove('add')

+                         continue

+                     dn = write_generic_user(

+                         LDIF, addc, props['addUsers'], props['parent'],

+                         name="addUser", changetype="\nchangetype: add")

+                     entry_dn_list.append(dn)

+                     addc -= 1

+                 elif op == 'mod':

+                     if modc == 0:

+                         # no more mods to do

+                         operations.remove('mod')

+                         continue

+                     attr = randomPick(props['modAttrs'])

+                     val = (''.join((random.choice(RANDOM_CHARS) for i in range(0, random.randint(10, 30)))))

+                     LDIF.write(f"dn: uid=user{get_index(modc, props['numUsers'])},{props['parent']}\n")

+                     LDIF.write("changetype: modify\n")

+                     LDIF.write(f"replace: {attr}\n")

+                     LDIF.write(f"{attr}: {val}\n")

+                     LDIF.write("\n")

+                     modc -= 1

+                 elif op == 'delete':

+                     if delc == 0:

+                         # no more deletes to do

+                         operations.remove('delete')

+                         continue

+ 

+                     dn_val = f"uid=user{get_index(delc, props['numUsers'])},{props['parent']}"

+                     LDIF.write(f"dn: {dn_val}\n")

+                     LDIF.write("changetype: delete\n")

+                     LDIF.write(" \n")

+                     delc -= 1

+                     if dn_val in entry_dn_list:

+                         entry_dn_list.remove(dn_val)

+                 elif op == 'modrdn':

+                     if mrdnc == 0:

+                         # no more modrdns to do

+                         operations.remove('modrdn')

+                         continue

+                     dn_val = f"uid=user{get_index(mrdnc, props['numUsers'])},{props['parent']}"

+                     new_dn_val = f"cn=user{get_index(mrdnc, props['numUsers'])},{props['parent']}"

+                     LDIF.write(f"dn: {dn_val}\n")

+                     LDIF.write("changetype: modrdn\n")

+                     LDIF.write(f"newrdn: {new_dn_val}\n")

+                     LDIF.write("deleteoldrdn: 1\n")

+                     LDIF.write("\n")

+                     mrdnc -= 1

+                     # Revise the DN list: add the new DN, and remove the old DN

+                     entry_dn_list.append(new_dn_val)

+                     if dn_val in entry_dn_list:

+                         entry_dn_list.remove(dn_val)

+ 

+                 # Update the total count

+                 total_ops -= 1

+         else:

+             # Sequentially do each type of operation

+ 

+             # Do the Adds

+             addc = int(props['addUsers'])

+             while addc != 0:

+                 dn = write_generic_user(

+                     LDIF, addc, props['addUsers'], props['parent'],

+                     name="addUser", changetype="\nchangetype: add")

+                 addc -= 1

+                 entry_dn_list.append(dn)

+ 

+             # Mods

+             while modc != 0:

+                 attr = randomPick(props['modAttrs'])

+                 val = (''.join((random.choice(RANDOM_CHARS) for i in range(0, random.randint(10, 30)))))

+                 LDIF.write(f"dn: uid=user{get_index(modc, props['numUsers'])},{props['parent']}\n")

+                 LDIF.write("changetype: modify\n")

+                 LDIF.write(f"replace: {attr}\n")

+                 LDIF.write(f"{attr}: {val}\n")

+                 LDIF.write("\n")

+                 modc -= 1

+ 

+             # Modrdns

+             while mrdnc != 0:

+                 dn_val = f"uid=user{get_index(mrdnc, props['numUsers'])},{props['parent']}"

+                 new_dn_val = f"cn=user{get_index(mrdnc, props['numUsers'])},{props['parent']}"

+                 LDIF.write(f"dn: {dn_val}\n")

+                 LDIF.write("changetype: modrdn\n")

+                 LDIF.write(f"newrdn: {new_dn_val}\n")

+                 LDIF.write("deleteoldrdn: 1\n")

+                 LDIF.write("\n")

+                 mrdnc -= 1

+                 # Revise the DN list: add the new DN, and remove the old DN

+                 entry_dn_list.append(new_dn_val)

+                 if dn_val in entry_dn_list:

+                     entry_dn_list.remove(dn_val)

+ 

+             # Deletes

+             while delc != 0:

+                 dn_val = f"uid=user{get_index(delc, props['numUsers'])},{props['parent']}"

+                 LDIF.write(f"dn: {dn_val}\n")

+                 LDIF.write("changetype: delete\n")

+                 LDIF.write(" \n")

+                 delc -= 1

+                 if dn_val in entry_dn_list:

+                     entry_dn_list.remove(dn_val)

+ 

+         # Cleanup - delete all known entries

+         if props['deleteUsers']:

+             for dn in entry_dn_list:

+                 LDIF.write(f"dn: {dn}\n")

+                 LDIF.write("changetype: delete\n")

+                 LDIF.write(" \n")

+ 

+ 

+ def build_recursive_nodes(LDIF, dn, node_limit, max_entries):

+     """

+     Recursively create two nodes under each node, continue until there are no

+     more max_entries.

+     """

+     global node_count

+     wrote_node1 = False

+     wrote_node2 = False

+ 

+     # Create containers for DN1 and DN2

+     dn1 = "ou=1," + dn

+     LDIF.write(f'dn: {dn1}\n')

+     LDIF.write('objectclass: top\n')

+     LDIF.write('objectclass: organizationalUnit\n')

+     LDIF.write('ou: ou=1\n\n')

+ 

+     dn2 = "ou=2," + dn

+     LDIF.write(f'dn: {dn2}\n')

+     LDIF.write('objectclass: top\n')

+     LDIF.write('objectclass: organizationalUnit\n')

+     LDIF.write('ou: ou=2\n\n')

+ 

+     # Add entries under each node

+     for entry_idx in range(1, node_limit + 1):

+         write_generic_user(LDIF, entry_idx, node_limit, dn1)

+         max_entries -= 1

+         if not wrote_node1:

+             wrote_node1 = True

+             node_count += 1

+         if max_entries == 0:

+             break

+ 

+         write_generic_user(LDIF, entry_idx, node_limit, dn2)

+         max_entries -= 1

+         if not wrote_node2:

+             wrote_node2 = True

+             node_count += 1

+         if max_entries == 0:

+             break

+ 

+     # Are we out of entries

+     if max_entries == 0:

+         return

+ 

+     # Get the remaining entries to be split between two nodes

+     new_node_max = max_entries // 2

+     if (max_entries % 2) > 0:

+         remainder = 1

+     else:

+         remainder = 0

+ 

+     # Recursively build child nodes

+     build_recursive_nodes(LDIF, dn1, node_limit, new_node_max)

+     build_recursive_nodes(LDIF, dn2, node_limit, new_node_max + remainder)

+ 

+ 

+ def dbgen_nested_ldif(instance, ldif_file, props):

+     """

+     Create a deeply nested LDIF

+ 

+         props = {

+             "numUsers": ####   --> Total number of user entries to create

+             'nodeLimit': ####  --> max number of entries to put into each node

+             "suffix": DN

+         }

+     """

+ 

+     global node_count

+     node_count = 0

+     with open(ldif_file, 'w') as LDIF:

+         # Create the top suffix

+         LDIF.write(get_node(props['suffix']))

+ 

+         # Create all the nodes

+         build_recursive_nodes(LDIF, props['suffix'], props['nodeLimit'], props['numUsers'])

+ 

+     finalize_ldif_file(instance, ldif_file)

+ 

+     return node_count

Description: Ported the main features to lib389 and added some other useful features:

          Now there are several LDIFs that can be created:

          - User LDIFs (different types)
          - Group LDIFs
          - COS LDIFs
          - Role LDIFs
          - Modification LDIFs
          - Nested LDIFs

    There is also an interactive mode which makes some of this much easier to understand.  The interactive mode is initiated by not providing any arguments to the subcommand,  See the design doc for more info.

Design Doc: https://www.port389.org/docs/389ds/design/dbgen-design.html

fixes: https://pagure.io/389-ds-base/issue/50545

maybe for the cli we call it ldifgen not dbgen?

Besides that one naming comment, it otherwise looks good to me. :) Great to see this nearly done!

Could you please take a look at the tests that use lib389.dbgen? They are failing now...

tests/suites/mapping_tree/referral_during_tot_init_test.py:17:from lib389.dbgen import dbgen
tests/suites/basic/basic_test.py:21:from lib389.dbgen import dbgen
tests/suites/syntax/mr_test.py:5:from lib389.dbgen import dbgen
tests/suites/import/regression_test.py:13:from lib389.dbgen import dbgen
tests/stress/search/simple.py:11:from lib389.dbgen import dbgen

Why not get the props from get_input() here too? I think it shouldn't hurt... Or do I miss something?

It gives Error: [Errno 13] Permission denied: '/tmp/dbgen.ldif' error when /tmp/dbgen.ldif already exists.
Maybe the tool should ask something like '/tmp/dbgen.ldif' already exists. Do you want to overwrite it? Or something like this...

Another a bit odd behaviour that I found is when we specify optional arguments like

dsctl localhost dbgen groups --number 10 --suffix dc=example,dc=com my_group

then dbgen just silently assumes that other arguments should be default ones.

It may be intended but then we shouldn't do that silently, in my opinion...
Or, as an option, we should ask about the non-specified arguments.

maybe for the cli we call it ldifgen not dbgen?

Hahaha, my tool I ported most of this from is call "ldif-gen.py", so yes I will gladly make that change :-)

Another a bit odd behaviour that I found is when we specify optional arguments like
dsctl localhost dbgen groups --number 10 --suffix dc=example,dc=com my_group

then dbgen just silently assumes that other arguments should be default ones.
It may be intended but then we shouldn't do that silently, in my opinion...
Or, as an option, we should ask about the non-specified arguments.

Not sure how I feel about this request. You should not have to specify 10 options if you just want to quickly create a basic group with commonly used attributes. I'm trying to make the tool really easy/simple and fast. So, if you want to tweak the default LDIF you can use the other options. Or, if you want that fine grained control you could use the interactive mode which asks for everything.

Not sure how I feel about this request. You should not have to specify 10 options if you just want to quickly create a basic group with commonly used attributes. I'm trying to make the tool really easy/simple and fast. So, if you want to tweak the default LDIF you can use the other options. Or, if you want that fine grained control you could use the interactive mode which asks for everything.

Maybe we can print the defaults that were used? Just one simple line.
It will be more explicit for the user. Which never hurts.

And if the user doesn't like the options he can regenerate the thing right away (basically, in the end, it is for speeding up the process)

Not sure how I feel about this request. You should not have to specify 10 options if you just want to quickly create a basic group with commonly used attributes. I'm trying to make the tool really easy/simple and fast. So, if you want to tweak the default LDIF you can use the other options. Or, if you want that fine grained control you could use the interactive mode which asks for everything.

Maybe we can print the defaults that were used? Just one simple line.
It will be more explicit for the user. Which never hurts.

Isn't that what "--help" is for? :-)

And if the user doesn't like the options he can regenerate the thing right away (basically, in the end, it is for speeding up the process)

Again isn't this what the interactive mode is for? The non-interactive mode should just run and assume you know what you want to do. I do not want to make the non-interactive mode interactive.

So... I'm not rejecting what you are saying, but I don't think I am visualizing what you want it to look like. Maybe you could write up some mock terminal output, or something, and show me how you think it should it behave? Thanks!!

rebased onto dfd34337bdff1889fb4bb3a031107fb37ff7147b

3 years ago

Fixed all of other issues in the meantime:

  • Improved LDIF file validation
  • Fixed CI tests
  • Fixed nested LDIF entries (nsContainer -> organizationalUnit)

Not sure how I feel about this request. You should not have to specify 10 options if you just want to quickly create a basic group with commonly used attributes. I'm trying to make the tool really easy/simple and fast. So, if you want to tweak the default LDIF you can use the other options. Or, if you want that fine grained control you could use the interactive mode which asks for everything.
Maybe we can print the defaults that were used? Just one simple line.
It will be more explicit for the user. Which never hurts.

Isn't that what "--help" is for? :-)

Okay:) I was thinking more and I think it's okay but we should make sure that the --help has all of the defaults in the text.
For example, we can run like this:

 dsctl localhost ldifgen groups --suffix dc=example,dc=com my_group  
 Writing LDIF file /tmp/dbgen.ldif ...
 Successfully created LDIF file: /tmp/dbgen.ldif

But the --help doesn't have a default for the number (and there are more cases like that)

 dsctl localhost ldifgen groups --suffix dc=example,dc=com my_group -h
 ...
 --number NUMBER       The number of groups to create.
 ...

Also, I am a bit confused here:

dsctl localhost ldifgen users --suffix dc=example,dc=com
Missing required parameters, switching to Interactive mode ...
Enter the suffix [dc=example,dc=com]:

Because --help doesn't have any mentioning about these required parameters. So I don't know what I missed (if I want a non-interactive mode)

dsctl localhost ldifgen users -h
usage: dsctl [instance] ldifgen users [-h] [--number NUMBER] [--suffix SUFFIX]
                                  [--parent PARENT] [--generic]
                                  [--start-idx START_IDX] [--rdn-cn]
                                  [--localize] [--ldif-file LDIF_FILE]

optional arguments:
-h, --help            show this help message and exit
--number NUMBER       The number of users to create.
--suffix SUFFIX       The database suffix where the entries will be created.
...

And if the user doesn't like the options he can regenerate the thing right away (basically, in the end, it is for speeding up the process)

So... I'm not rejecting what you are saying, but I don't think I am visualizing what you want it to look like. Maybe you could write up some mock terminal output, or something, and show me how you think it should it behave? Thanks!!

I thought about something like this:

dsctl localhost ldifgen users  --suffix dc=example,dc=com --number=10
Non-interactive mode is used.
Setting the defaults: parent='ou=people,dc=example,dc=com', rdn-cn='yes', etc.
Writing LDIF file /tmp/dbgen.ldif ...
Successfully created LDIF file: /tmp/dbgen.ldif

If I understand correctly, get_ldif_file_input returns NoneType so args.ldif_file becomes corrupted if we go with the interactive option

It gives Error: [Errno 13] Permission denied: '/tmp/dbgen.ldif' error when /tmp/dbgen.ldif already exists.
Maybe the tool should ask something like '/tmp/dbgen.ldif' already exists. Do you want to overwrite it? Or something like this...

Simple option here, why not write to the ldif dir of the related instance? If we plan to import it to that instance, then this would make the most sense because it then isolates between instances, and means the instance can actually read the ldif dir (a common issue with the import from say /root or /tmp is the dirsrv user of the instance can't read the directory the ldif is in ...)

Sorry to follow up, imagine this case:

instanceA = dbgen
instanceB= dbgen
instanceA.import_from_ldif
instanceb.import_from_ldif

The second dbgen would clobber the first. So if we had parallel tests or anything else it could destroy evidence or import the wrong data ....

It gives Error: [Errno 13] Permission denied: '/tmp/dbgen.ldif' error when /tmp/dbgen.ldif already exists.
Maybe the tool should ask something like '/tmp/dbgen.ldif' already exists. Do you want to overwrite it? Or something like this...

Simple option here, why not write to the ldif dir of the related instance? If we plan to import it to that instance, then this would make the most sense because it then isolates between instances, and means the instance can actually read the ldif dir (a common issue with the import from say /root or /tmp is the dirsrv user of the instance can't read the directory the ldif is in ...)

Well not all of these LDIFs are designed to be imported. Groups, COS, Roles and modification ldifs are design to be used by ldapmodify, not ldif2db. And smaller user LDIF's could also be added using ldapmodify. Now I did think about using the server's LDIF directory since that information is available, but I also want all LDIF options to be consistent and forcing all LDIFs into the server's LDIF directory is not something I want to do, especially since this is all for testing and not for production, and most of the LDIF options are not meant for ldif2db.

Now in the interactive mode I added checks to see if the file can be written, if it can't it keeps prompting for a valid path/name. So permission issues and invalid paths are covered in the interactive mode.

Anyway let me work on all of this and I'll try and come up with something that works for all the cases. :-)

rebased onto 2d9263020be4b14953bd26419293ac750b35a892

3 years ago

All changes applied @spichugi and @firstyear, please review...

Looks really good! You have my ack, at least. :)

rebased onto 326be2c

3 years ago

Pull-Request has been merged by mreynolds

3 years ago

389-ds-base is moving from Pagure to Github. This means that new issues and pull requests
will be accepted only in 389-ds-base's github repository.

This pull request has been cloned to Github as issue and is available here:
- https://github.com/389ds/389-ds-base/issues/4075

If you want to continue to work on the PR, please navigate to the github issue,
download the patch from the attachments and file a new pull request.

Thank you for understanding. We apologize for all inconvenience.

Pull-Request has been closed by spichugi

3 years ago