Blame SOURCES/0020-Issue-4418-ldif2db-offline.-Warn-the-user-of-skipped.patch

be9751
From d7b49259ff2f9e0295bbfeaf128369ed33421974 Mon Sep 17 00:00:00 2001
be9751
From: James Chapman <jachapma@redhat.com>
be9751
Date: Mon, 30 Nov 2020 15:28:05 +0000
be9751
Subject: [PATCH 1/6] Issue 4418 - ldif2db - offline. Warn the user of skipped
be9751
 entries
be9751
be9751
Bug Description: During an ldif2db import entries that do not
be9751
conform to various constraints will be skipped and not imported.
be9751
On completition of an import with skipped entries, the server
be9751
returns a success exit code and logs the skipped entry detail to
be9751
the error logs. The success exit code could lead the user to
be9751
believe that all entries were successfully imported.
be9751
be9751
Fix Description: If a skipped entry occurs during import, the
be9751
import will continue and a warning will be returned to the user.
be9751
be9751
CLI tools for offline import updated to handle warning code.
be9751
be9751
Test added to generate an incorrect ldif entry and perform an
be9751
import.
be9751
be9751
Fixes: #4418
be9751
be9751
Reviewed by: Firstyear, droideck  (Thanks)
be9751
be9751
(cherry picked from commit a98fe54292e9b183a2163efbc7bdfe208d4abfb0)
be9751
---
be9751
 .../tests/suites/import/import_test.py        | 54 ++++++++++++++++++-
be9751
 .../slapd/back-ldbm/db-bdb/bdb_import.c       | 22 ++++++--
be9751
 ldap/servers/slapd/main.c                     |  8 +++
be9751
 ldap/servers/slapd/pblock.c                   | 24 +++++++++
be9751
 ldap/servers/slapd/pblock_v3.h                |  1 +
be9751
 ldap/servers/slapd/slapi-private.h            | 14 +++++
be9751
 src/lib389/lib389/__init__.py                 | 18 +++----
be9751
 src/lib389/lib389/_constants.py               |  7 +++
be9751
 src/lib389/lib389/cli_ctl/dbtasks.py          |  8 ++-
be9751
 9 files changed, 140 insertions(+), 16 deletions(-)
be9751
be9751
diff --git a/dirsrvtests/tests/suites/import/import_test.py b/dirsrvtests/tests/suites/import/import_test.py
be9751
index 3803ecf43..b47db96ed 100644
be9751
--- a/dirsrvtests/tests/suites/import/import_test.py
be9751
+++ b/dirsrvtests/tests/suites/import/import_test.py
be9751
@@ -15,7 +15,7 @@ import pytest
be9751
 import time
be9751
 import glob
be9751
 from lib389.topologies import topology_st as topo
be9751
-from lib389._constants import DEFAULT_SUFFIX
be9751
+from lib389._constants import DEFAULT_SUFFIX, TaskWarning
be9751
 from lib389.dbgen import dbgen_users
be9751
 from lib389.tasks import ImportTask
be9751
 from lib389.index import Indexes
be9751
@@ -139,6 +139,38 @@ def _create_bogus_ldif(topo):
be9751
     return import_ldif1
be9751
 
be9751
 
be9751
+def _create_syntax_err_ldif(topo):
be9751
+    """
be9751
+    Create an incorrect ldif 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
+dn: ou=groups,dc=example,dc=com
be9751
+objectClass: top
be9751
+objectClass: organizationalUnit
be9751
+ou: groups
be9751
+dn: uid=JHunt,ou=groups,dc=example,dc=com
be9751
+objectClass: top
be9751
+objectClass: person
be9751
+objectClass: organizationalPerson
be9751
+objectClass: inetOrgPerson
be9751
+objectclass: inetUser
be9751
+cn: James Hunt
be9751
+sn: Hunt
be9751
+uid: JHunt
be9751
+givenName:
be9751
+"""
be9751
+    with open(f'{ldif_dir}/syntax_err.ldif', 'w') as out:
be9751
+        out.write(f'{line1}')
be9751
+        os.chmod(out.name, 0o777)
be9751
+    out.close()
be9751
+    import_ldif1 = ldif_dir + '/syntax_err.ldif'
be9751
+    return import_ldif1
be9751
+
be9751
+
be9751
 def test_import_with_index(topo, _import_clean):
