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 * Tests for Rollback functionality in EPStore.
20 */
21
22#include "connmap.h"
23#include "evp_store_test.h"
24#include "programs/engine_testapp/mock_server.h"
25
26class RollbackTest : public EventuallyPersistentStoreTest,
27                     public ::testing::WithParamInterface<std::string>
28{
29    void SetUp() override {
30        EventuallyPersistentStoreTest::SetUp();
31        // Start vbucket as active to allow us to store items directly to it.
32        store->setVBucketState(vbid, vbucket_state_active, false);
33
34        // For any rollback tests which actually want to rollback, we need
35        // to ensure that we don't rollback more than 50% of the seqno count
36        // as then the VBucket is just cleared (it'll instead expect a resync
37        // from zero.
38        // Therefore create 10 dummy items which we don't otherwise care
39        // about (most of the Rollback test only work with a couple of
40        // "active" items.
41        const auto dummy_elements = size_t{5};
42        for (size_t ii = 1; ii <= dummy_elements; ii++) {
43            auto res = store_item(vbid, "dummy" + std::to_string(ii),
44                                  "dummy");
45            ASSERT_EQ(ii, res.getBySeqno());
46        }
47        ASSERT_EQ(dummy_elements, store->flushVBucket(vbid));
48        initial_seqno = dummy_elements;
49    }
50
51protected:
52    /*
53     * Fake callback emulating dcp_add_failover_log
54     */
55    static ENGINE_ERROR_CODE fakeDcpAddFailoverLog(vbucket_failover_t* entry,
56                                                   size_t nentries,
57                                                   const void *cookie) {
58        return ENGINE_SUCCESS;
59    }
60
61    /**
62     * Test rollback after deleting an item.
63     * @param flush_before_rollback: Should the vbuckt be flushed to disk just
64     *        before the rollback (i.e. guaranteeing the in-memory state is in sync
65     *        with disk).
66     */
67    void rollback_after_deletion_test(bool flush_before_rollback) {
68        // Setup: Store an item then flush the vBucket (creating a checkpoint);
69        // then delete the item and create a second checkpoint.
70        std::string a("a");
71        auto item_v1 = store_item(vbid, a, "1");
72        ASSERT_EQ(initial_seqno + 1, item_v1.getBySeqno());
73        ASSERT_EQ(1, store->flushVBucket(vbid));
74        uint64_t cas = item_v1.getCas();
75        mutation_descr_t mut_info;
76        ASSERT_EQ(ENGINE_SUCCESS,
77                  store->deleteItem(a, &cas, vbid, /*cookie*/nullptr,
78                                    /*force*/false, /*itemMeta*/nullptr,
79                                    &mut_info));
80        if (flush_before_rollback) {
81            ASSERT_EQ(1, store->flushVBucket(vbid));
82        }
83        // Sanity-check - item should no longer exist.
84        EXPECT_EQ(ENGINE_KEY_ENOENT,
85                  store->get(a, vbid, nullptr, {}).getStatus());
86
87        // Test - rollback to seqno of item_v1 and verify that the previous value
88        // of the item has been restored.
89        store->setVBucketState(vbid, vbucket_state_replica, false);
90        ASSERT_EQ(TaskStatus::Complete,
91                  store->rollback(vbid, item_v1.getBySeqno()));
92        auto result = store->public_getInternal(a, vbid, /*cookie*/nullptr,
93                                                vbucket_state_replica, {});
94        ASSERT_EQ(ENGINE_SUCCESS, result.getStatus());
95        EXPECT_EQ(item_v1, *result.getValue())
96            << "Fetched item after rollback should match item_v1";
97        delete result.getValue();
98
99        if (!flush_before_rollback) {
100            EXPECT_EQ(0, store->flushVBucket(vbid));
101        }
102    }
103
104    // Test rollback after modifying an item.
105    void rollback_after_mutation_test(bool flush_before_rollback) {
106        // Setup: Store an item then flush the vBucket (creating a checkpoint);
107        // then update the item with a new value and create a second checkpoint.
108        std::string a("a");
109        auto item_v1 = store_item(vbid, a, "old");
110        ASSERT_EQ(initial_seqno + 1, item_v1.getBySeqno());
111        ASSERT_EQ(1, store->flushVBucket(vbid));
112
113        auto item2 = store_item(vbid, a, "new");
114        ASSERT_EQ(initial_seqno + 2, item2.getBySeqno());
115
116        std::string key("key");
117        store_item(vbid, key, "meh");
118
119        if (flush_before_rollback) {
120            EXPECT_EQ(2, store->flushVBucket(vbid));
121        }
122
123        // Test - rollback to seqno of item_v1 and verify that the previous value
124        // of the item has been restored.
125        store->setVBucketState(vbid, vbucket_state_replica, false);
126        ASSERT_EQ(TaskStatus::Complete,
127                  store->rollback(vbid, item_v1.getBySeqno()));
128        ASSERT_EQ(item_v1.getBySeqno(), store->getVBucket(vbid)->getHighSeqno());
129
130        // a should have the value of 'old'
131        {
132            auto result = store->get(a, vbid, nullptr, {});
133            ASSERT_EQ(ENGINE_SUCCESS, result.getStatus());
134            EXPECT_EQ(item_v1, *result.getValue())
135                << "Fetched item after rollback should match item_v1";
136            delete result.getValue();
137        }
138
139        // key should be gone
140        {
141            auto result = store->get(key, vbid, nullptr, {});
142            EXPECT_EQ(ENGINE_KEY_ENOENT, result.getStatus())
143                << "A key set after the rollback point was found";
144        }
145
146        if (!flush_before_rollback) {
147            // The rollback should of wiped out any keys waiting for persistence
148            EXPECT_EQ(0, store->flushVBucket(vbid));
149        }
150    }
151
152    // This test passes, but note that if we warmed up, there is data loss.
153    void rollback_to_middle_test(bool flush_before_rollback) {
154        // create some more checkpoints just to see a few iterations
155        // of parts of the rollback function.
156
157        // need to store a certain number of keys because rollback
158        // 'bails' if the rollback is too much.
159        for (int i = 0; i < 6; i++) {
160            std::string key = "key_" + std::to_string(i);
161            store_item(vbid, key.c_str(), "dontcare");
162        }
163        // the roll back function will rewind disk to key7.
164        auto rollback_item = store_item(vbid, "key7", "dontcare");
165        ASSERT_EQ(7, store->flushVBucket(vbid));
166
167        // every key past this point will be lost from disk in a mid-point.
168        auto item_v1 = store_item(vbid, "rollback-cp-1", "keep-me");
169        auto item_v2 = store_item(vbid, "rollback-cp-2", "rollback to me");
170        store_item(vbid, "rollback-cp-3", "i'm gone");
171        auto rollback = item_v2.getBySeqno(); // ask to rollback to here.
172        ASSERT_EQ(3, store->flushVBucket(vbid));
173
174        for (int i = 0; i < 3; i++) {
175            std::string key = "anotherkey_" + std::to_string(i);
176            store_item(vbid, key.c_str(), "dontcare");
177        }
178
179        if (flush_before_rollback) {
180            ASSERT_EQ(3, store->flushVBucket(vbid));
181        }
182
183
184        // Rollback should succeed, but rollback to 0
185        store->setVBucketState(vbid, vbucket_state_replica, false);
186        EXPECT_EQ(TaskStatus::Complete, store->rollback(vbid, rollback));
187
188        // These keys should be gone after the rollback
189        for (int i = 0; i < 3; i++) {
190            std::string key = "rollback-cp-" + std::to_string(i);
191            auto result = store->get(key, vbid, nullptr, {});
192            EXPECT_EQ(ENGINE_KEY_ENOENT, result.getStatus())
193                << "A key set after the rollback point was found";
194        }
195
196        // These keys should be gone after the rollback
197        for (int i = 0; i < 3; i++) {
198            std::string key = "anotherkey_" + std::to_string(i);
199            auto result = store->get(key, vbid, nullptr, {});
200            EXPECT_EQ(ENGINE_KEY_ENOENT, result.getStatus())
201                << "A key set after the rollback point was found";
202        }
203
204        // Rolled back to the previous checkpoint
205        EXPECT_EQ(rollback_item.getBySeqno(),
206                  store->getVBucket(vbid)->getHighSeqno());
207    }
208
209protected:
210    int64_t initial_seqno;
211};
212
213TEST_P(RollbackTest, RollbackAfterMutation) {
214    rollback_after_mutation_test(/*flush_before_rollbaack*/true);
215}
216
217TEST_P(RollbackTest, RollbackAfterMutationNoFlush) {
218    rollback_after_mutation_test(/*flush_before_rollback*/false);
219}
220
221TEST_P(RollbackTest, RollbackAfterDeletion) {
222    rollback_after_deletion_test(/*flush_before_rollback*/true);
223}
224
225TEST_P(RollbackTest, RollbackAfterDeletionNoFlush) {
226    rollback_after_deletion_test(/*flush_before_rollback*/false);
227}
228
229TEST_P(RollbackTest, RollbackToMiddleOfAPersistedSnapshot) {
230    rollback_to_middle_test(true);
231}
232
233TEST_P(RollbackTest, RollbackToMiddleOfAPersistedSnapshotNoFlush) {
234    rollback_to_middle_test(false);
235}
236
237#if !defined(_MSC_VER) || _MSC_VER != 1800
238TEST_P(RollbackTest, RollbackToMiddleOfAnUnPersistedSnapshot) {
239    /* need to store a certain number of keys because rollback
240       'bails (rolls back to 0)' if the rollback is too much. */
241    const int numItems = 10;
242    for (int i = 0; i < numItems; i++) {
243        std::string key = "key_" + std::to_string(i);
244        store_item(vbid, key.c_str(), "not rolled back");
245    }
246
247    /* the roll back function will rewind disk to key11. */
248    auto rollback_item =
249    store_item(vbid, "key11", "rollback pt");
250
251    ASSERT_EQ(numItems + 1, store->flushVBucket(vbid));
252
253    /* Keys to be lost in rollback */
254    auto item_v1 = store_item(vbid, "rollback-cp-1", "hope to keep till here");
255    /* ask to rollback to here; this item is in a checkpoint and
256       is not persisted */
257    auto rollbackReqSeqno = item_v1.getBySeqno();
258
259    auto item_v2 = store_item(vbid, "rollback-cp-2", "gone");
260
261    /* do rollback */
262    store->setVBucketState(vbid, vbucket_state_replica, false);
263    EXPECT_EQ(TaskStatus::Complete, store->rollback(vbid, rollbackReqSeqno));
264
265    /* confirm that we have rolled back to the disk snapshot */
266    EXPECT_EQ(rollback_item.getBySeqno(),
267              store->getVBucket(vbid)->getHighSeqno());
268
269    /* since we rely only on disk snapshots currently, we must lose the items in
270       the checkpoints */
271    for (int i = 0; i < 2; i++) {
272        auto res = store->get(std::string("rollback-cp-" + std::to_string(i)),
273                              vbid,
274                              nullptr,
275                              {});
276        EXPECT_EQ(ENGINE_KEY_ENOENT, res.getStatus())
277                << "A key set after the rollback point was found";
278    }
279}
280#endif
281
282/*
283 * The opencheckpointid of a bucket is one after a rollback.
284 */
285TEST_P(RollbackTest, MB21784) {
286    // Make the vbucket a replica
287    store->setVBucketState(vbid, vbucket_state_replica, false);
288    // Perform a rollback
289    EXPECT_EQ(TaskStatus::Complete, store->rollback(vbid, initial_seqno))
290            << "rollback did not return success";
291
292    // Assert the checkpointmanager clear function (called during rollback)
293    // has set the opencheckpointid to one
294    auto vb = store->getVbMap().getBucket(vbid);
295    auto& ckpt_mgr = vb->checkpointManager;
296    EXPECT_EQ(1, ckpt_mgr.getOpenCheckpointId()) << "opencheckpointId not one";
297
298    // Create a new Dcp producer, reserving its cookie.
299    get_mock_server_api()->cookie->reserve(cookie);
300    dcp_producer_t producer = engine->getDcpConnMap().newProducer(
301            cookie, "test_producer", /*notifyOnly*/false);
302
303    uint64_t rollbackSeqno;
304    auto err = producer->streamRequest(/*flags*/0,
305                                       /*opaque*/0,
306                                       /*vbucket*/vbid,
307                                       /*start_seqno*/0,
308                                       /*end_seqno*/0,
309                                       /*vb_uuid*/0,
310                                       /*snap_start*/0,
311                                       /*snap_end*/0,
312                                       &rollbackSeqno,
313                                       RollbackTest::fakeDcpAddFailoverLog);
314    EXPECT_EQ(ENGINE_SUCCESS, err)
315        << "stream request did not return ENGINE_SUCCESS";
316    // Close stream
317    ASSERT_EQ(ENGINE_SUCCESS, producer->closeStream(/*opaque*/0, vbid));
318    engine->handleDisconnect(cookie);
319}
320
321TEST_P(RollbackTest, RollbackOnActive) {
322    /* Store 3 items */
323    const int numItems = 3;
324    for (int i = 0; i < numItems; i++) {
325        std::string key = "key_" + std::to_string(i);
326        store_item(vbid, key.c_str(), "not rolled back");
327    }
328
329    /* Try to rollback on active (default state) vbucket */
330    EXPECT_EQ(TaskStatus::Abort,
331              store->rollback(vbid, numItems - 1 /*rollbackReqSeqno*/));
332
333    EXPECT_EQ(TaskStatus::Abort, store->rollback(vbid, 0 /*rollbackReqSeqno*/));
334}
335
336// Test cases which run in both Full and Value eviction
337INSTANTIATE_TEST_CASE_P(FullAndValueEviction,
338                        RollbackTest,
339                        ::testing::Values("value_only", "full_eviction"),
340                        [] (const ::testing::TestParamInfo<std::string>& info) {
341                            return info.param;
342                        });
343