256ebe
From 69ac1fd2da7a57f2f0854412863911959bf71fde Mon Sep 17 00:00:00 2001
256ebe
From: Sunny Kumar <sunkumar@redhat.com>
256ebe
Date: Tue, 2 Apr 2019 12:38:09 +0530
256ebe
Subject: [PATCH 161/169] geo-rep: Fix rename with existing destination with
256ebe
 same gfid
256ebe
256ebe
Problem:
256ebe
   Geo-rep fails to sync the rename properly if destination exists.
256ebe
It results in source to be remained on slave causing more number of
256ebe
files on slave. Also heavy rename workload like logrotate caused
256ebe
lot of ESTALE errors
256ebe
256ebe
Cause:
256ebe
   Geo-rep fails to sync rename if destination exists if creation
256ebe
of source file also falls into single batch of changelogs being
256ebe
processed. This is because, after fixing problematic gfids verifying
256ebe
from master, while re-processing original entries, CREATE also was
256ebe
re-processed causing more files on slave and rename to be failed.
256ebe
256ebe
Solution:
256ebe
   Entries need to be removed from retrial list after fixing
256ebe
problematic gfids on slave so that it's not re-created again on slave.
256ebe
   Also treat ESTALE as EEXIST so that the error is properly handled
256ebe
verifying the op on master volume.
256ebe
256ebe
Backport of:
256ebe
 > Patch: https://review.gluster.org/22519
256ebe
 > Change-Id: I50cf289e06b997adddff0552bf2466d9201dd1f9
256ebe
 > BUG: 1694820
256ebe
 > Signed-off-by: Kotresh HR <khiremat@redhat.com>
256ebe
 > Signed-off-by: Sunny Kumar <sunkumar@redhat.com>
256ebe
256ebe
Change-Id: I50cf289e06b997adddff0552bf2466d9201dd1f9
256ebe
fixes: bz#1708121
256ebe
Signed-off-by: Kotresh HR <khiremat@redhat.com>
256ebe
Signed-off-by: Sunny Kumar <sunkumar@redhat.com>
256ebe
Reviewed-on: https://code.engineering.redhat.com/gerrit/172393
256ebe
Tested-by: RHGS Build Bot <nigelb@redhat.com>
256ebe
Reviewed-by: Atin Mukherjee <amukherj@redhat.com>
256ebe
---
256ebe
 geo-replication/syncdaemon/master.py              | 41 +++++++++++++++++++++--
256ebe
 geo-replication/syncdaemon/resource.py            | 10 ++++--
256ebe
 tests/00-geo-rep/georep-basic-dr-rsync-arbiter.t  |  5 +++
256ebe
 tests/00-geo-rep/georep-basic-dr-rsync.t          |  5 +++
256ebe
 tests/00-geo-rep/georep-basic-dr-tarssh-arbiter.t |  5 +++
256ebe
 tests/00-geo-rep/georep-basic-dr-tarssh.t         |  5 +++
256ebe
 tests/geo-rep.rc                                  | 36 ++++++++++++++++++++
256ebe
 7 files changed, 102 insertions(+), 5 deletions(-)
256ebe
256ebe
diff --git a/geo-replication/syncdaemon/master.py b/geo-replication/syncdaemon/master.py
256ebe
index 3da7610..42c86d7 100644
256ebe
--- a/geo-replication/syncdaemon/master.py
256ebe
+++ b/geo-replication/syncdaemon/master.py
256ebe
@@ -65,6 +65,9 @@ def _volinfo_hook_relax_foreign(self):
256ebe
 def edct(op, **ed):
256ebe
     dct = {}
256ebe
     dct['op'] = op
256ebe
+    # This is used in automatic gfid conflict resolution.
256ebe
+    # When marked True, it's skipped during re-processing.
256ebe
+    dct['skip_entry'] = False
256ebe
     for k in ed:
256ebe
         if k == 'stat':
256ebe
             st = ed[k]
256ebe
@@ -792,6 +795,7 @@ class GMasterChangelogMixin(GMasterCommon):
256ebe
         pfx = gauxpfx()
256ebe
         fix_entry_ops = []
256ebe
         failures1 = []
256ebe
+        remove_gfids = set()
256ebe
         for failure in failures:
256ebe
             if failure[2]['name_mismatch']:
256ebe
                 pbname = failure[2]['slave_entry']
256ebe
@@ -822,6 +826,18 @@ class GMasterChangelogMixin(GMasterCommon):
256ebe
                             edct('UNLINK',
256ebe
                                  gfid=failure[2]['slave_gfid'],
256ebe
                                  entry=pbname))