be9751
     """
be9751
     Add an index, then import via cn=tasks
be9751
@@ -214,6 +246,26 @@ 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
+    """ldif2db should return a warning when a skipped entry has occured.
be9751
+    :id: 85e75670-42c5-4062-9edc-7f117c97a06f
be9751
+    :setup:
be9751
+        1. Standalone Instance
be9751
+        2. Ldif entry that violates syntax check rule (empty givenname)
be9751
+    :steps:
be9751
+        1. Create an ldif file which violates the syntax checking rule
be9751
+        2. Stop the server and import ldif file with ldif2db
be9751
+    :expected results:
be9751
+        1. ldif2db import returns a warning to signify skipped entries
be9751
+    """
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
+    assert ret == TaskWarning.WARN_SKIPPED_IMPORT_ENTRY
be9751
+    topo.standalone.start()
be9751
+
be9751
+
be9751
 def test_issue_a_warning_if_the_cache_size_is_smaller(topo, _import_clean):
be9751
     """Report during startup if nsslapd-cachememsize is too small
be9751
 
be9751
diff --git a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
be9751
index e7da0517f..1e4830e99 100644
be9751
--- a/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
be9751
+++ b/ldap/servers/slapd/back-ldbm/db-bdb/bdb_import.c
be9751
@@ -2563,7 +2563,7 @@ error:
be9751
                 slapi_task_dec_refcount(job->task);
be9751
             }
be9751
             import_all_done(job, ret);
be9751
-            ret = 1;
be9751
+            ret |= WARN_UPGARDE_DN_FORMAT_ALL;
be9751
         } else if (NEED_DN_NORM == ret) {
be9751
             import_log_notice(job, SLAPI_LOG_NOTICE, "bdb_import_main",
be9751
                               "%s complete. %s needs upgradednformat.",
be9751
@@ -2572,7 +2572,7 @@ error:
be9751
                 slapi_task_dec_refcount(job->task);
be9751
             }
be9751
             import_all_done(job, ret);
be9751
-            ret = 2;
be9751
+            ret |= WARN_UPGRADE_DN_FORMAT;
be9751
         } else if (NEED_DN_NORM_SP == ret) {
be9751
             import_log_notice(job, SLAPI_LOG_NOTICE, "bdb_import_main",
be9751
                               "%s complete. %s needs upgradednformat spaces.",
be9751
@@ -2581,7 +2581,7 @@ error:
be9751
                 slapi_task_dec_refcount(job->task);
be9751
             }
be9751
             import_all_done(job, ret);
be9751
-            ret = 3;
be9751
+            ret |= WARN_UPGRADE_DN_FORMAT_SPACE;
be9751
         } else {
be9751
             ret = -1;
be9751
             if (job->task != NULL) {
be9751
@@ -2600,6 +2600,11 @@ error:
be9751
         import_all_done(job, ret);
be9751
     }
be9751
 
be9751
+    /* set task warning if there are no errors */
be9751
+    if((!ret) && (job->skipped)) {
be9751
+        ret |= WARN_SKIPPED_IMPORT_ENTRY;
be9751
+    }
be9751
+
be9751
     /* This instance isn't busy anymore */
be9751
     instance_set_not_busy(job->inst);
be9751
 
be9751
@@ -2637,6 +2642,7 @@ bdb_back_ldif2db(Slapi_PBlock *pb)
be9751
     int total_files, i;
be9751
     int up_flags = 0;
be9751
     PRThread *thread = NULL;
be9751
+    int ret = 0;
be9751
 
be9751
     slapi_pblock_get(pb, SLAPI_BACKEND, &be);
be9751
     if (be == NULL) {
be9751
@@ -2764,7 +2770,15 @@ bdb_back_ldif2db(Slapi_PBlock *pb)
be9751
     }
be9751
 
be9751
     /* old style -- do it all synchronously (THIS IS GOING AWAY SOON) */
be9751
-    return import_main_offline((void *)job);
be9751
+    ret = import_main_offline((void *)job);
be9751
+
be9751
+    /* no error just warning, reset ret */
be9751
+    if(ret &= WARN_SKIPPED_IMPORT_ENTRY) {
be9751
+        slapi_pblock_set_task_warning(pb, WARN_SKIPPED_IMPORT_ENTRY);
be9751
+        ret = 0;
be9751
+    }
be9751
+
be9751
+    return ret;
be9751
 }
be9751
 
be9751
 struct _import_merge_thang
