]> git.proxmox.com Git - ceph.git/blob - ceph/src/test/os/TestLFNIndex.cc
update sources to v12.1.3
[ceph.git] / ceph / src / test / os / TestLFNIndex.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3 /*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (C) 2013 Cloudwatt <libre.licensing@cloudwatt.com>
7 *
8 * Author: Loic Dachary <loic@dachary.org>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Library Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
13 * any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Library Public License for more details.
19 *
20 */
21
22 #include <stdio.h>
23 #include <signal.h>
24 #include "os/filestore/LFNIndex.h"
25 #include "os/filestore/chain_xattr.h"
26 #include "common/ceph_argparse.h"
27 #include "global/global_init.h"
28 #include <gtest/gtest.h>
29
30 class TestWrapLFNIndex : public LFNIndex {
31 public:
32 TestWrapLFNIndex(CephContext* cct,
33 coll_t collection,
34 const char *base_path,
35 uint32_t index_version)
36 : LFNIndex(cct, collection, base_path, index_version) {}
37
38 uint32_t collection_version() override {
39 return index_version;
40 }
41
42 int cleanup() override { return 0; }
43
44 int _split(
45 uint32_t match,
46 uint32_t bits,
47 CollectionIndex* dest
48 ) override { return 0; }
49
50 void test_generate_and_parse(const ghobject_t &hoid, const std::string &mangled_expected) {
51 const std::string mangled_name = lfn_generate_object_name(hoid);
52 EXPECT_EQ(mangled_expected, mangled_name);
53 ghobject_t hoid_parsed;
54 EXPECT_EQ(0, lfn_parse_object_name(mangled_name, &hoid_parsed));
55 EXPECT_EQ(hoid, hoid_parsed);
56 }
57
58 protected:
59 int _init() override { return 0; }
60
61 int _created(
62 const vector<string> &path,
63 const ghobject_t &hoid,
64 const string &mangled_name
65 ) override { return 0; }
66
67 int _remove(
68 const vector<string> &path,
69 const ghobject_t &hoid,
70 const string &mangled_name
71 ) override { return 0; }
72
73 int _lookup(
74 const ghobject_t &hoid,
75 vector<string> *path,
76 string *mangled_name,
77 int *exists
78 ) override { return 0; }
79
80 int _collection_list_partial(
81 const ghobject_t &start,
82 const ghobject_t &end,
83 int max_count,
84 vector<ghobject_t> *ls,
85 ghobject_t *next
86 ) override { return 0; }
87 int _pre_hash_collection(
88 uint32_t pg_num,
89 uint64_t expected_num_objs
90 ) override { return 0; }
91
92 };
93
94 class TestHASH_INDEX_TAG : public TestWrapLFNIndex, public ::testing::Test {
95 public:
96 TestHASH_INDEX_TAG()
97 : TestWrapLFNIndex(g_ceph_context, coll_t(), "PATH_1",
98 CollectionIndex::HASH_INDEX_TAG) {
99 }
100 };
101
102 TEST_F(TestHASH_INDEX_TAG, generate_and_parse_name) {
103 const vector<string> path;
104 const std::string key;
105 uint64_t hash = 0xABABABAB;
106 uint64_t pool = -1;
107
108 test_generate_and_parse(ghobject_t(hobject_t(object_t(".A/B_\\C.D"), key, CEPH_NOSNAP, hash, pool, "")),
109 "\\.A\\sB_\\\\C.D_head_ABABABAB");
110 test_generate_and_parse(ghobject_t(hobject_t(object_t("DIR_A"), key, CEPH_NOSNAP, hash, pool, "")),
111 "\\dA_head_ABABABAB");
112 }
113
114 class TestHASH_INDEX_TAG_2 : public TestWrapLFNIndex, public ::testing::Test {
115 public:
116 TestHASH_INDEX_TAG_2()
117 : TestWrapLFNIndex(g_ceph_context,
118 coll_t(), "PATH_1", CollectionIndex::HASH_INDEX_TAG_2) {
119 }
120 };
121
122 TEST_F(TestHASH_INDEX_TAG_2, generate_and_parse_name) {
123 const vector<string> path;
124 const std::string key("KEY");
125 uint64_t hash = 0xABABABAB;
126 uint64_t pool = -1;
127
128 {
129 std::string name(".XA/B_\\C.D");
130 name[1] = '\0';
131 ghobject_t hoid(hobject_t(object_t(name), key, CEPH_NOSNAP, hash, pool, ""));
132
133 test_generate_and_parse(hoid, "\\.\\nA\\sB\\u\\\\C.D_KEY_head_ABABABAB");
134 }
135 test_generate_and_parse(ghobject_t(hobject_t(object_t("DIR_A"), key, CEPH_NOSNAP, hash, pool, "")),
136 "\\dA_KEY_head_ABABABAB");
137 }
138
139 class TestHOBJECT_WITH_POOL : public TestWrapLFNIndex, public ::testing::Test {
140 public:
141 TestHOBJECT_WITH_POOL()
142 : TestWrapLFNIndex(g_ceph_context, coll_t(),
143 "PATH_1", CollectionIndex::HOBJECT_WITH_POOL) {
144 }
145 };
146
147 TEST_F(TestHOBJECT_WITH_POOL, generate_and_parse_name) {
148 const vector<string> path;
149 const std::string key("KEY");
150 uint64_t hash = 0xABABABAB;
151 uint64_t pool = 0xCDCDCDCD;
152 int64_t gen = 0xefefefefef;
153 shard_id_t shard_id(0xb);
154
155 {
156 std::string name(".XA/B_\\C.D");
157 name[1] = '\0';
158 ghobject_t hoid(hobject_t(object_t(name), key, CEPH_NOSNAP, hash, pool, ""));
159 hoid.hobj.nspace = "NSPACE";
160
161 test_generate_and_parse(hoid, "\\.\\nA\\sB\\u\\\\C.D_KEY_head_ABABABAB_NSPACE_cdcdcdcd");
162 }
163 {
164 ghobject_t hoid(hobject_t(object_t("DIR_A"), key, CEPH_NOSNAP, hash, pool, ""));
165 hoid.hobj.nspace = "NSPACE";
166
167 test_generate_and_parse(hoid, "\\dA_KEY_head_ABABABAB_NSPACE_cdcdcdcd");
168 }
169 {
170 std::string name(".XA/B_\\C.D");
171 name[1] = '\0';
172 ghobject_t hoid(hobject_t(object_t(name), key, CEPH_NOSNAP, hash, pool, ""), gen, shard_id);
173 hoid.hobj.nspace = "NSPACE";
174
175 test_generate_and_parse(hoid, "\\.\\nA\\sB\\u\\\\C.D_KEY_head_ABABABAB_NSPACE_cdcdcdcd_efefefefef_b");
176 }
177 {
178 ghobject_t hoid(hobject_t(object_t("DIR_A"), key, CEPH_NOSNAP, hash, pool, ""), gen, shard_id);
179 hoid.hobj.nspace = "NSPACE";
180
181 test_generate_and_parse(hoid, "\\dA_KEY_head_ABABABAB_NSPACE_cdcdcdcd_efefefefef_b");
182 }
183 }
184
185 class TestLFNIndex : public TestWrapLFNIndex, public ::testing::Test {
186 public:
187 TestLFNIndex()
188 : TestWrapLFNIndex(g_ceph_context, coll_t(), "PATH_1",
189 CollectionIndex::HOBJECT_WITH_POOL) {
190 }
191
192 void SetUp() override {
193 ::chmod("PATH_1", 0700);
194 ASSERT_EQ(0, ::system("rm -fr PATH_1"));
195 ASSERT_EQ(0, ::mkdir("PATH_1", 0700));
196 }
197
198 void TearDown() override {
199 ASSERT_EQ(0, ::system("rm -fr PATH_1"));
200 }
201 };
202
203 TEST_F(TestLFNIndex, remove_object) {
204 const vector<string> path;
205
206 //
207 // small object name removal
208 //
209 {
210 std::string mangled_name;
211 int exists = 666;
212 ghobject_t hoid(hobject_t(sobject_t("ABC", CEPH_NOSNAP)));
213
214 EXPECT_EQ(0, ::chmod("PATH_1", 0000));
215 if (getuid() != 0) {
216 EXPECT_EQ(-EACCES, remove_object(path, hoid));
217 }
218 EXPECT_EQ(0, ::chmod("PATH_1", 0700));
219 EXPECT_EQ(-ENOENT, remove_object(path, hoid));
220 EXPECT_EQ(0, get_mangled_name(path, hoid, &mangled_name, &exists));
221 const std::string pathname("PATH_1/" + mangled_name);
222 EXPECT_EQ(0, ::close(::creat(pathname.c_str(), 0600)));
223 EXPECT_EQ(0, remove_object(path, hoid));
224 EXPECT_EQ(-1, ::access(pathname.c_str(), 0));
225 EXPECT_EQ(ENOENT, errno);
226 }
227 //
228 // long object name removal of a single file
229 //
230 {
231 std::string mangled_name;
232 int exists;
233 const std::string object_name(1024, 'A');
234 ghobject_t hoid(hobject_t(sobject_t(object_name, CEPH_NOSNAP)));
235
236 EXPECT_EQ(0, get_mangled_name(path, hoid, &mangled_name, &exists));
237 EXPECT_EQ(0, exists);
238 EXPECT_NE(std::string::npos, mangled_name.find("0_long"));
239 std::string pathname("PATH_1/" + mangled_name);
240 EXPECT_EQ(0, ::close(::creat(pathname.c_str(), 0600)));
241 EXPECT_EQ(0, created(hoid, pathname.c_str()));
242
243 EXPECT_EQ(0, remove_object(path, hoid));
244 EXPECT_EQ(-1, ::access(pathname.c_str(), 0));
245 EXPECT_EQ(ENOENT, errno);
246 }
247
248 //
249 // long object name removal of the last file
250 //
251 {
252 std::string mangled_name;
253 int exists;
254 const std::string object_name(1024, 'A');
255 ghobject_t hoid(hobject_t(sobject_t(object_name, CEPH_NOSNAP)));
256
257 //
258 // PATH_1/AAA..._0_long => does not match long object name
259 //
260 EXPECT_EQ(0, get_mangled_name(path, hoid, &mangled_name, &exists));
261 EXPECT_EQ(0, exists);
262 EXPECT_NE(std::string::npos, mangled_name.find("0_long"));
263 std::string pathname("PATH_1/" + mangled_name);
264 EXPECT_EQ(0, ::close(::creat(pathname.c_str(), 0600)));
265 EXPECT_EQ(0, created(hoid, pathname.c_str()));
266 string LFN_ATTR = "user.cephos.lfn";
267 if (index_version != HASH_INDEX_TAG) {
268 char buf[100];
269 snprintf(buf, sizeof(buf), "%d", index_version);
270 LFN_ATTR += string(buf);
271 }
272 const std::string object_name_1 = object_name + "SUFFIX";
273 EXPECT_EQ(object_name_1.size(), (unsigned)chain_setxattr(pathname.c_str(), LFN_ATTR.c_str(), object_name_1.c_str(), object_name_1.size()));
274
275 //
276 // PATH_1/AAA..._1_long => matches long object name
277 //
278 std::string mangled_name_1;
279 exists = 666;
280 EXPECT_EQ(0, get_mangled_name(path, hoid, &mangled_name_1, &exists));
281 EXPECT_NE(std::string::npos, mangled_name_1.find("1_long"));
282 EXPECT_EQ(0, exists);
283 std::string pathname_1("PATH_1/" + mangled_name_1);
284 EXPECT_EQ(0, ::close(::creat(pathname_1.c_str(), 0600)));
285 EXPECT_EQ(0, created(hoid, pathname_1.c_str()));
286
287 //
288 // remove_object skips PATH_1/AAA..._0_long and removes PATH_1/AAA..._1_long
289 //
290 EXPECT_EQ(0, remove_object(path, hoid));
291 EXPECT_EQ(0, ::access(pathname.c_str(), 0));
292 EXPECT_EQ(-1, ::access(pathname_1.c_str(), 0));
293 EXPECT_EQ(ENOENT, errno);
294 EXPECT_EQ(0, ::unlink(pathname.c_str()));
295 }
296
297 //
298 // long object name removal of a file in the middle of the list
299 //
300 {
301 std::string mangled_name;
302 int exists;
303 const std::string object_name(1024, 'A');
304 ghobject_t hoid(hobject_t(sobject_t(object_name, CEPH_NOSNAP)));
305
306 //
307 // PATH_1/AAA..._0_long => matches long object name
308 //
309 EXPECT_EQ(0, get_mangled_name(path, hoid, &mangled_name, &exists));
310 EXPECT_EQ(0, exists);
311 EXPECT_NE(std::string::npos, mangled_name.find("0_long"));
312 std::string pathname("PATH_1/" + mangled_name);
313 EXPECT_EQ(0, ::close(::creat(pathname.c_str(), 0600)));
314 EXPECT_EQ(0, created(hoid, pathname.c_str()));
315 //
316 // PATH_1/AAA..._1_long => matches long object name
317 //
318 std::string mangled_name_1 = mangled_name;
319 mangled_name_1.replace(mangled_name_1.find("0_long"), 6, "1_long");
320 const std::string pathname_1("PATH_1/" + mangled_name_1);
321 const std::string cmd("cp -a " + pathname + " " + pathname_1);
322 EXPECT_EQ(0, ::system(cmd.c_str()));
323 const string ATTR = "user.MARK";
324 EXPECT_EQ((unsigned)1, (unsigned)chain_setxattr(pathname_1.c_str(), ATTR.c_str(), "Y", 1));
325
326 //
327 // remove_object replaces the file to be removed with the last from the
328 // collision list. In this case it replaces
329 // PATH_1/AAA..._0_long
330 // with
331 // PATH_1/AAA..._1_long
332 //
333 EXPECT_EQ(0, remove_object(path, hoid));
334 EXPECT_EQ(0, ::access(pathname.c_str(), 0));
335 char buffer[1] = { 0, };
336 EXPECT_EQ((unsigned)1, (unsigned)chain_getxattr(pathname.c_str(), ATTR.c_str(), buffer, 1));
337 EXPECT_EQ('Y', buffer[0]);
338 EXPECT_EQ(-1, ::access(pathname_1.c_str(), 0));
339 EXPECT_EQ(ENOENT, errno);
340 }
341 }
342
343 TEST_F(TestLFNIndex, get_mangled_name) {
344 const vector<string> path;
345
346 //
347 // small object name
348 //
349 {
350 std::string mangled_name;
351 int exists = 666;
352 ghobject_t hoid(hobject_t(sobject_t("ABC", CEPH_NOSNAP)));
353
354 EXPECT_EQ(0, get_mangled_name(path, hoid, &mangled_name, &exists));
355 EXPECT_NE(std::string::npos, mangled_name.find("ABC__head"));
356 EXPECT_EQ(std::string::npos, mangled_name.find("0_long"));
357 EXPECT_EQ(0, exists);
358 const std::string pathname("PATH_1/" + mangled_name);
359 EXPECT_EQ(0, ::close(::creat(pathname.c_str(), 0600)));
360 EXPECT_EQ(0, get_mangled_name(path, hoid, &mangled_name, &exists));
361 EXPECT_NE(std::string::npos, mangled_name.find("ABC__head"));
362 EXPECT_EQ(1, exists);
363 EXPECT_EQ(0, ::unlink(pathname.c_str()));
364 }
365 //
366 // long object name
367 //
368 {
369 std::string mangled_name;
370 int exists;
371 const std::string object_name(1024, 'A');
372 ghobject_t hoid(hobject_t(sobject_t(object_name, CEPH_NOSNAP)));
373
374 //
375 // long version of the mangled name and no matching
376 // file exists
377 //
378 mangled_name.clear();
379 exists = 666;
380 EXPECT_EQ(0, get_mangled_name(path, hoid, &mangled_name, &exists));
381 EXPECT_NE(std::string::npos, mangled_name.find("0_long"));
382 EXPECT_EQ(0, exists);
383
384 const std::string pathname("PATH_1/" + mangled_name);
385
386 //
387 // if a file by the same name exists but does not have the
388 // expected extended attribute, it is silently removed
389 //
390 mangled_name.clear();
391 exists = 666;
392 EXPECT_EQ(0, ::close(::creat(pathname.c_str(), 0600)));
393 EXPECT_EQ(0, get_mangled_name(path, hoid, &mangled_name, &exists));
394 EXPECT_NE(std::string::npos, mangled_name.find("0_long"));
395 EXPECT_EQ(0, exists);
396 EXPECT_EQ(-1, ::access(pathname.c_str(), 0));
397 EXPECT_EQ(ENOENT, errno);
398
399 //
400 // if a file by the same name exists but does not have the
401 // expected extended attribute, and cannot be removed,
402 // return on error
403 //
404 mangled_name.clear();
405 exists = 666;
406 EXPECT_EQ(0, ::close(::creat(pathname.c_str(), 0600)));
407 EXPECT_EQ(0, ::chmod("PATH_1", 0500));
408 if (getuid() != 0) {
409 EXPECT_EQ(-EACCES, get_mangled_name(path, hoid, &mangled_name, &exists));
410 }
411 EXPECT_EQ("", mangled_name);
412 EXPECT_EQ(666, exists);
413 EXPECT_EQ(0, ::chmod("PATH_1", 0700));
414 EXPECT_EQ(0, ::unlink(pathname.c_str()));
415
416 //
417 // long version of the mangled name and a file
418 // exists by that name and contains the long object name
419 //
420 mangled_name.clear();
421 exists = 666;
422 EXPECT_EQ(0, ::close(::creat(pathname.c_str(), 0600)));
423 EXPECT_EQ(0, created(hoid, pathname.c_str()));
424 EXPECT_EQ(0, get_mangled_name(path, hoid, &mangled_name, &exists));
425 EXPECT_NE(std::string::npos, mangled_name.find("0_long"));
426 EXPECT_EQ(1, exists);
427 EXPECT_EQ(0, ::access(pathname.c_str(), 0));
428
429 //
430 // long version of the mangled name and a file exists by that name
431 // and contains a long object name with the same prefix but they
432 // are not identical and it so happens that their SHA1 is
433 // identical : a collision number is used to differentiate them
434 //
435 string LFN_ATTR = "user.cephos.lfn";
436 if (index_version != HASH_INDEX_TAG) {
437 char buf[100];
438 snprintf(buf, sizeof(buf), "%d", index_version);
439 LFN_ATTR += string(buf);
440 }
441 const std::string object_name_same_prefix = object_name + "SUFFIX";
442 EXPECT_EQ(object_name_same_prefix.size(), (unsigned)chain_setxattr(pathname.c_str(), LFN_ATTR.c_str(), object_name_same_prefix.c_str(), object_name_same_prefix.size()));
443 std::string mangled_name_same_prefix;
444 exists = 666;
445 EXPECT_EQ(0, get_mangled_name(path, hoid, &mangled_name_same_prefix, &exists));
446 EXPECT_NE(std::string::npos, mangled_name_same_prefix.find("1_long"));
447 EXPECT_EQ(0, exists);
448
449 EXPECT_EQ(0, ::unlink(pathname.c_str()));
450 }
451 }
452
453 int main(int argc, char **argv) {
454 int fd = ::creat("detect", 0600);
455 if (fd < 0){
456 cerr << "failed to create file detect" << std::endl;
457 return EXIT_FAILURE;
458 }
459 int ret = chain_fsetxattr(fd, "user.test", "A", 1);
460 ::close(fd);
461 ::unlink("detect");
462 if (ret < 0) {
463 cerr << "SKIP LFNIndex because unable to test for xattr" << std::endl;
464 } else {
465 vector<const char*> args;
466 argv_to_vec(argc, (const char **)argv, args);
467
468 auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT,
469 CODE_ENVIRONMENT_UTILITY, 0);
470 common_init_finish(g_ceph_context);
471
472 ::testing::InitGoogleTest(&argc, argv);
473 return RUN_ALL_TESTS();
474 }
475 }
476
477 /*
478 * Local Variables:
479 * compile-command: "cd ../.. ;
480 * make unittest_lfnindex &&
481 * valgrind --tool=memcheck ./unittest_lfnindex \
482 * # --gtest_filter=TestLFNIndex.* --log-to-stderr=true --debug-filestore=20"
483 * End:
484 */