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 
test_create_new_checkpoint(EngineIface* h)31 static 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 
test_validate_checkpoint_params(EngineIface* h)63 static 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 
test_checkpoint_create(EngineIface* h)105 static 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 
test_checkpoint_timeout(EngineIface* h)122 static 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 
test_checkpoint_deduplication(EngineIface* h)131 static 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 
146 extern "C" {
checkpoint_persistence_thread(void *arg)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 
test_checkpoint_persistence(EngineIface* h)176 static 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 
211 extern "C" {
wait_for_persistence_thread(void *arg)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 
test_wait_for_persist_vb_del(EngineIface* h)220 static 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 
244 const char *default_dbname = "./ep_testsuite_checkpoint";
245 
246 BaseTestCase 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