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