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

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