1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2015 Couchbase, Inc
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 
18 #include "ep_testsuite_common.h"
19 #include "ep_test_apis.h"
20 
21 #include <cstring>
22 #include <iostream>
23 #include <string>
24 #include <sstream>
25 
26 #include <sys/stat.h>
27 
28 #include <platform/cb_malloc.h>
29 #include <platform/compress.h>
30 #include <platform/dirutils.h>
31 
32 const char *dbname_env = NULL;
33 
34 static enum test_result skipped_test_function(ENGINE_HANDLE *h,
35                                               ENGINE_HANDLE_V1 *h1);
36 
BaseTestCase(const char * _name,const char * _cfg,bool _skip)37 BaseTestCase::BaseTestCase(const char *_name, const char *_cfg, bool _skip)
38   : name(_name),
39     cfg(_cfg),
40     skip(_skip) {
41 }
42 
BaseTestCase(const BaseTestCase & o)43 BaseTestCase::BaseTestCase(const BaseTestCase &o)
44   : name(o.name),
45     cfg(o.cfg),
46     skip(o.skip) {
47 
48     memset(&test, 0, sizeof(test));
49     test = o.test;
50 }
51 
TestCase(const char * _name,enum test_result (* _tfun)(ENGINE_HANDLE *,ENGINE_HANDLE_V1 *),bool (* _test_setup)(ENGINE_HANDLE *,ENGINE_HANDLE_V1 *),bool (* _test_teardown)(ENGINE_HANDLE *,ENGINE_HANDLE_V1 *),const char * _cfg,enum test_result (* _prepare)(engine_test_t * test),void (* _cleanup)(engine_test_t * test,enum test_result result),bool _skip)52 TestCase::TestCase(const char *_name,
53                    enum test_result(*_tfun)(ENGINE_HANDLE *, ENGINE_HANDLE_V1 *),
54                    bool(*_test_setup)(ENGINE_HANDLE *, ENGINE_HANDLE_V1 *),
55                    bool(*_test_teardown)(ENGINE_HANDLE *, ENGINE_HANDLE_V1 *),
56                    const char *_cfg,
57                    enum test_result (*_prepare)(engine_test_t *test),
58                    void (*_cleanup)(engine_test_t *test, enum test_result result),
59                    bool _skip)
60   : BaseTestCase(_name, _cfg, _skip) {
61 
62     memset(&test, 0, sizeof(test));
63     test.tfun = _tfun;
64     test.test_setup = _test_setup;
65     test.test_teardown = _test_teardown;
66     test.prepare = _prepare;
67     test.cleanup = _cleanup;
68 }
69 
TestCaseV2(const char * _name,enum test_result (* _tfun)(engine_test_t *),bool (* _test_setup)(engine_test_t *),bool (* _test_teardown)(engine_test_t *),const char * _cfg,enum test_result (* _prepare)(engine_test_t * test),void (* _cleanup)(engine_test_t * test,enum test_result result),bool _skip)70 TestCaseV2::TestCaseV2(const char *_name,
71                        enum test_result(*_tfun)(engine_test_t *),
72                        bool(*_test_setup)(engine_test_t *),
73                        bool(*_test_teardown)(engine_test_t *),
74                        const char *_cfg,
75                        enum test_result (*_prepare)(engine_test_t *test),
76                        void (*_cleanup)(engine_test_t *test, enum test_result result),
77                        bool _skip)
78   : BaseTestCase(_name, _cfg, _skip) {
79 
80     memset(&test, 0, sizeof(test));
81     test.api_v2.tfun = _tfun;
82     test.api_v2.test_setup = _test_setup;
83     test.api_v2.test_teardown = _test_teardown;
84     test.prepare = _prepare;
85     test.cleanup = _cleanup;
86 }
87 
getTest()88 engine_test_t* BaseTestCase::getTest() {
89     engine_test_t *ret = &test;
90 
91     std::string nm(name);
92     std::stringstream ss;
93 
94     if (cfg != 0) {
95         ss << cfg << ";";
96     } else {
97         ss << ";";
98     }
99 
100     // Default to the suite's dbname if the test config didn't already
101     // specify it.
102     if ((cfg == nullptr) ||
103         (std::string(cfg).find("dbname=") == std::string::npos)) {
104         ss << "dbname=" << default_dbname << ";";
105     }
106 
107     if (skip) {
108         nm.append(" (skipped)");
109         ret->tfun = skipped_test_function;
110     } else {
111         nm.append(" (couchstore)");
112     }
113 
114     ret->name = cb_strdup(nm.c_str());
115     std::string config = ss.str();
116     if (config.length() == 0) {
117         ret->cfg = 0;
118     } else {
119         ret->cfg = cb_strdup(config.c_str());
120     }
121 
122     return ret;
123 }
124 
125 
skipped_test_function(ENGINE_HANDLE * h,ENGINE_HANDLE_V1 * h1)126 static enum test_result skipped_test_function(ENGINE_HANDLE *h,
127                                               ENGINE_HANDLE_V1 *h1) {
128     (void) h;
129     (void) h1;
130     return SKIPPED;
131 }
132 
rmdb(const char * path)133 enum test_result rmdb(const char* path) {
134     try {
135         cb::io::rmrf(path);
136     } catch (std::system_error& e) {
137         throw e;
138     }
139     if (access(path, F_OK) != -1) {
140         std::cerr << "Failed to remove: " << path << " " << std::endl;
141         return FAIL;
142     }
143     return SUCCESS;
144 }
145 
test_setup(ENGINE_HANDLE * h,ENGINE_HANDLE_V1 * h1)146 bool test_setup(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
147     wait_for_warmup_complete(h, h1);
148 
149     check(set_vbucket_state(h, h1, 0, vbucket_state_active),
150           "Failed to set VB0 state.");
151 
152     const auto bucket_type = get_str_stat(h, h1, "ep_bucket_type");
153     if (bucket_type == "persistent") {
154         // Wait for vb0's state (active) to be persisted to disk, that way
155         // we know the KVStore files exist on disk.
156         wait_for_stat_to_be_gte(h, h1, "ep_persist_vbstate_total", 1);
157     } else if (bucket_type == "ephemeral") {
158         // No persistence to wait for here.
159     } else {
160         check(false,
161               (std::string("test_setup: unknown bucket_type '") + bucket_type +
162                "' - cannot continue.")
163                       .c_str());
164         return false;
165     }
166 
167     // warmup is complete, notify ep engine that it must now enable
168     // data traffic
169     protocol_binary_request_header *pkt = createPacket(PROTOCOL_BINARY_CMD_ENABLE_TRAFFIC);
170     check(h1->unknown_command(h, NULL, pkt, add_response, testHarness.doc_namespace) == ENGINE_SUCCESS,
171           "Failed to enable data traffic");
172     cb_free(pkt);
173 
174     return true;
175 }
176 
teardown(ENGINE_HANDLE * h,ENGINE_HANDLE_V1 * h1)177 bool teardown(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
178     (void)h; (void)h1;
179     vals.clear();
180     return true;
181 }
182 
teardown_v2(engine_test_t * test)183 bool teardown_v2(engine_test_t* test) {
184     (void)test;
185     vals.clear();
186     return true;
187 }
188 
get_dbname(const char * test_cfg)189 std::string get_dbname(const char* test_cfg) {
190     std::string dbname;
191 
192     if (!test_cfg) {
193         dbname.assign(dbname_env);
194         return dbname;
195     }
196 
197     const char *nm = strstr(test_cfg, "dbname=");
198     if (nm == NULL) {
199         dbname.assign(dbname_env);
200     } else {
201         dbname.assign(nm + 7);
202         std::string::size_type end = dbname.find(';');
203         if (end != dbname.npos) {
204             dbname = dbname.substr(0, end);
205         }
206     }
207     return dbname;
208 }
209 
prepare(engine_test_t * test)210 enum test_result prepare(engine_test_t *test) {
211 #ifdef __sun
212         // Some of the tests doesn't work on Solaris.. Don't know why yet..
213         if (strstr(test->name, "concurrent set") != NULL ||
214             strstr(test->name, "retain rowid over a soft delete") != NULL)
215         {
216             return SKIPPED;
217         }
218 #endif
219 
220     std::string dbname = get_dbname(test->cfg);
221     /* Remove if the same DB directory already exists */
222     try {
223         rmdb(dbname.c_str());
224     } catch (std::system_error& e) {
225         if (e.code() != std::error_code(ENOENT, std::system_category())) {
226             throw e;
227         }
228     }
229     cb::io::mkdirp(dbname);
230     return SUCCESS;
231 }
232 
prepare_ep_bucket(engine_test_t * test)233 enum test_result prepare_ep_bucket(engine_test_t* test) {
234     std::string cfg{test->cfg};
235     if (cfg.find("bucket_type=ephemeral") != std::string::npos) {
236         return SKIPPED;
237     }
238 
239     // Perform whatever prep the "base class" function wants.
240     return prepare(test);
241 }
242 
prepare_ep_bucket_skip_broken_under_rocks(engine_test_t * test)243 enum test_result prepare_ep_bucket_skip_broken_under_rocks(engine_test_t* test) {
244     std::string cfg{test->cfg};
245     if (cfg.find("backend=rocksdb") != std::string::npos) {
246         return SKIPPED_UNDER_ROCKSDB;
247     }
248 
249     // Perform whatever prep the ep bucket function wants.
250     return prepare_ep_bucket(test);
251 }
252 
prepare_skip_broken_under_rocks(engine_test_t * test)253 enum test_result prepare_skip_broken_under_rocks(engine_test_t* test) {
254     std::string cfg{test->cfg};
255     if (cfg.find("backend=rocksdb") != std::string::npos) {
256         return SKIPPED_UNDER_ROCKSDB;
257     }
258 
259     // Perform whatever prep the "base class" function wants.
260     return prepare(test);
261 }
262 
prepare_skip_broken_under_ephemeral_and_rocks(engine_test_t * test)263 enum test_result prepare_skip_broken_under_ephemeral_and_rocks(
264         engine_test_t* test) {
265     return prepare_ep_bucket_skip_broken_under_rocks(test);
266 }
267 
prepare_ephemeral_bucket(engine_test_t * test)268 enum test_result prepare_ephemeral_bucket(engine_test_t* test) {
269     std::string cfg{test->cfg};
270     if (cfg.find("bucket_type=ephemeral") == std::string::npos) {
271         return SKIPPED;
272     }
273 
274     // Perform whatever prep the "base class" function wants.
275     return prepare(test);
276 }
277 
prepare_full_eviction(engine_test_t * test)278 enum test_result prepare_full_eviction(engine_test_t *test) {
279 
280     // If we cannot find FE in the config, skip the test
281     if (std::string(test->cfg).find("item_eviction_policy=full_eviction") ==
282         std::string::npos) {
283         return SKIPPED;
284     }
285 
286     // Ephemeral buckets don't support full eviction.
287     if (std::string(test->cfg).find("bucket_type=ephemeral")
288             != std::string::npos) {
289         return SKIPPED;
290     }
291 
292     // Perform whatever prep the "base class" function wants.
293     return prepare(test);
294 }
295 
prepare_full_eviction_skip_under_rocks(engine_test_t * test)296 enum test_result prepare_full_eviction_skip_under_rocks(engine_test_t *test) {
297 
298     std::string cfg{test->cfg};
299     if (cfg.find("backend=rocksdb") != std::string::npos) {
300         return SKIPPED_UNDER_ROCKSDB;
301     }
302 
303     // Perform whatever prep the "base class" function wants.
304     return prepare_full_eviction(test);
305 
306 }
307 
prepare_skip_broken_under_ephemeral(engine_test_t * test)308 enum test_result prepare_skip_broken_under_ephemeral(engine_test_t *test) {
309     return prepare_ep_bucket(test);
310 }
311 
cleanup(engine_test_t * test,enum test_result result)312 void cleanup(engine_test_t *test, enum test_result result) {
313     (void)result;
314     // Nuke the database files we created
315     std::string dbname = get_dbname(test->cfg);
316     /* Remove only the db file this test created */
317     try {
318         rmdb(dbname.c_str());
319     } catch (std::system_error& e) {
320         if (e.code() != std::error_code(ENOENT, std::system_category())) {
321             throw e;
322         }
323     }
324 }
325 
326 // Array of testcases to return back to engine_testapp.
327 static engine_test_t *testcases;
328 
329 // Should only one test be run, and if so which number? If -1 then all tests
330 // are run.
331 static int oneTestIdx;
332 
333 struct test_harness testHarness;
334 
335 // Array of testcases. Provided by the specific testsuite.
336 extern BaseTestCase testsuite_testcases[];
337 
338 // Examines the list of tests provided by the specific testsuite
339 // via the testsuite_testcases[] array, populates `testcases` and returns it.
340 MEMCACHED_PUBLIC_API
get_tests(void)341 engine_test_t* get_tests(void) {
342 
343     // Calculate the size of the tests..
344     int num = 0;
345     while (testsuite_testcases[num].getName() != NULL) {
346         ++num;
347     }
348 
349     oneTestIdx = -1;
350     char *testNum = getenv("EP_TEST_NUM");
351     if (testNum) {
352         sscanf(testNum, "%d", &oneTestIdx);
353         if (oneTestIdx < 0 || oneTestIdx > num) {
354             oneTestIdx = -1;
355         }
356     }
357     dbname_env = getenv("EP_TEST_DIR");
358     if (!dbname_env) {
359         dbname_env = default_dbname;
360     }
361 
362     if (oneTestIdx == -1) {
363         testcases = static_cast<engine_test_t*>(cb_calloc(num + 1, sizeof(engine_test_t)));
364 
365         int ii = 0;
366         for (int jj = 0; jj < num; ++jj) {
367             engine_test_t *r = testsuite_testcases[jj].getTest();
368             if (r != 0) {
369                 testcases[ii++] = *r;
370             }
371         }
372     } else {
373         testcases = static_cast<engine_test_t*>(cb_calloc(1 + 1, sizeof(engine_test_t)));
374 
375         engine_test_t *r = testsuite_testcases[oneTestIdx].getTest();
376         if (r != 0) {
377             testcases[0] = *r;
378         }
379     }
380 
381     return testcases;
382 }
383 
384 MEMCACHED_PUBLIC_API
setup_suite(struct test_harness * th)385 bool setup_suite(struct test_harness *th) {
386     testHarness = *th;
387     return true;
388 }
389 
390 
391 MEMCACHED_PUBLIC_API
teardown_suite()392 bool teardown_suite() {
393     for (int i = 0; testcases[i].name != nullptr; i++) {
394         cb_free((char*)testcases[i].name);
395         cb_free((char*)testcases[i].cfg);
396     }
397     cb_free(testcases);
398     testcases = NULL;
399     return true;
400 }
401 
402 /*
403  * Create n_buckets and return how many were actually created.
404  */
create_buckets(const char * cfg,int n_buckets,std::vector<BucketHolder> & buckets)405 int create_buckets(const char* cfg, int n_buckets, std::vector<BucketHolder> &buckets) {
406     std::string dbname = get_dbname(cfg);
407 
408     for (int ii = 0; ii < n_buckets; ii++) {
409         std::stringstream config, dbpath;
410         dbpath << dbname.c_str() << ii;
411         std::string str_cfg(cfg);
412         /* Find the position of "dbname=" in str_cfg */
413         size_t pos = str_cfg.find("dbname=");
414         if (pos != std::string::npos) {
415             /* Move till end of the dbname */
416             size_t new_pos = str_cfg.find(';', pos);
417             str_cfg.insert(new_pos, std::to_string(ii));
418             config << str_cfg;
419         } else {
420             config << str_cfg << "dbname=" << dbpath.str();
421         }
422 
423         try {
424             rmdb(dbpath.str().c_str());
425         } catch (std::system_error& e) {
426             if (e.code() != std::error_code(ENOENT, std::system_category())) {
427                 throw e;
428             }
429         }
430         ENGINE_HANDLE_V1* handle = testHarness.create_bucket(true, config.str().c_str());
431         if (handle) {
432             buckets.push_back(BucketHolder((ENGINE_HANDLE*)handle, handle, dbpath.str()));
433         } else {
434             return ii;
435         }
436     }
437     return n_buckets;
438 }
439 
destroy_buckets(std::vector<BucketHolder> & buckets)440 void destroy_buckets(std::vector<BucketHolder> &buckets) {
441     for(auto bucket : buckets) {
442         testHarness.destroy_bucket(bucket.h, bucket.h1, false);
443         rmdb(bucket.dbpath.c_str());
444     }
445 }
446 
check_key_value(ENGINE_HANDLE * h,ENGINE_HANDLE_V1 * h1,const char * key,const char * val,size_t vlen,uint16_t vbucket)447 void check_key_value(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
448                      const char* key, const char* val, size_t vlen,
449                      uint16_t vbucket) {
450     // Fetch item itself, to ensure we maintain a ref-count on the underlying
451     // Blob while comparing the key.
452     auto getResult = get(h, h1, NULL, key, vbucket);
453     checkeq(cb::engine_errc::success,
454             getResult.first,
455             "Failed to fetch document");
456     item_info info;
457     check(h1->get_item_info(h, getResult.second.get(), &info),
458           "Failed to get_item_info");
459 
460     cb::const_char_buffer payload;
461     cb::compression::Buffer inflated;
462     if (isCompressionEnabled(h, h1) &&
463         (info.datatype & PROTOCOL_BINARY_DATATYPE_SNAPPY)) {
464         cb::compression::inflate(cb::compression::Algorithm::Snappy,
465                                  {static_cast<const char *>(info.value[0].iov_base),
466                                   info.value[0].iov_len},
467                                  inflated);
468         payload = inflated;
469     } else {
470         payload = {static_cast<const char *>(info.value[0].iov_base),
471                                              info.value[0].iov_len};
472     }
473 
474     checkeq(vlen, payload.size(), "Value length mismatch");
475     check(memcmp(payload.data(), val, vlen) == 0, "Data mismatch");
476 }
477 
isCompressionEnabled(ENGINE_HANDLE * h,ENGINE_HANDLE_V1 * h1)478 bool isCompressionEnabled(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
479     return (isPassiveCompressionEnabled(h, h1) ||
480             isActiveCompressionEnabled(h, h1));
481 }
482 
isActiveCompressionEnabled(ENGINE_HANDLE * h,ENGINE_HANDLE_V1 * h1)483 bool isActiveCompressionEnabled(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
484     return get_str_stat(h, h1, "ep_compression_mode") == "active";
485 }
486 
isPassiveCompressionEnabled(ENGINE_HANDLE * h,ENGINE_HANDLE_V1 * h1)487 bool isPassiveCompressionEnabled(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
488     return get_str_stat(h, h1, "ep_compression_mode") == "passive";
489 }
490 
isWarmupEnabled(ENGINE_HANDLE * h,ENGINE_HANDLE_V1 * h1)491 bool isWarmupEnabled(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
492     return get_bool_stat(h, h1, "ep_warmup");
493 }
494 
isPersistentBucket(ENGINE_HANDLE * h,ENGINE_HANDLE_V1 * h1)495 bool isPersistentBucket(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
496     return get_str_stat(h, h1, "ep_bucket_type") == "persistent";
497 }
498 
isEphemeralBucket(ENGINE_HANDLE * h,ENGINE_HANDLE_V1 * h1)499 bool isEphemeralBucket(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
500     return get_str_stat(h, h1, "ep_bucket_type") == "ephemeral";
501 }
502 
checkPersistentBucketTempItems(ENGINE_HANDLE * h,ENGINE_HANDLE_V1 * h1,int exp)503 void checkPersistentBucketTempItems(ENGINE_HANDLE* h,
504                                     ENGINE_HANDLE_V1* h1,
505                                     int exp) {
506     if (isPersistentBucket(h, h1)) {
507         checkeq(exp,
508                 get_int_stat(h, h1, "curr_temp_items"),
509                 "CheckPersistentBucketTempItems(): Num temp items not as "
510                 "expected");
511     }
512 }
513