1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2010-2013 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 #ifndef TESTS_MOCK_ENVIRONMENT_H
18 #define TESTS_MOCK_ENVIRONMENT_H 1
19 
20 #include "config.h"
21 #include <gtest/gtest.h>
22 #include <libcouchbase/couchbase.h>
23 #include "serverparams.h"
24 #include "contrib/lcb-jsoncpp/lcb-jsoncpp.h"
25 
26 
27 class HandleWrap
28 {
29 
30     friend class MockEnvironment;
31 
32 public:
getLcb()33     lcb_t getLcb() const {
34         return instance;
35     }
36 
37     void destroy();
38 
39     // Don't ever allow copying. C++0x allows = 0, though
40     HandleWrap operator= (const HandleWrap &) {
41         fprintf(stderr, "Can't copy this object around!\n");
42         abort();
43         return HandleWrap();
44     }
45 
HandleWrap()46     HandleWrap() : instance(NULL), iops(NULL) { }
47     virtual ~HandleWrap();
48 
49 
50 private:
51     lcb_t instance;
52     lcb_io_opt_t iops;
53 };
54 
55 
56 class MockCommand
57 {
58 #define XMOCKCMD(X) \
59     X(FAILOVER) \
60     X(RESPAWN) \
61     X(HICCUP) \
62     X(TRUNCATE) \
63     X(MOCKINFO) \
64     X(PERSIST) \
65     X(CACHE) \
66     X(UNPERSIST) \
67     X(UNCACHE) \
68     X(ENDURE) \
69     X(PURGE) \
70     X(KEYINFO) \
71     X(GET_MCPORTS) \
72     X(SET_CCCP) \
73     X(REGEN_VBCOORDS) \
74     X(RESET_QUERYSTATE) \
75     X(OPFAIL) \
76     X(START_RETRY_VERIFY) \
77     X(CHECK_RETRY_VERIFY) \
78     X(SET_ENHANCED_ERRORS) \
79     X(SET_COMPRESSION) \
80     X(SET_SASL_MECHANISMS)
81 
82 public:
83     enum Code {
84 #define X(cc) cc,
85         XMOCKCMD(X)
86 #undef X
87         _NONE
88     };
89 
GetName(Code code)90     static std::string GetName(Code code) {
91 
92 #define X(cc) if (code == cc) { return #cc; }
93         XMOCKCMD(X)
94 #undef X
95 
96         abort();
97         return "";
98     }
99 
100     MockCommand(Code code);
101 
102     // Various methods to set a field in the payload
set(const std::string & s,const T & v)103     template <typename T> void set(const std::string& s, const T& v) {
104         (*payload)[s] = v;
105     }
106     virtual ~MockCommand();
107 
108     // Encodes the command in a form suitable for sending over the network
109     std::string encode();
110 
111 protected:
112     Code code;
113     std::string name;
114     Json::Value command;
115     Json::Value *payload;
finalizePayload()116     virtual void finalizePayload() {}
117 
118 private:
119     MockCommand(const MockCommand &other);
120 };
121 
122 class MockKeyCommand : public MockCommand
123 {
124 public:
MockKeyCommand(Code code,std::string & key)125     MockKeyCommand(Code code, std::string &key)
126         : MockCommand(code), vbucket(-1) {
127         this->key = key;
128     }
129 
getKey()130     const std::string &getKey() const {
131         return key;
132     }
133 
134     short vbucket;
135     std::string bucket;
136     std::string key;
137 
138 protected:
139     virtual void finalizePayload();
140 };
141 
142 class MockMutationCommand : public MockKeyCommand
143 {
144 public:
MockMutationCommand(Code code,std::string & key)145     MockMutationCommand(Code code, std::string &key)
146         : MockKeyCommand(code, key), onMaster(false), replicaCount(0), cas(0) {}
147 
148     bool onMaster;
149     int replicaCount;
150     std::vector<int> replicaList;
151     lcb_uint64_t cas;
152     std::string value;
153 
154 protected:
155     virtual void finalizePayload();
156 };
157 
158 class MockBucketCommand : public MockCommand
159 {
160 public:
161     MockBucketCommand(Code code, int index, std::string bucketstr = "default")
MockCommand(code)162         : MockCommand(code) {
163         ix = index;
164         bucket = bucketstr;
165     }
166 
167 protected:
168     virtual void finalizePayload();
169     int ix;
170     std::string bucket;
171 };
172 
173 class MockOpfailCommand : public MockCommand
174 {
175 public:
176     MockOpfailCommand(uint16_t errcode, int index, int count = -1,
177                       std::string bucketstr = "default")
MockCommand(OPFAIL)178         : MockCommand(OPFAIL) {
179         set("count", count);
180         set("bucket", bucketstr);
181         set("code", errcode);
182 
183         Json::Value srvlist(Json::arrayValue);
184         srvlist.append(index);
185         set("servers", srvlist);
186     }
187 };
188 
189 class MockOpFailClearCommand : public MockCommand {
190 public:
191     MockOpFailClearCommand(size_t nservers, std::string bucketstr = "default")
MockCommand(OPFAIL)192         : MockCommand(OPFAIL) {
193         set("count", -1);
194         set("bucket", bucketstr);
195         set("code", 0);
196 
197         Json::Value srvlist(Json::arrayValue);
198         for (size_t ii = 0; ii < nservers; ++ii) {
199             srvlist.append(static_cast<int>(ii));
200         }
201         set("servers", srvlist);
202     }
203 };
204 
205 class MockResponse
206 {
207 public:
MockResponse()208     MockResponse() {}
209     ~MockResponse();
210     void assign(const std::string& s);
211 
212     bool isOk();
getRawResponse()213     const Json::Value& getRawResponse() {
214         return jresp;
215     }
constResp()216     const Json::Value& constResp() const { return jresp; }
217 
218 protected:
219     Json::Value jresp;
220     friend std::ostream& operator<<(std::ostream&, const MockResponse&);
221 private:
222     MockResponse(const MockResponse&);
223 };
224 
225 class MockEnvironment : public ::testing::Environment
226 {
227 public:
228     enum ServerVersion {
229         VERSION_UNKNOWN = 0,
230         VERSION_40 = 4,
231         VERSION_41 = 5,
232         VERSION_45 = 6,
233         VERSION_46 = 7,
234         VERSION_50 = 8
235     };
236 
237     virtual void SetUp();
238     virtual void TearDown();
239 
240     static MockEnvironment *getInstance(void);
241     static void Reset();
242 
243     /**
244      * Make a connect structure you may utilize to connect to
245      * the backend we're running the tests towards.
246      *
247      * @param crst the create structure to fill in
248      * @param io the io ops to use (pass NULL if you don't have a
249      *           special io ops you want to use
250      */
251     void makeConnectParams(lcb_create_st &crst, lcb_io_opt_t io=NULL) {
252         serverParams.makeConnectParams(crst, io);
253     }
254 
getUsername()255     std::string getUsername() {
256         return serverParams.getUsername();
257     }
258 
getPassword()259     std::string getPassword() {
260         return serverParams.getPassword();
261     }
262 
getBucket()263     std::string getBucket() {
264         return serverParams.getBucket();
265     }
266 
267     /**
268      * Get the number of nodes used in the backend
269      */
getNumNodes(void)270     int getNumNodes(void) const {
271         return numNodes;
272     }
273 
274     /**
275      * Are we currently using a real cluster as the backend, or
276      * are we using the mock server.
277      *
278      * You should try your very best to avoid using this variable, and
279      * rather extend the mock server to support the requested feature.
280      */
isRealCluster(void)281     bool isRealCluster(void) const {
282         return realCluster;
283     }
284 
285     /**
286      * Simulate node failover. In this case mock will disable server
287      * corresponding given index an push new configuration. No data
288      * rebalancing implemented on the mock.
289      *
290      * @param index the index of the node on the mock
291      * @param bucket the name of the bucket
292      */
293     void failoverNode(int index, std::string bucket = "default", bool rebalance = true);
294 
295     /**
296      * Simulate node reconvering. In this case mock will enable server
297      * corresponding given index an push new configuration. No data
298      * rebalancing implemented on the mock.
299      *
300      * @param index the index of the node on the mock
301      * @param bucket the name of the bucket
302      */
303     void respawnNode(int index, std::string bucket = "default");
304 
305     /**
306      * Regenerate existing UUIDs and sequence numbers on the cluster to
307      * simulate a dcp-style failover. This is a separate command as triggering
308      * this during a "Normal" failover severly slows down the mock.
309      *
310      * @param bucket
311      */
312     void regenVbCoords(std::string bucket = "default");
313 
314 
315     /**
316      * Retrieve the memcached listening ports for a given bucket
317      * @param bucket the bucket for which to retrieve memcached port into
318      * @return a vector of ports to use.
319      */
320     std::vector<int> getMcPorts(std::string bucket = "default");
321 
322     /**
323      * Enable SASL mechanisms on the mock cluster
324      * @param mechanisms list of mechanisms to enable
325      * @param bucket the bucket on which to enable these mechanisms
326      * @param nodes a list of by-index nodes on which to enable mechanisms. If NULL
327      * then all nodes are enabled
328      */
329     void setSaslMechs(std::vector<std::string>& mechanisms, std::string bucket = "",
330                       const std::vector<int>* nodes = NULL);
331 
332     /**
333      * Enable CCCP on the mock cluster
334      * @param bucket the bucket on which to enable CCCP
335      * @param nodes a list of by-index nodes on which to enable CCCP. If NULL
336      * then all nodes are enabled
337      * @param bucket the bucket on which to
338      */
339     void setCCCP(bool enabled,
340                  std::string bucket = "",
341                  const std::vector<int>* nodes = NULL);
342 
343     /**
344      * Enable enhanced errors on the mock cluster
345      *
346      * This includes generation event id (ref), and setting context for some errors
347      * .
348      * @param bucket the bucket on which to enable enhanced errors
349      * @param nodes a list of by-index nodes on which to enable Enhanced Errors. If NULL
350      * then all nodes are enabled
351      */
352     void setEnhancedErrors(bool enabled,
353                            std::string bucket = "",
354                            const std::vector<int>* nodes = NULL);
355 
356     /**
357      * Change compression mode on the server
358      *
359      * @param mode compression mode ("off", "passive", "active")
360      * @param bucket the bucket on which to enable compression
361      * @param nodes a list of by-index nodes on which to enable compression. If NULL
362      * then all nodes are enabled
363      */
364     void setCompression(std::string mode,
365                         std::string bucket = "",
366                         const std::vector<int>* nodes = NULL);
367 
368     const Json::Value getKeyInfo(std::string key, std::string bucket = "");
369 
370     /**
371      * Create a connection to the mock/real server.
372      *
373      * The instance will be initialized with the the connect parameters
374      * to either the mock or a real server (just like makeConnectParams),
375      * and call lcb_create. The io instance will be stored in the instance
376      * cookie so you may grab it from there.
377      *
378      * You should call lcb_destroy on the instance when you're done
379      * using it.
380      *
381      * @param instance the instane to create
382      */
383     void createConnection(lcb_t &instance);
384 
385     void createConnection(HandleWrap &handle, lcb_t &instance);
386     void createConnection(HandleWrap &handle, lcb_t& instance,
387         const lcb_create_st &options);
388 
389     /**
390      * Setup mock to split response in two parts: send first "offset" bytes
391      * immediately and send the rest after "msecs" milliseconds.
392      *
393      * @param msecs the number of milliseconds to wait before sending the
394      *              rest of the packet.
395      * @param offset the number of the bytes to send in first before delay
396      */
397     void hiccupNodes(int msecs, int offset);
398 
getServerVersion(void)399     ServerVersion getServerVersion(void) const {
400         return serverVersion;
401     }
402 
setServerVersion(ServerVersion ver)403     void setServerVersion(ServerVersion ver)  {
404         serverVersion = ver;
405     }
406 
407     void sendCommand(MockCommand &cmd);
408     void getResponse(MockResponse &resp);
getResponse()409     void getResponse() { MockResponse tmp; getResponse(tmp); }
410 
hasFeature(const char * feature)411     bool hasFeature(const char *feature) {
412         return featureRegistry.find(feature) != featureRegistry.end();
413     }
414 
printSkipMessage(std::string file,int line,std::string reason)415     static void printSkipMessage(std::string file, int line, std::string reason) {
416         std::cerr << "Skipping " << file << ":" << std::dec << line;
417         std::cerr << " (" << reason << ")";
418         std::cerr << std::endl;
419     }
420 
421     MockEnvironment(const char **argv, std::string name = "default");
422     virtual ~MockEnvironment();
423     void postCreate(lcb_t instance);
424 
425 protected:
426     /**
427      * Protected destructor to make it to a singleton
428      */
429     MockEnvironment();
430     /**
431      * Handle to the one and only instance of the mock environment
432      */
433     static MockEnvironment *instance;
434 
435     void bootstrapRealCluster();
436     const struct test_server_info *mock;
437     ServerParams serverParams;
438     int numNodes;
439     bool realCluster;
440     ServerVersion serverVersion;
441     const char *http;
442     lcb_io_opt_st *iops;
443     std::set<std::string> featureRegistry;
444     std::string bucketName;
445     std::string userName;
446     const char **argv;
447     void clearAndReset();
448 
449 private:
450     lcb_t innerClient;
451     void setupInnerClient();
452     void init();
453 };
454 
455 #define LCB_TEST_REQUIRE_CLUSTER_VERSION(v) \
456     if (!MockEnvironment::getInstance()->isRealCluster()) { \
457         MockEnvironment::printSkipMessage(__FILE__, __LINE__, \
458                                           "need real cluster"); \
459         return; \
460     } \
461     if (MockEnvironment::getInstance()->getServerVersion() < v) {      \
462         MockEnvironment::printSkipMessage(__FILE__, __LINE__, \
463                                           "needs higher cluster version"); \
464         return; \
465     }
466 
467 #define LCB_TEST_REQUIRE_FEATURE(s) \
468     if (!MockEnvironment::getInstance()->hasFeature(s)) { \
469         MockEnvironment::printSkipMessage(__FILE__, __LINE__, \
470                                           "Feature " s \
471                                           " missing in server implementation"); \
472         return; \
473     }
474 
475 #define CLUSTER_VERSION_IS_HIGHER_THAN(v)                                                                              \
476     (MockEnvironment::getInstance()->isRealCluster() && MockEnvironment::getInstance()->getServerVersion() >= v)
477 
478 #define SKIP_IF_CLUSTER_VERSION_IS_HIGHER_THAN(v)                                                                      \
479     if (CLUSTER_VERSION_IS_HIGHER_THAN(v)) {                                                                           \
480         MockEnvironment::printSkipMessage(__FILE__, __LINE__, "needs lower cluster version");                          \
481         return;                                                                                                        \
482     }
483 
484 #define CLUSTER_VERSION_IS_LOWER_THAN(v)                                                                               \
485     (MockEnvironment::getInstance()->isRealCluster() && MockEnvironment::getInstance()->getServerVersion() < v)
486 
487 #define SKIP_IF_CLUSTER_VERSION_IS_LOWER_THAN(v)                                                                       \
488     if (CLUSTER_VERSION_IS_LOWER_THAN(v)) {                                                                            \
489         MockEnvironment::printSkipMessage(__FILE__, __LINE__, "needs higher cluster version");                         \
490         return;                                                                                                        \
491     }
492 #endif
493