1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
3 #include "testapp.h"
4
5 #include "ssl_impl.h"
6
7 #include <JSON_checker.h>
8 #include <cbsasl/client.h>
9 #include <folly/portability/GTest.h>
10 #include <folly/portability/SysTypes.h>
11 #include <mcbp/protocol/framebuilder.h>
12 #include <platform/backtrace.h>
13 #include <platform/cb_malloc.h>
14 #include <platform/cbassert.h>
15 #include <platform/dirutils.h>
16 #include <platform/socket.h>
17 #include <platform/strerror.h>
18 #include <platform/string_hex.h>
19 #include <gsl/gsl>
20
21 #include <getopt.h>
22 #include <platform/compress.h>
23 #include <fstream>
24
25 #include <include/memcached/protocol_binary.h>
26 #include <nlohmann/json.hpp>
27 #include <protocol/mcbp/ewb_encode.h>
28 #include <utilities/json_utilities.h>
29 #include <atomic>
30 #include <chrono>
31 #include <csignal>
32 #include <thread>
33
34 McdEnvironment* mcd_env = nullptr;
35
36 /* test phases (bitmasks) */
37 #define phase_plain 0x2
38 #define phase_ssl 0x4
39
40 #define phase_max 4
41 static int current_phase = 0;
42
43 pid_t server_pid = -1;
44 in_port_t port = -1;
45 in_port_t ssl_port = -1;
46 SOCKET sock = INVALID_SOCKET;
47 SOCKET sock_ssl;
48 static std::atomic<bool> allow_closed_read;
49 static time_t server_start_time = 0;
50
51 // used in embedded mode to shutdown memcached
52 extern void shutdown_server();
53
54 std::set<cb::mcbp::Feature> enabled_hello_features;
55
56 int memcached_verbose = 0;
57 // State variable if we're running the memcached server in a
58 // thread in the same process or not
59 static bool embedded_memcached_server;
60
61 /* static storage for the different environment variables set by
62 * putenv().
63 *
64 * (These must be static as putenv() essentially 'takes ownership' of
65 * the provided array, so it is unsafe to use an automatic variable.
66 * However, if we use the result of cb_malloc() (i.e. the heap) then
67 * memory leak checkers (e.g. Valgrind) will report the memory as
68 * leaked as it's impossible to free it).
69 */
70 static char mcd_parent_monitor_env[80];
71 static char mcd_port_filename_env[80];
72
73 static SOCKET connect_to_server_ssl(in_port_t ssl_port);
74
set_allow_closed_read(bool enabled)75 void set_allow_closed_read(bool enabled) {
76 allow_closed_read = enabled;
77 }
78
sock_is_ssl()79 bool sock_is_ssl() {
80 return current_phase == phase_ssl;
81 }
82
get_server_start_time()83 time_t get_server_start_time() {
84 return server_start_time;
85 }
86
operator <<(std::ostream & os,const TransportProtocols & t)87 std::ostream& operator<<(std::ostream& os, const TransportProtocols& t) {
88 os << to_string(t);
89 return os;
90 }
91
to_string(const TransportProtocols & transport)92 std::string to_string(const TransportProtocols& transport) {
93 #ifdef JETBRAINS_CLION_IDE
94 // CLion don't properly parse the output when the
95 // output gets written as the string instead of the
96 // number. This makes it harder to debug the tests
97 // so let's just disable it while we're waiting
98 // for them to supply a fix.
99 // See https://youtrack.jetbrains.com/issue/CPP-6039
100 return std::to_string(static_cast<int>(transport));
101 #else
102 switch (transport) {
103 case TransportProtocols::McbpPlain:
104 return "Mcbp";
105 case TransportProtocols::McbpIpv6Plain:
106 return "McbpIpv6";
107 case TransportProtocols::McbpSsl:
108 return "McbpSsl";
109 case TransportProtocols::McbpIpv6Ssl:
110 return "McbpIpv6Ssl";
111 }
112 throw std::logic_error("Unknown transport");
113 #endif
114 }
115
to_string(ClientJSONSupport json)116 std::string to_string(ClientJSONSupport json) {
117 switch (json) {
118 case ClientJSONSupport::Yes:
119 return "JsonYes";
120 case ClientJSONSupport::No:
121 return "JsonNo";
122 }
123 throw std::logic_error("Unknown JSON support");
124 }
125
to_string(ClientSnappySupport snappy)126 std::string to_string(ClientSnappySupport snappy) {
127 switch (snappy) {
128 case ClientSnappySupport::Yes:
129 return "SnappyYes";
130 case ClientSnappySupport::No:
131 return "SnappyNo";
132 }
133 throw std::logic_error("Unknown ClientSnappySupport value: " +
134 std::to_string(int(snappy)));
135 }
136
CreateTestBucket()137 void TestappTest::CreateTestBucket() {
138 auto& conn = connectionMap.getConnection(false);
139
140 // Reconnect to the server so we know we're on a "fresh" connection
141 // to the server (and not one that might have been cached by the
142 // idle-timer, but not yet noticed on the client side)
143 conn.reconnect();
144 conn.authenticate("@admin", "password", "PLAIN");
145
146 mcd_env->getTestBucket().setUpBucket(bucketName, "", conn);
147
148 // Reconnect the object to avoid others to reuse the admin creds
149 conn.reconnect();
150 }
151
DeleteTestBucket()152 void TestappTest::DeleteTestBucket() {
153 auto& conn = connectionMap.getConnection(false);
154 conn.reconnect();
155 conn.authenticate("@admin", "password", "PLAIN");
156 try {
157 conn.deleteBucket(bucketName);
158 } catch (const ConnectionError& error) {
159 EXPECT_FALSE(error.isNotFound()) << "Delete bucket [" << bucketName
160 << "] failed with: " << error.what();
161 }
162 }
163
GetTestBucket()164 TestBucketImpl& TestappTest::GetTestBucket() {
165 return mcd_env->getTestBucket();
166 }
167
168 // Per-test-case set-up.
169 // Called before the first test in this test case.
SetUpTestCase()170 void TestappTest::SetUpTestCase() {
171 token = 0xdeadbeef;
172 memcached_cfg = generate_config(0);
173 start_memcached_server();
174
175 if (HasFailure()) {
176 std::cerr << "Error in TestappTest::SetUpTestCase, terminating process"
177 << std::endl;
178
179 exit(EXIT_FAILURE);
180 } else {
181 CreateTestBucket();
182 }
183 }
184
185 // Per-test-case tear-down.
186 // Called after the last test in this test case.
TearDownTestCase()187 void TestappTest::TearDownTestCase() {
188 if (sock != INVALID_SOCKET) {
189 cb::net::closesocket(sock);
190 }
191
192 if (server_pid != -1) {
193 DeleteTestBucket();
194 }
195 stop_memcached_server();
196 }
197
reconfigure_client_cert_auth(const std::string & state,const std::string & path,const std::string & prefix,const std::string & delimiter)198 void TestappTest::reconfigure_client_cert_auth(const std::string& state,
199 const std::string& path,
200 const std::string& prefix,
201 const std::string& delimiter) {
202 memcached_cfg["client_cert_auth"] = {};
203 memcached_cfg["client_cert_auth"]["state"] = state;
204 memcached_cfg["client_cert_auth"]["path"] = path;
205 memcached_cfg["client_cert_auth"]["prefix"] = prefix;
206 memcached_cfg["client_cert_auth"]["delimiter"] = delimiter;
207 // update the server to use this!
208 reconfigure();
209 }
210
setClientCertData(MemcachedConnection & connection)211 void TestappTest::setClientCertData(MemcachedConnection& connection) {
212 connection.setSslCertFile(SOURCE_ROOT +
213 std::string("/tests/cert/client.pem"));
214 connection.setSslKeyFile(SOURCE_ROOT +
215 std::string("/tests/cert/client.key"));
216 }
217
get_sasl_mechs()218 std::string get_sasl_mechs() {
219 using namespace cb::mcbp;
220 Request request = {};
221 request.setMagic(Magic::ClientRequest);
222 request.setOpcode(ClientOpcode::SaslListMechs);
223 request.setOpaque(0xdeadbeef);
224
225 safe_send(request.getFrame());
226
227 std::vector<uint8_t> blob;
228 safe_recv_packet(blob);
229 auto& response = *reinterpret_cast<Response*>(blob.data());
230 mcbp_validate_response_header(response,
231 cb::mcbp::ClientOpcode::SaslListMechs,
232 cb::mcbp::Status::Success);
233
234 auto val = response.getValue();
235 return std::string{reinterpret_cast<const char*>(val.data()), val.size()};
236 }
237
sasl_auth(const char * username,const char * password)238 cb::mcbp::Status TestappTest::sasl_auth(const char* username,
239 const char* password) {
240 cb::sasl::client::ClientContext client(
241 [username]() -> std::string { return username; },
242 [password]() -> std::string { return password; },
243 get_sasl_mechs());
244
245 auto client_data = client.start();
246 EXPECT_EQ(cb::sasl::Error::OK, client_data.first);
247 if (::testing::Test::HasFailure()) {
248 // Can't continue if we didn't suceed in starting SASL auth.
249 return cb::mcbp::Status::Einternal;
250 }
251
252 std::vector<uint8_t> blob;
253 {
254 BinprotSaslAuthCommand cmd;
255 cmd.setMechanism(client.getName());
256 cmd.setChallenge(client_data.second);
257 cmd.encode(blob);
258 safe_send(blob);
259 }
260
261 blob.resize(0);
262 safe_recv_packet(blob);
263
264 bool stepped = false;
265 auto* response = reinterpret_cast<cb::mcbp::Response*>(blob.data());
266 while (response->getStatus() == cb::mcbp::Status::AuthContinue) {
267 stepped = true;
268 auto challenge = response->getValue();
269 client_data = client.step(cb::const_char_buffer{
270 reinterpret_cast<const char*>(challenge.data()),
271 challenge.size()});
272 EXPECT_EQ(cb::sasl::Error::CONTINUE, client_data.first);
273 BinprotSaslStepCommand cmd;
274 cmd.setMechanism(client.getName());
275 cmd.setChallenge(client_data.second);
276 blob.resize(0);
277 cmd.encode(blob);
278
279 safe_send(blob);
280 blob.resize(0);
281 safe_recv_packet(blob);
282 response = reinterpret_cast<cb::mcbp::Response*>(blob.data());
283 }
284
285 if (stepped) {
286 mcbp_validate_response_header(*response,
287 cb::mcbp::ClientOpcode::SaslStep,
288 response->getStatus());
289 } else {
290 mcbp_validate_response_header(*response,
291 cb::mcbp::ClientOpcode::SaslAuth,
292 response->getStatus());
293 }
294
295 return response->getStatus();
296 }
297
isJSON(cb::const_char_buffer value)298 bool TestappTest::isJSON(cb::const_char_buffer value) {
299 JSON_checker::Validator validator;
300 const auto* ptr = reinterpret_cast<const uint8_t*>(value.data());
301 return validator.validate(ptr, value.size());
302 }
303
304 // per test setup function.
SetUp()305 void TestappTest::SetUp() {
306 verify_server_running();
307 current_phase = phase_plain;
308 sock = connect_to_server_plain(port);
309 ASSERT_NE(INVALID_SOCKET, sock);
310
311 // Set ewouldblock_engine test harness to default mode.
312 ewouldblock_engine_configure(ENGINE_EWOULDBLOCK,
313 EWBEngineMode::First,
314 /*unused*/ 0);
315
316 enabled_hello_features.clear();
317
318 const auto* info = ::testing::UnitTest::GetInstance()->current_test_info();
319 name.assign(info->test_case_name());
320 name.append("_");
321 name.append(info->name());
322 std::replace(name.begin(), name.end(), '/', '_');
323 }
324
325 // per test tear-down function.
TearDown()326 void TestappTest::TearDown() {
327 cb::net::closesocket(sock);
328 }
329
330 const std::string TestappTest::bucketName = "default";
331
332 // per test setup function.
SetUp()333 void McdTestappTest::SetUp() {
334 verify_server_running();
335 if (getProtocolParam() == TransportProtocols::McbpPlain) {
336 current_phase = phase_plain;
337 sock = connect_to_server_plain(port);
338 ASSERT_NE(INVALID_SOCKET, sock);
339 } else {
340 current_phase = phase_ssl;
341 sock_ssl = connect_to_server_ssl(ssl_port);
342 ASSERT_NE(INVALID_SOCKET, sock_ssl);
343 }
344
345 set_json_feature(hasJSONSupport() == ClientJSONSupport::Yes);
346
347 // Set ewouldblock_engine test harness to default mode.
348 ewouldblock_engine_configure(ENGINE_EWOULDBLOCK,
349 EWBEngineMode::First,
350 /*unused*/ 0);
351
352 setCompressionMode("off");
353 }
354
355 // per test tear-down function.
TearDown()356 void McdTestappTest::TearDown() {
357 if (getProtocolParam() == TransportProtocols::McbpPlain) {
358 cb::net::closesocket(sock);
359 } else {
360 cb::net::closesocket(sock_ssl);
361 sock_ssl = INVALID_SOCKET;
362 destroy_ssl_socket();
363 }
364 }
365
hasJSONSupport() const366 ClientJSONSupport McdTestappTest::hasJSONSupport() const {
367 return getJSONParam();
368 }
369
setCompressionMode(const std::string & compression_mode)370 void TestappTest::setCompressionMode(const std::string& compression_mode) {
371 mcd_env->getTestBucket().setCompressionMode(
372 getAdminConnection(), bucketName, compression_mode);
373 }
374
setMinCompressionRatio(float min_compression_ratio)375 void TestappTest::setMinCompressionRatio(float min_compression_ratio) {
376 mcd_env->getTestBucket().setMinCompressionRatio(
377 getAdminConnection(),
378 bucketName,
379 std::to_string(min_compression_ratio));
380 }
381
waitForAtLeastSeqno(Vbid vbid,uint64_t uuid,uint64_t seqno)382 void TestappTest::waitForAtLeastSeqno(Vbid vbid,
383 uint64_t uuid,
384 uint64_t seqno) {
385 ASSERT_TRUE(mcd_env->getTestBucket().supportsPersistence())
386 << "Error: your bucket does not support persistence";
387
388 // Poll for that sequence number to be persisted.
389 ObserveInfo observe;
390 MemcachedConnection& conn = getConnection();
391 do {
392 observe = conn.observeSeqno(vbid, uuid);
393 EXPECT_EQ(0, observe.formatType);
394 EXPECT_EQ(vbid, observe.vbId);
395 EXPECT_EQ(uuid, observe.uuid);
396
397 if (observe.lastPersistedSeqno < seqno) {
398 // Don't busy wait, yield a little. A 2019 poll of sleeps in KV
399 // reveals 100us to be our most popular wait time.
400 std::this_thread::sleep_for(std::chrono::microseconds(100));
401 } else {
402 break;
403 }
404 } while (true);
405 }
406
storeAndPersistItem(Vbid vbid,std::string key)407 Document TestappTest::storeAndPersistItem(Vbid vbid, std::string key) {
408 MemcachedConnection& conn = getConnection();
409 conn.setMutationSeqnoSupport(true);
410 Document doc;
411 doc.info.id = key;
412 doc.value = "persist me";
413 auto mutation = conn.mutate(doc, vbid, MutationType::Set);
414 EXPECT_NE(0, mutation.seqno);
415 EXPECT_NE(0, mutation.vbucketuuid);
416 doc.info.cas = mutation.cas;
417
418 waitForAtLeastSeqno(vbid, mutation.vbucketuuid, mutation.seqno);
419
420 return doc;
421 }
422
PrintToStringCombinedName(const::testing::TestParamInfo<::testing::tuple<TransportProtocols,ClientJSONSupport>> & info)423 std::string McdTestappTest::PrintToStringCombinedName(
424 const ::testing::TestParamInfo<
425 ::testing::tuple<TransportProtocols, ClientJSONSupport>>&
426 info) {
427 return to_string(::testing::get<0>(info.param)) + "_" +
428 to_string(::testing::get<1>(info.param));
429 }
430
CERTIFICATE_PATH(const std::string & file)431 std::string CERTIFICATE_PATH(const std::string& file) {
432 #ifdef WIN32
433 return std::string("\\tests\\cert\\") + file;
434 #else
435 return std::string("/tests/cert/") + file;
436 #endif
437 }
get_errmaps_dir()438 static std::string get_errmaps_dir() {
439 std::string dir(SOURCE_ROOT);
440 dir += "/etc/couchbase/kv/error_maps";
441 cb::io::sanitizePath(dir);
442 return dir;
443 }
444
generate_config(uint16_t ssl_port)445 nlohmann::json TestappTest::generate_config(uint16_t ssl_port) {
446 const std::string cwd = cb::io::getcwd();
447 const std::string pem_path = cwd + CERTIFICATE_PATH("testapp.pem");
448 const std::string cert_path = cwd + CERTIFICATE_PATH("testapp.cert");
449
450 nlohmann::json ret = {
451 {"max_connections", Testapp::MAX_CONNECTIONS},
452 {"system_connections", Testapp::MAX_CONNECTIONS / 4},
453 {"stdin_listener", false},
454 {"datatype_json", true},
455 {"datatype_snappy", true},
456 {"xattr_enabled", true},
457 {"dedupe_nmvb_maps", false},
458 {"active_external_users_push_interval", "30 m"},
459 {"error_maps_dir", get_errmaps_dir()},
460 {"audit_file", mcd_env->getAuditFilename()},
461 {"rbac_file", mcd_env->getRbacFilename()},
462 {"ssl_cipher_list",
463 {{"tls 1.2", "HIGH"},
464 {"tls 1.3",
465 "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_"
466 "128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_"
467 "SHA256"}}},
468 {"ssl_minimum_protocol", "tlsv1"},
469 {"opcode_attributes_override",
470 {{"version", 1}, {"EWB_CTL", {{"slow", 50}}}}},
471 {"logger", {{"unit_test", true}}},
472 };
473
474 if (memcached_verbose == 0) {
475 ret["logger"]["console"] = false;
476 } else {
477 ret["verbosity"] = memcached_verbose - 1;
478 }
479
480 // For simplicity in the test to check for max connections we mark the
481 // SSL port as an admin port
482 ret["interfaces"][0] = {{"tag", "plain"},
483 {"system", false},
484 {"port", 0},
485 {"ipv4", "required"},
486 {"ipv6", "required"},
487 {"host", "*"}};
488
489 ret["interfaces"][1] = {{"tag", "ssl"},
490 {"system", true},
491 {"port", ssl_port},
492 {"ipv4", "required"},
493 {"ipv6", "required"},
494 {"host", "*"},
495 {"ssl", {{"key", pem_path}, {"cert", cert_path}}}};
496
497 return ret;
498 }
499
write_config_to_file(const std::string & config,const std::string & fname)500 void write_config_to_file(const std::string& config, const std::string& fname) {
501 FILE* fp = fopen(fname.c_str(), "w");
502
503 if (fp == nullptr) {
504 throw std::system_error(errno,
505 std::system_category(),
506 "Failed to open file \"" + fname + "\"");
507 } else {
508 fprintf(fp, "%s", config.c_str());
509 fclose(fp);
510 }
511 }
512
513 #ifdef WIN32
pidTToHandle(pid_t pid)514 HANDLE TestappTest::pidTToHandle(pid_t pid) {
515 return reinterpret_cast<HANDLE>(static_cast<size_t>(pid));
516 }
517
handleToPidT(HANDLE handle)518 pid_t TestappTest::handleToPidT(HANDLE handle) {
519 return static_cast<pid_t>(reinterpret_cast<size_t>(handle));
520 }
521 #endif
522
verify_server_running()523 void TestappTest::verify_server_running() {
524 if (embedded_memcached_server) {
525 // we don't monitor this thread...
526 return;
527 }
528
529 if (-1 == server_pid) {
530 std::cerr << "Server not running (server_pid == -1)" << std::endl;
531 exit(EXIT_FAILURE);
532 }
533
534 #ifdef WIN32
535 DWORD status;
536
537 if (!GetExitCodeProcess(pidTToHandle(server_pid), &status)) {
538 std::cerr << "GetExitCodeProcess: failed: " << cb_strerror()
539 << std::endl;
540 exit(EXIT_FAILURE);
541 }
542 if (status != STILL_ACTIVE) {
543 std::cerr << "memcached process is not active: Exit code " << status
544 << std::endl;
545 exit(EXIT_FAILURE);
546 }
547 #else
548 int status;
549 pid_t ret = waitpid(server_pid, &status, WNOHANG);
550
551 if (ret == static_cast<pid_t>(-1)) {
552 std::cerr << "waitpid() failed with: " << strerror(errno) << std::endl;
553 exit(EXIT_FAILURE);
554 }
555
556 if (server_pid == ret) {
557 std::cerr << "waitpid status : " << status << std::endl
558 << "WIFEXITED(status) : " << WIFEXITED(status) << std::endl
559 << "WEXITSTATUS(status): " << WEXITSTATUS(status) << std::endl
560 << "WIFSIGNALED(status): " << WIFSIGNALED(status) << std::endl
561 << "WTERMSIG(status) : " << WTERMSIG(status) << std::endl
562 << "WCOREDUMP(status) : " << WCOREDUMP(status) << std::endl;
563 exit(EXIT_FAILURE);
564 }
565 #endif
566 }
567
parse_portnumber_file()568 void TestappTest::parse_portnumber_file() {
569 // I've seen that running under valgrind startup of the processes
570 // might be slow, and getting even worse if the machine is under
571 // load. Instead of having a "false timeout" just because the
572 // server is slow, lets set the deadline to a high value so that
573 // if we hit it we have a real problem and not just a loaded
574 // server (rebuilding all of the source one more time is just
575 // putting more load on the servers).
576 using std::chrono::steady_clock;
577 const auto timeout = steady_clock::now() + std::chrono::minutes(5);
578
579 do {
580 std::this_thread::sleep_for(std::chrono::milliseconds(10));
581 if (cb::io::isFile(portnumber_file)) {
582 break;
583 }
584
585 verify_server_running();
586 } while (steady_clock::now() < timeout);
587
588 ASSERT_TRUE(cb::io::isFile(portnumber_file))
589 << "Timed out after 5 minutes waiting for memcached port file '"
590 << portnumber_file << "' to be created.";
591
592 // We'll throw here if anything goes wrong
593 const auto json = nlohmann::json::parse(cb::io::loadFile(portnumber_file));
594
595 connectionMap.initialize(json);
596
597 // The tests which don't use the MemcachedConnection class needs the
598 // global variables port and ssl_port to be set
599 port = (in_port_t)-1;
600 ssl_port = (in_port_t)-1;
601
602 connectionMap.iterate([](MemcachedConnection& connection) {
603 if (connection.getFamily() == AF_INET) {
604 if (connection.isSsl()) {
605 ssl_port = connection.getPort();
606 } else {
607 port = connection.getPort();
608 }
609 }
610 });
611
612 if (port == in_port_t(-1)) {
613 throw std::runtime_error(
614 "parse_portnumber_file: Failed to locate an plain IPv4 "
615 "connection");
616 }
617
618 if (ssl_port == in_port_t(-1)) {
619 throw std::runtime_error(
620 "parse_portnumber_file: Failed to locate a SSL IPv4 "
621 "connection");
622 }
623
624 EXPECT_EQ(0, remove(portnumber_file.c_str()));
625 }
626
627 extern "C" int memcached_main(int argc, char** argv);
628
memcached_server_thread_main(void * arg)629 extern "C" void memcached_server_thread_main(void* arg) {
630 char* argv[4];
631 int argc = 0;
632 argv[argc++] = const_cast<char*>("./memcached");
633 argv[argc++] = const_cast<char*>("-C");
634 argv[argc++] = reinterpret_cast<char*>(arg);
635
636 // Reset getopt()'s optind so memcached_main starts from the first
637 // argument.
638 optind = 1;
639
640 memcached_main(argc, argv);
641 }
642
spawn_embedded_server()643 void TestappTest::spawn_embedded_server() {
644 char* filename = mcd_port_filename_env + strlen("MEMCACHED_PORT_FILENAME=");
645 snprintf(mcd_port_filename_env,
646 sizeof(mcd_port_filename_env),
647 "MEMCACHED_PORT_FILENAME=memcached_ports.%u.%lu",
648 (int)getpid(),
649 (unsigned long)time(NULL));
650 remove(filename);
651 portnumber_file.assign(filename);
652 putenv(mcd_port_filename_env);
653
654 ASSERT_EQ(0,
655 cb_create_thread(&memcached_server_thread,
656 memcached_server_thread_main,
657 const_cast<char*>(config_file.c_str()),
658 0));
659 }
660
start_external_server()661 void TestappTest::start_external_server() {
662 char* filename = mcd_port_filename_env + strlen("MEMCACHED_PORT_FILENAME=");
663 snprintf(mcd_parent_monitor_env,
664 sizeof(mcd_parent_monitor_env),
665 "MEMCACHED_PARENT_MONITOR=%u",
666 (int)getpid());
667 putenv(mcd_parent_monitor_env);
668
669 snprintf(mcd_port_filename_env,
670 sizeof(mcd_port_filename_env),
671 "MEMCACHED_PORT_FILENAME=memcached_ports.%u.%lu",
672 (int)getpid(),
673 (unsigned long)time(NULL));
674 remove(filename);
675 portnumber_file.assign(filename);
676 static char topkeys_env[] = "MEMCACHED_TOP_KEYS=10";
677 putenv(topkeys_env);
678
679 #ifdef WIN32
680 STARTUPINFO sinfo;
681 PROCESS_INFORMATION pinfo;
682 memset(&sinfo, 0, sizeof(sinfo));
683 memset(&pinfo, 0, sizeof(pinfo));
684 sinfo.cb = sizeof(sinfo);
685
686 char commandline[1024];
687 sprintf(commandline, "memcached.exe -C %s", config_file.c_str());
688
689 putenv(mcd_port_filename_env);
690
691 if (!CreateProcess("memcached.exe",
692 commandline,
693 NULL,
694 NULL,
695 /*bInheritHandles*/ FALSE,
696 0,
697 NULL,
698 NULL,
699 &sinfo,
700 &pinfo)) {
701 std::cerr << "Failed to start process: " << cb_strerror() << std::endl;
702 exit(EXIT_FAILURE);
703 }
704
705 server_pid = handleToPidT(pinfo.hProcess);
706 #else
707 server_pid = fork();
708 ASSERT_NE(reinterpret_cast<pid_t>(-1), server_pid);
709
710 if (server_pid == 0) {
711 /* Child */
712 const char* argv[20];
713 int arg = 0;
714 putenv(mcd_port_filename_env);
715
716 if (getenv("RUN_UNDER_VALGRIND") != nullptr) {
717 argv[arg++] = "valgrind";
718 argv[arg++] = "--log-file=valgrind.%p.log";
719 argv[arg++] = "--leak-check=full";
720 #if defined(__APPLE__)
721 /* Needed to ensure debugging symbols are up-to-date. */
722 argv[arg++] = "--dsymutil=yes";
723 #endif
724 }
725
726 if (getenv("RUN_UNDER_PERF") != nullptr) {
727 argv[arg++] = "perf";
728 argv[arg++] = "record";
729 argv[arg++] = "--call-graph";
730 argv[arg++] = "dwarf";
731 }
732
733 argv[arg++] = "./memcached";
734 argv[arg++] = "-C";
735 argv[arg++] = config_file.c_str();
736
737 argv[arg++] = nullptr;
738 cb_assert(execvp(argv[0], const_cast<char**>(argv)) != -1);
739 }
740 #endif // !WIN32
741 }
742
create_connect_plain_socket(in_port_t port)743 SOCKET create_connect_plain_socket(in_port_t port) {
744 if (port == in_port_t(-1)) {
745 throw std::runtime_error(
746 "create_connect_plain_socket: Can't connect to port == -1");
747 }
748 auto sock = cb::net::new_socket("", port, AF_INET);
749 if (sock == INVALID_SOCKET) {
750 ADD_FAILURE() << "Failed to connect socket to 127.0.0.1:" << port;
751 }
752 return sock;
753 }
754
connect_to_server_plain(in_port_t port)755 SOCKET connect_to_server_plain(in_port_t port) {
756 return create_connect_plain_socket(port);
757 }
758
connect_to_server_ssl(in_port_t ssl_port)759 static SOCKET connect_to_server_ssl(in_port_t ssl_port) {
760 if (ssl_port == in_port_t(-1)) {
761 throw std::runtime_error(
762 "connect_to_server_ssl: Can't connect to port == -1");
763 }
764 SOCKET sock = create_connect_ssl_socket(ssl_port);
765 if (sock == INVALID_SOCKET) {
766 ADD_FAILURE() << "Failed to connect SSL socket to port" << ssl_port;
767 return INVALID_SOCKET;
768 }
769
770 return sock;
771 }
772
773 /*
774 re-connect to server.
775 Uses global port and ssl_port values.
776 New socket-fd written to global "sock" and "ssl_bio"
777 */
reconnect_to_server()778 void reconnect_to_server() {
779 if (current_phase == phase_ssl) {
780 cb::net::closesocket(sock_ssl);
781 destroy_ssl_socket();
782
783 sock_ssl = connect_to_server_ssl(ssl_port);
784 ASSERT_NE(INVALID_SOCKET, sock_ssl);
785 } else {
786 cb::net::closesocket(sock);
787 sock = connect_to_server_plain(port);
788 ASSERT_NE(INVALID_SOCKET, sock);
789 }
790 }
791
set_feature(cb::mcbp::Feature feature,bool enable)792 static void set_feature(cb::mcbp::Feature feature, bool enable) {
793 // First update the currently enabled features.
794 if (enable) {
795 enabled_hello_features.insert(feature);
796 } else {
797 enabled_hello_features.erase(feature);
798 }
799
800 BinprotHelloCommand command("testapp");
801 for (auto f : enabled_hello_features) {
802 command.enableFeature(f, true);
803 }
804
805 std::vector<uint8_t> blob;
806 command.encode(blob);
807
808 safe_send(blob);
809
810 blob.resize(0);
811 safe_recv_packet(blob);
812 const auto& response = *reinterpret_cast<cb::mcbp::Response*>(blob.data());
813 ASSERT_EQ(cb::mcbp::Status::Success, response.getStatus());
814
815 BinprotHelloResponse rsp;
816 rsp.assign(std::move(blob));
817
818 auto enabled = rsp.getFeatures();
819 EXPECT_EQ(enabled_hello_features.size(), enabled.size());
820 for (auto f : enabled_hello_features) {
821 EXPECT_NE(enabled.end(), std::find(enabled.begin(), enabled.end(), f));
822 }
823 }
824
set_datatype_feature(bool enable)825 void set_datatype_feature(bool enable) {
826 set_feature(cb::mcbp::Feature::JSON, enable);
827 set_feature(cb::mcbp::Feature::SNAPPY, enable);
828 }
829
fetch_value(const std::string & key)830 std::pair<cb::mcbp::Status, std::string> fetch_value(const std::string& key) {
831 std::vector<uint8_t> blob;
832 BinprotGetCommand cmd;
833 cmd.setKey(key);
834 cmd.encode(blob);
835 safe_send(blob);
836
837 blob.resize(0);
838 safe_recv_packet(blob);
839 BinprotGetResponse rsp;
840 rsp.assign(std::move(blob));
841 return std::make_pair(rsp.getStatus(), rsp.getDataString());
842 }
843
validate_datatype_is_json(const std::string & key,bool isJson)844 void validate_datatype_is_json(const std::string& key, bool isJson) {
845 std::vector<uint8_t> blob;
846 BinprotGetCommand cmd;
847 cmd.setKey(key);
848 cmd.encode(blob);
849 safe_send(blob.data(), blob.size(), false);
850
851 blob.resize(0);
852 safe_recv_packet(blob);
853 BinprotGetResponse rsp;
854 rsp.assign(std::move(blob));
855 ASSERT_EQ(cb::mcbp::Status::Success, rsp.getStatus());
856 EXPECT_EQ(isJson, rsp.getDatatype() & PROTOCOL_BINARY_DATATYPE_JSON);
857 }
858
validate_json_document(const std::string & key,const std::string & expected_value)859 void validate_json_document(const std::string& key,
860 const std::string& expected_value) {
861 auto pair = fetch_value(key);
862 EXPECT_EQ(cb::mcbp::Status::Success, pair.first);
863 const auto exp = nlohmann::json::parse(expected_value).dump();
864 const auto val = nlohmann::json::parse(pair.second).dump();
865 EXPECT_EQ(exp, val);
866 }
867
validate_flags(const std::string & key,uint32_t expected_flags)868 void validate_flags(const std::string& key, uint32_t expected_flags) {
869 std::vector<uint8_t> blob;
870 BinprotGetCommand cmd;
871 cmd.setKey(key);
872 cmd.encode(blob);
873 safe_send(blob);
874
875 blob.resize(0);
876 safe_recv_packet(blob);
877 BinprotGetResponse rsp;
878 rsp.assign(std::move(blob));
879 EXPECT_EQ(expected_flags, rsp.getDocumentFlags());
880 }
881
delete_object(const std::string & key,bool ignore_missing)882 void delete_object(const std::string& key, bool ignore_missing) {
883 std::vector<uint8_t> blob;
884 BinprotRemoveCommand cmd;
885 cmd.setKey(key);
886 cmd.encode(blob);
887 safe_send(blob);
888
889 blob.resize(0);
890 safe_recv_packet(blob);
891 BinprotRemoveResponse rsp;
892 rsp.assign(std::move(blob));
893
894 if (ignore_missing && rsp.getStatus() == cb::mcbp::Status::KeyEnoent) {
895 /* Ignore. Just using this for cleanup then */
896 return;
897 }
898 mcbp_validate_response_header(
899 const_cast<cb::mcbp::Response&>(rsp.getResponse()),
900 cb::mcbp::ClientOpcode::Delete,
901 cb::mcbp::Status::Success);
902 }
903
start_memcached_server()904 void TestappTest::start_memcached_server() {
905 config_file = cb::io::mktemp("memcached_testapp.json");
906 write_config_to_file(memcached_cfg.dump(2), config_file);
907
908 server_start_time = time(0);
909
910 if (embedded_memcached_server) {
911 spawn_embedded_server();
912 } else {
913 start_external_server();
914 }
915 parse_portnumber_file();
916 }
917
store_object_w_datatype(const std::string & key,cb::const_char_buffer value,uint32_t flags,uint32_t expiration,cb::mcbp::Datatype datatype)918 void store_object_w_datatype(const std::string& key,
919 cb::const_char_buffer value,
920 uint32_t flags,
921 uint32_t expiration,
922 cb::mcbp::Datatype datatype) {
923 cb::mcbp::request::MutationPayload extras;
924 static_assert(sizeof(extras) == 8, "Unexpected extras size");
925 extras.setFlags(flags);
926 extras.setExpiration(expiration);
927
928 std::vector<uint8_t> buffer(sizeof(cb::mcbp::Request) + sizeof(extras) +
929 key.size() + value.size());
930 cb::mcbp::FrameBuilder<cb::mcbp::Request> builder(
931 {buffer.data(), buffer.size()});
932
933 builder.setMagic(cb::mcbp::Magic::ClientRequest);
934 builder.setOpcode(cb::mcbp::ClientOpcode::Set);
935 builder.setDatatype(datatype);
936 builder.setOpaque(0xdeadbeef);
937 builder.setExtras(extras.getBuffer());
938 builder.setKey({reinterpret_cast<const uint8_t*>(key.data()), key.size()});
939 builder.setValue(
940 {reinterpret_cast<const uint8_t*>(value.data()), value.size()});
941
942 safe_send(builder.getFrame()->getFrame());
943
944 std::vector<uint8_t> blob;
945 safe_recv_packet(blob);
946 mcbp_validate_response_header(
947 *reinterpret_cast<cb::mcbp::Response*>(blob.data()),
948 cb::mcbp::ClientOpcode::Set,
949 cb::mcbp::Status::Success);
950 }
951
store_document(const std::string & key,const std::string & value,uint32_t flags,uint32_t exptime,bool compress)952 void store_document(const std::string& key,
953 const std::string& value,
954 uint32_t flags,
955 uint32_t exptime,
956 bool compress) {
957 if (compress) {
958 bool disable_snappy = false;
959 if (enabled_hello_features.count(cb::mcbp::Feature::SNAPPY) == 0) {
960 // We need to enable snappy
961 set_feature(cb::mcbp::Feature::SNAPPY, true);
962 disable_snappy = true;
963 }
964 cb::compression::Buffer deflated;
965 cb::compression::deflate(
966 cb::compression::Algorithm::Snappy, value, deflated);
967
968 store_object_w_datatype(key.c_str(),
969 deflated,
970 flags,
971 exptime,
972 cb::mcbp::Datatype::Snappy);
973 if (disable_snappy) {
974 set_feature(cb::mcbp::Feature::SNAPPY, false);
975 }
976 } else {
977 store_object_w_datatype(
978 key.c_str(), value, flags, exptime, cb::mcbp::Datatype::Raw);
979 }
980 }
981
set_json_feature(bool enable)982 void set_json_feature(bool enable) {
983 set_feature(cb::mcbp::Feature::JSON, enable);
984 }
985
set_mutation_seqno_feature(bool enable)986 void set_mutation_seqno_feature(bool enable) {
987 set_feature(cb::mcbp::Feature::MUTATION_SEQNO, enable);
988 }
989
set_xerror_feature(bool enable)990 void set_xerror_feature(bool enable) {
991 set_feature(cb::mcbp::Feature::XERROR, enable);
992 }
993
waitForShutdown(bool killed)994 void TestappTest::waitForShutdown(bool killed) {
995 #ifdef WIN32
996 ASSERT_EQ(WAIT_OBJECT_0,
997 WaitForSingleObject(pidTToHandle(server_pid), 60000));
998 DWORD exit_code = NULL;
999 GetExitCodeProcess(pidTToHandle(server_pid), &exit_code);
1000 EXPECT_EQ(0, exit_code);
1001 #else
1002 int status;
1003 pid_t ret;
1004 while (true) {
1005 ret = waitpid(server_pid, &status, 0);
1006 if (ret == reinterpret_cast<pid_t>(-1) && errno == EINTR) {
1007 // Just loop again
1008 continue;
1009 }
1010 break;
1011 }
1012 ASSERT_NE(reinterpret_cast<pid_t>(-1), ret)
1013 << "waitpid failed: " << strerror(errno);
1014 bool correctShutdown = killed ? WIFSIGNALED(status) : WIFEXITED(status);
1015 EXPECT_TRUE(correctShutdown)
1016 << "waitpid status : " << status << std::endl
1017 << "WIFEXITED(status) : " << WIFEXITED(status) << std::endl
1018 << "WEXITSTATUS(status): " << WEXITSTATUS(status) << std::endl
1019 << "WIFSIGNALED(status): " << WIFSIGNALED(status) << std::endl
1020 << "WTERMSIG(status) : " << WTERMSIG(status) << " ("
1021 << strsignal(WTERMSIG(status)) << ")" << std::endl
1022 << "WCOREDUMP(status) : " << WCOREDUMP(status) << std::endl;
1023 EXPECT_EQ(0, WEXITSTATUS(status));
1024 #endif
1025 server_pid = pid_t(-1);
1026 }
1027
stop_memcached_server()1028 void TestappTest::stop_memcached_server() {
1029 connectionMap.invalidate();
1030 if (sock != INVALID_SOCKET) {
1031 cb::net::closesocket(sock);
1032 sock = INVALID_SOCKET;
1033 }
1034
1035 if (embedded_memcached_server) {
1036 shutdown_server();
1037 cb_join_thread(memcached_server_thread);
1038 }
1039
1040 if (server_pid != pid_t(-1)) {
1041 #ifdef WIN32
1042 TerminateProcess(pidTToHandle(server_pid), 0);
1043 waitForShutdown();
1044 #else
1045 if (kill(server_pid, SIGTERM) == 0) {
1046 waitForShutdown();
1047 }
1048 #endif
1049 }
1050
1051 if (!config_file.empty()) {
1052 EXPECT_NE(-1, remove(config_file.c_str()));
1053 config_file.clear();
1054 }
1055 }
1056
socket_send(SOCKET s,const char * buf,size_t len)1057 ssize_t socket_send(SOCKET s, const char* buf, size_t len) {
1058 return cb::net::send(s, buf, len, 0);
1059 }
1060
phase_send(const void * buf,size_t len)1061 static ssize_t phase_send(const void* buf, size_t len) {
1062 if (current_phase == phase_ssl) {
1063 return phase_send_ssl(buf, len);
1064 } else {
1065 return socket_send(sock, reinterpret_cast<const char*>(buf), len);
1066 }
1067 }
1068
socket_recv(SOCKET s,char * buf,size_t len)1069 ssize_t socket_recv(SOCKET s, char* buf, size_t len) {
1070 return cb::net::recv(s, buf, len, 0);
1071 }
1072
phase_recv(void * buf,size_t len)1073 ssize_t phase_recv(void* buf, size_t len) {
1074 if (current_phase == phase_ssl) {
1075 return phase_recv_ssl(buf, len);
1076 } else {
1077 return socket_recv(sock, reinterpret_cast<char*>(buf), len);
1078 }
1079 }
1080
1081 static bool dump_socket_traffic = getenv("TESTAPP_PACKET_DUMP") != nullptr;
1082
phase_get_errno()1083 static const std::string phase_get_errno() {
1084 if (current_phase == phase_ssl) {
1085 /* could do with more work here, but so far this has sufficed */
1086 return "SSL error";
1087 }
1088 return cb_strerror();
1089 }
1090
safe_send(const void * buf,size_t len,bool hickup)1091 void safe_send(const void* buf, size_t len, bool hickup) {
1092 size_t offset = 0;
1093 const char* ptr = reinterpret_cast<const char*>(buf);
1094 do {
1095 size_t num_bytes = len - offset;
1096 ssize_t nw;
1097 if (hickup) {
1098 if (num_bytes > 1024) {
1099 num_bytes = (rand() % 1023) + 1;
1100 }
1101 }
1102
1103 nw = phase_send(ptr + offset, num_bytes);
1104
1105 if (nw == -1) {
1106 if (errno != EINTR) {
1107 fprintf(stderr,
1108 "Failed to write: %s\n",
1109 phase_get_errno().c_str());
1110 print_backtrace_to_file(stderr);
1111 abort();
1112 }
1113 } else {
1114 if (hickup) {
1115 std::this_thread::sleep_for(std::chrono::microseconds(100));
1116 }
1117
1118 if (dump_socket_traffic) {
1119 if (current_phase == phase_ssl) {
1120 std::cerr << "SSL";
1121 } else {
1122 std::cerr << "PLAIN";
1123 }
1124 std::cerr << "> ";
1125 for (ssize_t ii = 0; ii < nw; ++ii) {
1126 std::cerr << "0x" << std::hex << std::setfill('0')
1127 << std::setw(2)
1128 << uint32_t(*(uint8_t*)(ptr + offset + ii))
1129 << ", ";
1130 }
1131 std::cerr << std::dec << std::endl;
1132 }
1133 offset += nw;
1134 }
1135 } while (offset < len);
1136 }
1137
safe_recv(void * buf,size_t len)1138 bool safe_recv(void* buf, size_t len) {
1139 size_t offset = 0;
1140 if (len == 0) {
1141 return true;
1142 }
1143 do {
1144 ssize_t nr = phase_recv(((char*)buf) + offset, len - offset);
1145
1146 if (nr == -1) {
1147 EXPECT_EQ(EINTR, errno) << "Failed to read: " << phase_get_errno();
1148 } else {
1149 if (nr == 0 && allow_closed_read) {
1150 return false;
1151 }
1152 EXPECT_NE(0u, nr);
1153 offset += nr;
1154 }
1155
1156 // Give up if we encountered an error.
1157 if (::testing::Test::HasFailure()) {
1158 return false;
1159 }
1160 } while (offset < len);
1161
1162 return true;
1163 }
1164
1165 /**
1166 * Internal function which receives a packet. The type parameter should have
1167 * these three functions:
1168 * - resize(n) ->: ensure the buffer has a total capacity for at least n bytes.
1169 * This function will usually be called once for the header size (i.e. 24)
1170 * and then another time for the total packet size (i.e. 24 + bodylen).
1171 * - data() -> char*: get the entire buffer
1172 * - size() -> size_t: get the current size of the buffer
1173 *
1174 * @param info an object conforming to the above.
1175 *
1176 * Once the function has completed, it will return true if no read errors
1177 * occurred. The actual size of the packet can be determined by parsing the
1178 * packet header.
1179 *
1180 * See StaticBufInfo which is an implementation that uses a fixed buffer.
1181 * std::vector naturally conforms to the interface.
1182 */
1183 template <typename T>
safe_recv_packetT(T & info)1184 bool safe_recv_packetT(T& info) {
1185 info.resize(sizeof(cb::mcbp::Response));
1186 auto* header = reinterpret_cast<cb::mcbp::Response*>(info.data());
1187
1188 if (!safe_recv(header, sizeof(*header))) {
1189 return false;
1190 }
1191
1192 auto bodylen = header->getBodylen();
1193
1194 // Set response to NULL, because the underlying buffer may change.
1195 header = nullptr;
1196
1197 info.resize(sizeof(*header) + bodylen);
1198 auto ret = safe_recv(info.data() + sizeof(*header), bodylen);
1199
1200 if (dump_socket_traffic) {
1201 if (dump_socket_traffic) {
1202 if (current_phase == phase_ssl) {
1203 std::cerr << "SSL";
1204 } else {
1205 std::cerr << "PLAIN";
1206 }
1207 std::cerr << "< ";
1208 for (const auto& val : info) {
1209 std::cerr << cb::to_hex(uint8_t(val)) << ", ";
1210 }
1211 }
1212 std::cerr << std::endl;
1213 }
1214 return ret;
1215 }
1216
1217 /**
1218 * Wrapper for a existing buffer which exposes an API suitable for use with
1219 * safe_recv_packetT()
1220 */
1221 struct StaticBufInfo {
StaticBufInfoStaticBufInfo1222 StaticBufInfo(void* buf_, size_t len_)
1223 : buf(reinterpret_cast<char*>(buf_)), len(len_) {
1224 }
sizeStaticBufInfo1225 size_t size(size_t) const {
1226 return len;
1227 }
dataStaticBufInfo1228 char* data() {
1229 return buf;
1230 }
1231
resizeStaticBufInfo1232 void resize(size_t n) {
1233 if (n > len) {
1234 throw std::runtime_error("Cannot enlarge buffer!");
1235 }
1236 }
1237
beginStaticBufInfo1238 constexpr char* begin() const {
1239 return buf;
1240 }
1241
endStaticBufInfo1242 constexpr char* end() const {
1243 return buf + len;
1244 }
1245
1246 char* buf;
1247 const size_t len;
1248 };
1249
safe_recv_packet(void * buf,size_t size)1250 bool safe_recv_packet(void* buf, size_t size) {
1251 StaticBufInfo info(buf, size);
1252 return safe_recv_packetT(info);
1253 }
1254
safe_recv_packet(std::vector<uint8_t> & buf)1255 bool safe_recv_packet(std::vector<uint8_t>& buf) {
1256 return safe_recv_packetT(buf);
1257 }
1258
safe_recv_packet(std::vector<char> & buf)1259 bool safe_recv_packet(std::vector<char>& buf) {
1260 return safe_recv_packetT(buf);
1261 }
1262
1263 // Configues the ewouldblock_engine to use the given mode; value
1264 // is a mode-specific parameter.
ewouldblock_engine_configure(ENGINE_ERROR_CODE err_code,const EWBEngineMode & mode,uint32_t value,const std::string & key)1265 void TestappTest::ewouldblock_engine_configure(ENGINE_ERROR_CODE err_code,
1266 const EWBEngineMode& mode,
1267 uint32_t value,
1268 const std::string& key) {
1269 cb::mcbp::request::EWB_Payload payload;
1270 payload.setMode(static_cast<uint32_t>(mode));
1271 payload.setValue(value);
1272 payload.setInjectError(static_cast<uint32_t>(err_code));
1273
1274 std::vector<uint8_t> buffer(sizeof(cb::mcbp::Request) +
1275 sizeof(cb::mcbp::request::EWB_Payload) +
1276 key.size());
1277 cb::mcbp::RequestBuilder builder({buffer.data(), buffer.size()});
1278 builder.setMagic(cb::mcbp::Magic::ClientRequest);
1279 builder.setOpcode(cb::mcbp::ClientOpcode::EwouldblockCtl);
1280 builder.setOpaque(0xdeadbeef);
1281 builder.setExtras(
1282 {reinterpret_cast<const uint8_t*>(&payload), sizeof(payload)});
1283 builder.setKey({reinterpret_cast<const uint8_t*>(key.data()), key.size()});
1284 safe_send(buffer);
1285
1286 buffer.resize(1024);
1287 safe_recv_packet(buffer.data(), buffer.size());
1288 mcbp_validate_response_header(
1289 *reinterpret_cast<cb::mcbp::Response*>(buffer.data()),
1290 cb::mcbp::ClientOpcode::EwouldblockCtl,
1291 cb::mcbp::Status::Success);
1292 }
1293
ewouldblock_engine_configure(std::vector<cb::engine_errc> sequence)1294 void TestappTest::ewouldblock_engine_configure(
1295 std::vector<cb::engine_errc> sequence) {
1296 ewouldblock_engine_configure(ENGINE_SUCCESS,
1297 EWBEngineMode::Sequence,
1298 0,
1299 ewb::encodeSequence(sequence));
1300 }
1301
ewouldblock_engine_disable()1302 void TestappTest::ewouldblock_engine_disable() {
1303 // Value for err_code doesn't matter...
1304 ewouldblock_engine_configure(ENGINE_EWOULDBLOCK, EWBEngineMode::Next_N, 0);
1305 }
1306
reconfigure()1307 void TestappTest::reconfigure() {
1308 write_config_to_file(memcached_cfg.dump(2), config_file);
1309 auto& conn = getAdminConnection();
1310
1311 BinprotGenericCommand req{cb::mcbp::ClientOpcode::ConfigReload, {}, {}};
1312 const auto resp = conn.execute(req);
1313 ASSERT_TRUE(resp.isSuccess()) << "Failed to reconfigure the server";
1314 conn.reconnect();
1315 }
1316
runCreateXattr(const std::string & path,const std::string & value,bool macro,cb::mcbp::Status expectedStatus)1317 void TestappTest::runCreateXattr(const std::string& path,
1318 const std::string& value,
1319 bool macro,
1320 cb::mcbp::Status expectedStatus) {
1321 auto& connection = getConnection();
1322
1323 BinprotSubdocCommand cmd;
1324 cmd.setOp(cb::mcbp::ClientOpcode::SubdocDictAdd);
1325 cmd.setKey(name);
1326 cmd.setPath(path);
1327 cmd.setValue(value);
1328 if (macro) {
1329 cmd.addPathFlags(SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_EXPAND_MACROS |
1330 SUBDOC_FLAG_MKDIR_P);
1331 } else {
1332 cmd.addPathFlags(SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
1333 }
1334
1335 connection.sendCommand(cmd);
1336
1337 BinprotResponse resp;
1338 connection.recvResponse(resp);
1339 EXPECT_EQ(expectedStatus, resp.getStatus());
1340 }
1341
createXattr(const std::string & path,const std::string & value,bool macro)1342 void TestappTest::createXattr(const std::string& path,
1343 const std::string& value,
1344 bool macro) {
1345 runCreateXattr(path, value, macro, cb::mcbp::Status::Success);
1346 }
1347
runGetXattr(const std::string & path,bool deleted,cb::mcbp::Status expectedStatus)1348 BinprotSubdocResponse TestappTest::runGetXattr(
1349 const std::string& path,
1350 bool deleted,
1351 cb::mcbp::Status expectedStatus) {
1352 auto& connection = getConnection();
1353
1354 BinprotSubdocCommand cmd;
1355 cmd.setOp(cb::mcbp::ClientOpcode::SubdocGet);
1356 cmd.setKey(name);
1357 cmd.setPath(path);
1358 if (deleted) {
1359 cmd.addPathFlags(SUBDOC_FLAG_XATTR_PATH);
1360 cmd.addDocFlags(mcbp::subdoc::doc_flag::AccessDeleted);
1361 } else {
1362 cmd.addPathFlags(SUBDOC_FLAG_XATTR_PATH);
1363 }
1364 connection.sendCommand(cmd);
1365
1366 BinprotSubdocResponse resp;
1367 connection.recvResponse(resp);
1368 auto status = resp.getStatus();
1369 if (deleted && status == cb::mcbp::Status::SubdocSuccessDeleted) {
1370 status = cb::mcbp::Status::Success;
1371 }
1372
1373 if (status != expectedStatus) {
1374 throw ConnectionError("runGetXattr() failed: ", resp);
1375 }
1376 return resp;
1377 }
1378
getXattr(const std::string & path,bool deleted)1379 BinprotSubdocResponse TestappTest::getXattr(const std::string& path,
1380 bool deleted) {
1381 return runGetXattr(path, deleted, cb::mcbp::Status::Success);
1382 }
1383
getResponseCount(cb::mcbp::Status statusCode)1384 int TestappTest::getResponseCount(cb::mcbp::Status statusCode) {
1385 auto stats = getConnection().stats("responses detailed");
1386 auto responses = stats["responses"];
1387 std::stringstream stream;
1388 stream << std::hex << uint16_t(statusCode);
1389 auto obj = responses.find(stream.str());
1390 if (obj == responses.end()) {
1391 return 0;
1392 }
1393
1394 return gsl::narrow<int>(obj->get<size_t>());
1395 }
1396
expectedJSONDatatype() const1397 cb::mcbp::Datatype TestappTest::expectedJSONDatatype() const {
1398 return hasJSONSupport() == ClientJSONSupport::Yes ? cb::mcbp::Datatype::JSON
1399 : cb::mcbp::Datatype::Raw;
1400 }
1401
getConnection()1402 MemcachedConnection& TestappTest::getConnection() {
1403 // The basic tests should use a plain IPv4 unless something else is
1404 // required (this makes the return value of getConnection predictable
1405 // (rather than returning whatever happened to be stored in "front" of
1406 // the map.
1407 return prepare(connectionMap.getConnection(false, AF_INET));
1408 }
1409
getAdminConnection()1410 MemcachedConnection& TestappTest::getAdminConnection() {
1411 auto& conn = getConnection();
1412 conn.authenticate("@admin", "password", conn.getSaslMechanisms());
1413 return conn;
1414 }
1415
prepare(MemcachedConnection & connection)1416 MemcachedConnection& TestappTest::prepare(MemcachedConnection& connection) {
1417 std::vector<cb::mcbp::Feature> features = {
1418 {cb::mcbp::Feature::MUTATION_SEQNO,
1419 cb::mcbp::Feature::XATTR,
1420 cb::mcbp::Feature::XERROR,
1421 cb::mcbp::Feature::SELECT_BUCKET}};
1422 if (hasSnappySupport() == ClientSnappySupport::Yes) {
1423 features.push_back(cb::mcbp::Feature::SNAPPY);
1424 }
1425
1426 if (hasJSONSupport() == ClientJSONSupport::Yes) {
1427 features.push_back(cb::mcbp::Feature::JSON);
1428 }
1429
1430 connection.reconnect();
1431 connection.setFeatures("testapp", features);
1432 return connection;
1433 }
1434
1435 nlohmann::json TestappTest::memcached_cfg;
1436 std::string TestappTest::portnumber_file;
1437 std::string TestappTest::config_file;
1438 ConnectionMap TestappTest::connectionMap;
1439 uint64_t TestappTest::token;
1440 cb_thread_t TestappTest::memcached_server_thread;
1441
main(int argc,char ** argv)1442 int main(int argc, char** argv) {
1443 // We need to set MEMCACHED_UNIT_TESTS to enable the use of
1444 // the ewouldblock engine..
1445 static char envvar[80];
1446 snprintf(envvar, sizeof(envvar), "MEMCACHED_UNIT_TESTS=true");
1447 putenv(envvar);
1448
1449 setupWindowsDebugCRTAssertHandling();
1450
1451 ::testing::InitGoogleTest(&argc, argv);
1452
1453 #ifndef WIN32
1454 /*
1455 ** When running the tests from within CLion it starts the test in
1456 ** another directory than pwd. This cause us to fail to locate the
1457 ** memcached binary to start. To work around that lets just do a
1458 ** chdir(dirname(argv[0])).
1459 */
1460 auto testdir = cb::io::dirname(argv[0]);
1461 if (chdir(testdir.c_str()) != 0) {
1462 std::cerr << "Failed to change directory to " << testdir << std::endl;
1463 exit(EXIT_FAILURE);
1464 }
1465 #endif
1466
1467 std::string engine_name("default");
1468 std::string engine_config;
1469
1470 int cmd;
1471 while ((cmd = getopt(argc, argv, "vc:eE:")) != EOF) {
1472 switch (cmd) {
1473 case 'v':
1474 memcached_verbose++;
1475 break;
1476 case 'c':
1477 engine_config = optarg;
1478 break;
1479 case 'e':
1480 embedded_memcached_server = true;
1481 break;
1482 case 'E':
1483 engine_name.assign(optarg);
1484 break;
1485 default:
1486 std::cerr << "Usage: " << argv[0] << " [-v] [-e]" << std::endl
1487 << std::endl
1488 << " -v Verbose - Print verbose memcached output "
1489 << "to stderr." << std::endl
1490 << " (use multiple times to increase the"
1491 << " verbosity level." << std::endl
1492 << " -c CONFIG - Additional configuration to pass to "
1493 << "bucket creation." << std::endl
1494 << " -e Embedded - Run the memcached daemon in the "
1495 << "same process (for debugging only..)" << std::endl
1496 << " -E ENGINE engine type to use. <default|ep>"
1497 << std::endl;
1498 return 1;
1499 }
1500 }
1501
1502 /*
1503 * If not running in embedded mode we need the McdEnvironment to manageSSL
1504 * initialization and shutdown.
1505 */
1506 mcd_env = new McdEnvironment(
1507 !embedded_memcached_server, engine_name, engine_config);
1508
1509 ::testing::AddGlobalTestEnvironment(mcd_env);
1510
1511 cb_initialize_sockets();
1512
1513 #if !defined(WIN32)
1514 /*
1515 * When shutting down SSL connections the SSL layer may attempt to
1516 * write to the underlying socket. If the socket has been closed
1517 * on the server side then this will raise a SIGPIPE (and
1518 * terminate the test program). This is Bad.
1519 * Therefore ignore SIGPIPE signals; we can use errno == EPIPE if
1520 * we need that information.
1521 */
1522 if (sigignore(SIGPIPE) == -1) {
1523 std::cerr << "Fatal: failed to ignore SIGPIPE; sigaction" << std::endl;
1524 return 1;
1525 }
1526 #endif
1527
1528 return RUN_ALL_TESTS();
1529 }
1530
1531 /* Request stats
1532 * @return a map of stat key & values in the server response.
1533 */
request_stats()1534 stats_response_t request_stats() {
1535 BinprotGenericCommand cmd(cb::mcbp::ClientOpcode::Stat);
1536 std::vector<uint8_t> blob;
1537 cmd.encode(blob);
1538 safe_send(blob);
1539
1540 stats_response_t result;
1541 while (true) {
1542 safe_recv_packet(blob);
1543 BinprotResponse rsp;
1544 rsp.assign(std::move(blob));
1545 mcbp_validate_response_header(
1546 const_cast<cb::mcbp::Response&>(rsp.getResponse()),
1547 cb::mcbp::ClientOpcode::Stat,
1548 cb::mcbp::Status::Success);
1549 // key length zero indicates end of the stats.
1550 if (rsp.getKeyString().empty()) {
1551 break;
1552 }
1553
1554 result.insert(std::make_pair(rsp.getKeyString(), rsp.getDataString()));
1555 }
1556
1557 return result;
1558 }
1559
1560 // Extracts a single statistic from the set of stats, returning as
1561 // a uint64_t
extract_single_stat(const stats_response_t & stats,const char * name)1562 uint64_t extract_single_stat(const stats_response_t& stats, const char* name) {
1563 auto iter = stats.find(name);
1564 EXPECT_NE(stats.end(), iter);
1565 uint64_t result = 0;
1566 result = std::stoul(iter->second);
1567 return result;
1568 }
1569
1570 /*
1571 Using a memcached protocol extesnsion, shift the time
1572 */
adjust_memcached_clock(int64_t clock_shift,cb::mcbp::request::AdjustTimePayload::TimeType timeType)1573 void adjust_memcached_clock(
1574 int64_t clock_shift,
1575 cb::mcbp::request::AdjustTimePayload::TimeType timeType) {
1576 cb::mcbp::request::AdjustTimePayload payload;
1577 payload.setOffset(uint64_t(clock_shift));
1578 payload.setTimeType(timeType);
1579
1580 std::vector<uint8_t> blob(sizeof(cb::mcbp::Request) + sizeof(payload));
1581 cb::mcbp::FrameBuilder<cb::mcbp::Request> builder(
1582 {blob.data(), blob.size()});
1583 builder.setMagic(cb::mcbp::Magic::ClientRequest);
1584 builder.setOpcode(cb::mcbp::ClientOpcode::AdjustTimeofday);
1585 builder.setExtras(payload.getBuffer());
1586 builder.setOpaque(0xdeadbeef);
1587 safe_send(builder.getFrame()->getFrame());
1588
1589 blob.resize(0);
1590 safe_recv_packet(blob);
1591 BinprotResponse rsp;
1592 rsp.assign(std::move(blob));
1593 mcbp_validate_response_header(
1594 const_cast<cb::mcbp::Response&>(rsp.getResponse()),
1595 cb::mcbp::ClientOpcode::AdjustTimeofday,
1596 cb::mcbp::Status::Success);
1597 }
1598