be9751
diff --git a/ldap/servers/slapd/main.c b/ldap/servers/slapd/main.c
be9751
index 694375b22..104f6826c 100644
be9751
--- a/ldap/servers/slapd/main.c
be9751
+++ b/ldap/servers/slapd/main.c
be9751
@@ -2069,6 +2069,14 @@ slapd_exemode_ldif2db(struct main_config *mcfg)
be9751
                       plugin->plg_name);
be9751
         return_value = -1;
be9751
     }
be9751
+
be9751
+    /* check for task warnings */
be9751
+    if(!return_value) {
be9751
+        if((return_value = slapi_pblock_get_task_warning(pb))) {
be9751
+            slapi_log_err(SLAPI_LOG_INFO, "slapd_exemode_ldif2db","returning task warning: %d\n", return_value);
be9751
+        }
be9751
+    }
be9751
+
be9751
     slapi_pblock_destroy(pb);
be9751
     charray_free(instances);
be9751
     charray_free(mcfg->cmd_line_instance_names);
be9751
diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c
be9751
index 454ea9cc3..1ad9d0399 100644
be9751
--- a/ldap/servers/slapd/pblock.c
be9751
+++ b/ldap/servers/slapd/pblock.c
be9751
@@ -28,12 +28,14 @@
be9751
 #define SLAPI_LDIF_DUMP_REPLICA 2003
be9751
 #define SLAPI_PWDPOLICY 2004
be9751
 #define SLAPI_PW_ENTRY 2005
be9751
+#define SLAPI_TASK_WARNING 2006
be9751
 
be9751
 /* Used for checking assertions about pblocks in some cases. */
be9751
 #define SLAPI_HINT 9999
be9751
 
be9751
 static PRLock *pblock_analytics_lock = NULL;
be9751
 
be9751
+
be9751
 static PLHashNumber
be9751
 hash_int_func(const void *key)
be9751
 {
be9751
@@ -4315,6 +4317,28 @@ slapi_pblock_set_ldif_dump_replica(Slapi_PBlock *pb, int32_t dump_replica)
be9751
     pb->pb_task->ldif_dump_replica = dump_replica;
be9751
 }
be9751
 
be9751
+int32_t
be9751
+slapi_pblock_get_task_warning(Slapi_PBlock *pb)
be9751
+{
be9751
+#ifdef PBLOCK_ANALYTICS
be9751
+    pblock_analytics_record(pb, SLAPI_TASK_WARNING);
be9751
+#endif
be9751
+    if (pb->pb_task != NULL) {
be9751
+        return pb->pb_task->task_warning;
be9751
+    }
be9751
+    return 0;
be9751
+}
be9751
+
be9751
+void
be9751
+slapi_pblock_set_task_warning(Slapi_PBlock *pb, task_warning warning)
be9751
+{
be9751
+#ifdef PBLOCK_ANALYTICS
be9751
+    pblock_analytics_record(pb, SLAPI_TASK_WARNING);
be9751
+#endif
be9751
+    _pblock_assert_pb_task(pb);
be9751
+    pb->pb_task->task_warning = warning;
be9751
+}
be9751
+
be9751
 void *
be9751
 slapi_pblock_get_vattr_context(Slapi_PBlock *pb)
be9751
 {
be9751
diff --git a/ldap/servers/slapd/pblock_v3.h b/ldap/servers/slapd/pblock_v3.h
be9751
index 90498c0b0..b35d78565 100644
be9751
--- a/ldap/servers/slapd/pblock_v3.h
be9751
+++ b/ldap/servers/slapd/pblock_v3.h
be9751
@@ -67,6 +67,7 @@ typedef struct _slapi_pblock_task
be9751
     int ldif2db_noattrindexes;
be9751
     int ldif_printkey;
be9751
     int task_flags;
be9751
+    int32_t task_warning;
be9751
     int import_state;
be9751
 
be9751
     int server_running; /* indicate that server is running */
be9751
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
be9751
index c98c1947c..31cb33472 100644
be9751
--- a/ldap/servers/slapd/slapi-private.h
be9751
+++ b/ldap/servers/slapd/slapi-private.h
be9751
@@ -1465,6 +1465,20 @@ 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
+
be9751
+int slapi_exists_or_add_internal(Slapi_DN *dn, const char *filter, const char *entry, const char *modifier_name);
be9751
+
be9751
 #ifdef __cplusplus
be9751
 }
