MB-24066/MB-22178: Set opencheckpointid to 1 after rollback
[ep-engine.git] / tests / module_tests / evp_store_rollback_test.cc
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
26 class 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
51 protected:
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(ENGINE_SUCCESS, store->rollback(vbid, item_v1.getBySeqno()));
91         auto result = store->public_getInternal(a, vbid, /*cookie*/nullptr,
92                                                 vbucket_state_replica, {});
93         ASSERT_EQ(ENGINE_SUCCESS, result.getStatus());
94         EXPECT_EQ(item_v1, *result.getValue())
95             << "Fetched item after rollback should match item_v1";
96         delete result.getValue();
97
98         if (!flush_before_rollback) {
99             EXPECT_EQ(0, store->flushVBucket(vbid));
100         }
101     }
102
103     // Test rollback after modifying an item.
104     void rollback_after_mutation_test(bool flush_before_rollback) {
105         // Setup: Store an item then flush the vBucket (creating a checkpoint);
106         // then update the item with a new value and create a second checkpoint.
107         std::string a("a");
108         auto item_v1 = store_item(vbid, a, "old");
109         ASSERT_EQ(initial_seqno + 1, item_v1.getBySeqno());
110         ASSERT_EQ(1, store->flushVBucket(vbid));
111
112         auto item2 = store_item(vbid, a, "new");
113         ASSERT_EQ(initial_seqno + 2, item2.getBySeqno());
114
115         std::string key("key");
116         store_item(vbid, key, "meh");
117
118         if (flush_before_rollback) {
119             EXPECT_EQ(2, store->flushVBucket(vbid));
120         }
121
122         // Test - rollback to seqno of item_v1 and verify that the previous value
123         // of the item has been restored.
124         store->setVBucketState(vbid, vbucket_state_replica, false);
125         ASSERT_EQ(ENGINE_SUCCESS, store->rollback(vbid, item_v1.getBySeqno()));
126         ASSERT_EQ(item_v1.getBySeqno(), store->getVBucket(vbid)->getHighSeqno());
127
128         // a should have the value of 'old'
129         {
130             auto result = store->get(a, vbid, nullptr, {});
131             ASSERT_EQ(ENGINE_SUCCESS, result.getStatus());
132             EXPECT_EQ(item_v1, *result.getValue())
133                 << "Fetched item after rollback should match item_v1";
134             delete result.getValue();
135         }
136
137         // key should be gone
138         {
139             auto result = store->get(key, vbid, nullptr, {});
140             EXPECT_EQ(ENGINE_KEY_ENOENT, result.getStatus())
141                 << "A key set after the rollback point was found";
142         }
143
144         if (!flush_before_rollback) {
145             // The rollback should of wiped out any keys waiting for persistence
146             EXPECT_EQ(0, store->flushVBucket(vbid));
147         }
148     }
149
150     // This test passes, but note that if we warmed up, there is data loss.
151     void rollback_to_middle_test(bool flush_before_rollback) {
152         // create some more checkpoints just to see a few iterations
153         // of parts of the rollback function.
154
155         // need to store a certain number of keys because rollback
156         // 'bails' if the rollback is too much.
157         for (int i = 0; i < 6; i++) {
158             std::string key = "key_" + std::to_string(i);
159             store_item(vbid, key.c_str(), "dontcare");
160         }
161         // the roll back function will rewind disk to key7.
162         auto rollback_item = store_item(vbid, "key7", "dontcare");
163         ASSERT_EQ(7, store->flushVBucket(vbid));
164
165         // every key past this point will be lost from disk in a mid-point.
166         auto item_v1 = store_item(vbid, "rollback-cp-1", "keep-me");
167         auto item_v2 = store_item(vbid, "rollback-cp-2", "rollback to me");
168         store_item(vbid, "rollback-cp-3", "i'm gone");
169         auto rollback = item_v2.getBySeqno(); // ask to rollback to here.
170         ASSERT_EQ(3, store->flushVBucket(vbid));
171
172         for (int i = 0; i < 3; i++) {
173             std::string key = "anotherkey_" + std::to_string(i);
174             store_item(vbid, key.c_str(), "dontcare");
175         }
176
177         if (flush_before_rollback) {
178             ASSERT_EQ(3, store->flushVBucket(vbid));
179         }
180
181
182         // Rollback should succeed, but rollback to 0
183         store->setVBucketState(vbid, vbucket_state_replica, false);
184         EXPECT_EQ(ENGINE_SUCCESS, store->rollback(vbid, rollback));
185
186         // These keys should be gone after the rollback
187         for (int i = 0; i < 3; i++) {
188             std::string key = "rollback-cp-" + std::to_string(i);
189             auto result = store->get(key, vbid, nullptr, {});
190             EXPECT_EQ(ENGINE_KEY_ENOENT, result.getStatus())
191                 << "A key set after the rollback point was found";
192         }
193
194         // These keys should be gone after the rollback
195         for (int i = 0; i < 3; i++) {
196             std::string key = "anotherkey_" + std::to_string(i);
197             auto result = store->get(key, vbid, nullptr, {});
198             EXPECT_EQ(ENGINE_KEY_ENOENT, result.getStatus())
199                 << "A key set after the rollback point was found";
200         }
201
202         // Rolled back to the previous checkpoint
203         EXPECT_EQ(rollback_item.getBySeqno(),
204                   store->getVBucket(vbid)->getHighSeqno());
205     }
206
207 protected:
208     int64_t initial_seqno;
209 };
210
211 TEST_P(RollbackTest, RollbackAfterMutation) {
212     rollback_after_mutation_test(/*flush_before_rollbaack*/true);
213 }
214
215 TEST_P(RollbackTest, RollbackAfterMutationNoFlush) {
216     rollback_after_mutation_test(/*flush_before_rollback*/false);
217 }
218
219 TEST_P(RollbackTest, RollbackAfterDeletion) {
220     rollback_after_deletion_test(/*flush_before_rollback*/true);
221 }
222
223 TEST_P(RollbackTest, RollbackAfterDeletionNoFlush) {
224     rollback_after_deletion_test(/*flush_before_rollback*/false);
225 }
226
227 TEST_P(RollbackTest, RollbackToMiddleOfAPersistedSnapshot) {
228     rollback_to_middle_test(true);
229 }
230
231 TEST_P(RollbackTest, RollbackToMiddleOfAPersistedSnapshotNoFlush) {
232     rollback_to_middle_test(false);
233 }
234
235 #if !defined(_MSC_VER) || _MSC_VER != 1800
236 TEST_P(RollbackTest, RollbackToMiddleOfAnUnPersistedSnapshot) {
237     /* need to store a certain number of keys because rollback
238        'bails (rolls back to 0)' if the rollback is too much. */
239     const int numItems = 10;
240     for (int i = 0; i < numItems; i++) {
241         std::string key = "key_" + std::to_string(i);
242         store_item(vbid, key.c_str(), "not rolled back");
243     }
244
245     /* the roll back function will rewind disk to key11. */
246     auto rollback_item =
247     store_item(vbid, "key11", "rollback pt");
248
249     ASSERT_EQ(numItems + 1, store->flushVBucket(vbid));
250
251     /* Keys to be lost in rollback */
252     auto item_v1 = store_item(vbid, "rollback-cp-1", "hope to keep till here");
253     /* ask to rollback to here; this item is in a checkpoint and
254        is not persisted */
255     auto rollbackReqSeqno = item_v1.getBySeqno();
256
257     auto item_v2 = store_item(vbid, "rollback-cp-2", "gone");
258
259     /* do rollback */
260     store->setVBucketState(vbid, vbucket_state_replica, false);
261     EXPECT_EQ(ENGINE_SUCCESS, store->rollback(vbid, rollbackReqSeqno));
262
263     /* confirm that we have rolled back to the disk snapshot */
264     EXPECT_EQ(rollback_item.getBySeqno(),
265               store->getVBucket(vbid)->getHighSeqno());
266
267     /* since we rely only on disk snapshots currently, we must lose the items in
268        the checkpoints */
269     for (int i = 0; i < 2; i++) {
270         auto res = store->get(std::string("rollback-cp-" + std::to_string(i)),
271                               vbid,
272                               nullptr,
273                               {});
274         EXPECT_EQ(ENGINE_KEY_ENOENT, res.getStatus())
275                 << "A key set after the rollback point was found";
276     }
277 }
278 #endif
279
280 /*
281  * The opencheckpointid of a bucket is one after a rollback.
282  */
283 TEST_P(RollbackTest, MB21784) {
284     // Make the vbucket a replica
285     store->setVBucketState(vbid, vbucket_state_replica, false);
286     // Perform a rollback
287     EXPECT_EQ(ENGINE_SUCCESS, store->rollback(vbid, initial_seqno))
288         << "rollback did not return ENGINE_SUCCESS";
289
290     // Assert the checkpointmanager clear function (called during rollback)
291     // has set the opencheckpointid to one
292     auto vb = store->getVbMap().getBucket(vbid);
293     auto& ckpt_mgr = vb->checkpointManager;
294     EXPECT_EQ(1, ckpt_mgr.getOpenCheckpointId()) << "opencheckpointId not one";
295
296     // Create a new Dcp producer, reserving its cookie.
297     get_mock_server_api()->cookie->reserve(cookie);
298     dcp_producer_t producer = engine->getDcpConnMap().newProducer(
299             cookie, "test_producer", /*notifyOnly*/false);
300
301     uint64_t rollbackSeqno;
302     auto err = producer->streamRequest(/*flags*/0,
303                                        /*opaque*/0,
304                                        /*vbucket*/vbid,
305                                        /*start_seqno*/0,
306                                        /*end_seqno*/0,
307                                        /*vb_uuid*/0,
308                                        /*snap_start*/0,
309                                        /*snap_end*/0,
310                                        &rollbackSeqno,
311                                        RollbackTest::fakeDcpAddFailoverLog);
312     EXPECT_EQ(ENGINE_SUCCESS, err)
313         << "stream request did not return ENGINE_SUCCESS";
314     // Close stream
315     ASSERT_EQ(ENGINE_SUCCESS, producer->closeStream(/*opaque*/0, vbid));
316     engine->handleDisconnect(cookie);
317 }
318
319 // Test cases which run in both Full and Value eviction
320 INSTANTIATE_TEST_CASE_P(FullAndValueEviction,
321                         RollbackTest,
322                         ::testing::Values("value_only", "full_eviction"),
323                         [] (const ::testing::TestParamInfo<std::string>& info) {
324                             return info.param;
325                         });