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

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