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