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 #include "config.h"
18 #include <gtest/gtest.h>
19 #include <libcouchbase/couchbase.h>
20 #include <libcouchbase/api3.h>
21 #include <mocksupport/server.h>
22 #include "mock-environment.h"
23 #include <sstream>
24 #include "internal.h" /* settings from lcb_t for logging */
25
26 #define LOGARGS(instance, lvl) instance->settings, "tests-ENV", LCB_LOG_##lvl, __FILE__, __LINE__
27
28 MockEnvironment *MockEnvironment::instance;
29
getInstance(void)30 MockEnvironment *MockEnvironment::getInstance(void)
31 {
32 if (instance == NULL) {
33 instance = new MockEnvironment;
34 }
35 return instance;
36 }
37
Reset()38 void MockEnvironment::Reset()
39 {
40 if (instance != NULL) {
41 instance->TearDown();
42 instance->SetUp();
43 }
44 }
45
init()46 void MockEnvironment::init()
47 {
48 mock = NULL;
49 http = NULL;
50 innerClient = NULL;
51 argv = NULL;
52 iops = NULL;
53
54 numNodes = 4;
55 realCluster = false;
56 serverVersion = VERSION_UNKNOWN;
57 }
58
MockEnvironment()59 MockEnvironment::MockEnvironment()
60 {
61 init();
62 }
63
MockEnvironment(const char ** args,std::string bucketname)64 MockEnvironment::MockEnvironment(const char **args, std::string bucketname)
65 {
66 init();
67 this->argv = args;
68 this->bucketName = bucketname;
69 SetUp();
70 }
71
failoverNode(int index,std::string bucket,bool rebalance)72 void MockEnvironment::failoverNode(int index, std::string bucket, bool rebalance)
73 {
74 MockBucketCommand bCmd(MockCommand::FAILOVER, index, bucket);
75 bCmd.set("rebalance", rebalance);
76 sendCommand(bCmd);
77 getResponse();
78 }
79
respawnNode(int index,std::string bucket)80 void MockEnvironment::respawnNode(int index, std::string bucket)
81 {
82 MockBucketCommand bCmd(MockCommand::RESPAWN, index, bucket);
83 sendCommand(bCmd);
84 getResponse();
85 }
86
hiccupNodes(int msecs,int offset)87 void MockEnvironment::hiccupNodes(int msecs, int offset)
88 {
89 MockCommand cmd(MockCommand::HICCUP);
90 cmd.set("msecs", msecs);
91 cmd.set("offset", offset);
92 sendCommand(cmd);
93 getResponse();
94 }
95
regenVbCoords(std::string bucket)96 void MockEnvironment::regenVbCoords(std::string bucket) {
97 MockBucketCommand bCmd(MockCommand::REGEN_VBCOORDS, 0, bucket);
98 MockResponse r;
99 sendCommand(bCmd);
100 getResponse(r);
101 EXPECT_TRUE(r.isOk());
102 }
103
getMcPorts(std::string bucket)104 std::vector<int> MockEnvironment::getMcPorts(std::string bucket)
105 {
106 MockCommand cmd(MockCommand::GET_MCPORTS);
107 if (!bucket.empty()) {
108 cmd.set("bucket", bucket);
109 }
110
111 sendCommand(cmd);
112 MockResponse resp;
113 getResponse(resp);
114 EXPECT_TRUE(resp.isOk());
115 const Json::Value& payload = resp.constResp()["payload"];
116
117 std::vector<int> ret;
118
119 for (int ii = 0; ii < (int)payload.size(); ii++) {
120 ret.push_back(payload[ii].asInt());
121 }
122 return ret;
123 }
124
setSaslMechs(std::vector<std::string> & mechanisms,std::string bucket,const std::vector<int> * nodes)125 void MockEnvironment::setSaslMechs(std::vector<std::string>& mechanisms, std::string bucket,
126 const std::vector<int>* nodes)
127 {
128 MockCommand cmd(MockCommand::SET_SASL_MECHANISMS);
129 Json::Value mechs(Json::arrayValue);
130 for (std::vector<std::string>::const_iterator ii = mechanisms.begin(); ii != mechanisms.end(); ii++) {
131 mechs.append(*ii);
132 }
133 cmd.set("mechs", mechs);
134
135 if (!bucket.empty()) {
136 cmd.set("bucket", bucket);
137 }
138
139 if (nodes != NULL) {
140 const std::vector<int>& v = *nodes;
141 Json::Value array(Json::arrayValue);
142
143 for (std::vector<int>::const_iterator ii = v.begin(); ii != v.end(); ii++) {
144 array.append(*ii);
145 }
146
147 cmd.set("servers", array);
148 }
149
150 sendCommand(cmd);
151 getResponse();
152 }
153
setCCCP(bool enabled,std::string bucket,const std::vector<int> * nodes)154 void MockEnvironment::setCCCP(bool enabled, std::string bucket,
155 const std::vector<int>* nodes)
156 {
157 MockCommand cmd(MockCommand::SET_CCCP);
158 cmd.set("enabled", enabled);
159
160 if (!bucket.empty()) {
161 cmd.set("bucket", bucket);
162 }
163
164 if (nodes != NULL) {
165 const std::vector<int>& v = *nodes;
166 Json::Value array(Json::arrayValue);
167
168 for (std::vector<int>::const_iterator ii = v.begin(); ii != v.end(); ii++) {
169 array.append(*ii);
170 }
171
172 cmd.set("servers", array);
173 }
174
175 sendCommand(cmd);
176 getResponse();
177 }
178
setEnhancedErrors(bool enabled,std::string bucket,const std::vector<int> * nodes)179 void MockEnvironment::setEnhancedErrors(bool enabled, std::string bucket,
180 const std::vector<int>* nodes)
181 {
182 MockCommand cmd(MockCommand::SET_ENHANCED_ERRORS);
183 cmd.set("enabled", enabled);
184
185 if (!bucket.empty()) {
186 cmd.set("bucket", bucket);
187 }
188
189 if (nodes != NULL) {
190 const std::vector<int>& v = *nodes;
191 Json::Value array(Json::arrayValue);
192
193 for (std::vector<int>::const_iterator ii = v.begin(); ii != v.end(); ii++) {
194 array.append(*ii);
195 }
196
197 cmd.set("servers", array);
198 }
199
200 sendCommand(cmd);
201 getResponse();
202 }
203
setCompression(std::string mode,std::string bucket,const std::vector<int> * nodes)204 void MockEnvironment::setCompression(std::string mode, std::string bucket,
205 const std::vector<int>* nodes)
206 {
207 MockCommand cmd(MockCommand::SET_COMPRESSION);
208 cmd.set("mode", mode);
209
210 if (!bucket.empty()) {
211 cmd.set("bucket", bucket);
212 }
213
214 if (nodes != NULL) {
215 const std::vector<int>& v = *nodes;
216 Json::Value array(Json::arrayValue);
217
218 for (std::vector<int>::const_iterator ii = v.begin(); ii != v.end(); ii++) {
219 array.append(*ii);
220 }
221
222 cmd.set("servers", array);
223 }
224
225 sendCommand(cmd);
226 getResponse();
227 }
228
getKeyInfo(std::string key,std::string bucket)229 const Json::Value MockEnvironment::getKeyInfo(std::string key, std::string bucket)
230 {
231 MockKeyCommand cmd(MockCommand::KEYINFO, key);
232 cmd.bucket = bucket;
233 sendCommand(cmd);
234 MockResponse resp;
235 getResponse(resp);
236 return resp.constResp()["payload"];
237 }
238
sendCommand(MockCommand & cmd)239 void MockEnvironment::sendCommand(MockCommand &cmd)
240 {
241 std::string s = cmd.encode();
242 lcb_ssize_t nw = send(mock->client, s.c_str(), (unsigned long)s.size(), 0);
243 assert(nw == s.size());
244 }
245
getResponse(MockResponse & ret)246 void MockEnvironment::getResponse(MockResponse& ret)
247 {
248 std::string rbuf;
249 do {
250 char c;
251 int rv = recv(mock->client, &c, 1, 0);
252 assert(rv == 1);
253 if (c == '\n') {
254 break;
255 }
256 rbuf += c;
257 } while (true);
258
259 ret.assign(rbuf);
260 if (!ret.isOk()) {
261 std::cerr << "Mock command failed!" << std::endl;
262 std::cerr << ret.constResp()["error"].asString() << std::endl;
263 std::cerr << ret;
264 }
265 }
266
postCreate(lcb_t instance)267 void MockEnvironment::postCreate(lcb_t instance)
268 {
269 lcb_error_t err;
270 if (!isRealCluster()) {
271 lcb_HTCONFIG_URLTYPE urltype = LCB_HTCONFIG_URLTYPE_COMPAT;
272 err = lcb_cntl(instance, LCB_CNTL_SET, LCB_CNTL_HTCONFIG_URLTYPE, &urltype);
273 ASSERT_EQ(LCB_SUCCESS, err);
274 }
275 err = lcb_cntl_string(instance, "fetch_mutation_tokens", "true");
276 ASSERT_EQ(LCB_SUCCESS, err);
277 }
278
279 void
createConnection(HandleWrap & handle,lcb_t & instance,const lcb_create_st & user_options)280 MockEnvironment::createConnection(HandleWrap &handle,
281 lcb_t& instance, const lcb_create_st &user_options)
282 {
283 lcb_io_opt_t io;
284 lcb_create_st options;
285 memcpy(&options, &user_options, sizeof user_options);
286
287 if (lcb_create_io_ops(&io, NULL) != LCB_SUCCESS) {
288 fprintf(stderr, "Failed to create IO instance\n");
289 exit(1);
290 }
291
292 options.v.v2.io = io;
293 lcb_error_t err = lcb_create(&instance, &options);
294 ASSERT_EQ(LCB_SUCCESS, err);
295 postCreate(instance);
296
297 (void)lcb_set_cookie(instance, io);
298
299 handle.instance = instance;
300 handle.iops = io;
301 }
302
createConnection(HandleWrap & handle,lcb_t & instance)303 void MockEnvironment::createConnection(HandleWrap &handle, lcb_t &instance)
304 {
305 lcb_create_st options;
306 makeConnectParams(options, NULL);
307 createConnection(handle, instance, options);
308 }
309
createConnection(lcb_t & instance)310 void MockEnvironment::createConnection(lcb_t &instance)
311 {
312 HandleWrap handle;
313 createConnection(handle, instance);
314
315 handle.iops->v.base.need_cleanup = 1;
316 handle.instance = NULL;
317 handle.iops = NULL;
318
319 }
320
321 #define STAT_VERSION "version"
322
323 extern "C" {
statsCallback(lcb_t instance,const void * cookie,lcb_error_t err,const lcb_server_stat_resp_t * resp)324 static void statsCallback(lcb_t instance, const void *cookie, lcb_error_t err, const lcb_server_stat_resp_t *resp)
325 {
326 MockEnvironment *me = (MockEnvironment *)cookie;
327 ASSERT_EQ(LCB_SUCCESS, err);
328
329 if (resp->v.v0.server_endpoint == NULL) {
330 return;
331 }
332
333 if (!resp->v.v0.nkey) {
334 return;
335 }
336
337 if (resp->v.v0.nkey != sizeof(STAT_VERSION) - 1 ||
338 memcmp(resp->v.v0.key, STAT_VERSION, sizeof(STAT_VERSION) - 1) != 0) {
339 return;
340 }
341 MockEnvironment::ServerVersion version = MockEnvironment::VERSION_UNKNOWN;
342 if (resp->v.v0.nbytes > 2) {
343 int major = ((const char *)resp->v.v0.bytes)[0] - '0';
344 int minor = ((const char *)resp->v.v0.bytes)[2] - '0';
345 switch (major) {
346 case 4:
347 switch (minor) {
348 case 0:
349 version = MockEnvironment::VERSION_40;
350 break;
351 case 1:
352 version = MockEnvironment::VERSION_41;
353 break;
354 case 5:
355 version = MockEnvironment::VERSION_45;
356 break;
357 case 6:
358 version = MockEnvironment::VERSION_46;
359 break;
360 }
361 break;
362 case 5:
363 case 6:
364 version = MockEnvironment::VERSION_50;
365 break;
366 }
367 }
368 if (version == MockEnvironment::VERSION_UNKNOWN) {
369 lcb_log(LOGARGS(instance, ERROR), "Unable to determine version from string '%.*s', assuming 4.0",
370 (int)resp->v.v0.nbytes, (const char *)resp->v.v0.bytes);
371 version = MockEnvironment::VERSION_40;
372 }
373 me->setServerVersion(version);
374 lcb_log(LOGARGS(instance, INFO), "Using real cluster version %.*s (id=%d)", (int)resp->v.v0.nbytes,
375 (const char *)resp->v.v0.bytes, version);
376 }
377 }
378
bootstrapRealCluster()379 void MockEnvironment::bootstrapRealCluster()
380 {
381 serverParams = ServerParams(mock->http, mock->bucket,
382 mock->username, mock->password);
383
384 lcb_t tmphandle;
385 lcb_error_t err;
386 lcb_create_st options;
387 serverParams.makeConnectParams(options, NULL);
388
389 ASSERT_EQ(LCB_SUCCESS, lcb_create(&tmphandle, &options));
390 postCreate(tmphandle);
391 ASSERT_EQ(LCB_SUCCESS, lcb_connect(tmphandle));
392 lcb_wait(tmphandle);
393
394 lcb_set_stat_callback(tmphandle, statsCallback);
395 lcb_server_stats_cmd_t scmd, *pscmd;
396 pscmd = &scmd;
397 err = lcb_server_stats(tmphandle, this, 1, &pscmd);
398 ASSERT_EQ(LCB_SUCCESS, err);
399 lcb_wait(tmphandle);
400
401 const char *const *servers = lcb_get_server_list(tmphandle);
402 int ii;
403 for (ii = 0; servers[ii] != NULL; ii++) {
404 // no body
405 }
406
407 featureRegistry.insert("observe");
408 featureRegistry.insert("views");
409 featureRegistry.insert("http");
410 featureRegistry.insert("replica_read");
411 featureRegistry.insert("lock");
412
413 numNodes = ii;
414 lcb_destroy(tmphandle);
415 }
416
417 extern "C" {
mock_flush_callback(lcb_t,int,const lcb_RESPBASE * resp)418 static void mock_flush_callback(lcb_t, int, const lcb_RESPBASE *resp) {
419 ASSERT_EQ(LCB_SUCCESS, resp->rc);
420 }
421 }
422
clearAndReset()423 void MockEnvironment::clearAndReset()
424 {
425 if (is_using_real_cluster()) {
426 return;
427 }
428
429 for (int ii = 0; ii < getNumNodes(); ii++) {
430 respawnNode(ii, bucketName);
431 }
432
433 std::vector<int> mcPorts = getMcPorts(bucketName);
434 serverParams.setMcPorts(mcPorts);
435 setCCCP(true, bucketName);
436
437 if (this != getInstance()) {
438 return;
439 }
440
441 if (!innerClient) {
442 lcb_create_st crParams;
443 lcb_config_transport_t transports[] = {
444 LCB_CONFIG_TRANSPORT_CCCP,
445 LCB_CONFIG_TRANSPORT_LIST_END
446 };
447 memset(&crParams, 0, sizeof(crParams));
448 // Use default I/O here..
449 serverParams.makeConnectParams(crParams, NULL);
450 crParams.v.v2.transports = transports;
451 lcb_error_t err = lcb_create(&innerClient, &crParams);
452 if (err != LCB_SUCCESS) {
453 printf("Error on create: 0x%x\n", err);
454 }
455 EXPECT_FALSE(NULL == innerClient);
456 postCreate(innerClient);
457 err = lcb_connect(innerClient);
458 EXPECT_EQ(LCB_SUCCESS, err);
459 lcb_wait(innerClient);
460 lcb_install_callback3(innerClient, LCB_CALLBACK_CBFLUSH, mock_flush_callback);
461 }
462
463 lcb_CMDCBFLUSH fcmd = { 0 };
464 lcb_error_t err;
465
466 err = lcb_cbflush3(innerClient, NULL, &fcmd);
467 ASSERT_EQ(LCB_SUCCESS, err);
468 lcb_wait(innerClient);
469 }
470
SetUp()471 void MockEnvironment::SetUp()
472 {
473 numNodes = 4;
474 if (!mock) {
475 mock = (struct test_server_info *)start_test_server((char **)argv);
476 }
477
478 realCluster = is_using_real_cluster() != 0;
479 ASSERT_NE((const void *)(NULL), mock);
480 http = get_mock_http_server(mock);
481 ASSERT_NE((const char *)(NULL), http);
482
483 if (realCluster) {
484 bootstrapRealCluster();
485 return;
486 }
487
488 if (bucketName.empty()) {
489 const char *name = getenv("LCB_TEST_BUCKET");
490 if (name != NULL) {
491 bucketName = name;
492 } else {
493 bucketName = "default";
494 }
495 }
496 serverParams = ServerParams(http,
497 bucketName.c_str(),
498 userName.c_str(),
499 NULL);
500
501 // Mock 0.6
502 featureRegistry.insert("observe");
503 featureRegistry.insert("views");
504 featureRegistry.insert("replica_read");
505 featureRegistry.insert("lock");
506
507 clearAndReset();
508 }
509
TearDown()510 void MockEnvironment::TearDown()
511 {
512
513 }
514
~MockEnvironment()515 MockEnvironment::~MockEnvironment()
516 {
517 shutdown_mock_server(mock);
518 mock = NULL;
519 if (innerClient != NULL) {
520 lcb_destroy(innerClient);
521 innerClient = NULL;
522 }
523 }
524
destroy()525 void HandleWrap::destroy()
526 {
527 if (instance) {
528 lcb_destroy(instance);
529 }
530 if (iops) {
531 lcb_destroy_io_ops(iops);
532 }
533
534 instance = NULL;
535 iops = NULL;
536 }
537
~HandleWrap()538 HandleWrap::~HandleWrap()
539 {
540 destroy();
541 }
542
MockCommand(Code code)543 MockCommand::MockCommand(Code code)
544 {
545 this->code = code;
546 name = GetName(code);
547 command["command"] = name;
548 payload = &(command["payload"] = Json::Value(Json::objectValue));
549 }
550
~MockCommand()551 MockCommand::~MockCommand()
552 {
553 }
554
encode()555 std::string MockCommand::encode()
556 {
557 finalizePayload();
558 return Json::FastWriter().write(command);
559 }
560
finalizePayload()561 void MockKeyCommand::finalizePayload()
562 {
563 MockCommand::finalizePayload();
564 if (vbucket != -1) {
565 set("vBucket", vbucket);
566 }
567
568 if (!bucket.empty()) {
569 set("Bucket", bucket);
570 }
571 set("Key", key);
572 }
573
finalizePayload()574 void MockMutationCommand::finalizePayload()
575 {
576 MockKeyCommand::finalizePayload();
577 set("OnMaster", onMaster);
578
579 if (!replicaList.empty()) {
580 Json::Value arr(Json::arrayValue);
581 Json::Value& arrval = (*payload)["OnReplicas"] = Json::Value(Json::arrayValue);
582 for (std::vector<int>::iterator ii = replicaList.begin();
583 ii != replicaList.end(); ii++) {
584 arrval.append(*ii);
585 }
586 } else {
587 set("OnReplicas", replicaCount);
588 }
589
590 if (cas != 0) {
591 if (cas > (1LU << 30)) {
592 fprintf(stderr, "Detected incompatible > 31 bit integer\n");
593 abort();
594 }
595 set("CAS", static_cast<Json::UInt64>(cas));
596 }
597
598 if (!value.empty()) {
599 set("Value", value);
600 }
601 }
602
finalizePayload()603 void MockBucketCommand::finalizePayload()
604 {
605 MockCommand::finalizePayload();
606 set("idx", ix);
607 set("bucket", bucket);
608 }
609
assign(const std::string & resp)610 void MockResponse::assign(const std::string &resp)
611 {
612 bool rv = Json::Reader().parse(resp, jresp);
613 assert(rv);
614 }
615
~MockResponse()616 MockResponse::~MockResponse()
617 {
618 }
619
operator <<(std::ostream & os,const MockResponse & resp)620 std::ostream& operator<<(std::ostream& os, const MockResponse& resp)
621 {
622 os << Json::FastWriter().write(resp.jresp) << std::endl;
623 return os;
624 }
625
isOk()626 bool MockResponse::isOk()
627 {
628 const Json::Value& status = static_cast<const Json::Value&>(jresp)["status"];
629 if (!status.isString()) {
630 return false;
631 }
632 return tolower(status.asString()[0]) == 'o';
633 }
634