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 42using namespace std::string_literals; 43 44CouchstoreFileAccessGuard::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 77CouchstoreFileAccessGuard::~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 86template<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. 91std::mutex vals_mutex; 92statistic_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). 102struct { 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 112bool dump_stats = false; 113std::atomic<cb::mcbp::Status> last_status(cb::mcbp::Status::Success); 114std::string last_key; 115std::string last_body; 116std::string last_ext; 117std::atomic<uint64_t> last_cas(0); 118std::atomic<uint8_t> last_datatype(0x00); 119ItemMetaData last_meta; 120std::atomic<uint64_t> last_uuid(0); 121std::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 */ 126template<typename T> 127class HistogramBinStats { 128public: 129 HistogramBinStats(const T& s, const T& e, uint64_t count) 130 : start_(s), end_(e), count_(count) { } 131 132 T start() const { 133 return start_; 134 } 135 136 T end() const { 137 return end_; 138 } 139 140 uint64_t count() const { 141 return count_; 142 } 143 144private: 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 */ 154template<typename T> 155class HistogramStats { 156public: 157 HistogramStats() : total_count(0) {} 158 159 /* Add a new bin */ 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 */ 166 size_t num_bins() const { 167 return bins.size(); 168 } 169 170 uint64_t total() const { 171 return total_count; 172 } 173 174 /* Add a bin iterator when needed */ 175private: 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 182static void get_histo_stat(EngineIface* h, 183 const char* statname, 184 const char* statkey); 185 186void encodeExt(char* buffer, uint32_t val, size_t offset = 0); 187void encodeWithMetaExt(char *buffer, ItemMetaData *meta); 188 189void 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 195bool 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 217bool 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 242bool 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 267void 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 */ 287void 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 300void 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 339void encodeExt(char* buffer, uint32_t val, size_t offset) { 340 val = htonl(val); 341 memcpy(buffer + offset, (char*)&val, sizeof(val)); 342} 343 344void 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 355void 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 363void 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 371void 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 378ENGINE_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 387ENGINE_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 */ 415ENGINE_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 438ENGINE_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 466ENGINE_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 507void 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 549ENGINE_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 564ENGINE_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 576cb::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 596bool 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 609cb::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 630bool 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 638bool 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 658ENGINE_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 675ENGINE_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 685void 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 696unique_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 723bool 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 746bool 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 781bool 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 822void 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 866static 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 909ENGINE_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 936ENGINE_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 963static 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 991ENGINE_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 1016ENGINE_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 1041ENGINE_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 1061void 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 1071void 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 1081void 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 1096void 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 1119ENGINE_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 1151ENGINE_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 1181cb::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 1226ENGINE_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 1274ENGINE_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 1294ENGINE_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 1313void 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 1350ENGINE_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 1358ENGINE_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 1363std::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 1381bool 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 1409bool 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 1438void 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 1454class engine_error : public std::exception { 1455public: 1456 engine_error(ENGINE_ERROR_CODE code_) 1457 : code(code_) {} 1458 1459 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 */ 1472template <> 1473int get_stat(EngineIface* h, 1474 const char* statname, 1475 const char* statkey) { 1476 return std::stoi(get_str_stat(h, statname, statkey)); 1477} 1478template <> 1479uint64_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 1485template <> 1486bool get_stat(EngineIface* h, 1487 const char* statname, 1488 const char* statkey) { 1489 return get_str_stat(h, statname, statkey) == "true"; 1490} 1491 1492template <> 1493float get_stat(EngineIface* h, const char* statname, const char* statkey) { 1494 return std::stof(get_str_stat(h, statname, statkey)); 1495} 1496 1497template <> 1498std::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). 1531int get_int_stat(EngineIface* h, 1532 const char* statname, 1533 const char* statkey) { 1534 return get_stat<int>(h, statname, statkey); 1535} 1536 1537float 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 1543uint32_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 1549uint64_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 1555std::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 1561bool 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 */ 1579int 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 1590uint64_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 1616static 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 1636statistic_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 1656void 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 1667void 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 1686void 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 1705void 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 1722bool 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 1742void 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 1757void 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 1764void 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 1771void 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 1790void 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() */ 1797void 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 1814void 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) */ 1845int 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 1873uint64_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 1884cb::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 1911cb::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 1933bool 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 1947void 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 1953ENGINE_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