be9751
 #endif
be9751
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
be9751
index 4e6a1905a..5b36a79e1 100644
be9751
--- a/src/lib389/lib389/__init__.py
be9751
+++ b/src/lib389/lib389/__init__.py
be9751
@@ -2683,7 +2683,7 @@ class DirSrv(SimpleLDAPObject, object):
be9751
     # server is stopped)
be9751
     #
be9751
     def ldif2db(self, bename, suffixes, excludeSuffixes, encrypt,
be9751
-                import_file):
be9751
+                import_file, import_cl):
be9751
         """
be9751
         @param bename - The backend name of the database to import
be9751
         @param suffixes - List/tuple of suffixes to import
be9751
@@ -2731,14 +2731,14 @@ class DirSrv(SimpleLDAPObject, object):
be9751
         try:
be9751
             result = subprocess.check_output(cmd, encoding='utf-8')
be9751
         except subprocess.CalledProcessError as e:
be9751
-            self.log.debug("Command: %s failed with the return code %s and the error %s",
be9751
-                           format_cmd_list(cmd), e.returncode, e.output)
be9751
-            return False
be9751
-
be9751
-        self.log.debug("ldif2db output: BEGIN")
be9751
-        for line in result.split("\n"):
be9751
-            self.log.debug(line)
be9751
-        self.log.debug("ldif2db output: END")
be9751
+            if e.returncode == TaskWarning.WARN_SKIPPED_IMPORT_ENTRY:
be9751
+                self.log.debug("Command: %s skipped import entry warning %s",
be9751
+                               format_cmd_list(cmd), e.returncode)
be9751
+                return e.returncode
be9751
+            else:
be9751
+                self.log.debug("Command: %s failed with the return code %s and the error %s",
be9751
+                               format_cmd_list(cmd), e.returncode, e.output)
be9751
+                return False
be9751
 
be9751
         return True
be9751
 
be9751
diff --git a/src/lib389/lib389/_constants.py b/src/lib389/lib389/_constants.py
be9751
index e28c602a3..38ba04565 100644
be9751
--- a/src/lib389/lib389/_constants.py
be9751
+++ b/src/lib389/lib389/_constants.py
be9751
@@ -162,6 +162,13 @@ DB2BAK = 'db2bak'
be9751
 DB2INDEX = 'db2index'
be9751
 DBSCAN = 'dbscan'
be9751
 
be9751
+# Task warnings
be9751
+class TaskWarning(IntEnum):
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
+
be9751
 RDN_REPLICA = "cn=replica"
be9751
 
be9751
 RETROCL_SUFFIX = "cn=changelog"
be9751
diff --git a/src/lib389/lib389/cli_ctl/dbtasks.py b/src/lib389/lib389/cli_ctl/dbtasks.py
be9751
index 590a1ea0e..02830239c 100644
be9751
--- a/src/lib389/lib389/cli_ctl/dbtasks.py
be9751
+++ b/src/lib389/lib389/cli_ctl/dbtasks.py
be9751
@@ -7,6 +7,7 @@
be9751
 # See LICENSE for details.
be9751
 # --- END COPYRIGHT BLOCK ---
be9751
 
be9751
+from lib389._constants import TaskWarning
be9751
 
be9751
 def dbtasks_db2index(inst, log, args):
be9751
     if not inst.db2index(bename=args.backend):
be9751
@@ -44,10 +45,13 @@ def dbtasks_db2ldif(inst, log, args):
be9751
 
be9751
 
be9751
 def dbtasks_ldif2db(inst, log, args):
be9751
-    if not inst.ldif2db(bename=args.backend, encrypt=args.encrypted, import_file=args.ldif,
be9751
-                        suffixes=None, excludeSuffixes=None):
be9751
+    ret = inst.ldif2db(bename=args.backend, encrypt=args.encrypted, import_file=args.ldif,
be9751
+                        suffixes=None, excludeSuffixes=None, import_cl=False)
be9751
+    if not ret:
be9751
         log.fatal("ldif2db failed")
be9751
         return False
be9751
+    elif ret == TaskWarning.WARN_SKIPPED_IMPORT_ENTRY:
be9751
+        log.warn("ldif2db successful with skipped entries")
be9751
     else:
be9751
         log.info("ldif2db successful")
be9751
 
be9751
-- 
be9751
2.26.2
be9751