1/* -*- MODE: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2016 Couchbase, Inc
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17
18/*
19 * Testsuite for checkpoint functionality in ep-engine.
20 */
21#include "ep_test_apis.h"
22#include "ep_testsuite_common.h"
23
24#include <platform/cbassert.h>
25#include <platform/platform_thread.h>
26
27// Helper functions ///////////////////////////////////////////////////////////
28
29// Testcases //////////////////////////////////////////////////////////////////
30
31static enum test_result test_create_new_checkpoint(EngineIface* h) {
32    // Inserting more than 5 items (see testcase config) will cause a new open
33    // checkpoint with id 2 to be created.
34
35    write_items(h, 5);
36    wait_for_flusher_to_settle(h);
37
38    checkeq(1,
39            get_int_stat(h, "vb_0:last_closed_checkpoint_id", "checkpoint 0"),
40            "Last closed checkpoint Id for VB 0 should still be 1 after "
41            "storing 50 items");
42
43    // Store 1 more - should push it over to the next checkpoint.
44    write_items(h, 1, 5);
45    wait_for_flusher_to_settle(h);
46
47    checkeq(2,
48            get_int_stat(h, "vb_0:last_closed_checkpoint_id", "checkpoint 0"),
49            "Last closed checkpoint Id for VB 0 should increase to 2 after "
50            "storing 51 items");
51
52    createCheckpoint(h);
53    checkeq(cb::mcbp::Status::Success, last_status.load(),
54            "Expected success response from creating a new checkpoint");
55
56    checkeq(3,
57            get_int_stat(h, "vb_0:last_closed_checkpoint_id", "checkpoint 0"),
58            "Last closed checkpoint Id for VB 0 should be 3");
59
60    return SUCCESS;
61}
62
63static enum test_result test_validate_checkpoint_params(EngineIface* h) {
64    set_param(h,
65              cb::mcbp::request::SetParamPayload::Type::Checkpoint,
66              "chk_max_items",
67              "1000");
68    checkeq(cb::mcbp::Status::Success, last_status.load(),
69            "Failed to set checkpoint_max_item param");
70    set_param(h,
71              cb::mcbp::request::SetParamPayload::Type::Checkpoint,
72              "chk_period",
73              "100");
74    checkeq(cb::mcbp::Status::Success, last_status.load(),
75            "Failed to set checkpoint_period param");
76    set_param(h,
77              cb::mcbp::request::SetParamPayload::Type::Checkpoint,
78              "max_checkpoints",
79              "2");
80    checkeq(cb::mcbp::Status::Success, last_status.load(),
81            "Failed to set max_checkpoints param");
82
83    set_param(h,
84              cb::mcbp::request::SetParamPayload::Type::Checkpoint,
85              "chk_max_items",
86              "5");
87    checkeq(cb::mcbp::Status::Einval, last_status.load(),
88            "Expected to have an invalid value error for checkpoint_max_items param");
89    set_param(h,
90              cb::mcbp::request::SetParamPayload::Type::Checkpoint,
91              "chk_period",
92              "0");
93    checkeq(cb::mcbp::Status::Einval, last_status.load(),
94            "Expected to have an invalid value error for checkpoint_period param");
95    set_param(h,
96              cb::mcbp::request::SetParamPayload::Type::Checkpoint,
97              "max_checkpoints",
98              "6");
99    checkeq(cb::mcbp::Status::Einval, last_status.load(),
100            "Expected to have an invalid value error for max_checkpoints param");
101
102    return SUCCESS;
103}
104
105static enum test_result test_checkpoint_create(EngineIface* h) {
106    for (int i = 0; i < 5001; i++) {
107        char key[8];
108        sprintf(key, "key%d", i);
109        checkeq(ENGINE_SUCCESS,
110                store(h, NULL, OPERATION_SET, key, "value"),
111                "Failed to store an item.");
112    }
113    checkeq(3,
114            get_int_stat(h, "vb_0:open_checkpoint_id", "checkpoint"),
115            "New checkpoint wasn't create after 5001 item creates");
116    checkeq(1,
117            get_int_stat(h, "vb_0:num_open_checkpoint_items", "checkpoint"),
118            "New open checkpoint should has only one dirty item");
119    return SUCCESS;
120}
121
122static enum test_result test_checkpoint_timeout(EngineIface* h) {
123    checkeq(ENGINE_SUCCESS,
124            store(h, NULL, OPERATION_SET, "key", "value"),
125            "Failed to store an item.");
126    testHarness->time_travel(600);
127    wait_for_stat_to_be(h, "vb_0:open_checkpoint_id", 2, "checkpoint");
128    return SUCCESS;
129}
130
131static enum test_result test_checkpoint_deduplication(EngineIface* h) {
132    for (int i = 0; i < 5; i++) {
133        for (int j = 0; j < 4500; j++) {
134            char key[8];
135            sprintf(key, "key%d", j);
136            checkeq(ENGINE_SUCCESS,
137                    store(h, NULL, OPERATION_SET, key, "value"),
138                    "Failed to store an item.");
139        }
140    }
141    // 4500 keys + 1x checkpoint_start + 1x set_vbucket_state.
142    wait_for_stat_to_be(h, "vb_0:num_checkpoint_items", 4502, "checkpoint");
143    return SUCCESS;
144}
145
146extern "C" {
147    static void checkpoint_persistence_thread(void *arg) {
148        auto* h = static_cast<EngineIface*>(arg);
149
150        // Issue a request with the unexpected large checkpoint id 100, which
151        // will cause timeout.
152        checkeq(ENGINE_TMPFAIL, checkpointPersistence(h, 100, Vbid(0)),
153              "Expected temp failure for checkpoint persistence request");
154        checklt(10,
155                get_int_stat(h, "ep_chk_persistence_timeout"),
156                "Expected CHECKPOINT_PERSISTENCE_TIMEOUT was adjusted to be "
157                "greater"
158                " than 10 secs");
159
160        for (int j = 0; j < 10; ++j) {
161            std::stringstream ss;
162            ss << "key" << j;
163            checkeq(ENGINE_SUCCESS,
164                    store(h,
165                          NULL,
166                          OPERATION_SET,
167                          ss.str().c_str(),
168                          ss.str().c_str()),
169                    "Failed to store a value");
170        }
171
172        createCheckpoint(h);
173    }
174}
175
176static enum test_result test_checkpoint_persistence(EngineIface* h) {
177    if (!isPersistentBucket(h)) {
178        checkeq(ENGINE_SUCCESS,
179                checkpointPersistence(h, 0, Vbid(0)),
180                "Failed to request checkpoint persistence");
181        checkeq(last_status.load(),
182                cb::mcbp::Status::NotSupported,
183                "Expected checkpoint persistence not be supported");
184        return SUCCESS;
185    }
186
187    const int  n_threads = 2;
188    cb_thread_t threads[n_threads];
189
190    for (int i = 0; i < n_threads; ++i) {
191        int r = cb_create_thread(
192                &threads[i], checkpoint_persistence_thread, h, 0);
193        cb_assert(r == 0);
194    }
195
196    for (int i = 0; i < n_threads; ++i) {
197        int r = cb_join_thread(threads[i]);
198        cb_assert(r == 0);
199    }
200
201    // Last closed checkpoint id for vbucket 0.
202    int closed_chk_id =
203            get_int_stat(h, "vb_0:last_closed_checkpoint_id", "checkpoint 0");
204    // Request to prioritize persisting vbucket 0.
205    checkeq(ENGINE_SUCCESS, checkpointPersistence(h, closed_chk_id, Vbid(0)),
206          "Failed to request checkpoint persistence");
207
208    return SUCCESS;
209}
210
211extern "C" {
212    static void wait_for_persistence_thread(void *arg) {
213        auto* h = static_cast<EngineIface*>(arg);
214
215        checkeq(ENGINE_TMPFAIL, checkpointPersistence(h, 100, Vbid(1)),
216                "Expected temp failure for checkpoint persistence request");
217    }
218}
219
220static enum test_result test_wait_for_persist_vb_del(EngineIface* h) {
221    cb_thread_t th;
222    check(set_vbucket_state(h, Vbid(1), vbucket_state_active),
223          "Failed to set vbucket state.");
224
225    int ret = cb_create_thread(&th, wait_for_persistence_thread, h, 0);
226    cb_assert(ret == 0);
227
228    wait_for_stat_to_be(h, "ep_chk_persistence_remains", 1);
229
230    checkeq(ENGINE_SUCCESS, vbucketDelete(h, Vbid(1)), "Expected success");
231    checkeq(cb::mcbp::Status::Success, last_status.load(),
232            "Failure deleting dead bucket.");
233    check(verify_vbucket_missing(h, Vbid(1)),
234          "vbucket 1 was not missing after deleting it.");
235
236    ret = cb_join_thread(th);
237    cb_assert(ret == 0);
238
239    return SUCCESS;
240}
241
242// Test manifest //////////////////////////////////////////////////////////////
243
244const char *default_dbname = "./ep_testsuite_checkpoint";
245
246BaseTestCase testsuite_testcases[] = {
247        TestCase("checkpoint: create a new checkpoint",
248                 test_create_new_checkpoint,
249                 test_setup,
250                 teardown,
251                 "chk_max_items=5;item_num_based_new_chk=true;chk_period=600",
252                 prepare,
253                 cleanup),
254        TestCase("checkpoint: validate checkpoint config params",
255                 test_validate_checkpoint_params,
256                 test_setup,
257                 teardown,
258                 NULL,
259                 prepare,
260                 cleanup),
261        TestCase("test checkpoint create",
262                 test_checkpoint_create,
263                 test_setup,
264                 teardown,
265                 "chk_max_items=5000;chk_period=600",
266                 prepare,
267                 cleanup),
268        TestCase("test checkpoint timeout",
269                 test_checkpoint_timeout,
270                 test_setup,
271                 teardown,
272                 "chk_max_items=5000;chk_period=600",
273                 prepare,
274                 cleanup),
275        TestCase("test checkpoint deduplication",
276                 test_checkpoint_deduplication,
277                 test_setup,
278                 teardown,
279                 "chk_max_items=5000;"
280                 "chk_period=600;"
281                 "chk_expel_enabled=false",
282                /* Checkpoint expelling needs to be disabled for this test because
283                 * the test checks that 4500 items created 5 times are correctly
284                 * de-duplicated (i.e. 4 * 4500 items are duplicated away).
285                 * If expelling is enabled it is possible that some items will be
286                 * expelled and hence will not get duplicated away.  Therefore the
287                 * expected number of items in the checkpoint will not match.
288                 */
289                 prepare,
290                 cleanup),
291        TestCase("checkpoint: wait for persistence",
292                 test_checkpoint_persistence,
293                 test_setup,
294                 teardown,
295                 "chk_max_items=500;max_checkpoints=5;item_num_based_new_chk=true;chk_period=600"
296                 "true",
297                 prepare,
298                 cleanup),
299        TestCase("test wait for persist vb del",
300                 test_wait_for_persist_vb_del,
301                 test_setup,
302                 teardown,
303                 NULL,
304                 prepare_ep_bucket, /* checks if we delete vb is successful
305                                       in presence of a pending chkPersistence
306                                       req; in ephemeral buckets we don't
307                                       handle chkPersistence requests */
308                 cleanup),
309
310        TestCase(NULL, NULL, NULL, NULL, NULL, prepare, cleanup)};
311