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