Skip to content

Commit 2eef2c1

Browse files
committed
Fix rclone mover crash on PVCs with symlinks
Exclude symlinks from the getfacl pipeline in active.sh. On UBI9's acl-2.4.0, getfacl -P returns ELOOP on symlinks, killing the script under pipefail before rclone sync runs. Symlinks have no meaningful ACLs so skipping them loses no data. Adds e2e test with dangling and relative symlinks on the source PVC. Signed-off-by: Ismail Ibrahim Quwarah <iquwarah@redhat.com>
1 parent 44ebe86 commit 2eef2c1

3 files changed

Lines changed: 202 additions & 1 deletion

File tree

mover-rclone/active.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ fi
7777
START_TIME=$SECONDS
7878
case "${DIRECTION}" in
7979
source)
80-
find "${MOUNT_PATH}" -path "${MOUNT_PATH}/lost+found" -prune -o -print | getfacl -P - > /tmp/permissions.facl
80+
find "${MOUNT_PATH}" -path "${MOUNT_PATH}/lost+found" -prune -o -not -type l -print | getfacl -P - > /tmp/permissions.facl
8181
rclone sync "${RCLONE_FLAGS_SYNC[@]}" --exclude "lost+found/**" "${MOUNT_PATH}" "${RCLONE_CONFIG_SECTION}:${RCLONE_DEST_PATH}" --log-level DEBUG
8282
rclone copy "${RCLONE_FLAGS_COPY[@]}" --include permissions.facl /tmp "${RCLONE_CONFIG_SECTION}:${RCLONE_DEST_PATH}" --log-level DEBUG
8383
;;

test-e2e/roles/compare_pvc_data/templates/job.yml.j2

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ spec:
3838

3939
echo "... all file contents matched"
4040

41+
{% if not skip_symlink_check | default(false) %}
4142
echo "Validating symlinks: PVC1 -> PVC2"
4243
find /mnt -type l | { grep -v lost+found || true; } | sort | while IFS= read -r f; do
4344
f2=$(echo "$f" | sed 's|^/mnt/|/mnt2/|')
@@ -64,13 +65,24 @@ spec:
6465
done
6566

6667
echo "... all symlinks matched"
68+
{% else %}
69+
echo "Skipping symlink validation (skip_symlink_check=true)"
70+
{% endif %}
6771

