]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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) 2004-2006 Sage Weil <sage@newdream.net> | |
7 | * | |
8 | * This is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License version 2.1, as published by the Free Software | |
11 | * Foundation. See file COPYING. | |
12 | * | |
13 | */ | |
14 | ||
15 | #include <stdio.h> | |
16 | #include <string.h> | |
17 | #include <iostream> | |
18 | #include <time.h> | |
19 | #include <sys/mount.h> | |
20 | #include "kv/KeyValueDB.h" | |
21 | #include "include/Context.h" | |
22 | #include "common/ceph_argparse.h" | |
23 | #include "global/global_init.h" | |
24 | #include "common/Mutex.h" | |
25 | #include "common/Cond.h" | |
26 | #include "common/errno.h" | |
27 | #include "include/stringify.h" | |
28 | #include <gtest/gtest.h> | |
29 | ||
30 | #if GTEST_HAS_PARAM_TEST | |
31 | ||
32 | class KVTest : public ::testing::TestWithParam<const char*> { | |
33 | public: | |
34 | boost::scoped_ptr<KeyValueDB> db; | |
35 | ||
36 | KVTest() : db(0) {} | |
37 | ||
38 | void rm_r(string path) { | |
39 | string cmd = string("rm -r ") + path; | |
40 | cout << "==> " << cmd << std::endl; | |
41 | int r = ::system(cmd.c_str()); | |
42 | if (r) { | |
43 | cerr << "failed with exit code " << r | |
44 | << ", continuing anyway" << std::endl; | |
45 | } | |
46 | } | |
47 | ||
48 | void init() { | |
49 | cout << "Creating " << string(GetParam()) << "\n"; | |
50 | db.reset(KeyValueDB::create(g_ceph_context, string(GetParam()), | |
51 | "kv_test_temp_dir")); | |
52 | } | |
53 | void fini() { | |
54 | db.reset(NULL); | |
55 | } | |
56 | ||
57 | void SetUp() override { | |
58 | int r = ::mkdir("kv_test_temp_dir", 0777); | |
59 | if (r < 0 && errno != EEXIST) { | |
60 | r = -errno; | |
61 | cerr << __func__ << ": unable to create kv_test_temp_dir: " | |
62 | << cpp_strerror(r) << std::endl; | |
63 | return; | |
64 | } | |
65 | init(); | |
66 | } | |
67 | void TearDown() override { | |
68 | fini(); | |
69 | rm_r("kv_test_temp_dir"); | |
70 | } | |
71 | }; | |
72 | ||
73 | TEST_P(KVTest, OpenClose) { | |
74 | ASSERT_EQ(0, db->create_and_open(cout)); | |
75 | fini(); | |
76 | } | |
77 | ||
78 | TEST_P(KVTest, OpenCloseReopenClose) { | |
79 | ASSERT_EQ(0, db->create_and_open(cout)); | |
80 | fini(); | |
81 | init(); | |
82 | ASSERT_EQ(0, db->open(cout)); | |
83 | fini(); | |
84 | } | |
85 | ||
86 | /* | |
87 | * Basic write and read test case in same database session. | |
88 | */ | |
89 | TEST_P(KVTest, OpenWriteRead) { | |
90 | ASSERT_EQ(0, db->create_and_open(cout)); | |
91 | { | |
92 | KeyValueDB::Transaction t = db->get_transaction(); | |
93 | bufferlist value; | |
94 | value.append("value"); | |
95 | t->set("prefix", "key", value); | |
96 | value.clear(); | |
97 | value.append("value2"); | |
98 | t->set("prefix", "key2", value); | |
99 | value.clear(); | |
100 | value.append("value3"); | |
101 | t->set("prefix", "key3", value); | |
102 | db->submit_transaction_sync(t); | |
103 | ||
104 | bufferlist v1, v2; | |
105 | ASSERT_EQ(0, db->get("prefix", "key", &v1)); | |
106 | ASSERT_EQ(v1.length(), 5u); | |
107 | (v1.c_str())[v1.length()] = 0x0; | |
108 | ASSERT_EQ(std::string(v1.c_str()), std::string("value")); | |
109 | ASSERT_EQ(0, db->get("prefix", "key2", &v2)); | |
110 | ASSERT_EQ(v2.length(), 6u); | |
111 | (v2.c_str())[v2.length()] = 0x0; | |
112 | ASSERT_EQ(std::string(v2.c_str()), std::string("value2")); | |
113 | } | |
114 | fini(); | |
115 | } | |
116 | ||
117 | TEST_P(KVTest, PutReopen) { | |
118 | ASSERT_EQ(0, db->create_and_open(cout)); | |
119 | { | |
120 | KeyValueDB::Transaction t = db->get_transaction(); | |
121 | bufferlist value; | |
122 | value.append("value"); | |
123 | t->set("prefix", "key", value); | |
124 | t->set("prefix", "key2", value); | |
125 | t->set("prefix", "key3", value); | |
126 | db->submit_transaction_sync(t); | |
127 | } | |
128 | fini(); | |
129 | ||
130 | init(); | |
131 | ASSERT_EQ(0, db->open(cout)); | |
132 | { | |
133 | bufferlist v1, v2; | |
134 | ASSERT_EQ(0, db->get("prefix", "key", &v1)); | |
135 | ASSERT_EQ(v1.length(), 5u); | |
136 | ASSERT_EQ(0, db->get("prefix", "key2", &v2)); | |
137 | ASSERT_EQ(v2.length(), 5u); | |
138 | } | |
139 | { | |
140 | KeyValueDB::Transaction t = db->get_transaction(); | |
141 | t->rmkey("prefix", "key"); | |
142 | t->rmkey("prefix", "key3"); | |
143 | db->submit_transaction_sync(t); | |
144 | } | |
145 | fini(); | |
146 | ||
147 | init(); | |
148 | ASSERT_EQ(0, db->open(cout)); | |
149 | { | |
150 | bufferlist v1, v2, v3; | |
151 | ASSERT_EQ(-ENOENT, db->get("prefix", "key", &v1)); | |
152 | ASSERT_EQ(0, db->get("prefix", "key2", &v2)); | |
153 | ASSERT_EQ(v2.length(), 5u); | |
154 | ASSERT_EQ(-ENOENT, db->get("prefix", "key3", &v3)); | |
155 | } | |
156 | fini(); | |
157 | } | |
158 | ||
159 | TEST_P(KVTest, BenchCommit) { | |
160 | int n = 1024; | |
161 | ASSERT_EQ(0, db->create_and_open(cout)); | |
162 | utime_t start = ceph_clock_now(); | |
163 | { | |
164 | cout << "priming" << std::endl; | |
165 | // prime | |
166 | bufferlist big; | |
167 | bufferptr bp(1048576); | |
168 | bp.zero(); | |
169 | big.append(bp); | |
170 | for (int i=0; i<30; ++i) { | |
171 | KeyValueDB::Transaction t = db->get_transaction(); | |
172 | t->set("prefix", "big" + stringify(i), big); | |
173 | db->submit_transaction_sync(t); | |
174 | } | |
175 | } | |
176 | cout << "now doing small writes" << std::endl; | |
177 | bufferlist data; | |
178 | bufferptr bp(1024); | |
179 | bp.zero(); | |
180 | data.append(bp); | |
181 | for (int i=0; i<n; ++i) { | |
182 | KeyValueDB::Transaction t = db->get_transaction(); | |
183 | t->set("prefix", "key" + stringify(i), data); | |
184 | db->submit_transaction_sync(t); | |
185 | } | |
186 | utime_t end = ceph_clock_now(); | |
187 | utime_t dur = end - start; | |
188 | cout << n << " commits in " << dur << ", avg latency " << (dur / (double)n) | |
189 | << std::endl; | |
190 | fini(); | |
191 | } | |
192 | ||
193 | struct AppendMOP : public KeyValueDB::MergeOperator { | |
194 | void merge_nonexistent( | |
195 | const char *rdata, size_t rlen, std::string *new_value) override { | |
196 | *new_value = "?" + std::string(rdata, rlen); | |
197 | } | |
198 | void merge( | |
199 | const char *ldata, size_t llen, | |
200 | const char *rdata, size_t rlen, | |
201 | std::string *new_value) override { | |
202 | ||
203 | *new_value = std::string(ldata, llen) + std::string(rdata, rlen); | |
204 | } | |
205 | // We use each operator name and each prefix to construct the | |
206 | // overall RocksDB operator name for consistency check at open time. | |
91327a77 | 207 | const char *name() const override { |
7c673cae FG |
208 | return "Append"; |
209 | } | |
210 | }; | |
211 | ||
212 | string tostr(bufferlist& b) { | |
213 | return string(b.c_str(),b.length()); | |
214 | } | |
215 | ||
216 | TEST_P(KVTest, Merge) { | |
217 | shared_ptr<KeyValueDB::MergeOperator> p(new AppendMOP); | |
218 | int r = db->set_merge_operator("A",p); | |
219 | if (r < 0) | |
220 | return; // No merge operators for this database type | |
221 | ASSERT_EQ(0, db->create_and_open(cout)); | |
222 | { | |
223 | KeyValueDB::Transaction t = db->get_transaction(); | |
224 | bufferlist v1, v2, v3; | |
225 | v1.append(string("1")); | |
226 | v2.append(string("2")); | |
227 | v3.append(string("3")); | |
228 | t->set("P", "K1", v1); | |
229 | t->set("A", "A1", v2); | |
230 | t->rmkey("A", "A2"); | |
231 | t->merge("A", "A2", v3); | |
232 | db->submit_transaction_sync(t); | |
233 | } | |
234 | { | |
235 | bufferlist v1, v2, v3; | |
236 | ASSERT_EQ(0, db->get("P", "K1", &v1)); | |
237 | ASSERT_EQ(tostr(v1), "1"); | |
238 | ASSERT_EQ(0, db->get("A", "A1", &v2)); | |
239 | ASSERT_EQ(tostr(v2), "2"); | |
240 | ASSERT_EQ(0, db->get("A", "A2", &v3)); | |
241 | ASSERT_EQ(tostr(v3), "?3"); | |
242 | } | |
243 | { | |
244 | KeyValueDB::Transaction t = db->get_transaction(); | |
245 | bufferlist v1; | |
246 | v1.append(string("1")); | |
247 | t->merge("A", "A2", v1); | |
248 | db->submit_transaction_sync(t); | |
249 | } | |
250 | { | |
251 | bufferlist v; | |
252 | ASSERT_EQ(0, db->get("A", "A2", &v)); | |
253 | ASSERT_EQ(tostr(v), "?31"); | |
254 | } | |
255 | fini(); | |
256 | } | |
257 | ||
258 | TEST_P(KVTest, RMRange) { | |
259 | ASSERT_EQ(0, db->create_and_open(cout)); | |
260 | bufferlist value; | |
261 | value.append("value"); | |
262 | { | |
263 | KeyValueDB::Transaction t = db->get_transaction(); | |
264 | t->set("prefix", "key1", value); | |
265 | t->set("prefix", "key2", value); | |
266 | t->set("prefix", "key3", value); | |
267 | t->set("prefix", "key4", value); | |
268 | t->set("prefix", "key45", value); | |
269 | t->set("prefix", "key5", value); | |
270 | t->set("prefix", "key6", value); | |
271 | db->submit_transaction_sync(t); | |
272 | } | |
273 | ||
274 | { | |
275 | KeyValueDB::Transaction t = db->get_transaction(); | |
276 | t->set("prefix", "key7", value); | |
277 | t->set("prefix", "key8", value); | |
278 | t->rm_range_keys("prefix", "key2", "key7"); | |
279 | db->submit_transaction_sync(t); | |
280 | bufferlist v1, v2; | |
281 | ASSERT_EQ(0, db->get("prefix", "key1", &v1)); | |
282 | v1.clear(); | |
283 | ASSERT_EQ(-ENOENT, db->get("prefix", "key45", &v1)); | |
284 | ASSERT_EQ(0, db->get("prefix", "key8", &v1)); | |
285 | v1.clear(); | |
286 | ASSERT_EQ(-ENOENT, db->get("prefix", "key2", &v1)); | |
287 | ASSERT_EQ(0, db->get("prefix", "key7", &v2)); | |
288 | } | |
289 | ||
290 | { | |
291 | KeyValueDB::Transaction t = db->get_transaction(); | |
292 | t->rm_range_keys("prefix", "key", "key"); | |
293 | db->submit_transaction_sync(t); | |
294 | bufferlist v1, v2; | |
295 | ASSERT_EQ(0, db->get("prefix", "key1", &v1)); | |
296 | ASSERT_EQ(0, db->get("prefix", "key8", &v2)); | |
297 | } | |
298 | ||
299 | { | |
300 | KeyValueDB::Transaction t = db->get_transaction(); | |
301 | t->rm_range_keys("prefix", "key-", "key~"); | |
302 | db->submit_transaction_sync(t); | |
303 | bufferlist v1, v2; | |
304 | ASSERT_EQ(-ENOENT, db->get("prefix", "key1", &v1)); | |
305 | ASSERT_EQ(-ENOENT, db->get("prefix", "key8", &v2)); | |
306 | } | |
307 | ||
308 | fini(); | |
309 | } | |
310 | ||
311 | ||
312 | INSTANTIATE_TEST_CASE_P( | |
313 | KeyValueDB, | |
314 | KVTest, | |
315 | ::testing::Values("leveldb", "rocksdb", "memdb")); | |
316 | ||
317 | #else | |
318 | ||
319 | // Google Test may not support value-parameterized tests with some | |
320 | // compilers. If we use conditional compilation to compile out all | |
321 | // code referring to the gtest_main library, MSVC linker will not link | |
322 | // that library at all and consequently complain about missing entry | |
323 | // point defined in that library (fatal error LNK1561: entry point | |
324 | // must be defined). This dummy test keeps gtest_main linked in. | |
325 | TEST(DummyTest, ValueParameterizedTestsAreNotSupportedOnThisPlatform) {} | |
326 | ||
327 | #endif | |
328 | ||
329 | int main(int argc, char **argv) { | |
330 | vector<const char*> args; | |
331 | argv_to_vec(argc, (const char **)argv, args); | |
332 | env_to_vec(args); | |
333 | ||
334 | auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, | |
335 | CODE_ENVIRONMENT_UTILITY, 0); | |
336 | common_init_finish(g_ceph_context); | |
337 | g_ceph_context->_conf->set_val( | |
338 | "enable_experimental_unrecoverable_data_corrupting_features", | |
339 | "rocksdb, memdb"); | |
340 | g_ceph_context->_conf->apply_changes(NULL); | |
341 | ||
342 | ::testing::InitGoogleTest(&argc, argv); | |
343 | return RUN_ALL_TESTS(); | |
344 | } |