256ebe
+                    remove_gfids.add(slave_gfid)
256ebe
+                    if op in ['RENAME']:
256ebe
+                        # If renamed gfid doesn't exists on master, remove
256ebe
+                        # rename entry and unlink src on slave
256ebe
+                        st = lstat(os.path.join(pfx, failure[0]['gfid']))
256ebe
+                        if isinstance(st, int) and st == ENOENT:
256ebe
+                            logging.debug("Unlink source %s" % repr(failure))
256ebe
+                            remove_gfids.add(failure[0]['gfid'])
256ebe
+                            fix_entry_ops.append(
256ebe
+                                edct('UNLINK',
256ebe
+                                     gfid=failure[0]['gfid'],
256ebe
+                                     entry=failure[0]['entry']))
256ebe
                 # Takes care of scenarios of hardlinks/renames on master
256ebe
                 elif not isinstance(st, int):
256ebe
                     if matching_disk_gfid(slave_gfid, pbname):
256ebe
@@ -831,7 +847,12 @@ class GMasterChangelogMixin(GMasterCommon):
256ebe
                                         ' Safe to ignore, take out entry',
256ebe
                                         retry_count=retry_count,
256ebe
                                         entry=repr(failure)))
256ebe
-                        entries.remove(failure[0])
256ebe
+                        remove_gfids.add(failure[0]['gfid'])
256ebe
+                        if op == 'RENAME':
256ebe
+                            fix_entry_ops.append(
256ebe
+                                edct('UNLINK',
256ebe
+                                     gfid=failure[0]['gfid'],
256ebe
+                                     entry=failure[0]['entry']))
256ebe
                     # The file exists on master but with different name.
256ebe
                     # Probably renamed and got missed during xsync crawl.
256ebe
                     elif failure[2]['slave_isdir']:
256ebe
@@ -856,7 +877,10 @@ class GMasterChangelogMixin(GMasterCommon):
256ebe
                                             'take out entry',
256ebe
                                             retry_count=retry_count,
256ebe
                                             entry=repr(failure)))
256ebe
-                            entries.remove(failure[0])
256ebe
+                            try:
256ebe
+                                entries.remove(failure[0])
256ebe
+                            except ValueError:
256ebe
+                                pass
256ebe
                         else:
256ebe
                             rename_dict = edct('RENAME', gfid=slave_gfid,
256ebe
                                                entry=src_entry,
256ebe
@@ -896,7 +920,10 @@ class GMasterChangelogMixin(GMasterCommon):
256ebe
                                     'ignore, take out entry',
256ebe
                                     retry_count=retry_count,
256ebe
                                     entry=repr(failure)))
256ebe
-                    entries.remove(failure[0])
256ebe
+                    try:
256ebe
+                        entries.remove(failure[0])
256ebe
+                    except ValueError:
256ebe
+                        pass
256ebe
                 else:
256ebe
                     logging.info(lf('Fixing ENOENT error in slave. Create '
256ebe
                                     'parent directory on slave.',
256ebe
@@ -913,6 +940,14 @@ class GMasterChangelogMixin(GMasterCommon):
256ebe
                         edct('MKDIR', gfid=pargfid, entry=dir_entry,
256ebe
                              mode=st.st_mode, uid=st.st_uid, gid=st.st_gid))
256ebe
 
256ebe
+        logging.debug("remove_gfids: %s" % repr(remove_gfids))
256ebe
+        if remove_gfids:
256ebe
+            for e in entries:
256ebe
+                if e['op'] in ['MKDIR', 'MKNOD', 'CREATE', 'RENAME'] \
256ebe
+                   and e['gfid'] in remove_gfids:
256ebe
+                    logging.debug("Removed entry op from retrial list: entry: %s" % repr(e))
256ebe
+                    e['skip_entry'] = True
256ebe
+
256ebe
         if fix_entry_ops:
256ebe
             # Process deletions of entries whose gfids are mismatched
256ebe
             failures1 = self.slave.server.entry_ops(fix_entry_ops)
256ebe
diff --git a/geo-replication/syncdaemon/resource.py b/geo-replication/syncdaemon/resource.py
256ebe
index c290d86..f54ccd9 100644
256ebe
--- a/geo-replication/syncdaemon/resource.py
256ebe
+++ b/geo-replication/syncdaemon/resource.py
256ebe
@@ -426,7 +426,7 @@ class Server(object):
256ebe
                 e['stat']['uid'] = uid
256ebe
                 e['stat']['gid'] = gid
256ebe
 
256ebe
-            if cmd_ret == EEXIST:
256ebe
+            if cmd_ret in [EEXIST, ESTALE]:
256ebe
                 if dst:
256ebe
                     en = e['entry1']
256ebe
                 else:
256ebe
@@ -510,6 +510,12 @@ class Server(object):
256ebe
             entry = e['entry']
256ebe
             uid = 0
256ebe
             gid = 0
256ebe
+
256ebe
+            # Skip entry processing if it's marked true during gfid
256ebe
+            # conflict resolution
256ebe
+            if e['skip_entry']:
256ebe
+                continue
256ebe
+
256ebe
             if e.get("stat", {}):
256ebe
                 # Copy UID/GID value and then reset to zero. Copied UID/GID
256ebe
                 # will be used to run chown once entry is created.
256ebe
@@ -688,7 +694,7 @@ class Server(object):
256ebe
             if blob:
256ebe
                 cmd_ret = errno_wrap(Xattr.lsetxattr,
256ebe
                                      [pg, 'glusterfs.gfid.newfile', blob],
256ebe
-                                     [EEXIST, ENOENT],
256ebe
+                                     [EEXIST, ENOENT, ESTALE],
256ebe
                                      [ESTALE, EINVAL, EBUSY])
256ebe
                 collect_failure(e, cmd_ret, uid, gid)
256ebe
 
256ebe
diff --git a/tests/00-geo-rep/georep-basic-dr-rsync-arbiter.t b/tests/00-geo-rep/georep-basic-dr-rsync-arbiter.t
256ebe
index 67ac167..1a55ed2 100644
256ebe
--- a/tests/00-geo-rep/georep-basic-dr-rsync-arbiter.t
256ebe
+++ b/tests/00-geo-rep/georep-basic-dr-rsync-arbiter.t
256ebe
@@ -203,6 +203,11 @@ EXPECT_WITHIN $GEO_REP_TIMEOUT 0 verify_rsnapshot_data ${slave_mnt}
256ebe
 TEST gluster volume geo-rep $master $slave config rsync-options "--whole-file"
256ebe
 TEST "echo sampledata > $master_mnt/rsync_option_test_file"
256ebe
 
256ebe
+#rename with existing destination case BUG:1694820
256ebe
+TEST create_rename_with_existing_destination ${master_mnt}
256ebe
+#verify rename with existing destination case BUG:1694820
256ebe
+EXPECT_WITHIN $GEO_REP_TIMEOUT 0 verify_rename_with_existing_destination ${slave_mnt}
256ebe
+
256ebe
 #Verify arequal for whole volume
256ebe
 EXPECT_WITHIN $GEO_REP_TIMEOUT 0 arequal_checksum ${master_mnt} ${slave_mnt}
256ebe
 
256ebe
diff --git a/tests/00-geo-rep/georep-basic-dr-rsync.t b/tests/00-geo-rep/georep-basic-dr-rsync.t
256ebe
index 8b64370..d0c0fc9 100644
256ebe
--- a/tests/00-geo-rep/georep-basic-dr-rsync.t
256ebe
+++ b/tests/00-geo-rep/georep-basic-dr-rsync.t
256ebe
@@ -204,6 +204,11 @@ EXPECT_WITHIN $GEO_REP_TIMEOUT 0 verify_rsnapshot_data ${slave_mnt}
256ebe
 TEST gluster volume geo-rep $master $slave config rsync-options "--whole-file"
256ebe
 TEST "echo sampledata > $master_mnt/rsync_option_test_file"
256ebe
 
256ebe
+#rename with existing destination case BUG:1694820
256ebe
+TEST create_rename_with_existing_destination ${master_mnt}
256ebe
+#verify rename with existing destination case BUG:1694820
256ebe
+EXPECT_WITHIN $GEO_REP_TIMEOUT 0 verify_rename_with_existing_destination ${slave_mnt}
256ebe
+
256ebe
 #Verify arequal for whole volume
256ebe
 EXPECT_WITHIN $GEO_REP_TIMEOUT 0 arequal_checksum ${master_mnt} ${slave_mnt}
256ebe
 
256ebe
diff --git a/tests/00-geo-rep/georep-basic-dr-tarssh-arbiter.t b/tests/00-geo-rep/georep-basic-dr-tarssh-arbiter.t
256ebe
index 1726d0b..cb530ad 100644
256ebe
--- a/tests/00-geo-rep/georep-basic-dr-tarssh-arbiter.t
256ebe
+++ b/tests/00-geo-rep/georep-basic-dr-tarssh-arbiter.t
256ebe
@@ -202,6 +202,11 @@ EXPECT_WITHIN $GEO_REP_TIMEOUT 0 verify_symlink_rename_mkdir_data ${slave_mnt}/s
256ebe
 #rsnapshot usecase
256ebe
 EXPECT_WITHIN $GEO_REP_TIMEOUT 0 verify_rsnapshot_data ${slave_mnt}
256ebe
 
256ebe
+#rename with existing destination case BUG:1694820
256ebe
+TEST create_rename_with_existing_destination ${master_mnt}
256ebe
+#verify rename with existing destination case BUG:1694820
256ebe
+EXPECT_WITHIN $GEO_REP_TIMEOUT 0 verify_rename_with_existing_destination ${slave_mnt}
256ebe
+
256ebe
 #Verify arequal for whole volume
256ebe
 EXPECT_WITHIN $GEO_REP_TIMEOUT 0 arequal_checksum ${master_mnt} ${slave_mnt}
256ebe
 
256ebe
diff --git a/tests/00-geo-rep/georep-basic-dr-tarssh.t b/tests/00-geo-rep/georep-basic-dr-tarssh.t
256ebe
index c5d16ac..9e2f613 100644
256ebe
--- a/tests/00-geo-rep/georep-basic-dr-tarssh.t
256ebe
+++ b/tests/00-geo-rep/georep-basic-dr-tarssh.t
256ebe
@@ -202,6 +202,11 @@ EXPECT_WITHIN $GEO_REP_TIMEOUT 0 verify_hardlink_rename_data ${slave_mnt}
256ebe
 #rsnapshot usecase
256ebe
 EXPECT_WITHIN $GEO_REP_TIMEOUT 0 verify_rsnapshot_data ${slave_mnt}
256ebe
 
256ebe
+#rename with existing destination case BUG:1694820
256ebe
+TEST create_rename_with_existing_destination ${master_mnt}
256ebe
+#verify rename with existing destination case BUG:1694820
256ebe
+EXPECT_WITHIN $GEO_REP_TIMEOUT 0 verify_rename_with_existing_destination ${slave_mnt}
256ebe
+
256ebe
 #Verify arequal for whole volume
256ebe
 EXPECT_WITHIN $GEO_REP_TIMEOUT 0 arequal_checksum ${master_mnt} ${slave_mnt}
256ebe
 
256ebe
diff --git a/tests/geo-rep.rc b/tests/geo-rep.rc
256ebe
index d723129..e357ba8 100644
256ebe
--- a/tests/geo-rep.rc
256ebe
+++ b/tests/geo-rep.rc
256ebe
@@ -403,3 +403,39 @@ function check_slave_read_only()
256ebe
     gluster volume info $1 | grep 'features.read-only: on'
256ebe
     echo $?
256ebe
 }
