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