├── .gitignore ├── LICENSE ├── README.md ├── src ├── ZKClient.cc ├── ZKClient.hpp ├── ZKLeader.cc ├── ZKLeader.hpp ├── zookeeper_utils.cc └── zookeeper_utils.hpp └── test ├── ZooKeeperHarness.hpp ├── ZooKeeperLeaderElectionHarness.hpp ├── leader_election_tests ├── .gitignore ├── SConscript └── zkleader_election_tests.cc └── zkclient ├── .gitignore ├── SConscript └── zkclient_test.cc /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alexander Gallego 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zk 2 | zookeeper c++11. Uses facebook folly. 3 | 4 | ### Pending build system & some cleanup 5 | 6 | Will clean up and fix up so you can use it :) 7 | 8 | ### Authors: 9 | 10 | - [Alexander Gallego](twitter.com/gallegoxx) 11 | - [Cole Brown](twitter.com/dtcb) 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/ZKClient.cc: -------------------------------------------------------------------------------- 1 | #include "bolt/zookeeper/ZKClient.hpp" 2 | 3 | namespace bolt { 4 | using namespace ::folly; 5 | 6 | static const int kMaxTriesPerSyncOperation = 10; 7 | 8 | static void 9 | watchCb(zhandle_t *zh, int type, int state, const char *path, void *ctx); 10 | 11 | static void statCompletionCb(int rc, const struct Stat *stat, const void *data); 12 | 13 | static void stringCompletionCb(int rc, const char *value, const void *data); 14 | static void stringsAndStatCompletionCb(int rc, 15 | const struct String_vector *strs, 16 | const struct Stat *stat, 17 | const void *data); 18 | static void voidCompletionCb(int rc, const void *data); 19 | 20 | static void dataCompletionCb(int rc, 21 | const char *value, 22 | int value_len, 23 | const struct Stat *stat, 24 | const void *data); 25 | 26 | static std::shared_ptr> promiseFromData(const void *data) { 27 | const Promise *constPromise = 28 | static_cast *>(data); 29 | 30 | Promise *promise = const_cast *>(constPromise); 31 | 32 | return std::shared_ptr>(promise); 33 | } 34 | 35 | // copy from stout / modified w/ __builtin_unreachable() 36 | bool ZKClient::retryable(int rc) { 37 | switch(rc) { 38 | case ZCONNECTIONLOSS: 39 | case ZOPERATIONTIMEOUT: 40 | case ZSESSIONEXPIRED: 41 | case ZSESSIONMOVED: 42 | return true; 43 | 44 | case ZOK: // No need to retry! 45 | 46 | case ZSYSTEMERROR: // Should not be encountered, here for completeness. 47 | case ZRUNTIMEINCONSISTENCY: 48 | case ZDATAINCONSISTENCY: 49 | case ZMARSHALLINGERROR: 50 | case ZUNIMPLEMENTED: 51 | case ZBADARGUMENTS: 52 | case ZINVALIDSTATE: 53 | 54 | case ZAPIERROR: // Should not be encountered, here for completeness. 55 | case ZNONODE: 56 | case ZNOAUTH: 57 | case ZBADVERSION: 58 | case ZNOCHILDRENFOREPHEMERALS: 59 | case ZNODEEXISTS: 60 | case ZNOTEMPTY: 61 | case ZINVALIDCALLBACK: 62 | case ZINVALIDACL: 63 | case ZAUTHFAILED: 64 | case ZCLOSING: 65 | case ZNOTHING: // Is this used? It's not exposed in the Java API. 66 | return false; 67 | 68 | default: 69 | LOG(FATAL) << "Unknown ZooKeeper code: " << rc; 70 | __builtin_unreachable(); // Make compiler happy. 71 | } 72 | } 73 | 74 | 75 | void ZKClient::init(bool block) { 76 | DLOG(INFO) << "Initializing zookeeper connection: " << hosts_; 77 | CHECK(zoo_ == nullptr) << "Doubly initializing zookeeper"; 78 | CHECK(!hosts_.empty()) << "Passed in an invalid host string"; 79 | 80 | rawInitHandle(this); 81 | 82 | LOG(INFO) << "Zookeeper initialized. State: " << getState() 83 | << ", session id: " << getSessionId(); 84 | } 85 | 86 | void ZKClient::destroy() { 87 | if(!zoo_) { 88 | return; 89 | } 90 | int ret = zookeeper_close(zoo_); 91 | if(ret != ZOK) { 92 | LOG(ERROR) << "Failed to cleanup ZooKeeper, zookeeper_close: " 93 | << zerror(ret); 94 | } 95 | zoo_ = nullptr; 96 | } 97 | 98 | ZKClient::~ZKClient() { destroy(); } 99 | 100 | void ZKClient::rawInitHandle(ZKClient *cli) { 101 | std::lock_guard(cli->rawInitMutex_); 102 | if(cli->zoo_ && cli->getState() != ZOO_EXPIRED_SESSION_STATE) { 103 | return; 104 | } 105 | if(cli->zoo_) { 106 | // This is due to server connection failure. give it a second. 107 | // On local host testing, this is due to the zookeepr process is out of heap 108 | // and is doing a major GC compaction. So its useless to try and reconnect 109 | std::this_thread::sleep_for(std::chrono::seconds(1)); 110 | } 111 | cli->ready = false; 112 | // Idea taken from Zookeper/zookeeper.cpp in mesos 113 | // We retry zookeeper_init until the timeout elapses because we've 114 | // seen cases where temporary DNS outages cause the slave to abort 115 | // here. See MESOS-1326 for more information. 116 | // ZooKeeper masks EAI_AGAIN as EINVAL and a name resolution timeout 117 | // may be upwards of 30 seconds. As such, a 10 second timeout is not 118 | // enough. Hard code this to 10 minutes to be sure we're trying again 119 | // in the face of temporary name resolution failures. See MESOS-1523 120 | // for more information. 121 | int maxInitTries = 600; 122 | while(maxInitTries-- > 0) { 123 | cli->zoo_ = zookeeper_init(cli->hosts().c_str(), &watchCb, cli->timeout(), 124 | cli->getClientId(), (void *)cli, cli->flags()); 125 | // Unfortunately, EINVAL is highly overloaded in zookeeper_init 126 | // and can correspond to: 127 | // (1) Empty / invalid 'host' string format. 128 | // (2) Any getaddrinfo error other than EAI_NONAME, 129 | // EAI_NODATA, and EAI_MEMORY are mapped to EINVAL. 130 | // Either way, retrying is not problematic. 131 | if(cli->zoo_ == nullptr && errno == EINVAL) { 132 | LOG(ERROR) << "Error initializing zookeeper. Retrying in 1 second"; 133 | std::this_thread::sleep_for(std::chrono::seconds(1)); 134 | continue; 135 | } 136 | 137 | break; 138 | } 139 | 140 | if(cli->zoo_ == NULL) { 141 | PLOG(FATAL) << "Failed to create ZooKeeper, zookeeper_init"; 142 | } 143 | 144 | CHECK(cli->zoo_) << "Failed to initialize zookeeper"; 145 | 146 | while(!cli->ready) { 147 | std::this_thread::yield(); 148 | } 149 | } 150 | 151 | 152 | static void 153 | watchCb(zhandle_t *zh, int type, int state, const char *cpath, void *ctx) { 154 | DCHECK(ctx) << "invalid context on the callback"; 155 | ZKClient *self = static_cast(ctx); 156 | 157 | if(type == ZOO_SESSION_EVENT) { 158 | if(state == ZOO_CONNECTED_STATE) { 159 | LOG(INFO) << "Zookeeper connected..."; 160 | self->ready = true; 161 | } else if(state == ZOO_ASSOCIATING_STATE) { 162 | LOG(ERROR) << "Zookeeper associating..."; 163 | } else if(state == ZOO_EXPIRED_SESSION_STATE) { 164 | LOG(ERROR) << "Zookeeper session expired. ZOO_EXPIRED_SESSION_STATE. " 165 | "Attempting to retry session stablishment"; 166 | } 167 | } 168 | self->watch_(type, state, std::string(cpath == nullptr ? "" : cpath), self); 169 | } 170 | 171 | static void dataCompletionCb(int rc, 172 | const char *value, 173 | int value_len, 174 | const struct Stat *stat, 175 | const void *data) { 176 | 177 | auto promise = promiseFromData(data); 178 | 179 | struct ZKResult result(rc, stat ? boost::optional(*stat) : boost::none); 180 | 181 | if(value) { 182 | result.buff = folly::IOBuf::copyBuffer((void *)value, value_len); 183 | } 184 | 185 | promise->setValue(std::move(result)); 186 | } 187 | 188 | static void stringsAndStatCompletionCb(int rc, 189 | const struct String_vector *strs, 190 | const struct Stat *stat, 191 | const void *data) { 192 | auto promise = promiseFromData(data); 193 | struct ZKResult result(rc, stat ? boost::optional(*stat) : boost::none); 194 | 195 | for(auto i = 0; strs && i < strs->count; ++i) { 196 | result.strings.push_back(strs->data[i]); 197 | } 198 | 199 | promise->setValue(std::move(result)); 200 | } 201 | 202 | std::string ZKClient::printZookeeperEventType(int type) { 203 | if(type == ZOO_CREATED_EVENT) { 204 | return "ZOO_CREATED_EVENT"; 205 | } 206 | 207 | if(type == ZOO_DELETED_EVENT) { 208 | return "ZOO_DELETED_EVENT"; 209 | } 210 | 211 | if(type == ZOO_CHANGED_EVENT) { 212 | return "ZOO_CHANGED_EVENT"; 213 | } 214 | 215 | if(type == ZOO_CHILD_EVENT) { 216 | return "ZOO_CHILD_EVENT"; 217 | } 218 | 219 | if(type == ZOO_SESSION_EVENT) { 220 | return "ZOO_SESSION_EVENT"; 221 | } 222 | 223 | if(type == ZOO_NOTWATCHING_EVENT) { 224 | return "ZOO_NOTWATCHING_EVENT"; 225 | } 226 | 227 | return "UNKNOWN_EVENT: " + std::to_string(type); 228 | } 229 | 230 | std::string ZKClient::printZookeeperState(int state) { 231 | if(state == ZOO_EXPIRED_SESSION_STATE) { 232 | return "ZOO_EXPIRED_SESSION_STATE"; 233 | } 234 | 235 | if(state == ZOO_AUTH_FAILED_STATE) { 236 | return "ZOO_AUTH_FAILED_STATE"; 237 | } 238 | 239 | if(state == ZOO_CONNECTING_STATE) { 240 | return "ZOO_CONNECTING_STATE"; 241 | } 242 | 243 | if(state == ZOO_ASSOCIATING_STATE) { 244 | return "ZOO_ASSOCIATING_STATE"; 245 | } 246 | 247 | if(state == ZOO_CONNECTED_STATE) { 248 | return "ZOO_CONNECTED_STATE"; 249 | } 250 | 251 | return "ZOO_UNKNOWN_STATE: " + std::to_string(state); 252 | } 253 | 254 | static void 255 | statCompletionCb(int rc, const struct Stat *stat, const void *data) { 256 | 257 | auto promise = promiseFromData(data); 258 | struct ZKResult result(rc, stat ? boost::optional(*stat) : boost::none); 259 | promise->setValue(std::move(result)); 260 | } 261 | 262 | Future ZKClient::get(std::string path, bool watch) { 263 | Promise *promise = new Promise; 264 | 265 | if(!ready) { 266 | promise->setException(std::runtime_error("Not connected")); 267 | } else { 268 | zoo_aget(zoo_, path.c_str(), watch ? 1 : 0, &dataCompletionCb, 269 | static_cast(promise)); 270 | } 271 | 272 | return promise->getFuture(); 273 | } 274 | 275 | const clientid_t *ZKClient::getClientId() { 276 | if(!zoo_ || getState() == ZSESSIONEXPIRED) { 277 | return nullptr; 278 | } 279 | return zoo_client_id(zoo_); 280 | } 281 | 282 | 283 | ZKResult ZKClient::getSync(std::string path, bool watch) { 284 | struct Stat stat; 285 | int bufLen = 1 << 20; // 1MB is max for zookeeper 286 | std::unique_ptr buf(new char[bufLen]()); 287 | int rc = 288 | zoo_get(zoo_, path.c_str(), watch ? 1 : 0, buf.get(), &bufLen, &stat); 289 | int maxTries = kMaxTriesPerSyncOperation; 290 | while(maxTries-- > 0 && (rc == ZINVALIDSTATE || retryable(rc))) { 291 | CHECK(getState() != ZOO_AUTH_FAILED_STATE); 292 | ZKClient::rawInitHandle(this); 293 | rc = zoo_get(zoo_, path.c_str(), watch ? 1 : 0, buf.get(), &bufLen, &stat); 294 | } 295 | 296 | if(rc != ZOK) { 297 | return ZKResult(rc); 298 | } 299 | 300 | struct ZKResult result(rc, stat, folly::IOBuf::copyBuffer(buf.get(), bufLen)); 301 | 302 | return result; 303 | } 304 | 305 | Future ZKClient::set(std::string path, 306 | std::unique_ptr &&val, 307 | int version) { 308 | Promise *promise = new Promise; 309 | 310 | if(!ready) { 311 | promise->setException(std::runtime_error("Not connected")); 312 | } else { 313 | zoo_aset(zoo_, path.c_str(), (char *)val->data(), val->length(), version, 314 | &statCompletionCb, static_cast(promise)); 315 | } 316 | 317 | return promise->getFuture(); 318 | } 319 | 320 | ZKResult ZKClient::setSync(std::string path, 321 | std::unique_ptr &&val, 322 | int version) { 323 | 324 | struct Stat stat; 325 | int rc = zoo_set2(zoo_, path.c_str(), (const char *)val->data(), 326 | val->length(), version, &stat); 327 | int maxTries = kMaxTriesPerSyncOperation; 328 | while(maxTries-- > 0 && (rc == ZINVALIDSTATE || retryable(rc))) { 329 | CHECK(getState() != ZOO_AUTH_FAILED_STATE); 330 | ZKClient::rawInitHandle(this); 331 | rc = zoo_set2(zoo_, path.c_str(), (const char *)val->data(), val->length(), 332 | version, &stat); 333 | } 334 | struct ZKResult result(rc, stat); 335 | return result; 336 | } 337 | 338 | Future ZKClient::children(std::string path, bool watch) { 339 | Promise *promise = new Promise; 340 | 341 | if(!ready) { 342 | promise->setException(std::runtime_error("Not connected")); 343 | } else { 344 | // zoo_aget_children2(zhandle_t *zh, const char *path, int watch, 345 | // strings_stat_completion_t completion, const void *data); 346 | zoo_aget_children2(zoo_, path.c_str(), watch ? 1 : 0, 347 | stringsAndStatCompletionCb, 348 | static_cast(promise)); 349 | } 350 | 351 | return promise->getFuture(); 352 | } 353 | ZKResult ZKClient::childrenSync(std::string path, bool watch) { 354 | 355 | struct String_vector strs { 356 | 0, nullptr 357 | }; // = nullptr; 358 | struct Stat stat; 359 | int rc = zoo_get_children(zoo_, path.c_str(), watch ? 1 : 0, &strs); 360 | int maxTries = kMaxTriesPerSyncOperation; 361 | while(maxTries-- > 0 && (rc == ZINVALIDSTATE || retryable(rc))) { 362 | CHECK(getState() != ZOO_AUTH_FAILED_STATE); 363 | ZKClient::rawInitHandle(this); 364 | rc = zoo_get_children(zoo_, path.c_str(), watch ? 1 : 0, &strs); 365 | } 366 | struct ZKResult result(rc, stat); 367 | for(auto i = 0; strs.data && i < strs.count; ++i) { 368 | char *ptr = strs.data[i]; 369 | if(ptr) { 370 | result.strings.push_back(std::string(ptr)); 371 | } 372 | } 373 | return result; 374 | } 375 | 376 | Future ZKClient::exists(std::string path, bool watch) { 377 | Promise *p = new Promise; 378 | 379 | if(!ready) { 380 | p->setException(std::runtime_error("Not connected")); 381 | } else { 382 | zoo_aexists(zoo_, path.c_str(), watch ? 1 : 0, &statCompletionCb, 383 | static_cast(p)); 384 | } 385 | 386 | return p->getFuture(); 387 | } 388 | 389 | ZKResult ZKClient::existsSync(std::string path, bool watch) { 390 | 391 | struct Stat stat; 392 | int rc = zoo_exists(zoo_, path.c_str(), watch ? 1 : 0, &stat); 393 | int maxTries = kMaxTriesPerSyncOperation; 394 | while(maxTries-- > 0 && (rc == ZINVALIDSTATE || retryable(rc))) { 395 | CHECK(getState() != ZOO_AUTH_FAILED_STATE); 396 | ZKClient::rawInitHandle(this); 397 | rc = zoo_exists(zoo_, path.c_str(), watch ? 1 : 0, &stat); 398 | } 399 | struct ZKResult result(rc, stat); 400 | return result; 401 | } 402 | 403 | static void stringCompletionCb(int rc, const char *value, const void *data) { 404 | auto promise = promiseFromData(data); 405 | struct ZKResult result(rc); 406 | 407 | if(value) { 408 | result.buff = folly::IOBuf::copyBuffer( 409 | (void *)value, std::char_traits::length(value)); 410 | } 411 | 412 | promise->setValue(std::move(result)); 413 | } 414 | 415 | Future ZKClient::create(std::string path, 416 | std::unique_ptr &&val, 417 | ACL_vector *acl, 418 | int flags) { 419 | VLOG(1) << "Create path: " << path; 420 | Promise *p = new Promise; 421 | 422 | if(!ready) { 423 | p->setException(std::runtime_error("Not connected")); 424 | } else { 425 | zoo_acreate(zoo_, path.c_str(), (char *)val->data(), val->length(), acl, 426 | flags, &stringCompletionCb, static_cast(p)); 427 | } 428 | 429 | return p->getFuture(); 430 | } 431 | 432 | ZKResult ZKClient::createSync(std::string path, 433 | std::unique_ptr &&val, 434 | ACL_vector *acl, 435 | int flags) { 436 | 437 | std::unique_ptr pathBuf(new char[1024]()); 438 | int rc = zoo_create(zoo_, path.c_str(), (const char *)val->data(), 439 | val->length(), acl, flags, pathBuf.get(), 1024); 440 | int maxTries = kMaxTriesPerSyncOperation; 441 | while(maxTries-- > 0 && (rc == ZINVALIDSTATE || retryable(rc))) { 442 | CHECK(getState() != ZOO_AUTH_FAILED_STATE); 443 | ZKClient::rawInitHandle(this); 444 | rc = zoo_create(zoo_, path.c_str(), (const char *)val->data(), 445 | val->length(), acl, flags, pathBuf.get(), 1024); 446 | } 447 | 448 | struct ZKResult result( 449 | rc, boost::none, 450 | folly::IOBuf::copyBuffer(pathBuf.get(), 451 | std::char_traits::length(pathBuf.get()))); 452 | 453 | return result; 454 | } 455 | 456 | static void voidCompletionCb(int rc, const void *data) { 457 | 458 | auto promise = promiseFromData(data); 459 | 460 | struct ZKResult result(rc); 461 | promise->setValue(std::move(result)); 462 | } 463 | 464 | Future ZKClient::del(std::string path, int version) { 465 | Promise *p = new Promise; 466 | 467 | if(!ready) { 468 | p->setException(std::runtime_error("Not connected")); 469 | } else { 470 | zoo_adelete(zoo_, path.c_str(), version, &voidCompletionCb, 471 | static_cast(p)); 472 | } 473 | 474 | return p->getFuture(); 475 | } 476 | 477 | ZKResult ZKClient::delSync(std::string path, int version) { 478 | int rc = zoo_delete(zoo_, path.c_str(), version); 479 | int maxTries = kMaxTriesPerSyncOperation; 480 | while(maxTries-- > 0 && (rc == ZINVALIDSTATE || retryable(rc))) { 481 | CHECK(getState() != ZOO_AUTH_FAILED_STATE); 482 | ZKClient::rawInitHandle(this); 483 | rc = zoo_delete(zoo_, path.c_str(), version); 484 | } 485 | 486 | struct ZKResult result(rc); 487 | return result; 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /src/ZKClient.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BOLT_ZOOKEEPER_HPP 2 | #define BOLT_ZOOKEEPER_HPP 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace bolt { 17 | using namespace ::folly; 18 | class ZKClient; 19 | 20 | struct ZKResult { 21 | ZKResult(int rc, 22 | boost::optional stat = boost::none, 23 | std::unique_ptr val = nullptr) 24 | : result(rc), status(stat), buff(std::move(val)) {} 25 | 26 | const uint8_t *data() const { return buff->data(); } 27 | bool ok() { 28 | return result == ZOK; // might need something else 29 | } 30 | 31 | int result = -1; 32 | // struct Stat { 33 | // int64_t czxid; 34 | // int64_t mzxid; 35 | // int64_t ctime; 36 | // int64_t mtime; 37 | // int32_t version; 38 | // int32_t cversion; 39 | // int32_t aversion; 40 | // int64_t ephemeralOwner; 41 | // int32_t dataLength; 42 | // int32_t numChildren; 43 | // int64_t pzxid; 44 | // }; 45 | // int serialize_Stat(struct oarchive *out 46 | // , const char *tag, struct Stat *v); 47 | // int deserialize_Stat(struct iarchive *in 48 | // , const char *tag, struct Stat*v); 49 | // void deallocate_Stat(struct Stat*); 50 | boost::optional status; 51 | std::unique_ptr buff; 52 | // represents the strings api of zookeeper 53 | // when the appropriate call is made - i.e.: zoo_wget_children2 54 | // cannot be a std::set! must keep zoo api fidelity. 55 | // struct String_vector { 56 | // int32_t count; 57 | // char * *data; 58 | // }; 59 | // int serialize_String_vector(struct oarchive *out 60 | // , const char *tag, struct String_vector *v); 61 | // int deserialize_String_vector(struct iarchive *in 62 | // , const char *tag, struct String_vector *v); 63 | // int allocate_String_vector(struct String_vector *v, int32_t len); 64 | // int deallocate_String_vector(struct String_vector *v); 65 | std::vector strings; 66 | }; 67 | 68 | typedef std::function ZKWatchCb; 69 | 70 | class ZKClient { 71 | public: 72 | static std::string printZookeeperEventType(int type); 73 | 74 | static std::string printZookeeperState(int state); 75 | 76 | static bool retryable(int rc); 77 | 78 | static void rawInitHandle(ZKClient *); 79 | 80 | 81 | enum { NO_TIMEOUT = std::numeric_limits::max() }; 82 | template 83 | ZKClient(F &&watch, 84 | const std::string &hosts = "127.0.0.1:2181", 85 | int timeout = 30, // ms 86 | int flags = 0, 87 | bool block = true) 88 | : watch_(watch) 89 | , ready(false) 90 | , hosts_(hosts) 91 | , timeout_(timeout) 92 | , flags_(flags) { 93 | init(block); 94 | } 95 | 96 | ~ZKClient(); 97 | 98 | Future children(std::string path, bool watch = false); 99 | 100 | ZKResult childrenSync(std::string path, bool watch = false); 101 | 102 | Future get(std::string path, bool watch = false); 103 | 104 | ZKResult getSync(std::string path, bool watch = false); 105 | 106 | Future 107 | set(std::string path, std::unique_ptr &&val, int version = -1); 108 | 109 | ZKResult setSync(std::string path, 110 | std::unique_ptr &&val, 111 | int version = -1); 112 | 113 | Future exists(std::string path, bool watch = false); 114 | 115 | ZKResult existsSync(std::string path, bool watch = false); 116 | 117 | Future create(std::string path, 118 | std::unique_ptr &&val, 119 | ACL_vector *acl, 120 | int flags); 121 | 122 | ZKResult createSync(std::string path, 123 | std::unique_ptr &&val, 124 | ACL_vector *acl, 125 | int flags); 126 | 127 | Future del(std::string path, int version = -1); 128 | 129 | ZKResult delSync(std::string path, int version = -1); 130 | 131 | const clientid_t *getClientId(); 132 | 133 | // State constants 134 | int getState() { return zoo_state(zoo_); } 135 | 136 | int64_t getSessionId() { return zoo_client_id(zoo_)->client_id; } 137 | 138 | 139 | void decrementSessionTries() { maxSessionConnTries_--; } 140 | int getSessionsTriesLeft() const { return maxSessionConnTries_; } 141 | int timeout() const { return timeout_; } 142 | int flags() const { return flags_; } 143 | std::string hosts() { return hosts_; } 144 | 145 | // The following should be considered private API 146 | // needed for the callback. XXX (agallego,bigs): 147 | // not part of public api - internal - use pimpl idom 148 | // and refactor 149 | ZKWatchCb watch_; 150 | std::atomic ready; 151 | zhandle_t *zoo_{nullptr}; 152 | std::mutex rawInitMutex_; 153 | 154 | void destroy(); 155 | void init(bool block); 156 | 157 | private: 158 | const std::string hosts_; 159 | int timeout_; 160 | int flags_; 161 | int maxSessionConnTries_ = {600}; 162 | }; 163 | } 164 | 165 | #endif 166 | -------------------------------------------------------------------------------- /src/ZKLeader.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "bolt/zookeeper/ZKLeader.hpp" 4 | #include "bolt/utils/url_utils.hpp" 5 | #include "bolt/utils/string_utils.hpp" 6 | 7 | namespace bolt { 8 | const folly::Uri &ZKLeader::uri() const { return zkUri_; } 9 | 10 | std::shared_ptr ZKLeader::client() const { return zk_; } 11 | 12 | 13 | ZKLeader::ZKLeader(folly::Uri zkUri, 14 | std::function leaderfn, 15 | std::function zkcb) 16 | : zkUri_(zkUri), leadercb_(leaderfn), zkcb_(zkcb) { 17 | using namespace std::placeholders; 18 | auto cb = std::bind(&ZKLeader::zkCbWrapper, this, _1, _2, _3, _4); 19 | const auto baseElectionPath = zkUri_.path().toStdString() + "/election"; 20 | const auto baseElectionId = baseElectionPath + "/" + uuid() + "_n_"; 21 | zk_ = std::make_shared(cb, zookeeperHostsFromUrl(zkUri_), 500, 0, 22 | true /*yield until connected*/); 23 | LOG(INFO) << "Watching: " << baseElectionPath; 24 | auto zkret = zk_->existsSync(baseElectionPath, true); 25 | 26 | if(zkret.result == ZNONODE) { 27 | touchZKPathSync(baseElectionPath); 28 | } else { 29 | CHECK(zkret.result == ZOK) 30 | << "Failed to watch the directory ret code: " << zkret.result; 31 | } 32 | 33 | LOG(INFO) << "Creating election node: " << baseElectionId; 34 | zkret = zk_->createSync(baseElectionId, std::make_unique(), 35 | &ZOO_OPEN_ACL_UNSAFE, ZOO_SEQUENCE | ZOO_EPHEMERAL); 36 | CHECK(zkret.result == ZOK) 37 | << "Couldn't create election path: " << baseElectionId 38 | << ", ret: " << zkret.result; 39 | std::string ephemeral((char *)zkret.data(), zkret.buff->length()); 40 | LOG(INFO) << "Ephemeral node: " << ephemeral; 41 | electionPath_ = std::move(ephemeral); 42 | auto optId = extractIdFromEphemeralPath(electionPath_); 43 | CHECK(optId) << "Could not parse id from ephemeral path"; 44 | id_ = optId.get(); 45 | LOG(INFO) << "Leader election id: " << id_; 46 | leaderElect(0, 0, ""); 47 | } 48 | 49 | void ZKLeader::touchZKPathSync(const std::string &nestedPath) { 50 | std::vector paths; 51 | boost::split(paths, nestedPath, boost::is_any_of("/")); 52 | CHECK(!paths.empty()) << "Empty nested path: " << nestedPath; 53 | std::string path = ""; 54 | 55 | for(auto p : paths) { 56 | if(p.empty() || p == "/") { 57 | continue; 58 | } 59 | 60 | path += "/" + p; 61 | auto zkret = zk_->existsSync(path); 62 | 63 | if(zkret.result == ZNONODE) { 64 | LOG(INFO) << "Creating directory: " << path; 65 | zkret = zk_->createSync(path, std::make_unique(), 66 | &ZOO_OPEN_ACL_UNSAFE, 0); 67 | CHECK(zkret.result == ZOK) 68 | << "failed to create path, code: " << zkret.result; 69 | } else { 70 | CHECK(zkret.result == ZOK) 71 | << "failed to read path, code: " << zkret.result; 72 | } 73 | } 74 | } 75 | 76 | void ZKLeader::leaderElect(int type, int state, std::string path) { 77 | 78 | const std::string baseElectionPath = 79 | zkUri_.path().toStdString() + "/election"; 80 | 81 | auto zkret = zk_->childrenSync(baseElectionPath, true); 82 | auto retcode = zkret.result; 83 | 84 | if(retcode == -1 || retcode == ZCLOSING || retcode == ZSESSIONEXPIRED) { 85 | LOG(ERROR) << "Zookeeper not ready|closing|expired socket [MYID: " << id_ 86 | << "] "; 87 | return; 88 | } 89 | 90 | if(!(retcode == ZOK || retcode == ZNONODE)) { 91 | LOG(ERROR) << "[MYID " << id_ << "] " 92 | << "No children: " << baseElectionPath 93 | << ", retcode: " << zkret.result; 94 | return; 95 | } 96 | 97 | std::set runningLeaders; 98 | 99 | for(auto &s : zkret.strings) { 100 | LOG(INFO) << "Running for election: [MYID: " << id_ << "] " << s 101 | << ", base path: " << path; 102 | runningLeaders.insert(extractIdFromEphemeralPath(s).get()); 103 | } 104 | 105 | if(!runningLeaders.empty()) { 106 | if(id_ >= 0) { 107 | if(runningLeaders.find(id_) == runningLeaders.end()) { 108 | CHECK(!isLeader_) 109 | << "Cannot be leader and then not leader. Doesn't work. exit"; 110 | isLeader_ = false; 111 | LOG(ERROR) << "Could not find my id [MYID: " << id_ 112 | << "]. out of sync w/ zookeeper"; 113 | } else if(id_ <= *runningLeaders.begin()) { 114 | LOG(INFO) << "LEADER! - ONE ID TO RULE THEM ALL: " << id_; 115 | if(!isLeader_) { 116 | // ONLY CALL ONCE 117 | leadercb_(this); 118 | } 119 | isLeader_ = true; 120 | } 121 | } 122 | } 123 | } 124 | 125 | void ZKLeader::zkCbWrapper(int type, 126 | int state, 127 | std::string path, 128 | ZKClient *cli) { 129 | LOG(INFO) << "Notification received[MYID: " << id_ 130 | << "]. type: " << ZKClient::printZookeeperEventType(type) 131 | << ", state: " << ZKClient::printZookeeperState(state) 132 | << ", path: " << path; 133 | 134 | if(id_ > 0) { 135 | leaderElect(type, state, path); 136 | } 137 | zkcb_(type, state, path, cli); 138 | } 139 | bool ZKLeader::isLeader() const { return isLeader_; } 140 | int32_t ZKLeader::id() const { return id_; } 141 | std::string ZKLeader::ephemeralPath() const { return electionPath_; } 142 | boost::optional 143 | ZKLeader::extractIdFromEphemeralPath(const std::string &path) { 144 | // zookeeper ephemeral ids are always guaranteed to end in 10 145 | // monotonically increasing digits by the C api - see zookeeper.h 146 | static const boost::regex zkid(".*(\\d{10})$"); 147 | boost::smatch what; 148 | 149 | if(boost::regex_search(path, what, zkid)) { 150 | return std::stoi(what[1].str()); 151 | } 152 | 153 | return boost::none; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/ZKLeader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "bolt/zookeeper/ZKClient.hpp" 7 | namespace bolt { 8 | class ZKLeader { 9 | public: 10 | // utility functions 11 | static boost::optional 12 | extractIdFromEphemeralPath(const std::string &path); 13 | 14 | // Note that this is a very simple leader election. 15 | // it is prone to the herd effect. Since our scheduler list will be small 16 | // this is considered OK behavior. 17 | ZKLeader(folly::Uri zkUri, 18 | std::function leaderfn, 19 | std::function zkcb); 20 | bool isLeader() const; 21 | int32_t id() const; 22 | std::string ephemeralPath() const; 23 | const folly::Uri &uri() const; 24 | std::shared_ptr client() const; 25 | 26 | private: 27 | void zkCbWrapper(int type, int state, std::string path, ZKClient *); 28 | void touchZKPathSync(const std::string &path); 29 | void leaderElect(int type, int state, std::string path); 30 | folly::Uri zkUri_; 31 | std::function leadercb_; 32 | std::function zkcb_; 33 | std::atomic isLeader_{false}; 34 | int32_t id_{-1}; 35 | std::shared_ptr zk_; 36 | std::string electionPath_; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/zookeeper_utils.cc: -------------------------------------------------------------------------------- 1 | #include "bolt/zookeeper/zookeeper_utils.hpp" 2 | #include 3 | #include 4 | 5 | namespace bolt { 6 | void failFastOnZooKeeperGet(int rc) { 7 | static std::set failstatus{ 8 | ZNOAUTH, ZBADARGUMENTS, ZINVALIDSTATE, ZMARSHALLINGERROR}; 9 | if(failstatus.find(rc) != failstatus.end()) { 10 | LOG(FATAL) << "Bad status for zookeeper get: " << rc; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/zookeeper_utils.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | namespace bolt { 3 | void failFastOnZooKeeperGet(int rc); 4 | } 5 | -------------------------------------------------------------------------------- /test/ZooKeeperHarness.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "bolt/testutils/SubprocessHarness.hpp" 5 | #include "bolt/zookeeper/ZKClient.hpp" 6 | namespace bolt { 7 | static const std::vector kZooEnv{ 8 | "ZOO_LOG4J_PROP=INFO,ROLLINGFILE", 9 | "CLASSPATH=/usr/share/java/jline.jar:/usr/share/java/log4j-1.2.jar:" 10 | "/usr/share/java/xercesImpl.jar:/usr/share/java/xmlParserAPIs.jar:" 11 | "/usr/share/java/netty.jar:/usr/share/java/slf4j-api.jar:" 12 | "/usr/share/java/slf4j-log4j12.jar:/usr/share/java/zookeeper.jar", 13 | "ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain"}; 14 | static const std::string kZooCmd = 15 | "java \"-Dzookeeper.log.dir=${ZOO_LOG_DIR} " 16 | " -Dzookeeper.root.logger=${ZOO_LOG4J_PROP}\" " 17 | " -cp ${CLASSPATH} ${ZOOMAIN} ${ZOOCFG}"; 18 | 19 | class ZooKeeperHarness : public SubprocessHarness { 20 | public: 21 | ZooKeeperHarness() : SubprocessHarness(kZooCmd, kZooEnv) { 22 | env_.push_back("ZOOCFG=" + tmpDir_ + "/zoo.cfg"); 23 | env_.push_back("ZOO_LOG_DIR=" + tmpDir_ + "/"); 24 | writeConfigFile(); 25 | } 26 | 27 | virtual void SetUp() { 28 | SubprocessHarness::SetUp(); 29 | zk = std::make_shared([](int, int, std::string, ZKClient *) {}); 30 | } 31 | virtual void TearDown() { 32 | zk = nullptr; 33 | SubprocessHarness::TearDown(); 34 | } 35 | 36 | virtual void writeConfigFile() { 37 | std::ofstream cfg(tmpDir_ + "/zoo.cfg"); 38 | // Documentation: http://goo.gl/m1n2jN 39 | // tickTIme is in millisecs. this is for testing 40 | // 41 | const std::string config("tickTime=10\n" 42 | "initLimit=10\n" 43 | "syncLimit=5\n" 44 | "traceFile=" + tmpDir_ + "/tracefile.log\n" 45 | "dataDir=" + tmpDir_ 46 | + "/data\n" 47 | "clientPort=2181\n"); 48 | cfg.write(config.c_str(), config.size()); 49 | cfg.close(); 50 | } 51 | std::shared_ptr zk; 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /test/ZooKeeperLeaderElectionHarness.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "bolt/testutils/SubprocessHarness.hpp" 6 | #include "bolt/zookeeper/ZKLeader.hpp" 7 | 8 | namespace bolt { 9 | static const std::vector kZooEnv{ 10 | "ZOO_LOG4J_PROP=INFO,ROLLINGFILE", 11 | "CLASSPATH=/usr/share/java/jline.jar:/usr/share/java/log4j-1.2.jar:" 12 | "/usr/share/java/xercesImpl.jar:/usr/share/java/xmlParserAPIs.jar:" 13 | "/usr/share/java/netty.jar:/usr/share/java/slf4j-api.jar:" 14 | "/usr/share/java/slf4j-log4j12.jar:/usr/share/java/zookeeper.jar", 15 | "ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain"}; 16 | static const std::string kZooCmd = 17 | "java \"-Dzookeeper.log.dir=${ZOO_LOG_DIR} " 18 | " -Dzookeeper.root.logger=${ZOO_LOG4J_PROP}\" " 19 | " -cp ${CLASSPATH} ${ZOOMAIN} ${ZOOCFG}"; 20 | class ZooKeeperLeaderElectionHarness : public SubprocessHarness { 21 | public: 22 | ZooKeeperLeaderElectionHarness() : SubprocessHarness(kZooCmd, kZooEnv) { 23 | env_.push_back("ZOOCFG=" + tmpDir_ + "/zoo.cfg"); 24 | env_.push_back("ZOO_LOG_DIR=" + tmpDir_ + "/"); 25 | writeConfigFile(); 26 | } 27 | 28 | virtual void SetUp() { 29 | SubprocessHarness::SetUp(); 30 | 31 | for(auto i = 0u; i < 3; ++i) { 32 | leaders.push_back(std::make_shared( 33 | zkUri, [](ZKLeader *) { LOG(INFO) << "harness leader cb"; }, 34 | [](int type, int state, std::string path, ZKClient *cli) { 35 | LOG(INFO) << "harness zoo cb"; 36 | })); 37 | } 38 | 39 | bool haveLeader = false; 40 | int maxTries = 100; 41 | 42 | while(!haveLeader) { 43 | for(auto &ptr : leaders) { 44 | if(ptr->isLeader()) { 45 | haveLeader = true; 46 | break; 47 | } 48 | } 49 | 50 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 51 | 52 | if(maxTries-- < 0) { 53 | break; 54 | } 55 | } 56 | 57 | CHECK(haveLeader) << "Could not get a single leader elected. FIXME NOW"; 58 | } 59 | virtual void TearDown() { 60 | leaders.clear(); 61 | SubprocessHarness::TearDown(); 62 | } 63 | 64 | virtual void writeConfigFile() { 65 | std::ofstream cfg(tmpDir_ + "/zoo.cfg"); 66 | // Documentation: http://goo.gl/m1n2jN 67 | // tickTIme is in millisecs. this is for testing 68 | // 69 | const std::string config("tickTime=3\n" 70 | "initLimit=10\n" 71 | "syncLimit=5\n" 72 | "traceFile=" + tmpDir_ + "/tracefile.log\n" 73 | "dataDir=" + tmpDir_ 74 | + "/data\n" 75 | "clientPort=2181\n"); 76 | cfg.write(config.c_str(), config.size()); 77 | cfg.close(); 78 | } 79 | folly::Uri zkUri{"zk:///bolt?host=localhost:2181"}; 80 | std::deque> leaders; 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /test/leader_election_tests/.gitignore: -------------------------------------------------------------------------------- 1 | zkclient_test 2 | zkleader_election_tests 3 | -------------------------------------------------------------------------------- /test/leader_election_tests/SConscript: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | Import('testing_libs') 4 | Import('env') 5 | Import('cxxflags') 6 | Import('path') 7 | Import('lib_path') 8 | e = env.Clone() 9 | prgs = e.Program( 10 | source = Glob('*.cc') 11 | ,CPPPATH = path 12 | ,LIBS = testing_libs 13 | ,LIBPATH = lib_path 14 | ,CCFLAGS = ' '.join(cxxflags)) 15 | Return('prgs') 16 | 17 | -------------------------------------------------------------------------------- /test/leader_election_tests/zkleader_election_tests.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "bolt/testutils/ZooKeeperLeaderElectionHarness.hpp" 7 | #include "bolt/utils/Random.hpp" 8 | 9 | using namespace bolt; 10 | 11 | TEST_F(ZooKeeperLeaderElectionHarness, ctor) { 12 | // pop the leader (first one) 13 | for(;;) { 14 | leaders.pop_front(); // allow for zk conn to close 15 | std::this_thread::sleep_for(std::chrono::milliseconds(5)); 16 | if(leaders.empty()) { 17 | break; 18 | } 19 | 20 | LOG(INFO) << "Leaders ID's left: " 21 | << std::accumulate( 22 | leaders.begin(), leaders.end(), std::string(), 23 | [](const std::string &a, std::shared_ptr b) { 24 | auto bstr = std::to_string(b->id()); 25 | return (a.empty() ? bstr : a + "," + bstr); 26 | }); 27 | 28 | 29 | LOG(INFO) << "Leaders left: " << leaders.size(); 30 | bool haveLeader = false; 31 | int maxTries = 100; 32 | 33 | while(!haveLeader) { 34 | for(auto &ptr : leaders) { 35 | if(ptr->isLeader()) { 36 | LOG(INFO) << "Found leader: " << ptr->id(); 37 | haveLeader = true; 38 | break; 39 | } 40 | } 41 | 42 | std::this_thread::yield(); 43 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 44 | 45 | if(maxTries-- < 0) { 46 | break; 47 | } 48 | } 49 | 50 | EXPECT_EQ(true, haveLeader); 51 | } 52 | } 53 | 54 | 55 | TEST_F(ZooKeeperLeaderElectionHarness, randomPopingOrder) { 56 | int maxNumberOfAdditions = 20; 57 | const auto kHalfOfLuck = std::numeric_limits::max() / 2; 58 | Random rand; 59 | for(;;) { 60 | if(rand.nextRand() > kHalfOfLuck) { 61 | leaders.pop_front(); 62 | } else { 63 | leaders.pop_back(); 64 | } 65 | // allow for zk conn to close 66 | std::this_thread::yield(); 67 | std::this_thread::sleep_for(std::chrono::milliseconds(5)); 68 | if(maxNumberOfAdditions-- > 0) { 69 | leaders.push_back(std::make_shared( 70 | zkUri, [](ZKLeader *) { LOG(INFO) << "testbody leader cb"; }, 71 | [](int type, int state, std::string path, ZKClient *cli) { 72 | LOG(INFO) << "testbody zoo cb"; 73 | })); 74 | std::this_thread::yield(); 75 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 76 | } 77 | 78 | if(leaders.empty()) { 79 | break; 80 | } 81 | 82 | LOG(INFO) << "Leaders ID's left: " 83 | << std::accumulate( 84 | leaders.begin(), leaders.end(), std::string(), 85 | [](const std::string &a, std::shared_ptr b) { 86 | auto bstr = std::to_string(b->id()); 87 | return (a.empty() ? bstr : a + "," + bstr); 88 | }); 89 | 90 | 91 | LOG(INFO) << "Leaders left: " << leaders.size(); 92 | bool haveLeader = false; 93 | int maxTries = 100; 94 | 95 | while(!haveLeader) { 96 | for(auto &ptr : leaders) { 97 | if(ptr->isLeader()) { 98 | LOG(INFO) << "Found leader: " << ptr->id(); 99 | haveLeader = true; 100 | break; 101 | } 102 | } 103 | 104 | std::this_thread::yield(); 105 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 106 | 107 | if(maxTries-- < 0) { 108 | break; 109 | } 110 | } 111 | 112 | EXPECT_EQ(true, haveLeader); 113 | } 114 | } 115 | 116 | TEST(ZookeeperLeaderEphemeralNode, id_parsing) { 117 | auto str = "asdfasdfasdf_70f7d1ad-6a4c-4ad4-b187-d33483ebd728_n_0000000002"; 118 | auto ret = ZKLeader::extractIdFromEphemeralPath(str); 119 | ASSERT_EQ(2, ret.get()); 120 | } 121 | 122 | TEST(ZookeeperLeaderEphemeralNode, id_parsing_bad_id) { 123 | auto str = "asdfasdfasdf_70f7d1ad-6a4c-4ad4-b187-d33483ebd728_asdfasdf"; 124 | auto ret = ZKLeader::extractIdFromEphemeralPath(str); 125 | ASSERT_EQ(boost::none, ret); 126 | } 127 | 128 | TEST(ZookeeperLeaderEphemeralNode, id_parsing_close_but_no_cigar) { 129 | // match hast to be exactly 10 digits as specified by zk api 130 | auto str = "asdfasdfasdf_70f7d1ad-6a4c-4ad4-b187-d33483ebd728_n_000000002"; 131 | auto ret = ZKLeader::extractIdFromEphemeralPath(str); 132 | ASSERT_EQ(boost::none, ret); 133 | } 134 | 135 | int main(int argc, char **argv) { 136 | bolt::logging::glog_init(argv[0]); 137 | testing::InitGoogleTest(&argc, argv); 138 | return RUN_ALL_TESTS(); 139 | } 140 | -------------------------------------------------------------------------------- /test/zkclient/.gitignore: -------------------------------------------------------------------------------- 1 | zkclient_test 2 | -------------------------------------------------------------------------------- /test/zkclient/SConscript: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | Import('testing_libs') 4 | Import('env') 5 | Import('cxxflags') 6 | Import('path') 7 | Import('lib_path') 8 | e = env.Clone() 9 | prgs = e.Program( 10 | source = Glob('*.cc') 11 | ,CPPPATH = path 12 | ,LIBS = testing_libs 13 | ,LIBPATH = lib_path 14 | ,CCFLAGS = ' '.join(cxxflags)) 15 | Return('prgs') 16 | 17 | -------------------------------------------------------------------------------- /test/zkclient/zkclient_test.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "bolt/zookeeper/ZKClient.hpp" 6 | #include "bolt/testutils/ZooKeeperHarness.hpp" 7 | 8 | using namespace bolt; 9 | 10 | TEST_F(ZooKeeperHarness, CanNotSetNodeThatDoesNotExist) { 11 | auto data = folly::IOBuf::copyBuffer("thingo", 6); 12 | auto result = zk->setSync("/foobar", std::move(data)); 13 | EXPECT_EQ(result.result, ZNONODE); 14 | } 15 | 16 | TEST_F(ZooKeeperHarness, CreateNodeThatDoesNotExist) { 17 | EXPECT_FALSE(zk->existsSync("/foobar").ok()); 18 | auto data = folly::IOBuf::copyBuffer("thingo", 7); 19 | auto result = zk->createSync("/foobar", std::move(data), &ZOO_OPEN_ACL_UNSAFE, 20 | ZOO_EPHEMERAL); 21 | EXPECT_TRUE(result.ok()); 22 | 23 | EXPECT_TRUE(zk->existsSync("/foobar").ok()); 24 | auto nodeTuple = zk->getSync("/foobar"); 25 | EXPECT_STREQ((char *)nodeTuple.data(), (char *)data->data()); 26 | } 27 | 28 | TEST_F(ZooKeeperHarness, SetNodeThatDoesExist) { 29 | auto data = folly::IOBuf::copyBuffer("thingo", 7); 30 | zk->createSync("/foobar", std::move(data), &ZOO_OPEN_ACL_UNSAFE, 31 | ZOO_EPHEMERAL); 32 | auto data2 = folly::IOBuf::copyBuffer("asdf", 5); 33 | auto result = zk->setSync("/foobar", std::move(data2)); 34 | EXPECT_TRUE(result.ok()); 35 | auto nodeTuple = zk->getSync("/foobar"); 36 | EXPECT_STREQ((char *)nodeTuple.data(), (char *)data2->data()); 37 | } 38 | 39 | TEST_F(ZooKeeperHarness, createNode) { 40 | auto data = folly::IOBuf::copyBuffer("thingo", 7); 41 | zk->createSync("/foobar", std::move(data), &ZOO_OPEN_ACL_UNSAFE, 42 | ZOO_EPHEMERAL); 43 | auto delResult = zk->delSync("/foobar"); 44 | EXPECT_TRUE(delResult.ok()); 45 | } 46 | 47 | int main(int argc, char **argv) { 48 | ::testing::FLAGS_gtest_death_test_style = "threadsafe"; 49 | google::InstallFailureSignalHandler(); 50 | google::InitGoogleLogging(argv[0]); 51 | testing::InitGoogleTest(&argc, argv); 52 | return RUN_ALL_TESTS(); 53 | } 54 | --------------------------------------------------------------------------------