256ebe
+
256ebe
+function create_rename_with_existing_destination()
256ebe
+{
256ebe
+    dir=$1/rename_with_existing_destination
256ebe
+    mkdir $dir
256ebe
+    for i in {1..5}
256ebe
+    do
256ebe
+        echo "Data_set$i" > $dir/data_set$i
256ebe
+        mv $dir/data_set$i $dir/data_set -f
256ebe
+    done
256ebe
+}
256ebe
+
256ebe
+function verify_rename_with_existing_destination()
256ebe
+{
256ebe
+    dir=$1/rename_with_existing_destination
256ebe
+
256ebe
+    if [ ! -d $dir ]; then
256ebe
+        echo 1
256ebe
+    elif [ ! -f $dir/data_set ]; then
256ebe
+        echo 2
256ebe
+    elif [ -f $dir/data_set1 ]; then
256ebe
+        echo 3
256ebe
+    elif [ -f $dir/data_set2 ]; then
256ebe
+        echo 4
256ebe
+    elif [ -f $dir/data_set3 ]; then
256ebe
+        echo 5
256ebe
+    elif [ -f $dir/data_set4 ]; then
256ebe
+        echo 6
256ebe
+    elif [ -f $dir/data_set5 ]; then
256ebe
+        echo 7
256ebe
+    elif test "XData_set5" != "X$(cat $dir/data_set)"; then
256ebe
+        echo 8
256ebe
+    else
256ebe
+        echo 0
256ebe
+    fi
256ebe
+}
256ebe
-- 
256ebe
1.8.3.1
256ebe