]>
Commit | Line | Data |
---|---|---|
b32b8144 FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Tests for Ceph delegation handling | |
5 | * | |
6 | * (c) 2017, Jeff Layton <jlayton@redhat.com> | |
7 | */ | |
8 | ||
9 | #include "gtest/gtest.h" | |
1e59de90 | 10 | #include "include/compat.h" |
b32b8144 | 11 | #include "include/cephfs/libcephfs.h" |
1e59de90 | 12 | #include "include/fs_types.h" |
b32b8144 FG |
13 | #include "include/stat.h" |
14 | #include <errno.h> | |
15 | #include <fcntl.h> | |
16 | #include <unistd.h> | |
17 | #include <sys/types.h> | |
18 | #include <sys/stat.h> | |
19 | #include <dirent.h> | |
b32b8144 FG |
20 | #include <sys/uio.h> |
21 | ||
22 | #ifdef __linux__ | |
23 | #include <limits.h> | |
eafe8130 | 24 | #include <sys/xattr.h> |
b32b8144 FG |
25 | #endif |
26 | ||
27 | #include <map> | |
28 | #include <vector> | |
29 | #include <thread> | |
30 | #include <atomic> | |
31 | ||
11fdf7f2 TL |
32 | #include "include/ceph_assert.h" |
33 | ||
94b18763 FG |
34 | /* in ms -- 1 minute */ |
35 | #define MAX_WAIT (60 * 1000) | |
36 | ||
37 | static void wait_for_atomic_bool(std::atomic_bool &recalled) | |
38 | { | |
39 | int i = 0; | |
40 | ||
41 | while (!recalled.load()) { | |
42 | ASSERT_LT(i++, MAX_WAIT); | |
43 | usleep(1000); | |
44 | } | |
45 | } | |
46 | ||
1911f103 TL |
47 | static int ceph_ll_delegation_wait(struct ceph_mount_info *cmount, Fh *fh, |
48 | unsigned cmd, ceph_deleg_cb_t cb, void *priv) | |
49 | { | |
50 | int ret, retry = 0; | |
51 | ||
52 | /* Wait 10s at most */ | |
53 | do { | |
54 | ret = ceph_ll_delegation(cmount, fh, cmd, cb, priv); | |
55 | usleep(10000); | |
1e59de90 | 56 | } while (ret == -CEPHFS_EAGAIN && retry++ < 1000); |
1911f103 TL |
57 | |
58 | return ret; | |
59 | } | |
60 | ||
b32b8144 FG |
61 | static int set_default_deleg_timeout(struct ceph_mount_info *cmount) |
62 | { | |
63 | uint32_t session_timeout = ceph_get_cap_return_timeout(cmount); | |
64 | return ceph_set_deleg_timeout(cmount, session_timeout - 1); | |
65 | } | |
66 | ||
67 | static void dummy_deleg_cb(Fh *fh, void *priv) | |
68 | { | |
69 | std::atomic_bool *recalled = (std::atomic_bool *)priv; | |
70 | recalled->store(true); | |
71 | } | |
72 | ||
73 | static void open_breaker_func(struct ceph_mount_info *cmount, const char *filename, int flags, std::atomic_bool *opened) | |
74 | { | |
75 | bool do_shutdown = false; | |
76 | ||
77 | if (!cmount) { | |
78 | ASSERT_EQ(ceph_create(&cmount, NULL), 0); | |
79 | ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0); | |
80 | ASSERT_EQ(ceph_conf_parse_env(cmount, NULL), 0); | |
81 | ASSERT_EQ(ceph_mount(cmount, "/"), 0); | |
82 | ASSERT_EQ(set_default_deleg_timeout(cmount), 0); | |
83 | do_shutdown = true; | |
84 | } | |
85 | ||
86 | Inode *root, *file; | |
87 | ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); | |
88 | ||
89 | Fh *fh; | |
90 | struct ceph_statx stx; | |
91 | UserPerm *perms = ceph_mount_perms(cmount); | |
92 | ||
94b18763 FG |
93 | ASSERT_EQ(ceph_ll_lookup(cmount, root, filename, &file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); |
94 | int ret, i = 0; | |
b32b8144 | 95 | for (;;) { |
94b18763 | 96 | ASSERT_EQ(ceph_ll_getattr(cmount, file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); |
b32b8144 | 97 | ret = ceph_ll_open(cmount, file, flags, &fh, perms); |
1e59de90 | 98 | if (ret != -CEPHFS_EAGAIN) |
b32b8144 | 99 | break; |
94b18763 | 100 | ASSERT_LT(i++, MAX_WAIT); |
b32b8144 FG |
101 | usleep(1000); |
102 | } | |
103 | ASSERT_EQ(ret, 0); | |
104 | opened->store(true); | |
105 | ASSERT_EQ(ceph_ll_close(cmount, fh), 0); | |
106 | ||
107 | if (do_shutdown) | |
108 | ceph_shutdown(cmount); | |
109 | } | |
110 | ||
111 | enum { | |
112 | DelegTestLink, | |
113 | DelegTestRename, | |
114 | DelegTestUnlink | |
115 | }; | |
116 | ||
117 | static void namespace_breaker_func(struct ceph_mount_info *cmount, int cmd, const char *oldname, const char *newname) | |
118 | { | |
119 | bool do_shutdown = false; | |
120 | ||
121 | if (!cmount) { | |
122 | ASSERT_EQ(ceph_create(&cmount, NULL), 0); | |
123 | ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0); | |
124 | ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); | |
125 | ASSERT_EQ(ceph_mount(cmount, "/"), 0); | |
126 | ASSERT_EQ(set_default_deleg_timeout(cmount), 0); | |
127 | do_shutdown = true; | |
128 | } | |
129 | ||
130 | Inode *root, *file = nullptr; | |
131 | ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); | |
132 | ||
133 | struct ceph_statx stx; | |
134 | UserPerm *perms = ceph_mount_perms(cmount); | |
135 | ||
94b18763 | 136 | int ret, i = 0; |
b32b8144 FG |
137 | for (;;) { |
138 | switch (cmd) { | |
139 | case DelegTestRename: | |
140 | ret = ceph_ll_rename(cmount, root, oldname, root, newname, perms); | |
141 | break; | |
142 | case DelegTestLink: | |
143 | if (!file) { | |
144 | ASSERT_EQ(ceph_ll_lookup(cmount, root, oldname, &file, &stx, 0, 0, perms), 0); | |
145 | } | |
146 | ret = ceph_ll_link(cmount, file, root, newname, perms); | |
147 | break; | |
148 | case DelegTestUnlink: | |
149 | ret = ceph_ll_unlink(cmount, root, oldname, perms); | |
150 | break; | |
151 | default: | |
152 | // Bad command | |
11fdf7f2 | 153 | ceph_abort(); |
b32b8144 | 154 | } |
1e59de90 | 155 | if (ret != -CEPHFS_EAGAIN) |
b32b8144 | 156 | break; |
94b18763 | 157 | ASSERT_LT(i++, MAX_WAIT); |
b32b8144 FG |
158 | usleep(1000); |
159 | } | |
160 | ASSERT_EQ(ret, 0); | |
161 | ||
162 | if (do_shutdown) | |
163 | ceph_shutdown(cmount); | |
164 | } | |
165 | ||
166 | static void simple_deleg_test(struct ceph_mount_info *cmount, struct ceph_mount_info *tcmount) | |
167 | { | |
168 | Inode *root, *file; | |
169 | ||
170 | ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); | |
171 | ||
172 | char filename[32]; | |
173 | ||
174 | Fh *fh; | |
175 | struct ceph_statx stx; | |
176 | UserPerm *perms = ceph_mount_perms(cmount); | |
177 | ||
178 | std::atomic_bool recalled(false); | |
179 | std::atomic_bool opened(false); | |
180 | ||
181 | // ensure r/w open breaks a r/w delegation | |
182 | sprintf(filename, "deleg.rwrw.%x", getpid()); | |
183 | ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, | |
184 | O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); | |
1911f103 | 185 | ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); |
b32b8144 | 186 | std::thread breaker1(open_breaker_func, tcmount, filename, O_RDWR, &opened); |
94b18763 FG |
187 | |
188 | wait_for_atomic_bool(recalled); | |
b32b8144 FG |
189 | ASSERT_EQ(opened.load(), false); |
190 | ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); | |
191 | breaker1.join(); | |
192 | ASSERT_EQ(ceph_ll_close(cmount, fh), 0); | |
193 | ASSERT_EQ(ceph_ll_unlink(cmount, root, filename, perms), 0); | |
194 | ||
195 | // ensure r/o open breaks a r/w delegation | |
196 | recalled.store(false); | |
197 | opened.store(false); | |
198 | sprintf(filename, "deleg.rorw.%x", getpid()); | |
199 | ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, | |
200 | O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); | |
1911f103 | 201 | ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); |
b32b8144 | 202 | std::thread breaker2(open_breaker_func, tcmount, filename, O_RDONLY, &opened); |
94b18763 | 203 | wait_for_atomic_bool(recalled); |
b32b8144 FG |
204 | ASSERT_EQ(opened.load(), false); |
205 | ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); | |
206 | breaker2.join(); | |
207 | ASSERT_EQ(ceph_ll_close(cmount, fh), 0); | |
208 | ASSERT_EQ(ceph_ll_unlink(cmount, root, filename, perms), 0); | |
209 | ||
210 | // ensure r/o open does not break a r/o delegation | |
211 | sprintf(filename, "deleg.rwro.%x", getpid()); | |
212 | ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, | |
213 | O_RDONLY|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); | |
214 | recalled.store(false); | |
1911f103 | 215 | ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_RD, dummy_deleg_cb, &recalled), 0); |
b32b8144 FG |
216 | std::thread breaker3(open_breaker_func, tcmount, filename, O_RDONLY, &opened); |
217 | breaker3.join(); | |
218 | ASSERT_EQ(recalled.load(), false); | |
219 | ||
220 | // ensure that r/w open breaks r/o delegation | |
221 | opened.store(false); | |
222 | std::thread breaker4(open_breaker_func, tcmount, filename, O_WRONLY, &opened); | |
94b18763 | 223 | wait_for_atomic_bool(recalled); |
b32b8144 FG |
224 | usleep(1000); |
225 | ASSERT_EQ(opened.load(), false); | |
226 | ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); | |
227 | breaker4.join(); | |
228 | ASSERT_EQ(ceph_ll_close(cmount, fh), 0); | |
229 | ASSERT_EQ(ceph_ll_unlink(cmount, root, filename, perms), 0); | |
230 | ||
231 | // ensure hardlinking breaks a r/w delegation | |
232 | recalled.store(false); | |
233 | char newname[32]; | |
234 | sprintf(filename, "deleg.old.%x", getpid()); | |
235 | sprintf(newname, "deleg.new.%x", getpid()); | |
236 | ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, | |
237 | O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); | |
1911f103 | 238 | ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); |
b32b8144 | 239 | std::thread breaker5(namespace_breaker_func, tcmount, DelegTestLink, filename, newname); |
94b18763 | 240 | wait_for_atomic_bool(recalled); |
b32b8144 FG |
241 | ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); |
242 | breaker5.join(); | |
243 | ASSERT_EQ(ceph_ll_close(cmount, fh), 0); | |
244 | ASSERT_EQ(ceph_ll_unlink(cmount, root, filename, perms), 0); | |
245 | ASSERT_EQ(ceph_ll_unlink(cmount, root, newname, perms), 0); | |
246 | ||
247 | // ensure renaming breaks a r/w delegation | |
248 | recalled.store(false); | |
249 | ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, | |
250 | O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); | |
1911f103 | 251 | ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); |
b32b8144 | 252 | std::thread breaker6(namespace_breaker_func, tcmount, DelegTestRename, filename, newname); |
94b18763 | 253 | wait_for_atomic_bool(recalled); |
b32b8144 FG |
254 | ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); |
255 | breaker6.join(); | |
256 | ASSERT_EQ(ceph_ll_close(cmount, fh), 0); | |
257 | ASSERT_EQ(ceph_ll_unlink(cmount, root, newname, perms), 0); | |
258 | ||
259 | // ensure unlinking breaks a r/w delegation | |
260 | recalled.store(false); | |
261 | ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, | |
262 | O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); | |
1911f103 | 263 | ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_WR, dummy_deleg_cb, &recalled), 0); |
b32b8144 | 264 | std::thread breaker7(namespace_breaker_func, tcmount, DelegTestUnlink, filename, nullptr); |
94b18763 | 265 | wait_for_atomic_bool(recalled); |
b32b8144 FG |
266 | ASSERT_EQ(ceph_ll_delegation(cmount, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, &recalled), 0); |
267 | breaker7.join(); | |
268 | ASSERT_EQ(ceph_ll_close(cmount, fh), 0); | |
269 | } | |
270 | ||
271 | TEST(LibCephFS, DelegMultiClient) { | |
272 | struct ceph_mount_info *cmount; | |
273 | ||
274 | ASSERT_EQ(ceph_create(&cmount, NULL), 0); | |
275 | ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0); | |
276 | ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); | |
277 | ASSERT_EQ(ceph_mount(cmount, "/"), 0); | |
278 | ASSERT_EQ(set_default_deleg_timeout(cmount), 0); | |
279 | ||
280 | simple_deleg_test(cmount, nullptr); | |
281 | ||
282 | ceph_shutdown(cmount); | |
283 | } | |
284 | ||
285 | TEST(LibCephFS, DelegSingleClient) { | |
286 | struct ceph_mount_info *cmount; | |
287 | ||
288 | ASSERT_EQ(ceph_create(&cmount, NULL), 0); | |
289 | ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0); | |
290 | ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); | |
291 | ASSERT_EQ(ceph_mount(cmount, "/"), 0); | |
292 | ASSERT_EQ(set_default_deleg_timeout(cmount), 0); | |
293 | ||
294 | simple_deleg_test(cmount, cmount); | |
295 | ||
296 | ceph_shutdown(cmount); | |
297 | } | |
298 | ||
299 | TEST(LibCephFS, DelegTimeout) { | |
300 | struct ceph_mount_info *cmount; | |
301 | ASSERT_EQ(ceph_create(&cmount, NULL), 0); | |
302 | ASSERT_EQ(ceph_conf_read_file(cmount, NULL), 0); | |
303 | ASSERT_EQ(0, ceph_conf_parse_env(cmount, NULL)); | |
304 | ASSERT_EQ(ceph_mount(cmount, "/"), 0); | |
305 | // tweak timeout to run quickly, since we don't plan to return it anyway | |
306 | ASSERT_EQ(ceph_set_deleg_timeout(cmount, 2), 0); | |
307 | ||
308 | Inode *root, *file; | |
309 | ASSERT_EQ(ceph_ll_lookup_root(cmount, &root), 0); | |
310 | ||
311 | char filename[32]; | |
312 | sprintf(filename, "delegtimeo%x", getpid()); | |
313 | ||
314 | Fh *fh; | |
315 | struct ceph_statx stx; | |
316 | UserPerm *perms = ceph_mount_perms(cmount); | |
317 | ||
318 | ASSERT_EQ(ceph_ll_create(cmount, root, filename, 0666, | |
319 | O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); | |
320 | ||
321 | /* Reopen read-only */ | |
322 | ASSERT_EQ(ceph_ll_close(cmount, fh), 0); | |
323 | ASSERT_EQ(ceph_ll_open(cmount, file, O_RDONLY, &fh, perms), 0); | |
324 | ||
325 | std::atomic_bool recalled(false); | |
1911f103 | 326 | ASSERT_EQ(ceph_ll_delegation_wait(cmount, fh, CEPH_DELEGATION_RD, dummy_deleg_cb, &recalled), 0); |
b32b8144 FG |
327 | std::atomic_bool opened(false); |
328 | std::thread breaker1(open_breaker_func, nullptr, filename, O_RDWR, &opened); | |
329 | breaker1.join(); | |
330 | ASSERT_EQ(recalled.load(), true); | |
1e59de90 | 331 | ASSERT_EQ(ceph_ll_getattr(cmount, root, &stx, 0, 0, perms), -CEPHFS_ENOTCONN); |
b32b8144 FG |
332 | ceph_release(cmount); |
333 | } | |
94b18763 FG |
334 | |
335 | TEST(LibCephFS, RecalledGetattr) { | |
336 | struct ceph_mount_info *cmount1; | |
337 | ASSERT_EQ(ceph_create(&cmount1, NULL), 0); | |
338 | ASSERT_EQ(ceph_conf_read_file(cmount1, NULL), 0); | |
339 | ASSERT_EQ(0, ceph_conf_parse_env(cmount1, NULL)); | |
340 | ASSERT_EQ(ceph_mount(cmount1, "/"), 0); | |
341 | ASSERT_EQ(set_default_deleg_timeout(cmount1), 0); | |
342 | ||
343 | Inode *root, *file; | |
344 | ASSERT_EQ(ceph_ll_lookup_root(cmount1, &root), 0); | |
345 | ||
346 | char filename[32]; | |
347 | sprintf(filename, "recalledgetattr%x", getpid()); | |
348 | ||
349 | Fh *fh; | |
350 | struct ceph_statx stx; | |
351 | UserPerm *perms = ceph_mount_perms(cmount1); | |
352 | ||
353 | ASSERT_EQ(ceph_ll_create(cmount1, root, filename, 0666, | |
354 | O_RDWR|O_CREAT|O_EXCL, &file, &fh, &stx, 0, 0, perms), 0); | |
11fdf7f2 TL |
355 | ASSERT_EQ(ceph_ll_write(cmount1, fh, 0, sizeof(filename), filename), |
356 | static_cast<int>(sizeof(filename))); | |
94b18763 FG |
357 | ASSERT_EQ(ceph_ll_close(cmount1, fh), 0); |
358 | ||
359 | /* New mount for read delegation */ | |
360 | struct ceph_mount_info *cmount2; | |
361 | ASSERT_EQ(ceph_create(&cmount2, NULL), 0); | |
362 | ASSERT_EQ(ceph_conf_read_file(cmount2, NULL), 0); | |
363 | ASSERT_EQ(0, ceph_conf_parse_env(cmount2, NULL)); | |
364 | ASSERT_EQ(ceph_mount(cmount2, "/"), 0); | |
365 | ASSERT_EQ(set_default_deleg_timeout(cmount2), 0); | |
366 | ||
367 | ASSERT_EQ(ceph_ll_lookup_root(cmount2, &root), 0); | |
368 | perms = ceph_mount_perms(cmount2); | |
369 | ASSERT_EQ(ceph_ll_lookup(cmount2, root, filename, &file, &stx, 0, 0, perms), 0); | |
370 | ||
371 | ASSERT_EQ(ceph_ll_open(cmount2, file, O_WRONLY, &fh, perms), 0); | |
11fdf7f2 TL |
372 | ASSERT_EQ(ceph_ll_write(cmount2, fh, 0, sizeof(filename), filename), |
373 | static_cast<int>(sizeof(filename))); | |
94b18763 FG |
374 | ASSERT_EQ(ceph_ll_close(cmount2, fh), 0); |
375 | ||
376 | ASSERT_EQ(ceph_ll_open(cmount2, file, O_RDONLY, &fh, perms), 0); | |
377 | ||
378 | /* Break delegation */ | |
379 | std::atomic_bool recalled(false); | |
1911f103 | 380 | ASSERT_EQ(ceph_ll_delegation_wait(cmount2, fh, CEPH_DELEGATION_RD, dummy_deleg_cb, &recalled), 0); |
11fdf7f2 TL |
381 | ASSERT_EQ(ceph_ll_read(cmount2, fh, 0, sizeof(filename), filename), |
382 | static_cast<int>(sizeof(filename))); | |
94b18763 FG |
383 | ASSERT_EQ(ceph_ll_getattr(cmount2, file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); |
384 | std::atomic_bool opened(false); | |
385 | std::thread breaker1(open_breaker_func, cmount1, filename, O_WRONLY, &opened); | |
386 | int i = 0; | |
387 | do { | |
388 | ASSERT_EQ(ceph_ll_getattr(cmount2, file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); | |
389 | ASSERT_LT(i++, MAX_WAIT); | |
390 | usleep(1000); | |
391 | } while (!recalled.load()); | |
392 | ASSERT_EQ(opened.load(), false); | |
393 | ASSERT_EQ(ceph_ll_getattr(cmount2, file, &stx, CEPH_STATX_ALL_STATS, 0, perms), 0); | |
394 | ASSERT_EQ(ceph_ll_delegation(cmount2, fh, CEPH_DELEGATION_NONE, dummy_deleg_cb, nullptr), 0); | |
395 | breaker1.join(); | |
396 | ASSERT_EQ(ceph_ll_close(cmount2, fh), 0); | |
397 | ceph_unmount(cmount2); | |
398 | ceph_release(cmount2); | |
399 | ceph_unmount(cmount1); | |
400 | ceph_release(cmount1); | |
401 | } |