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 * Unit tests for the EPBucket class.
20 *
21 * Note that these test do *not* have the normal Tasks running (BGFetcher,
22 * flusher etc) as we do not initialise EPEngine. This means that such tasks
23 * need to be manually run. This can be very helpful as it essentially gives us
24 * synchronous control of EPStore.
25 */
26
27#include "evp_store_test.h"
28
29#include "../mock/mock_dcp_producer.h"
30#include "bgfetcher.h"
31#include "checkpoint_remover.h"
32#include "dcp/dcpconnmap.h"
33#include "flusher.h"
34#include "tests/mock/mock_global_task.h"
35#include "tests/module_tests/test_helpers.h"
36#include "vbucketdeletiontask.h"
37#include "warmup.h"
38
39#include <string_utilities.h>
40#include <xattr/blob.h>
41#include <xattr/utils.h>
42
43#include <thread>
44
45// Verify that when handling a bucket delete with open DCP
46// connections, we don't deadlock when notifying the front-end
47// connection.
48// This is a potential issue because notify_IO_complete
49// needs to lock the worker thread mutex the connection is assigned
50// to, to update the event list for that connection, which the worker
51// thread itself will have locked while it is running. Normally
52// deadlock is avoided by using a background thread (ConnNotifier),
53// which only calls notify_IO_complete and isnt' involved with any
54// other mutexes, however we cannot use that task as it gets shut down
55// during shutdownAllConnections.
56// This test requires ThreadSanitizer or similar to validate;
57// there's no guarantee we'll actually deadlock on any given run.
58TEST_F(EPBucketTest, test_mb20751_deadlock_on_disconnect_delete) {
59
60    // Create a new Dcp producer, reserving its cookie.
61    get_mock_server_api()->cookie->reserve(cookie);
62    DcpProducer* producer = engine->getDcpConnMap().newProducer(
63            cookie, "mb_20716r", /*flags*/ 0);
64
65    // Check preconditions.
66    EXPECT_TRUE(producer->isPaused());
67
68    // 1. To check that there's no potential data-race with the
69    //    concurrent connection disconnect on another thread
70    //    (simulating a front-end thread).
71    std::thread frontend_thread_handling_disconnect{[this](){
72            // Frontend thread always runs with the cookie locked, so
73            // lock here to match.
74            lock_mock_cookie(cookie);
75            engine->handleDisconnect(cookie);
76            unlock_mock_cookie(cookie);
77        }};
78
79    // 2. Trigger a bucket deletion.
80    engine->handleDeleteBucket(cookie);
81
82    frontend_thread_handling_disconnect.join();
83}
84
85class EPStoreEvictionTest : public EPBucketTest,
86                             public ::testing::WithParamInterface<std::string> {
87    void SetUp() override {
88        config_string += std::string{"item_eviction_policy="} + GetParam();
89        EPBucketTest::SetUp();
90
91        // Have all the objects, activate vBucket zero so we can store data.
92        store->setVBucketState(vbid, vbucket_state_active, false);
93
94    }
95};
96
97// getKeyStats tests //////////////////////////////////////////////////////////
98
99// Check that keystats on ejected items. When ejected should return ewouldblock
100// until bgfetch completes.
101TEST_P(EPStoreEvictionTest, GetKeyStatsEjected) {
102    key_stats kstats;
103
104    // Store then eject an item. Note we cannot forcefully evict as we have
105    // to ensure it's own disk so we can later bg fetch from there :)
106    store_item(vbid, makeStoredDocKey("key"), "value");
107
108    // Trigger a flush to disk.
109    flush_vbucket_to_disk(vbid);
110
111    evict_key(vbid, makeStoredDocKey("key"));
112
113    // Setup a lambda for how we want to call getKeyStats (saves repeating the
114    // same arguments for each instance below).
115    auto do_getKeyStats = [this, &kstats]() {
116        return store->getKeyStats(makeStoredDocKey("key"),
117                                  vbid,
118                                  cookie,
119                                  kstats,
120                                  WantsDeleted::No);
121    };
122
123    if (GetParam() == "value_only") {
124        EXPECT_EQ(ENGINE_SUCCESS, do_getKeyStats())
125            << "Expected to get key stats on evicted item";
126
127    } else if (GetParam() == "full_eviction") {
128
129        // Try to get key stats. This should return EWOULDBLOCK (as the whole
130        // item is no longer resident). As we arn't running the full EPEngine
131        // task system, then no BGFetch task will be automatically run, we'll
132        // manually run it.
133
134        EXPECT_EQ(ENGINE_EWOULDBLOCK, do_getKeyStats())
135            << "Expected to need to go to disk to get key stats on fully evicted item";
136
137        // Try a second time - this should detect the already-created temp
138        // item, and re-schedule the bgfetch.
139        EXPECT_EQ(ENGINE_EWOULDBLOCK, do_getKeyStats())
140            << "Expected to need to go to disk to get key stats on fully evicted item (try 2)";
141
142        // Manually run the BGFetcher task; to fetch the two outstanding
143        // requests (for the same key).
144        runBGFetcherTask();
145
146        ASSERT_EQ(ENGINE_SUCCESS, do_getKeyStats())
147            << "Expected to get key stats on evicted item after notify_IO_complete";
148
149    } else {
150        FAIL() << "Unhandled GetParam() value:" << GetParam();
151    }
152}
153
154// Replace tests //////////////////////////////////////////////////////////////
155
156// Test replace against an ejected key.
157TEST_P(EPStoreEvictionTest, ReplaceEExists) {
158
159    // Store then eject an item.
160    store_item(vbid, makeStoredDocKey("key"), "value");
161    flush_vbucket_to_disk(vbid);
162    evict_key(vbid, makeStoredDocKey("key"));
163
164    // Setup a lambda for how we want to call replace (saves repeating the
165    // same arguments for each instance below).
166    auto do_replace = [this]() {
167        auto item = make_item(vbid, makeStoredDocKey("key"), "value2");
168        return store->replace(item, cookie);
169    };
170
171    if (GetParam() == "value_only") {
172        // Should be able to replace as still have metadata resident.
173        EXPECT_EQ(ENGINE_SUCCESS, do_replace());
174
175    } else if (GetParam() == "full_eviction") {
176        // Should get EWOULDBLOCK as need to go to disk to get metadata.
177        EXPECT_EQ(ENGINE_EWOULDBLOCK, do_replace());
178
179        // A second request should also get EWOULDBLOCK and add to the
180        // existing pending BGFetch
181        EXPECT_EQ(ENGINE_EWOULDBLOCK, do_replace());
182
183        // Manually run the BGFetcher task; to fetch the two outstanding
184        // requests (for the same key).
185        runBGFetcherTask();
186
187        EXPECT_EQ(ENGINE_SUCCESS, do_replace())
188            << "Expected to replace on evicted item after notify_IO_complete";
189
190    } else {
191        FAIL() << "Unhandled GetParam() value:" << GetParam();
192    }
193}
194
195// Set tests //////////////////////////////////////////////////////////////////
196
197// Test set against an ejected key.
198TEST_P(EPStoreEvictionTest, SetEExists) {
199
200    // Store an item, then eject it.
201    auto item = make_item(vbid, makeStoredDocKey("key"), "value");
202    EXPECT_EQ(ENGINE_SUCCESS, store->set(item, cookie));
203    flush_vbucket_to_disk(vbid);
204    evict_key(item.getVBucketId(), item.getKey());
205
206    if (GetParam() == "value_only") {
207        // Should be able to set (with same cas as previously)
208        // as still have metadata resident.
209        ASSERT_NE(0, item.getCas());
210        EXPECT_EQ(ENGINE_SUCCESS, store->set(item, cookie));
211
212    } else if (GetParam() == "full_eviction") {
213        // Should get EWOULDBLOCK as need to go to disk to get metadata.
214        EXPECT_EQ(ENGINE_EWOULDBLOCK, store->set(item, cookie));
215
216        // A second request should also get EWOULDBLOCK and add to the
217        // existing pending BGFetch
218        EXPECT_EQ(ENGINE_EWOULDBLOCK, store->set(item, cookie));
219
220        // Manually run the BGFetcher task; to fetch the two outstanding
221        // requests (for the same key).
222        runBGFetcherTask();
223
224        EXPECT_EQ(ENGINE_SUCCESS, store->set(item, cookie))
225            << "Expected to set on evicted item after notify_IO_complete";
226
227    } else {
228        FAIL() << "Unhandled GetParam() value:" << GetParam();
229    }
230}
231
232// Add tests //////////////////////////////////////////////////////////////////
233
234// Test add against an ejected key.
235TEST_P(EPStoreEvictionTest, AddEExists) {
236
237    // Store an item, then eject it.
238    auto item = make_item(vbid, makeStoredDocKey("key"), "value");
239    EXPECT_EQ(ENGINE_SUCCESS, store->set(item, cookie));
240    flush_vbucket_to_disk(vbid);
241    evict_key(item.getVBucketId(), item.getKey());
242
243    // Setup a lambda for how we want to call add (saves repeating the
244    // same arguments for each instance below).
245    auto do_add = [this]() {
246        auto item = make_item(vbid, makeStoredDocKey("key"), "value2");
247        return store->add(item, cookie);
248    };
249
250    if (GetParam() == "value_only") {
251        // Should immediately return NOT_STORED (as metadata is still resident).
252        EXPECT_EQ(ENGINE_NOT_STORED, do_add());
253
254    } else if (GetParam() == "full_eviction") {
255        // Should get EWOULDBLOCK as need to go to disk to get metadata.
256        EXPECT_EQ(ENGINE_EWOULDBLOCK, do_add());
257
258        // A second request should also get EWOULDBLOCK and add to the
259        // existing pending BGFetch
260        EXPECT_EQ(ENGINE_EWOULDBLOCK, do_add());
261
262        // Manually run the BGFetcher task; to fetch the two outstanding
263        // requests (for the same key).
264        runBGFetcherTask();
265
266        EXPECT_EQ(ENGINE_NOT_STORED, do_add())
267            << "Expected to fail to add on evicted item after notify_IO_complete";
268
269    } else {
270        FAIL() << "Unhandled GetParam() value:" << GetParam();
271    }
272}
273
274// SetWithMeta tests //////////////////////////////////////////////////////////
275
276// Test setWithMeta replacing an existing, non-resident item
277TEST_P(EPStoreEvictionTest, SetWithMeta_ReplaceNonResident) {
278    // Store an item, then evict it.
279    auto item = make_item(vbid, makeStoredDocKey("key"), "value");
280    EXPECT_EQ(ENGINE_SUCCESS, store->set(item, cookie));
281    flush_vbucket_to_disk(vbid);
282    evict_key(item.getVBucketId(), item.getKey());
283
284    // Increase revSeqno so conflict resolution doesn't fail.
285    item.setRevSeqno(item.getRevSeqno() + 1);
286
287    // Setup a lambda for how we want to call setWithMeta (saves repeating the
288    // same arguments for each instance below).
289    auto do_setWithMeta = [this, item]() mutable {
290        uint64_t seqno;
291        return store->setWithMeta(std::ref(item),
292                                  item.getCas(),
293                                  &seqno,
294                                  cookie,
295                                  {vbucket_state_active},
296                                  CheckConflicts::No,
297                                  /*allowExisting*/ true);
298    };
299
300    if (GetParam() == "value_only") {
301        // Should succeed as the metadata is still resident.
302        EXPECT_EQ(ENGINE_SUCCESS, do_setWithMeta());
303
304    } else if (GetParam() == "full_eviction") {
305        // Should get EWOULDBLOCK as need to go to disk to get metadata.
306        EXPECT_EQ(ENGINE_EWOULDBLOCK, do_setWithMeta());
307
308        // A second request should also get EWOULDBLOCK and add to the
309        // existing pending BGFetch
310        EXPECT_EQ(ENGINE_EWOULDBLOCK, do_setWithMeta());
311
312        // Manually run the BGFetcher task; to fetch the two outstanding
313        // requests (for the same key).
314        runBGFetcherTask();
315
316        ASSERT_EQ(ENGINE_SUCCESS, do_setWithMeta())
317            << "Expected to setWithMeta on evicted item after notify_IO_complete";
318
319    } else {
320        FAIL() << "Unhandled GetParam() value:" << GetParam();
321    }
322}
323
324// Deleted-with-Value Tests ///////////////////////////////////////////////////
325
326TEST_P(EPStoreEvictionTest, DeletedValue) {
327    // Create a deleted item which has a value, then evict it.
328    auto key = makeStoredDocKey("key");
329    auto item = make_item(vbid, key, "deleted value");
330    item.setDeleted();
331    EXPECT_EQ(ENGINE_SUCCESS, store->set(item, cookie));
332
333    // The act of flushing will remove the item from the HashTable.
334    flush_vbucket_to_disk(vbid);
335    EXPECT_EQ(0, store->getVBucket(vbid)->getNumItems());
336
337    // Setup a lambda for how we want to call get (saves repeating the
338    // same arguments for each instance below).
339    auto do_get = [this, &key]() {
340        auto options = get_options_t(GET_DELETED_VALUE | QUEUE_BG_FETCH);
341        return store->get(key, vbid, cookie, options);
342    };
343
344    // Try to get the Deleted-with-value key. This should return EWOULDBLOCK
345    // (as the Deleted value is not resident).
346    EXPECT_EQ(ENGINE_EWOULDBLOCK, do_get().getStatus())
347            << "Expected to need to go to disk to get Deleted-with-value key";
348
349    // Try a second time - this should detect the already-created temp
350    // item, and re-schedule the bgfetch.
351    EXPECT_EQ(ENGINE_EWOULDBLOCK, do_get().getStatus())
352            << "Expected to need to go to disk to get Deleted-with-value key "
353               "(try 2)";
354
355    // Manually run the BGFetcher task; to fetch the two outstanding
356    // requests (for the same key).
357    MockGlobalTask mockTask(engine->getTaskable(), TaskId::MultiBGFetcherTask);
358    store->getVBucket(vbid)->getShard()->getBgFetcher()->run(&mockTask);
359
360    auto result = do_get();
361    EXPECT_EQ(ENGINE_SUCCESS, result.getStatus())
362            << "Expected to get Deleted-with-value for evicted key after "
363               "notify_IO_complete";
364    EXPECT_EQ("deleted value", result.item->getValue()->to_s());
365}
366
367// Test to ensure all pendingBGfetches are deleted when the
368// VBucketMemoryDeletionTask is run
369TEST_P(EPStoreEvictionTest, MB_21976) {
370    // Store an item, then eject it.
371    auto item = make_item(vbid, makeStoredDocKey("key"), "value");
372    EXPECT_EQ(ENGINE_SUCCESS, store->set(item, cookie));
373    flush_vbucket_to_disk(vbid);
374    evict_key(item.getVBucketId(), item.getKey());
375
376    // Perform a get, which should EWOULDBLOCK
377    get_options_t options = static_cast<get_options_t>(QUEUE_BG_FETCH |
378                                                       HONOR_STATES |
379                                                       TRACK_REFERENCE |
380                                                       DELETE_TEMP |
381                                                       HIDE_LOCKED_CAS |
382                                                       TRACK_STATISTICS);
383    GetValue gv = store->get(makeStoredDocKey("key"), vbid, cookie, options);
384    EXPECT_EQ(ENGINE_EWOULDBLOCK, gv.getStatus());
385
386    // Mark the status of the cookie so that we can see if notify is called
387    lock_mock_cookie(cookie);
388    struct mock_connstruct* c = (struct mock_connstruct *)cookie;
389    c->status = ENGINE_E2BIG;
390    unlock_mock_cookie(cookie);
391
392    const void* deleteCookie = create_mock_cookie();
393
394    // lock the cookie, waitfor will release and enter the internal cond-var
395    lock_mock_cookie(deleteCookie);
396    store->deleteVBucket(vbid, deleteCookie);
397    waitfor_mock_cookie(deleteCookie);
398    unlock_mock_cookie(deleteCookie);
399
400    // Check the status of the cookie to see if the cookie status has changed
401    // to ENGINE_NOT_MY_VBUCKET, which means the notify was sent
402    EXPECT_EQ(ENGINE_NOT_MY_VBUCKET, c->status);
403
404    destroy_mock_cookie(deleteCookie);
405}
406
407TEST_P(EPStoreEvictionTest, TouchCmdDuringBgFetch) {
408    const DocKey dockey("key", DocNamespace::DefaultCollection);
409    const int numTouchCmds = 2;
410    auto expiryTime = time(NULL) + 1000;
411
412    // Store an item
413    store_item(vbid, dockey, "value");
414
415    // Trigger a flush to disk.
416    flush_vbucket_to_disk(vbid);
417
418    // Evict the item
419    evict_key(vbid, dockey);
420
421    // Issue 2 touch commands
422    for (int i = 0; i < numTouchCmds; ++i) {
423        GetValue gv = store->getAndUpdateTtl(dockey, vbid, cookie,
424                                             (i + 1) * expiryTime);
425        EXPECT_EQ(ENGINE_EWOULDBLOCK, gv.getStatus());
426    }
427
428    // Manually run the BGFetcher task; to fetch the two outstanding
429    // requests (for the same key).
430    runBGFetcherTask();
431
432    // Issue 2 touch commands again to mock actions post notify from bgFetch
433    for (int i = 0; i < numTouchCmds; ++i) {
434        GetValue gv = store->getAndUpdateTtl(dockey, vbid, cookie,
435                                             (i + 1) * (expiryTime));
436        EXPECT_EQ(ENGINE_SUCCESS, gv.getStatus());
437    }
438    EXPECT_EQ(numTouchCmds + 1 /* Initial item store */,
439              store->getVBucket(vbid)->getHighSeqno());
440}
441
442TEST_P(EPStoreEvictionTest, checkIfResidentAfterBgFetch) {
443    const DocKey dockey("key", DocNamespace::DefaultCollection);
444
445    //Store an item
446    store_item(vbid, dockey, "value");
447
448    //Trigger a flush to disk
449    flush_vbucket_to_disk(vbid);
450
451    //Now, delete the item
452    uint64_t cas = 0;
453    mutation_descr_t mutation_descr;
454    ASSERT_EQ(ENGINE_SUCCESS,
455              store->deleteItem(dockey,
456                                cas,
457                                vbid,
458                                /*cookie*/ cookie,
459                                /*itemMeta*/ nullptr,
460                                mutation_descr));
461
462    flush_vbucket_to_disk(vbid);
463
464    get_options_t options = static_cast<get_options_t>(QUEUE_BG_FETCH |
465                                                       HONOR_STATES   |
466                                                       TRACK_REFERENCE |
467                                                       DELETE_TEMP |
468                                                       HIDE_LOCKED_CAS |
469                                                       TRACK_STATISTICS |
470                                                       GET_DELETED_VALUE);
471
472    GetValue gv = store->get(makeStoredDocKey("key"), vbid, cookie, options);
473    EXPECT_EQ(ENGINE_EWOULDBLOCK, gv.getStatus());
474
475    runBGFetcherTask();
476
477    // The Get should succeed in this case
478    gv = store->get(makeStoredDocKey("key"), vbid, cookie, options);
479    EXPECT_EQ(ENGINE_SUCCESS, gv.getStatus());
480
481    VBucketPtr vb = store->getVBucket(vbid);
482
483    auto hbl = vb->ht.getLockedBucket(dockey);
484    StoredValue* v = vb->ht.unlocked_find(dockey,
485                                          hbl.getBucketNum(),
486                                          WantsDeleted::Yes,
487                                          TrackReference::No);
488
489    EXPECT_TRUE(v->isResident());
490}
491
492TEST_P(EPStoreEvictionTest, xattrExpiryOnFullyEvictedItem) {
493    if (GetParam() == "value_only") {
494        return;
495    }
496
497    cb::xattr::Blob builder;
498
499    //Add a few values
500    builder.set("_meta", "{\"rev\":10}");
501    builder.set("foo", "{\"blob\":true}");
502
503    auto blob = builder.finalize();
504    auto blob_data = to_string(blob);
505    auto itm = store_item(vbid,
506                          makeStoredDocKey("key"),
507                          blob_data,
508                          0,
509                          {cb::engine_errc::success},
510                          (PROTOCOL_BINARY_DATATYPE_JSON |
511                           PROTOCOL_BINARY_DATATYPE_XATTR));
512
513    GetValue gv = store->getAndUpdateTtl(makeStoredDocKey("key"), vbid, cookie,
514                                         time(NULL) + 120);
515    EXPECT_EQ(ENGINE_SUCCESS, gv.getStatus());
516    std::unique_ptr<Item> get_itm(std::move(gv.item));
517
518    flush_vbucket_to_disk(vbid);
519    evict_key(vbid, makeStoredDocKey("key"));
520    store->deleteExpiredItem(itm, time(NULL) + 121, ExpireBy::Compactor);
521
522    get_options_t options = static_cast<get_options_t>(QUEUE_BG_FETCH |
523                                                       HONOR_STATES |
524                                                       TRACK_REFERENCE |
525                                                       DELETE_TEMP |
526                                                       HIDE_LOCKED_CAS |
527                                                       TRACK_STATISTICS |
528                                                       GET_DELETED_VALUE);
529
530    gv = store->get(makeStoredDocKey("key"), vbid, cookie, options);
531    EXPECT_EQ(ENGINE_SUCCESS, gv.getStatus());
532
533    get_itm = std::move(gv.item);
534    auto get_data = const_cast<char*>(get_itm->getData());
535    EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_XATTR, get_itm->getDataType())
536              << "Unexpected Datatype";
537
538    cb::char_buffer value_buf{get_data, get_itm->getNBytes()};
539    cb::xattr::Blob new_blob(value_buf, /*compressed?*/ false);
540
541    const std::string& rev_str{"{\"rev\":10}"};
542    const std::string& meta_str = to_string(new_blob.get("_meta"));
543
544    EXPECT_EQ(rev_str, meta_str) << "Unexpected system xattrs";
545    EXPECT_TRUE(new_blob.get("foo").empty())
546            << "The foo attribute should be gone";
547}
548
549/**
550 * Verify that when getIf is used it only fetches the metdata from disk for
551 * the filter, and not the complete document.
552 * Negative case where filter doesn't match.
553**/
554TEST_P(EPStoreEvictionTest, getIfOnlyFetchesMetaForFilterNegative) {
555    // Store an item, then eject it.
556    auto item = make_item(vbid, makeStoredDocKey("key"), "value");
557    EXPECT_EQ(ENGINE_SUCCESS, store->set(item, cookie));
558    flush_vbucket_to_disk(vbid);
559    evict_key(item.getVBucketId(), item.getKey());
560
561    // Setup a lambda for how we want to call get_if() - filter always returns
562    // false.
563    auto do_getIf = [this]() {
564        return engine->get_if(cookie,
565                              makeStoredDocKey("key"),
566                              vbid,
567                              [](const item_info& info) { return false; });
568    };
569
570    auto& stats = engine->getEpStats();
571    ASSERT_EQ(0, stats.bg_fetched);
572    ASSERT_EQ(0, stats.bg_meta_fetched);
573
574    if (GetParam() == "value_only") {
575        // Value-only should reject (via filter) on first attempt (no need to
576        // go to disk).
577        auto res = do_getIf();
578        EXPECT_EQ(cb::engine_errc::success, res.first);
579        EXPECT_EQ(nullptr, res.second.get());
580
581    } else if (GetParam() == "full_eviction") {
582
583        // First attempt should return EWOULDBLOCK (as the item has been evicted
584        // and we need to fetch).
585        auto res = do_getIf();
586        EXPECT_EQ(cb::engine_errc::would_block, res.first);
587
588        // Manually run the BGFetcher task; to fetch the outstanding meta fetch.
589        // requests (for the same key).
590        runBGFetcherTask();
591        EXPECT_EQ(0, stats.bg_fetched);
592        EXPECT_EQ(1, stats.bg_meta_fetched);
593
594        // Second attempt - should succeed this time, without a match.
595        res = do_getIf();
596        EXPECT_EQ(cb::engine_errc::success, res.first);
597        EXPECT_EQ(nullptr, res.second.get());
598
599    } else {
600        FAIL() << "Unhandled GetParam() value:" << GetParam();
601    }
602}
603
604/**
605 * Verify that when getIf is used it only fetches the metdata from disk for
606 * the filter, and not the complete document.
607 * Positive case where filter does match.
608**/
609TEST_P(EPStoreEvictionTest, getIfOnlyFetchesMetaForFilterPositive) {
610    // Store an item, then eject it.
611    auto item = make_item(vbid, makeStoredDocKey("key"), "value");
612    EXPECT_EQ(ENGINE_SUCCESS, store->set(item, cookie));
613    flush_vbucket_to_disk(vbid);
614    evict_key(item.getVBucketId(), item.getKey());
615
616    // Setup a lambda for how we want to call get_if() - filter always returns
617    // true.
618    auto do_getIf = [this]() {
619        return engine->get_if(cookie,
620                              makeStoredDocKey("key"),
621                              vbid,
622                              [](const item_info& info) { return true; });
623    };
624
625    auto& stats = engine->getEpStats();
626    ASSERT_EQ(0, stats.bg_fetched);
627    ASSERT_EQ(0, stats.bg_meta_fetched);
628
629    if (GetParam() == "value_only") {
630        // Value-only should match filter on first attempt, and then return
631        // bgfetch to get the body.
632        auto res = do_getIf();
633        EXPECT_EQ(cb::engine_errc::would_block, res.first);
634        EXPECT_EQ(nullptr, res.second.get());
635
636        // Manually run the BGFetcher task; to fetch the outstanding body.
637        runBGFetcherTask();
638        EXPECT_EQ(1, stats.bg_fetched);
639        ASSERT_EQ(0, stats.bg_meta_fetched);
640
641        res = do_getIf();
642        EXPECT_EQ(cb::engine_errc::success, res.first);
643        ASSERT_NE(nullptr, res.second.get());
644        Item* epItem = static_cast<Item*>(res.second.get());
645        ASSERT_NE(nullptr, epItem->getValue().get().get());
646        EXPECT_EQ("value", epItem->getValue()->to_s());
647
648    } else if (GetParam() == "full_eviction") {
649
650        // First attempt should return would_block (as the item has been evicted
651        // and we need to fetch).
652        auto res = do_getIf();
653        EXPECT_EQ(cb::engine_errc::would_block, res.first);
654
655        // Manually run the BGFetcher task; to fetch the outstanding meta fetch.
656        runBGFetcherTask();
657        EXPECT_EQ(0, stats.bg_fetched);
658        EXPECT_EQ(1, stats.bg_meta_fetched);
659
660        // Second attempt - should get as far as applying the filter, but
661        // will need to go to disk a second time for the body.
662        res = do_getIf();
663        EXPECT_EQ(cb::engine_errc::would_block, res.first);
664
665        // Manually run the BGFetcher task; this time to fetch the body.
666        runBGFetcherTask();
667        EXPECT_EQ(1, stats.bg_fetched);
668        EXPECT_EQ(1, stats.bg_meta_fetched);
669
670        // Third call to getIf - should have result now.
671        res = do_getIf();
672        EXPECT_EQ(cb::engine_errc::success, res.first);
673        ASSERT_NE(nullptr, res.second.get());
674        Item* epItem = static_cast<Item*>(res.second.get());
675        ASSERT_NE(nullptr, epItem->getValue().get().get());
676        EXPECT_EQ("value", epItem->getValue()->to_s());
677
678    } else {
679        FAIL() << "Unhandled GetParam() value:" << GetParam();
680    }
681}
682
683/**
684 * Verify that a get of a deleted item with no value successfully
685 * returns an item
686 */
687TEST_P(EPStoreEvictionTest, getDeletedItemWithNoValue) {
688    const DocKey dockey("key", DocNamespace::DefaultCollection);
689
690    // Store an item
691    store_item(vbid, dockey, "value");
692
693    // Trigger a flush to disk
694    flush_vbucket_to_disk(vbid);
695
696    uint64_t cas = 0;
697    mutation_descr_t mutation_descr;
698    ASSERT_EQ(ENGINE_SUCCESS,
699              store->deleteItem(dockey,
700                                cas,
701                                vbid,
702                                /*cookie*/ cookie,
703                                /*itemMeta*/ nullptr,
704                                mutation_descr));
705
706    // Ensure that the delete has been persisted
707    flush_vbucket_to_disk(vbid);
708
709    get_options_t options = static_cast<get_options_t>(QUEUE_BG_FETCH |
710                                                       HONOR_STATES |
711                                                       TRACK_REFERENCE |
712                                                       DELETE_TEMP |
713                                                       HIDE_LOCKED_CAS |
714                                                       TRACK_STATISTICS |
715                                                       GET_DELETED_VALUE);
716
717    GetValue gv = store->get(makeStoredDocKey("key"), vbid, cookie, options);
718    EXPECT_EQ(ENGINE_EWOULDBLOCK, gv.getStatus());
719
720    runBGFetcherTask();
721
722    // The Get should succeed in this case
723    gv = store->get(makeStoredDocKey("key"), vbid, cookie, options);
724    EXPECT_EQ(ENGINE_SUCCESS, gv.getStatus());
725
726    // Ensure that the item is deleted and the value length is zero
727    Item* itm = gv.item.get();
728    value_t value = itm->getValue();
729    EXPECT_EQ(0, value->valueSize());
730    EXPECT_TRUE(itm->isDeleted());
731}
732
733/**
734 * Verify that a get of a deleted item with value successfully
735 * returns an item
736 */
737TEST_P(EPStoreEvictionTest, getDeletedItemWithValue) {
738    const DocKey dockey("key", DocNamespace::DefaultCollection);
739
740    // Store an item
741    store_item(vbid, dockey, "value");
742
743    // Trigger a flush to disk
744    flush_vbucket_to_disk(vbid);
745
746    auto item = make_item(vbid, dockey, "deletedvalue");
747    item.setDeleted();
748    EXPECT_EQ(ENGINE_SUCCESS, store->set(item, cookie));
749    flush_vbucket_to_disk(vbid);
750
751    //Perform a get
752    get_options_t options = static_cast<get_options_t>(QUEUE_BG_FETCH |
753                                                       HONOR_STATES |
754                                                       TRACK_REFERENCE |
755                                                       DELETE_TEMP |
756                                                       HIDE_LOCKED_CAS |
757                                                       TRACK_STATISTICS |
758                                                       GET_DELETED_VALUE);
759
760    GetValue gv = store->get(dockey, vbid, cookie, options);
761    EXPECT_EQ(ENGINE_EWOULDBLOCK, gv.getStatus());
762
763    runBGFetcherTask();
764
765    // The Get should succeed in this case
766    gv = store->get(dockey, vbid, cookie, options);
767    EXPECT_EQ(ENGINE_SUCCESS, gv.getStatus());
768
769    // Ensure that the item is deleted and the value matches
770    Item* itm = gv.item.get();
771    EXPECT_EQ("deletedvalue", itm->getValue()->to_s());
772    EXPECT_TRUE(itm->isDeleted());
773}
774
775//Test to verify the behavior in the condition where
776//memOverhead is greater than the bucket quota
777TEST_P(EPStoreEvictionTest, memOverheadMemoryCondition) {
778    //Limit the bucket quota to 200K
779    Configuration& config = engine->getConfiguration();
780    config.setMaxSize(204800);
781    config.setMemHighWat(0.8 * 204800);
782    config.setMemLowWat(0.6 * 204800);
783
784    //Ensure the memOverhead is greater than the bucket quota
785    auto& stats = engine->getEpStats();
786    stats.coreLocal.get()->memOverhead.store(config.getMaxSize() + 1);
787
788    // Fill bucket until we hit ENOMEM - note storing via external
789    // API (epstore) so we trigger the memoryCondition() code in the event of
790    // ENGINE_ENOMEM.
791    size_t count = 0;
792    const std::string value(512, 'x'); // 512B value to use for documents.
793    ENGINE_ERROR_CODE result;
794    auto dummyCookie = std::make_unique<mock_connstruct>();
795    for (result = ENGINE_SUCCESS; result == ENGINE_SUCCESS; count++) {
796        auto item = make_item(vbid,
797                              makeStoredDocKey("key_" + std::to_string(count)),
798                              value);
799        uint64_t cas;
800        result = engine->store(dummyCookie.get(), &item, cas, OPERATION_SET);
801    }
802
803    if (GetParam() == "value_only") {
804        ASSERT_EQ(ENGINE_ENOMEM, result);
805    } else {
806        ASSERT_EQ(ENGINE_TMPFAIL, result);
807    }
808}
809
810class EPStoreEvictionBloomOnOffTest
811        : public EPBucketTest,
812          public ::testing::WithParamInterface<
813                  ::testing::tuple<std::string, bool>> {
814public:
815    void SetUp() override {
816        config_string += std::string{"item_eviction_policy="} +
817                         ::testing::get<0>(GetParam());
818
819        if (::testing::get<1>(GetParam())) {
820            config_string += ";bfilter_enabled=true";
821        } else {
822            config_string += ";bfilter_enabled=false";
823        }
824
825        EPBucketTest::SetUp();
826
827        // Have all the objects, activate vBucket zero so we can store data.
828        store->setVBucketState(vbid, vbucket_state_active, false);
829    }
830};
831
832TEST_P(EPStoreEvictionBloomOnOffTest, store_if_throws) {
833    // You can't keep returning GetItemInfo
834    cb::StoreIfPredicate predicate = [](
835            const boost::optional<item_info>& existing,
836            cb::vbucket_info vb) -> cb::StoreIfStatus {
837        return cb::StoreIfStatus::GetItemInfo;
838    };
839
840    auto key = makeStoredDocKey("key");
841    auto item = make_item(vbid, key, "value2");
842
843    store_item(vbid, key, "value1");
844    flush_vbucket_to_disk(vbid);
845    evict_key(vbid, key);
846
847    if (::testing::get<0>(GetParam()) == "full_eviction") {
848        EXPECT_NO_THROW(engine->store_if(
849                cookie, item, 0 /*cas*/, OPERATION_SET, predicate));
850        runBGFetcherTask();
851    }
852
853    // If the itemInfo exists, you can't ask for it again - so expect throw
854    EXPECT_THROW(
855            engine->store_if(cookie, item, 0 /*cas*/, OPERATION_SET, predicate),
856            std::logic_error);
857}
858
859TEST_P(EPStoreEvictionBloomOnOffTest, store_if) {
860    struct TestData {
861        StoredDocKey key;
862        cb::StoreIfPredicate predicate;
863        cb::engine_errc expectedVEStatus;
864        cb::engine_errc expectedFEStatus;
865        cb::engine_errc actualStatus;
866    };
867
868    std::vector<TestData> testData;
869    cb::StoreIfPredicate predicate1 = [](
870            const boost::optional<item_info>& existing,
871            cb::vbucket_info vb) -> cb::StoreIfStatus {
872        return cb::StoreIfStatus::Continue;
873    };
874    cb::StoreIfPredicate predicate2 = [](
875            const boost::optional<item_info>& existing,
876            cb::vbucket_info vb) -> cb::StoreIfStatus {
877        return cb::StoreIfStatus::Fail;
878    };
879    cb::StoreIfPredicate predicate3 = [](
880            const boost::optional<item_info>& existing,
881            cb::vbucket_info vb) -> cb::StoreIfStatus {
882        if (existing.is_initialized()) {
883            return cb::StoreIfStatus::Continue;
884        }
885        return cb::StoreIfStatus::GetItemInfo;
886    };
887    cb::StoreIfPredicate predicate4 = [](
888            const boost::optional<item_info>& existing,
889            cb::vbucket_info vb) -> cb::StoreIfStatus {
890        if (existing.is_initialized()) {
891            return cb::StoreIfStatus::Fail;
892        }
893        return cb::StoreIfStatus::GetItemInfo;
894    };
895
896    testData.push_back({makeStoredDocKey("key1"),
897                        predicate1,
898                        cb::engine_errc::success,
899                        cb::engine_errc::success});
900    testData.push_back({makeStoredDocKey("key2"),
901                        predicate2,
902                        cb::engine_errc::predicate_failed,
903                        cb::engine_errc::predicate_failed});
904    testData.push_back({makeStoredDocKey("key3"),
905                        predicate3,
906                        cb::engine_errc::success,
907                        cb::engine_errc::would_block});
908    testData.push_back({makeStoredDocKey("key4"),
909                        predicate4,
910                        cb::engine_errc::predicate_failed,
911                        cb::engine_errc::would_block});
912
913    for (auto& test : testData) {
914        store_item(vbid, test.key, "value");
915        flush_vbucket_to_disk(vbid);
916        evict_key(vbid, test.key);
917        auto item = make_item(vbid, test.key, "new_value");
918        test.actualStatus = engine->store_if(cookie,
919                                             item,
920                                             0 /*cas*/,
921                                             OPERATION_SET,
922                                             test.predicate)
923                                    .status;
924        if (test.actualStatus == cb::engine_errc::success) {
925            flush_vbucket_to_disk(vbid);
926        }
927    }
928
929    for (size_t i = 0; i < testData.size(); i++) {
930        if (::testing::get<0>(GetParam()) == "value_only") {
931            EXPECT_EQ(testData[i].expectedVEStatus, testData[i].actualStatus)
932                    << "Failed value_only iteration " + std::to_string(i);
933        } else if (::testing::get<0>(GetParam()) == "full_eviction") {
934            EXPECT_EQ(testData[i].expectedFEStatus, testData[i].actualStatus)
935                    << "Failed full_eviction iteration " + std::to_string(i);
936        } else {
937            FAIL() << "Unhandled GetParam() value:"
938                   << ::testing::get<0>(GetParam());
939        }
940    }
941
942    if (::testing::get<0>(GetParam()) == "full_eviction") {
943        runBGFetcherTask();
944        for (size_t i = 0; i < testData.size(); i++) {
945            if (testData[i].actualStatus == cb::engine_errc::would_block) {
946                auto item = make_item(vbid, testData[i].key, "new_value");
947                auto status = engine->store_if(cookie,
948                                               item,
949                                               0 /*cas*/,
950                                               OPERATION_SET,
951                                               testData[i].predicate);
952                // The second run should result the same as VE
953                EXPECT_EQ(testData[i].expectedVEStatus, status.status);
954            }
955        }
956    }
957}
958
959TEST_P(EPStoreEvictionBloomOnOffTest, store_if_fe_interleave) {
960    if (::testing::get<0>(GetParam()) != "full_eviction") {
961        return;
962    }
963
964    cb::StoreIfPredicate predicate = [](
965            const boost::optional<item_info>& existing,
966            cb::vbucket_info vb) -> cb::StoreIfStatus {
967        if (existing.is_initialized()) {
968            return cb::StoreIfStatus::Continue;
969        }
970        return cb::StoreIfStatus::GetItemInfo;
971    };
972
973    auto key = makeStoredDocKey("key");
974    auto item = make_item(vbid, key, "value2");
975
976    store_item(vbid, key, "value1");
977    flush_vbucket_to_disk(vbid);
978    evict_key(vbid, key);
979
980    EXPECT_EQ(
981            cb::engine_errc::would_block,
982            engine->store_if(cookie, item, 0 /*cas*/, OPERATION_SET, predicate)
983                    .status);
984
985    // expect another store to the same key to be told the same, even though the
986    // first store has populated the store with a temp item
987    EXPECT_EQ(
988            cb::engine_errc::would_block,
989            engine->store_if(cookie, item, 0 /*cas*/, OPERATION_SET, predicate)
990                    .status);
991
992    runBGFetcherTask();
993    EXPECT_EQ(
994            cb::engine_errc::success,
995            engine->store_if(cookie, item, 0 /*cas*/, OPERATION_SET, predicate)
996                    .status);
997}
998
999// Run in FE only with bloom filter off, so all gets turn into bgFetches
1000class EPStoreFullEvictionNoBloomFIlterTest : public EPBucketTest {
1001    void SetUp() override {
1002        config_string += std::string{"item_eviction_policy=full_eviction;"
1003                                     "bfilter_enabled=false"};
1004        EPBucketTest::SetUp();
1005
1006        // Have all the objects, activate vBucket zero so we can store data.
1007        store->setVBucketState(vbid, vbucket_state_active, false);
1008    }
1009};
1010
1011// Demonstrate the couchstore issue affects get - if we have multiple gets in
1012// one batch and the keys are crafted in such a way, we will skip out the get
1013// of the one key which really does exist.
1014TEST_F(EPStoreFullEvictionNoBloomFIlterTest, MB_29816) {
1015    auto key = makeStoredDocKey("005");
1016    store_item(vbid, key, "value");
1017    flush_vbucket_to_disk(vbid);
1018    evict_key(vbid, key);
1019
1020    auto key2 = makeStoredDocKey("004");
1021    get_options_t options = static_cast<get_options_t>(
1022            QUEUE_BG_FETCH | HONOR_STATES | TRACK_REFERENCE | DELETE_TEMP |
1023            HIDE_LOCKED_CAS | TRACK_STATISTICS);
1024    auto gv = store->get(key, vbid, cookie, options);
1025    EXPECT_EQ(ENGINE_EWOULDBLOCK, gv.getStatus());
1026    gv = store->get(key2, vbid, cookie, options);
1027    EXPECT_EQ(ENGINE_EWOULDBLOCK, gv.getStatus());
1028
1029    runBGFetcherTask();
1030
1031    // Get the keys again
1032    gv = store->get(key, vbid, cookie, options);
1033    ASSERT_EQ(ENGINE_SUCCESS, gv.getStatus()) << "key:005 should of been found";
1034
1035    gv = store->get(key2, vbid, cookie, options);
1036    ASSERT_EQ(ENGINE_KEY_ENOENT, gv.getStatus());
1037}
1038
1039struct PrintToStringCombinedName {
1040    std::string operator()(
1041            const ::testing::TestParamInfo<::testing::tuple<std::string, bool>>&
1042                    info) const {
1043        std::string bfilter = "_bfilter_enabled";
1044        if (!::testing::get<1>(info.param)) {
1045            bfilter = "_bfilter_disabled";
1046        }
1047        return ::testing::get<0>(info.param) + bfilter;
1048    }
1049};
1050
1051// Test cases which run in both Full and Value eviction, and with bloomfilter
1052// on and off.
1053INSTANTIATE_TEST_CASE_P(FullAndValueEvictionBloomOnOff,
1054                        EPStoreEvictionBloomOnOffTest,
1055                        ::testing::Combine(::testing::Values("value_only",
1056                                                             "full_eviction"),
1057                                           ::testing::Bool()),
1058                        PrintToStringCombinedName());
1059
1060// Test cases which run in both Full and Value eviction
1061INSTANTIATE_TEST_CASE_P(FullAndValueEviction,
1062                        EPStoreEvictionTest,
1063                        ::testing::Values("value_only", "full_eviction"),
1064                        [] (const ::testing::TestParamInfo<std::string>& info) {
1065                            return info.param;
1066                        });
1067