1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 * Copyright 2012 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 #include "ep_test_apis.h"
19 #include "ep_testsuite_common.h"
20
21 #include <folly/portability/SysStat.h>
22 #include <mcbp/protocol/framebuilder.h>
23 #include <memcached/util.h>
24 #include <platform/cb_malloc.h>
25 #include <platform/cbassert.h>
26 #include <platform/dirutils.h>
27 #include <platform/strerror.h>
28
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include <algorithm>
33 #include <chrono>
34 #include <iostream>
35 #include <list>
36 #include <mutex>
37 #include <sstream>
38 #include <thread>
39
40 #include "mock/mock_dcp.h"
41
42 using namespace std::string_literals;
43
CouchstoreFileAccessGuard(std::string dbName,CouchstoreFileAccessGuard::Mode mode)44 CouchstoreFileAccessGuard::CouchstoreFileAccessGuard(
45 std::string dbName, CouchstoreFileAccessGuard::Mode mode) {
46 /* Make the couchstore files in the db directory unwritable.
47 *
48 * Note we can't just make the directory itself unwritable as other
49 * files (e.g. stats.json) need to be written and we are just testing
50 * document write failures here.
51 */
52 const auto dbFiles = cb::io::findFilesContaining(dbName, ".couch.");
53 checkeq(size_t{1},
54 dbFiles.size(),
55 ("Expected to find exactly 1 data file in db directory '"s +
56 dbName + "', found:" + std::to_string(dbFiles.size()))
57 .c_str());
58
59 filename = dbFiles.front();
60 // Save existing permissions
61 checkeq(0,
62 lstat(filename.c_str(), &originalStat),
63 ("Failed to read existing permissions for file '"s + filename +
64 "': " + cb_strerror())
65 .c_str());
66
67 const auto perms =
68 (mode == Mode::ReadOnly) ? (S_IRUSR | S_IRGRP | S_IROTH) : 0;
69
70 checkeq(0,
71 chmod(filename.c_str(), perms),
72 ("Failed to make file '"s + dbFiles.at(0) +
73 "' read-only: " + cb_strerror())
74 .c_str());
75 }
76
~CouchstoreFileAccessGuard()77 CouchstoreFileAccessGuard::~CouchstoreFileAccessGuard() {
78 // Restore permissions to before we changed them.
79 checkeq(0,
80 chmod(filename.c_str(), originalStat.st_mode),
81 ("Failed to make restore permissions to file '"s + filename +
82 "': " + cb_strerror())
83 .c_str());
84 }
85
86 template<typename T> class HistogramStats;
87
88 // Due to the limitations of the add_stats callback (essentially we cannot pass
89 // a context into it) we instead have a single, global `vals` map. The
90 // vals_mutex is to ensure serialised modifications to this data structure.
91 std::mutex vals_mutex;
92 statistic_map vals;
93
94 // get_stat and get_histo_stat can only be called one at a time as they use
95 // the three global variables (requested_stat_name, actual_stat_value and
96 // histogram_stat_int_value). Therefore the two functions need to acquire a
97 // lock and keep it for the whole function duration.
98
99 // The requested_stat_name and actual_stat_value are used in an optimized
100 // add_stats callback (add_individual_stat) which checks for one stat
101 // (and hence doesn't have to keep a map of all of them).
102 struct {
103 std::mutex mutex;
104 std::string requested_stat_name;
105 std::string actual_stat_value;
106 /* HistogramStats<T>* is supported C++14 onwards.
107 * Until then use a separate ptr for each type.
108 */
109 HistogramStats<uint64_t>* histogram_stat_int_value;
110 } get_stat_context;
111
112 bool dump_stats = false;
113 std::atomic<cb::mcbp::Status> last_status(cb::mcbp::Status::Success);
114 std::string last_key;
115 std::string last_body;
116 std::string last_ext;
117 std::atomic<uint64_t> last_cas(0);
118 std::atomic<uint8_t> last_datatype(0x00);
119 ItemMetaData last_meta;
120 std::atomic<uint64_t> last_uuid(0);
121 std::atomic<uint64_t> last_seqno(0);
122
123 /* HistogramBinStats is used to hold a histogram bin object a histogram stat.
124 This is a class used to hold already computed stats. Hence we do not expect
125 any change once a bin object is created */
126 template<typename T>
127 class HistogramBinStats {
128 public:
HistogramBinStats(const T & s,const T & e,uint64_t count)129 HistogramBinStats(const T& s, const T& e, uint64_t count)
130 : start_(s), end_(e), count_(count) { }
131
start() const132 T start() const {
133 return start_;
134 }
135
end() const136 T end() const {
137 return end_;
138 }
139
count() const140 uint64_t count() const {
141 return count_;
142 }
143
144 private:
145 T start_;
146 T end_;
147 uint64_t count_;
148 };
149
150
151 /* HistogramStats is used to hold necessary info from a histogram stat.
152 Since this class used to hold already computed stats, only write apis to add
153 new bins is implemented */
154 template<typename T>
155 class HistogramStats {
156 public:
HistogramStats()157 HistogramStats() : total_count(0) {}
158
159 /* Add a new bin */
add_bin(const T & start,const T & end,uint64_t count)160 void add_bin(const T& start, const T& end, uint64_t count) {
161 bins.push_back(HistogramBinStats<T>(start, end, count));
162 total_count += count;
163 }
164
165 /* Num of bins in the histogram */
num_bins() const166 size_t num_bins() const {
167 return bins.size();
168 }
169
total() const170 uint64_t total() const {
171 return total_count;
172 }
173
174 /* Add a bin iterator when needed */
175 private:
176 /* List of all the bins in the histogram stats */
177 std::list<HistogramBinStats<T>> bins;
178 /* Total number of samples across all histogram bins */
179 uint64_t total_count;
180 };
181
182 static void get_histo_stat(EngineIface* h,
183 const char* statname,
184 const char* statkey);
185
186 void encodeExt(char* buffer, uint32_t val, size_t offset = 0);
187 void encodeWithMetaExt(char *buffer, ItemMetaData *meta);
188
decayingSleep(useconds_t * sleepTime)189 void decayingSleep(useconds_t *sleepTime) {
190 static const useconds_t maxSleepTime = 500000;
191 std::this_thread::sleep_for(std::chrono::microseconds(*sleepTime));
192 *sleepTime = std::min(*sleepTime << 1, maxSleepTime);
193 }
194
add_response(const void * key,uint16_t keylen,const void * ext,uint8_t extlen,const void * body,uint32_t bodylen,uint8_t datatype,cb::mcbp::Status status,uint64_t cas,const void * cookie)195 bool add_response(const void* key,
196 uint16_t keylen,
197 const void* ext,
198 uint8_t extlen,
199 const void* body,
200 uint32_t bodylen,
201 uint8_t datatype,
202 cb::mcbp::Status status,
203 uint64_t cas,
204 const void* cookie) {
205 (void)cookie;
206 static std::mutex m;
207 std::lock_guard<std::mutex> lg(m);
208 last_status.store(status);
209 last_body.assign(static_cast<const char*>(body), bodylen);
210 last_ext.assign(static_cast<const char*>(ext), extlen);
211 last_key.assign(static_cast<const char*>(key), keylen);
212 last_cas.store(cas);
213 last_datatype.store(datatype);
214 return true;
215 }
216
add_response_set_del_meta(const void * key,uint16_t keylen,const void * ext,uint8_t extlen,const void * body,uint32_t bodylen,uint8_t datatype,cb::mcbp::Status status,uint64_t cas,const void * cookie)217 bool add_response_set_del_meta(const void* key,
218 uint16_t keylen,
219 const void* ext,
220 uint8_t extlen,
221 const void* body,
222 uint32_t bodylen,
223 uint8_t datatype,
224 cb::mcbp::Status status,
225 uint64_t cas,
226 const void* cookie) {
227 (void)cookie;
228 const auto* ext_bytes = reinterpret_cast<const uint8_t*>(ext);
229 if (ext && extlen > 0) {
230 uint64_t vb_uuid;
231 uint64_t seqno;
232 memcpy(&vb_uuid, ext_bytes, 8);
233 memcpy(&seqno, ext_bytes + 8, 8);
234 last_uuid.store(ntohll(vb_uuid));
235 last_seqno.store(ntohll(seqno));
236 }
237
238 return add_response(key, keylen, ext, extlen, body, bodylen, datatype,
239 status, cas, cookie);
240 }
241
add_response_ret_meta(const void * key,uint16_t keylen,const void * ext,uint8_t extlen,const void * body,uint32_t bodylen,uint8_t datatype,cb::mcbp::Status status,uint64_t cas,const void * cookie)242 bool add_response_ret_meta(const void* key,
243 uint16_t keylen,
244 const void* ext,
245 uint8_t extlen,
246 const void* body,
247 uint32_t bodylen,
248 uint8_t datatype,
249 cb::mcbp::Status status,
250 uint64_t cas,
251 const void* cookie) {
252 (void)cookie;
253 const auto* ext_bytes = reinterpret_cast<const uint8_t*>(ext);
254 if (ext && extlen == 16) {
255 memcpy(&last_meta.flags, ext_bytes, 4);
256 memcpy(&last_meta.exptime, ext_bytes + 4, 4);
257 last_meta.exptime = ntohl(last_meta.exptime);
258 uint64_t revId = 0;
259 memcpy(&revId, ext_bytes + 8, 8);
260 last_meta.revSeqno = ntohll(revId);
261 last_meta.cas = cas;
262 }
263 return add_response(key, keylen, ext, extlen, body, bodylen, datatype,
264 status, cas, cookie);
265 }
266
add_stats(const char * key,const uint16_t klen,const char * val,const uint32_t vlen,gsl::not_null<const void * >)267 void add_stats(const char* key,
268 const uint16_t klen,
269 const char* val,
270 const uint32_t vlen,
271 gsl::not_null<const void*>) {
272 std::string k(key, klen);
273 std::string v(val, vlen);
274
275 if (dump_stats) {
276 std::cout << "stat[" << k << "] = " << v << std::endl;
277 }
278
279 std::lock_guard<std::mutex> lh(vals_mutex);
280 vals[k] = v;
281 }
282
283 /* Callback passed to engine interface `get_stats`, used by get_int_stat and
284 * friends to lookup a specific stat. If `key` matches the requested key name,
285 * then record its value in actual_stat_value.
286 */
add_individual_stat(const char * key,const uint16_t klen,const char * val,const uint32_t vlen,gsl::not_null<const void * >)287 void add_individual_stat(const char* key,
288 const uint16_t klen,
289 const char* val,
290 const uint32_t vlen,
291 gsl::not_null<const void*>) {
292 if (get_stat_context.actual_stat_value.empty() &&
293 get_stat_context.requested_stat_name.compare(
294 0, get_stat_context.requested_stat_name.size(),
295 key, klen) == 0) {
296 get_stat_context.actual_stat_value = std::string(val, vlen);
297 }
298 }
299
add_individual_histo_stat(const char * key,const uint16_t klen,const char * val,const uint32_t vlen,gsl::not_null<const void * > cookie)300 void add_individual_histo_stat(const char* key,
301 const uint16_t klen,
302 const char* val,
303 const uint32_t vlen,
304 gsl::not_null<const void*> cookie) {
305 /* Convert key to string */
306 std::string key_str(key, klen);
307 /* Exclude mean value keys e.g. backfill_tasks_mean */
308 if (key_str.find("_mean") != std::string::npos) {
309 return;
310 }
311
312 size_t pos1 = key_str.find(get_stat_context.requested_stat_name);
313 if (pos1 != std::string::npos) {
314 get_stat_context.actual_stat_value.append(val, vlen);
315 /* Parse start and end from the key.
316 Key is in the format task_name_START,END (backfill_tasks_20,100)
317 */
318 pos1 += get_stat_context.requested_stat_name.length();
319 /* Find ',' to move to end of bin_start */
320 size_t pos2 = key_str.find(',', pos1);
321 if ((std::string::npos == pos2) || (pos1 >= pos2)) {
322 throw std::invalid_argument("Malformed histogram stat: " + key_str);
323 }
324 auto start = std::stoull(std::string(key_str, pos1, pos2));
325
326 /* Move next to ',' for starting character of bin_end */
327 pos1 = pos2 + 1;
328 /* key_str ends with bin_end */
329 pos2 = key_str.length();
330 if (pos1 >= pos2) {
331 throw std::invalid_argument("Malformed histogram stat: " + key_str);
332 }
333 auto end = std::stoull(std::string(key_str, pos1, pos2));
334 get_stat_context.histogram_stat_int_value->add_bin(
335 start, end, std::stoull(val));
336 }
337 }
338
encodeExt(char * buffer,uint32_t val,size_t offset)339 void encodeExt(char* buffer, uint32_t val, size_t offset) {
340 val = htonl(val);
341 memcpy(buffer + offset, (char*)&val, sizeof(val));
342 }
343
encodeWithMetaExt(char * buffer,uint64_t cas,uint64_t revSeqno,uint32_t flags,uint32_t exp)344 void encodeWithMetaExt(char* buffer,
345 uint64_t cas,
346 uint64_t revSeqno,
347 uint32_t flags,
348 uint32_t exp) {
349 memcpy(buffer, (char*)&flags, sizeof(flags));
350 memcpy(buffer + 4, (char*)&exp, sizeof(exp));
351 memcpy(buffer + 8, (char*)&revSeqno, sizeof(revSeqno));
352 memcpy(buffer + 16, (char*)&cas, sizeof(cas));
353 }
354
encodeWithMetaExt(char * buffer,RawItemMetaData * meta)355 void encodeWithMetaExt(char* buffer, RawItemMetaData* meta) {
356 uint32_t flags = meta->flags;
357 uint32_t exp = htonl(meta->exptime);
358 uint64_t seqno = htonll(meta->revSeqno);
359 uint64_t cas = htonll(meta->cas);
360 encodeWithMetaExt(buffer, cas, seqno, flags, exp);
361 }
362
encodeWithMetaExt(char * buffer,ItemMetaData * meta)363 void encodeWithMetaExt(char* buffer, ItemMetaData* meta) {
364 uint32_t flags = meta->flags;
365 uint32_t exp = htonl(meta->exptime);
366 uint64_t seqno = htonll(meta->revSeqno);
367 uint64_t cas = htonll(meta->cas);
368 encodeWithMetaExt(buffer, cas, seqno, flags, exp);
369 }
370
createCheckpoint(EngineIface * h)371 void createCheckpoint(EngineIface* h) {
372 auto request = createPacket(cb::mcbp::ClientOpcode::CreateCheckpoint);
373 checkeq(ENGINE_SUCCESS,
374 h->unknown_command(nullptr, *request, add_response),
375 "Failed to create a new checkpoint.");
376 }
377
del(EngineIface * h,const char * key,uint64_t cas,Vbid vbucket,const void * cookie)378 ENGINE_ERROR_CODE del(EngineIface* h,
379 const char* key,
380 uint64_t cas,
381 Vbid vbucket,
382 const void* cookie) {
383 mutation_descr_t mut_info{};
384 return del(h, key, &cas, vbucket, cookie, &mut_info);
385 }
386
del(EngineIface * h,const char * key,uint64_t * cas,Vbid vbucket,const void * cookie,mutation_descr_t * mut_info)387 ENGINE_ERROR_CODE del(EngineIface* h,
388 const char* key,
389 uint64_t* cas,
390 Vbid vbucket,
391 const void* cookie,
392 mutation_descr_t* mut_info) {
393 bool create_cookie = false;
394 if (cookie == nullptr) {
395 cookie = testHarness->create_cookie();
396 create_cookie = true;
397 }
398
399 auto ret = h->remove(cookie,
400 DocKey(key, DocKeyEncodesCollectionId::No),
401 *cas,
402 vbucket,
403 {},
404 *mut_info);
405 if (create_cookie) {
406 testHarness->destroy_cookie(cookie);
407 }
408
409 return ret;
410 }
411
412 /** Simplified version of store for handling the common case of performing
413 * a delete with a value.
414 */
delete_with_value(EngineIface * h,const void * cookie,uint64_t cas,const char * key,cb::const_char_buffer value,cb::mcbp::Datatype datatype)415 ENGINE_ERROR_CODE delete_with_value(EngineIface* h,
416 const void* cookie,
417 uint64_t cas,
418 const char* key,
419 cb::const_char_buffer value,
420 cb::mcbp::Datatype datatype) {
421 auto ret = storeCasVb11(h,
422 cookie,
423 OPERATION_SET,
424 key,
425 value.data(),
426 value.size(),
427 9258,
428 cas,
429 Vbid(0),
430 /*exp*/ 0,
431 uint8_t(datatype),
432 DocumentState::Deleted);
433 wait_for_flusher_to_settle(h);
434
435 return ENGINE_ERROR_CODE(ret.first);
436 }
437
del_with_meta(EngineIface * h,const char * key,const size_t keylen,const Vbid vb,ItemMetaData * itemMeta,uint64_t cas_for_delete,uint32_t options,const void * cookie,const std::vector<char> & nmeta,protocol_binary_datatype_t datatype,const std::vector<char> & value)438 ENGINE_ERROR_CODE del_with_meta(EngineIface* h,
439 const char* key,
440 const size_t keylen,
441 const Vbid vb,
442 ItemMetaData* itemMeta,
443 uint64_t cas_for_delete,
444 uint32_t options,
445 const void* cookie,
446 const std::vector<char>& nmeta,
447 protocol_binary_datatype_t datatype,
448 const std::vector<char>& value) {
449 RawItemMetaData meta{itemMeta->cas,
450 itemMeta->revSeqno,
451 itemMeta->flags,
452 itemMeta->exptime};
453 return del_with_meta(h,
454 key,
455 keylen,
456 vb,
457 &meta,
458 cas_for_delete,
459 options,
460 cookie,
461 nmeta,
462 datatype,
463 value);
464 }
465
del_with_meta(EngineIface * h,const char * key,const size_t keylen,const Vbid vb,RawItemMetaData * itemMeta,uint64_t cas_for_delete,uint32_t options,const void * cookie,const std::vector<char> & nmeta,protocol_binary_datatype_t datatype,const std::vector<char> & value)466 ENGINE_ERROR_CODE del_with_meta(EngineIface* h,
467 const char* key,
468 const size_t keylen,
469 const Vbid vb,
470 RawItemMetaData* itemMeta,
471 uint64_t cas_for_delete,
472 uint32_t options,
473 const void* cookie,
474 const std::vector<char>& nmeta,
475 protocol_binary_datatype_t datatype,
476 const std::vector<char>& value) {
477 size_t blen = 24;
478 std::unique_ptr<char[]> ext(new char[30]);
479 std::unique_ptr<ExtendedMetaData> emd;
480
481 encodeWithMetaExt(ext.get(), itemMeta);
482
483 if (options) {
484 uint32_t optionsSwapped = htonl(options);
485 memcpy(ext.get() + blen, (char*)&optionsSwapped, sizeof(optionsSwapped));
486 blen += sizeof(uint32_t);
487 }
488
489 if (nmeta.size() > 0) {
490 uint16_t nmetaSize = htons(nmeta.size());
491 memcpy(ext.get() + blen, (char*)&nmetaSize, sizeof(nmetaSize));
492 blen += sizeof(uint16_t);
493 }
494
495 auto pkt = createPacket(cb::mcbp::ClientOpcode::DelWithMeta,
496 vb,
497 cas_for_delete,
498 {ext.get(), blen},
499 {key, keylen},
500 {value.data(), value.size()},
501 datatype,
502 {nmeta.data(), nmeta.size()});
503
504 return h->unknown_command(cookie, *pkt, add_response_set_del_meta);
505 }
506
evict_key(EngineIface * h,const char * key,Vbid vbucketId,const char * msg,bool expectError)507 void evict_key(EngineIface* h,
508 const char* key,
509 Vbid vbucketId,
510 const char* msg,
511 bool expectError) {
512 int nonResidentItems = get_int_stat(h, "ep_num_non_resident");
513 int numEjectedItems = get_int_stat(h, "ep_num_value_ejects");
514 auto pkt = createPacket(cb::mcbp::ClientOpcode::EvictKey,
515 vbucketId,
516 0,
517 {},
518 {key, strlen(key)});
519 checkeq(ENGINE_SUCCESS,
520 h->unknown_command(NULL, *pkt, add_response),
521 "Failed to perform CMD_EVICT_KEY.");
522
523 if (expectError) {
524 checkeq(cb::mcbp::Status::KeyEexists, last_status.load(),
525 "evict_key: expected KEY_EEXISTS when evicting key");
526 } else {
527 if (last_body != "Already ejected.") {
528 nonResidentItems++;
529 numEjectedItems++;
530 }
531 checkeq(cb::mcbp::Status::Success, last_status.load(),
532 "evict_key: expected SUCCESS when evicting key.");
533 }
534
535 checkeq(nonResidentItems,
536 get_int_stat(h, "ep_num_non_resident"),
537 "Incorrect number of non-resident items");
538 checkeq(numEjectedItems,
539 get_int_stat(h, "ep_num_value_ejects"),
540 "Incorrect number of ejected items");
541
542 if (msg != NULL && last_body != msg) {
543 fprintf(stderr, "Expected evict to return ``%s'', but it returned ``%s''\n",
544 msg, last_body.c_str());
545 abort();
546 }
547 }
548
checkpointPersistence(EngineIface * h,uint64_t checkpoint_id,Vbid vb)549 ENGINE_ERROR_CODE checkpointPersistence(EngineIface* h,
550 uint64_t checkpoint_id,
551 Vbid vb) {
552 checkpoint_id = htonll(checkpoint_id);
553 auto request =
554 createPacket(cb::mcbp::ClientOpcode::CheckpointPersistence,
555 vb,
556 0,
557 {},
558 {},
559 {(const char*)&checkpoint_id, sizeof(uint64_t)});
560 ENGINE_ERROR_CODE rv = h->unknown_command(nullptr, *request, add_response);
561 return rv;
562 }
563
seqnoPersistence(EngineIface * h,const void * cookie,Vbid vbucket,uint64_t seqno)564 ENGINE_ERROR_CODE seqnoPersistence(EngineIface* h,
565 const void* cookie,
566 Vbid vbucket,
567 uint64_t seqno) {
568 seqno = htonll(seqno);
569 char buffer[8];
570 memcpy(buffer, &seqno, sizeof(uint64_t));
571 auto request = createPacket(
572 cb::mcbp::ClientOpcode::SeqnoPersistence, vbucket, 0, {buffer, 8});
573 return h->unknown_command(cookie, *request, add_response);
574 }
575
gat(EngineIface * h,const char * key,Vbid vb,uint32_t exp)576 cb::EngineErrorItemPair gat(EngineIface* h,
577 const char* key,
578 Vbid vb,
579 uint32_t exp) {
580 const auto* cookie = testHarness->create_cookie();
581 auto ret = h->get_and_touch(
582 cookie, DocKey(key, DocKeyEncodesCollectionId::No), vb, exp, {});
583 testHarness->destroy_cookie(cookie);
584
585 if (ret.first == cb::engine_errc::success) {
586 item_info info;
587 check(h->get_item_info(ret.second.get(), &info),
588 "gat Failed to get item info");
589
590 last_body.assign((const char*)info.value[0].iov_base,
591 info.value[0].iov_len);
592 }
593 return ret;
594 }
595
get_item_info(EngineIface * h,item_info * info,const char * key,Vbid vb)596 bool get_item_info(EngineIface* h, item_info* info, const char* key, Vbid vb) {
597 auto ret = get(h, NULL, key, vb);
598 if (ret.first != cb::engine_errc::success) {
599 return false;
600 }
601 if (!h->get_item_info(ret.second.get(), info)) {
602 fprintf(stderr, "get_item_info failed\n");
603 return false;
604 }
605
606 return true;
607 }
608
getl(EngineIface * h,const void * cookie,const char * key,Vbid vb,uint32_t lock_timeout)609 cb::EngineErrorItemPair getl(EngineIface* h,
610 const void* cookie,
611 const char* key,
612 Vbid vb,
613 uint32_t lock_timeout) {
614 bool create_cookie = false;
615 if (cookie == nullptr) {
616 cookie = testHarness->create_cookie();
617 create_cookie = true;
618 }
619 auto ret = h->get_locked(cookie,
620 DocKey(key, DocKeyEncodesCollectionId::No),
621 vb,
622 lock_timeout);
623 if (create_cookie) {
624 testHarness->destroy_cookie(cookie);
625 }
626
627 return ret;
628 }
629
get_meta(EngineIface * h,const char * key,const void * cookie)630 bool get_meta(EngineIface* h,
631 const char* key,
632 const void* cookie) {
633 cb::EngineErrorMetadataPair out;
634
635 return get_meta(h, key, out, cookie);
636 }
637
get_meta(EngineIface * h,const char * key,cb::EngineErrorMetadataPair & out,const void * cookie)638 bool get_meta(EngineIface* h,
639 const char* key,
640 cb::EngineErrorMetadataPair& out,
641 const void* cookie) {
642 DocKey docKey(key, DocKeyEncodesCollectionId::No);
643 bool cookie_create = false;
644 if (cookie == nullptr) {
645 cookie = testHarness->create_cookie();
646 cookie_create = true;
647 }
648
649 out = h->get_meta(cookie, docKey, Vbid(0));
650
651 if (cookie_create) {
652 testHarness->destroy_cookie(cookie);
653 }
654
655 return out.first == cb::engine_errc::success;
656 }
657
observe(EngineIface * h,std::map<std::string,Vbid> obskeys)658 ENGINE_ERROR_CODE observe(EngineIface* h, std::map<std::string, Vbid> obskeys) {
659 std::stringstream value;
660 std::map<std::string, Vbid>::iterator it;
661 for (it = obskeys.begin(); it != obskeys.end(); ++it) {
662 Vbid vb = it->second.hton();
663 uint16_t keylen = htons(it->first.length());
664 value.write((char*)&vb, sizeof(Vbid));
665 value.write((char*) &keylen, sizeof(uint16_t));
666 value.write(it->first.c_str(), it->first.length());
667 }
668
669 auto request = createPacket(
670 cb::mcbp::ClientOpcode::Observe, Vbid(0), 0, {}, {}, value.str());
671
672 return h->unknown_command(nullptr, *request, add_response);
673 }
674
observe_seqno(EngineIface * h,Vbid vb_id,uint64_t uuid)675 ENGINE_ERROR_CODE observe_seqno(EngineIface* h, Vbid vb_id, uint64_t uuid) {
676 uint64_t vb_uuid = htonll(uuid);
677 std::stringstream data;
678 data.write((char *) &vb_uuid, sizeof(uint64_t));
679
680 auto request = createPacket(
681 cb::mcbp::ClientOpcode::ObserveSeqno, vb_id, 0, {}, {}, data.str());
682 return h->unknown_command(nullptr, *request, add_response);
683 }
684
get_replica(EngineIface * h,const char * key,Vbid vbid)685 void get_replica(EngineIface* h, const char* key, Vbid vbid) {
686 auto request = createPacket(cb::mcbp::ClientOpcode::GetReplica,
687 vbid,
688 0,
689 {},
690 {key, strlen(key)});
691 checkeq(ENGINE_SUCCESS,
692 h->unknown_command(nullptr, *request, add_response),
693 "Get Replica Failed");
694 }
695
prepare_get_replica(EngineIface * h,vbucket_state_t state,bool makeinvalidkey)696 unique_request_ptr prepare_get_replica(EngineIface* h,
697 vbucket_state_t state,
698 bool makeinvalidkey) {
699 Vbid id(0);
700 const char *key = "k0";
701 auto request = createPacket(
702 cb::mcbp::ClientOpcode::GetReplica, id, 0, {}, {key, strlen(key)});
703
704 if (!makeinvalidkey) {
705 checkeq(ENGINE_SUCCESS,
706 store(h,
707 nullptr,
708 OPERATION_SET,
709 key,
710 "replicadata",
711 nullptr,
712 0,
713 id),
714 "Get Replica Failed");
715
716 check(set_vbucket_state(h, id, state),
717 "Failed to set vbucket active state, Get Replica Failed");
718 }
719
720 return request;
721 }
722
set_param(EngineIface * h,cb::mcbp::request::SetParamPayload::Type paramtype,const char * param,const char * val,Vbid vb)723 bool set_param(EngineIface* h,
724 cb::mcbp::request::SetParamPayload::Type paramtype,
725 const char* param,
726 const char* val,
727 Vbid vb) {
728 cb::mcbp::request::SetParamPayload payload;
729 payload.setParamType(paramtype);
730 auto buffer = payload.getBuffer();
731 auto request = createPacket(
732 cb::mcbp::ClientOpcode::SetParam,
733 vb,
734 0,
735 {reinterpret_cast<const char*>(buffer.data()), buffer.size()},
736 {param, strlen(param)},
737 {val, strlen(val)});
738
739 if (h->unknown_command(nullptr, *request, add_response) != ENGINE_SUCCESS) {
740 return false;
741 }
742
743 return last_status == cb::mcbp::Status::Success;
744 }
745
set_vbucket_state(EngineIface * h,Vbid vb,vbucket_state_t state,cb::const_char_buffer meta)746 bool set_vbucket_state(EngineIface* h,
747 Vbid vb,
748 vbucket_state_t state,
749 cb::const_char_buffer meta) {
750 uint8_t datatype = 0x0;
751 std::unique_ptr<char[]> extras;
752 uint8_t extSize = 0;
753 if (!meta.empty()) {
754 // mad-hatter encoding
755 datatype = 0x1;
756 extSize = 1;
757 extras = std::make_unique<char[]>(extSize);
758 *extras.get() = static_cast<char>(state);
759 } else {
760 // pre mad-hatter encoding
761 extSize = 4;
762 extras = std::make_unique<char[]>(extSize);
763 encodeExt(extras.get(), static_cast<int32_t>(state));
764 }
765
766 auto request = createPacket(cb::mcbp::ClientOpcode::SetVbucket,
767 vb,
768 0 /*cas*/,
769 {extras.get(), extSize},
770 {} /*key*/,
771 meta /*value*/,
772 datatype);
773
774 if (h->unknown_command(nullptr, *request, add_response) != ENGINE_SUCCESS) {
775 return false;
776 }
777
778 return last_status == cb::mcbp::Status::Success;
779 }
780
get_all_vb_seqnos(EngineIface * h,boost::optional<RequestedVBState> state,const void * cookie,boost::optional<CollectionIDType> collection)781 bool get_all_vb_seqnos(EngineIface* h,
782 boost::optional<RequestedVBState> state,
783 const void* cookie,
784 boost::optional<CollectionIDType> collection) {
785 unique_request_ptr pkt;
786
787 if (collection) {
788 if (!state) {
789 // Do the same check so we can print for the user...
790 checkeq(state.is_initialized(),
791 true,
792 "State must be set when "
793 "collection is specified");
794 return false;
795 }
796
797 char ext[sizeof(vbucket_state_t) + sizeof(CollectionIDType)];
798 encodeExt(ext, static_cast<uint32_t>(*state));
799 encodeExt(ext, *collection, sizeof(vbucket_state_t));
800 pkt = createPacket(cb::mcbp::ClientOpcode::GetAllVbSeqnos,
801 Vbid(0),
802 0,
803 {ext,sizeof(vbucket_state_t) + sizeof(CollectionIDType)});
804 } else if (state) {
805 char ext[sizeof(vbucket_state_t)];
806 encodeExt(ext, static_cast<uint32_t>(*state));
807 pkt = createPacket(cb::mcbp::ClientOpcode::GetAllVbSeqnos,
808 Vbid(0),
809 0,
810 {ext, sizeof(vbucket_state_t)});
811 } else {
812 pkt = createPacket(cb::mcbp::ClientOpcode::GetAllVbSeqnos);
813 }
814
815 checkeq(ENGINE_SUCCESS,
816 h->unknown_command(cookie, *pkt, add_response),
817 "Error in getting all vb info");
818
819 return last_status == cb::mcbp::Status::Success;
820 }
821
verify_all_vb_seqnos(EngineIface * h,int vb_start,int vb_end,boost::optional<CollectionID> cid)822 void verify_all_vb_seqnos(EngineIface* h,
823 int vb_start,
824 int vb_end,
825 boost::optional<CollectionID> cid) {
826 const int per_vb_resp_size = sizeof(uint16_t) + sizeof(uint64_t);
827 const int high_seqno_offset = sizeof(uint16_t);
828
829 /* Check if the total response length is as expected. We expect 10 bytes
830 (2 for vb_id + 8 for seqno) */
831 checkeq((vb_end - vb_start + 1) * per_vb_resp_size,
832 static_cast<int>(last_body.size()),
833 "Failed to get all vb info.");
834 /* Check if the contents are correct */
835 for (int i = 0; i < (vb_end - vb_start + 1); i++) {
836 /* Check for correct vb_id */
837 checkeq(static_cast<const uint16_t>(vb_start + i),
838 ntohs(*(reinterpret_cast<const uint16_t*>(last_body.data() +
839 per_vb_resp_size*i))),
840 "vb_id mismatch");
841
842 uint64_t high_seqno_vb;
843 if (cid) {
844 // Get high seqno for the collection in the vBucket
845 std::string vb_stat_seqno("vb_" + std::to_string(vb_start + i) +
846 ":collection:" + cid->to_string() +
847 ":entry:high_seqno");
848 high_seqno_vb = get_ull_stat(
849 h, vb_stat_seqno.c_str(), "collections-details");
850 } else {
851 // Get high seqno for the vBucket
852 std::string vb_stat_seqno("vb_" + std::to_string(vb_start + i) +
853 ":high_seqno");
854 high_seqno_vb =
855 get_ull_stat(h, vb_stat_seqno.c_str(), "vbucket-seqno");
856 }
857
858 checkeq(high_seqno_vb,
859 ntohll(*(reinterpret_cast<const uint64_t*>(last_body.data() +
860 per_vb_resp_size*i +
861 high_seqno_offset))),
862 "high_seqno mismatch");
863 }
864 }
865
store_with_meta(EngineIface * h,cb::mcbp::ClientOpcode cmd,const char * key,const size_t keylen,const char * val,const size_t vallen,const Vbid vb,ItemMetaData * itemMeta,uint64_t cas_for_store,uint32_t options,uint8_t datatype,const void * cookie,const std::vector<char> & nmeta)866 static ENGINE_ERROR_CODE store_with_meta(EngineIface* h,
867 cb::mcbp::ClientOpcode cmd,
868 const char* key,
869 const size_t keylen,
870 const char* val,
871 const size_t vallen,
872 const Vbid vb,
873 ItemMetaData* itemMeta,
874 uint64_t cas_for_store,
875 uint32_t options,
876 uint8_t datatype,
877 const void* cookie,
878 const std::vector<char>& nmeta) {
879 size_t blen = 24;
880 std::unique_ptr<char[]> ext(new char[30]);
881 std::unique_ptr<ExtendedMetaData> emd;
882
883 encodeWithMetaExt(ext.get(), itemMeta);
884
885 if (options) {
886 uint32_t optionsSwapped = htonl(options);
887 memcpy(ext.get() + blen, (char*)&optionsSwapped, sizeof(optionsSwapped));
888 blen += sizeof(uint32_t);
889 }
890
891 if (nmeta.size() > 0) {
892 uint16_t nmetaSize = htons(nmeta.size());
893 memcpy(ext.get() + blen, (char*)&nmetaSize, sizeof(nmetaSize));
894 blen += sizeof(uint16_t);
895 }
896
897 auto request = createPacket(cmd,
898 vb,
899 cas_for_store,
900 {ext.get(), blen},
901 {key, keylen},
902 {val, vallen},
903 datatype,
904 {nmeta.data(), nmeta.size()});
905
906 return h->unknown_command(cookie, *request, add_response_set_del_meta);
907 }
908
set_with_meta(EngineIface * h,const char * key,const size_t keylen,const char * val,const size_t vallen,const Vbid vb,ItemMetaData * itemMeta,uint64_t cas_for_set,uint32_t options,uint8_t datatype,const void * cookie,const std::vector<char> & nmeta)909 ENGINE_ERROR_CODE set_with_meta(EngineIface* h,
910 const char* key,
911 const size_t keylen,
912 const char* val,
913 const size_t vallen,
914 const Vbid vb,
915 ItemMetaData* itemMeta,
916 uint64_t cas_for_set,
917 uint32_t options,
918 uint8_t datatype,
919 const void* cookie,
920 const std::vector<char>& nmeta) {
921 return store_with_meta(h,
922 cb::mcbp::ClientOpcode::SetWithMeta,
923 key,
924 keylen,
925 val,
926 vallen,
927 vb,
928 itemMeta,
929 cas_for_set,
930 options,
931 datatype,
932 cookie,
933 nmeta);
934 }
935
add_with_meta(EngineIface * h,const char * key,const size_t keylen,const char * val,const size_t vallen,const Vbid vb,ItemMetaData * itemMeta,uint64_t cas_for_add,uint32_t options,uint8_t datatype,const void * cookie,const std::vector<char> & nmeta)936 ENGINE_ERROR_CODE add_with_meta(EngineIface* h,
937 const char* key,
938 const size_t keylen,
939 const char* val,
940 const size_t vallen,
941 const Vbid vb,
942 ItemMetaData* itemMeta,
943 uint64_t cas_for_add,
944 uint32_t options,
945 uint8_t datatype,
946 const void* cookie,
947 const std::vector<char>& nmeta) {
948 return store_with_meta(h,
949 cb::mcbp::ClientOpcode::AddWithMeta,
950 key,
951 keylen,
952 val,
953 vallen,
954 vb,
955 itemMeta,
956 cas_for_add,
957 options,
958 datatype,
959 cookie,
960 nmeta);
961 }
962
return_meta(EngineIface * h,const char * key,const size_t keylen,const char * val,const size_t vallen,const Vbid vb,const uint64_t cas,const uint32_t flags,const uint32_t exp,cb::mcbp::request::ReturnMetaType type,uint8_t datatype,const void * cookie)963 static ENGINE_ERROR_CODE return_meta(EngineIface* h,
964 const char* key,
965 const size_t keylen,
966 const char* val,
967 const size_t vallen,
968 const Vbid vb,
969 const uint64_t cas,
970 const uint32_t flags,
971 const uint32_t exp,
972 cb::mcbp::request::ReturnMetaType type,
973 uint8_t datatype,
974 const void* cookie) {
975 cb::mcbp::request::ReturnMetaPayload meta;
976 meta.setMutationType(type);
977 meta.setFlags(flags);
978 meta.setExpiration(exp);
979
980 auto pkt =
981 createPacket(cb::mcbp::ClientOpcode::ReturnMeta,
982 vb,
983 cas,
984 {reinterpret_cast<const char*>(&meta), sizeof(meta)},
985 {key, keylen},
986 {val, vallen},
987 datatype);
988 return h->unknown_command(cookie, *pkt, add_response_ret_meta);
989 }
990
set_ret_meta(EngineIface * h,const char * key,const size_t keylen,const char * val,const size_t vallen,const Vbid vb,const uint64_t cas,const uint32_t flags,const uint32_t exp,uint8_t datatype,const void * cookie)991 ENGINE_ERROR_CODE set_ret_meta(EngineIface* h,
992 const char* key,
993 const size_t keylen,
994 const char* val,
995 const size_t vallen,
996 const Vbid vb,
997 const uint64_t cas,
998 const uint32_t flags,
999 const uint32_t exp,
1000 uint8_t datatype,
1001 const void* cookie) {
1002 return return_meta(h,
1003 key,
1004 keylen,
1005 val,
1006 vallen,
1007 vb,
1008 cas,
1009 flags,
1010 exp,
1011 cb::mcbp::request::ReturnMetaType::Set,
1012 datatype,
1013 cookie);
1014 }
1015
add_ret_meta(EngineIface * h,const char * key,const size_t keylen,const char * val,const size_t vallen,const Vbid vb,const uint64_t cas,const uint32_t flags,const uint32_t exp,uint8_t datatype,const void * cookie)1016 ENGINE_ERROR_CODE add_ret_meta(EngineIface* h,
1017 const char* key,
1018 const size_t keylen,
1019 const char* val,
1020 const size_t vallen,
1021 const Vbid vb,
1022 const uint64_t cas,
1023 const uint32_t flags,
1024 const uint32_t exp,
1025 uint8_t datatype,
1026 const void* cookie) {
1027 return return_meta(h,
1028 key,
1029 keylen,
1030 val,
1031 vallen,
1032 vb,
1033 cas,
1034 flags,
1035 exp,
1036 cb::mcbp::request::ReturnMetaType::Add,
1037 datatype,
1038 cookie);
1039 }
1040
del_ret_meta(EngineIface * h,const char * key,const size_t keylen,const Vbid vb,const uint64_t cas,const void * cookie)1041 ENGINE_ERROR_CODE del_ret_meta(EngineIface* h,
1042 const char* key,
1043 const size_t keylen,
1044 const Vbid vb,
1045 const uint64_t cas,
1046 const void* cookie) {
1047 return return_meta(h,
1048 key,
1049 keylen,
1050 NULL,
1051 0,
1052 vb,
1053 cas,
1054 0,
1055 0,
1056 cb::mcbp::request::ReturnMetaType::Del,
1057 0x00,
1058 cookie);
1059 }
1060
disable_traffic(EngineIface * h)1061 void disable_traffic(EngineIface* h) {
1062 auto pkt = createPacket(cb::mcbp::ClientOpcode::DisableTraffic);
1063 checkeq(ENGINE_SUCCESS,
1064 h->unknown_command(nullptr, *pkt, add_response),
1065 "Failed to send data traffic command to the server");
1066 checkeq(cb::mcbp::Status::Success,
1067 last_status.load(),
1068 "Failed to disable data traffic");
1069 }
1070
enable_traffic(EngineIface * h)1071 void enable_traffic(EngineIface* h) {
1072 auto pkt = createPacket(cb::mcbp::ClientOpcode::EnableTraffic);
1073 checkeq(ENGINE_SUCCESS,
1074 h->unknown_command(nullptr, *pkt, add_response),
1075 "Failed to send data traffic command to the server");
1076 checkeq(cb::mcbp::Status::Success,
1077 last_status.load(),
1078 "Failed to enable data traffic");
1079 }
1080
start_persistence(EngineIface * h)1081 void start_persistence(EngineIface* h) {
1082 if (!isPersistentBucket(h)) {
1083 // Nothing to do for non-persistent buckets
1084 return;
1085 }
1086
1087 auto pkt = createPacket(cb::mcbp::ClientOpcode::StartPersistence);
1088 checkeq(ENGINE_SUCCESS,
1089 h->unknown_command(nullptr, *pkt, add_response),
1090 "Failed to stop persistence.");
1091 checkeq(cb::mcbp::Status::Success,
1092 last_status.load(),
1093 "Error starting persistence.");
1094 }
1095
stop_persistence(EngineIface * h)1096 void stop_persistence(EngineIface* h) {
1097 if (!isPersistentBucket(h)) {
1098 // Nothing to do for non-persistent buckets
1099 return;
1100 }
1101
1102 useconds_t sleepTime = 128;
1103 while (true) {
1104 if (get_str_stat(h, "ep_flusher_state", 0) == "running") {
1105 break;
1106 }
1107 decayingSleep(&sleepTime);
1108 }
1109
1110 auto pkt = createPacket(cb::mcbp::ClientOpcode::StopPersistence);
1111 checkeq(ENGINE_SUCCESS,
1112 h->unknown_command(nullptr, *pkt, add_response),
1113 "Failed to stop persistence.");
1114 checkeq(cb::mcbp::Status::Success,
1115 last_status.load(),
1116 "Error stopping persistence.");
1117 }
1118
store(EngineIface * h,const void * cookie,ENGINE_STORE_OPERATION op,const char * key,const char * value,item ** outitem,uint64_t casIn,Vbid vb,uint32_t exp,uint8_t datatype,DocumentState docState,const boost::optional<cb::durability::Requirements> & durReqs)1119 ENGINE_ERROR_CODE store(
1120 EngineIface* h,
1121 const void* cookie,
1122 ENGINE_STORE_OPERATION op,
1123 const char* key,
1124 const char* value,
1125 item** outitem,
1126 uint64_t casIn,
1127 Vbid vb,
1128 uint32_t exp,
1129 uint8_t datatype,
1130 DocumentState docState,
1131 const boost::optional<cb::durability::Requirements>& durReqs) {
1132 auto ret = storeCasVb11(h,
1133 cookie,
1134 op,
1135 key,
1136 value,
1137 strlen(value),
1138 9258,
1139 casIn,
1140 vb,
1141 exp,
1142 datatype,
1143 docState,
1144 durReqs);
1145 if (outitem) {
1146 *outitem = ret.second.release();
1147 }
1148 return ENGINE_ERROR_CODE(ret.first);
1149 }
1150
storeCasOut(EngineIface * h,const void * cookie,Vbid vb,const std::string & key,const std::string & value,protocol_binary_datatype_t datatype,item * & out_item,uint64_t & out_cas,DocumentState docState)1151 ENGINE_ERROR_CODE storeCasOut(EngineIface* h,
1152 const void* cookie,
1153 Vbid vb,
1154 const std::string& key,
1155 const std::string& value,
1156 protocol_binary_datatype_t datatype,
1157 item*& out_item,
1158 uint64_t& out_cas,
1159 DocumentState docState) {
1160 bool create_cookie = false;
1161 if (cookie == nullptr) {
1162 cookie = testHarness->create_cookie();
1163 create_cookie = true;
1164 }
1165
1166 auto ret = allocate(h, cookie, key, value.size(), 0, 0, datatype, vb);
1167 checkeq(cb::engine_errc::success, ret.first, "Allocation failed.");
1168 item_info info;
1169 check(h->get_item_info(ret.second.get(), &info), "Unable to get item_info");
1170 memcpy(info.value[0].iov_base, value.data(), value.size());
1171 ENGINE_ERROR_CODE res = h->store(
1172 cookie, ret.second.get(), out_cas, OPERATION_SET, {}, docState);
1173
1174 if (create_cookie) {
1175 testHarness->destroy_cookie(cookie);
1176 }
1177
1178 return res;
1179 }
1180
storeCasVb11(EngineIface * h,const void * cookie,ENGINE_STORE_OPERATION op,const char * key,const char * value,size_t vlen,uint32_t flags,uint64_t casIn,Vbid vb,uint32_t exp,uint8_t datatype,DocumentState docState,const boost::optional<cb::durability::Requirements> & durReqs)1181 cb::EngineErrorItemPair storeCasVb11(
1182 EngineIface* h,
1183 const void* cookie,
1184 ENGINE_STORE_OPERATION op,
1185 const char* key,
1186 const char* value,
1187 size_t vlen,
1188 uint32_t flags,
1189 uint64_t casIn,
1190 Vbid vb,
1191 uint32_t exp,
1192 uint8_t datatype,
1193 DocumentState docState,
1194 const boost::optional<cb::durability::Requirements>& durReqs) {
1195 uint64_t cas = 0;
1196
1197 auto rv = allocate(h, cookie, key, vlen, flags, exp, datatype, vb);
1198 if (rv.first != cb::engine_errc::success) {
1199 return rv;
1200 }
1201 item_info info;
1202 if (!h->get_item_info(rv.second.get(), &info)) {
1203 abort();
1204 }
1205
1206 cb_assert(info.value[0].iov_len == vlen);
1207 std::copy(value, value + vlen, reinterpret_cast<char*>(info.value[0].iov_base));
1208 h->item_set_cas(rv.second.get(), casIn);
1209
1210 bool create_cookie = false;
1211 if (cookie == nullptr) {
1212 cookie = testHarness->create_cookie();
1213 create_cookie = true;
1214 }
1215
1216 auto storeRet =
1217 h->store(cookie, rv.second.get(), cas, op, durReqs, docState);
1218
1219 if (create_cookie) {
1220 testHarness->destroy_cookie(cookie);
1221 }
1222
1223 return {cb::engine_errc(storeRet), std::move(rv.second)};
1224 }
1225
replace(EngineIface * h,const void * cookie,const char * key,const char * value,uint32_t flags,Vbid vb)1226 ENGINE_ERROR_CODE replace(EngineIface* h,
1227 const void* cookie,
1228 const char* key,
1229 const char* value,
1230 uint32_t flags,
1231 Vbid vb) {
1232 Expects(cookie);
1233
1234 const auto allocRes = allocate(h,
1235 cookie,
1236 key,
1237 strlen(value),
1238 flags,
1239 0 /*expiry*/,
1240 0 /*datatype*/,
1241 vb);
1242 if (allocRes.first != cb::engine_errc::success) {
1243 return ENGINE_ERROR_CODE(allocRes.first);
1244 }
1245
1246 const auto& item = allocRes.second;
1247 item_info info;
1248 if (!h->get_item_info(item.get(), &info)) {
1249 abort();
1250 }
1251 h->item_set_cas(allocRes.second.get(), 0);
1252
1253 // A predicate that allows the replace.
1254 // This simulates the behaviour of replace when the doc being updated does
1255 // not contain any xattr, and the vbucket that owns the doc has surely never
1256 // seen a doc with xattr. Which means that we do not need any pre-fetch for
1257 // preserving xattrs, the replace can just proceed.
1258 const cb::StoreIfPredicate predicate = [](const boost::optional<item_info>&,
1259 cb::vbucket_info) {
1260 return cb::StoreIfStatus::Continue;
1261 };
1262
1263 auto res = h->store_if(cookie,
1264 item.get(),
1265 0 /*cas*/,
1266 ENGINE_STORE_OPERATION::OPERATION_REPLACE,
1267 predicate,
1268 {} /*durReqs*/,
1269 DocumentState::Alive);
1270
1271 return ENGINE_ERROR_CODE(res.status);
1272 }
1273
touch(EngineIface * h,const char * key,Vbid vb,uint32_t exp)1274 ENGINE_ERROR_CODE touch(EngineIface* h,
1275 const char* key,
1276 Vbid vb,
1277 uint32_t exp) {
1278 const auto* cookie = testHarness->create_cookie();
1279 auto result = h->get_and_touch(
1280 cookie, DocKey(key, DocKeyEncodesCollectionId::No), vb, exp, {});
1281 testHarness->destroy_cookie(cookie);
1282
1283 // Update the global cas value (used by some tests)
1284 if (result.first == cb::engine_errc::success) {
1285 item_info info{};
1286 check(h->get_item_info(result.second.get(), &info),
1287 "Failed to get item info");
1288 last_cas.store(info.cas);
1289 }
1290
1291 return ENGINE_ERROR_CODE(result.first);
1292 }
1293
unl(EngineIface * h,const void * cookie,const char * key,Vbid vb,uint64_t cas)1294 ENGINE_ERROR_CODE unl(EngineIface* h,
1295 const void* cookie,
1296 const char* key,
1297 Vbid vb,
1298 uint64_t cas) {
1299 bool create_cookie = false;
1300 if (cookie == nullptr) {
1301 cookie = testHarness->create_cookie();
1302 create_cookie = true;
1303 }
1304 auto ret = h->unlock(
1305 cookie, DocKey(key, DocKeyEncodesCollectionId::No), vb, cas);
1306
1307 if (create_cookie) {
1308 testHarness->destroy_cookie(cookie);
1309 }
1310 return ret;
1311 }
1312
compact_db(EngineIface * h,const Vbid vbucket_id,const Vbid db_file_id,const uint64_t purge_before_ts,const uint64_t purge_before_seq,const uint8_t drop_deletes)1313 void compact_db(EngineIface* h,
1314 const Vbid vbucket_id,
1315 const Vbid db_file_id,
1316 const uint64_t purge_before_ts,
1317 const uint64_t purge_before_seq,
1318 const uint8_t drop_deletes) {
1319 cb::mcbp::request::CompactDbPayload payload;
1320 payload.setPurgeBeforeTs(purge_before_ts);
1321 payload.setPurgeBeforeSeq(purge_before_seq);
1322 payload.setDropDeletes(drop_deletes);
1323 payload.setDbFileId(db_file_id);
1324
1325 auto pkt = createPacket(
1326 cb::mcbp::ClientOpcode::CompactDb,
1327 vbucket_id,
1328 0,
1329 {reinterpret_cast<const char*>(&payload), sizeof(payload)});
1330 auto ret = h->unknown_command(nullptr, *pkt, add_response);
1331
1332 const auto backend = get_str_stat(h, "ep_backend");
1333
1334 if (backend == "couchdb" || backend == "magma") {
1335 if (ret == ENGINE_ENOTSUP) {
1336 // Ephemeral, couchdb and magma (but not rocksdb) buckets can
1337 // return ENGINE_ENOTSUP. This method is called from a lot
1338 // of test cases we run. Lets remap the error code to success.
1339 // Note: Ephemeral buckets use couchdb as backend.
1340 ret = ENGINE_SUCCESS;
1341 }
1342 checkeq(ENGINE_SUCCESS, ret, "Failed to request compact vbucket");
1343 } else {
1344 checkeq(ENGINE_FAILED,
1345 ret,
1346 "checkForDBExistence returns ENGINE_FAILED for !couchdb");
1347 }
1348 }
1349
vbucketDelete(EngineIface * h,Vbid vb,const char * args)1350 ENGINE_ERROR_CODE vbucketDelete(EngineIface* h, Vbid vb, const char* args) {
1351 uint32_t argslen = args ? strlen(args) : 0;
1352 auto pkt = createPacket(
1353 cb::mcbp::ClientOpcode::DelVbucket, vb, 0, {}, {}, {args, argslen});
1354
1355 return h->unknown_command(nullptr, *pkt, add_response);
1356 }
1357
verify_key(EngineIface * h,const char * key,Vbid vbucket)1358 ENGINE_ERROR_CODE verify_key(EngineIface* h, const char* key, Vbid vbucket) {
1359 auto rv = get(h, NULL, key, vbucket);
1360 return ENGINE_ERROR_CODE(rv.first);
1361 }
1362
get_value(EngineIface * h,const void * cookie,const char * key,Vbid vbucket,DocStateFilter state)1363 std::pair<ENGINE_ERROR_CODE, std::string> get_value(EngineIface* h,
1364 const void* cookie,
1365 const char* key,
1366 Vbid vbucket,
1367 DocStateFilter state) {
1368 auto rv = get(h, cookie, key, vbucket, state);
1369 if (rv.first != cb::engine_errc::success) {
1370 return {ENGINE_ERROR_CODE(rv.first), ""};
1371 }
1372 item_info info;
1373 if (!h->get_item_info(rv.second.get(), &info)) {
1374 return {ENGINE_FAILED, ""};
1375 }
1376 auto value = std::string(reinterpret_cast<char*>(info.value[0].iov_base),
1377 info.value[0].iov_len);
1378 return make_pair(ENGINE_ERROR_CODE(rv.first), value);
1379 }
1380
verify_vbucket_missing(EngineIface * h,Vbid vb)1381 bool verify_vbucket_missing(EngineIface* h, Vbid vb) {
1382 const auto vbstr = "vb_" + std::to_string(vb.get());
1383
1384 // Try up to three times to verify the bucket is missing. Bucket
1385 // state changes are async.
1386 {
1387 std::lock_guard<std::mutex> lh(vals_mutex);
1388 vals.clear();
1389 }
1390
1391 const auto* cookie = testHarness->create_cookie();
1392 checkeq(ENGINE_SUCCESS,
1393 h->get_stats(cookie, {}, {}, add_stats),
1394 "Failed to get stats.");
1395 testHarness->destroy_cookie(cookie);
1396
1397 {
1398 std::lock_guard<std::mutex> lh(vals_mutex);
1399 if (vals.find(vbstr) == vals.end()) {
1400 return true;
1401 }
1402
1403 std::cerr << "Expected bucket missing, got " <<
1404 vals[vbstr] << std::endl;
1405 }
1406 return false;
1407 }
1408
verify_vbucket_state(EngineIface * h,Vbid vb,vbucket_state_t expected,bool mute)1409 bool verify_vbucket_state(EngineIface* h,
1410 Vbid vb,
1411 vbucket_state_t expected,
1412 bool mute) {
1413 auto pkt = createPacket(cb::mcbp::ClientOpcode::GetVbucket, vb, 0);
1414
1415 ENGINE_ERROR_CODE errcode = h->unknown_command(NULL, *pkt, add_response);
1416 if (errcode != ENGINE_SUCCESS) {
1417 if (!mute) {
1418 fprintf(stderr, "Error code when getting vbucket %d\n", errcode);
1419 }
1420 return false;
1421 }
1422
1423 if (last_status != cb::mcbp::Status::Success) {
1424 if (!mute) {
1425 fprintf(stderr, "Last protocol status was %s (%s)\n",
1426 to_string(last_status.load()).c_str(),
1427 last_body.size() > 0 ? last_body.c_str() : "unknown");
1428 }
1429 return false;
1430 }
1431
1432 vbucket_state_t state;
1433 memcpy(&state, last_body.data(), sizeof(state));
1434 state = static_cast<vbucket_state_t>(ntohl(state));
1435 return state == expected;
1436 }
1437
sendDcpAck(EngineIface * h,const void * cookie,cb::mcbp::ClientOpcode opcode,cb::mcbp::Status status,uint32_t opaque)1438 void sendDcpAck(EngineIface* h,
1439 const void* cookie,
1440 cb::mcbp::ClientOpcode opcode,
1441 cb::mcbp::Status status,
1442 uint32_t opaque) {
1443 protocol_binary_response_header pkt;
1444 pkt.response.setMagic(cb::mcbp::Magic::ClientResponse);
1445 pkt.response.setOpcode(opcode);
1446 pkt.response.setStatus(status);
1447 pkt.response.setOpaque(opaque);
1448
1449 auto& dcp = dynamic_cast<DcpIface&>(*h);
1450 checkeq(ENGINE_SUCCESS, dcp.response_handler(cookie, &pkt),
1451 "Expected success");
1452 }
1453
1454 class engine_error : public std::exception {
1455 public:
engine_error(ENGINE_ERROR_CODE code_)1456 engine_error(ENGINE_ERROR_CODE code_)
1457 : code(code_) {}
1458
what() const1459 virtual const char* what() const NOEXCEPT {
1460 return "engine_error";
1461 }
1462
1463 ENGINE_ERROR_CODE code;
1464 };
1465
1466 /* The following set of functions get a given stat as the specified type
1467 * (int, float, unsigned long, string, bool, etc).
1468 * If the engine->get_stats() call fails, throws a engine_error exception.
1469 * If the given statname doesn't exist under the given statname, throws a
1470 * std::out_of_range exception.
1471 */
1472 template <>
get_stat(EngineIface * h,const char * statname,const char * statkey)1473 int get_stat(EngineIface* h,
1474 const char* statname,
1475 const char* statkey) {
1476 return std::stoi(get_str_stat(h, statname, statkey));
1477 }
1478 template <>
get_stat(EngineIface * h,const char * statname,const char * statkey)1479 uint64_t get_stat(EngineIface* h,
1480 const char* statname,
1481 const char* statkey) {
1482 return std::stoull(get_str_stat(h, statname, statkey));
1483 }
1484
1485 template <>
get_stat(EngineIface * h,const char * statname,const char * statkey)1486 bool get_stat(EngineIface* h,
1487 const char* statname,
1488 const char* statkey) {
1489 return get_str_stat(h, statname, statkey) == "true";
1490 }
1491
1492 template <>
get_stat(EngineIface * h,const char * statname,const char * statkey)1493 float get_stat(EngineIface* h, const char* statname, const char* statkey) {
1494 return std::stof(get_str_stat(h, statname, statkey));
1495 }
1496
1497 template <>
get_stat(EngineIface * h,const char * statname,const char * statkey)1498 std::string get_stat(EngineIface* h,
1499 const char* statname,
1500 const char* statkey) {
1501 std::lock_guard<std::mutex> lh(get_stat_context.mutex);
1502
1503 get_stat_context.requested_stat_name = statname;
1504 get_stat_context.actual_stat_value.clear();
1505
1506 const auto* cookie = testHarness->create_cookie();
1507 ENGINE_ERROR_CODE err =
1508 h->get_stats(cookie,
1509 {statkey, statkey == NULL ? 0 : strlen(statkey)},
1510 {},
1511 add_individual_stat);
1512 testHarness->destroy_cookie(cookie);
1513
1514 if (err != ENGINE_SUCCESS) {
1515 throw engine_error(err);
1516 }
1517
1518 if (get_stat_context.actual_stat_value.empty()) {
1519 throw std::out_of_range(std::string("Failed to find requested statname '") +
1520 statname + "'");
1521 }
1522
1523 // Here we are explictly forcing a copy of the object to work
1524 // around std::string copy-on-write data-race issues seen on some
1525 // versions of libstdc++ - see MB-18510 / MB-19688.
1526 return std::string(get_stat_context.actual_stat_value.begin(),
1527 get_stat_context.actual_stat_value.end());
1528 }
1529
1530 /// Backward-compatible functions (encode type name in function name).
get_int_stat(EngineIface * h,const char * statname,const char * statkey)1531 int get_int_stat(EngineIface* h,
1532 const char* statname,
1533 const char* statkey) {
1534 return get_stat<int>(h, statname, statkey);
1535 }
1536
get_float_stat(EngineIface * h,const char * statname,const char * statkey)1537 float get_float_stat(EngineIface* h,
1538 const char* statname,
1539 const char* statkey) {
1540 return std::stof(get_str_stat(h, statname, statkey));
1541 }
1542
get_ul_stat(EngineIface * h,const char * statname,const char * statkey)1543 uint32_t get_ul_stat(EngineIface* h,
1544 const char* statname,
1545 const char* statkey) {
1546 return std::stoul(get_str_stat(h, statname, statkey));
1547 }
1548
get_ull_stat(EngineIface * h,const char * statname,const char * statkey)1549 uint64_t get_ull_stat(EngineIface* h,
1550 const char* statname,
1551 const char* statkey) {
1552 return get_stat<uint64_t>(h, statname, statkey);
1553 }
1554
get_str_stat(EngineIface * h,const char * statname,const char * statkey)1555 std::string get_str_stat(EngineIface* h,
1556 const char* statname,
1557 const char* statkey) {
1558 return get_stat<std::string>(h, statname, statkey);
1559 }
1560
get_bool_stat(EngineIface * h,const char * statname,const char * statkey)1561 bool get_bool_stat(EngineIface* h,
1562 const char* statname,
1563 const char* statkey) {
1564 const auto s = get_str_stat(h, statname, statkey);
1565
1566 if (s == "true") {
1567 return true;
1568 } else if (s == "false") {
1569 return false;
1570 } else {
1571 throw std::invalid_argument("Unable to convert string '" + s + "' to type bool");
1572 }
1573 }
1574
1575 /* Fetches the value for a given statname in the given statkey set.
1576 * @return te value of statname, or default_value if that statname was not
1577 * found.
1578 */
get_int_stat_or_default(EngineIface * h,int default_value,const char * statname,const char * statkey)1579 int get_int_stat_or_default(EngineIface* h,
1580 int default_value,
1581 const char* statname,
1582 const char* statkey) {
1583 try {
1584 return get_int_stat(h, statname, statkey);
1585 } catch (std::out_of_range&) {
1586 return default_value;
1587 }
1588 }
1589
get_histo_stat(EngineIface * h,const char * statname,const char * statkey,const Histo_stat_info histo_info)1590 uint64_t get_histo_stat(EngineIface* h,
1591 const char* statname,
1592 const char* statkey,
1593 const Histo_stat_info histo_info) {
1594 std::lock_guard<std::mutex> lh(get_stat_context.mutex);
1595
1596 get_stat_context.histogram_stat_int_value = new HistogramStats<uint64_t>();
1597 get_histo_stat(h, statname, statkey);
1598
1599 /* Get the necessary info from the histogram */
1600 uint64_t ret_val = 0;
1601 switch (histo_info) {
1602 case Histo_stat_info::TOTAL_COUNT:
1603 ret_val = get_stat_context.histogram_stat_int_value->total();
1604 break;
1605 case Histo_stat_info::NUM_BINS:
1606 ret_val =
1607 static_cast<uint64_t>(get_stat_context.
1608 histogram_stat_int_value->num_bins());
1609 break;
1610 }
1611
1612 delete get_stat_context.histogram_stat_int_value;
1613 return ret_val;
1614 }
1615
get_histo_stat(EngineIface * h,const char * statname,const char * statkey)1616 static void get_histo_stat(EngineIface* h,
1617 const char* statname,
1618 const char* statkey) {
1619 get_stat_context.requested_stat_name = statname;
1620 /* Histo stats for tasks are append as task_name_START,END.
1621 Hence append _ */
1622 get_stat_context.requested_stat_name.append("_");
1623
1624 const auto* cookie = testHarness->create_cookie();
1625 auto err = h->get_stats(cookie,
1626 {statkey, statkey == NULL ? 0 : strlen(statkey)},
1627 {},
1628 add_individual_histo_stat);
1629 testHarness->destroy_cookie(cookie);
1630
1631 if (err != ENGINE_SUCCESS) {
1632 throw engine_error(err);
1633 }
1634 }
1635
get_all_stats(EngineIface * h,const char * statset)1636 statistic_map get_all_stats(EngineIface* h, const char* statset) {
1637 {
1638 std::lock_guard<std::mutex> lh(vals_mutex);
1639 vals.clear();
1640 }
1641 const auto* cookie = testHarness->create_cookie();
1642 auto err = h->get_stats(cookie,
1643 {statset, statset == NULL ? 0 : strlen(statset)},
1644 {},
1645 add_stats);
1646 testHarness->destroy_cookie(cookie);
1647
1648 if (err != ENGINE_SUCCESS) {
1649 throw engine_error(err);
1650 }
1651
1652 std::lock_guard<std::mutex> lh(vals_mutex);
1653 return vals;
1654 }
1655
verify_curr_items(EngineIface * h,int exp,const char * msg)1656 void verify_curr_items(EngineIface* h,
1657 int exp,
1658 const char* msg) {
1659 int curr_items = get_int_stat(h, "curr_items");
1660 if (curr_items != exp) {
1661 std::cerr << "Expected "<< exp << " curr_items after " << msg
1662 << ", got " << curr_items << std::endl;
1663 abort();
1664 }
1665 }
1666
wait_for_stat_to_be_gte(EngineIface * h,const char * stat,int final,const char * stat_key,const time_t max_wait_time_in_secs)1667 void wait_for_stat_to_be_gte(EngineIface* h,
1668 const char* stat,
1669 int final,
1670 const char* stat_key,
1671 const time_t max_wait_time_in_secs) {
1672 useconds_t sleepTime = 128;
1673 WaitTimeAccumulator<int> accumulator("to be greater or equal than", stat,
1674 stat_key, final,
1675 max_wait_time_in_secs);
1676 for (;;) {
1677 auto current = get_int_stat(h, stat, stat_key);
1678 if (current >= final) {
1679 break;
1680 }
1681 accumulator.incrementAndAbortIfLimitReached(current, sleepTime);
1682 decayingSleep(&sleepTime);
1683 }
1684 }
1685
wait_for_expired_items_to_be(EngineIface * h,int final,const time_t max_wait_time_in_secs)1686 void wait_for_expired_items_to_be(EngineIface* h,
1687 int final,
1688 const time_t max_wait_time_in_secs) {
1689 useconds_t sleepTime = 128;
1690 WaitTimeAccumulator<int> accumulator("to be", "expired items",
1691 NULL, final,
1692 max_wait_time_in_secs);
1693 for (;;) {
1694 auto current = get_int_stat(h, "ep_expired_access") +
1695 get_int_stat(h, "ep_expired_compactor") +
1696 get_int_stat(h, "ep_expired_pager");
1697 if (current == final) {
1698 break;
1699 }
1700 accumulator.incrementAndAbortIfLimitReached(current, sleepTime);
1701 decayingSleep(&sleepTime);
1702 }
1703 }
1704
wait_for_memory_usage_below(EngineIface * h,int mem_threshold,const time_t max_wait_time_in_secs)1705 void wait_for_memory_usage_below(EngineIface* h,
1706 int mem_threshold,
1707 const time_t max_wait_time_in_secs) {
1708 useconds_t sleepTime = 128;
1709 WaitTimeAccumulator<int> accumulator("to be below", "mem_used", NULL,
1710 mem_threshold,
1711 max_wait_time_in_secs);
1712 for (;;) {
1713 auto current = get_int_stat(h, "mem_used");
1714 if (current <= mem_threshold) {
1715 break;
1716 }
1717 accumulator.incrementAndAbortIfLimitReached(current, sleepTime);
1718 decayingSleep(&sleepTime);
1719 }
1720 }
1721
wait_for_warmup_complete(EngineIface * h)1722 bool wait_for_warmup_complete(EngineIface* h) {
1723 if (!isWarmupEnabled(h)) {
1724 return true;
1725 }
1726
1727 useconds_t sleepTime = 128;
1728 do {
1729 try {
1730 if (get_str_stat(h, "ep_warmup_thread", "warmup") == "complete") {
1731 return true;
1732 }
1733 } catch (engine_error&) {
1734 // If the stat call failed then the warmup stats group no longer
1735 // exists and hence warmup is complete.
1736 return true;
1737 }
1738 decayingSleep(&sleepTime);
1739 } while(true);
1740 }
1741
wait_for_flusher_to_settle(EngineIface * h)1742 void wait_for_flusher_to_settle(EngineIface* h) {
1743 wait_for_stat_to_be(h, "ep_queue_size", 0);
1744
1745 if (!isPersistentBucket(h)) {
1746 // We don't run flusher in non-persistent buckets
1747 return;
1748 }
1749 // We also need to to wait for any outstanding flushes to disk to
1750 // complete - specifically so when in full eviction mode we have
1751 // waited for the item counts in each vBucket to be synced with
1752 // the number of items on disk. See
1753 // EPBucket::commit().
1754 wait_for_stat_to_be(h, "ep_flusher_todo", 0);
1755 }
1756
wait_for_item_compressor_to_settle(EngineIface * h)1757 void wait_for_item_compressor_to_settle(EngineIface* h) {
1758 int visited_count = get_int_stat(h, "ep_item_compressor_num_visited");
1759
1760 // We need to wait for at least one more run of the item compressor
1761 wait_for_stat_to_be(h, "ep_item_compressor_num_visited", visited_count + 1);
1762 }
1763
wait_for_rollback_to_finish(EngineIface * h)1764 void wait_for_rollback_to_finish(EngineIface* h) {
1765 useconds_t sleepTime = 128;
1766 while (get_int_stat(h, "ep_rollback_count") == 0) {
1767 decayingSleep(&sleepTime);
1768 }
1769 }
1770
wait_for_persisted_value(EngineIface * h,const char * key,const char * val,Vbid vbucketId)1771 void wait_for_persisted_value(EngineIface* h,
1772 const char* key,
1773 const char* val,
1774 Vbid vbucketId) {
1775 int commitNum = 0;
1776 if (isPersistentBucket(h)) {
1777 commitNum = get_int_stat(h, "ep_commit_num");
1778 }
1779 checkeq(ENGINE_SUCCESS,
1780 store(h, nullptr, OPERATION_SET, key, val, nullptr, 0, vbucketId),
1781 "Failed to store an item.");
1782
1783 if (isPersistentBucket(h)) {
1784 // Wait for persistence...
1785 wait_for_flusher_to_settle(h);
1786 wait_for_stat_change(h, "ep_commit_num", commitNum);
1787 }
1788 }
1789
abort_msg(const char * expr,const char * msg,const char * file,int line)1790 void abort_msg(const char* expr, const char* msg, const char* file, int line) {
1791 fprintf(stderr, "%s:%d Test failed: `%s' (%s)\n",
1792 file, line, msg, expr);
1793 throw TestExpectationFailed();
1794 }
1795
1796 /* Helper function to validate the return from store() */
validate_store_resp(ENGINE_ERROR_CODE ret,int & num_items)1797 void validate_store_resp(ENGINE_ERROR_CODE ret, int& num_items)
1798 {
1799 switch (ret) {
1800 case ENGINE_SUCCESS:
1801 num_items++;
1802 break;
1803 case ENGINE_TMPFAIL:
1804 /* TMPFAIL means we are hitting high memory usage; retry */
1805 break;
1806 default:
1807 check(false,
1808 ("write_items_upto_mem_perc: Unexpected response from "
1809 "store(): " + std::to_string(ret)).c_str());
1810 break;
1811 }
1812 }
1813
write_items(EngineIface * h,int num_items,int start_seqno,const char * key_prefix,const char * value,uint32_t expiry,Vbid vb,DocumentState docState)1814 void write_items(EngineIface* h,
1815 int num_items,
1816 int start_seqno,
1817 const char* key_prefix,
1818 const char* value,
1819 uint32_t expiry,
1820 Vbid vb,
1821 DocumentState docState) {
1822 int j = 0;
1823 while (1) {
1824 if (j == num_items) {
1825 break;
1826 }
1827 std::string key(key_prefix + std::to_string(j + start_seqno));
1828 ENGINE_ERROR_CODE ret = store(h,
1829 nullptr,
1830 OPERATION_SET,
1831 key.c_str(),
1832 value,
1833 nullptr,
1834 /*cas*/ 0,
1835 vb,
1836 expiry,
1837 0,
1838 docState);
1839 validate_store_resp(ret, j);
1840 }
1841 }
1842
1843 /* Helper function to write unique items starting from keyXX until memory usage
1844 hits "mem_thresh_perc" (XX is start_seqno) */
write_items_upto_mem_perc(EngineIface * h,int mem_thresh_perc,int start_seqno,const char * key_prefix,const char * value)1845 int write_items_upto_mem_perc(EngineIface* h,
1846 int mem_thresh_perc,
1847 int start_seqno,
1848 const char* key_prefix,
1849 const char* value) {
1850 float maxSize =
1851 static_cast<float>(get_int_stat(h, "ep_max_size", "memory"));
1852 float mem_thresh = static_cast<float>(mem_thresh_perc) / (100.0);
1853 int num_items = 0;
1854 while (1) {
1855 /* Load items into server until mem_thresh_perc of the mem quota
1856 is used. Getting stats is expensive, only check every 100
1857 iterations. */
1858 if ((num_items % 100) == 0) {
1859 float memUsed = float(get_int_stat(h, "mem_used", "memory"));
1860 if (memUsed > (maxSize * mem_thresh)) {
1861 /* Persist all items written so far. */
1862 break;
1863 }
1864 }
1865 std::string key("key" + std::to_string(num_items + start_seqno));
1866 ENGINE_ERROR_CODE ret =
1867 store(h, nullptr, OPERATION_SET, key.c_str(), "somevalue");
1868 validate_store_resp(ret, num_items);
1869 }
1870 return num_items;
1871 }
1872
get_CAS(EngineIface * h,const std::string & key)1873 uint64_t get_CAS(EngineIface* h, const std::string& key) {
1874 auto ret = get(h, nullptr, key, Vbid(0));
1875 checkeq(cb::engine_errc::success, ret.first, "get_CAS: Failed to get key");
1876
1877 item_info info;
1878 check(h->get_item_info(ret.second.get(), &info),
1879 "get_CAS: Failed to get item info for key");
1880
1881 return info.cas;
1882 }
1883
allocate(EngineIface * h,const void * cookie,const std::string & key,size_t nbytes,int flags,rel_time_t exptime,uint8_t datatype,Vbid vb)1884 cb::EngineErrorItemPair allocate(EngineIface* h,
1885 const void* cookie,
1886 const std::string& key,
1887 size_t nbytes,
1888 int flags,
1889 rel_time_t exptime,
1890 uint8_t datatype,
1891 Vbid vb) {
1892 bool cookie_created = false;
1893 if (cookie == nullptr) {
1894 cookie = testHarness->create_cookie();
1895 cookie_created = true;
1896 }
1897 auto ret = h->allocate(cookie,
1898 DocKey(key, DocKeyEncodesCollectionId::No),
1899 nbytes,
1900 flags,
1901 exptime,
1902 datatype,
1903 vb);
1904 if (cookie_created) {
1905 testHarness->destroy_cookie(cookie);
1906 }
1907
1908 return ret;
1909 }
1910
get(EngineIface * h,const void * cookie,const std::string & key,Vbid vb,DocStateFilter documentStateFilter)1911 cb::EngineErrorItemPair get(EngineIface* h,
1912 const void* cookie,
1913 const std::string& key,
1914 Vbid vb,
1915 DocStateFilter documentStateFilter) {
1916 bool create_cookie = false;
1917 if (cookie == nullptr) {
1918 cookie = testHarness->create_cookie();
1919 create_cookie = true;
1920 }
1921
1922 auto ret = h->get(cookie,
1923 DocKey(key, DocKeyEncodesCollectionId::No),
1924 vb,
1925 documentStateFilter);
1926
1927 if (create_cookie) {
1928 testHarness->destroy_cookie(cookie);
1929 }
1930 return ret;
1931 }
1932
repeat_till_true(std::function<bool ()> functor,uint16_t max_repeat,std::chrono::microseconds sleepTime)1933 bool repeat_till_true(std::function<bool()> functor,
1934 uint16_t max_repeat,
1935 std::chrono::microseconds sleepTime) {
1936 bool fSuccess = false;
1937 do {
1938 fSuccess = functor();
1939 if (!fSuccess) {
1940 std::this_thread::sleep_for(sleepTime);
1941 max_repeat--;
1942 }
1943 } while (!fSuccess && max_repeat > 0);
1944 return fSuccess;
1945 }
1946
reset_stats(gsl::not_null<EngineIface * > h)1947 void reset_stats(gsl::not_null<EngineIface*> h) {
1948 const auto* cookie = testHarness->create_cookie();
1949 h->reset_stats(cookie);
1950 testHarness->destroy_cookie(cookie);
1951 }
1952
get_stats(gsl::not_null<EngineIface * > h,cb::const_char_buffer key,cb::const_char_buffer value,const AddStatFn & callback)1953 ENGINE_ERROR_CODE get_stats(gsl::not_null<EngineIface*> h,
1954 cb::const_char_buffer key,
1955 cb::const_char_buffer value,
1956 const AddStatFn& callback) {
1957 const auto* cookie = testHarness->create_cookie();
1958 auto ret = h->get_stats(cookie, key, value, callback);
1959 testHarness->destroy_cookie(cookie);
1960 return ret;
1961 }
1962