1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2017 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 * Unit tests for the KVBucket class.
20 */
21
22#include "kv_bucket_test.h"
23
24#include "../mock/mock_dcp_producer.h"
25#include "access_scanner.h"
26#include "bgfetcher.h"
27#include "checkpoint.h"
28#include "checkpoint_remover.h"
29#include "dcp/dcpconnmap.h"
30#include "dcp/flow-control-manager.h"
31#include "ep_engine.h"
32#include "ep_time.h"
33#include "flusher.h"
34#include "globaltask.h"
35#include "lambda_task.h"
36#include "replicationthrottle.h"
37#include "tasks.h"
38#include "tests/mock/mock_global_task.h"
39#include "tests/module_tests/test_helpers.h"
40#include "vbucketdeletiontask.h"
41#include "warmup.h"
42
43#include <platform/dirutils.h>
44#include <chrono>
45#include <thread>
46
47#include <string_utilities.h>
48#include <xattr/blob.h>
49#include <xattr/utils.h>
50
51void KVBucketTest::SetUp() {
52    // Paranoia - kill any existing files in case they are left over
53    // from a previous run.
54    try {
55        cb::io::rmrf(test_dbname);
56    } catch (std::system_error& e) {
57        if (e.code() != std::error_code(ENOENT, std::system_category())) {
58            throw e;
59        }
60    }
61
62    initialise(config_string);
63
64    if (completeWarmup && engine->getKVBucket()->getWarmup()) {
65        engine->getKVBucket()->getWarmup()->setComplete();
66        engine->getKVBucket()->getWarmup()->processCreateVBucketsComplete();
67    }
68}
69
70void KVBucketTest::initialise(std::string config) {
71    // Add dbname to config string.
72    if (config.size() > 0) {
73        config += ";";
74    }
75    config += "dbname=" + std::string(test_dbname);
76
77    // Need to initialize ep_real_time and friends.
78    initialize_time_functions(get_mock_server_api()->core);
79
80    engine = SynchronousEPEngine::build(config);
81
82    store = engine->getKVBucket();
83
84    store->chkTask = std::make_shared<ClosedUnrefCheckpointRemoverTask>(
85            engine.get(),
86            engine->getEpStats(),
87            engine->getConfiguration().getChkRemoverStime());
88
89    cookie = create_mock_cookie();
90}
91
92void KVBucketTest::TearDown() {
93    destroy();
94    // Shutdown the ExecutorPool singleton (initialized when we create
95    // an EPBucket object). Must happen after engine
96    // has been destroyed (to allow the tasks the engine has
97    // registered a chance to be unregistered).
98    ExecutorPool::shutdown();
99}
100
101void KVBucketTest::destroy() {
102    destroy_mock_cookie(cookie);
103    destroy_mock_event_callbacks();
104    engine->getDcpConnMap().manageConnections();
105    ObjectRegistry::onSwitchThread(nullptr);
106    engine.reset();
107}
108
109void KVBucketTest::reinitialise(std::string config) {
110    destroy();
111    initialise(config);
112}
113
114Item KVBucketTest::store_item(uint16_t vbid,
115                              const DocKey& key,
116                              const std::string& value,
117                              uint32_t exptime,
118                              const std::vector<cb::engine_errc>& expected,
119                              protocol_binary_datatype_t datatype) {
120    auto item = make_item(vbid, key, value, exptime, datatype);
121    auto returnCode = store->set(item, cookie);
122    EXPECT_NE(expected.end(),
123              std::find(expected.begin(),
124                        expected.end(),
125                        cb::engine_errc(returnCode)));
126    return item;
127}
128
129::testing::AssertionResult KVBucketTest::store_items(
130        int nitems,
131        uint16_t vbid,
132        const DocKey& key,
133        const std::string& value,
134        uint32_t exptime,
135        protocol_binary_datatype_t datatype) {
136    for (int ii = 0; ii < nitems; ii++) {
137        auto keyii = makeStoredDocKey(
138                std::string(reinterpret_cast<const char*>(key.data()),
139                            key.size()) +
140                        std::to_string(ii),
141                key.getDocNamespace());
142        auto item = make_item(vbid, keyii, value, exptime, datatype);
143        auto err = store->set(item, cookie);
144        if (ENGINE_SUCCESS != err) {
145            return ::testing::AssertionFailure()
146                   << "Failed to store " << keyii.data() << " error:" << err;
147        }
148    }
149    return ::testing::AssertionSuccess();
150}
151
152void KVBucketTest::flush_vbucket_to_disk(uint16_t vbid, size_t expected) {
153    size_t actualFlushed = 0;
154    const auto time_limit = std::chrono::seconds(10);
155    const auto deadline = std::chrono::steady_clock::now() + time_limit;
156
157    // Need to retry as warmup may not have completed, or if the flush is
158    // in multiple parts.
159    bool flush_successful = false;
160    bool moreAvailable;
161    do {
162        size_t count;
163        std::tie(moreAvailable, count) =
164                dynamic_cast<EPBucket&>(*store).flushVBucket(vbid);
165        actualFlushed += count;
166        if (!moreAvailable) {
167            flush_successful = true;
168            break;
169        }
170        std::this_thread::sleep_for(std::chrono::microseconds(100));
171    } while ((std::chrono::steady_clock::now() < deadline) && moreAvailable);
172
173    ASSERT_TRUE(flush_successful)
174            << "Hit timeout (" << time_limit.count()
175            << " seconds) waiting for "
176               "warmup to complete while flushing VBucket.";
177
178    ASSERT_EQ(expected, actualFlushed)
179            << "Unexpected items (" << actualFlushed
180            << ") in flush_vbucket_to_disk(" << vbid << ", " << expected << ")";
181}
182
183void KVBucketTest::flushVBucketToDiskIfPersistent(uint16_t vbid, int expected) {
184    if (engine->getConfiguration().getBucketType() == "persistent") {
185        flush_vbucket_to_disk(vbid, expected);
186    }
187}
188
189void KVBucketTest::delete_item(uint16_t vbid, const DocKey& key) {
190    uint64_t cas = 0;
191    mutation_descr_t mutation_descr;
192    EXPECT_EQ(ENGINE_SUCCESS,
193              store->deleteItem(key,
194                                cas,
195                                vbid,
196                                cookie,
197                                /*itemMeta*/ nullptr,
198                                mutation_descr));
199}
200
201void KVBucketTest::evict_key(uint16_t vbid, const DocKey& key) {
202    const char* msg;
203    EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS,
204              store->evictKey(key, vbid, &msg));
205    EXPECT_STREQ("Ejected.", msg);
206}
207
208GetValue KVBucketTest::getInternal(const DocKey& key,
209                                   uint16_t vbucket,
210                                   const void* cookie,
211                                   vbucket_state_t allowedState,
212                                   get_options_t options) {
213    return store->getInternal(key, vbucket, cookie, allowedState, options);
214}
215
216void KVBucketTest::scheduleItemPager() {
217    ExecutorPool::get()->schedule(store->itemPagerTask);
218}
219
220void KVBucketTest::initializeExpiryPager() {
221    store->initializeExpiryPager(engine->getConfiguration());
222}
223
224bool KVBucketTest::isItemFreqDecayerTaskSnoozed() const {
225    return store->isItemFreqDecayerTaskSnoozed();
226}
227
228void KVBucketTest::runBGFetcherTask() {
229    MockGlobalTask mockTask(engine->getTaskable(),
230                            TaskId::MultiBGFetcherTask);
231    store->getVBucket(vbid)->getShard()->getBgFetcher()->run(&mockTask);
232}
233
234/**
235 * Create a del_with_meta packet with the key/body (body can be empty)
236 */
237std::vector<char> KVBucketTest::buildWithMetaPacket(
238        protocol_binary_command opcode,
239        protocol_binary_datatype_t datatype,
240        uint16_t vbucket,
241        uint32_t opaque,
242        uint64_t cas,
243        ItemMetaData metaData,
244        const std::string& key,
245        const std::string& body,
246        const std::vector<char>& emd,
247        int options) {
248    EXPECT_EQ(sizeof(protocol_binary_request_set_with_meta),
249              sizeof(protocol_binary_request_delete_with_meta));
250
251    size_t size = sizeof(protocol_binary_request_set_with_meta);
252    // body at least the meta
253    size_t extlen = (sizeof(uint32_t) * 2) + (sizeof(uint64_t) * 2);
254    size_t bodylen = extlen;
255    if (options) {
256        size += sizeof(uint32_t);
257        bodylen += sizeof(uint32_t);
258        extlen += sizeof(uint32_t);
259    }
260    if (!emd.empty()) {
261        EXPECT_TRUE(emd.size() < std::numeric_limits<uint16_t>::max());
262        size += sizeof(uint16_t) + emd.size();
263        bodylen += sizeof(uint16_t) + emd.size();
264        extlen += sizeof(uint16_t);
265    }
266    size += body.size();
267    bodylen += body.size();
268    size += key.size();
269    bodylen += key.size();
270
271    protocol_binary_request_set_with_meta header;
272    header.message.header.request.magic = PROTOCOL_BINARY_REQ;
273    header.message.header.request.opcode = opcode;
274    header.message.header.request.keylen = htons(key.size());
275    header.message.header.request.extlen = uint8_t(extlen);
276    header.message.header.request.datatype = datatype;
277    header.message.header.request.vbucket = htons(vbucket);
278    header.message.header.request.bodylen = htonl(bodylen);
279    header.message.header.request.opaque = opaque;
280    header.message.header.request.cas = htonll(cas);
281    header.message.body.flags = metaData.flags;
282    header.message.body.expiration = htonl(metaData.exptime);
283    header.message.body.seqno = htonll(metaData.revSeqno);
284    header.message.body.cas = htonll(metaData.cas);
285
286    std::vector<char> packet;
287    packet.reserve(size);
288    packet.insert(packet.end(),
289                  reinterpret_cast<char*>(&header),
290                  reinterpret_cast<char*>(&header) +
291                          sizeof(protocol_binary_request_set_with_meta));
292
293    if (options) {
294        options = htonl(options);
295        std::copy_n(reinterpret_cast<char*>(&options),
296                    sizeof(uint32_t),
297                    std::back_inserter(packet));
298    }
299
300    if (!emd.empty()) {
301        uint16_t emdSize = htons(emd.size());
302        std::copy_n(reinterpret_cast<char*>(&emdSize),
303                    sizeof(uint16_t),
304                    std::back_inserter(packet));
305    }
306
307    std::copy_n(key.c_str(), key.size(), std::back_inserter(packet));
308    std::copy_n(body.c_str(), body.size(), std::back_inserter(packet));
309    packet.insert(packet.end(), emd.begin(), emd.end());
310    return packet;
311}
312
313bool KVBucketTest::addResponse(const void* k,
314                               uint16_t keylen,
315                               const void* ext,
316                               uint8_t extlen,
317                               const void* body,
318                               uint32_t bodylen,
319                               uint8_t datatype,
320                               uint16_t status,
321                               uint64_t pcas,
322                               const void* cookie) {
323    addResponseStatus = protocol_binary_response_status(status);
324    return true;
325}
326
327protocol_binary_response_status KVBucketTest::getAddResponseStatus(
328        protocol_binary_response_status newval) {
329    protocol_binary_response_status rv = addResponseStatus;
330    addResponseStatus = newval;
331    return rv;
332}
333
334protocol_binary_response_status KVBucketTest::addResponseStatus =
335        PROTOCOL_BINARY_RESPONSE_SUCCESS;
336
337void KVBucketTest::setRandomFunction(std::function<long()>& randFunction) {
338    store->getRandom = randFunction;
339}
340
341// getKeyStats tests //////////////////////////////////////////////////////////
342
343// Check that keystats on resident items works correctly.
344TEST_P(KVBucketParamTest, GetKeyStatsResident) {
345    key_stats kstats;
346
347    // Should start with key not existing.
348    EXPECT_EQ(ENGINE_KEY_ENOENT,
349              store->getKeyStats(makeStoredDocKey("key"),
350                                 0,
351                                 cookie,
352                                 kstats,
353                                 WantsDeleted::No));
354
355    store_item(0, makeStoredDocKey("key"), "value");
356    EXPECT_EQ(ENGINE_SUCCESS,
357              store->getKeyStats(makeStoredDocKey("key"),
358                                 0,
359                                 cookie,
360                                 kstats,
361                                 WantsDeleted::No))
362            << "Expected to get key stats on existing item";
363    EXPECT_EQ(vbucket_state_active, kstats.vb_state);
364    EXPECT_FALSE(kstats.logically_deleted);
365}
366
367// Create then delete an item, checking we get keyStats reporting the item as
368// deleted.
369TEST_P(KVBucketParamTest, GetKeyStatsDeleted) {
370    auto& kvbucket = *engine->getKVBucket();
371    key_stats kstats;
372
373    store_item(0, makeStoredDocKey("key"), "value");
374    delete_item(vbid, makeStoredDocKey("key"));
375
376    // Should get ENOENT if we don't ask for deleted items.
377    EXPECT_EQ(ENGINE_KEY_ENOENT,
378              kvbucket.getKeyStats(makeStoredDocKey("key"),
379                                   0,
380                                   cookie,
381                                   kstats,
382                                   WantsDeleted::No));
383
384    // Should get success (and item flagged as deleted) if we ask for deleted
385    // items.
386    EXPECT_EQ(ENGINE_SUCCESS,
387              kvbucket.getKeyStats(makeStoredDocKey("key"),
388                                   0,
389                                   cookie,
390                                   kstats,
391                                   WantsDeleted::Yes));
392    EXPECT_EQ(vbucket_state_active, kstats.vb_state);
393    EXPECT_TRUE(kstats.logically_deleted);
394}
395
396// Check incorrect vbucket returns not-my-vbucket.
397TEST_P(KVBucketParamTest, GetKeyStatsNMVB) {
398    auto& kvbucket = *engine->getKVBucket();
399    key_stats kstats;
400
401    EXPECT_EQ(ENGINE_NOT_MY_VBUCKET,
402              kvbucket.getKeyStats(makeStoredDocKey("key"),
403                                   1,
404                                   cookie,
405                                   kstats,
406                                   WantsDeleted::No));
407}
408
409// Replace tests //////////////////////////////////////////////////////////////
410
411// Test replace against a non-existent key.
412TEST_P(KVBucketParamTest, ReplaceENOENT) {
413    // Should start with key not existing (and hence cannot replace).
414    auto item = make_item(vbid, makeStoredDocKey("key"), "value");
415    EXPECT_EQ(ENGINE_KEY_ENOENT, store->replace(item, cookie));
416}
417
418// Create then delete an item, checking replace reports ENOENT.
419TEST_P(KVBucketParamTest, ReplaceDeleted) {
420    store_item(vbid, makeStoredDocKey("key"), "value");
421    delete_item(vbid, makeStoredDocKey("key"));
422
423    // Replace should fail.
424    auto item = make_item(vbid, makeStoredDocKey("key"), "value2");
425    EXPECT_EQ(ENGINE_KEY_ENOENT, store->replace(item, cookie));
426}
427
428// Check incorrect vbucket returns not-my-vbucket.
429TEST_P(KVBucketParamTest, ReplaceNMVB) {
430    auto item = make_item(vbid + 1, makeStoredDocKey("key"), "value2");
431    EXPECT_EQ(ENGINE_NOT_MY_VBUCKET, store->replace(item, cookie));
432}
433
434// Check pending vbucket returns EWOULDBLOCK.
435TEST_P(KVBucketParamTest, ReplacePendingVB) {
436    store->setVBucketState(vbid, vbucket_state_pending, false);
437    auto item = make_item(vbid, makeStoredDocKey("key"), "value2");
438    EXPECT_EQ(ENGINE_EWOULDBLOCK, store->replace(item, cookie));
439}
440
441// Set tests //////////////////////////////////////////////////////////////////
442
443// Test CAS set against a non-existent key
444TEST_P(KVBucketParamTest, SetCASNonExistent) {
445    // Create an item with a non-zero CAS.
446    auto item = make_item(vbid, makeStoredDocKey("key"), "value");
447    item.setCas();
448    ASSERT_NE(0, item.getCas());
449
450    // Should get ENOENT as we should immediately know (either from metadata
451    // being resident, or by bloomfilter) that key doesn't exist.
452    EXPECT_EQ(ENGINE_KEY_ENOENT, store->set(item, cookie));
453}
454
455// Test CAS set against a deleted item
456TEST_P(KVBucketParamTest, SetCASDeleted) {
457    auto key = makeStoredDocKey("key");
458    auto item = make_item(vbid, key, "value");
459
460    // Store item
461    EXPECT_EQ(ENGINE_SUCCESS, store->set(item, cookie));
462
463    // Delete item
464    uint64_t cas = 0;
465    mutation_descr_t mutation_descr;
466    EXPECT_EQ(ENGINE_SUCCESS,
467              store->deleteItem(key,
468                                cas,
469                                vbid,
470                                cookie,
471                                /*itemMeta*/ nullptr,
472                                mutation_descr));
473
474    if (engine->getConfiguration().getBucketType() == "persistent") {
475        // Trigger a flush to disk.
476        flush_vbucket_to_disk(vbid);
477    }
478
479    // check we have the cas
480    ASSERT_NE(0, cas);
481
482    auto item2 = make_item(vbid, key, "value2");
483    item2.setCas(cas);
484
485    // Store item
486    if (engine->getConfiguration().getItemEvictionPolicy() ==
487               "full_eviction") {
488        EXPECT_EQ(ENGINE_EWOULDBLOCK, store->set(item2, cookie));
489
490        // Manually run the bgfetch task.
491        MockGlobalTask mockTask(engine->getTaskable(),
492                                TaskId::MultiBGFetcherTask);
493        store->getVBucket(vbid)->getShard()->getBgFetcher()->run(&mockTask);
494    }
495
496    EXPECT_EQ(ENGINE_KEY_ENOENT, store->set(item2, cookie));
497}
498
499/**
500 * Regression test for MB-25398 - Test CAS set (deleted value) against a
501 * deleted, non-resident key.
502 */
503TEST_P(KVBucketParamTest, MB_25398_SetCASDeletedItem) {
504    auto key = makeStoredDocKey("key");
505    store_item(vbid, key, "value");
506
507    flushVBucketToDiskIfPersistent(vbid);
508
509    // delete it, retaining a value.
510    auto item = make_item(vbid, key, "deletedvalue");
511    item.setDeleted();
512    const auto inCAS = item.getCas();
513    ASSERT_EQ(ENGINE_SUCCESS, store->set(item, cookie));
514    ASSERT_NE(inCAS, item.getCas());
515
516    // Flush, ensuring that the persistence callback runs and item is removed
517    // from the HashTable.
518    flushVBucketToDiskIfPersistent(vbid);
519
520    // Create a different deleted value (with an incorrect CAS).
521    auto item2 = make_item(vbid, key, "deletedvalue2");
522    item2.setDeleted();
523    item2.setCas(item.getCas() + 1);
524
525    if (engine->getConfiguration().getBucketType() == "persistent") {
526        // Deleted item won't be resident (after a flush), so expect to need to
527        // bgfetch.
528        EXPECT_EQ(ENGINE_EWOULDBLOCK, store->set(item2, cookie));
529
530        runBGFetcherTask();
531    }
532
533    // Try with incorrect CAS.
534    EXPECT_EQ(ENGINE_KEY_EEXISTS, store->set(item2, cookie));
535
536    // Try again, this time with correct CAS.
537    item2.setCas(item.getCas());
538    EXPECT_EQ(ENGINE_SUCCESS, store->set(item2, cookie));
539}
540
541/**
542 * Negative variant of the regression test for MB-25398 - Test that a CAS set
543 * (deleted value) to a non-existent item fails.
544 */
545TEST_P(KVBucketParamTest, MB_25398_SetCASDeletedItemNegative) {
546    auto key = makeStoredDocKey("key");
547
548    flushVBucketToDiskIfPersistent(vbid, 0);
549
550    // Attempt to mutate a non-existent key (with a specific, incorrect CAS)
551    auto item2 = make_item(vbid, key, "deletedvalue");
552    item2.setDeleted();
553    item2.setCas(1234);
554
555    if (engine->getConfiguration().getBucketType() == "persistent") {
556        // Deleted item won't be resident (after a flush), so expect to need to
557        // bgfetch.
558        EXPECT_EQ(ENGINE_EWOULDBLOCK, store->set(item2, cookie));
559        runBGFetcherTask();
560    }
561
562    // Try with a specific CAS.
563    EXPECT_EQ(ENGINE_KEY_ENOENT, store->set(item2, cookie));
564
565    // Try with no CAS (wildcard) - should be possible to store.
566    item2.setCas(0);
567    EXPECT_EQ(ENGINE_SUCCESS, store->set(item2, cookie));
568}
569
570// Add tests //////////////////////////////////////////////////////////////////
571
572// Test successful add
573TEST_P(KVBucketParamTest, Add) {
574    auto item = make_item(vbid, makeStoredDocKey("key"), "value");
575    EXPECT_EQ(ENGINE_SUCCESS, store->add(item, cookie));
576}
577
578// Check incorrect vbucket returns not-my-vbucket.
579TEST_P(KVBucketParamTest, AddNMVB) {
580    auto item = make_item(vbid + 1, makeStoredDocKey("key"), "value2");
581    EXPECT_EQ(ENGINE_NOT_MY_VBUCKET, store->add(item, cookie));
582}
583
584// SetWithMeta tests //////////////////////////////////////////////////////////
585
586// Test basic setWithMeta
587TEST_P(KVBucketParamTest, SetWithMeta) {
588    auto item = make_item(vbid, makeStoredDocKey("key"), "value");
589    item.setCas();
590    uint64_t seqno;
591    EXPECT_EQ(ENGINE_SUCCESS,
592              store->setWithMeta(item,
593                                 0,
594                                 &seqno,
595                                 cookie,
596                                 {vbucket_state_active},
597                                 CheckConflicts::Yes,
598                                 /*allowExisting*/ false));
599}
600
601// Test setWithMeta with a conflict with an existing item.
602TEST_P(KVBucketParamTest, SetWithMeta_Conflicted) {
603    auto item = make_item(vbid, makeStoredDocKey("key"), "value");
604    EXPECT_EQ(ENGINE_SUCCESS, store->set(item, cookie));
605
606    uint64_t seqno;
607    // Attempt to set with the same rev Seqno - should get EEXISTS.
608    EXPECT_EQ(ENGINE_KEY_EEXISTS,
609              store->setWithMeta(item,
610                                 item.getCas(),
611                                 &seqno,
612                                 cookie,
613                                 {vbucket_state_active},
614                                 CheckConflicts::Yes,
615                                 /*allowExisting*/ true));
616}
617
618// Test setWithMeta replacing existing item
619TEST_P(KVBucketParamTest, SetWithMeta_Replace) {
620    auto item = make_item(vbid, makeStoredDocKey("key"), "value");
621    EXPECT_EQ(ENGINE_SUCCESS, store->set(item, cookie));
622
623    // Increase revSeqno so conflict resolution doesn't fail.
624    item.setRevSeqno(item.getRevSeqno() + 1);
625    uint64_t seqno;
626    // Should get EEXISTS if we don't force (and use wrong CAS).
627    EXPECT_EQ(ENGINE_KEY_EEXISTS,
628              store->setWithMeta(item,
629                                 item.getCas() + 1,
630                                 &seqno,
631                                 cookie,
632                                 {vbucket_state_active},
633                                 CheckConflicts::Yes,
634                                 /*allowExisting*/ true));
635
636    // Should succeed with correct CAS, and different RevSeqno.
637    EXPECT_EQ(ENGINE_SUCCESS,
638              store->setWithMeta(item,
639                                 item.getCas(),
640                                 &seqno,
641                                 cookie,
642                                 {vbucket_state_active},
643                                 CheckConflicts::Yes,
644                                 /*allowExisting*/ true));
645}
646
647/**
648 * 1. setWithMeta to store an item with an expiry value
649 * 2. Call get after expiry to ensure that item is deleted
650 * 3. setWithMeta to store an item with lesser rev seqno
651 *    than what is stored in hash table
652 * 4. (3) should result in an EWOULDBLOCK and a temporary
653 *    deleted item in hash table
654 * 5. setWithMeta after BG Fetch should result in EEXISTS
655 * 6. Temporary item should be deleted from the hash table
656 */
657TEST_P(KVBucketParamTest, MB_28078_SetWithMeta_tempDeleted) {
658    auto key = makeStoredDocKey("key");
659    auto item = make_item(vbid, key, "value");
660    item.setExpTime(1);
661    item.setCas();
662    uint64_t seqno;
663    EXPECT_EQ(ENGINE_SUCCESS,
664              store->setWithMeta(item,
665                                 0,
666                                 &seqno,
667                                 cookie,
668                                 {vbucket_state_active},
669                                 CheckConflicts::No,
670                                 /*allowExisting*/ true));
671
672    TimeTraveller docBrown(20);
673    get_options_t options =
674            static_cast<get_options_t>(QUEUE_BG_FETCH | GET_DELETED_VALUE);
675
676    auto doGet = [&]() { return store->get(key, vbid, cookie, options); };
677    GetValue result = doGet();
678
679    flushVBucketToDiskIfPersistent(vbid, 1);
680
681    auto doSetWithMeta = [&]() {
682        return store->setWithMeta(item,
683                                  item.getCas(),
684                                  &seqno,
685                                  cookie,
686                                  {vbucket_state_active},
687                                  CheckConflicts::Yes,
688                                  /*allowExisting*/ true);
689    };
690
691    if (engine->getConfiguration().getBucketType() == "persistent") {
692        ASSERT_EQ(ENGINE_EWOULDBLOCK, doSetWithMeta());
693    }
694
695    auto* shard = store->getVBucket(vbid)->getShard();
696    MockGlobalTask mockTask(engine->getTaskable(), TaskId::MultiBGFetcherTask);
697    if (engine->getConfiguration().getBucketType() == "persistent") {
698        shard->getBgFetcher()->run(&mockTask);
699        ASSERT_EQ(ENGINE_KEY_EEXISTS, doSetWithMeta());
700    }
701
702    EXPECT_EQ(0, store->getVBucket(vbid)->getNumItems());
703    EXPECT_EQ(0, store->getVBucket(vbid)->getNumTempItems());
704}
705
706// Test forced setWithMeta
707TEST_P(KVBucketParamTest, SetWithMeta_Forced) {
708    auto item = make_item(vbid, makeStoredDocKey("key"), "value");
709    item.setCas();
710    uint64_t seqno;
711    EXPECT_EQ(ENGINE_SUCCESS,
712              store->setWithMeta(item,
713                                 0,
714                                 &seqno,
715                                 cookie,
716                                 {vbucket_state_active,
717                                  vbucket_state_replica,
718                                  vbucket_state_pending},
719                                 CheckConflicts::No,
720                                 /*allowExisting*/ false));
721}
722
723// MB and test was raised because a few commits back this was broken but no
724// existing test covered the case. I.e. run this test  against 0810540 and it
725// fails, but now fixed
726TEST_P(KVBucketParamTest, mb22824) {
727    auto key = makeStoredDocKey("key");
728
729    // Store key and force expiry
730    store_item(0, key, "value", 1);
731    TimeTraveller docBrown(20);
732
733    uint32_t deleted = false;
734    ItemMetaData itemMeta1;
735    uint8_t datatype = PROTOCOL_BINARY_RAW_BYTES;
736    EXPECT_EQ(ENGINE_SUCCESS,
737              store->getMetaData(
738                      key, vbid, cookie, itemMeta1, deleted, datatype));
739
740    uint64_t cas = 0;
741    ItemMetaData itemMeta2;
742    mutation_descr_t mutation_descr;
743    EXPECT_EQ(ENGINE_KEY_ENOENT,
744              store->deleteItem(
745                      key, cas, vbid, cookie, &itemMeta2, mutation_descr));
746
747    // Should be getting the same CAS from the failed delete as getMetaData
748    EXPECT_EQ(itemMeta1.cas, itemMeta2.cas);
749}
750
751/**
752 *  Test that the first item updates the hlcSeqno, but not the second
753 */
754TEST_P(KVBucketParamTest, test_hlcEpochSeqno) {
755    auto vb = store->getVBucket(vbid);
756
757    // A persistent bucket will store something then set the hlc_epoch
758    // An ephemeral bucket always has an epoch
759    int64_t initialEpoch =
760            engine->getConfiguration().getBucketType() == "persistent"
761                    ? HlcCasSeqnoUninitialised
762                    : 0;
763
764    EXPECT_EQ(initialEpoch, vb->getHLCEpochSeqno());
765    auto item = make_item(vbid, makeStoredDocKey("key1"), "value");
766    EXPECT_EQ(ENGINE_SUCCESS, store->add(item, cookie));
767    if (engine->getConfiguration().getBucketType() == "persistent") {
768        // Trigger a flush to disk.
769        flush_vbucket_to_disk(vbid);
770    }
771
772    auto seqno = vb->getHLCEpochSeqno();
773    EXPECT_NE(HlcCasSeqnoUninitialised, seqno);
774
775    auto item2 = make_item(vbid, makeStoredDocKey("key2"), "value");
776    EXPECT_EQ(ENGINE_SUCCESS, store->add(item2, cookie));
777    if (engine->getConfiguration().getBucketType() == "persistent") {
778        // Trigger a flush to disk.
779        flush_vbucket_to_disk(vbid);
780    }
781
782    // hlc seqno doesn't change was more items are stored
783    EXPECT_EQ(seqno, vb->getHLCEpochSeqno());
784}
785
786TEST_F(KVBucketTest, DataRaceInDoWorkerStat) {
787    /* MB-23529: TSAN intermittently reports a data race.
788     * This race appears to be caused by GGC's buggy string COW as seen
789     * multiple times, e.g., MB-23454.
790     * doWorkerStat calls getLog/getSlowLog to get a vector of TaskLogEntrys,
791     * which have been copied out of the tasklog ringbuffer of a given
792     * ExecutorThread. These copies logically have copies of the original's
793     * `std::string name`.
794     * As the ringbuffer overwrites older entries, the deletion of the old
795     * entry's `std::string name` races with doWorkerStats reading the COW'd
796     * name of its copy.
797     * */
798    EpEngineTaskable& taskable = engine->getTaskable();
799    ExecutorPool* pool = ExecutorPool::get();
800
801    // Task which does nothing
802    ExTask task = std::make_shared<LambdaTask>(
803            taskable, TaskId::DcpConsumerTask, 0, true, [&]() -> bool {
804                return true; // reschedule (immediately)
805            });
806
807    pool->schedule(task);
808
809    // nop callback to serve as add_stat
810    auto dummy_cb = [](const char* key,
811                       const uint16_t klen,
812                       const char* val,
813                       const uint32_t vlen,
814                       gsl::not_null<const void*> cookie) {};
815
816    for (uint64_t i = 0; i < 10; ++i) {
817        pool->doWorkerStat(engine.get(),
818                           // The callback don't use the cookie at all, but
819                           // the API requires it to be set.. use the pool
820                           // as the cookie
821                           static_cast<const void*>(pool),
822                           dummy_cb);
823    }
824
825    pool->cancel(task->getId());
826}
827
828void KVBucketTest::storeAndDeleteItem(uint16_t vbid,
829                                      const DocKey& key,
830                                      std::string value) {
831    Item item = store_item(vbid,
832                           key,
833                           value,
834                           0,
835                           {cb::engine_errc::success},
836                           PROTOCOL_BINARY_RAW_BYTES);
837
838    delete_item(vbid, key);
839    flushVBucketToDiskIfPersistent(vbid, 1);
840}
841
842ENGINE_ERROR_CODE KVBucketTest::getMeta(uint16_t vbid,
843                                        const DocKey key,
844                                        const void* cookie,
845                                        ItemMetaData& itemMeta,
846                                        uint32_t& deleted,
847                                        uint8_t& datatype) {
848    auto doGetMetaData = [&]() {
849        return store->getMetaData(
850                key, vbid, cookie, itemMeta, deleted, datatype);
851    };
852
853    auto engineResult = doGetMetaData();
854    auto* shard = store->getVBucket(vbid)->getShard();
855    MockGlobalTask mockTask(engine->getTaskable(), TaskId::MultiBGFetcherTask);
856    if (engine->getConfiguration().getBucketType() == "persistent") {
857        EXPECT_EQ(ENGINE_EWOULDBLOCK, engineResult);
858        // Manually run the bgfetch task, and re-attempt getMetaData
859        shard->getBgFetcher()->run(&mockTask);
860
861        engineResult = doGetMetaData();
862    }
863
864    return engineResult;
865}
866
867TEST_P(KVBucketParamTest, lockKeyTempDeletedTest) {
868    //This test is to check if the lockKey function will
869    //remove temporary deleted items from memory
870    auto key = makeStoredDocKey("key");
871    storeAndDeleteItem(vbid, key, std::string("value"));
872
873    ItemMetaData itemMeta;
874    uint32_t deleted = 0;
875    uint8_t datatype = 0;
876    auto engineResult = getMeta(vbid, key, cookie, itemMeta, deleted, datatype);
877
878    // Verify that GetMeta succeeded; and metadata is correct.
879    ASSERT_EQ(ENGINE_SUCCESS, engineResult);
880    ASSERT_TRUE(deleted);
881
882    int expTempItems = 0;
883    if (engine->getConfiguration().getBucketType() == "persistent") {
884        expTempItems = 1;
885    }
886
887    //Check that the temp item is removed for getLocked
888    EXPECT_EQ(expTempItems, store->getVBucket(vbid)->getNumTempItems());
889    GetValue gv = store->getLocked(key, vbid, ep_current_time(), 10, cookie);
890    EXPECT_EQ(ENGINE_KEY_ENOENT, gv.getStatus());
891    EXPECT_EQ(0, store->getVBucket(vbid)->getNumTempItems());
892}
893
894TEST_P(KVBucketParamTest, unlockKeyTempDeletedTest) {
895    //This test is to check if the unlockKey function will
896    //remove temporary deleted items from memory
897    auto key = makeStoredDocKey("key");
898    std::string value("value");
899
900    Item itm = store_item(vbid,
901                          key,
902                          value,
903                          0,
904                          {cb::engine_errc::success},
905                          PROTOCOL_BINARY_RAW_BYTES);
906
907    GetValue gv = store->getAndUpdateTtl(key, vbid, cookie, ep_real_time());
908    EXPECT_EQ(ENGINE_SUCCESS, gv.getStatus());
909
910    gv = store->getLocked(key, vbid, ep_current_time(), 10, cookie);
911    EXPECT_EQ(ENGINE_SUCCESS, gv.getStatus());
912
913    itm.setCas(gv.item->getCas());
914    store->deleteExpiredItem(itm, ep_real_time() + 10, ExpireBy::Pager);
915
916    flushVBucketToDiskIfPersistent(vbid, 1);
917
918    ItemMetaData itemMeta;
919    uint32_t deleted = 0;
920    uint8_t datatype = 0;
921    auto engineResult = getMeta(vbid, key, cookie, itemMeta, deleted, datatype);
922
923    // Verify that GetMeta succeeded; and metadata is correct.
924    ASSERT_EQ(ENGINE_SUCCESS, engineResult);
925    ASSERT_TRUE(deleted);
926
927    int expTempItems = 0;
928    if (engine->getConfiguration().getBucketType() == "persistent") {
929        expTempItems = 1;
930    }
931
932    //Check that the temp item is removed for unlockKey
933    EXPECT_EQ(expTempItems, store->getVBucket(vbid)->getNumTempItems());
934    EXPECT_EQ(ENGINE_KEY_ENOENT, store->unlockKey(key, vbid, 0,
935                                 ep_current_time()));
936    EXPECT_EQ(0, store->getVBucket(vbid)->getNumTempItems());
937}
938
939TEST_P(KVBucketParamTest, replaceTempDeletedTest) {
940    //This test is to check if the replace function will
941    //remove temporary deleted items from memory
942    auto key = makeStoredDocKey("key");
943    storeAndDeleteItem(vbid, key, std::string("value"));
944
945    ItemMetaData itemMeta;
946    uint32_t deleted = 0;
947    uint8_t datatype = 0;
948    auto engineResult = getMeta(vbid, key, cookie, itemMeta, deleted, datatype);
949    ASSERT_EQ(ENGINE_SUCCESS, engineResult);
950    ASSERT_TRUE(deleted);
951
952    int expTempItems = 0;
953    if (engine->getConfiguration().getBucketType() == "persistent") {
954        expTempItems = 1;
955    }
956
957    //Check that the temp item is removed for replace
958    EXPECT_EQ(expTempItems, store->getVBucket(vbid)->getNumTempItems());
959    auto replace_item = make_item(vbid, makeStoredDocKey("key"), "value2");
960    EXPECT_EQ(ENGINE_KEY_ENOENT, store->replace(replace_item, cookie));
961    EXPECT_EQ(0, store->getVBucket(vbid)->getNumTempItems());
962}
963
964TEST_P(KVBucketParamTest, statsVKeyTempDeletedTest) {
965    //This test is to check if the statsVKey function will
966    //remove temporary deleted items from memory
967    auto key = makeStoredDocKey("key");
968    storeAndDeleteItem(vbid, key, std::string("value"));
969
970    ItemMetaData itemMeta;
971    uint32_t deleted = 0;
972    uint8_t datatype = 0;
973    auto engineResult = getMeta(vbid, key, cookie, itemMeta, deleted, datatype);
974
975    // Verify that GetMeta succeeded; and metadata is correct.
976    ASSERT_EQ(ENGINE_SUCCESS, engineResult);
977    ASSERT_TRUE(deleted);
978
979    int expTempItems = 0;
980    ENGINE_ERROR_CODE expRetCode = ENGINE_ENOTSUP;
981    if (engine->getConfiguration().getBucketType() == "persistent") {
982        expTempItems = 1;
983        expRetCode = ENGINE_KEY_ENOENT;
984    }
985
986    //Check that the temp item is removed for statsVKey
987    EXPECT_EQ(expTempItems, store->getVBucket(vbid)->getNumTempItems());
988    EXPECT_EQ(expRetCode, store->statsVKey(key, vbid, cookie));
989    EXPECT_EQ(0, store->getVBucket(vbid)->getNumTempItems());
990}
991
992TEST_P(KVBucketParamTest, getAndUpdateTtlTempDeletedItemTest) {
993    //This test is to check if the getAndUpdateTtl function will
994    //remove temporary deleted items from memory
995    auto key = makeStoredDocKey("key");
996    storeAndDeleteItem(vbid, key, std::string("value"));
997
998    ItemMetaData itemMeta;
999    uint32_t deleted = 0;
1000    uint8_t datatype = 0;
1001    auto engineResult = getMeta(vbid, key, cookie, itemMeta, deleted, datatype);
1002    // Verify that GetMeta succeeded; and metadata is correct.
1003    ASSERT_EQ(ENGINE_SUCCESS, engineResult);
1004    ASSERT_TRUE(deleted);
1005
1006    int expTempItems = 0;
1007    if (engine->getConfiguration().getBucketType() == "persistent") {
1008        expTempItems = 1;
1009    }
1010
1011    //Check that the temp item is removed for getAndUpdateTtl
1012    EXPECT_EQ(expTempItems, store->getVBucket(vbid)->getNumTempItems());
1013    GetValue gv = store->getAndUpdateTtl(makeStoredDocKey("key"), vbid,
1014                                         cookie, time(NULL));
1015    EXPECT_EQ(ENGINE_KEY_ENOENT, gv.getStatus());
1016    EXPECT_EQ(0, store->getVBucket(vbid)->getNumTempItems());
1017}
1018
1019TEST_P(KVBucketParamTest, validateKeyTempDeletedItemTest) {
1020    //This test is to check if the getAndUpdateTtl function will
1021    //remove temporary deleted items from memory
1022    auto key = makeStoredDocKey("key");
1023    storeAndDeleteItem(vbid, key, std::string("value"));
1024
1025    ItemMetaData itemMeta;
1026    uint32_t deleted;
1027    uint8_t datatype;
1028    auto engineResult = getMeta(vbid, key, cookie, itemMeta, deleted, datatype);
1029
1030    // Verify that GetMeta succeeded; and metadata is correct.
1031    ASSERT_EQ(ENGINE_SUCCESS, engineResult);
1032    ASSERT_TRUE(deleted);
1033
1034    int expTempItems = 0;
1035    if (engine->getConfiguration().getBucketType() == "persistent") {
1036        expTempItems = 1;
1037    }
1038
1039    //Check that the temp item is removed for validateKey
1040    EXPECT_EQ(expTempItems, store->getVBucket(vbid)->getNumTempItems());
1041
1042    // dummy item; don't expect to need it for deleted case.
1043    auto dummy = make_item(vbid, key, {});
1044    std::string result = store->validateKey(key, vbid, dummy);
1045    EXPECT_STREQ("item_deleted", result.c_str());
1046    EXPECT_EQ(0, store->getVBucket(vbid)->getNumTempItems());
1047}
1048
1049// Test demonstrates MB-25948 with a subtle difference. In the MB the issue
1050// says delete(key1), but in this test we use expiry. That is because using
1051// ep-engine deleteItem doesn't do the system-xattr pruning (that's part of
1052// memcached). So we use expiry which will use the pre_expiry hook to prune
1053// the xattrs.
1054TEST_P(KVBucketParamTest, MB_25948) {
1055
1056    // 1. Store key1 with an xattr value
1057    auto key = makeStoredDocKey("key");
1058
1059    std::string value = createXattrValue("body");
1060
1061    Item item = store_item(0,
1062                           key,
1063                           value,
1064                           1,
1065                           {cb::engine_errc::success},
1066                           PROTOCOL_BINARY_DATATYPE_XATTR);
1067
1068    TimeTraveller docBrown(20);
1069
1070    // 2. Force expiry of the item and flush the delete
1071    get_options_t options =
1072            static_cast<get_options_t>(QUEUE_BG_FETCH | GET_DELETED_VALUE);
1073    auto doGet = [&]() { return store->get(key, vbid, cookie, options); };
1074    GetValue result = doGet();
1075
1076    flushVBucketToDiskIfPersistent(vbid, 1);
1077
1078    // 3. GetMeta for key1, retrieving the tombstone
1079    ItemMetaData itemMeta;
1080    uint32_t deleted = 0;
1081    uint8_t datatype = 0;
1082    auto doGetMetaData = [&]() {
1083        return store->getMetaData(
1084                key, vbid, cookie, itemMeta, deleted, datatype);
1085    };
1086
1087    auto engineResult = doGetMetaData();
1088
1089    auto* shard = store->getVBucket(vbid)->getShard();
1090    MockGlobalTask mockTask(engine->getTaskable(), TaskId::MultiBGFetcherTask);
1091    if (engine->getConfiguration().getBucketType() == "persistent") {
1092        EXPECT_EQ(ENGINE_EWOULDBLOCK, engineResult);
1093        // Manually run the bgfetch task, and re-attempt getMetaData
1094        shard->getBgFetcher()->run(&mockTask);
1095
1096        engineResult = doGetMetaData();
1097    }
1098    // Verify that GetMeta succeeded; and metadata is correct.
1099    ASSERT_EQ(ENGINE_SUCCESS, engineResult);
1100    ASSERT_TRUE(deleted);
1101    ASSERT_EQ(PROTOCOL_BINARY_DATATYPE_XATTR, datatype);
1102    ASSERT_EQ(item.getFlags(), itemMeta.flags);
1103    // CAS and revSeqno not checked as changed when the document was expired.
1104
1105    // 4. Now get deleted value - we want to retrieve the _sync field.
1106    result = doGet();
1107
1108    // Manually run the bgfetch task and retry the get()
1109    if (engine->getConfiguration().getBucketType() == "persistent") {
1110        ASSERT_EQ(ENGINE_EWOULDBLOCK, result.getStatus());
1111        shard->getBgFetcher()->run(&mockTask);
1112        result = doGet();
1113    }
1114    ASSERT_EQ(ENGINE_SUCCESS, result.getStatus());
1115
1116    cb::xattr::Blob blob({const_cast<char*>(result.item->getData()),
1117                          result.item->getNBytes()},
1118                         false);
1119
1120    // user and meta gone, _sync remains.
1121    EXPECT_EQ(0, blob.get("user").size());
1122    EXPECT_EQ(0, blob.get("meta").size());
1123    ASSERT_NE(0, blob.get("_sync").size());
1124    EXPECT_STREQ("{\"cas\":\"0xdeadbeefcafefeed\"}",
1125                 reinterpret_cast<char*>(blob.get("_sync").data()));
1126}
1127
1128/**
1129 * Test performs the following operations
1130 * 1. Store an item
1131 * 2. Delete an item and make sure it is removed from memory
1132 * 3. Store the item again
1133 * 4. Evict the item from memory to ensure that meta data
1134 *    will be retrieved from disk
1135 * 5. Check that the revision seq no. retrieved from disk
1136 *    is equal to 3 (the number of updates on that item)
1137 */
1138TEST_P(KVBucketParamTest, MB_27162) {
1139     auto key = makeStoredDocKey("key");
1140     std::string value("value");
1141
1142     Item item = store_item(vbid, key, value, 0, {cb::engine_errc::success},
1143                            PROTOCOL_BINARY_RAW_BYTES);
1144
1145     delete_item(vbid, key);
1146
1147     flushVBucketToDiskIfPersistent(vbid, 1);
1148
1149     store_item(vbid, key, value, 0, {cb::engine_errc::success},
1150                PROTOCOL_BINARY_RAW_BYTES);
1151
1152     flushVBucketToDiskIfPersistent(vbid, 1);
1153
1154     if (GetParam() != "bucket_type=ephemeral") {
1155         evict_key(vbid, key);
1156     }
1157
1158     ItemMetaData itemMeta;
1159     uint32_t deleted = 0;
1160     uint8_t datatype = 0;
1161     auto doGetMetaData = [&]() {
1162        return store->getMetaData(
1163                key, vbid, cookie, itemMeta, deleted, datatype);
1164     };
1165
1166     auto engineResult = doGetMetaData();
1167
1168     auto* shard = store->getVBucket(vbid)->getShard();
1169     MockGlobalTask mockTask(engine->getTaskable(), TaskId::MultiBGFetcherTask);
1170     if (engine->getConfiguration().getBucketType() == "persistent" &&
1171         GetParam() == "item_eviction_policy=full_eviction") {
1172         ASSERT_EQ(ENGINE_EWOULDBLOCK, engineResult);
1173         // Manually run the bgfetch task, and re-attempt getMetaData
1174         shard->getBgFetcher()->run(&mockTask);
1175
1176         engineResult = doGetMetaData();
1177     }
1178     // Verify that GetMeta succeeded; and metadata is correct.
1179     ASSERT_EQ(ENGINE_SUCCESS, engineResult);
1180     EXPECT_EQ(3, itemMeta.revSeqno);
1181}
1182
1183TEST_P(KVBucketParamTest, numberOfVBucketsInState) {
1184    EXPECT_EQ(1, store->getNumOfVBucketsInState(vbucket_state_active));
1185    EXPECT_EQ(0, store->getNumOfVBucketsInState(vbucket_state_replica));
1186}
1187
1188/***
1189 * Test class to expose the behaviour needed to create an ItemAccessVisitor
1190 */
1191class MockAccessScanner : public AccessScanner {
1192public:
1193    MockAccessScanner(KVBucket& _store,
1194                      Configuration& conf,
1195                      EPStats& st,
1196                      double sleeptime = 0,
1197                      bool useStartTime = false,
1198                      bool completeBeforeShutdown = false)
1199        : AccessScanner(_store,
1200                        conf,
1201                        st,
1202                        sleeptime,
1203                        useStartTime,
1204                        completeBeforeShutdown) {
1205    }
1206
1207    void public_createAndScheduleTask(const size_t shard) {
1208        return createAndScheduleTask(shard);
1209    }
1210};
1211
1212/***
1213 * Test to make sure the Access Scanner doesn't throw an exception with a log
1214 * location specified in the config which doesn't exist.
1215 */
1216TEST_P(KVBucketParamTest, AccessScannerInvalidLogLocation) {
1217    /* Manually edit the configuration to change the location of the
1218     * access log to be somewhere that doesn't exist */
1219    engine->getConfiguration().setAlogPath("/path/to/somewhere");
1220    ASSERT_EQ(engine->getConfiguration().getAlogPath(), "/path/to/somewhere");
1221    ASSERT_FALSE(cb::io::isDirectory(engine->getConfiguration().getAlogPath()));
1222
1223    /* Create the Access Scanner task with our modified configuration
1224     * In this case, the 1000 refers to the sleep time for the job, but it never
1225     * gets used as part of the test case. */
1226    auto as = std::make_unique<MockAccessScanner>(*(engine->getKVBucket()),
1227                                                  engine->getConfiguration(),
1228                                                  engine->getEpStats(),
1229                                                  1000);
1230
1231    /* Make sure this doesn't throw an exception when tyring to run the task*/
1232    EXPECT_NO_THROW(as->public_createAndScheduleTask(0))
1233            << "Access Scanner threw unexpected "
1234               "exception where log location does "
1235               "not exist";
1236}
1237
1238// Check that getRandomKey works correctly when given a random value of zero
1239TEST_P(KVBucketParamTest, MB31495_GetRandomKey) {
1240    std::function<long()> returnZero = []() { return 0; };
1241    setRandomFunction(returnZero);
1242
1243    // Try with am empty hash table
1244    auto gv = store->getRandomKey();
1245    EXPECT_EQ(ENGINE_KEY_ENOENT, gv.getStatus());
1246
1247    Item item = store_item(
1248            vbid, {"key", DocNamespace::DefaultCollection}, "value", 0);
1249
1250    // Try with a non-empty hash table
1251    gv = store->getRandomKey();
1252    EXPECT_EQ(ENGINE_SUCCESS, gv.getStatus());
1253}
1254
1255// Test that expiring a compressed xattr doesn't trigger any errors
1256TEST_P(KVBucketParamTest, MB_34346) {
1257    // Create an XTTR value with only a large system xattr, and compress the lot
1258    // Note the large xattr should be highly compressible to make it easier to
1259    // trigger the MB.
1260    cb::xattr::Blob blob;
1261    blob.set("_sync",
1262             R"({"fffff":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"})");
1263    auto xattr = blob.finalize();
1264    cb::compression::Buffer output;
1265    cb::compression::deflate(cb::compression::Algorithm::Snappy,
1266                             {xattr.data(), xattr.size()},
1267                             output);
1268    EXPECT_LT(output.size(), xattr.size())
1269            << "Expected the compressed buffer to be smaller than the input";
1270
1271    auto key = makeStoredDocKey("key_1");
1272    store_item(
1273            vbid,
1274            key,
1275            {output.data(), output.size()},
1276            ep_abs_time(ep_current_time() + 10),
1277            {cb::engine_errc::success},
1278            PROTOCOL_BINARY_DATATYPE_XATTR | PROTOCOL_BINARY_DATATYPE_SNAPPY);
1279
1280    flushVBucketToDiskIfPersistent(vbid, 1);
1281
1282    EXPECT_EQ(1, engine->getVBucket(vbid)->getNumItems())
1283            << "Should have 1 item after calling store()";
1284
1285    TimeTraveller docBrown(15);
1286
1287    get_options_t options = static_cast<get_options_t>(
1288            QUEUE_BG_FETCH | HONOR_STATES | TRACK_REFERENCE | DELETE_TEMP |
1289            HIDE_LOCKED_CAS | TRACK_STATISTICS | GET_DELETED_VALUE);
1290    GetValue gv2 = store->get(key, vbid, cookie, options);
1291    EXPECT_TRUE(gv2.item->isDeleted());
1292    // Check that the datatype does not include SNAPPY
1293    EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_XATTR, gv2.item->getDataType());
1294    // Check the returned blob is what we initially set
1295    cb::xattr::Blob returnedBlob(
1296            {const_cast<char*>(gv2.item->getData()), gv2.item->getNBytes()},
1297            false);
1298    EXPECT_STREQ(
1299            "{\"fffff\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}",
1300            reinterpret_cast<char*>(returnedBlob.get("_sync").data()));
1301
1302    flushVBucketToDiskIfPersistent(vbid, 1);
1303
1304    EXPECT_EQ(0, engine->getVBucket(vbid)->getNumItems())
1305            << "Should still have 0 items after time-travelling/expiry";
1306}
1307
1308class StoreIfTest : public KVBucketTest {
1309public:
1310    void SetUp() override {
1311        config_string += "warmup=false";
1312        KVBucketTest::SetUp();
1313        // Have all the objects, activate vBucket zero so we can store data.
1314        store->setVBucketState(vbid, vbucket_state_active, false);
1315    }
1316};
1317
1318/**
1319 * Test the basic store_if (via engine) - a forced fail predicate will allow
1320 * add, but fail set/replace with predicate_failed
1321 */
1322TEST_F(StoreIfTest, store_if_basic) {
1323    cb::StoreIfPredicate pred = [](const boost::optional<item_info>& existing,
1324                                   cb::vbucket_info vb) -> cb::StoreIfStatus {
1325        return cb::StoreIfStatus::Fail;
1326    };
1327    auto item = make_item(vbid, {"key", DocNamespace::DefaultCollection}, "value", 0, 0);
1328    auto rv = engine->store_if(cookie, item, 0, OPERATION_ADD, pred);
1329    EXPECT_EQ(cb::engine_errc::success, rv.status);
1330    rv = engine->store_if(cookie, item, 0, OPERATION_REPLACE, pred);
1331    EXPECT_EQ(cb::engine_errc::predicate_failed, rv.status);
1332    rv = engine->store_if(cookie, item, 0, OPERATION_SET, pred);
1333    EXPECT_EQ(cb::engine_errc::predicate_failed, rv.status);
1334}
1335
1336class ExpiryLimitTest : public KVBucketTest {
1337public:
1338    void SetUp() override {
1339        config_string += "max_ttl=86400";
1340        KVBucketTest::SetUp();
1341        // Have all the objects, activate vBucket zero so we can store data.
1342        store->setVBucketState(vbid, vbucket_state_active, false);
1343    }
1344};
1345
1346// Test that item allocate with a limit stops 0 expiry
1347TEST_F(ExpiryLimitTest, itemAllocate) {
1348    item* itm;
1349    EXPECT_EQ(ENGINE_SUCCESS,
1350              engine->itemAllocate(&itm,
1351                                   {"key", DocNamespace::DefaultCollection},
1352                                   5,
1353                                   0,
1354                                   0,
1355                                   0 /*expiry*/,
1356                                   0,
1357                                   vbid));
1358
1359    Item* i = reinterpret_cast<Item*>(itm);
1360    auto info = engine->getItemInfo(*i);
1361    EXPECT_NE(0, info.exptime);
1362
1363    engine->itemRelease(itm);
1364}
1365
1366// Test that GAT with a limit stops 0 expiry
1367TEST_F(ExpiryLimitTest, gat) {
1368    // This will actually skip the initial expiry limiting code as this function
1369    // doesn't use itemAllocate
1370    Item item = store_item(
1371            vbid, {"key", DocNamespace::DefaultCollection}, "value", 0);
1372
1373    // Now touch with 0
1374    auto rval = engine->get_and_touch(
1375            cookie, {"key", DocNamespace::DefaultCollection}, vbid, 0);
1376
1377    EXPECT_EQ(cb::engine_errc::success, rval.first);
1378
1379    Item* i = reinterpret_cast<Item*>(rval.second.get());
1380    auto info = engine->getItemInfo(*i);
1381    EXPECT_NE(0, info.exptime);
1382}
1383
1384// Test cases which run for EP (Full and Value eviction) and Ephemeral
1385INSTANTIATE_TEST_CASE_P(EphemeralOrPersistent,
1386                        KVBucketParamTest,
1387                        ::testing::Values("item_eviction_policy=value_only",
1388                                          "item_eviction_policy=full_eviction",
1389                                          "bucket_type=ephemeral"),
1390                        [](const ::testing::TestParamInfo<std::string>& info) {
1391                            return info.param.substr(info.param.find('=') + 1);
1392                        });
1393
1394const char KVBucketTest::test_dbname[] = "ep_engine_ep_unit_tests_db";
1395