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