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