Blame SOURCES/0023-Issue-4419-Warn-users-of-skipped-entries-during-ldif.patch

be9751
From 43f8a317bcd9040874b27cad905347a9e6bc8a6f Mon Sep 17 00:00:00 2001
be9751
From: James Chapman <jachapma@redhat.com>
be9751
Date: Wed, 9 Dec 2020 22:42:59 +0000
be9751
Subject: [PATCH 4/6] Issue 4419 - Warn users of skipped entries during ldif2db
be9751
 online import (#4476)
be9751
be9751
Bug Description:  During an online ldif2db import entries that do not
be9751
                  conform to various constraints will be skipped and
be9751
                  not imported. On completition of an import with skipped
be9751
                  entries, the server responds with a success message
be9751
                  and logs the skipped entry detail to the error logs.
be9751
                  The success messgae could lead the user to believe
be9751
                  that all entries were successfully imported.
be9751
be9751
Fix Description:  If a skipped entry occurs during import, the import
be9751
                  will continue and a warning message will be displayed.
be9751
                  The schema is extended with a nsTaskWarning attribute
be9751
                  which is used to capture and retrieve any task
be9751
                  warnings.
be9751
be9751
                  CLI tools for online import updated.
be9751
be9751
                  Test added to generate an incorrect ldif entry and perform an
be9751
                  online import.
be9751
be9751
Fixes: https://github.com/389ds/389-ds-base/issues/4419
be9751
be9751
Reviewed by: tbordaz, mreynolds389, droideck, Firstyear (Thanks)
be9751
---
be9751
 .../tests/suites/import/import_test.py        | 39 +++++++++++++++++--
be9751
 ldap/schema/02common.ldif                     |  3 +-
be9751
 .../back-ldbm/db-bdb/bdb_import_threads.c     |  5 +++
be9751
 ldap/servers/slapd/slap.h                     |  1 +
be9751
 ldap/servers/slapd/slapi-plugin.h             | 11 ++++++
be9751
 ldap/servers/slapd/slapi-private.h            |  8 ----
be9751
 ldap/servers/slapd/task.c                     | 29 +++++++++++++-
be9751
 src/lib389/lib389/cli_conf/backend.py         |  6 ++-
be9751
 src/lib389/lib389/tasks.py                    | 23 +++++++++--
be9751
 9 files changed, 108 insertions(+), 17 deletions(-)
be9751
be9751
diff --git a/dirsrvtests/tests/suites/import/import_test.py b/dirsrvtests/tests/suites/import/import_test.py
be9751
index b47db96ed..77c915026 100644
be9751
--- a/dirsrvtests/tests/suites/import/import_test.py
be9751
+++ b/dirsrvtests/tests/suites/import/import_test.py
be9751
@@ -65,6 +65,9 @@ def _import_clean(request, topo):
be9751
         import_ldif = ldif_dir + '/basic_import.ldif'
be9751
         if os.path.exists(import_ldif):
be9751
             os.remove(import_ldif)
be9751
+        syntax_err_ldif = ldif_dir + '/syntax_err.dif'
be9751
+        if os.path.exists(syntax_err_ldif):
be9751
+            os.remove(syntax_err_ldif)
be9751
 
be9751
     request.addfinalizer(finofaci)
be9751
 
be9751
@@ -141,17 +144,19 @@ def _create_bogus_ldif(topo):
be9751
 
be9751
 def _create_syntax_err_ldif(topo):
be9751
     """
be9751
-    Create an incorrect ldif entry that violates syntax check
be9751
+    Create an ldif file, which contains an entry that violates syntax check
be9751
     """
be9751
     ldif_dir = topo.standalone.get_ldif_dir()
be9751
     line1 = """dn: dc=example,dc=com
be9751
 objectClass: top
be9751
 objectClass: domain
be9751
 dc: example
be9751
+
be9751
 dn: ou=groups,dc=example,dc=com
be9751
 objectClass: top
be9751
 objectClass: organizationalUnit
be9751
 ou: groups
be9751
+
be9751
 dn: uid=JHunt,ou=groups,dc=example,dc=com
be9751
 objectClass: top
be9751
 objectClass: person
be9751
@@ -201,6 +206,34 @@ def test_import_with_index(topo, _import_clean):
be9751
     assert f'{place}/userRoot/roomNumber.db' in glob.glob(f'{place}/userRoot/*.db', recursive=True)
be9751
 
be9751
 
be9751
+def test_online_import_with_warning(topo, _import_clean):
be9751
+    """
be9751
+    Import an ldif file with syntax errors, verify skipped entry warning code
be9751
+
be9751
+    :id: 5bf75c47-a283-430e-a65c-3c5fd8dbadb8
be9751
+    :setup: Standalone Instance
be9751
+    :steps:
be9751
+        1. Create standalone Instance
be9751
+        2. Create an ldif file with an entry that violates syntax check (empty givenname)
be9751
+        3. Online import of troublesome ldif file
be9751
+    :expected results:
be9751
+        1. Successful import with skipped entry warning
be9751
+        """
be9751
+    topo.standalone.restart()
be9751
+
be9751
+    import_task = ImportTask(topo.standalone)
be9751
+    import_ldif1 = _create_syntax_err_ldif(topo)
be9751
+
be9751
+    # Importing  the offending ldif file - online
be9751
+    import_task.import_suffix_from_ldif(ldiffile=import_ldif1, suffix=DEFAULT_SUFFIX)
be9751
+
be9751
+    # There is just  a single entry in this ldif
be9751
+    import_task.wait(5)
be9751
+
be9751
+    # Check for the task nsTaskWarning attr, make sure its set to skipped entry code
be9751
+    assert import_task.present('nstaskwarning')
be9751
+    assert TaskWarning.WARN_SKIPPED_IMPORT_ENTRY == import_task.get_task_warn()
be9751
+
be9751
 def test_crash_on_ldif2db(topo, _import_clean):
be9751
     """
be9751
     Delete the cn=monitor entry for an LDBM backend instance. Doing this will
be9751
@@ -246,7 +279,7 @@ def test_ldif2db_allows_entries_without_a_parent_to_be_imported(topo, _import_cl
be9751
     topo.standalone.start()
be9751
 
be9751
 
be9751
-def test_ldif2db_syntax_check(topo):
be9751
+def test_ldif2db_syntax_check(topo, _import_clean):
be9751
     """ldif2db should return a warning when a skipped entry has occured.
be9751
     :id: 85e75670-42c5-4062-9edc-7f117c97a06f
be9751
     :setup:
be9751
@@ -261,7 +294,7 @@ def test_ldif2db_syntax_check(topo):
be9751
     import_ldif1 = _create_syntax_err_ldif(topo)
be9751
     # Import the offending LDIF data - offline
be9751
     topo.standalone.stop()
be9751
-    ret = topo.standalone.ldif2db('userRoot', None, None, None, import_ldif1)
be9751
+    ret = topo.standalone.ldif2db('userRoot', None, None, None, import_ldif1, None)
be9751
     assert ret == TaskWarning.WARN_SKIPPED_IMPORT_ENTRY
be9751
     topo.standalone.start()
be9751
 
be9751
diff --git a/ldap/schema/02common.ldif b/ldap/schema/02common.ldif
be9751
index c6dc074db..821640d03 100644
be9751
--- a/ldap/schema/02common.ldif
be9751
+++ b/ldap/schema/02common.ldif
be9751
@@ -145,6 +145,7 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2356 NAME 'nsTaskExitCode' DESC 'Slapi T
be9751
 attributeTypes: ( 2.16.840.1.113730.3.1.2357 NAME 'nsTaskCurrentItem' DESC 'Slapi Task item' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN '389 Directory Server' )
be9751
 attributeTypes: ( 2.16.840.1.113730.3.1.2358 NAME 'nsTaskTotalItems' DESC 'Slapi Task total items' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN '389 Directory Server' )
be9751
 attributeTypes: ( 2.16.840.1.113730.3.1.2359 NAME 'nsTaskCreated' DESC 'Slapi Task creation date' SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE X-ORIGIN '389 Directory Server' )
be9751
+attributeTypes: ( 2.16.840.1.113730.3.1.2375 NAME 'nsTaskWarning' DESC 'Slapi Task warning code' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN '389 Directory Server' )
be9751
 #
be9751
 # objectclasses:
be9751
 #
be9751
@@ -177,5 +178,5 @@ objectClasses: ( 2.16.840.1.113730.3.2.503 NAME 'nsDSWindowsReplicationAgreement
be9751
 objectClasses: ( 2.16.840.1.113730.3.2.128 NAME 'costemplate' DESC 'Netscape defined objectclass' SUP top MAY ( cn $ cospriority ) X-ORIGIN 'Netscape Directory Server' )
be9751
 objectClasses: ( 2.16.840.1.113730.3.2.304 NAME 'nsView' DESC 'Netscape defined objectclass' SUP top AUXILIARY MAY ( nsViewFilter $ description ) X-ORIGIN 'Netscape Directory Server' )
be9751
 objectClasses: ( 2.16.840.1.113730.3.2.316 NAME 'nsAttributeEncryption' DESC 'Netscape defined objectclass' SUP top MUST ( cn $ nsEncryptionAlgorithm ) X-ORIGIN 'Netscape Directory Server' )
be9751
-objectClasses: ( 2.16.840.1.113730.3.2.335 NAME 'nsSlapiTask' DESC 'Slapi_Task objectclass' SUP top MUST ( cn ) MAY ( ttl $ nsTaskLog $ nsTaskStatus $ nsTaskExitCode $ nsTaskCurrentItem $ nsTaskTotalItems $ nsTaskCreated ) X-ORIGIN '389 Directory Server' )
be9751
+objectClasses: ( 2.16.840.1.113730.3.2.335 NAME 'nsSlapiTask' DESC 'Slapi_Task objectclass' SUP top MUST ( cn ) MAY ( ttl $ nsTaskLog $ nsTaskStatus $ nsTaskExitCode $ nsTaskCurrentItem $ nsTaskTotalItems $ nsTaskCreated $ nsTaskWarning ) X-ORIGIN '389 Directory Server' )
be9751
 
be9751
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c
be9751
index 310893884..5c7d9c8f7 100644
be9751
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c
be9751
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import_threads.c
be9751
@@ -747,6 +747,11 @@ import_producer(void *param)
be9751
         }
be9751
     }
be9751
 
be9751
+    /* capture skipped entry warnings for this task */
be9751
+    if((job) && (job->skipped)) {
be9751
+        slapi_task_set_warning(job->task, WARN_SKIPPED_IMPORT_ENTRY);
be9751
+    }
be9751
+
be9751
     slapi_value_free(&(job->usn_value));
be9751
     import_free_ldif(&c);
be9751
     info->state = FINISHED;
be9751
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
be9751
index 53c9161d1..be4d38739 100644
be9751
--- a/ldap/servers/slapd/slap.h
be9751
+++ b/ldap/servers/slapd/slap.h
be9751
@@ -1753,6 +1753,7 @@ typedef struct slapi_task
be9751
     int task_progress;         /* number between 0 and task_work */
be9751
     int task_work;             /* "units" of work to be done */
be9751
     int task_flags;            /* (see above) */
be9751
+    task_warning task_warn;    /* task warning */
be9751
     char *task_status;         /* transient status info */
be9751
     char *task_log;            /* appended warnings, etc */
be9751
     char task_date[SLAPI_TIMESTAMP_BUFSIZE]; /* Date/time when task was created */
be9751
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
be9751
index 96313ef2c..ddb11bc7c 100644
be9751
--- a/ldap/servers/slapd/slapi-plugin.h
be9751
+++ b/ldap/servers/slapd/slapi-plugin.h
be9751
@@ -6638,6 +6638,15 @@ int slapi_config_remove_callback(int operation, int flags, const char *base, int
be9751
 /* task flags (set by the task-control code) */
be9751
 #define SLAPI_TASK_DESTROYING 0x01 /* queued event for destruction */
be9751
 
be9751
+/* task warnings */
be9751
+typedef enum task_warning_t{
be9751
+    WARN_UPGARDE_DN_FORMAT_ALL    = (1 << 0),
be9751
+    WARN_UPGRADE_DN_FORMAT        = (1 << 1),
be9751
+    WARN_UPGRADE_DN_FORMAT_SPACE  = (1 << 2),
be9751
+    WARN_SKIPPED_IMPORT_ENTRY     = (1 << 3)
be9751
+} task_warning;
be9751
+
be9751
+
be9751
 int slapi_task_register_handler(const char *name, dseCallbackFn func);
be9751
 int slapi_plugin_task_register_handler(const char *name, dseCallbackFn func, Slapi_PBlock *plugin_pb);
be9751
 int slapi_plugin_task_unregister_handler(const char *name, dseCallbackFn func);
be9751
@@ -6654,6 +6663,8 @@ int slapi_task_get_refcount(Slapi_Task *task);
be9751
 void slapi_task_set_destructor_fn(Slapi_Task *task, TaskCallbackFn func);
be9751
 void slapi_task_set_cancel_fn(Slapi_Task *task, TaskCallbackFn func);
be9751
 void slapi_task_status_changed(Slapi_Task *task);
be9751
+void slapi_task_set_warning(Slapi_Task *task, task_warning warn);
be9751
+int slapi_task_get_warning(Slapi_Task *task);
be9751
 void slapi_task_log_status(Slapi_Task *task, char *format, ...)
be9751
 #ifdef __GNUC__
be9751
     __attribute__((format(printf, 2, 3)));
be9751
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
be9751
index d5abe8ac1..b956ebe63 100644
be9751
--- a/ldap/servers/slapd/slapi-private.h
be9751
+++ b/ldap/servers/slapd/slapi-private.h
be9751
@@ -1465,14 +1465,6 @@ void slapi_pblock_set_operation_notes(Slapi_PBlock *pb, uint32_t opnotes);
be9751
 void slapi_pblock_set_flag_operation_notes(Slapi_PBlock *pb, uint32_t opflag);
be9751
 void slapi_pblock_set_result_text_if_empty(Slapi_PBlock *pb, char *text);
be9751
 
be9751
-/* task warnings */
be9751
-typedef enum task_warning_t{
be9751
-    WARN_UPGARDE_DN_FORMAT_ALL    = (1 << 0),
be9751
-    WARN_UPGRADE_DN_FORMAT        = (1 << 1),
be9751
-    WARN_UPGRADE_DN_FORMAT_SPACE  = (1 << 2),
be9751
-    WARN_SKIPPED_IMPORT_ENTRY     = (1 << 3)
be9751
-} task_warning;
be9751
-
be9751
 int32_t slapi_pblock_get_task_warning(Slapi_PBlock *pb);
be9751
 void slapi_pblock_set_task_warning(Slapi_PBlock *pb, task_warning warn);
be9751
 
be9751
diff --git a/ldap/servers/slapd/task.c b/ldap/servers/slapd/task.c
be9751
index 936c64920..806077a16 100644
be9751
--- a/ldap/servers/slapd/task.c
be9751
+++ b/ldap/servers/slapd/task.c
be9751
@@ -46,6 +46,7 @@ static uint64_t shutting_down = 0;
be9751
 #define TASK_PROGRESS_NAME "nsTaskCurrentItem"
be9751
 #define TASK_WORK_NAME "nsTaskTotalItems"
be9751
 #define TASK_DATE_NAME "nsTaskCreated"
be9751
+#define TASK_WARNING_NAME "nsTaskWarning"
be9751
 
be9751
 #define DEFAULT_TTL "3600"                        /* seconds */
be9751
 #define TASK_SYSCONFIG_FILE_ATTR "sysconfigfile" /* sysconfig reload task file attr */
be9751
@@ -332,7 +333,7 @@ slapi_task_status_changed(Slapi_Task *task)
be9751
     LDAPMod modlist[20];
be9751
     LDAPMod *mod[20];
be9751
     int cur = 0, i;
be9751
-    char s1[20], s2[20], s3[20];
be9751
+    char s1[20], s2[20], s3[20], s4[20];
be9751
 
be9751
     if (shutting_down) {
be9751
         /* don't care about task status updates anymore */
be9751
@@ -346,9 +347,11 @@ slapi_task_status_changed(Slapi_Task *task)
be9751
     sprintf(s1, "%d", task->task_exitcode);
be9751
     sprintf(s2, "%d", task->task_progress);
be9751
     sprintf(s3, "%d", task->task_work);
be9751
+    sprintf(s4, "%d", task->task_warn);
be9751
     NEXTMOD(TASK_PROGRESS_NAME, s2);
be9751
     NEXTMOD(TASK_WORK_NAME, s3);
be9751
     NEXTMOD(TASK_DATE_NAME, task->task_date);
be9751
+    NEXTMOD(TASK_WARNING_NAME, s4);
be9751
     /* only add the exit code when the job is done */
be9751
     if ((task->task_state == SLAPI_TASK_FINISHED) ||
be9751
         (task->task_state == SLAPI_TASK_CANCELLED)) {
be9751
@@ -452,6 +455,30 @@ slapi_task_get_refcount(Slapi_Task *task)
be9751
     return 0; /* return value not currently used */
be9751
 }
be9751
 
be9751
+/*
be9751
+ * Return task warning
be9751
+ */
be9751
+int
be9751
+slapi_task_get_warning(Slapi_Task *task)
be9751
+{
be9751
+    if (task) {
be9751
+        return task->task_warn;
be9751
+    }
be9751
+
be9751
+    return 0; /* return value not currently used */
be9751
+}
be9751
+
be9751
+/*
be9751
+ * Set task warning
be9751
+ */
be9751
+void
be9751
+slapi_task_set_warning(Slapi_Task *task, task_warning warn)
be9751
+{
be9751
+    if (task) {
be9751
+        return task->task_warn |= warn;
be9751
+    }
be9751
+}
be9751
+
be9751
 int
be9751
 slapi_plugin_task_unregister_handler(const char *name, dseCallbackFn func)
be9751
 {
be9751
diff --git a/src/lib389/lib389/cli_conf/backend.py b/src/lib389/lib389/cli_conf/backend.py
be9751
index d7a6e670c..6bfbcb036 100644
be9751
--- a/src/lib389/lib389/cli_conf/backend.py
be9751
+++ b/src/lib389/lib389/cli_conf/backend.py
be9751
@@ -243,9 +243,13 @@ def backend_import(inst, basedn, log, args):
be9751
                           exclude_suffixes=args.exclude_suffixes)
be9751
     task.wait(timeout=None)
be9751
     result = task.get_exit_code()
be9751
+    warning = task.get_task_warn()
be9751
 
be9751
     if task.is_complete() and result == 0:
be9751
-        log.info("The import task has finished successfully")
be9751
+        if warning is None or (warning == 0):
be9751
+            log.info("The import task has finished successfully")
be9751
+        else:
be9751
+            log.info("The import task has finished successfully, with warning code {}, check the logs for more detail".format(warning))
be9751
     else:
be9751
         raise ValueError("Import task failed\n-------------------------\n{}".format(ensure_str(task.get_task_log())))
be9751
 
be9751
diff --git a/src/lib389/lib389/tasks.py b/src/lib389/lib389/tasks.py
be9751
index dc7bb9206..bf20d1e61 100644
be9751
--- a/src/lib389/lib389/tasks.py
be9751
+++ b/src/lib389/lib389/tasks.py
be9751
@@ -38,6 +38,7 @@ class Task(DSLdapObject):
be9751
         self._protected = False
be9751
         self._exit_code = None
be9751
         self._task_log = ""
be9751
+        self._task_warn = None
be9751
 
be9751
     def status(self):
be9751
         """Return the decoded status of the task
be9751
@@ -49,6 +50,7 @@ class Task(DSLdapObject):
be9751
 
be9751
         self._exit_code = self.get_attr_val_utf8("nsTaskExitCode")
be9751
         self._task_log = self.get_attr_val_utf8("nsTaskLog")
be9751
+        self._task_warn = self.get_attr_val_utf8("nsTaskWarning")
be9751
         if not self.exists():
be9751
             self._log.debug("complete: task has self cleaned ...")
be9751
             # The task cleaned it self up.
be9751
@@ -77,6 +79,15 @@ class Task(DSLdapObject):
be9751
                 return None
be9751
         return None
be9751
 
be9751
+    def get_task_warn(self):
be9751
+        """Return task's warning code if task is complete, else None."""
be9751
+        if self.is_complete():
be9751
+            try:
be9751
+                return int(self._task_warn)
be9751
+            except TypeError:
be9751
+                return None
be9751
+        return None
be9751
+
be9751
     def wait(self, timeout=120):
be9751
         """Wait until task is complete."""
be9751
 
be9751
@@ -390,14 +401,17 @@ class Tasks(object):
be9751
         running, true if done - if true, second is the exit code - if dowait
be9751
         is True, this function will block until the task is complete'''
be9751
         attrlist = ['nsTaskLog', 'nsTaskStatus', 'nsTaskExitCode',
be9751
-                    'nsTaskCurrentItem', 'nsTaskTotalItems']
be9751
+                    'nsTaskCurrentItem', 'nsTaskTotalItems', 'nsTaskWarning']
be9751
         done = False
be9751
         exitCode = 0
be9751
+        warningCode = 0
be9751
         dn = entry.dn
be9751
         while not done:
be9751
             entry = self.conn.getEntry(dn, attrlist=attrlist)
be9751
             self.log.debug("task entry %r", entry)
be9751
 
be9751
+            if entry.nsTaskWarning:
be9751
+                warningCode = int(entry.nsTaskWarning)
be9751
             if entry.nsTaskExitCode:
be9751
                 exitCode = int(entry.nsTaskExitCode)
be9751
                 done = True
be9751
@@ -405,7 +419,7 @@ class Tasks(object):
be9751
                 time.sleep(1)
be9751
             else:
be9751
                 break
be9751
-        return (done, exitCode)
be9751
+        return (done, exitCode, warningCode)
be9751
 
be9751
     def importLDIF(self, suffix=None, benamebase=None, input_file=None,
be9751
                    args=None):
be9751
@@ -461,8 +475,9 @@ class Tasks(object):
be9751
         self.conn.add_s(entry)
be9751
 
be9751
         exitCode = 0
be9751
+        warningCode = 0
be9751
         if args and args.get(TASK_WAIT, False):
be9751
-            (done, exitCode) = self.conn.tasks.checkTask(entry, True)
be9751
+            (done, exitCode, warningCode) = self.conn.tasks.checkTask(entry, True)
be9751
 
be9751
         if exitCode:
be9751
             self.log.error("Error: import task %s for file %s exited with %d",
be9751
@@ -470,6 +485,8 @@ class Tasks(object):
be9751
         else:
be9751
             self.log.info("Import task %s for file %s completed successfully",
be9751
                           cn, input_file)
be9751
+            if warningCode:
be9751
+                self.log.info("with warning code %d", warningCode)
be9751
         self.dn = dn
be9751
         self.entry = entry
be9751
         return exitCode
be9751
-- 
be9751
2.26.2
be9751