6872
echo "File attributes:"
73+
{% if skip_symlink_check | default(false) %}
74+
find /mnt -not -type l -exec stat -c "{{ properties_to_verify }}" {} \; | \
75+
{% else %}
6976
find /mnt -exec stat -c "{{ properties_to_verify }}" {} \; | \
77+
{% endif %}
7078
grep -v lost+found | \
7179
sort | tee /tmp/attributes-mnt
7280
sed -ri 's|/mnt|/mnt2|' /tmp/attributes-mnt
81+
{% if skip_symlink_check | default(false) %}
82+
find /mnt2 -not -type l -exec stat -c "{{ properties_to_verify }}" {} \; | \
83+
{% else %}
7384
find /mnt2 -exec stat -c "{{ properties_to_verify }}" {} \; | \
85+
{% endif %}
7486
grep -v lost+found | \
7587
sort > /tmp/attributes-mnt2
7688

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
---
2+
# Tests that the rclone mover handles symlinks on the source PVC without
3+
# crashing. Covers dangling absolute symlinks (e.g. mysql.sock) that cause
4+
# getfacl to return ELOOP under UBI9's acl-2.4.0.
5+
- hosts: localhost
6+
tags:
7+
- e2e
8+
- rclone
9+
- symlinks
10+
- privileged
11+
- volumepopulator
12+
vars:
13+
rclone_secret_name: rclone-secret
14+
tasks:
15+
- include_role:
16+
name: create_namespace
17+
18+
- include_role:
19+
name: gather_cluster_info
20+
21+
- include_role:
22+
name: enable_privileged_mover
23+
24+
- include_role:
25+
name: create_rclone_secret
26+
vars:
27+
minio_namespace: minio
28+
29+
- name: Create source PVC
30+
kubernetes.core.k8s:
31+
state: present
32+
definition:
33+
kind: PersistentVolumeClaim
34+
apiVersion: v1
35+
metadata:
36+
name: data-source
37+
namespace: "{{ namespace }}"
38+
spec:
39+
accessModes:
40+
- ReadWriteOnce
41+
resources:
42+
requests:
43+
storage: 1Gi
44+
45+
- name: Write data and symlinks into the source PVC
46+
include_role:
47+
name: write_to_pvc
48+
vars:
49+
data: 'data'
50+
path: '/datafile'
51+
pvc_name: 'data-source'
52+
symlinks:
53+
# Dangling absolute symlink — matches what MySQL creates on its
54+
# data volume (mysql.sock -> /var/run/mysqld/mysqld.sock).
55+
# On UBI9 acl-2.4.0, getfacl -P returns ELOOP on these.
56+
- path: '/mysql.sock'
57+
target: '/var/run/mysqld/mysqld.sock'
58+
# Valid relative symlink pointing to the data file
59+
- path: '/link-relative'
60+
target: 'datafile'
61+
62+
- name: Sync data from source volume
63+
kubernetes.core.k8s:
64+
state: present
65+
definition:
66+
apiVersion: volsync.backube/v1alpha1
67+
kind: ReplicationSource
68+
metadata:
69+
name: source
70+
namespace: "{{ namespace }}"
71+
spec:
72+
sourcePVC: data-source
73+
trigger:
74+
manual: once
75+
rclone:
76+
rcloneConfigSection: rclone-data-mover
77+
rcloneDestPath: "rclone-{{ namespace }}"
78+
rcloneConfig: "{{ rclone_secret_name }}"
79+
copyMethod: Snapshot
80+
81+
- name: Wait for sync to MinIO to complete
82+
kubernetes.core.k8s_info:
83+
api_version: volsync.backube/v1alpha1
84+
kind: ReplicationSource
85+
name: source
86+
namespace: "{{ namespace }}"
87+
register: res
88+
until: >
89+
res.resources | length > 0 and
90+
res.resources[0].status.lastManualSync is defined and
91+
res.resources[0].status.lastManualSync=="once" and
92+
res.resources[0].status.latestMoverStatus is defined and
93+
res.resources[0].status.latestMoverStatus.result == "Successful" and
94+
res.resources[0].status.latestMoverStatus.logs is search("Transferred:.*") and
95+
res.resources[0].status.latestMoverStatus.logs is search("Elapsed time:.*") and
96+
res.resources[0].status.latestMoverStatus.logs is search("Rclone completed in.*")
97+
delay: 1
98+
retries: 900
99+
100+
- name: Sync data to destination
101+
kubernetes.core.k8s:
102+
state: present
103+
definition:
104+
apiVersion: volsync.backube/v1alpha1
105+
kind: ReplicationDestination
106+
metadata:
107+
name: destination
108+
namespace: "{{ namespace }}"
109+
spec:
110+
trigger:
111+
manual: once
112+
rclone:
113+
rcloneConfigSection: rclone-data-mover
114+
rcloneDestPath: "rclone-{{ namespace }}"
115+
rcloneConfig: "{{ rclone_secret_name }}"
116+
copyMethod: Snapshot
117+
accessModes: [ReadWriteOnce]
118+
capacity: 1Gi
119+
120+
- name: Wait for sync from MinIO to complete
121+
kubernetes.core.k8s_info:
122+
api_version: volsync.backube/v1alpha1
123+
kind: ReplicationDestination
124+
name: destination
125+
namespace: "{{ namespace }}"
126+
register: res
127+
until: >
128+
res.resources | length > 0 and
129+
res.resources[0].status.lastManualSync is defined and
130+
res.resources[0].status.lastManualSync=="once" and
131+
res.resources[0].status.latestMoverStatus is defined and
132+
res.resources[0].status.latestMoverStatus.result == "Successful" and
133+
res.resources[0].status.latestMoverStatus.logs is search("Transferred:.*") and
134+
res.resources[0].status.latestMoverStatus.logs is search("Elapsed time:.*") and
135+
res.resources[0].status.latestMoverStatus.logs is search("Rclone completed in.*")
136+
delay: 1
137+
retries: 300
138+
139+
- name: Convert latestImage to PVC using VolumePopulator
140+
kubernetes.core.k8s:
141+
state: present
142+
definition:
143+
apiVersion: v1
144+
kind: PersistentVolumeClaim
145+
metadata:
146+
name: data-dest
147+
namespace: "{{ namespace }}"
148+
spec:
149+
accessModes:
150+
- ReadWriteOnce
151+
dataSourceRef:
152+
kind: ReplicationDestination
153+
apiGroup: volsync.backube
154+
name: destination
155+
resources:
156+
requests:
157+
storage: 1Gi
158+
when: cluster_info.volumepopulator_supported
159+
160+
- name: Convert latestImage to PVC
161+
kubernetes.core.k8s:
162+
state: present
163+
definition:
164+
apiVersion: v1
165+
kind: PersistentVolumeClaim
166+
metadata:
167+
name: data-dest
168+
namespace: "{{ namespace }}"
169+
spec:
170+
accessModes:
171+
- ReadWriteOnce
172+
dataSource:
173+
kind: VolumeSnapshot
174+
apiGroup: snapshot.storage.k8s.io
175+
name: "{{ res.resources[0].status.latestImage.name }}"
176+
resources:
177+
requests:
178+
storage: 1Gi
179+
when: not cluster_info.volumepopulator_supported
180+
181+
- name: Verify regular file contents were synced (symlinks are not
182+
replicated by rclone, so only check regular files)
183+
include_role:
184+
name: compare_pvc_data
185+
vars:
186+
pvc1_name: data-source
187+
pvc2_name: data-dest
188+
timeout: 900
189+
skip_symlink_check: true

0 commit comments

Comments
 (0)