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