1/* -*- MODE: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2016 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/*
19 * Testsuite for 'basic' key-value functionality in ep-engine.
20 */
21
22#include "ep_test_apis.h"
23#include "ep_testsuite_common.h"
24
25#include <platform/cb_malloc.h>
26#include <platform/cbassert.h>
27#include <platform/dirutils.h>
28#include <platform/platform_thread.h>
29
30#include <array>
31#include <memcached/types.h>
32
33#define WHITESPACE_DB "whitespace sucks.db"
34
35// Types //////////////////////////////////////////////////////////////////////
36
37
38// Helper functions ///////////////////////////////////////////////////////////
39
40static bool epsilon(int val, int target, int ep=5) {
41    return abs(val - target) < ep;
42}
43
44
45// Testcases //////////////////////////////////////////////////////////////////
46
47static enum test_result test_alloc_limit(EngineIface* h) {
48    auto rv = allocate(h,
49                       NULL,
50                       "key",
51                       20 * 1024 * 1024,
52                       0,
53                       0,
54                       PROTOCOL_BINARY_RAW_BYTES,
55                       Vbid(0));
56    checkeq(cb::engine_errc::success, rv.first, "Allocated 20MB item");
57
58    rv = allocate(h,
59                  NULL,
60                  "key",
61                  (20 * 1024 * 1024) + 1,
62                  0,
63                  0,
64                  PROTOCOL_BINARY_RAW_BYTES,
65                  Vbid(0));
66    checkeq(cb::engine_errc::too_big, rv.first, "Object too big");
67
68    return SUCCESS;
69}
70
71static enum test_result test_memory_tracking(EngineIface* h) {
72    // Need memory tracker to be able to check our memory usage.
73    std::string tracker = get_str_stat(h, "ep_mem_tracker_enabled");
74    if (tracker == "true") {
75        return SUCCESS;
76    } else {
77        std::cerr << "Memory tracker not enabled ...";
78        return SKIPPED;
79    }
80}
81
82static enum test_result test_max_size_and_water_marks_settings(EngineIface* h) {
83    checkeq(1000, get_int_stat(h, "ep_max_size"), "Incorrect initial size.");
84    check(epsilon(get_int_stat(h, "ep_mem_low_wat"), 750),
85          "Incorrect initial low wat.");
86    check(epsilon(get_int_stat(h, "ep_mem_high_wat"), 850),
87          "Incorrect initial high wat.");
88    checkeq(0.75f,
89            get_float_stat(h, "ep_mem_low_wat_percent"),
90            "Incorrect initial low wat. percent");
91    checkeq(0.85f,
92            get_float_stat(h, "ep_mem_high_wat_percent"),
93            "Incorrect initial high wat. percent");
94
95    set_param(h,
96              cb::mcbp::request::SetParamPayload::Type::Flush,
97              "max_size",
98              "1000000");
99
100    checkeq(1000000, get_int_stat(h, "ep_max_size"), "Incorrect new size.");
101    check(epsilon(get_int_stat(h, "ep_mem_low_wat"), 750000),
102          "Incorrect larger low wat.");
103    check(epsilon(get_int_stat(h, "ep_mem_high_wat"), 850000),
104          "Incorrect larger high wat.");
105    checkeq(0.75f,
106            get_float_stat(h, "ep_mem_low_wat_percent"),
107            "Incorrect larger low wat. percent");
108    checkeq(0.85f,
109            get_float_stat(h, "ep_mem_high_wat_percent"),
110            "Incorrect larger high wat. percent");
111
112    set_param(h,
113              cb::mcbp::request::SetParamPayload::Type::Flush,
114              "mem_low_wat",
115              "700000");
116    set_param(h,
117              cb::mcbp::request::SetParamPayload::Type::Flush,
118              "mem_high_wat",
119              "800000");
120
121    checkeq(700000,
122            get_int_stat(h, "ep_mem_low_wat"),
123            "Incorrect even larger low wat.");
124    checkeq(800000,
125            get_int_stat(h, "ep_mem_high_wat"),
126            "Incorrect even larger high wat.");
127    checkeq(0.7f,
128            get_float_stat(h, "ep_mem_low_wat_percent"),
129            "Incorrect even larger low wat. percent");
130    checkeq(0.8f,
131            get_float_stat(h, "ep_mem_high_wat_percent"),
132            "Incorrect even larger high wat. percent");
133
134    set_param(h,
135              cb::mcbp::request::SetParamPayload::Type::Flush,
136              "max_size",
137              "100");
138
139    checkeq(100, get_int_stat(h, "ep_max_size"), "Incorrect smaller size.");
140    check(epsilon(get_int_stat(h, "ep_mem_low_wat"), 70),
141          "Incorrect smaller low wat.");
142    check(epsilon(get_int_stat(h, "ep_mem_high_wat"), 80),
143          "Incorrect smaller high wat.");
144    checkeq(0.7f,
145            get_float_stat(h, "ep_mem_low_wat_percent"),
146            "Incorrect smaller low wat. percent");
147    checkeq(0.8f,
148            get_float_stat(h, "ep_mem_high_wat_percent"),
149            "Incorrect smaller high wat. percent");
150
151    set_param(h,
152              cb::mcbp::request::SetParamPayload::Type::Flush,
153              "mem_low_wat",
154              "50");
155    set_param(h,
156              cb::mcbp::request::SetParamPayload::Type::Flush,
157              "mem_high_wat",
158              "70");
159
160    checkeq(50,
161            get_int_stat(h, "ep_mem_low_wat"),
162            "Incorrect even smaller low wat.");
163    checkeq(70,
164            get_int_stat(h, "ep_mem_high_wat"),
165            "Incorrect even smaller high wat.");
166    checkeq(0.5f, get_float_stat(h, "ep_mem_low_wat_percent"),
167            "Incorrect even smaller low wat. percent");
168    checkeq(0.7f, get_float_stat(h, "ep_mem_high_wat_percent"),
169            "Incorrect even smaller high wat. percent");
170
171    testHarness->reload_engine(&h,
172                               testHarness->engine_path,
173                               testHarness->get_current_testcase()->cfg,
174                               true,
175                               true);
176
177    wait_for_warmup_complete(h);
178
179    checkeq(1000, get_int_stat(h, "ep_max_size"), "Incorrect initial size.");
180    check(epsilon(get_int_stat(h, "ep_mem_low_wat"), 750),
181          "Incorrect intial low wat.");
182    check(epsilon(get_int_stat(h, "ep_mem_high_wat"), 850),
183          "Incorrect initial high wat.");
184    checkeq(0.75f, get_float_stat(h, "ep_mem_low_wat_percent"),
185            "Incorrect initial low wat. percent");
186    checkeq(0.85f, get_float_stat(h, "ep_mem_high_wat_percent"),
187            "Incorrect initial high wat. percent");
188
189    return SUCCESS;
190}
191
192static enum test_result test_whitespace_db(EngineIface* h) {
193    vals.clear();
194
195    checkeq(ENGINE_SUCCESS,
196            get_stats(h, {}, {}, add_stats),
197            "Failed to get stats.");
198
199    std::string dbname;
200    std::string policy;
201    policy = isPersistentBucket(h)
202                     ? vals.find("ep_item_eviction_policy")->second
203                     : "ephemeral";
204    dbname.assign(policy + std::string(WHITESPACE_DB));
205
206    std::string oldparam("dbname=" + vals["ep_dbname"]);
207    std::string newparam("dbname=" + dbname);
208    std::string config = testHarness->get_current_testcase()->cfg;
209    std::string::size_type found = config.find(oldparam);
210    if (found != config.npos) {
211        config.replace(found, oldparam.size(), newparam);
212    }
213    testHarness->reload_engine(
214            &h, testHarness->engine_path, config.c_str(), true, false);
215    wait_for_warmup_complete(h);
216
217    vals.clear();
218    checkeq(ENGINE_SUCCESS,
219            get_stats(h, {}, {}, add_stats),
220            "Failed to get stats.");
221
222    if (vals["ep_dbname"] != dbname) {
223        std::cerr << "Expected dbname = '" << dbname << "'"
224                  << ", got '" << vals["ep_dbname"] << "'" << std::endl;
225        return FAIL;
226    }
227
228    check(cb::io::isDirectory(dbname), "I expected the whitespace db to exist");
229    return SUCCESS;
230}
231
232static enum test_result test_get_miss(EngineIface* h) {
233    checkeq(ENGINE_KEY_ENOENT, verify_key(h, "k"), "Expected miss.");
234    return SUCCESS;
235}
236
237static enum test_result test_set(EngineIface* h) {
238    item_info info;
239    uint64_t vb_uuid = 0, high_seqno = 0;
240    const int num_sets = 5, num_keys = 4;
241
242    std::string key_arr[num_keys] = { "dummy_key",
243                                      "checkpoint_start",
244                                      "checkpoint_end",
245                                      "key" };
246
247
248    for (int k = 0; k < num_keys; k++) {
249        for (int j = 0; j < num_sets; j++) {
250            memset(&info, 0, sizeof(info));
251            vb_uuid = get_ull_stat(h, "vb_0:0:id", "failovers");
252            high_seqno = get_ull_stat(h, "vb_0:high_seqno", "vbucket-seqno");
253
254            std::string err_str_store("Error setting " + key_arr[k]);
255            checkeq(ENGINE_SUCCESS,
256                    store(h,
257                          NULL,
258                          OPERATION_SET,
259                          key_arr[k].c_str(),
260                          "somevalue"),
261                    err_str_store.c_str());
262
263            std::string err_str_get_item_info("Error getting " + key_arr[k]);
264            checkeq(true,
265                    get_item_info(h, &info, key_arr[k].c_str()),
266                    err_str_get_item_info.c_str());
267
268            std::string err_str_vb_uuid("Expected valid vbucket uuid for " +
269                                        key_arr[k]);
270            checkeq(vb_uuid, info.vbucket_uuid, err_str_vb_uuid.c_str());
271
272            std::string err_str_seqno("Expected valid sequence number for " +
273                                        key_arr[k]);
274            checkeq(high_seqno + 1, info.seqno, err_str_seqno.c_str());
275        }
276    }
277
278    if (isPersistentBucket(h)) {
279        wait_for_flusher_to_settle(h);
280
281        std::stringstream error1, error2;
282        error1 << "Expected ep_total_persisted >= num_keys (" << num_keys << ")";
283        error2 << "Expected ep_total_persisted <= num_sets*num_keys ("
284               << num_sets*num_keys << ")";
285
286        // The flusher could of ran > 1 times. We can only assert
287        // that we persisted between num_keys and upto num_keys*num_sets
288        checkle(num_keys, get_int_stat(h, "ep_total_persisted"),
289                error1.str().c_str());
290        checkge((num_sets * num_keys), get_int_stat(h, "ep_total_persisted"),
291                error2.str().c_str());
292    }
293    return SUCCESS;
294}
295
296extern "C" {
297    static void conc_del_set_thread(void *arg) {
298        auto* h = static_cast<EngineIface*>(arg);
299
300        for (int i = 0; i < 5000; ++i) {
301            store(h, NULL, OPERATION_ADD, "key", "somevalue");
302            checkeq(ENGINE_SUCCESS,
303                    store(h, nullptr, OPERATION_SET, "key", "somevalue"),
304                    "Error setting.");
305            // Ignoring the result here -- we're racing.
306            del(h, "key", 0, Vbid(0));
307        }
308    }
309}
310
311static enum test_result test_conc_set(EngineIface* h) {
312    const int n_threads = 8;
313    cb_thread_t threads[n_threads];
314
315    wait_for_persisted_value(h, "key", "value1");
316
317    for (int i = 0; i < n_threads; i++) {
318        int r = cb_create_thread(&threads[i], conc_del_set_thread, h, 0);
319        cb_assert(r == 0);
320    }
321
322    for (int i = 0; i < n_threads; i++) {
323        int r = cb_join_thread(threads[i]);
324        cb_assert(r == 0);
325    }
326
327    if (isWarmupEnabled(h)) {
328        wait_for_flusher_to_settle(h);
329
330        testHarness->reload_engine(&h,
331                                   testHarness->engine_path,
332                                   testHarness->get_current_testcase()->cfg,
333                                   true,
334                                   false);
335        wait_for_warmup_complete(h);
336
337        cb_assert(0 == get_int_stat(h, "ep_warmup_dups"));
338    }
339
340    return SUCCESS;
341}
342
343struct multi_set_args {
344    EngineIface* h;
345    std::string prefix;
346    int count;
347};
348
349extern "C" {
350    static void multi_set_thread(void *arg) {
351        struct multi_set_args *msa = static_cast<multi_set_args *>(arg);
352
353        for (int i = 0; i < msa->count; i++) {
354            std::stringstream s;
355            s << msa->prefix << i;
356            std::string key(s.str());
357            checkeq(ENGINE_SUCCESS,
358                    store(msa->h,
359                          NULL,
360                          OPERATION_SET,
361                          key.c_str(),
362                          "somevalue"),
363                    "Set failure!");
364        }
365    }
366}
367
368static enum test_result test_multi_set(EngineIface* h) {
369    cb_thread_t thread1, thread2;
370    struct multi_set_args msa1, msa2;
371    msa1.h = h;
372    msa1.prefix = "ONE_";
373    msa1.count = 50000;
374    cb_assert(cb_create_thread(&thread1, multi_set_thread, &msa1, 0) == 0);
375
376    msa2.h = h;
377    msa2.prefix = "TWO_";
378    msa2.count = 50000;
379    cb_assert(cb_create_thread(&thread2, multi_set_thread, &msa2, 0) == 0);
380
381    cb_assert(cb_join_thread(thread1) == 0);
382    cb_assert(cb_join_thread(thread2) == 0);
383
384    wait_for_flusher_to_settle(h);
385
386    checkeq(100000,
387            get_int_stat(h, "curr_items"),
388            "Mismatch in number of items inserted");
389    checkeq(100000,
390            get_int_stat(h, "vb_0:high_seqno", "vbucket-seqno"),
391            "Unexpected high sequence number");
392
393    return SUCCESS;
394}
395
396static enum test_result test_set_get_hit(EngineIface* h) {
397    checkeq(ENGINE_SUCCESS,
398            store(h, NULL, OPERATION_SET, "key", "somevalue"),
399            "store failure");
400    check_key_value(h, "key", "somevalue", 9);
401    return SUCCESS;
402}
403
404static enum test_result test_getl_delete_with_cas(EngineIface* h) {
405    checkeq(ENGINE_SUCCESS,
406            store(h, NULL, OPERATION_SET, "key", "value"),
407            "Failed to set key");
408
409    auto ret = getl(h, nullptr, "key", Vbid(0), 15);
410    checkeq(cb::engine_errc::success,
411            ret.first,
412            "Expected getl to succeed on key");
413    item_info info;
414    check(h->get_item_info(ret.second.get(), &info), "Failed to get item info");
415
416    checkeq(ENGINE_SUCCESS,
417            del(h, "key", info.cas, Vbid(0)),
418            "Expected SUCCESS");
419
420    return SUCCESS;
421}
422
423static enum test_result test_getl_delete_with_bad_cas(EngineIface* h) {
424    checkeq(ENGINE_SUCCESS,
425            store(h, NULL, OPERATION_SET, "key", "value"),
426            "Failed to set key");
427
428    uint64_t cas = last_cas;
429    checkeq(cb::engine_errc::success,
430            getl(h, nullptr, "key", Vbid(0), 15).first,
431            "Expected getl to succeed on key");
432
433    checkeq(ENGINE_LOCKED_TMPFAIL,
434            del(h, "key", cas, Vbid(0)),
435            "Expected TMPFAIL");
436
437    return SUCCESS;
438}
439
440static enum test_result test_getl_set_del_with_meta(EngineIface* h) {
441    const char *key = "key";
442    const char *val = "value";
443    const char *newval = "newvalue";
444    checkeq(ENGINE_SUCCESS,
445            store(h, NULL, OPERATION_SET, key, val),
446            "Failed to set key");
447
448    checkeq(cb::engine_errc::success,
449            getl(h, nullptr, key, Vbid(0), 15).first,
450            "Expected getl to succeed on key");
451
452    cb::EngineErrorMetadataPair errorMetaPair;
453    check(get_meta(h, key, errorMetaPair), "Expected to get meta");
454
455    //init some random metadata
456    ItemMetaData itm_meta(0xdeadbeef, 10, 0xdeadbeef, time(NULL) + 300);
457
458    //do a set with meta
459    checkeq(ENGINE_LOCKED,
460            set_with_meta(h,
461                          key,
462                          strlen(key),
463                          newval,
464                          strlen(newval),
465                          Vbid(0),
466                          &itm_meta,
467                          errorMetaPair.second.cas),
468            "Expected item to be locked");
469
470    //do a del with meta
471    checkeq(ENGINE_LOCKED_TMPFAIL,
472            del_with_meta(h, key, strlen(key), Vbid(0), &itm_meta, last_cas),
473            "Expected item to be locked");
474    return SUCCESS;
475}
476
477static enum test_result test_getl(EngineIface* h) {
478    const char *key = "k1";
479    Vbid vbucketId = Vbid(0);
480    uint32_t expiration = 25;
481
482    const void* cookie = testHarness->create_cookie();
483
484    checkeq(cb::engine_errc::no_such_key,
485            getl(h, cookie, key, vbucketId, expiration).first,
486            "expected the key to be missing...");
487
488    checkeq(ENGINE_SUCCESS,
489            store(h,
490                  cookie,
491                  OPERATION_SET,
492                  key,
493                  "{\"lock\":\"data\"}",
494                  nullptr,
495                  0,
496                  vbucketId,
497                  3600,
498                  PROTOCOL_BINARY_DATATYPE_JSON),
499            "Failed to store an item.");
500
501    /* retry getl, should succeed */
502    auto ret = getl(h, cookie, key, vbucketId, expiration);
503    checkeq(cb::engine_errc::success,
504            ret.first,
505            "Expected to be able to getl on first try");
506
507    item_info info;
508    check(h->get_item_info(ret.second.get(), &info), "Failed to get item info");
509
510    checkeq(std::string{"{\"lock\":\"data\"}"},
511            std::string((const char*)info.value[0].iov_base,
512                        info.value[0].iov_len),
513            "Body was malformed.");
514    checkeq(static_cast<uint8_t>(PROTOCOL_BINARY_DATATYPE_JSON),
515            info.datatype,
516            "Expected datatype to be JSON");
517
518    /* wait 16 seconds */
519    testHarness->time_travel(16);
520
521    /* lock's taken so this should fail */
522    checkeq(cb::engine_errc::locked_tmpfail,
523            getl(h, cookie, key, vbucketId, expiration).first,
524            "Expected to fail getl on second try");
525
526    checkne(ENGINE_SUCCESS,
527            store(h,
528                  cookie,
529                  OPERATION_SET,
530                  key,
531                  "lockdata2",
532                  nullptr,
533                  0,
534                  vbucketId),
535            "Should have failed to store an item.");
536
537    /* wait another 10 seconds */
538    testHarness->time_travel(10);
539
540    /* retry set, should succeed */
541    checkeq(ENGINE_SUCCESS,
542            store(h,
543                  cookie,
544                  OPERATION_SET,
545                  key,
546                  "lockdata",
547                  nullptr,
548                  0,
549                  vbucketId),
550            "Failed to store an item.");
551
552    /* point to wrong vbucket, to test NOT_MY_VB response */
553    checkeq(cb::engine_errc::not_my_vbucket,
554            getl(h, cookie, key, Vbid(10), expiration).first,
555            "Should have received not my vbucket response");
556
557    /* acquire lock, should succeed */
558    ret = getl(h, cookie, key, vbucketId, expiration);
559    checkeq(cb::engine_errc::success,
560            ret.first,
561            "Acquire lock should have succeeded");
562    check(h->get_item_info(ret.second.get(), &info), "Failed to get item info");
563    checkeq(static_cast<uint8_t>(PROTOCOL_BINARY_RAW_BYTES), info.datatype,
564            "Expected datatype to be RAW BYTES");
565
566    /* try an delete operation which should fail */
567    uint64_t cas = 0;
568
569    checkeq(ENGINE_LOCKED_TMPFAIL, del(h, key, 0, Vbid(0)), "Delete failed");
570
571    /* bug MB 2699 append after getl should fail with ENGINE_TMPFAIL */
572
573    testHarness->time_travel(26);
574
575    char binaryData1[] = "abcdefg\0gfedcba";
576
577    checkeq(cb::engine_errc::success,
578            storeCasVb11(h,
579                         cookie,
580                         OPERATION_SET,
581                         key,
582                         binaryData1,
583                         sizeof(binaryData1) - 1,
584                         82758,
585                         0,
586                         Vbid(0))
587                    .first,
588            "Failed set.");
589
590    /* acquire lock, should succeed */
591    checkeq(cb::engine_errc::success,
592            getl(h, cookie, key, vbucketId, expiration).first,
593            "Acquire lock should have succeeded");
594
595    /* bug MB 3252 & MB 3354.
596     * 1. Set a key with an expiry value.
597     * 2. Take a lock on the item before it expires
598     * 3. Wait for the item to expire
599     * 4. Perform a CAS operation, should fail
600     * 5. Perform a set operation, should succeed
601     */
602    const char *ekey = "test_expiry";
603    const char *edata = "some test data here.";
604
605    ret = allocate(h,
606                   cookie,
607                   ekey,
608                   strlen(edata),
609                   0,
610                   2,
611                   PROTOCOL_BINARY_RAW_BYTES,
612                   Vbid(0));
613    checkeq(cb::engine_errc::success, ret.first, "Allocation Failed");
614
615    check(h->get_item_info(ret.second.get(), &info), "Failed to get item info");
616
617    memcpy(info.value[0].iov_base, edata, strlen(edata));
618
619    checkeq(ENGINE_SUCCESS,
620            h->store(cookie,
621                     ret.second.get(),
622                     cas,
623                     OPERATION_SET,
624                     {},
625                     DocumentState::Alive),
626            "Failed to Store item");
627    check_key_value(h, ekey, edata, strlen(edata));
628
629    testHarness->time_travel(3);
630    cas = last_cas;
631
632    /* cas should fail */
633    ret = storeCasVb11(h,
634                       cookie,
635                       OPERATION_CAS,
636                       ekey,
637                       binaryData1,
638                       sizeof(binaryData1) - 1,
639                       82758,
640                       cas,
641                       Vbid(0));
642    checkne(cb::engine_errc::success, ret.first, "CAS succeeded.");
643
644    /* but a simple store should succeed */
645    checkeq(ENGINE_SUCCESS,
646            store(h,
647                  cookie,
648                  OPERATION_SET,
649                  ekey,
650                  edata,
651                  nullptr,
652                  0,
653                  vbucketId),
654            "Failed to store an item.");
655
656    testHarness->destroy_cookie(cookie);
657    return SUCCESS;
658}
659
660static enum test_result test_unl(EngineIface* h) {
661    const char *key = "k2";
662    Vbid vbucketId = Vbid(0);
663
664    checkeq(ENGINE_SUCCESS,
665            get_stats(h, {}, {}, add_stats),
666            "Failed to get stats.");
667
668    std::string eviction_policy;
669    auto itr = vals.find("ep_item_eviction_policy");
670    if (itr != vals.end()) {
671        eviction_policy = itr->second;
672    } else {
673        eviction_policy = "value_only";
674    }
675
676    if (eviction_policy == "full_eviction") {
677        checkeq(ENGINE_TMPFAIL,
678                unl(h, nullptr, key, vbucketId),
679                "expected a TMPFAIL");
680    } else {
681        checkeq(ENGINE_KEY_ENOENT,
682                unl(h, nullptr, key, vbucketId),
683                "expected the key to be missing...");
684    }
685
686    checkeq(ENGINE_SUCCESS,
687            store(h,
688                  NULL,
689                  OPERATION_SET,
690                  key,
691                  "lockdata",
692                  nullptr,
693                  0,
694                  vbucketId),
695            "Failed to store an item.");
696
697    /* getl, should succeed */
698    auto ret = getl(h, nullptr, key, vbucketId, 0);
699    checkeq(cb::engine_errc::success,
700            ret.first,
701            "Expected to be able to getl on first try");
702    item_info info;
703    checkeq(true,
704            h->get_item_info(ret.second.get(), &info),
705            "failed to get item info");
706    uint64_t cas = info.cas;
707
708    /* lock's taken unlocking with a random cas value should fail */
709    checkeq(ENGINE_LOCKED_TMPFAIL,
710            unl(h, nullptr, key, vbucketId),
711            "Expected to fail getl on second try");
712
713    checkeq(ENGINE_SUCCESS,
714            unl(h, nullptr, key, vbucketId, cas),
715            "Expected to succed unl with correct cas");
716
717    /* acquire lock, should succeed */
718    checkeq(cb::engine_errc::success,
719            getl(h, nullptr, key, vbucketId, 0).first,
720            "Lock should work after unlock");
721
722    /* wait 16 seconds */
723    testHarness->time_travel(16);
724
725    /* lock has expired, unl should fail */
726    checkeq(ENGINE_TMPFAIL,
727            unl(h, nullptr, key, vbucketId, last_cas),
728            "Expected to fail unl on lock timeout");
729
730    return SUCCESS;
731}
732
733static enum test_result test_unl_nmvb(EngineIface* h) {
734    const char *key = "k2";
735    Vbid vbucketId = Vbid(10);
736
737    checkeq(ENGINE_NOT_MY_VBUCKET,
738            unl(h, nullptr, key, vbucketId),
739            "expected NOT_MY_VBUCKET to unlocking a key in a vbucket we don't "
740            "own");
741
742    return SUCCESS;
743}
744
745static enum test_result test_set_get_hit_bin(EngineIface* h) {
746    char binaryData[] = "abcdefg\0gfedcba";
747    cb_assert(sizeof(binaryData) != strlen(binaryData));
748
749    checkeq(cb::engine_errc::success,
750            storeCasVb11(h,
751                         nullptr,
752                         OPERATION_SET,
753                         "key",
754                         binaryData,
755                         sizeof(binaryData),
756                         82758,
757                         0,
758                         Vbid(0))
759                    .first,
760            "Failed to set.");
761    check_key_value(h, "key", binaryData, sizeof(binaryData));
762    return SUCCESS;
763}
764
765static enum test_result test_set_with_cas_non_existent(EngineIface* h) {
766    const char *key = "test_expiry_flush";
767    const auto* cookie = testHarness->create_cookie();
768    auto ret = allocate(
769            h, cookie, key, 10, 0, 0, PROTOCOL_BINARY_RAW_BYTES, Vbid(0));
770    checkeq(cb::engine_errc::success, ret.first, "Allocation failed.");
771
772    Item* it = reinterpret_cast<Item*>(ret.second.get());
773    it->setCas(1234);
774
775    uint64_t cas = 0;
776    checkeq(ENGINE_KEY_ENOENT,
777            h->store(cookie,
778                     ret.second.get(),
779                     cas,
780                     OPERATION_SET,
781                     {},
782                     DocumentState::Alive),
783            "Expected not found");
784
785    testHarness->destroy_cookie(cookie);
786    return SUCCESS;
787}
788
789static enum test_result test_set_change_flags(EngineIface* h) {
790    checkeq(ENGINE_SUCCESS,
791            store(h, NULL, OPERATION_SET, "key", "somevalue"),
792            "Failed to set.");
793
794    item_info info;
795    uint32_t flags = 828258;
796    check(get_item_info(h, &info, "key"), "Failed to get value.");
797    cb_assert(info.flags != flags);
798
799    checkeq(cb::engine_errc::success,
800            storeCasVb11(h,
801                         nullptr,
802                         OPERATION_SET,
803                         "key",
804                         "newvalue",
805                         strlen("newvalue"),
806                         flags,
807                         0,
808                         Vbid(0))
809                    .first,
810            "Failed to set again.");
811
812    check(get_item_info(h, &info, "key"), "Failed to get value.");
813
814    return info.flags == flags ? SUCCESS : FAIL;
815}
816
817static enum test_result test_add(EngineIface* h) {
818    item_info info;
819    uint64_t vb_uuid = 0;
820    uint64_t high_seqno = 0;
821
822    memset(&info, 0, sizeof(info));
823
824    vb_uuid = get_ull_stat(h, "vb_0:0:id", "failovers");
825    high_seqno = get_ull_stat(h, "vb_0:high_seqno", "vbucket-seqno");
826
827    checkeq(ENGINE_SUCCESS,
828            store(h, NULL, OPERATION_ADD, "key", "somevalue"),
829            "Failed to add value.");
830
831    check(get_item_info(h, &info, "key"), "Error getting item info");
832    checkeq(vb_uuid, info.vbucket_uuid, "Expected valid vbucket uuid");
833    checkeq(high_seqno + 1, info.seqno, "Expected valid sequence number");
834
835    checkeq(ENGINE_NOT_STORED,
836            store(h, NULL, OPERATION_ADD, "key", "somevalue"),
837            "Failed to fail to re-add value.");
838
839    // This aborts on failure.
840    check_key_value(h, "key", "somevalue", 9);
841
842    // Expiration above was an hour, so let's go to The Future
843    testHarness->time_travel(3800);
844
845    checkeq(ENGINE_SUCCESS,
846            store(h, NULL, OPERATION_ADD, "key", "newvalue"),
847            "Failed to add value again.");
848
849    check_key_value(h, "key", "newvalue", 8);
850    return SUCCESS;
851}
852
853static enum test_result test_add_add_with_cas(EngineIface* h) {
854    item *i = NULL;
855    checkeq(ENGINE_SUCCESS,
856            store(h, NULL, OPERATION_ADD, "key", "somevalue", &i),
857            "Failed set.");
858    check_key_value(h, "key", "somevalue", 9);
859    item_info info;
860    check(h->get_item_info(i, &info), "Should be able to get info");
861
862    checkeq(ENGINE_KEY_EEXISTS,
863            store(h,
864                  NULL,
865                  OPERATION_ADD,
866                  "key",
867                  "somevalue",
868                  nullptr,
869                  info.cas),
870            "Should not be able to add the key two times");
871
872    h->release(i);
873    return SUCCESS;
874}
875
876static enum test_result test_cas(EngineIface* h) {
877    checkeq(ENGINE_SUCCESS,
878            store(h, NULL, OPERATION_SET, "key", "somevalue"),
879            "Failed to do initial set.");
880    checkne(ENGINE_SUCCESS, store(h, NULL, OPERATION_CAS, "key", "failcas"),
881            "Failed to fail initial CAS.");
882    check_key_value(h, "key", "somevalue", 9);
883
884    auto ret = get(h, NULL, "key", Vbid(0));
885    checkeq(cb::engine_errc::success, ret.first, "Failed to get value.");
886
887    item_info info;
888    check(h->get_item_info(ret.second.get(), &info),
889          "Failed to get item info.");
890
891    checkeq(ENGINE_SUCCESS,
892            store(h,
893                  NULL,
894                  OPERATION_CAS,
895                  "key",
896                  "winCas",
897                  nullptr,
898                  info.cas),
899            "Failed to store CAS");
900    check_key_value(h, "key", "winCas", 6);
901
902    uint64_t cval = 99999;
903    checkeq(ENGINE_KEY_ENOENT,
904            store(h,
905                  NULL,
906                  OPERATION_CAS,
907                  "non-existing",
908                  "winCas",
909                  nullptr,
910                  cval),
911            "CAS for non-existing key returned the wrong error code");
912    return SUCCESS;
913}
914
915static enum test_result test_replace(EngineIface* h) {
916    item_info info;
917    uint64_t vb_uuid = 0;
918    uint64_t high_seqno = 0;
919
920    memset(&info, 0, sizeof(info));
921
922    checkne(ENGINE_SUCCESS,
923            store(h, NULL, OPERATION_REPLACE, "key", "somevalue"),
924            "Failed to fail to replace non-existing value.");
925
926    checkeq(ENGINE_SUCCESS,
927            store(h, NULL, OPERATION_SET, "key", "somevalue"),
928            "Failed to set value.");
929
930    vb_uuid = get_ull_stat(h, "vb_0:0:id", "failovers");
931    high_seqno = get_ull_stat(h, "vb_0:high_seqno", "vbucket-seqno");
932
933    checkeq(ENGINE_SUCCESS,
934            store(h, NULL, OPERATION_REPLACE, "key", "somevalue"),
935            "Failed to replace existing value.");
936
937    check(get_item_info(h, &info, "key"), "Error getting item info");
938
939    checkeq(vb_uuid, info.vbucket_uuid, "Expected valid vbucket uuid");
940    checkeq(high_seqno + 1, info.seqno, "Expected valid sequence number");
941
942    check_key_value(h, "key", "somevalue", 9);
943    return SUCCESS;
944}
945
946static enum test_result test_touch(EngineIface* h) {
947    // Try to touch an unknown item...
948    checkeq(ENGINE_KEY_ENOENT,
949            touch(h, "mykey", Vbid(0), 0),
950            "Testing unknown key");
951
952    // illegal vbucket
953    checkeq(ENGINE_NOT_MY_VBUCKET,
954            touch(h, "mykey", Vbid(5), 0),
955            "Testing illegal vbucket");
956
957    // Store the item!
958    checkeq(ENGINE_SUCCESS,
959            store(h, NULL, OPERATION_SET, "mykey", "somevalue"),
960            "Failed set.");
961
962    check_key_value(h, "mykey", "somevalue", strlen("somevalue"));
963
964    cb::EngineErrorMetadataPair errorMetaPair;
965
966    check(get_meta(h, "mykey", errorMetaPair), "Get meta failed");
967
968    item_info currMeta = errorMetaPair.second;
969
970    checkeq(ENGINE_SUCCESS,
971            touch(h, "mykey", Vbid(0), uint32_t(time(NULL) + 10)),
972            "touch mykey");
973    checkne(last_cas.load(),
974            currMeta.cas,
975            "touch should have returned an updated CAS");
976
977    check(get_meta(h, "mykey", errorMetaPair), "Get meta failed");
978
979    checkne(errorMetaPair.second.cas, currMeta.cas,
980          "touch should have updated the CAS");
981    checkne(errorMetaPair.second.exptime, currMeta.exptime,
982          "touch should have updated the expiry time");
983    checkeq(errorMetaPair.second.seqno, (currMeta.seqno + 1),
984          "touch should have incremented rev seqno");
985
986    // time-travel 9 secs..
987    testHarness->time_travel(9);
988
989    // The item should still exist
990    check_key_value(h, "mykey", "somevalue", 9);
991
992    // time-travel 2 secs..
993    testHarness->time_travel(2);
994
995    // The item should have expired now...
996    checkeq(cb::engine_errc::no_such_key,
997            get(h, NULL, "mykey", Vbid(0)).first,
998            "Item should be gone");
999    return SUCCESS;
1000}
1001
1002static enum test_result test_touch_mb7342(EngineIface* h) {
1003    const char *key = "MB-7342";
1004    // Store the item!
1005    checkeq(ENGINE_SUCCESS,
1006            store(h, NULL, OPERATION_SET, key, "v"),
1007            "Failed set.");
1008
1009    checkeq(ENGINE_SUCCESS, touch(h, key, Vbid(0), 0), "touch key");
1010
1011    check_key_value(h, key, "v", 1);
1012
1013    // Travel a loong time to see if the object is still there (the default
1014    // store sets an exp time of 3600
1015    testHarness->time_travel(3700);
1016
1017    check_key_value(h, key, "v", 1);
1018
1019    return SUCCESS;
1020}
1021
1022static enum test_result test_touch_mb10277(EngineIface* h) {
1023    const char *key = "MB-10277";
1024    // Store the item!
1025    checkeq(ENGINE_SUCCESS,
1026            store(h, NULL, OPERATION_SET, key, "v"),
1027            "Failed set.");
1028    wait_for_flusher_to_settle(h);
1029    evict_key(h, key, Vbid(0), "Ejected.");
1030
1031    checkeq(ENGINE_SUCCESS,
1032            touch(h,
1033                  key,
1034                  Vbid(0),
1035                  3600), // A new expiration time remains in the same.
1036            "touch key");
1037
1038    return SUCCESS;
1039}
1040
1041static enum test_result test_gat(EngineIface* h) {
1042    // Try to gat an unknown item...
1043    auto ret = gat(h, "mykey", Vbid(0), 10);
1044    checkeq(ENGINE_KEY_ENOENT, ENGINE_ERROR_CODE(ret.first),
1045            "Testing unknown key");
1046
1047    // illegal vbucket
1048    ret = gat(h, "mykey", Vbid(5), 10);
1049    checkeq(ENGINE_NOT_MY_VBUCKET,
1050            ENGINE_ERROR_CODE(ret.first), "Testing illegal vbucket");
1051
1052    // Store the item!
1053    checkeq(ENGINE_SUCCESS,
1054            store(h,
1055                  NULL,
1056                  OPERATION_SET,
1057                  "mykey",
1058                  "{\"some\":\"value\"}",
1059                  nullptr,
1060                  0,
1061                  Vbid(0),
1062                  3600,
1063                  PROTOCOL_BINARY_DATATYPE_JSON),
1064            "Failed set.");
1065
1066    check_key_value(
1067            h, "mykey", "{\"some\":\"value\"}", strlen("{\"some\":\"value\"}"));
1068
1069    ret = gat(h, "mykey", Vbid(0), 10);
1070    checkeq(ENGINE_SUCCESS,
1071            ENGINE_ERROR_CODE(ret.first), "gat mykey");
1072
1073    item_info info;
1074    check(h->get_item_info(ret.second.get(), &info),
1075          "Getting item info failed");
1076
1077    checkeq(static_cast<uint8_t>(PROTOCOL_BINARY_DATATYPE_JSON),
1078            info.datatype, "Expected datatype to be JSON");
1079
1080    std::string body{static_cast<char*>(info.value[0].iov_base),
1081    info.value[0].iov_len};
1082    check(body.compare(0, sizeof("{\"some\":\"value\"}"),
1083                       "{\"some\":\"value\"}") == 0,
1084          "Invalid data returned");
1085
1086    // time-travel 9 secs..
1087    testHarness->time_travel(9);
1088
1089    // The item should still exist
1090    check_key_value(
1091            h, "mykey", "{\"some\":\"value\"}", strlen("{\"some\":\"value\"}"));
1092
1093    // time-travel 2 secs..
1094    testHarness->time_travel(2);
1095
1096    // The item should have expired now...
1097    checkeq(cb::engine_errc::no_such_key,
1098            get(h, NULL, "mykey", Vbid(0)).first,
1099            "Item should be gone");
1100    return SUCCESS;
1101}
1102
1103static enum test_result test_gat_locked(EngineIface* h) {
1104    checkeq(ENGINE_SUCCESS,
1105            store(h, NULL, OPERATION_SET, "key", "value"),
1106            "Failed to set key");
1107
1108    checkeq(cb::engine_errc::success,
1109            getl(h, nullptr, "key", Vbid(0), 15).first,
1110            "Expected getl to succeed on key");
1111
1112    auto ret = gat(h, "key", Vbid(0), 10);
1113    checkeq(ENGINE_LOCKED, ENGINE_ERROR_CODE(ret.first), "Expected LOCKED");
1114
1115    testHarness->time_travel(16);
1116    ret = gat(h, "key", Vbid(0), 10);
1117    checkeq(ENGINE_SUCCESS, ENGINE_ERROR_CODE(ret.first), "Expected success");
1118
1119    testHarness->time_travel(11);
1120    checkeq(cb::engine_errc::no_such_key,
1121            get(h, NULL, "key", Vbid(0)).first,
1122            "Expected value to be expired");
1123    return SUCCESS;
1124}
1125
1126static enum test_result test_touch_locked(EngineIface* h) {
1127    item *itm = NULL;
1128    checkeq(ENGINE_SUCCESS,
1129            store(h, NULL, OPERATION_SET, "key", "value", &itm),
1130            "Failed to set key");
1131    h->release(itm);
1132
1133    checkeq(cb::engine_errc::success,
1134            getl(h, nullptr, "key", Vbid(0), 15).first,
1135            "Expected getl to succeed on key");
1136
1137    checkeq(ENGINE_LOCKED, touch(h, "key", Vbid(0), 10), "Expected tmp fail");
1138
1139    testHarness->time_travel(16);
1140    checkeq(ENGINE_SUCCESS, touch(h, "key", Vbid(0), 10), "Expected success");
1141
1142    testHarness->time_travel(11);
1143    checkeq(cb::engine_errc::no_such_key,
1144            get(h, NULL, "key", Vbid(0)).first,
1145            "Expected value to be expired");
1146
1147    return SUCCESS;
1148}
1149
1150static enum test_result test_mb5215(EngineIface* h) {
1151    if (!isWarmupEnabled(h)) {
1152        return SKIPPED;
1153    }
1154
1155    checkeq(ENGINE_SUCCESS,
1156            store(h, NULL, OPERATION_SET, "coolkey", "cooler"),
1157            "Failed set.");
1158
1159    check_key_value(h, "coolkey", "cooler", strlen("cooler"));
1160
1161    // set new exptime to 111
1162    int expTime = time(NULL) + 111;
1163
1164    checkeq(ENGINE_SUCCESS,
1165            touch(h, "coolkey", Vbid(0), expTime),
1166            "touch coolkey");
1167
1168    //reload engine
1169    testHarness->reload_engine(&h,
1170                               testHarness->engine_path,
1171                               testHarness->get_current_testcase()->cfg,
1172                               true,
1173                               false);
1174    wait_for_warmup_complete(h);
1175
1176    //verify persisted expiration time
1177    const char *statkey = "key coolkey 0";
1178    int newExpTime;
1179    checkeq(cb::engine_errc::success,
1180            get(h, NULL, "coolkey", Vbid(0)).first,
1181            "Missing key");
1182    newExpTime = get_int_stat(h, "key_exptime", statkey);
1183    checkeq(expTime, newExpTime, "Failed to persist new exptime");
1184
1185    // evict key, touch expiration time, and verify
1186    evict_key(h, "coolkey", Vbid(0), "Ejected.");
1187
1188    expTime = time(NULL) + 222;
1189    checkeq(ENGINE_SUCCESS,
1190            touch(h, "coolkey", Vbid(0), expTime),
1191            "touch coolkey");
1192
1193    testHarness->reload_engine(&h,
1194                               testHarness->engine_path,
1195                               testHarness->get_current_testcase()->cfg,
1196                               true,
1197                               false);
1198
1199    wait_for_warmup_complete(h);
1200
1201    checkeq(cb::engine_errc::success,
1202            get(h, NULL, "coolkey", Vbid(0)).first,
1203            "Missing key");
1204    newExpTime = get_int_stat(h, "key_exptime", statkey);
1205    checkeq(expTime, newExpTime, "Failed to persist new exptime");
1206
1207    return SUCCESS;
1208}
1209
1210/* Testing functionality to store a value for a deleted item
1211 * and also retrieve the value of a deleted item.
1212 * Need to check:
1213 *
1214 * - Each possible state transition between Alive, Deleted-with-value and
1215 *   Deleted-no-value.
1216 */
1217static enum test_result test_delete_with_value(EngineIface* h) {
1218    const uint64_t cas_0 = 0;
1219    const Vbid vbid = Vbid(0);
1220    const void* cookie = testHarness->create_cookie();
1221
1222    // Store an initial (not-deleted) value.
1223    checkeq(ENGINE_SUCCESS,
1224            store(h, cookie, OPERATION_SET, "key", "somevalue"),
1225            "Failed set");
1226    wait_for_flusher_to_settle(h);
1227
1228    checkeq(uint64_t(1),
1229            get_stat<uint64_t>(h, "vb_0:num_items", "vbucket-details 0"),
1230            "Unexpected initial item count");
1231
1232    /* Alive -> Deleted-with-value */
1233    checkeq(ENGINE_SUCCESS,
1234            delete_with_value(h, cookie, cas_0, "key", "deleted"),
1235            "Failed Alive -> Delete-with-value");
1236
1237    checkeq(uint64_t(0),
1238            get_stat<uint64_t>(h, "vb_0:num_items", "vbucket-details 0"),
1239            "Unexpected num_items after Alive -> Delete-with-value");
1240
1241    auto res = get_value(h, cookie, "key", vbid, DocStateFilter::Alive);
1242    checkeq(ENGINE_KEY_ENOENT,
1243            res.first,
1244            "Unexpectedly accessed Deleted-with-value via DocState::Alive");
1245
1246    res = get_value(h, cookie, "key", vbid, DocStateFilter::AliveOrDeleted);
1247    checkeq(ENGINE_SUCCESS,
1248            res.first,
1249            "Failed to fetch Alive -> Delete-with-value");
1250    checkeq(std::string("deleted"), res.second, "Unexpected value (deleted)");
1251
1252    /* Deleted-with-value -> Deleted-with-value (different value). */
1253    checkeq(ENGINE_SUCCESS,
1254            delete_with_value(h, cookie, cas_0, "key", "deleted 2"),
1255            "Failed Deleted-with-value -> Deleted-with-value");
1256
1257    checkeq(uint64_t(0),
1258            get_stat<uint64_t>(h, "vb_0:num_items", "vbucket-details 0"),
1259            "Unexpected num_items after Delete-with-value -> "
1260            "Delete-with-value");
1261
1262    res = get_value(h, cookie, "key", vbid, DocStateFilter::AliveOrDeleted);
1263    checkeq(ENGINE_SUCCESS, res.first, "Failed to fetch key (deleted 2)");
1264    checkeq(std::string("deleted 2"),
1265            res.second,
1266            "Unexpected value (deleted 2)");
1267
1268    /* Delete-with-value -> Alive */
1269    checkeq(ENGINE_SUCCESS,
1270            store(h, cookie, OPERATION_SET, "key", "alive 2", nullptr),
1271            "Failed Delete-with-value -> Alive");
1272    wait_for_flusher_to_settle(h);
1273
1274    checkeq(uint64_t(1),
1275            get_stat<uint64_t>(h, "vb_0:num_items", "vbucket-details 0"),
1276            "Unexpected num_items after Delete-with-value -> Alive");
1277
1278    res = get_value(h, cookie, "key", vbid, DocStateFilter::Alive);
1279    checkeq(ENGINE_SUCCESS,
1280            res.first,
1281            "Failed to fetch Delete-with-value -> Alive via DocState::Alive");
1282    checkeq(std::string("alive 2"), res.second, "Unexpected value (alive 2)");
1283
1284    // Also check via DocState::Deleted
1285    res = get_value(h, cookie, "key", vbid, DocStateFilter::AliveOrDeleted);
1286    checkeq(ENGINE_SUCCESS,
1287            res.first,
1288            "Failed to fetch Delete-with-value -> Alive via DocState::Deleted");
1289    checkeq(std::string("alive 2"),
1290            res.second,
1291            "Unexpected value (alive 2) via DocState::Deleted");
1292
1293    /* Alive -> Deleted-no-value */
1294    checkeq(ENGINE_SUCCESS,
1295            del(h, "key", cas_0, vbid, cookie),
1296            "Failed Alive -> Deleted-no-value");
1297    wait_for_flusher_to_settle(h);
1298
1299    checkeq(uint64_t(0),
1300            get_stat<uint64_t>(h, "vb_0:num_items", "vbucket-details 0"),
1301            "Unexpected num_items after Alive -> Delete-no-value");
1302
1303    res = get_value(h, cookie, "key", vbid, DocStateFilter::Alive);
1304    checkeq(ENGINE_KEY_ENOENT,
1305            res.first,
1306            "Unexpectedly accessed Deleted-no-value via DocState::Alive");
1307
1308    /* Deleted-no-value -> Delete-with-value */
1309    checkeq(ENGINE_SUCCESS,
1310            delete_with_value(h, cookie, cas_0, "key", "deleted 3"),
1311            "Failed delete with value (deleted 2)");
1312
1313    res = get_value(h, cookie, "key", vbid, DocStateFilter::AliveOrDeleted);
1314    checkeq(ENGINE_SUCCESS, res.first, "Failed to fetch key (deleted 3)");
1315    checkeq(std::string("deleted 3"),
1316            res.second,
1317            "Unexpected value (deleted 3)");
1318
1319    testHarness->destroy_cookie(cookie);
1320
1321    return SUCCESS;
1322}
1323
1324/* Similar to test_delete_with_value, except also checks that CAS values
1325 */
1326static enum test_result test_delete_with_value_cas(EngineIface* h) {
1327    checkeq(ENGINE_SUCCESS,
1328            store(h, nullptr, OPERATION_SET, "key1", "somevalue"),
1329            "Failed set");
1330
1331    cb::EngineErrorMetadataPair errorMetaPair;
1332
1333    check(get_meta(h, "key1", errorMetaPair), "Get meta failed");
1334
1335    uint64_t curr_revseqno = errorMetaPair.second.seqno;
1336
1337    /* Store a deleted item first with CAS 0 */
1338    checkeq(ENGINE_SUCCESS,
1339            store(h,
1340                  nullptr,
1341                  OPERATION_SET,
1342                  "key1",
1343                  "deletevalue",
1344                  nullptr,
1345                  0,
1346                  Vbid(0),
1347                  3600,
1348                  0x00,
1349                  DocumentState::Deleted),
1350            "Failed delete with value");
1351
1352    check(get_meta(h, "key1", errorMetaPair), "Get meta failed");
1353
1354    checkeq(errorMetaPair.second.seqno,
1355            curr_revseqno + 1,
1356            "rev seqno should have incremented");
1357
1358    item *i = nullptr;
1359    checkeq(ENGINE_SUCCESS,
1360            store(h, nullptr, OPERATION_SET, "key2", "somevalue", &i),
1361            "Failed set");
1362
1363    item_info info;
1364    check(h->get_item_info(i, &info), "Getting item info failed");
1365
1366    h->release(i);
1367
1368    check(get_meta(h, "key2", errorMetaPair), "Get meta failed");
1369
1370    curr_revseqno = errorMetaPair.second.seqno;
1371
1372    /* Store a deleted item with the existing CAS value */
1373    checkeq(ENGINE_SUCCESS,
1374            store(h,
1375                  nullptr,
1376                  OPERATION_SET,
1377                  "key2",
1378                  "deletevaluewithcas",
1379                  nullptr,
1380                  info.cas,
1381                  Vbid(0),
1382                  3600,
1383                  0x00,
1384                  DocumentState::Deleted),
1385            "Failed delete value with cas");
1386
1387    wait_for_flusher_to_settle(h);
1388
1389    check(get_meta(h, "key2", errorMetaPair), "Get meta failed");
1390
1391    checkeq(errorMetaPair.second.seqno,
1392            curr_revseqno + 1,
1393            "rev seqno should have incremented");
1394
1395    curr_revseqno = errorMetaPair.second.seqno;
1396
1397    checkeq(ENGINE_SUCCESS,
1398            store(h,
1399                  nullptr,
1400                  OPERATION_SET,
1401                  "key2",
1402                  "newdeletevalue",
1403                  &i,
1404                  0,
1405                  Vbid(0),
1406                  3600,
1407                  0x00,
1408                  DocumentState::Deleted),
1409            "Failed delete value with cas");
1410
1411    wait_for_flusher_to_settle(h);
1412
1413    check(h->get_item_info(i, &info), "Getting item info failed");
1414    checkeq(int(DocumentState::Deleted),
1415            int(info.document_state),
1416            "Incorrect DocState for deleted item");
1417    checkne(uint64_t(0), info.cas, "Expected non-zero CAS for deleted item");
1418
1419    h->release(i);
1420
1421    check(get_meta(h, "key2", errorMetaPair), "Get meta failed");
1422
1423    checkeq(errorMetaPair.second.seqno,
1424            curr_revseqno + 1,
1425            "rev seqno should have incremented");
1426
1427    curr_revseqno = errorMetaPair.second.seqno;
1428
1429    // Attempt to Delete-with-value using incorrect CAS (should fail)
1430    const uint64_t incorrect_CAS = info.cas + 1;
1431    checkeq(ENGINE_KEY_EEXISTS,
1432            store(h,
1433                  nullptr,
1434                  OPERATION_SET,
1435                  "key2",
1436                  "newdeletevaluewithcas",
1437                  nullptr,
1438                  incorrect_CAS,
1439                  Vbid(0),
1440                  3600,
1441                  0x00,
1442                  DocumentState::Deleted),
1443            "Expected KEY_EEXISTS with incorrect CAS");
1444
1445    // Attempt with correct CAS.
1446    checkeq(ENGINE_SUCCESS,
1447            store(h,
1448                  nullptr,
1449                  OPERATION_SET,
1450                  "key2",
1451                  "newdeletevaluewithcas",
1452                  nullptr,
1453                  info.cas,
1454                  Vbid(0),
1455                  3600,
1456                  0x00,
1457                  DocumentState::Deleted),
1458            "Failed delete value with cas");
1459
1460    wait_for_flusher_to_settle(h);
1461
1462    auto ret = get(h, nullptr, "key2", Vbid(0), DocStateFilter::AliveOrDeleted);
1463    checkeq(cb::engine_errc::success, ret.first, "Failed to get value");
1464
1465    check(get_meta(h, "key2", errorMetaPair), "Get meta failed");
1466
1467    checkeq(errorMetaPair.second.seqno,
1468            curr_revseqno + 1,
1469            "rev seqno should have incremented");
1470
1471    check(h->get_item_info(ret.second.get(), &info),
1472          "Getting item info failed");
1473    checkeq(int(DocumentState::Deleted),
1474            int(info.document_state),
1475            "Incorrect DocState for deleted item");
1476
1477    checkeq(static_cast<uint8_t>(DocumentState::Deleted),
1478            static_cast<uint8_t>(info.document_state),
1479            "document must be in deleted state");
1480
1481    std::string buf(static_cast<char*>(info.value[0].iov_base),
1482                    info.value[0].iov_len);
1483
1484    checkeq(0, buf.compare("newdeletevaluewithcas"), "Data mismatch");
1485
1486    ret = get(h, nullptr, "key", Vbid(0), DocStateFilter::Alive);
1487    checkeq(cb::engine_errc::no_such_key,
1488            ret.first,
1489            "Getting value should have failed");
1490
1491    return SUCCESS;
1492}
1493
1494static enum test_result test_delete(EngineIface* h) {
1495    item *i = NULL;
1496    // First try to delete something we know to not be there.
1497    checkeq(ENGINE_KEY_ENOENT,
1498            del(h, "key", 0, Vbid(0)),
1499            "Failed to fail initial delete.");
1500    checkeq(ENGINE_SUCCESS,
1501            store(h, NULL, OPERATION_SET, "key", "somevalue", &i),
1502            "Failed set.");
1503    Item *it = reinterpret_cast<Item*>(i);
1504    uint64_t orig_cas = it->getCas();
1505    h->release(i);
1506    check_key_value(h, "key", "somevalue", 9);
1507
1508    uint64_t cas = 0;
1509    uint64_t vb_uuid = 0;
1510    mutation_descr_t mut_info;
1511    uint64_t high_seqno = 0;
1512
1513    memset(&mut_info, 0, sizeof(mut_info));
1514
1515    vb_uuid = get_ull_stat(h, "vb_0:0:id", "failovers");
1516    high_seqno = get_ull_stat(h, "vb_0:high_seqno", "vbucket-seqno");
1517    checkeq(ENGINE_SUCCESS,
1518            del(h, "key", &cas, Vbid(0), nullptr, &mut_info),
1519            "Failed remove with value.");
1520    checkne(orig_cas, cas, "Expected CAS to be updated on delete");
1521    checkeq(ENGINE_KEY_ENOENT, verify_key(h, "key"), "Expected missing key");
1522    checkeq(vb_uuid, mut_info.vbucket_uuid, "Expected valid vbucket uuid");
1523    checkeq(high_seqno + 1, mut_info.seqno, "Expected valid sequence number");
1524
1525    // Can I time travel to an expired object and delete it?
1526    checkeq(ENGINE_SUCCESS,
1527            store(h, NULL, OPERATION_SET, "key", "somevalue", &i),
1528            "Failed set.");
1529    h->release(i);
1530    testHarness->time_travel(3617);
1531    checkeq(ENGINE_KEY_ENOENT,
1532            del(h, "key", 0, Vbid(0)),
1533            "Did not get ENOENT removing an expired object.");
1534    checkeq(ENGINE_KEY_ENOENT, verify_key(h, "key"), "Expected missing key");
1535
1536    return SUCCESS;
1537}
1538
1539static enum test_result test_set_delete(EngineIface* h) {
1540    checkeq(ENGINE_SUCCESS,
1541            store(h, NULL, OPERATION_SET, "key", "somevalue"),
1542            "Failed set.");
1543    check_key_value(h, "key", "somevalue", 9);
1544    checkeq(ENGINE_SUCCESS,
1545            del(h, "key", 0, Vbid(0)),
1546            "Failed remove with value.");
1547    checkeq(ENGINE_KEY_ENOENT, verify_key(h, "key"), "Expected missing key");
1548    wait_for_flusher_to_settle(h);
1549    wait_for_stat_to_be(h, "curr_items", 0);
1550    return SUCCESS;
1551}
1552
1553static enum test_result test_set_delete_invalid_cas(EngineIface* h) {
1554    item *i = NULL;
1555    checkeq(ENGINE_SUCCESS,
1556            store(h, NULL, OPERATION_SET, "key", "somevalue", &i),
1557            "Failed set.");
1558    check_key_value(h, "key", "somevalue", 9);
1559    item_info info;
1560    check(h->get_item_info(i, &info), "Should be able to get info");
1561    h->release(i);
1562
1563    checkeq(ENGINE_KEY_EEXISTS,
1564            del(h, "key", info.cas + 1, Vbid(0)),
1565            "Didn't expect to be able to remove the item with wrong cas");
1566
1567    checkeq(ENGINE_SUCCESS,
1568            del(h, "key", info.cas, Vbid(0)),
1569            "Subsequent delete with correct CAS did not succeed");
1570
1571    return SUCCESS;
1572}
1573
1574static enum test_result test_delete_set(EngineIface* h) {
1575    if (!isWarmupEnabled(h)) {
1576        return SKIPPED;
1577    }
1578
1579    wait_for_persisted_value(h, "key", "value1");
1580
1581    checkeq(ENGINE_SUCCESS,
1582            del(h, "key", 0, Vbid(0)),
1583            "Failed remove with value.");
1584
1585    wait_for_persisted_value(h, "key", "value2");
1586
1587    testHarness->reload_engine(&h,
1588                               testHarness->engine_path,
1589                               testHarness->get_current_testcase()->cfg,
1590                               true,
1591                               false);
1592
1593    wait_for_warmup_complete(h);
1594
1595    check_key_value(h, "key", "value2", 6);
1596    checkeq(ENGINE_SUCCESS,
1597            del(h, "key", 0, Vbid(0)),
1598            "Failed remove with value.");
1599    wait_for_flusher_to_settle(h);
1600
1601    testHarness->reload_engine(&h,
1602                               testHarness->engine_path,
1603                               testHarness->get_current_testcase()->cfg,
1604                               true,
1605                               false);
1606
1607    wait_for_warmup_complete(h);
1608
1609    checkeq(ENGINE_KEY_ENOENT, verify_key(h, "key"), "Expected missing key");
1610
1611    return SUCCESS;
1612}
1613
1614static enum test_result test_get_delete_missing_file(EngineIface* h) {
1615    checkeq(ENGINE_SUCCESS,
1616            get_stats(h, {}, {}, add_stats),
1617            "Failed to get stats.");
1618
1619    const char *key = "key";
1620    wait_for_persisted_value(h, key, "value2delete");
1621
1622    // Make the couchstore files in the db directory totally inaccessible.
1623    std::string dbname = vals["ep_dbname"];
1624    CouchstoreFileAccessGuard makeCouchstoreFileInaccessible(
1625            dbname, CouchstoreFileAccessGuard::Mode::DenyAll);
1626
1627    auto ret = get(h, NULL, key, Vbid(0));
1628
1629    // ep engine must be unaware of well-being of the db file as long as
1630    // the item is still in the memory
1631    checkeq(cb::engine_errc::success, ret.first, "Expected success for get");
1632
1633    evict_key(h, key);
1634    ret = get(h, NULL, key, Vbid(0));
1635
1636    // ep engine must be now aware of the ill-fated db file where
1637    // the item is supposedly stored
1638    checkeq(cb::engine_errc::temporary_failure,
1639            ret.first,
1640            "Expected tmp fail for get");
1641
1642    return SUCCESS;
1643}
1644
1645static enum test_result test_bug7023(EngineIface* h) {
1646    std::vector<std::string> keys;
1647    // Make a vbucket mess.
1648    const int nitems = 10000;
1649    const int iterations = 5;
1650    for (int j = 0; j < nitems; ++j) {
1651        keys.push_back("key" + std::to_string(j));
1652    }
1653
1654    std::vector<std::string>::iterator it;
1655    for (int j = 0; j < iterations; ++j) {
1656        check(set_vbucket_state(h, Vbid(0), vbucket_state_dead),
1657              "Failed set set vbucket 0 dead.");
1658        checkeq(ENGINE_SUCCESS, vbucketDelete(h, Vbid(0)), "expected success");
1659        checkeq(cb::mcbp::Status::Success,
1660                last_status.load(),
1661                "Expected vbucket deletion to work.");
1662        check(set_vbucket_state(h, Vbid(0), vbucket_state_active),
1663              "Failed set set vbucket 0 active.");
1664        for (it = keys.begin(); it != keys.end(); ++it) {
1665            checkeq(ENGINE_SUCCESS,
1666                    store(h, NULL, OPERATION_SET, it->c_str(), it->c_str()),
1667                    "Failed to store a value");
1668        }
1669    }
1670    wait_for_flusher_to_settle(h);
1671
1672    if (isWarmupEnabled(h)) {
1673        // Restart again, to verify no data loss.
1674        testHarness->reload_engine(&h,
1675                                   testHarness->engine_path,
1676                                   testHarness->get_current_testcase()->cfg,
1677                                   true,
1678                                   false);
1679
1680        wait_for_warmup_complete(h);
1681        checkeq(nitems,
1682                get_int_stat(h, "ep_warmup_value_count", "warmup"),
1683                "Incorrect items following warmup");
1684    }
1685    return SUCCESS;
1686}
1687
1688static enum test_result test_mb3169(EngineIface* h) {
1689    checkeq(ENGINE_SUCCESS,
1690            store(h, NULL, OPERATION_SET, "set", "value"),
1691            "Failed to store a value");
1692    checkeq(ENGINE_SUCCESS,
1693            store(h, NULL, OPERATION_SET, "delete", "0"),
1694            "Failed to store a value");
1695    checkeq(ENGINE_SUCCESS,
1696            store(h, NULL, OPERATION_SET, "get", "getvalue"),
1697            "Failed to store a value");
1698
1699    wait_for_stat_to_be(h, "ep_total_persisted", 3);
1700
1701    evict_key(h, "set", Vbid(0), "Ejected.");
1702    evict_key(h, "delete", Vbid(0), "Ejected.");
1703    evict_key(h, "get", Vbid(0), "Ejected.");
1704
1705    checkeq(3, get_int_stat(h, "curr_items"), "Expected 3 items");
1706    checkeq(3,
1707            get_int_stat(h, "ep_num_non_resident"),
1708            "Expected all items to be resident");
1709
1710    checkeq(ENGINE_SUCCESS,
1711            store(h, NULL, OPERATION_SET, "set", "value2"),
1712            "Failed to store a value");
1713    wait_for_flusher_to_settle(h);
1714
1715    checkeq(3, get_int_stat(h, "curr_items"), "Expected 3 items");
1716    checkeq(2,
1717            get_int_stat(h, "ep_num_non_resident"),
1718            "Expected mutation to mark item resident");
1719
1720    checkeq(ENGINE_SUCCESS, del(h, "delete", 0, Vbid(0)), "Delete failed");
1721
1722    wait_for_flusher_to_settle(h);
1723
1724    checkeq(2, get_int_stat(h, "curr_items"), "Expected 2 items after del");
1725    checkeq(1,
1726            get_int_stat(h, "ep_num_non_resident"),
1727            "Expected delete to remove non-resident item");
1728
1729    check_key_value(h, "get", "getvalue", 8);
1730
1731    checkeq(2, get_int_stat(h, "curr_items"), "Expected 2 items after get");
1732    checkeq(0,
1733            get_int_stat(h, "ep_num_non_resident"),
1734            "Expected all items to be resident");
1735    return SUCCESS;
1736}
1737
1738static enum test_result test_mb5172(EngineIface* h) {
1739    if (!isWarmupEnabled(h)) {
1740        return SKIPPED;
1741    }
1742
1743    checkeq(ENGINE_SUCCESS,
1744            store(h, NULL, OPERATION_SET, "key-1", "value-1"),
1745            "Failed to store a value");
1746
1747    checkeq(ENGINE_SUCCESS,
1748            store(h, NULL, OPERATION_SET, "key-2", "value-2"),
1749            "Failed to store a value");
1750
1751    wait_for_flusher_to_settle(h);
1752
1753    checkeq(0,
1754            get_int_stat(h, "ep_num_non_resident"),
1755            "Expected all items to be resident");
1756
1757    // restart the server.
1758    testHarness->reload_engine(&h,
1759                               testHarness->engine_path,
1760                               testHarness->get_current_testcase()->cfg,
1761                               true,
1762                               false);
1763
1764    wait_for_warmup_complete(h);
1765    checkeq(0,
1766            get_int_stat(h, "ep_num_non_resident"),
1767            "Expected all items to be resident");
1768    return SUCCESS;
1769}
1770
1771static enum test_result test_set_vbucket_out_of_range(EngineIface* h) {
1772    check(!set_vbucket_state(h, Vbid(10000), vbucket_state_active),
1773          "Shouldn't have been able to set vbucket 10000");
1774    return SUCCESS;
1775}
1776
1777static enum test_result set_max_cas_mb21190(EngineIface* h) {
1778    uint64_t max_cas = get_ull_stat(h, "vb_0:max_cas", "vbucket-details 0");
1779    std::string max_cas_str = std::to_string(max_cas+1);
1780    set_param(h,
1781              cb::mcbp::request::SetParamPayload::Type::Vbucket,
1782              "max_cas",
1783              max_cas_str.data(),
1784              Vbid(0));
1785    checkeq(cb::mcbp::Status::Success, last_status.load(),
1786            "Failed to set_param max_cas");
1787    checkeq(max_cas + 1,
1788            get_ull_stat(h, "vb_0:max_cas", "vbucket-details 0"),
1789            "max_cas didn't change");
1790    set_param(h,
1791              cb::mcbp::request::SetParamPayload::Type::Vbucket,
1792              "max_cas",
1793              max_cas_str.data(),
1794              Vbid(1));
1795    checkeq(cb::mcbp::Status::NotMyVbucket, last_status.load(),
1796            "Expected not my vbucket for vb 1");
1797    set_param(h,
1798              cb::mcbp::request::SetParamPayload::Type::Vbucket,
1799              "max_cas",
1800              "JUNK",
1801              Vbid(0));
1802    checkeq(cb::mcbp::Status::Einval, last_status.load(),
1803            "Expected EINVAL");
1804    return SUCCESS;
1805}
1806
1807static enum test_result warmup_mb21769(EngineIface* h) {
1808    if (!isWarmupEnabled(h)) {
1809        return SKIPPED;
1810    }
1811
1812    // Validate some VB data post warmup
1813    // VB 0 will be empty
1814    // VB 1 will not be empty
1815    // VB 2 will not be empty and will have had set_state as the final ops
1816
1817    check(set_vbucket_state(h, Vbid(1), vbucket_state_active),
1818          "Failed to set vbucket state for vb1");
1819    check(set_vbucket_state(h, Vbid(2), vbucket_state_active),
1820          "Failed to set vbucket state for vb2");
1821
1822    const int num_items = 10;
1823    write_items(h, num_items, 0, "vb1", "value", 0 /*expiry*/, Vbid(1));
1824    write_items(h, num_items, 0, "vb2", "value", 0 /*expiry*/, Vbid(2));
1825    wait_for_flusher_to_settle(h);
1826
1827    // flip replica to active to drive more _local writes
1828    check(set_vbucket_state(h, Vbid(2), vbucket_state_replica),
1829          "Failed to set vbucket state (replica) for vb2");
1830    wait_for_flusher_to_settle(h);
1831
1832    check(set_vbucket_state(h, Vbid(2), vbucket_state_active),
1833          "Failed to set vbucket state (replica) for vb2");
1834    wait_for_flusher_to_settle(h);
1835
1836    // Force a shutdown so the warmup will create failover entries
1837    testHarness->reload_engine(&h,
1838                               testHarness->engine_path,
1839                               testHarness->get_current_testcase()->cfg,
1840                               true,
1841                               true);
1842
1843    wait_for_warmup_complete(h);
1844
1845    // values of interested stats for each VB
1846    std::array<uint64_t, 3> high_seqnos = {{0, num_items, num_items}};
1847    std::array<uint64_t, 3> snap_starts = {{0, num_items, num_items}};
1848    std::array<uint64_t, 3> snap_ends = {{0, num_items, num_items}};
1849    // we will check the seqno of the 0th entry of each vbucket's failover table
1850    std::array<uint64_t, 3> failover_entry0 = {{0, num_items, num_items}};
1851
1852    for (uint64_t vb = 0; vb <= 2; vb++) {
1853        std::string vb_prefix = "vb_" + std::to_string(vb) + ":";
1854        std::string high_seqno = vb_prefix + "high_seqno";
1855        std::string snap_start = vb_prefix + "last_persisted_snap_start";
1856        std::string snap_end = vb_prefix + "last_persisted_snap_end";
1857        std::string fail0 = vb_prefix + "0:seq";
1858        std::string vb_group_key = "vbucket-seqno " + std::to_string(vb);
1859        std::string failovers_key = "failovers " + std::to_string(vb);
1860
1861        checkeq(high_seqnos[vb],
1862                get_ull_stat(h, high_seqno.c_str(), vb_group_key.c_str()),
1863                std::string("high_seqno incorrect vb:" + std::to_string(vb))
1864                        .c_str());
1865        checkeq(snap_starts[vb],
1866                get_ull_stat(h, snap_start.c_str(), vb_group_key.c_str()),
1867                std::string("snap_start incorrect vb:" + std::to_string(vb))
1868                        .c_str());
1869        checkeq(snap_ends[vb],
1870                get_ull_stat(h, snap_end.c_str(), vb_group_key.c_str()),
1871                std::string("snap_end incorrect vb:" + std::to_string(vb))
1872                        .c_str());
1873        auto failoverTable = get_all_stats(h, failovers_key.c_str());
1874        if (failoverTable[fail0] != std::to_string(failover_entry0[vb])) {
1875            std::cerr << "failover table entry 0 is incorrect for vb:" << vb
1876                      << " expected:" << failover_entry0[vb]
1877                      << " got:" << failoverTable[fail0]
1878                      << " dumping failover table\n";
1879            for (const auto& stat : failoverTable) {
1880                std::cerr << stat.first << ":" << stat.second << std::endl;
1881            }
1882            std::string detail = "vbucket-details " + std::to_string(vb);
1883            auto details = get_all_stats(h, detail.c_str());
1884            std::cerr << detail << std::endl;
1885            for (const auto& stat : details) {
1886                std::cerr << stat.first << ":" << stat.second << std::endl;
1887            }
1888            return FAIL;
1889        }
1890    }
1891
1892    return SUCCESS;
1893}
1894
1895/**
1896 * Callback from the document API being called after the CAS was assigned
1897 * to the object. We're allowed to modify the content, so let's just change
1898 * the string.
1899 *
1900 * @param info info about the document
1901 */
1902static uint64_t pre_link_seqno(0);
1903static void pre_link_doc_callback(item_info& info) {
1904    checkne(uint64_t(0), info.cas, "CAS value should be set");
1905    // mock the actual value so we can see it was changed
1906    memcpy(info.value[0].iov_base, "valuesome", 9);
1907    pre_link_seqno = info.seqno;
1908}
1909
1910/**
1911 * Verify that we've hooked into the checkpoint and that the pre-link
1912 * document api method is called.
1913 */
1914static test_result pre_link_document(EngineIface* h) {
1915    item_info info;
1916
1917    PreLinkFunction function = pre_link_doc_callback;
1918    testHarness->set_pre_link_function(function);
1919    checkeq(ENGINE_SUCCESS,
1920            store(h, nullptr, OPERATION_SET, "key", "somevalue"),
1921            "Failed set.");
1922    testHarness->set_pre_link_function({});
1923
1924    // Fetch the value and verify that the callback was called!
1925    auto ret = get(h, nullptr, "key", Vbid(0));
1926    checkeq(cb::engine_errc::success, ret.first, "get failed");
1927    check(h->get_item_info(ret.second.get(), &info),
1928          "Failed to get item info.");
1929    checkeq(0, memcmp(info.value[0].iov_base, "valuesome", 9),
1930           "Expected value to be modified");
1931    checkeq(pre_link_seqno, info.seqno, "Sequence numbers should match");
1932
1933    return SUCCESS;
1934}
1935
1936/**
1937 * verify that get_if works as expected
1938 */
1939static test_result get_if(EngineIface* h) {
1940    const std::string key("get_if");
1941
1942    checkeq(ENGINE_SUCCESS,
1943            store(h, nullptr, OPERATION_SET, key.c_str(), "somevalue"),
1944            "Failed set.");
1945
1946    if (isPersistentBucket(h)) {
1947        wait_for_flusher_to_settle(h);
1948        evict_key(h, key.c_str(), Vbid(0), "Ejected.");
1949    }
1950
1951    const auto* cookie = testHarness->create_cookie();
1952    auto doc = h->get_if(cookie,
1953                         DocKey(key, DocKeyEncodesCollectionId::No),
1954                         Vbid(0),
1955                         [](const item_info&) { return true; });
1956    check(doc.second, "document should be found");
1957
1958    doc = h->get_if(cookie,
1959                    DocKey(key, DocKeyEncodesCollectionId::No),
1960                    Vbid(0),
1961                    [](const item_info&) { return false; });
1962    check(!doc.second, "document should not be found");
1963
1964    doc = h->get_if(cookie,
1965                    DocKey("no", DocKeyEncodesCollectionId::No),
1966                    Vbid(0),
1967                    [](const item_info&) { return true; });
1968    check(!doc.second, "non-existing document should not be found");
1969
1970    checkeq(ENGINE_SUCCESS,
1971            del(h, key.c_str(), 0, Vbid(0)),
1972            "Failed remove with value");
1973
1974    doc = h->get_if(cookie,
1975                    DocKey(key, DocKeyEncodesCollectionId::No),
1976                    Vbid(0),
1977                    [](const item_info&) { return true; });
1978    check(!doc.second, "deleted document should not be found");
1979
1980    testHarness->destroy_cookie(cookie);
1981
1982    return SUCCESS;
1983}
1984
1985static test_result max_ttl_out_of_range(EngineIface* h) {
1986    // Test absolute first as this is the bigger time travel
1987    check(!set_param(h,
1988                     cb::mcbp::request::SetParamPayload::Type::Flush,
1989                     "max_ttl",
1990                     "-1"),
1991          "Should not be allowed to set a negative value");
1992    check(!set_param(h,
1993                     cb::mcbp::request::SetParamPayload::Type::Flush,
1994                     "max_ttl",
1995                     "2147483648"),
1996          "Should not be allowed to set > int32::max");
1997
1998    return SUCCESS;
1999}
2000
2001static test_result max_ttl(EngineIface* h) {
2002    // Make limit be greater than 30 days in seconds so that ep-engine must
2003    // create a absolute expiry time internally.
2004    const int absoluteExpiry = (60 * 60 * 24 * 31);
2005    auto absoluteExpiryStr = std::to_string(absoluteExpiry);
2006
2007    const int relativeExpiry = 100;
2008    auto relativeExpiryStr = std::to_string(relativeExpiry);
2009
2010    checkeq(0, get_int_stat(h, "ep_max_ttl"), "max_ttl should be 0");
2011
2012    // Test absolute first as this is the bigger time travel
2013    check(set_param(h,
2014                    cb::mcbp::request::SetParamPayload::Type::Flush,
2015                    "max_ttl",
2016                    absoluteExpiryStr.c_str()),
2017          "Failed to set max_ttl");
2018    checkeq(absoluteExpiry,
2019            get_int_stat(h, "ep_max_ttl"),
2020            "max_ttl didn't change");
2021
2022    // Store will set 0 expiry, which results in 100 seconds of ttl
2023    checkeq(ENGINE_SUCCESS,
2024            store(h,
2025                  nullptr,
2026                  OPERATION_SET,
2027                  "key-abs",
2028                  "somevalue",
2029                  nullptr,
2030                  0,
2031                  Vbid(0),
2032                  0 /*exp*/),
2033            "Failed set.");
2034
2035    cb::EngineErrorMetadataPair errorMetaPair;
2036    check(get_meta(h, "key-abs", errorMetaPair), "Get meta failed");
2037    checkne(time_t(0),
2038            errorMetaPair.second.exptime,
2039            "expiry should not be zero");
2040
2041    // Force expiry
2042    testHarness->time_travel(absoluteExpiry + 1);
2043
2044    auto ret = get(h, NULL, "key-abs", Vbid(0));
2045    checkeq(cb::engine_errc::no_such_key,
2046            ret.first,
2047            "Failed, expected no_such_key.");
2048
2049    check(set_param(h,
2050                    cb::mcbp::request::SetParamPayload::Type::Flush,
2051                    "max_ttl",
2052                    relativeExpiryStr.c_str()),
2053          "Failed to set max_ttl");
2054    checkeq(relativeExpiry,
2055            get_int_stat(h, "ep_max_ttl"),
2056            "max_ttl didn't change");
2057
2058    // Store will set 0 expiry, which results in 100 seconds of ttl
2059    checkeq(ENGINE_SUCCESS,
2060            store(h,
2061                  nullptr,
2062                  OPERATION_SET,
2063                  "key-rel",
2064                  "somevalue",
2065                  nullptr,
2066                  0,
2067                  Vbid(0),
2068                  0 /*exp*/),
2069            "Failed set.");
2070
2071    check(get_meta(h, "key-rel", errorMetaPair), "Get meta failed");
2072    checkne(time_t(0),
2073            errorMetaPair.second.exptime,
2074            "expiry should not be zero");
2075
2076    // Force expiry
2077    testHarness->time_travel(relativeExpiry + 1);
2078
2079    ret = get(h, NULL, "key-rel", Vbid(0));
2080    checkeq(cb::engine_errc::no_such_key,
2081            ret.first,
2082            "Failed, expected no_such_key.");
2083
2084    return SUCCESS;
2085}
2086
2087static test_result max_ttl_setWithMeta(EngineIface* h) {
2088    // Make limit be greater than 30 days in seconds so that ep-engine must
2089    // create a absolute expiry time internally.
2090    const int absoluteExpiry = (60 * 60 * 24 * 31);
2091    auto absoluteExpiryStr = std::to_string(absoluteExpiry);
2092    std::string keyAbs = "key-abs";
2093
2094    const int relativeExpiry = 100;
2095    auto relativeExpiryStr = std::to_string(relativeExpiry);
2096    std::string keyRel = "key-rel";
2097
2098    checkeq(0, get_int_stat(h, "ep_max_ttl"), "max_ttl should be 0");
2099
2100    // Test absolute first as this is the bigger time travel
2101    check(set_param(h,
2102                    cb::mcbp::request::SetParamPayload::Type::Flush,
2103                    "max_ttl",
2104                    absoluteExpiryStr.c_str()),
2105          "Failed to set max_ttl");
2106    checkeq(absoluteExpiry,
2107            get_int_stat(h, "ep_max_ttl"),
2108            "max_ttl didn't change");
2109
2110    // SWM with 0 expiry which results in an expiry being set
2111    ItemMetaData itemMeta(0xdeadbeef, 10, 0xf1a95, 0 /*expiry*/);
2112    checkeq(ENGINE_SUCCESS,
2113            set_with_meta(h,
2114                          keyAbs.c_str(),
2115                          keyAbs.size(),
2116                          keyAbs.c_str(),
2117                          keyAbs.size(),
2118                          Vbid(0),
2119                          &itemMeta,
2120                          0 /*cas*/),
2121            "Expected to store item");
2122
2123    cb::EngineErrorMetadataPair errorMetaPair;
2124    check(get_meta(h, keyAbs.c_str(), errorMetaPair), "Get meta failed");
2125    checkne(time_t(0),
2126            errorMetaPair.second.exptime,
2127            "expiry should not be zero");
2128
2129    // Force expiry
2130    testHarness->time_travel(absoluteExpiry + 1);
2131
2132    auto ret = get(h, NULL, keyAbs.c_str(), Vbid(0));
2133    checkeq(cb::engine_errc::no_such_key,
2134            ret.first,
2135            "Failed, expected no_such_key.");
2136
2137    check(set_param(h,
2138                    cb::mcbp::request::SetParamPayload::Type::Flush,
2139                    "max_ttl",
2140                    relativeExpiryStr.c_str()),
2141          "Failed to set max_ttl");
2142    checkeq(relativeExpiry,
2143            get_int_stat(h, "ep_max_ttl"),
2144            "max_ttl didn't change");
2145
2146    checkeq(ENGINE_SUCCESS,
2147            set_with_meta(h,
2148                          keyRel.c_str(),
2149                          keyRel.size(),
2150                          keyRel.c_str(),
2151                          keyRel.size(),
2152                          Vbid(0),
2153                          &itemMeta,
2154                          0 /*cas*/),
2155            "Expected to store item");
2156
2157    check(get_meta(h, keyRel.c_str(), errorMetaPair), "Get meta failed");
2158    checkne(time_t(0),
2159            errorMetaPair.second.exptime,
2160            "expiry should not be zero");
2161
2162    // Force expiry
2163    testHarness->time_travel(relativeExpiry + 1);
2164
2165    ret = get(h, NULL, keyRel.c_str(), Vbid(0));
2166    checkeq(cb::engine_errc::no_such_key,
2167            ret.first,
2168            "Failed, expected no_such_key.");
2169
2170    // Final test, exceed the maxTTL and check we got capped!
2171#if 0
2172    // TN: This piece of the test is broken as the set call fails with
2173    //     KeyEExists.. I haven't looked in details on what we're actually
2174    //     trying to test.. Disable for now
2175    itemMeta.exptime = errorMetaPair.second.exptime + 1000;
2176    checkeq(ENGINE_SUCCESS,
2177            set_with_meta(h,
2178                          keyRel.c_str(),
2179                          keyRel.size(),
2180                          keyRel.c_str(),
2181                          keyRel.size(),
2182                          Vbid(0),
2183                          &itemMeta,
2184                          0 /*cas*/),
2185            "Expected to store item");
2186
2187    check(get_meta(h, keyRel.c_str(), errorMetaPair), "Get meta failed");
2188    checkne(itemMeta.exptime,
2189            errorMetaPair.second.exptime,
2190            "expiry should have been changed/capped");
2191#endif
2192    return SUCCESS;
2193}
2194
2195///////////////////////////////////////////////////////////////////////////////
2196// Test manifest //////////////////////////////////////////////////////////////
2197///////////////////////////////////////////////////////////////////////////////
2198
2199const char *default_dbname = "./ep_testsuite_basic";
2200
2201BaseTestCase testsuite_testcases[] = {
2202        TestCase("test alloc limit",
2203                 test_alloc_limit,
2204                 test_setup,
2205                 teardown,
2206                 NULL,
2207                 prepare,
2208                 cleanup),
2209        TestCase("test_memory_tracking",
2210                 test_memory_tracking,
2211                 test_setup,
2212                 teardown,
2213                 NULL,
2214                 prepare,
2215                 cleanup),
2216        TestCase("test max_size - water_mark changes",
2217                 test_max_size_and_water_marks_settings,
2218                 test_setup,
2219                 teardown,
2220                 "max_size=1000;ht_locks=1;ht_size=3",
2221                 prepare,
2222                 cleanup),
2223        TestCase("test whitespace dbname",
2224                 test_whitespace_db,
2225                 test_setup,
2226                 teardown,
2227                 "dbname=" WHITESPACE_DB ";ht_locks=1;ht_size=3",
2228                 prepare,
2229                 cleanup),
2230        TestCase("get miss",
2231                 test_get_miss,
2232                 test_setup,
2233                 teardown,
2234                 NULL,
2235                 prepare,
2236                 cleanup),
2237        TestCase("set", test_set, test_setup, teardown, NULL, prepare, cleanup),
2238        TestCase("concurrent set",
2239                 test_conc_set,
2240                 test_setup,
2241                 teardown,
2242                 NULL,
2243                 prepare,
2244                 cleanup),
2245        TestCase("multi set",
2246                 test_multi_set,
2247                 test_setup,
2248                 teardown,
2249                 NULL,
2250                 prepare_ep_bucket_skip_broken_under_magma, // MB-36547
2251                 cleanup),
2252        TestCase("set+get hit",
2253                 test_set_get_hit,
2254                 test_setup,
2255                 teardown,
2256                 NULL,
2257                 prepare,
2258                 cleanup),
2259        TestCase("test getl then del with cas",
2260                 test_getl_delete_with_cas,
2261                 test_setup,
2262                 teardown,
2263                 NULL,
2264                 prepare,
2265                 cleanup),
2266        TestCase("test getl then del with bad cas",
2267                 test_getl_delete_with_bad_cas,
2268                 test_setup,
2269                 teardown,
2270                 NULL,
2271                 prepare,
2272                 cleanup),
2273        TestCase("test getl then set with meta",
2274                 test_getl_set_del_with_meta,
2275                 test_setup,
2276                 teardown,
2277                 NULL,
2278                 prepare,
2279                 cleanup),
2280        TestCase("getl",
2281                 test_getl,
2282                 test_setup,
2283                 teardown,
2284                 NULL,
2285                 prepare,
2286                 cleanup),
2287        TestCase("unl", test_unl, test_setup, teardown, NULL, prepare, cleanup),
2288        TestCase("unl not my vbucket",
2289                 test_unl_nmvb,
2290                 test_setup,
2291                 teardown,
2292                 NULL,
2293                 prepare,
2294                 cleanup),
2295        TestCase("set+get hit (bin)",
2296                 test_set_get_hit_bin,
2297                 test_setup,
2298                 teardown,
2299                 NULL,
2300                 prepare,
2301                 cleanup),
2302        TestCase("set with cas non-existent",
2303                 test_set_with_cas_non_existent,
2304                 test_setup,
2305                 teardown,
2306                 NULL,
2307                 prepare,
2308                 cleanup),
2309        TestCase("set+change flags",
2310                 test_set_change_flags,
2311                 test_setup,
2312                 teardown,
2313                 NULL,
2314                 prepare,
2315                 cleanup),
2316        TestCase("add", test_add, test_setup, teardown, NULL, prepare, cleanup),
2317        TestCase("add+add(same cas)",
2318                 test_add_add_with_cas,
2319                 test_setup,
2320                 teardown,
2321                 NULL,
2322                 prepare,
2323                 cleanup),
2324        TestCase("cas", test_cas, test_setup, teardown, NULL, prepare, cleanup),
2325        TestCase("replace",
2326                 test_replace,
2327                 test_setup,
2328                 teardown,
2329                 NULL,
2330                 prepare,
2331                 cleanup),
2332        TestCase("test touch",
2333                 test_touch,
2334                 test_setup,
2335                 teardown,
2336                 NULL,
2337                 prepare,
2338                 cleanup),
2339        TestCase("test touch (MB-7342)",
2340                 test_touch_mb7342,
2341                 test_setup,
2342                 teardown,
2343                 NULL,
2344                 prepare,
2345                 cleanup),
2346        TestCase("test touch (MB-10277)",
2347                 test_touch_mb10277,
2348                 test_setup,
2349                 teardown,
2350                 NULL,
2351                 prepare_ep_bucket,
2352                 cleanup),
2353        TestCase("test gat",
2354                 test_gat,
2355                 test_setup,
2356                 teardown,
2357                 NULL,
2358                 prepare,
2359                 cleanup),
2360        TestCase("test locked gat",
2361                 test_gat_locked,
2362                 test_setup,
2363                 teardown,
2364                 NULL,
2365                 prepare,
2366                 cleanup),
2367        TestCase("test locked touch",
2368                 test_touch_locked,
2369                 test_setup,
2370                 teardown,
2371                 NULL,
2372                 prepare,
2373                 cleanup),
2374        TestCase("test mb5215",
2375                 test_mb5215,
2376                 test_setup,
2377                 teardown,
2378                 NULL,
2379                 // TODO RDB: implement getItemCount. Needs the
2380                 // 'ep_num_non_resident' stat.
2381                 prepare_ep_bucket_skip_broken_under_rocks,
2382                 cleanup),
2383        TestCase("delete",
2384                 test_delete,
2385                 test_setup,
2386                 teardown,
2387                 NULL,
2388                 prepare,
2389                 cleanup),
2390        TestCase("delete with value",
2391                 test_delete_with_value,
2392                 test_setup,
2393                 teardown,
2394                 NULL,
2395                 /* TODO RDB: vBucket num_items not correct under Rocks when
2396                  * full eviction */
2397                 prepare_skip_broken_under_rocks_full_eviction,
2398                 cleanup),
2399        TestCase("delete with value CAS",
2400                 test_delete_with_value_cas,
2401                 test_setup,
2402                 teardown,
2403                 NULL,
2404                 prepare,
2405                 cleanup),
2406        TestCase("set/delete",
2407                 test_set_delete,
2408                 test_setup,
2409                 teardown,
2410                 NULL,
2411                 prepare,
2412                 cleanup),
2413        TestCase("set/delete (invalid cas)",
2414                 test_set_delete_invalid_cas,
2415                 test_setup,
2416                 teardown,
2417                 NULL,
2418                 prepare,
2419                 cleanup),
2420        TestCase("delete/set/delete",
2421                 test_delete_set,
2422                 test_setup,
2423                 teardown,
2424                 NULL,
2425                 prepare,
2426                 cleanup),
2427        TestCase("get/delete with missing db file",
2428                 test_get_delete_missing_file,
2429                 test_setup,
2430                 teardown,
2431                 NULL,
2432                 // TODO RDB: This test fails because under RocksDB we can
2433                 // still find a key after deleting the DB file and evicting
2434                 // the key in the internal MemTable (which is also used as
2435                 // read-cache).
2436                 // TODO magma: uses couchstore specific functions
2437                 prepare_ep_bucket_skip_broken_under_rocks_and_magma,
2438                 cleanup),
2439        TestCase("vbucket deletion doesn't affect new data",
2440                 test_bug7023,
2441                 test_setup,
2442                 teardown,
2443                 NULL,
2444                 // TODO RDB: implement getItemCount. Needs the
2445                 // 'ep_warmup_value_count' stat.
2446                 prepare_skip_broken_under_rocks,
2447                 cleanup),
2448        TestCase("non-resident decrementers",
2449                 test_mb3169,
2450                 test_setup,
2451                 teardown,
2452                 NULL,
2453                 // TODO RDB: implement getItemCount. Needs the 'curr_items'
2454                 // stat.
2455                 prepare_ep_bucket_skip_broken_under_rocks,
2456                 cleanup),
2457        TestCase("resident ratio after warmup",
2458                 test_mb5172,
2459                 test_setup,
2460                 teardown,
2461                 NULL,
2462                 prepare,
2463                 cleanup),
2464        TestCase("set vb 10000",
2465                 test_set_vbucket_out_of_range,
2466                 test_setup,
2467                 teardown,
2468                 "max_vbuckets=1024",
2469                 prepare,
2470                 cleanup),
2471        TestCase("set max_cas MB21190",
2472                 set_max_cas_mb21190,
2473                 test_setup,
2474                 teardown,
2475                 nullptr,
2476                 prepare,
2477                 cleanup),
2478        TestCase("warmup_mb21769",
2479                 warmup_mb21769,
2480                 test_setup,
2481                 teardown,
2482                 nullptr,
2483                 prepare,
2484                 cleanup),
2485
2486        TestCase("pre_link_document",
2487                 pre_link_document,
2488                 test_setup,
2489                 teardown,
2490                 nullptr,
2491                 prepare,
2492                 cleanup),
2493
2494        TestCase("engine get_if",
2495                 get_if,
2496                 test_setup,
2497                 teardown,
2498                 nullptr,
2499                 prepare,
2500                 cleanup),
2501
2502        TestCase("test max_ttl range",
2503                 max_ttl_out_of_range,
2504                 test_setup,
2505                 teardown,
2506                 nullptr,
2507                 prepare,
2508                 cleanup),
2509
2510        TestCase("test max_ttl",
2511                 max_ttl,
2512                 test_setup,
2513                 teardown,
2514                 nullptr,
2515                 prepare,
2516                 cleanup),
2517
2518        TestCase("test max_ttl_setWithMeta",
2519                 max_ttl_setWithMeta,
2520                 test_setup,
2521                 teardown,
2522                 nullptr,
2523                 prepare,
2524                 cleanup),
2525
2526        // sentinel
2527        TestCase(NULL, NULL, NULL, NULL, NULL, prepare, cleanup)};
2528